Page 1 of 3

Anyone want to write scripts using JavaScript?

Posted: Tue Jan 09, 2007 9:11 am
by dajt
Hi,

As a fun hack I've embedded the Mozilla SpiderMonkey scripting engine into Oolite. It seems to work, so now I need to know if anyone would use it.

Anyone who's written an OXP is probably already comfortable with the PLIST scripting, but it's always bugged the hell out of me. OOScript was an attempt to improve the life of scripters but failed because (a) the script parser was broken until recently and (b) it could only do what the PLIST scripts could do - it was just syntactic sugar.

My first goal with the JavaScript engine would be to replicate what is possible with the existing PLIST scripts. This means I'll have to implement all the scripting commands and properties in the JavaScript object model. This will be a very big job.

I don't want to waste my time if everyone is happy with the PLIST scripts. So, would you OXP authors rather write your scripts in JavaScript or are you happy to stay as you are?

Here are some advantages of using JavaScript (my opinion, of course):

* Can use "or" as well as "and" in conditions (PLIST scripts can only do "and") eg

Code: Select all

if (PlanetNumber == 23 || PlanetNumber == 45) {
...
}
* Can define names as symbolic "constants" (they're not constant) eg

Code: Select all

var StartPlanet = 7;
...
if (PlanetNumber == StartPlanet) {
    ...
}
* Can do maths using more normal expressions rather than the limited set of operations provided by the PLIST scripts that have to be done one per line eg:

Code: Select all

var result = a * (b + c) / e;
I know most of you are not coders, so JavaScript may actually be harder for you to learn and use than the PLIST scripts. Although I'd be surprised if someone coming from scratch doing mostly copy-and-paste development didn't find it about the same difficulty, and advanced scripters would find a world of improvement.

The second stage of development would extend the object model available to scripts if we can think of things that are useful.

The third stage would be to modify the game engine so that OXP scripts could create new stuff that act as native game objects, like new weapons or equipment. I'm not clear on how that could be done (it may be too big a job).

Here is the dinky OXP script I'm testing with at the moment. You don't have to define event handlers for all the states - if you don't define one for say STATUS_LAUNCHING then your script doesn't get run while the player is in that state.

Don't worry about the crazy syntax for defining the event handlers. That will always stay the same (unless I can find a way to simplify it). You don't need to understand what it does, just copy it. Any functions you write to call from the event handlers will look much more understandable.

There is a ready made object called "Universe" which will have most of the stuff you do in scripts, like AddShips, AddMoon, etc, and references to other objects like PlayerEntity. GalaxyNumber and PlanetNumber are pre-supplied global variables that are always available. Other common variables will be available the same way. Mission variables will be held in an array so will be accessed like MissionVars["myVar"] = 10;

Code: Select all

this.Name = "testoxp";
this.Description = "A test OXP written using JavaScript";
this.Version = "1.0";

this.STATUS_DOCKED = function () {
	Universe.Log(Name + ".STATUS_DOCKED");
}

this.STATUS_LAUNCHING = function () {
	var pe = Universe.PlayerEntity;
	Universe.Log(Name + ".STATUS_LAUNCHING " + pe.CommanderName + " has launched in a " + pe.ShipDescription + " into galaxy " + GalaxyNumber + ", planet " + PlanetNumber);
}

this.STATUS_EXITING_WITCHSPACE = function () {
	Universe.Log(Name + ".STATUS_EXITING_WITCHSPACE into galaxy " + GalaxyNumber + ", planet " + PlanetNumber);
}

this.STATUS_IN_FLIGHT = function () {
	Universe.Log(Name + ".STATUS_IN_FLIGHT in galaxy " + GalaxyNumber + ", planet " + PlanetNumber);
}

Posted: Tue Jan 09, 2007 10:34 am
by johnsmith
Wow, this could be huge. I've found myself tearing my hair out over "or" conditions, and just cursing plists in general. Plists are quite hard to debug too. With javascript, can we do things like write to the filesystem? This for example could mean a global change in market prices following an event using commodities.plist (or is this file only read once at startup?).

That said, firstly I don't know javascript very well, and secondly, I don't have that much time at the moment. But it does sound exciting.

Posted: Tue Jan 09, 2007 10:55 am
by Dr. Nil
:D

I like the idea a lot.

Posted: Tue Jan 09, 2007 11:24 am
by dajt
johnsmith wrote:
With javascript, can we do things like write to the filesystem? This for example could mean a global change in market prices following an event using commodities.plist (or is this file only read once at startup?).
I don't plan to allow writes to the filesystem or the modification of default game files.

Perhaps mission vars would be a way of persisting changes - when the OXP is loaded back up it reinitialises certain game data structures (when more of the engine is exposed) based on its mission var values. Clumsy, but it might work while a better way is being designed.

Posted: Tue Jan 09, 2007 11:16 pm
by DaddyHoggy
I like javascript more than I like plists!

Took me a while and a lot of reading and quite a lot of checking of other people's oxps to realise that something as simple as "or" wasn't actually available :oops:

If you're crazy enough to take this on then you can certainly count me in - having done some javascript, matlab, tcl/tk <shudders> MS VB </shudders> I'd certainly appreciate something a bit more "language-y" (sorry that's not a real word but you catch my drift I hope).

Not taking their names in vain but I'm sure even super scripters like LittleBear (for size) and Dr. Nil (for speed and turn-over) would appreciate an alternative and a way of being more clever with oxp content.

Progress report

Posted: Thu Jan 11, 2007 10:51 am
by dajt
Things are going well. I have a good amount of the standard scripting methods available now, including mission screens.

JavaScript-based OXPs now co-exist alongside OOS and PLIST OXPs (which are actually the same thing), and can share mission variables with them. When you get/set a mission variable in JavaScript the value actually comes from/is set in the standard mission_variables array. The OXP structure is the same except it has a Config/script.js file rather than a Config/script.plist file.

The JavaScript OXPs live in the same list as the others, get checked at the same time, and should basically work the same.

I'll start writing up the object model on the wiki, and when I have it so that all current OXP functionality is possible with JavaScript I'll post a WIN32 build. I can look at a linux build, but I'll need someone else to do the Mac build for me.

EDIT: Object model documentation here.

I've translated the trumbles mission to JavaScript as an example:

Code: Select all

this.Name = "Trumbles"
this.Description = "The trouble with trumbles"
this.Version = "1.0"

function d100() { return Math.Random() * 100 }

this.STATUS_DOCKED = function () {
	if (MissionVars["trumbles"] == undefined) {
		if (DockedAtMainStation == true && Player.Credits > 65535.5) {
			MissionVars["trumbles"] = "BUY_ME"
			Mission.ResetMissionChoice()
		} else
			return
	}

	if (MissionVars["trumbles"] == "BUY_ME" && d100() < 20 && Mission.Choice == "") {
		Mission.ShowShipModel("none")
		Mission.ImageFilename = "trumblebox.png"
		Mission.ShowMissionScreen()
		Mission.MissionScreenTextKey = "trumble_offer"
		Mission.ChoicesKey = "trumble_offer_yesno"
		MissionVars["trumbles"] = "OFFER_MADE"
	}

	if (MissionVars["trumbles"] == "OFFER_MADE" && Mission.Choice != "") {
		Mission.ImageFilename = "none"

		if (Mission.Choice == "YES") {
			MissionVars["trumbles"] = "TRUMBLE_BOUGHT"
			Player.Credits -= 30
			Player.AwardEquipment("EQ_TRUMBLE")
		} else {
			MissionVars["trumbles"] = "NOT_NOW"
		}

		Mission.ResetMissionChoice()
  	}
}

this.STATUS_EXITING_WITCHSPACE = function () {
	if (MissionVars["trumbles"] == "NOT_NOW" && d100() < 2) {
		MissionVars["trumbles"] = "BUY_ME"
		Mission.ResetMissionChoice()
	}
}
For comparison, here is how it looks as a PLIST:

Code: Select all

    trumbles = (
        {
            conditions = (
                "dockedAtMainStation_bool equal YES", 
                "mission_trumbles undefined", 
                "mission_novacount undefined",
				"credits_number greaterthan 6553.5"
            ); 
            do = ("set: mission_trumbles BUY_ME"); 
        }, 
        {
            conditions = (
                "status_string equal STATUS_DOCKED", 
                "mission_trumbles equal BUY_ME", 
                "d100_number lessthan 20", 
                "missionChoice_string undefined"
            ); 
            do = (
				"showShipModel: none",
                "setMissionImage: trumblebox.png", 
                setGuiToMissionScreen,
                "addMissionText: trumble_offer", 
                "setMissionChoices: trumble_offer_yesno",
                "set: mission_trumbles OFFER_MADE"
            ); 
        }, 
        {
            conditions = (
                "status_string equal STATUS_DOCKED", 
                "mission_trumbles equal OFFER_MADE", 
                "missionChoice_string equal YES"
            ); 
            do = (
            	resetMissionChoice,
                setGuiToStatusScreen, 
                "setMissionImage: none", 
                "set: mission_trumbles TRUMBLE_BOUGHT", 
                "awardCredits: -30", 
                "awardEquipment: EQ_TRUMBLE"
            ); 
        }, 
        {
            conditions = (
                "status_string equal STATUS_DOCKED", 
                "mission_trumbles equal OFFER_MADE", 
                "missionChoice_string equal NO"
            ); 
            do = (
            	resetMissionChoice,
            	setGuiToStatusScreen,
                "setMissionImage: none", 
            	"set: mission_trumbles NOT_NOW"
            ); 
        }, 
        {
            conditions = (
                "status_string equal STATUS_EXITING_WITCHSPACE", 
                "mission_trumbles equal NOT_NOW", 
                "d100_number lessthan 2"
            ); 
            do = ("set: mission_trumbles BUY_ME", resetMissionChoice); 
        }
    );

Posted: Thu Jan 11, 2007 11:14 am
by TGHC
Clear as mud to me, I'm so glad you guys know what you are doing.

Just one point, IIRC someone said that javascript is for PC only, if so it would be a shame. Perhaps it might persuade Mac users to buy a PC.

(Dives into Nuclear Bunker)

Posted: Thu Jan 11, 2007 11:41 am
by dajt
The JavaScript engine I'm using is the same one used in FireFox - it should be fine on a Mac. The problem is I can't do a Mac build because I don't have a Mac.

Re: Progress report

Posted: Thu Jan 11, 2007 12:42 pm
by Dr. Nil
"(make it read-write?)"

Anything that could be made read-write should be so IMO. :)
dajt wrote:
I've translated the trumbles mission to JavaScript as an example:
I look forward to learning some Java.

Posted: Thu Jan 11, 2007 1:26 pm
by Star Gazer
If none of the younger contributors is up for it, I will take on building the Mac version - I may well have a lot more free time in the future - I may be getting early retirement, just in time for my bus pass in March!!

Posted: Thu Jan 11, 2007 2:05 pm
by DaddyHoggy
@dajt - the side by side comparison of trumbles javascript and plist is a good one.

It looks good - I look forward to a proper release and well done for doing the documentation too!

Posted: Thu Jan 11, 2007 6:38 pm
by TGHC
Star Gazer wrote:
If none of the younger contributors is up for it,
That would be everybody on this BBS then, even me!!! Can't remember the last time I was referred to as "younger"

That aside, good on you SG, can't keep a good man down :)

Posted: Thu Jan 11, 2007 9:33 pm
by dajt
Star Gazer wrote:
If none of the younger contributors is up for it, I will take on building the Mac version - I may well have a lot more free time in the future - I may be getting early retirement, just in time for my bus pass in March!!
That would be great, thanks! Unfortunately I can't give you any help getting the XCode project settings right or building the JS interpreter on the Mac. If someone would just buy me a Mac this wouldn't be a problem.

Regards,
David.

Progress report

Posted: Mon Jan 15, 2007 6:00 am
by dajt
Added two global functions today and also a way for OXPs to do their own setup processing at the start of the game.

Log(message)

Send a message to the game's log stream. This is equivalent to debugMessage, but there is no debugOn/debugOff yet.


ListenForKey(keycode)

If the given keycode is not already taken (see keyconfig.plist), the OXP's KeyPressed function will be called with the keycode if it is pressed while the player is flying (ie STATUS_INFLIGHT).

This is a first step at allowing first-class equipment items to be created entirely by OXPs.

Here is a sample usage:

Code: Select all

this.Name = "KeyExample"
this.Description = "How to capture keystrokes in an OXP"
this.Version = "1.0"

// This function is called at the beginning of the game so OXPs can do their own setup.
// The ListenForKey call says this OXP wants to hear when lower-case l is pressed.
this.Initialise = function () {
	Log("Initialising " + Name + " " + Version)
	ListenForKey(108)
}

// This function will be called when l is pressed while the player is flying around.
this.KeyPressed = function (keycode) {
	Log("Key " + keycode + " sent to  " + Name)
}

Re: Progress report

Posted: Mon Jan 15, 2007 4:22 pm
by Dr. Nil
dajt wrote:

ListenForKey(keycode)
:D Good job! Very useful.