just remembered: Shaky Drive by Astrobe causes one to wobble infuriatingly when the Torus Drive is employedphkb wrote: ↑Thu Jun 10, 2021 10:41 pmShould be.Cholmondely wrote: ↑Thu Jun 10, 2021 9:44 ami) Can BC's MFD really be made just to pop up, all on its own, without the player having to prime it?No. MFD's are only configurable via the HUD, and to change the colour we'd have to swap the entire HUD out.Cholmondely wrote: ↑Thu Jun 10, 2021 9:44 amii) Could the BC HUD be modified - change the colour, for exampleThe default HUD defines 2 MFD's, which should be visible when activated.Cholmondely wrote: ↑Thu Jun 10, 2021 9:44 amiii) Is my sneaky suspicion that the Vanilla HUD includes invisible MFDs built on anything other than quicksand?That's the fundamental question, I guess. For clarity, I was only planning on using BCC to host the response to the passenger; the actual message from the passenger I would either have in an MFD on it's own, or via a comms message (because we have to allow for the prospect that the player will have no free MFD slots left, so we would have to default back to a comms message where there is no other option).
The only other viable method I can think of, which would avoid BCC altogether, would be to create some prime-able equipment items that can be awarded to the ship when a response is required. The first of these items would have the textual equivalent of "Respond with a 'Yes'". The second would have "Respond with a 'No'". The "Yes" response would be automatically primed when given to the player, so all they have to do is activate it if they want to say "Yes". The "No" response could be defaulted after, say, 60 seconds without any response. Otherwise the player can manually prime the "No" response and activate that.
There's no response available to any of these comms messages. They're purely flavour text. I expanded on this concept in Enhanced Passenger Contracts.Cholmondely wrote: ↑Thu Jun 10, 2021 9:44 amAccording to the description it seems to generate random messages at various moments and also at changes to yellow alert or whatever.
Well, from looking at the JellyBabies dispenser, it was essentially overriding the Trumble image and actually giving the player Trumbles. And so you end up with Jelly Babies on the screen instead of Trumbles, but all the other charactistics of Trumbles would still be active (ie filling up the hold, unable to remove them, etc). Which might be a cute side-effect of having the bubble bath on board - but one I'd prefer to avoid if possible (as a pilot and a programmer!).Cholmondely wrote: ↑Thu Jun 10, 2021 9:44 amwe can do trumbles, we can do jelly-babies... can we do soap bubbles?
Hmm... curious. I'll have to play around with a couple of ideas here. Not sure if we can get precisely what you want, but it will be fun to experiment.Cholmondely wrote: ↑Thu Jun 10, 2021 9:44 amAnd a third edit: if OB was to offer the player some Soladean Evil Brandy, can one mimic the resulting inebriation in the ship's responses for a short time afterwards?
Long way round/Scourge of the Black Baron missions
Moderators: winston, another_commander
- Cholmondely
- Archivist
- Posts: 5366
- Joined: Tue Jul 07, 2020 11:00 am
- Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
- Contact:
Re: Long way round/Scourge of the Black Baron missions
•Missing OXPs? What do you think is missing?
•Lore: The economics of ship building How many built for Aronar?
•Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
- Cholmondely
- Archivist
- Posts: 5366
- Joined: Tue Jul 07, 2020 11:00 am
- Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
- Contact:
Re: Long way round/Scourge of the Black Baron missions
Failing dismally at mastering this Javascript gubbins. I don't want a step-by-step instruction as to what to do - I don't really learn anything worthwhile from that.
For example, the tutorial in fixing the SothisTC markets has left me still utterly unable to fix the DisoB, Biosphere & Galactic Navy markets. The different languages, the different styles in the oxp's etc do not seem to correlate with what I was taught for handling the SothisTC.
I work best from books. Having already searched the Oxford bookshops, I whizzed over to London last week and spent half a day scouring the better second-hand bookshops for old 2012 JavaScript manuals (and also for dictionaries of hieroglyphics!) and found nothing useful.
I know what I want to do with this oxp. But have no idea as to how to do it. I can see, for example, how to give the accept/reject mission choice in the dialogue box, but have no idea as to how to register and then deal with that choice once made.
The material on the wiki all seems to be written for someone who already knows how to programme. I have printed out several of the pages and reread them several times, but they are all mostly gobbledegook to me. I'm tempted to revert to the much simpler-looking legacy script.
Any advice?
•Missing OXPs? What do you think is missing?
•Lore: The economics of ship building How many built for Aronar?
•Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
- phkb
- Impressively Grand Sub-Admiral
- Posts: 4830
- Joined: Tue Jan 21, 2014 10:37 pm
- Location: Writing more OXPs, because the world needs more OXPs.
Re: Long way round/Scourge of the Black Baron missions
Well...
Cholmondely wrote: ↑Sun Jul 11, 2021 12:48 pmI don't want a step-by-step instruction as to what to do
Now that's a challenge! Haven't written a book before. Just really large OXP's...
Maybe we should start by breaking down what's currently in the Long Way Round mission, then we can nail down what changes we want to make, and then look at ways to implement said changes. This is going to be a long one, folks.
So, what's in the original mission pack?
The heart of the mission is in two files: missiontext.plist, and script.js. missiontext.plist doesn't have anything to do with the flow of the mission, merely holding mission text and descriptions. So, the majority of the functionality of the mission comes from the script file.
Before we look at the content of the script file, it's worth noting that the name of this file is significant. Oolite will automatically include in the world scripts pool any "script.js" file that is placed in the "Config" folder of an OXP. That means we don't need to do anything additional to have the script file included with all the other script files. If we wanted to organise out scripts and break them out in some way, we would need to have a "world-script.plist" file that specifically names all the JS files we're adding, and those JS files would need to be in the "Scripts" folder. But for now, given the simplicity of this mission, we can stick with the single "script.js" file.
The content of this file can be broken down into four main sections:
1. Header and preamble
2. Script-wide variation declarations
3. World event definitions
4. Custom JS routines.
We'll look at each of these in turn
1. Header and preamble
Code: Select all
"use strict";
this.name = "LongWayRound";
this.author = "aegidian, phkb";
this.copyright = "2018 phkb";
this.description = "Script for Long Way Round mission - original script by aegidian, JS script by phkb.";
this.licence = "CC BY-NC-SA 3.0";
(a)
"use strict";
tells the Javascript engine to enforce variable declarations and catches a few other common errors. Basically it's a way of making sure the code that follows passes some basic tests. (Edit to add: while it is not strictly necessary to include this like, it is highly recommended).(b)
this.name = "LongWayRound";
this line defines the name this script will have in the The other elements are useful, but not necessary for the functionality of the script.
2. Script-wide variable declarations.
Code: Select all
this._counter = 0;
this._timer = null;
"this._counter = 0;"
. Of note here is the prefix of "this." - any time you see this prefix on something, it is referring to something inside the script file. If that prefix is missing, then the JS engine will assume the reference is to something inside the current function it is executing. (It can get a lot more complicated that this, but we'll leave the discussion about context until later)."this._counter" is used later in the script to count time before spawning some enemies (which we'll get to later). It is being predefined with a value of 0 (zero), which causes the JS engine to recognise it as an integer number.
You can define variables with different types, based on the content of the variable. Javascript is, shall we say, "flexible" when it comes to variable types. It doesn't enforce strict type definitions. So, you can define a variable as an integer variable (like this._counter), but then later put a string value inside it (ie some value inside quotes), or even an complex object (we'll get to these later). Javascript is fine with that. Your program might not work, but JS will plough on regardless (if the code didn't just implode into a million random bits). JS is not a "type-safe" language. Which means you need to be careful about how your variables are defined and (more importantly) used.
Anyway, because we've defined this._counter as an integer, we can do arithmetic operations to it.
"this._timer" is going to be a reference or pointer to a timer object, but for now is just given a default value of "null", which in essence means it's pointing at nothing. (For non-programmers, there is a distinction between simple objects, like integers, and complex objects, like a timer. With a simple object, the variable is the value. In our example, "this._counter" equals zero. For a complex object, the variable is a pointer to the object, a way of accessing it, like a phone number is a way of accessing a person. Having a reference to an object is important, because otherwise there is no way to access it).
These variables are set up when the script is loaded, and before any of the scripts functions are executed.
3. World event definitions.
The rest of the script file contains functions, the first type being world event function definitions. World events are game-specific events that occur at pre-defined times, usually in response to some sort of game action or activity. To hook into one of these events, the first job is to decide which event you need to use. You can see a full list of all the different events and their function definitions on the wiki.
Once you have chosen the appropriate event, you can copy the definition from the wiki and paste it into your script file. Then, when the event takes place, Oolite will go through all the script files that have been loaded and run each of the event scripts that it finds.
For "Long way round", there are three world events being hooked into: "startUp", "missionScreenOpportunity", and "shipExitedWitchspace".
The first one, "startUp", is called after all OXP scripts have been loaded, either after a new game has been started, or a game file has been loaded. It's normally used to do any initialisation steps that should only be done once (for example, loading game file saved values). In our case, it's doing some cleanup:
Code: Select all
if (missionVariables.longwayround === "MISSION_COMPLETE") {
delete this.missionScreenOpportunity;
delete this.shipExitedWitchspace;
delete this.startUp;
}
The "missionVariables" object is a special object used by Oolite to save any information between sessions. Scripts can add anything they like to this object, but to ensure everything works smoothly, the types of things that should be saved are strings (ie any characters between quotation marks) and numbers. If you have a complex object which needs to be stored, like a dictionary or array (we'll get to those things later), OXP's usually convert them into a string. We can cover that later as well when we start needing to store more detailed info.
The next function is "missionScreenOpportunity". This function is called if there are no mission screens active and the player is docked at a station. It can fire multiple times, so any code inside here will need to make sure it can only run once.
In our example, the routine begins with this check:
Code: Select all
if (player.ship.dockedStation.isMainStation && galaxyNumber === 0) {
Code: Select all
if (!missionVariables.longwayround && system.ID === 3) {
if (missionVariables.longwayround == undefined...
). We're also checking to see if we're in system ID 3 (Biarge). If those two things are true, we then bring up the first mission page.
Code: Select all
mission.runScreen(
{
screenID: "longwayround",
title: "Incoming Message",
exitScreen: "GUI_SCREEN_STATUS",
messageKey: "long_way_round_Biarge_briefing"
}
);
missionVariables.longwayround = "STAGE1";
mission.markSystem({system:248, name:this.name});
mission.setInstructionsKey("em1_short_desc1", this.name);
return;
The mission screen definition is perhaps the most basic example of a mission screen, in that there are no options or anything complicated. We define a screen ID (which isn't used here, but it can be helpful for other OXP's to know), we put a title on it, we tell it where to exit to when the mission screen closes, and we tell it what key to use out of missiontext.plist to get the text to display.
After all these steps are done, we get to the "return;" statement. That will exit the function at that point, without executing any more code.
If, however, we've docked at a main station in a system other that Biarge, the code would fall through to the next IF statement:
Code: Select all
if (missionVariables.longwayround === "STAGE1" && system.ID === 248) {
Code: Select all
mission.runScreen(
{
screenID: "longwayround",
title: "Incoming Message",
exitScreen: "GUI_SCREEN_STATUS",
messageKey: "long_way_round_Soladies_briefing"
}
);
missionVariables.longwayround = "STAGE2";
player.credits += 500;
player.consoleMessage("You have been awarded 500cr.");
mission.unmarkSystem({system:248, name:this.name});
mission.markSystem({system:233, name:this.name});
mission.setInstructionsKey("em1_short_desc2", this.name);
return;
We'll assume the player has moved on to Qubeen and docked. That will trigger the third IF statement in this function:
Code: Select all
if (missionVariables.longwayround === "STAGE2" && system.ID === 233) {
Code: Select all
mission.runScreen(
{
screenID: "longwayround",
title: "Incoming Message",
overlay: "loyalistflag.png",
exitScreen: "GUI_SCREEN_STATUS",
messageKey: "long_way_round_Qubeen_briefing"
}
);
missionVariables.longwayround = "MISSION_COMPLETE";
player.credits += 2000;
player.consoleMessage("You have been awarded 2000cr.");
mission.unmarkSystem({system:233, name:this.name});
mission.setInstructionsKey(null, this.name);
delete this.shipExitedWitchspace;
delete this.missionScreenOppportunity;
return;
And that's the end of the missionScreenOpportunity function call.
We move on to the final world event in this mission, shipExitedWitchspace. This world event is called after the player arrives in a system and the tunnel effect has been shown. The system should be full populated by this time. In our function we're checking 3 things:
Code: Select all
if (galaxyNumber === 0 && system.ID === 233 && missionVariables.longwayround === "STAGE2") {
Code: Select all
this._counter = 0;
this._timer = new Timer(this, this.$addExtraShips, 1, 1);
system.addShips("longWay_rebel", 2);
Next, we start a timer. We keep a reference to the timer in
this._timer
, and anytime you create a timer, you should keep a reference to it in a similar fashion.You'll notice I said a "reference". A timer is a complex object. An integer or a string is a simple object - they have generally 1 value, and there is a limited number of things that can be done with them. Complex objects, like a timer, can have multiple values, all independent. Each value of an object is called a property. For a timer, there are 3 properties and 2 methods available. The properties are "interval" (the rate at which the timer repeats, in seconds), "isRunning" (a true/false value indicating whether the timer is running or not), and "nextTime" (the next time this timer will fire). And a timer has two methods: "start" and "stop". We'll get to these in a minute.
A timer is created by using the code "new Timer". Into this definition we can pass a number of properties: the first is always "this", which basically indicates where the timer will call home. It sets the context* of what "this" will refer to in the next property, being the function to call when the event fires. In our case, the function we're calling is "this.$addExtraShips". The next two properties are the seconds until the first firing, and the next is how often afterwards the event will fire. If we left off the last number, the event would only fire once.
(* A bit of an aside about context: Context in code is very important. As the Javascript executes the various functions, the execution happens inside a particular context. If something is referenced in that context that hasn't been defined, errors will occur. To use an example: in a group of friends, where "Fred" is present, someone might say, "Fred's shirt is so bright it's hurting my eyes." Now, if there was another group of people, but only one of them knows of Fred and his predilection for loud shirts, and this person says the same thing, no one in the group will understand. They don't have the right context to know who Fred is. So, the context in scope at the time a piece of code is run is very important. Normally, the context of our JS script files is the script file itself - which is why we can refer to "this" throughout the script. And if our code runs in isolation from other scripts, in that we don't need to reference or use anything from somewhere else, we can usually be confident the context will be our own script file. But, timers can throw this out of alignment if we don't set the context correctly when creating them).
Immediately after starting the timer, we add 2 ships with the role "longWay_rebel" to the system at the witchpoint. The witchpoint is the default position when no other parameters are given.
So, we've arrived in the system, and there are now two ships at the witchpoint who are probably trying to poke us with a sharply pointed laser at this point. Then, after 1 second passes, the timer fires, and we execute the "$addExtraShips" function. We now move onto the final section of code.
4. Custom JS routines.
Custom routines are created by the programmer to perform whatever functions are required. You can fill a script file with these functions, but none of them will be called without some trigger. In our case, the trigger is a timer firing and causing the function to execute. But they can also be called from world events, or ship scripts, or any number of other possibilities. The point is, while world event functions have a predefined structure, our custom functions can have whatever properties and parameters we want.
In our example, the structure is quite simple:
Code: Select all
this.$addExtraShips = function $addExtraShips() {
The first section of code in the function is doing a check to see if the player has died:
Code: Select all
if (player.ship.isValid === false || player.ship.isInSpace === false) {
this._timer.stop();
return;
}
But, assuming the player has managed to survive, we move onto the next piece of code:
Code: Select all
this._counter += 1;
if (this._counter < 60) {
if (system.countShipsWithPrimaryRole("longWay_rebel") < 5) {
if (Math.random() < 0.5) {
system.addShips("longWay_rebel", 1);
}
}
} else {
this._timer.stop();
}
If it's been more than 60 seconds, though, we tell the timer to stop. That's the bit after the "else" statement.
So, functionally, that's how the script currently works. The OXP also includes a ship definition for the longWay_rebel, with a texture to go with it.
There's more to follow, and I'll probably have some grammar/spelling issues to correct, but that's a start.
(Edited to add some clarity to some of the discussion points)
- Cholmondely
- Archivist
- Posts: 5366
- Joined: Tue Jul 07, 2020 11:00 am
- Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
- Contact:
Re: Long way round/Scourge of the Black Baron missions
Quick Question on your previous post:
Could one not write a "HUD override" which would be "activated" by the message, cause an MFD to pop up and flash in different colours and then allow the MFDs to revert to normal? Could it be programmed to happen during Green Alert?phkb wrote: ↑Thu Jun 10, 2021 10:41 pmNo. MFD's are only configurable via the HUD, and to change the colour we'd have to swap the entire HUD out.Cholmondely wrote: ↑Thu Jun 10, 2021 9:44 amii) Could the BC HUD be modified - change the colour, for example
Edited to add:
Gnievmir's Vimana HUD has warning signs that flash up in the middle of the viewscreen, quite independently of the MFDs, when Energy/Altitude/Shields etc are low - they are stored as .pdf's in the images folder of his oxp
•Missing OXPs? What do you think is missing?
•Lore: The economics of ship building How many built for Aronar?
•Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
- phkb
- Impressively Grand Sub-Admiral
- Posts: 4830
- Joined: Tue Jan 21, 2014 10:37 pm
- Location: Writing more OXPs, because the world needs more OXPs.
Re: Long way round/Scourge of the Black Baron missions
You could, but you would have to swap out the players current HUD to do it, which would be a bit disconcerting for the player.Cholmondely wrote: ↑Mon Jul 12, 2021 12:13 pmCould one not write a "HUD override" which would be "activated" by the message, cause an MFD to pop up and flash in different colours and then allow the MFDs to revert to normal? Could it be programmed to happen during Green Alert?
- phkb
- Impressively Grand Sub-Admiral
- Posts: 4830
- Joined: Tue Jan 21, 2014 10:37 pm
- Location: Writing more OXPs, because the world needs more OXPs.
Re: Long way round/Scourge of the Black Baron missions
I think it’s probably worth trying out some of the options that don’t require a HUD change, and other ways to alert the player, and use the HUD swap option as the last resort.
- Cholmondely
- Archivist
- Posts: 5366
- Joined: Tue Jul 07, 2020 11:00 am
- Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
- Contact:
Re: Long way round/Scourge of the Black Baron missions
Gnievmir's Vimana HUD has warning signs that flash up in the middle of the viewscreen, quite independently of the MFDs, when Energy/Altitude/Shields etc are low - they are stored as .pdf's in the images folder of his oxpphkb wrote: ↑Mon Jul 12, 2021 12:29 pmI should elaborate. Yes, it’s possible to create a custom HUD with an MFD in highlighter yellow in some prominent position. But HUD’s are quite personal things - I know I get annoyed when an OXP messes with my setup. Plus, there is the jarring nature of the change - anything we swap to is not going to look like the players current HUD, so why would the look of it change just to receive a message from another ship? Rather than having the story slip in quietly in the bounds of the players config, we toss them off the deep end with a shock change to everything they’re used to seeing.
I think it’s probably worth trying out some of the options that don’t require a HUD change, and other ways to alert the player, and use the HUD swap option as the last resort.
So a HUD override would really have to replace the entire HUD? It can't just add something to the display?
Edited to add:
I of course totally agree that changing the entire HUD is not the solution. I was merely hoping that one could add just the one element!
Incidentally, I presume that if one did change the entire HUD, that it would no longer be possible to change it back to the player's preferred after dear old OB had completed his peroration?
Re-edited to add:
And just to clarify, there is no way of using a mission screen once one is undocked (during "Green Alert", say)?
•Missing OXPs? What do you think is missing?
•Lore: The economics of ship building How many built for Aronar?
•Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
- hiran
- Theorethicist
- Posts: 2403
- Joined: Fri Mar 26, 2021 1:39 pm
- Location: a parallel world I created for myself. Some call it a singularity...
Re: Long way round/Scourge of the Black Baron missions
Currently we are in a mode where players choose their HUD.phkb wrote: ↑Mon Jul 12, 2021 12:29 pmI should elaborate. Yes, it’s possible to create a custom HUD with an MFD in highlighter yellow in some prominent position. But HUD’s are quite personal things - I know I get annoyed when an OXP messes with my setup. Plus, there is the jarring nature of the change - anything we swap to is not going to look like the players current HUD, so why would the look of it change just to receive a message from another ship? Rather than having the story slip in quietly in the bounds of the players config, we toss them off the deep end with a shock change to everything they’re used to seeing.
I think it’s probably worth trying out some of the options that don’t require a HUD change, and other ways to alert the player, and use the HUD swap option as the last resort.
How about doing it as it is done with cars, planes or just so much other machinery? The HUD is designed by the machine vendor and at most customizable to some degree. For me as a player the advantage would be we can see which ship a player is flying by seeing the HUD, and every ship would have some more differential taste to it. Today flying different ships is a bit too equal - at least for my taste...
- Cholmondely
- Archivist
- Posts: 5366
- Joined: Tue Jul 07, 2020 11:00 am
- Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
- Contact:
Re: Long way round/Scourge of the Black Baron missions
Some ship oxp's are like that. Have a good look at Captain Beatnik's "Coluber Racers": each of the four types comes with its own HUD, as do some others: Isis Interstellar ships, Neolite Wolfies, etc.hiran wrote: ↑Mon Jul 12, 2021 1:27 pmCurrently we are in a mode where players choose their HUD.phkb wrote: ↑Mon Jul 12, 2021 12:29 pmI should elaborate. Yes, it’s possible to create a custom HUD with an MFD in highlighter yellow in some prominent position. But HUD’s are quite personal things - I know I get annoyed when an OXP messes with my setup. Plus, there is the jarring nature of the change - anything we swap to is not going to look like the players current HUD, so why would the look of it change just to receive a message from another ship? Rather than having the story slip in quietly in the bounds of the players config, we toss them off the deep end with a shock change to everything they’re used to seeing.
I think it’s probably worth trying out some of the options that don’t require a HUD change, and other ways to alert the player, and use the HUD swap option as the last resort.
How about doing it as it is done with cars, planes or just so much other machinery? The HUD is designed by the machine vendor and at most customizable to some degree. For me as a player the advantage would be we can see which ship a player is flying by seeing the HUD, and every ship would have some more differential taste to it. Today flying different ships is a bit too equal - at least for my taste...
•Missing OXPs? What do you think is missing?
•Lore: The economics of ship building How many built for Aronar?
•Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
Re: Long way round/Scourge of the Black Baron missions
Brevity is the soul of wit and vulgarity is wit's downfall
Good Night and Good Luck - Read You Soon
- phkb
- Impressively Grand Sub-Admiral
- Posts: 4830
- Joined: Tue Jan 21, 2014 10:37 pm
- Location: Writing more OXPs, because the world needs more OXPs.
Re: Long way round/Scourge of the Black Baron missions
You can only use what is there in the first place. HUDs are not dynamic - you can’t add an image to a part of the screen on the fly. You can only use what’s there in the first place. Vimana HUD (and a couple of others) use images for some of their display, but if they haven’t defined an image in the location you want, you can’t add one in without editing the HUD definition outside the game.
- Cholmondely
- Archivist
- Posts: 5366
- Joined: Tue Jul 07, 2020 11:00 am
- Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
- Contact:
Re: Long way round/Scourge of the Black Baron missions
There are at least two methods of including the multiple contents of an OXP in the game's world-script which "runs the game".phkb wrote: ↑Mon Jul 12, 2021 7:46 amMaybe we should start by breaking down what's currently in the Long Way Round mission, then we can nail down what changes we want to make, and then look at ways to implement said changes. This is going to be a long one, folks.
So, what's in the original mission pack?
The heart of the mission is in two files: missiontext.plist, and script.js. missiontext.plist doesn't have anything to do with the flow of the mission, merely holding mission text and descriptions. So, the majority of the functionality of the mission comes from the script file.
Before we look at the content of the script file, it's worth noting that the name of this file is significant. Oolite will automatically include in the world scripts pool any "script.js" file that is placed in the "Config" folder of an OXP. That means we don't need to do anything additional to have the script file included with all the other script files. If we wanted to organise out scripts and break them out in some way, we would need to have a "world-script.plist" file that specifically names all the JS files we're adding, and those JS files would need to be in the "Scripts" folder. But for now, given the simplicity of this mission, we can stick with the single "script.js" file.
The content of this file can be broken down into four main sections:
1. Header and preamble
2. Script-wide variation declarations
3. World event definitions
4. Custom JS routines.
We'll look at each of these in turn
The first method is used here - a script.js file which summons the rest of the OXP as and when needed.
The second method (used - for example - in GalCop Missions, a much more complex OXP) would be to use a "world-script.plist" file instead.
•A function in effect changes something in the game - even if just the time changing on the HUD's clock or something equally inconsequential to the gameplay (unless you are on a contract run!).phkb wrote: ↑Mon Jul 12, 2021 7:46 am1. Header and preambleThese lines are generally at the top of every JS script file. From a functionality point of view, only two items are considered necessaryCode: Select all
"use strict"; this.name = "LongWayRound"; this.author = "aegidian, phkb"; this.copyright = "2018 phkb"; this.description = "Script for Long Way Round mission - original script by aegidian, JS script by phkb."; this.licence = "CC BY-NC-SA 3.0";
(a)"use strict";
tells the Javascript engine to enforce variable declarations and catches a few other common errors. Basically it's a way of making sure the code that follows passes some basic tests. (Edit to add: while it is not strictly necessary to include this like, it is highly recommended).
(b)this.name = "LongWayRound";
this line defines the name this script will have in theJavascript object modelworld script pool. It must be unique.
The other elements are useful, but not necessary for the functionality of the script.
2. Script-wide variable declarations.This script defines two variables that will be used at various points around the script. The first isCode: Select all
this._counter = 0; this._timer = null;
"this._counter = 0;"
. Of note here is the prefix of "this." - any time you see this prefix on something, it is referring to something inside the script file. If that prefix is missing, then the JS engine will assume the reference is to something inside the current function it is executing. (It can get a lot more complicated that this, but we'll leave the discussion about context until later).
"this._counter" is used later in the script to count time before spawning some enemies (which we'll get to later). It is being predefined with a value of 0 (zero), which causes the JS engine to recognise it as an integer number.
You can define variables with different types, based on the content of the variable. Javascript is, shall we say, "flexible" when it comes to variable types. It doesn't enforce strict type definitions. So, you can define a variable as an integer variable (like this._counter), but then later put a string value inside it (ie some value inside quotes), or even an complex object (we'll get to these later). Javascript is fine with that. Your program might not work, but JS will plough on regardless (if the code didn't just implode into a million random bits). JS is not a "type-safe" language. Which means you need to be careful about how your variables are defined and (more importantly) used.
Anyway, because we've defined this._counter as an integer, we can do arithmetic operations to it.
"this._timer" is going to be a reference or pointer to a timer object, but for now is just given a default value of "null", which in essence means it's pointing at nothing. (For non-programmers, there is a distinction between simple objects, like integers, and complex objects, like a timer. With a simple object, the variable is the value. In our example, "this._counter" equals zero. For a complex object, the variable is a pointer to the object, a way of accessing it, like a phone number is a way of accessing a person. Having a reference to an object is important, because otherwise there is no way to access it).
These variables are set up when the script is loaded, and before any of the scripts functions are executed.
•Variables come in maybe a dozen different varieties. Integers, decimals, words, arrays (lists of words), etc. "Type-safe" languages don't allow you to change one into another later on. Javascript does.
•A timer-object is another variable.
•Use of "="
one "=" means assign/put this value into that variable
two "==" & three "===" are different ways of saying "check" if the value of that variable equals this value. For LongWay, we should not need to worry about "===".
•world event function definitions are listed on the wiki at Oolite JavaScript Reference: World script event handlers (http://wiki.alioth.net/index.php/Oolite ... t_handlers)phkb wrote: ↑Mon Jul 12, 2021 7:46 am3. World event definitions.
The rest of the script file contains functions, the first type being world event function definitions. World events are game-specific events that occur at pre-defined times, usually in response to some sort of game action or activity. To hook into one of these events, the first job is to decide which event you need to use. You can see a full list of all the different events and their function definitions on the wiki.
Once you have chosen the appropriate event, you can copy the definition from the wiki and paste it into your script file. Then, when the event takes place, Oolite will go through all the script files that have been loaded and run each of the event scripts that it finds.
For "Long way round", there are three world events being hooked into: "startUp", "missionScreenOpportunity", and "shipExitedWitchspace".
The first one, "startUp", is called after all OXP scripts have been loaded, either after a new game has been started, or a game file has been loaded. It's normally used to do any initialisation steps that should only be done once (for example, loading game file saved values). In our case, it's doing some cleanup:What this snippet is doing is checking a variable "longwayround" stored in the "missionVariables" object (I'll get to that shortly). The "longwayround" variable is how this mission keeps track of where the player is up to. When they complete the mission, the "longwayround" variable is set to "MISSION_COMPLETE", and if the game loads in with this stored value, it then deletes all the world event script items so that those pieces of code don't get run unnecessarily.Code: Select all
if (missionVariables.longwayround === "MISSION_COMPLETE") { delete this.missionScreenOpportunity; delete this.shipExitedWitchspace; delete this.startUp; }
The "missionVariables" object is a special object used by Oolite to save any information between sessions. Scripts can add anything they like to this object, but to ensure everything works smoothly, the types of things that should be saved are strings (ie any characters between quotation marks) and numbers. If you have a complex object which needs to be stored, like a dictionary or array (we'll get to those things later), OXP's usually convert them into a string. We can cover that later as well when we start needing to store more detailed info.
The next function is "missionScreenOpportunity". This function is called if there are no mission screens active and the player is docked at a station. It can fire multiple times, so any code inside here will need to make sure it can only run once.
In our example, the routine begins with this check:This is checking to make sure the player is docked at a main station, somewhere in Galaxy 1 (the "galaxyNumber" is zero-based, so a value of 0 means Galaxy 1). If both those things are true, we move on to the next checks:Code: Select all
if (player.ship.dockedStation.isMainStation && galaxyNumber === 0) {
This is checking to see if there is no "longwayround" variable inside the missionVariables object (the "!" mark prefixing the statement makes the check into a "not" - the long-winded version of this would beCode: Select all
if (!missionVariables.longwayround && system.ID === 3) {
if (missionVariables.longwayround == undefined...
). We're also checking to see if we're in system ID 3 (Biarge). If those two things are true, we then bring up the first mission page.There are 4 things happening here. First, we're showing the mission screen. Second, we're changing the value of missionVariables.longwayround to be "STAGE1". Next, we're marking system ID 248 (Soladies) so that it shows on the galaxy map. And finally, were putting some instructions on the F5F5 manifest screen, to remind the player what they should be doing to further this mission.Code: Select all
mission.runScreen( { screenID: "longwayround", title: "Incoming Message", exitScreen: "GUI_SCREEN_STATUS", messageKey: "long_way_round_Biarge_briefing" } ); missionVariables.longwayround = "STAGE1"; mission.markSystem({system:248, name:this.name}); mission.setInstructionsKey("em1_short_desc1", this.name); return;
The mission screen definition is perhaps the most basic example of a mission screen, in that there are no options or anything complicated. We define a screen ID (which isn't used here, but it can be helpful for other OXP's to know), we put a title on it, we tell it where to exit to when the mission screen closes, and we tell it what key to use out of missiontext.plist to get the text to display.
After all these steps are done, we get to the "return;" statement. That will exit the function at that point, without executing any more code.
If, however, we've docked at a main station in a system other that Biarge, the code would fall through to the next IF statement:This is checking to see if our mission tracking variable has been set to "STAGE1" (ie we've started the mission), and we've made it to Soladies. If so, we then display the next mission screen:Code: Select all
if (missionVariables.longwayround === "STAGE1" && system.ID === 248) {
Again, a very simple mission screen, with no options or complexities. Just a different key to use to find the text to display. We also change the mission tracking variable to "STAGE2", we award the player some credits, then we tell them about their windfall via a consoleMessage. Next, we unmark Soladies on the galaxy map, and mark a new system, ID 233 (Qubeen), and update the instructions on the F5F5 manifest page. Once all these things are done, we exit the function.Code: Select all
mission.runScreen( { screenID: "longwayround", title: "Incoming Message", exitScreen: "GUI_SCREEN_STATUS", messageKey: "long_way_round_Soladies_briefing" } ); missionVariables.longwayround = "STAGE2"; player.credits += 500; player.consoleMessage("You have been awarded 500cr."); mission.unmarkSystem({system:248, name:this.name}); mission.markSystem({system:233, name:this.name}); mission.setInstructionsKey("em1_short_desc2", this.name); return;
We'll assume the player has moved on to Qubeen and docked. That will trigger the third IF statement in this function:If our tracking variable is at "STAGE2" (ie. we've stopped in Soladies and picked up the passenger), and we're now docked in Qubeen, we display the final mission screen.Code: Select all
if (missionVariables.longwayround === "STAGE2" && system.ID === 233) {
Another simple mission screen, just with an overlay image that will be displayed behind the text and a new key to use to look up the text to display. We set our tracking variable to "MISSION_COMPLETE", we award to player 2K in credits, tell them about it with a console message, then unmark the system on the galaxy map, remove the instructions from the F5F5 manifest screen, and remove the two functions that control the operation of the mission (shipExitedWitchspace and missionScreenOpportunity).Code: Select all
mission.runScreen( { screenID: "longwayround", title: "Incoming Message", overlay: "loyalistflag.png", exitScreen: "GUI_SCREEN_STATUS", messageKey: "long_way_round_Qubeen_briefing" } ); missionVariables.longwayround = "MISSION_COMPLETE"; player.credits += 2000; player.consoleMessage("You have been awarded 2000cr."); mission.unmarkSystem({system:233, name:this.name}); mission.setInstructionsKey(null, this.name); delete this.shipExitedWitchspace; delete this.missionScreenOppportunity; return;
And that's the end of the missionScreenOpportunity function call.
We move on to the final world event in this mission, shipExitedWitchspace. This world event is called after the player arrives in a system and the tunnel effect has been shown. The system should be full populated by this time. In our function we're checking 3 things:Are we in galaxy 1? Are we in system 233 (Qubeen)? And is our tracking variable set to "STAGE2" (ie we have a passenger on board)? If all those things are true, we can start the challenge of the mission, which we initiate by starting a timer.Code: Select all
if (galaxyNumber === 0 && system.ID === 233 && missionVariables.longwayround === "STAGE2") {
Here is where we start accessing those variables we defined at the top of our script. At this point though, all we're going to do is just make sure this._counter is set to zero. On your first play-though, it should already be zero, but if you have arrived here previously and jumped out before docking at the main station, it could be at a value greater than zero, so we'll set it to zero for safety.Code: Select all
this._counter = 0; this._timer = new Timer(this, this.$addExtraShips, 1, 1); system.addShips("longWay_rebel", 2);
Next, we start a timer. We keep a reference to the timer inthis._timer
, and anytime you create a timer, you should keep a reference to it in a similar fashion.
You'll notice I said a "reference". A timer is a complex object. An integer or a string is a simple object - they have generally 1 value, and there is a limited number of things that can be done with them. Complex objects, like a timer, can have multiple values, all independent. Each value of an object is called a property. For a timer, there are 3 properties and 2 methods available. The properties are "interval" (the rate at which the timer repeats, in seconds), "isRunning" (a true/false value indicating whether the timer is running or not), and "nextTime" (the next time this timer will fire). And a timer has two methods: "start" and "stop". We'll get to these in a minute.
A timer is created by using the code "new Timer". Into this definition we can pass a number of properties: the first is always "this", which basically indicates where the timer will call home. It sets the context* of what "this" will refer to in the next property, being the function to call when the event fires. In our case, the function we're calling is "this.$addExtraShips". The next two properties are the seconds until the first firing, and the next is how often afterwards the event will fire. If we left off the last number, the event would only fire once.
(* A bit of an aside about context: Context in code is very important. As the Javascript executes the various functions, the execution happens inside a particular context. If something is referenced in that context that hasn't been defined, errors will occur. To use an example: in a group of friends, where "Fred" is present, someone might say, "Fred's shirt is so bright its hurting my eyes." Now, if there was another group of people, but only one of them knows of Fred and he predeliction for loud shirts, and this person says the same thing, no one in the group will understand. They don't have the right context to know who Fred is. So, the context in scope at the time a piece of code is run is very important. Normally, the context of our JS script files is the script file itself - which is why we can refer to "this" throughout the script. And if our code runs in isolation from other scripts, in that we don't need to reference or use anything from somewhere else, we can usually be confident the context will be our own script file. But, timers can throw this out of alignment if we don't set the context correctly when creating them).
Immediately after starting the timer, we add 2 ships with the role "longWay_rebel" to the system at the witchpoint. The witchpoint is the default position when no other parameters are given.
So, we've arrived in the system, and there are now two ships at the witchpoint who are probably trying to poke us with a sharply pointed laser at this point. Then, after 1 second passes, the timer fires, and we execute the "$addExtraShips" function. We now move onto the final section of code.
The three events listed here are on that wiki list. Luckily, these three events are not too popular with other OXPs - a long list of OXPs all with happenings at the same World script event can constipate the flow of the game for the player!
They appear three times - at the beginning of each of the three paragraphs of relevant programming which happens at that event if all the conditions are met (eg being at the right stage of the mission and being at the right star system).
Code: Select all
this.startUp = function() {
if (missionVariables.longwayround === "MISSION_COMPLETE") {
Code: Select all
this.missionScreenOpportunity = function() {
if (player.ship.dockedStation.isMainStation && galaxyNumber === 0) {
Code: Select all
this.shipExitedWitchspace = function() {
if (galaxyNumber === 0 && system.ID === 233 && missionVariables.longwayround === "STAGE2") {
•"missionScreenOpportunity". This function is called if there are no mission screens active and the player is docked at a station. It can fire multiple times
It fires the first time and if an OXP hooks into it, when the OXP has been satisfied it then fires a second time. When the second OXP has been satiated, it fires a third time. This multiple firing carries on until no OXP is caught. Then it stops. This is all very fast and takes virtually no time at all!
Code: Select all
{
screenID: "longwayround",
title: "Incoming Message",
exitScreen: "GUI_SCREEN_STATUS",
messageKey: "long_way_round_Biarge_briefing"
or messageKey: "long_way_round_Soladies_briefing"
or messageKey: "long_way_round_Qubeen_briefing"
}
•The messageKey is the link to the message in the actual "missiontext.plist" mentioned at the top of this post as being one of the two hearts of the mission. The "missiontext.plist" has a number of messages in it, the messageKey identifies the appropriate one for this stage of the mission.
The mission texts could have been included here in the script.js file instead. They are easier for non-programmers to identify and tweak if they are kept separate.
Key in the text refers to messageKey
•We keep a reference to the timer in
this._timer
, and anytime you create a timer, you should keep a reference to it in a similar fashion.This reference is like the telephone number referred to above at the top. The reference is not the timer itself, but allows the javascript to control and/or to assess what the timer is doing.
•Custom routines are not purely triggered by "world events" from the long list in the wiki. They may happen once the world event triggers a timer which then triggers the customised function. Or they may be triggered by events which are more customised then this.phkb wrote: ↑Mon Jul 12, 2021 7:46 am4. Custom JS routines.
Custom routines are created by the programmer to perform whatever functions are required. You can fill a script file with these functions, but none of them will be called without some trigger. In our case, the trigger is a timer firing and causing the function to execute. But they can also be called from world events, or ship scripts, or any number of other possibilities. The point is, while world event functions have a predefined structure, our custom functions can have whatever properties and parameters we want.
In our example, the structure is quite simple:This line of code defines our function. The function is called "$addExtraShips", and it exists in the context of the "LongWayRound" script file (ie, it's what "this" is referring to). The repeated function name is not required, but is quite useful for debugging, particularly with timers. We'll ignore it for now. Finally, the "()" are where we could potentially pass some parameters into our function. In this case, there are no parameters required, so the brackets are empty.Code: Select all
this.$addExtraShips = function $addExtraShips() {
The first section of code in the function is doing a check to see if the player has died:If the player.ship object is invalid, or it's not in space, then something has happened to them. There's no point in going on at this point, so the timer is stopped by calling the "stop" method of the object. And we then exit this function.Code: Select all
if (player.ship.isValid === false || player.ship.isInSpace === false) { this._timer.stop(); return; }
But, assuming the player has managed to survive, we move onto the next piece of code:Here, we're adding 1 to our counter variable. We're then checking to see if this value is less than 60. What this means in practical terms, given that our timer is running every second, is that we will only be doing this for 1 minute after the player arrives. So, if it's less than 60, we're then asking the system to tell us how many ships with the role of "longWay_rebel" are currently present. If that number is less than 5, there is then a 50% chance that another ship will be spawned at the witchpoint.Code: Select all
this._counter += 1; if (this._counter < 60) { if (system.countShipsWithPrimaryRole("longWay_rebel") < 5) { if (Math.random() < 0.5) { system.addShips("longWay_rebel", 1); } } } else { this._timer.stop(); }
If it's been more than 60 seconds, though, we tell the timer to stop. That's the bit after the "else" statement.
So, functionally, that's how the script currently works. The OXP also includes a ship definition for the longWay_rebel, with a texture to go with it.
There's more to follow, and I'll probably have some grammar/spelling issues to correct, but that's a start.
(Edited to add some clarity to some of the discussion points)
•Parameter - this term here is used interchangeably with property. This may not be true elsewhere for other programmes.
_______________________________________________________________________________________________________________________________
Sir Nicolas: please feel free to hack the above to death where it needs it!
•Missing OXPs? What do you think is missing?
•Lore: The economics of ship building How many built for Aronar?
•Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
- phkb
- Impressively Grand Sub-Admiral
- Posts: 4830
- Joined: Tue Jan 21, 2014 10:37 pm
- Location: Writing more OXPs, because the world needs more OXPs.
Re: Long way round/Scourge of the Black Baron missions
Well, I corrected my glaring mistake in regard to "Fred" and his "predilection for loud shirts", but otherwise it looks fine. I do need to be clearer when talking about properties and parameters. And for anyone joining this conversion at this point: a property is a part of an object (for example, for a timer object, it has a "isRunning" property, which is a boolean true/false value), while a parameter is something that is used in the creation of an object or in the calling of a function (for example, when creating a new timer, we pass parameters into theCholmondely wrote: ↑Fri Jul 23, 2021 4:53 pmplease feel free to hack the above to death where it needs it!
new Timer
call).- Cholmondely
- Archivist
- Posts: 5366
- Joined: Tue Jul 07, 2020 11:00 am
- Location: The Delightful Domains of His Most Britannic Majesty (industrial? agricultural? mainly anything?)
- Contact:
Re: Long way round/Scourge of the Black Baron missions
1) In Missiontext.plist on the wiki (but in legacy script only)
2) In Littlebear & your posts on the first page of this thread
3) In the Black Baron sequel to LongWay
Would this do it? (Copy & Paste from BlackBaron with tweaking to taste)
Code: Select all
// mission offer with accept/reject choice -------------------------------------
if (missionVariables.longwayround === "STAGE1" && system.ID === 248) {
// show mission screen
mission.runScreen(
{
screenID: "longwayround",
title: "Incoming Message",
exitScreen: "GUI_SCREEN_STATUS",
messageKey: "long_way_round_Soladies_briefing", choicesKey: "longwayround_choices"},
this.choice);
return;
}
);
missionVariables.longwayround = "STAGE2";
player.credits += 500;
player.consoleMessage("You have been awarded 500cr.");
mission.unmarkSystem({system:248, name:this.name});
mission.markSystem({system:233, name:this.name});
mission.setInstructionsKey("em1_short_desc2", this.name);
return;
}
this.choice = function(choice) {
switch(choice) {
case "longwayround_choiceAccept": {
mission.setInstructionsKey("longwayround_short2");
missionVariables.longwayround='STAGE2';
break;
}
case "longwayround_choiceReject": {
missionVariables.longwayround='MISSION_COMPLETE';
mission.setInstructionsKey(null);
break;
}
}
}
Code: Select all
missionVariables.longwayround = "STAGE2";
and then
Code: Select all
mission.setInstructionsKey("longwayround_short2");
missionVariables.longwayround='STAGE2';
Code: Select all
);
missionVariables.longwayround = "STAGE2";
player.credits += 500;
player.consoleMessage("You have been awarded 500cr.");
mission.unmarkSystem({system:248, name:this.name});
mission.markSystem({system:233, name:this.name});
mission.setInstructionsKey("em1_short_desc2", this.name);
return;
Code: Select all
switch(choice) {
case "longwayround_choiceAccept": {
mission.setInstructionsKey("longwayround_short2");
missionVariables.longwayround='STAGE2';
player.credits += 500;
player.consoleMessage("You have been awarded 500cr.");
mission.unmarkSystem({system:248, name:this.name});
mission.markSystem({system:233, name:this.name});
mission.setInstructionsKey("em1_short_desc2", this.name);
break;
}
•Missing OXPs? What do you think is missing?
•Lore: The economics of ship building How many built for Aronar?
•Lore: The Space Traders Flight Training Manual: Cowell & MgRath Do you agree with Redspear?
- phkb
- Impressively Grand Sub-Admiral
- Posts: 4830
- Joined: Tue Jan 21, 2014 10:37 pm
- Location: Writing more OXPs, because the world needs more OXPs.
Re: Long way round/Scourge of the Black Baron missions
Code: Select all
this.choice = function (choice) {
switch (choice) {
case "longwayround_choiceAccept": {
mission.setInstructionsKey("longwayround_short2");
missionVariables.longwayround = 'STAGE2';
player.credits += 500;
player.consoleMessage("You have been awarded 500cr.");
mission.unmarkSystem({
system: 248,
name: this.name
});
mission.markSystem({
system: 233,
name: this.name
});
mission.setInstructionsKey("em1_short_desc2", this.name);
break;
}
case "longwayround_choiceReject": {
missionVariables.longwayround = 'MISSION_COMPLETE';
mission.unmarkSystem({
system: 248,
name: this.name
});
mission.setInstructionsKey(null);
break;
}
}
}
There are a few bracketing issues in your sample (a few too many). All brackets need to balance - for each "(", "{" and "[" there should be an equivalent ")", "}" and "]". They shouldn't cross over either (eg this is invalid: "( { ) }" . I think all you would need is this:
Code: Select all
// mission offer with accept/reject choice -------------------------------------
if (missionVariables.longwayround === "STAGE1" && system.ID === 248) { // round bracket 1 open/close, curly bracket 1 open
mission.runScreen({ // round bracket 2 open, curly bracket 2 open
screenID: "longwayround",
title: "Incoming Message",
exitScreen: "GUI_SCREEN_STATUS",
messageKey: "long_way_round_Soladies_briefing",
choicesKey: "longwayround_choices"
}, // curly bracket 2 close
this.choice); // round bracket 2 close
return;
} // curly bracket 1 close