Long way round/Scourge of the Black Baron missions

Discussion and information relevant to creating special missions, new ships, skins etc.

Moderators: another_commander, winston

User avatar
Cholmondely
Archivist
Archivist
Posts: 4965
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

Post by Cholmondely »

phkb wrote: Thu Jun 10, 2021 10:41 pm
Cholmondely wrote: Thu Jun 10, 2021 9:44 am
i) Can BC's MFD really be made just to pop up, all on its own, without the player having to prime it?
Should be.
Cholmondely wrote: Thu Jun 10, 2021 9:44 am
ii) Could the BC HUD be modified - change the colour, for example
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 am
iii) Is my sneaky suspicion that the Vanilla HUD includes invisible MFDs built on anything other than quicksand?
The default HUD defines 2 MFD's, which should be visible when activated.
Cholmondely wrote: Thu Jun 10, 2021 9:44 am
iv) Is there no other way of doing it?
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.
Cholmondely wrote: Thu Jun 10, 2021 9:44 am
According to the description it seems to generate random messages at various moments and also at changes to yellow alert or whatever.
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 am
we can do trumbles, we can do jelly-babies... can we do soap bubbles?
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 am
And 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?
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.
just remembered: Shaky Drive by Astrobe causes one to wobble infuriatingly when the Torus Drive is employed
Comments wanted:
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?
User avatar
Cholmondely
Archivist
Archivist
Posts: 4965
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

Post by Cholmondely »

Umm...

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?
Last edited by Cholmondely on Sun Jul 11, 2021 12:50 pm, edited 1 time in total.
Comments wanted:
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?
User avatar
phkb
Impressively Grand Sub-Admiral
Impressively Grand Sub-Admiral
Posts: 4612
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

Post by phkb »

Cholmondely wrote: Sun Jul 11, 2021 12:48 pm
Any advice?
Well...
Cholmondely wrote: Sun Jul 11, 2021 12:48 pm
I don't want a step-by-step instruction as to what to do
Cholmondely wrote: Sun Jul 11, 2021 12:48 pm
I work best from books
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";
These lines are generally at the top of every JS script file. From a functionality point of view, only two items are considered necessary
(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 Javascript object model world 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.

Code: Select all

this._counter = 0;
this._timer = null;
This script defines two variables that will be used at various points around the script. The first is "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;
    }
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.

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) {
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 (!missionVariables.longwayround && system.ID === 3) {
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 be 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;
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.

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) {
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

            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;
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.

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) {
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

            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;
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).

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") {
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

        this._counter = 0;
        this._timer = new Timer(this, this.$addExtraShips, 1, 1);
        system.addShips("longWay_rebel", 2);
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.

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() {
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.

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;
    }
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.

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();
    }
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.

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)
Last edited by phkb on Tue Feb 07, 2023 12:33 am, edited 7 times in total.
User avatar
Cholmondely
Archivist
Archivist
Posts: 4965
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

Post by Cholmondely »

Thank you for the above. Thank you for taking the time and the care. I'm looking forwards to understanding it!!

Quick Question on your previous post:
phkb wrote: Thu Jun 10, 2021 10:41 pm
Cholmondely wrote: Thu Jun 10, 2021 9:44 am
ii) Could the BC HUD be modified - change the colour, for example
No. MFD's are only configurable via the HUD, and to change the colour we'd have to swap the entire HUD out.
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?

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
Last edited by Cholmondely on Mon Jul 12, 2021 1:08 pm, edited 1 time in total.
Comments wanted:
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?
User avatar
phkb
Impressively Grand Sub-Admiral
Impressively Grand Sub-Admiral
Posts: 4612
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

Post by phkb »

Cholmondely wrote: Mon Jul 12, 2021 12:13 pm
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?
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.
User avatar
phkb
Impressively Grand Sub-Admiral
Impressively Grand Sub-Admiral
Posts: 4612
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

Post by phkb »

I 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.
User avatar
Cholmondely
Archivist
Archivist
Posts: 4965
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

Post by Cholmondely »

phkb wrote: Mon Jul 12, 2021 12:29 pm
I 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.
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

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)?
Last edited by Cholmondely on Mon Jul 12, 2021 1:30 pm, edited 1 time in total.
Comments wanted:
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?
User avatar
hiran
Theorethicist
Posts: 2026
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

Post by hiran »

phkb wrote: Mon Jul 12, 2021 12:29 pm
I 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.
Currently we are in a mode where players choose their HUD.
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...
Sunshine - Moonlight - Good Times - Oolite
User avatar
Cholmondely
Archivist
Archivist
Posts: 4965
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

Post by Cholmondely »

hiran wrote: Mon Jul 12, 2021 1:27 pm
phkb wrote: Mon Jul 12, 2021 12:29 pm
I 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.
Currently we are in a mode where players choose their HUD.
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...
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.
Comments wanted:
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?
User avatar
Nite Owl
---- E L I T E ----
---- E L I T E ----
Posts: 518
Joined: Sat Jan 20, 2018 4:08 pm
Location: In The Dark

Re: Long way round/Scourge of the Black Baron missions

Post by Nite Owl »

Thank You phkb - Most Enlightening, especially for a cut and paste person like myself. Another step closer to writing my own scripts from scratch one day.
Humor is the second most subjective thing on the planet

Brevity is the soul of wit and vulgarity is wit's downfall

Good Night and Good Luck - Read You Soon
User avatar
phkb
Impressively Grand Sub-Admiral
Impressively Grand Sub-Admiral
Posts: 4612
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

Post by phkb »

Cholmondely wrote: Mon Jul 12, 2021 1:10 pm
It can't just add something to the display?
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.
User avatar
Cholmondely
Archivist
Archivist
Posts: 4965
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

Post by Cholmondely »

Summary of a Zoom discussion concerning the long earlier post:
phkb wrote: Mon Jul 12, 2021 7:46 am
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
There are at least two methods of including the multiple contents of an OXP in the game's world-script which "runs the game".
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.
phkb wrote: Mon Jul 12, 2021 7:46 am
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";
These lines are generally at the top of every JS script file. From a functionality point of view, only two items are considered necessary
(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 Javascript object model world 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.

Code: Select all

this._counter = 0;
this._timer = null;
This script defines two variables that will be used at various points around the script. The first is "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.
•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!).

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 "===".
phkb wrote: Mon Jul 12, 2021 7:46 am
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;
    }
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.

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) {
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 (!missionVariables.longwayround && system.ID === 3) {
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 be 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;
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.

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) {
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

            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;
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.

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) {
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

            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;
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).

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") {
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

        this._counter = 0;
        this._timer = new Timer(this, this.$addExtraShips, 1, 1);
        system.addShips("longWay_rebel", 2);
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.

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 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.
•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)
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) {
There are three repetitions of this - one for Biarge (#3), one for Soladies (#248) and one for Qubeen (#233)

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"
                } 
messageKey: "long_way_round_Soladies_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.
phkb wrote: Mon Jul 12, 2021 7:46 am
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() {
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.

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;
    }
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.

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();
    }
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.

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)
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.

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!
Last edited by Cholmondely on Fri Jan 07, 2022 8:49 am, edited 1 time in total.
Comments wanted:
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?
User avatar
phkb
Impressively Grand Sub-Admiral
Impressively Grand Sub-Admiral
Posts: 4612
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

Post by phkb »

Cholmondely wrote: Fri Jul 23, 2021 4:53 pm
please feel free to hack the above to death where it needs it!
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 the new Timer call).
User avatar
Cholmondely
Archivist
Archivist
Posts: 4965
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

Post by Cholmondely »

So I've found three sources showing a mission screen with a choice.

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;
		}
	}
}
I notice that there seems to be repetition in terms of setting the missionVariable to 'STAGE2'

Code: Select all

            missionVariables.longwayround = "STAGE2";

and then

Code: Select all

			mission.setInstructionsKey("longwayround_short2"); 
			missionVariables.longwayround='STAGE2';
I presume that I need to delete the upper paragraph from

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;
and build up the accept choice with the deleted tid-bits:

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;
		}
So... how many egregious errors have I made - and what are they?
Comments wanted:
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?
User avatar
phkb
Impressively Grand Sub-Admiral
Impressively Grand Sub-Admiral
Posts: 4612
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

Post by phkb »

Yes, by separating out the choice function, all the items that were previously with the mission screen need to be moved to the choice function, (which is why it looks like "STAGE2" gets set twice). eg:

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;
        }
    }
}
I've also added some cleanup to the reject choice, unmarking the 248 system.

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
Post Reply