Mission offering

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

Moderators: another_commander, winston

User avatar
Commander McLane
---- E L I T E ----
---- E L I T E ----
Posts: 9520
Joined: Thu Dec 14, 2006 9:08 am
Location: a Hacker Outpost in a moderately remote area
Contact:

Post by Commander McLane »

Thanks for uploading the wiki-page. One last request, though: Could you give it a category, so that it appears on the index page?

Don't ask me how to do it, I don't know, but it would be nice to have the page reachable if someone searches for it.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

Commander McLane wrote:
Thanks for uploading the wiki-page. One last request, though: Could you give it a category, so that it appears on the index page?

Don't ask me how to do it, I don't know, but it would be nice to have the page reachable if someone searches for it.
It is a simple matter of adding [[Category:Oolite scripting]] somewhere in the page, traditionally at the bottom. This I have done.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

I’m implementing a JavaScript equivalent to MOP-Test for testing purposes, and as a prelude to porting some more of the built-in missions. One thing I notice is the use of the mission variable name mission_test. This is Bad, since mission variables are shared between all missions. Use something reasonably unique, like mission_ew_MOP_test. The same goes for missiontext.plist keys like “test1”.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Ahruman wrote:
One thing I notice is the use of the mission variable name mission_test. This is Bad, since mission variables are shared between all missions.
You are completely right. I wrote this thing some months ago for private use. So I didn't use all programmers rules. At that time I also didn't know of the existence of local variables that are not saved with the save-file.
I just last minute added one local_variable to replace the test of scriptimer being small for detecting if oolite just had started.
I corrected this name and changed it into a local variable. It now won't leave a trace in the save file. I just uploaded it over the old verion on wiki.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

To simplify mission screen management, I’ve added a new method, clearMissionScreen. This is available to both legacy scripts and JavaScript (as mission.clearMissionScreen()). It is equivalent to:

Code: Select all

"resetMissionChoice",
"setMissionImage: none",
"setMissionMusic: none",
"showShipModel: none"
Am I missing anything?

I’ve also ported the trumble script to JavaScript, using clearMissionScreen, which is called before showing the mission screen and also when responding to mission screen selections (which is handled in a new event handler, missionScreenEnded). It seems to work, but I’d appreciate any comments on the logic or design.

Code: Select all

this.name           = "oolite-trumbles";
this.author         = "Jens Ayton";
this.copyright      = "© 2007 the Oolite team.";
this.description    = "Random offers of trumbles.";
this.version        = "1.69.2";


this.log = function(message)
{
    // Uncomment next line for diagnostics.
//  LogWithClass("js.trumbles", message);
}


this.startUp = this.reset = function()
{
    /*  For simplicity, ensure that missionVariables.trumbles is never
        undefined when running the rest of the script. If it could be
        undefined, it would be necessary to test for undefinedness before
        doing any tests on the value, like so:
            if (missionVariables.trumbles && missionVariables.trumbles == "FOO")
    */
    if (!missionVariables.trumbles)
    {
        missionVariables.trumbles = "";
        this.log("missionVariables.trumbles was undefined, set it to empty string.");
    }
    else
    {
        this.log("Reset with missionVariables.trumbles = " + missionVariables.trumbles);
    }
}


this.didDock = function()
{
    /*  In the pre-JavaScript implementation, the mission variable was set to
        OFFER_MADE while the mission screen was shown. If the player lanched
        in that state, the offer would never be made again -- unless some
        other script used the mission choice keys "YES" or "NO". This
        implementation uses unique choice keys and doesn't change the mission
        variable, which should be more reliable in all cases.
    */
    if (missionVariables.trumbles == "OFFER_MADE")  missionVariables.trumbles = "BUY_ME"
    
    if (player.dockedStation.isMainStation &&
        missionVariables.trumbles == "" &&
        !missionVariables.novacount &&    // Hmm. Why is this here? (Ported from legacy script)
        player.credits > 6553.5)
    {
        this.log("Time to start selling trumbles.")
        missionVariables.trumbles = "BUY_ME"
    }
    
    if (missionVariables.trumbles == "BUY_ME")
    {
        this.log("Trumbles are on the market.")
        // 20% chance of trumble being offered, if no other script got this dock session first.
        if (guiScreen == "GUI_SCREEN_STATUS"
            && Math.random() < 0.2)
        {
            this.log("Offering trumble.")
            
            let message =
            "Commander " + player.name + ",\n\n" +
            "You look like someone who could use a Trumble on your " + player.shipDescription + "!\n\n" +
            "This is yours for only 30 credits."
            
            // Show a mission screen.
            mission.clearMissionScreen()
            mission.setBackgroundImage("trumblebox.png")
            mission.showMissionScreen()
            mission.addMessageText(message)
            mission.setChoicesKey("oolite_trumble_offer_yesno")
        }
        else
        {
            this.log("Not offering trumble. GUI screen is " + guiScreen)
        }
    }
    else
    {
        this.log("Not offering trumble. mission_trumbles is " + missionVariables.trumbles)
    }
}


this.missionScreenEnded = function()
{
    if (missionVariables.trumbles == "BUY_ME")
    {
        this.log("Trumble mission screen closed.")
        
        if (mission.choice == "OOLITE_TRUMBLE_YES")
        {
            this.log("Trumble bought.")
            mission.clearMissionScreen()
            missionVariables.trumbles = "TRUMBLE_BOUGHT"
            player.credits -= 30
            player.awardEquipment("EQ_TRUMBLE")
        }
        else if (mission.choice == "OOLITE_TRUMBLE_NO")
        {
            this.log("Trumble rejected.")
            mission.clearMissionScreen()
            missionVariables.trumbles = "NOT_NOW"
        }
    }
    else
    {
        this.log("Non-trumble mission screen closed.")
    }
}


this.willExitWitchSpace = function()
{
    // If player has rejected a trumble offer, reset trumble mission with 2% probability per jump.
    if (missionVariables.trumbles == "NOT_NOW" && Math.random < 0.02)
    {
        this.log("Resetting trumble buyability.")
        missionVariables.trumbles = "BUY_ME"
    }
}
Note also that this version will only consider displaying the message once per docking, not every script tickle while docked.
Last edited by JensAyton on Tue Oct 02, 2007 7:03 am, edited 1 time in total.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Ahruman wrote:
To simplify mission screen management, I’ve added a new method, clearMissionScreen. This is available to both legacy scripts and JavaScript (as mission.clearMissionScreen).
It will certainly simplify things. Now I often forget to set the missionmusic and the Shipmodel to "none" on every occasion. In my ups-courier.oxp just did a clear on all four in one place in the script on launching. But that could still give troubles on multiple offers during one docking. One single clearcommand for hem all makes sure that it will be used.

The trumbles script looks good. I can't see any flaws. It addresses the two bugs/flaws from the old one. The main one was that the OFFER_MADE stayed open on launching during offering. With your approach you also correct the potentially wrong OFFER_MADE from an older oolite version into BUY_ME.

The 20% chance from the original version was not really a 20% change as you would get is certainly if you waited long enough. (But that could be intentionally).

The "!missionVariables.novacount" part was not in the old Oolites. It just makes sure that if you were persistent enough to refuse the trumble offers until arriving in galaxy 4, you will not be bothered again with offers. Personally I can't imagine anyone to refuse such a good offer for so a long time.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Thinking about this I realized that just one problem is not addressed yet. That is the multiple screen offering. In the texts within my MOP.OXP I voted for the introduction of a variable mission_offering. In my ups-courier.oxp I consequently use it.

What is the problem:

An oxp sets up a missionscreen. Other oxp's wait politely till the screen is free again. But what if the script wants to put up a second screen in succession. Script execution takes a fraction of the total game time. So most likely no script is executed when the player presses space or selects an option. At that moment the mission screen is cleared. The next script that executes could grab the screen if there is no method to signal that the first script wants the screen for a second display.

Introduction of a mission_offering variable would make it foolproof if everyone would use it. But is that realistic?

An other, almost foolproof way would be within Oolite's script handling. I don't know if it is already done but if the player presses space on a missionscreen, the program should have remembered witch script did set up the screen and than run that script first to give that script the opportunity to grab the missionscreen back first.

In legacy script it would work, but I don't know for JS as the timing is completely different.
User avatar
JensAyton
Grand Admiral Emeritus
Grand Admiral Emeritus
Posts: 6657
Joined: Sat Apr 02, 2005 2:43 pm
Location: Sweden
Contact:

Post by JensAyton »

As it stands, the trumbles script will only show its screen if there is no mission currently showing when the player docks. It won’t “queue up” in any way. (The way to do that would be to try again each time missionScreenEnded() is called, if it hasn’t done it yet; at that point, checking for other missions being partway through a sequence would become relevant.)

No effort is made to ensure that only the relevant script is called. I really want to create a better way to handle mission screens in JavaScript, but that won’t be before the next stable release. On the other hand, it might be reasonable for missionScreenEnded() to be called only for the script that opened the mission screen, and another event (say, someMissionScreenEnded()) to be sent to all scripts after that. In this way, a JS script could chain mission screens with impunity.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Yet an other option for improving the missionscreen. It has happened to me once or twice that I dock, go for refueling and while I press return on the fuel a missionscreen pops up. When it is one with choices, I make the default choice without any time to react by stopping my action on the keybord.

It would be a good thing that when a missionscreen is put up, the keystrokes for the next half a second are ignored, just to give the player time to react properly on the new situation.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

This was the first topic I started long ago with my first message o this board. By now I understand Oolite much better and see some flaws in my original MOP code (also known as mission clash code). While translating my scripts into Java I discovered the main course of the delays:

On being docked the scripts are only triggered by a 10 second interval timer. In most cases it starts soon but it could take up to several seconds in which time a player could switch to an other page. Therefor I also added checks for other pages. But the script is also evaluated directly after docking. This is an extra check separate from the 10 second timed one. Often the follow each other shortly.

Main difference is that during the extra one the conditions = (gui_screen_string equal GUI_SCREEN_MAIN). This one was not included by my proposal. During the time most scripts were bad, this was sort of good. Bad scripts showed up directly and the good scripts only showed up at the second script firing. In this case it was avoided that bad scripts could overwrite the good ones that politely waited. (It was never my intention however to do it this way)

By now most mission offering OXP's are rewritten to wait for each other so the risk of conflict is much less. A good reason to change the MOP code for future versions into:

conditions = (gui_screen_string oneof GUI_SCREEN_MAIN, GUI_SCREEN_STATUS)

Further screen check are not necessary as reaction is now immediately and there is no risk the player has already switched to an other page.

For scripts that only run on version 1.69 and higher (e.g. random_hits) it will be easier to use:

conditions = (gui_screen_string notequal GUI_SCREEN_MISSION)

In JS I used the same check that worked fine and immediately:

if (guiScreen != "GUI_SCREEN_MISSION") {}

----

One can also use the fact that the gui_screen_string is only once equal GUI_SCREEN_MAIN while status is docked. Some code you only want to run once while docked. An additional check for GUI_SCREEN_MAIN will do the trick.
User avatar
Commander McLane
---- E L I T E ----
---- E L I T E ----
Posts: 9520
Joined: Thu Dec 14, 2006 9:08 am
Location: a Hacker Outpost in a moderately remote area
Contact:

Post by Commander McLane »

Do I understand you correctly that the docking-effect (circles) is still on GUI_SCREEN_MAIN? Because otherwise that's only the in-flight-screen.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Do I understand you correctly that the docking-effect (circles) is still on GUI_SCREEN_MAIN? Because otherwise that's only the in-flight-screen.
YES, I checked this: the first time the script runs during status docked the gui_screen_string equals GUI_SCREEN_MAIN.
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Last week I have been working on several concepts for mission offering, but I came to the conclusion:

Mission offering needs a timer!

With some types of offers you can do without a timer but for others you won't always get the signal to do the offers. With legacy it is simple. That runs on a 10 second timer and there is no problem. With JS you can avoid using the tickle, but than you need to install a timer.

Lets explain the problem wit a concrete example: On docking there are two missions that are scheduled to do an offer. Script A is a legacy script and script B is a JS script.

On docking the system generates a tickle, directly followed by the docking event. Because the tickle is first, script A gets the first opportunity to set up a missionscreen. When script B starts, the missionscreen is occupied and he has to wait for a screen ended event.

When the screen ends the system will now generate the screen ended before the tickle. So script B is now the fist to react. Script B has to look at the missionscreen first. When occupied again there is no problem. There will be a new screen ending event. When no other script took the page script B has to look at the mission choice key.

When that key is still undefined he can put up his own missionscreen. But when there is an active choice he must verify he does not need the choices key. When not, he can set up his mission. (script A can still read out the choices when an other screen is put up). But when script B must set up a screen with his own choices he has to wait until script A has run and read out and cleared his choices. But there will be no new event on this and script B will not start by itself.

So script B needs a timer to get back in control. When you don't want to bother with timers you can use the tickle event as timer. But that will give long time-gaps as this timer only runs every 10 seconds.

For UPS I now added a 1 second timer. I set this timer up on startup and directly set it in stop mode. Now on determining if I can show a missionscreen I start the timer whenever I have to wait for the choices to be cleared. Every time the timer runs it checks if the choices are already cleared. When cleared, it puts the timer on hold again and calls the function that does the mission-offering. To prevent the timer running forever the script clears any hanging choices on launch. (I have to check if the system itself is doing this. When not I think it should be added in 1.71).

Adding a timer is a little more complicated that using the tickle event, but now reaction will be within a second of clearing the choices by the other script. With only the tickle it takes 10 seconds in which time the player could be everywhere including launched again.
User avatar
Hoopy
---- E L I T E ----
---- E L I T E ----
Posts: 438
Joined: Wed Oct 03, 2007 8:54 pm
Location: Durham, England

Post by Hoopy »

I think there should also be some warning that a mission screen is about to appear. A number of times I've been in the action of pressing return to fill the hold with Furs when a mission screen has come up. The return then accepted the missions and caused it to disappear so I don't even know what I've done!

Perhaps an 'incoming message' alert and the return accepts that and displays the mission screen as normal?
User avatar
Eric Walch
Slightly Grand Rear Admiral
Slightly Grand Rear Admiral
Posts: 5536
Joined: Sat Jun 16, 2007 3:48 pm
Location: Netherlands

Post by Eric Walch »

Hoopy wrote:
I think there should also be some warning that a mission screen is about to appear. A number of times I've been in the action of pressing return to fill the hold with Furs when a mission screen has come up. The return then accepted the missions and caused it to disappear so I don't even know what I've done!
This should be impossible. In the old system, the "tickles" were suppressed when in the market screen so there could be no code running. But is does in the equipment screen when buying fuel. It happened me twice there. But with the 1 second timer it will not happen in future I think. After docking when a missionoffer is scheduled, there won't be a long pause were the player can go to an other screen and also do some selections. I must just figure out if 1 second pause is enough or maybe 0.5 would be better.

A incoming message alert were the player has to press space would help, but most OXP's don't respect each others offers and could break in on such a two page display. Half a year ago I suggested a mechanism to prevent it but an OXP then needs additional code. Until now I only saw it being used in the work of Svengalii and my own of cause.
Post Reply