Anyone want to write scripts using JavaScript?

An area for discussing new ideas and additions to Oolite.

Moderators: another_commander, winston

dajt
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 365
Joined: Tue Aug 17, 2004 7:05 am
Location: Orange, NSW, Australia

More progress

Post by dajt »

I have implemented nearly everything documented as available for plist/oos scripts. The few things not implemented explicitly in the JavaScript object model can be get at using a general-purpose escape mechanism which can call any Player/Ship method that takes no arguments or 1 argument.

I have redone LB's Asteroid Storm script in JavaScript and it works fine, and comes in at half the size (but so would the oos version). One genuine improvement was possible due to the new Initialise function that is only called once when the OXPs are loaded.

Now I'm looking at how to be able to add new equipment to the game via OXPs. I am working on a targeting computer enhancement that automatically targets the nearest hostile when you go into red alert mode.

If I add a EntityDestroyed event the equipment could be made to target the next hostile each time one is destroyed.

The new AlertConditionChanged event, and the Player.AlertCondition and Player.AlertFlags properties are the main scripting enhancements used here, although I am had to modify the game code to add a new function because there was no way to hook up the results of the scanForHostiles method to the addTarget method, plus getting the entity targeted.

This will be the main problem - it is easy to add stuff to the JavaScript OM, but there is heaps of stuff that needs to be exposed in a useful way from the game's engine and it will just be a matter of doing that piece by piece as people find they need things.

Anyway, here is an example of what can be done with the new scripts:

Code: Select all

this.Name = "AutoTarget"
this.Description = "Automatically targets the nearest hostile when entering red alert"
this.Version = "1.0"

// This event is called when the green/yellow/red alert status changes
this.AlertConditionChanged = function () {
	// If we're not in red alert don't do anything
	if (Player.AlertCondition != 3)
		return

	// Check if the new piece of equipment is installed (it is added to the game via the OXP's equipment.plist file)
	if (Player.HasEquipment("EQ_AUTO_TARGET") {
		// Check if hostiles are present - Player.AlertFlags property can be used to figure this out
		if (Player.AlertFlags & 0x200) {
			// Call new engine method to search for nearest hostile and target it
         // Notice the Player.Call method is used to call the new function because
         // it is not in the JavaScript object model yet
			Player.Call("targetNearestHostile")
		}
	}
}
Regards,
David Taylor.
User avatar
TGHC
---- E L I T E ----
---- E L I T E ----
Posts: 2157
Joined: Mon Jan 31, 2005 4:16 pm
Location: Berkshire, UK

Re: More progress

Post by TGHC »

Sounds excellent, I don't have a clue about scripting, but if it makes it easiere for people to write OXP's, brilliant.
dajt wrote:
I am working on a targeting computer enhancement that automatically targets the nearest hostile when you go into red alert mode.
Sounds like a really tasty bit of kit, my guess is that it's very expensive, and only available at Tech level 15.
The Grey Haired Commander has spoken!
OK so I'm a PC user - "you know whats scary? Out of billions of sperm I was the fastest"
User avatar
Captain Hesperus
Grand High Clock-Tower Poobah
Grand High Clock-Tower Poobah
Posts: 2312
Joined: Tue Sep 19, 2006 1:10 pm
Location: Anywhere I can sell Trumbles.....

Re: More progress

Post by Captain Hesperus »

TGHC wrote:
Sounds like a really tasty bit of kit, my guess is that it's very expensive, and only available at Tech level 15.
Or on Military vessels (Leviathans and Behemoths)

Captain Hesperus
"I have missile lock! No wait, the coffee machine is out of Coffee Mate!"
User avatar
stevesims
Dangerous
Dangerous
Posts: 78
Joined: Wed Jun 23, 2004 4:07 am
Location: London, England

Post by stevesims »

Hey Dajt,

Excellent work! Using JavaScript for OXP seems to me to be a fantastic idea and could allow for some very interesting possibilities.

I've only a few quick comments.

Back in the mists of time when scripting was new one of the first problems that was run into was mission variables getting clobbered by other scripts. IIRC this showed up in the first non-Giles OXP, since the plist script for that one was initially a direct copy of Giles' first script. (I think both scripts had used "MISSION_STATUS".)

The solution that was adopted was to just try to make sure that mission variables don't overlap between OXPs. This has mostly worked OK, but it's always been something that can cause problems.

I'd suggest that when storing the MissionVars that the value of this.Name could be used as a prefix to effectively define the namespace. Existing style mission variables could be accessed instead through GlobalMissionVars, thus allowing for compatibility with plist style OXPs.

As for your ideas concerning ListenForKey and this.KeyPressed - this is very cool indeed. I'd be a bit concerned though about using keycodes so directly here. It seems to me that keycodes should really be defined within keyconfig.plist files. OXPs could provide their own keyconfig.plist files to add a key definition. IMHO ListenForKey and this.KeyPressed should really take a string for the key name.

Ideally there should be an order of precedence for keys, looking at the keyconfig.plist in the AddOns folder first, then all those provided by OXPs, then finally the built-in definitions. This could of course be managed at load time.

Actually just looking at the keyconfig.plist file I find that there's still a key defined for the emergency hyperdrive. Shouldn't be too hard to re-implement that with a JavaScript OXP. :-)
dajt
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 365
Joined: Tue Aug 17, 2004 7:05 am
Location: Orange, NSW, Australia

Post by dajt »

Funny thing about the mission vars. I couldn't remember if it was a feature or a bug that missions could access each other's vars so just made sure the new system worked like the old.

I am going to leave it as-is, I think most people can find their way around the problem of duplicate var names and the vars have to be shared between the OXP scripts and other scripts like ship death actions etc (this is something Asteroid Storm does that I hadn't thought of).

I'll have a look at your idea about using symbolic key names from keyconfig.plist rather than key code numbers. I'm not sure what it gains (there can still be conflicts) but it sort of feels like the right thing to do now you mention it.

Thanks for the ideas.
Regards,
David Taylor.
Brianetta
Competent
Competent
Posts: 47
Joined: Sat Jan 27, 2007 5:02 pm
Location: Newcastle upon Tyne, UK
Contact:

Post by Brianetta »

Could such a script use a database? If so, it opens a whole world of possibility for pseudo-multiplayer features. News articles, universal events, an actual economy... it'd be interesting.
PGP fingerprint: E66A 9D58 AA10 E967 41A6 474E E41D 10AE 082C F3ED
dajt
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 365
Joined: Tue Aug 17, 2004 7:05 am
Location: Orange, NSW, Australia

Post by dajt »

I have posted a Win32 installer for the latest code from the "javascript" branch. Get it from the Oolite-PC site: https://developer.berlios.de/project/sh ... up_id=2999

This has the basic JavaScript scripting ability, which should be able to reproduce any existing OXP's main script with the exception that it does not allow multiple mission scripts in a single file. This feature should not be necessary within any single OXP.

Some preliminary documentation for writing scripts in JavaScript can be found at:
http://wiki.alioth.net/index.php/Oolite ... pt_Scripts
http://wiki.alioth.net/index.php/Oolite ... bjectModel

The "listen for keypress" feature is broken at present due to having lost some of the JavaScript integration code when some file renames came across from the trunk. The SVN docs say you can safely rename files, so I must have done it wrong :-(

This version also includes the initial equipment damage work I've been doing.

In answer to the database question, it is an interesting idea but not something I have time to do. It would be a pretty well isolated piece of work if anyone wants to have a go at it.

I'd like this code to make it into the main game one day, which is why it hasn't been done in the textured-planets branch. Textured-planets users should be able to save their oolite.exe and libnoise.dll files and put them back into this one's installation directory to go back to it. If this code ever gets finished and into the trunk, it can then be merged into textured-planets.

If you want to try this, I suggest you back up your current installation (zipping it up should work fine), then copy you saved game files somewhere else AGAIN, then uninstall the current version and install this one.

Here is LittleBear's Asteroid Storm! script translated into JavaScript. You can drop this in the OXP's Config directory and get rid of the existing script.plist and everything should still work.

Code: Select all

this.Name = "AsteroidStorm"
this.Description = "A large asteroid heads for a well-known station!"
this.Version = "1.0"

// This is called from a couple of places, so put into a common function
function removeStation() {
	// Setting info for a specific planet has to be done the "old" way via the Player.Call method until I have added the facility to get an arbitrary system object to the object model.
	Player.Call("setSpecificPlanetInfo", "0=55=station=none")
	Player.Call("setSpecificPlanetInfo", "0=55=description=The planet Leesti is reasonably fabled for Zero-G cricket and Leestiian evil juice, but cursed by deadly asteroid storms. Leesti's orbital station was recently destroyed by a large asteroid strike. Ships without planet landing capabilities are therefore advised to avoid this system.")
}

this.Initialise = function () {
	// Remove the station if it was blown up in a previous session
	if (MissionVars["mission_asteroids_statdead"] == 1)
		removeStation()
}

this.STATUS_DOCKED = function () {
	if (!DockedAtMainStation)
		return

	if (GalaxyNumber == 0 && PlanetNumber == 55) {
		if (MissionVars["mission_asteroids"] == undefined && Player.Score > 63) {
			Mission.ImageFilename = ""
			Mission.ShowMissionScreen()
			Mission.ShowShipModel("ast1")
			Mission.MusicFilename = "warning.ogg"
			Mission.MissionScreenTextKey = "asteroidsbrief_1"
			MissionVars["mission_asteroids"] = "B"
			MissionVars["mission_asteroids_statdead"] = 0
			MissionVars["mission_asteroids_rockdead"] = 0
			MissionVars["mission_asteroids_toldoff"] = 0
			MissionVars["mission_asteroids_final"] = 0
			return
		}

		if (MissionVars["mission_asteroids"] == "R" && MissionVars["mission_asteroids_toldoff"] == 0) {
			Mission.ImageFilename = "GalCop.PNG"
			Mission.ShowMissionScreen()
			Mission.MusicFilename = "warning.ogg"
			Mission.MissionScreenTextKey = "asteroidsbrief_2"
			MissionVars["mission_asteroids_toldoff"] = 1
			return
		}
	}

	if (MissionVars["mission_asteroids_final"] == 0) {
		if (MissionVars["mission_asteroids"] == "C") {
			Mission.ImageFilename = "GalCop.PNG"
			Mission.ShowMissionScreen()
			Mission.MissionScreenTextKey = "asteroidsbrief_3"
			Player.Credits = Player.Credits - 200
			Player.LegalStatus = Player.LegalStatus + 25
			MissionVars["mission_asteroids"] = "F"
			MissionVars["mission_asteroids_final"] = 1
			return
		}

		if (MissionVars["mission_asteroids"] == "asteroids_OVER") {
			if (MissionVars["mission_asteroids_statdead"] == 1) {
				Mission.ImageFilename = "GalCop.PNG"
				Mission.ShowMissionScreen()
				Mission.MissionScreenTextKey = "asteroidsbrief_4"
				Player.Credits = Player.Credits + 200
				MissionVars["mission_asteroids"] = "F"
				MissionVars["mission_asteroids_final"] = 1 // I am guessing this should have been done in the original script
				return
			}

			if (MissionVars["mission_asteroids_statdead"] == 0 && MissionVars["mission_asteroids_rockdead"] == 1) {
				Mission.ImageFilename = "GalCop.PNG"
				Mission.ShowMissionScreen()
				Mission.MissionScreenTextKey = "asteroidsbrief_5"
				Player.Credits = Player.Credits + 1000
				Player.LegalStatus = 0
				Player.AwardEquipment("EQ_ENERGY_BOMB")
				MissionVars["mission_asteroids"] = "F"
				MissionVars["mission_asteroids_final"] = 1
				return
			}
		}
	}
}

this.STATUS_LAUNCHING = function () {
	if (MissionVars["mission_asteroids"] == "B") {
		Player.RemoveEquipment("EQ_ENERGY_BOMB")
		Universe.AddSystemShips("ast1", 30, 1.0)
		Universe.AddSystemShips("badrock", 1, 1.0)
		MissionVars["mission_asteroids"] = "R"
		MissionVars["mission_asteroids_toldoff"] = 0
		return
	}

	if (MissionVars["mission_asteroids"] == "R") {
		Player.RemoveEquipment("EQ_ENERGY_BOMB")
		MissionVars["mission_asteroids_toldoff"] = 0
		return
	}
}

this.STATUS_EXITING_WITCHSPACE = function () {
	if (MissionVars["mission_asteroids"] == "R") {
		MissionVars["mission_asteroids"] = "C"
		MissionVars["mission_asteroids_statdead"] = 1
		removeStation()
	}
}
Regards,
David Taylor.
User avatar
CaptKev
---- E L I T E ----
---- E L I T E ----
Posts: 519
Joined: Fri Jan 26, 2007 3:21 pm
Location: Shropshire, UK

Post by CaptKev »

Great work, dajt.

Everything install okay, time to start work on writing an OXP. :)

Can local variables be used?

Code: Select all

var = GalaxyNumber * 256 + PlanetNumber
if (var & 0x10) {
   System.AddMoon("moon1")
}
dajt
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 365
Joined: Tue Aug 17, 2004 7:05 am
Location: Orange, NSW, Australia

Post by dajt »

Yes, you can use local vars, functions, etc. All JavaScript language features are available.

Local vars won't hold their value between two different invocations of the script.
Regards,
David Taylor.
User avatar
CaptKev
---- E L I T E ----
---- E L I T E ----
Posts: 519
Joined: Fri Jan 26, 2007 3:21 pm
Location: Shropshire, UK

Post by CaptKev »

Hi dajt, I'm having a bit of a problem with the javascript which I hope you can help me with. I want to have moons and gas giants in each system, but want them to be static and not randomly generated.

So I'm using the first four bits to hold the moon info and the last four to hold the gas giant info, e.g. 0x13 = 1 gas giant and two moons, but I'm getting "system_info is not defined" in the addMoons function.

Code: Select all

this.Initialise = function () {

	var system_info = new Array(0x01, 0x03, 0x00, 0x07, 0x00, 0x02, 0x01, 0x03, 0x02, 0x00...)
}

function addMoons() {

	if (system_info[PlanetNumber] & 0x01) {

		System.AddMoon("moon1")
	}
	if (system_info[PlanetNumber] & 0x02) {

		System.AddMoon("moon2")
	}
}
Download Fighter HUD, Stingray and System Redux from the EliteWiki
User avatar
CaptKev
---- E L I T E ----
---- E L I T E ----
Posts: 519
Joined: Fri Jan 26, 2007 3:21 pm
Location: Shropshire, UK

Post by CaptKev »

Silly me! :oops:, the array should be local to the addMoons function.
Download Fighter HUD, Stingray and System Redux from the EliteWiki
dajt
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 365
Joined: Tue Aug 17, 2004 7:05 am
Location: Orange, NSW, Australia

Post by dajt »

You have defined system_info to be local to the Initialise function. You need to move its declaration outside of that so the whole script can see it:

Code: Select all

var system_info = new Array(0x01, 0x03, 0x00, 0x07, 0x00, 0x02, 0x01, 0x03, 0x02, 0x00...);

this.Initialise = function () {
    // anything else you need to do in here...
}

function addMoons() {

   if (system_info[PlanetNumber] & 0x01) {
      System.AddMoon("moon1")
   }
   if (system_info[PlanetNumber] & 0x02) {
      System.AddMoon("moon2")
   }
}
As an alternative to hard-coding the array like that you could try using some permutation of the galaxy and planet numbers to come up with a value for each system and generate the extra stuff with that. Of course, this may be exactly what you're trying to avoid when you say you want the values to be static and not random!
Regards,
David Taylor.
User avatar
CaptKev
---- E L I T E ----
---- E L I T E ----
Posts: 519
Joined: Fri Jan 26, 2007 3:21 pm
Location: Shropshire, UK

Post by CaptKev »

Thanks dajt, I've made it gobal now!

I want to add moons and other planets to each system, so if I'm in a system it's always the same planet and moon combination.

BTW, great job with the javascript :D , will the javascript version include the textured planets branch at some point?
Download Fighter HUD, Stingray and System Redux from the EliteWiki
dajt
Quite Grand Sub-Admiral
Quite Grand Sub-Admiral
Posts: 365
Joined: Tue Aug 17, 2004 7:05 am
Location: Orange, NSW, Australia

Post by dajt »

Textured planets is dead - apparently there are better ways to get the same, or better, results.

I know you want the same planets and moons to appear in a system each time. The galaxy_number and planet_number values will always be the same in a given system so if you munge them somehow you'll always get the same number - this is basically how the galaxy and planet generation in Elite/Oolite work.

If you have specific plans for specific planets that sort of scheme falls apart. Textured-planets actually has this problem - if a planet's description includes any hint about its appearance it will very likely disagree with the texture generated for that planet.
Regards,
David Taylor.
User avatar
Arexack_Heretic
Dangerous Subversive Element
Dangerous Subversive Element
Posts: 1878
Joined: Tue Jun 07, 2005 7:32 pm
Location: [%H] = Earth surface, Lattitude 52°10'58.19"N, longtitude 4°30'0.25"E.
Contact:

Post by Arexack_Heretic »

heey great!

:D

finally normal calculus in scripting, yaaaay!
now to learn yet another code-language. :sighs:
Riding the Rocket!
Post Reply