Page 1 of 3

RFC: revised mission screens for JavaScript

Posted: Wed Oct 07, 2009 7:47 pm
by JensAyton
The current interface/protocol for mission screens is a horrible mess. I’d like to replace it with a less horrible mess, but as I don’t actually use this stuff from the scripting side very much I need some sanity checking. (Paging Dr. Eric… Paging Dr Eric…)

Proposal:
1. Deprecate mission.showShipModel(), mission.setMusic(), mission.setBackgroundImage(), mission.showMissionScreen(), mission.addMessageText(), mission.addMessageTextKey() and mission.setChoicesKey(). I am not aware of any need to use these instead of mission.runMissionScreen().

2. Introduce a new mission screen method. I’m not sure what to call it since showMissionScreen() and runMissionScreen() are taken, but let’s call it newMissionScreen() for now. This function will take two arguments: a settings object, and a completion callback function. It will return true if the mission screen could be shown, and false otherwise. Example:

Code: Select all

var trumbleCompletion = function(choice)
{
    if (choice == "OOLITE_TRUMBLE_YES")
    {
        missionVariables.trumbles = "TRUMBLE_BOUGHT";
        player.credits -= 30;
        player.ship.awardEquipment("EQ_TRUMBLE");
    }
    else
    {
        missionVariables.trumbles = "NOT_NOW";
    }
}
mission.newMissionScreen({ messageKey: "oolite_trumble_offer", choiceKey: "oolite_trumble_offer_yesno", backgroundImage: "trumblebox.png" }, trumbleCompletion);
Since only the active mission screen’s completion function is called, and choice is passed as a parameter rather than a property of mission, there is no need to clear choice after the mission screen or deal with the possibility of other scripts’ choices being passed. The use of missionVariables.offering in Eric’s example also becomes unnecessary.

3. Also deprecate mission.runMissionScreen() and mission.choice, since the new mechanism makes them redundant. For 1.74, mission.runMissionScreen() would be implemented by calling mission.newMissionScreen(), then sending missionScreenEnded to the calling script only (not all world scripts as before).

4. Add a new event, missionScreenOpportunity, which is sent at the following times:
  • Immediately after shipDockedWithStation, if no report screen was shown.
  • Immediately after reportScreenEnded.
  • Immediately after missionScreenEnded, as long as we’re still on the F5 screen.
Unlike other events, this would not necessarily be sent to every world script, but would instead stop once a mission screen is shown. Since it’s sent again on missionScreenEnded, it will continue to be sent until every script that wants to show a mission screen has done so, unless the player launches or otherwise “escapes”.

Because any given script may receive it an arbitrary number of times, scripts that want to decide whether to show a screen randomly should make the decision in shipDockedWithStation, rather than missionScreenEnded.

Issues
One issue I haven’t dealt with is the missionChoiceWasReset event, because frankly I can’t remember what it’s for.

Questions
  • Would changing to this system, and deprecating (in 1.74) and removing (in 1.75) the old system restrict OXPs in some way? In other words, can the additional flexibility of the current horribly complicated system actually be used for anything useful?
  • Is there some source of complexity I haven’t addressed? Would it still be necessary to do a bunch of work outside the missionScreenOpportunity in order to implement simple mission screens?

Posted: Wed Oct 07, 2009 7:57 pm
by Kaks
I like! I was thinking along those lines last night. The missionChoiceWasReset seems to be used to jump from screen 1 to screen 2 within the same script, but yes, Dr Eric is your consultant physician on that one, I just clean the floors!

One thing that slightly bothers me is that ship models are displayed behind the mission's background image. I think we could do with a way to specify which one we want to see in the foreground, the model, or the image.
To be honest, I can't think of any particular reason anyone woud like to see a ship model behind a background image, but that's what we've got at the moment....

Posted: Wed Oct 07, 2009 8:37 pm
by Micha
Would it be possible to revamp mission screens so that players can switch between the mission screen and (possibly a subset of) the other screens?

Main use case I can think of: Be offered a mission to to something at planet X. Would be nice to be able to switch to Galactic Map screen to check where X is, before accepting the mission.

Posted: Wed Oct 07, 2009 9:13 pm
by Svengali
Can you clarify this for us scripters, please?
What happens then to scripts like:

Code: Select all

	if(this.audio) mission.setMusic('filename.ogg');
	else mission.setMusic(null);
	if(this.extraA) {
		mission.setBackgroundImage('filename'+p+'.png');
		if(flagID) mission.showShipModel('rolesname'+p);
		else mission.showShipModel('rolesname'); }
	else {
		mission.setBackgroundImage(null);
		mission.showShipModel(null); }
	mission.showMissionScreen();
	mission.setChoicesKey(null);
	if(flagMT) {
		mission.addMessageText("\n\n\n\n\n\n\n");
		mission.addMessageTextKey(m); }
	else mission.addMessageText("\n\n\n\n\n\n\n\n"+m);
This thing needs a flexible approach or a lot more code. And adding texts after showing the screen is often used in my oxps to give different reactions and actions (see Vector, last mission-stage, timer controlled). And is this approch 'static' or can the keys be passed as variables? e.g.

Code: Select all

mission.newMissionScreen({ messageKey: varOffer, choiceKey: varChoice, backgroundImage: varPic }, myVeryOwnNotification);
Or do we have to ship every possible combination then?

Re: RFC: revised mission screens for JavaScript

Posted: Wed Oct 07, 2009 9:44 pm
by Eric Walch
Ahruman wrote:
One issue I haven’t dealt with is the missionChoiceWasReset event, because frankly I can’t remember what it’s for.
I agree it is a mess as it is now. It is totally up to scripters to ensure no screens are overwritten and on the long run there will always be scripts that ignore checks. Your proposal looks good.

missionChoiceWasReset is currently needed because all scripts currently get a notification when the missionscreen ends. But when a missionscreen of script A ends with a made choice, no script other than the script A should react. It might be that script A has a second page to show and there is the risk of script B of overwriting the choice before evaluation by script A. When there is no other screen we had the missionChoiceWasReset as trigger that other scripts could start reacting. With the callback function we now ensue the choices are presented to the right script.

What is missing is a way to present multi screen presentations. Therefor my mission.offering to keep the screen claimed until ready.

When making a choice there must be a mechanisme that ensures script A also can show the next screen depending on the players choice. Probably that can be achieved with the callback function returning a boolean?

Posted: Wed Oct 07, 2009 9:48 pm
by JensAyton
Svengali wrote:
Can you clarify this for us scripters, please?
What happens then to scripts like:

Code: Select all

	if(this.audio) mission.setMusic('filename.ogg');
	else mission.setMusic(null);
	if(this.extraA) {
		mission.setBackgroundImage('filename'+p+'.png');
		if(flagID) mission.showShipModel('rolesname'+p);
		else mission.showShipModel('rolesname'); }
	else {
		mission.setBackgroundImage(null);
		mission.showShipModel(null); }
	mission.showMissionScreen();
	mission.setChoicesKey(null);
	if(flagMT) {
		mission.addMessageText("\n\n\n\n\n\n\n");
		mission.addMessageTextKey(m); }
	else mission.addMessageText("\n\n\n\n\n\n\n\n"+m);
This thing needs a flexible approach or a lot more code. And adding texts after showing the screen is often used in my oxps to give different reactions and actions (see Vector, last mission-stage, timer controlled). And is this approch 'static' or can the keys be passed as variables? e.g.

Code: Select all

mission.newMissionScreen({ messageKey: varOffer, choiceKey: varChoice, backgroundImage: varPic }, myVeryOwnNotification);
Or do we have to ship every possible combination then?
You know, if you’re going to do complex scripting, it might be worth learning the syntax of the language at some point. In any case, the equivalent of the above would be:

Code: Select all

var missionParams = {};
if (this.extraA)
{
    missionParams.backgroundImage = 'filename' + p + '.png';
    if (flagID) missionParams.shipModel = 'rolesname' + p;
    else mission.shipModel = 'rolesname';
    /* Or:
    mission.shipModel = 'rolesname';
    if (flagID) mission.shipModel += p;
    */
}
var text = "";
// Build message text here, through liberal application of expandDescription() and the + operator.
missionParams.message = text;
mission.newMissionScreen(missionParams, function(choice) { /* whatever */ });

Re: RFC: revised mission screens for JavaScript

Posted: Wed Oct 07, 2009 9:52 pm
by JensAyton
Eric Walch wrote:
When making a choice there must be a mechanisme that ensures script A also can show the next screen depending on the players choice.
In the proposed mechanism (including the bits that are only in my head at this point), showing a new mission screen from the callback would be a guaranteed way of achieving this. No script would then get missionScreenOpportunity after the first screen, because by the time the zeroth missionScreenOpportunity would be considered there would already be a new mission screen in progress. (If this isn’t very clear, don’t worry; it just means the Right Thing would happen.)

Implementation specifics: -[PlayerEntity doWorldScriptEvent:withArguments:] will get a continuePredicate parameter, which will be a pointer to a function that will be called before each script’s event handler. If the predicate returns false, no more event handlers will be invoked. The predicate for the missionScreenOpportunity will simply return true if we’re on the F5 screen, false otherwise.

Posted: Wed Oct 07, 2009 9:56 pm
by Kaks
let me count the ways:

Code: Select all

    mission.shipModel = 'rolesname';
    if (flagID) += p; 
doesn't compute. However:

Code: Select all

    mission.shipModel = 'rolesname';
    if (flagID) mission.shipModel += p; 
or even

Code: Select all

    mission.shipModel = 'rolesname' + (flagID ? p : ''); 
does!

Posted: Wed Oct 07, 2009 10:21 pm
by Svengali
Ahruman wrote:
You know, if you’re going to do complex scripting, it might be worth learning the syntax of the language at some point.
Point taken and thanks for the link :-)

Re: RFC: revised mission screens for JavaScript

Posted: Wed Oct 07, 2009 10:28 pm
by Eric Walch
Ahruman wrote:
4. Add a new event, missionScreenOpportunity, which is sent at the following times:
  • Immediately after shipDockedWithStation, if no report screen was shown.
  • Immediately after reportScreenEnded.
  • Immediately after missionScreenEnded, as long as we’re still on the F5 screen.
Unlike other events, this would not necessarily be sent to every world script, but would instead stop once a mission screen is shown. Since it’s sent again on missionScreenEnded, it will continue to be sent until every script that wants to show a mission screen has done so, unless the player launches or otherwise “escapes”.
Probably there could be also a new function to signal it wants a new opportunity. This function than decides if it again and when gets a new missionScreenOpportunity.
e.g. in the current legacy version of thargoids wars there is not immediately a screen on docking but only after some time the player gets a message the station is under attack.
Also in ups I stop mission screens to allow players to examine the destination on the galactic maps and on detection of the player leaving the galactic map, I start showing screens again. That also would stay possible with a request for a new opportunity.

Giving oolite control over which script can display a message would certainly be a great improvement over the current situation were individual script decide this.

Posted: Wed Oct 07, 2009 10:44 pm
by Eric Walch
Kaks wrote:
To be honest, I can't think of any particular reason anyone woud like to see a ship model behind a background image, but that's what we've got at the moment....
To be honest, it took me hours of work to do it in a way it still looked good. In ups the whole picture is grayscale and in the alpha channel. (Stole the technique from military fiasco) That way the background can be transparent and even the logo is a bit transparent so the ship still looks good. But I happily change it for the other way round what would make more sense and allow coloured pictures.

Re: RFC: revised mission screens for JavaScript

Posted: Thu Oct 08, 2009 12:01 pm
by Commander McLane
Sounds all fine. :)

One issue, however:
Ahruman wrote:
Proposal:
1. Deprecate ... mission.addMessageText(), mission.addMessageTextKey() ...
Honestly I am not even aware of addMessageText() (it's not in the Wiki, what does it do? Display the string in the argument?), but I am using addMessageTextKey() quite frequently, if I want some mission-dependent choice of what to display. The current runMissionScreen() method is used to set up the mission screen and display a common text, and then addMessageTextKey() adds a different final paragraph, depending on a missionVariable, the player's current credits, legal status, or whatever. Example from Anarchies:

Code: Select all

this.rippedOffByRenegades = function()
{
	// nothing happens if the player is fugitive
	if(player.bounty > 50) return
	if(!this.rippedOff)
	{
		mission.runMissionScreen("Anarchies_Renegade_Station_Rippedoff", null, "Anarchies_Renegade_Station_launch")
		missionVariables.offering = "ANARCHIES"
		// how much money have you got?
		if(player.credits < 10000)
		{
			mission.addMessageTextKey("Anarchies_Renegade_Station_Rippedoff_1")
			player.credits = 0
		}
		else
		{
			mission.addMessageTextKey("Anarchies_Renegade_Station_Rippedoff_2")
			player.credits -= 10000
		}
		this.rippedOff = true
	}
	// actually you don't have a choice. this is only to give you the chance to read the missionscreen before being launched
	else if(mission.choice == "Anarchies_LAUNCH")
	{
		player.ship.removeAllCargo()
		mission.choice = null
		this.rippedOff = null
		delete missionVariables.offering
		player.ship.launch()
		// and off you go
	}
}
This is called if the player dares to dock at a Renegade Station while not a fugitive himself. The first case is that he has less than 10000 credits. In this case the renegades simply take all his money. In the second case, if he has more than that, they take a ransom of 10000 credits. The clou is that, while this happens an alternative paragraph is displayed on the screen, either:

Code: Select all

"And, yeah, one more thing: We want a ransom. You want to keep your miserable life? Give us your miserable little money. Don't say a word, just type the transfer in your datapad. And be happy that we let you live.\n\nAnd now leave our station immediatly, and better don't come back!"
or

Code: Select all

"And, yeah, one more thing: We want a ransom. You want to keep your miserable life? Pay us 10000 ₢. Don't say a word, just type the transfer in your datapad. And be happy that we let you live.\n\nAnd now leave our station immediatly, and better don't come back!"
Another example: In Cataclysm an additional paragraph to a mission screen is displayed only if a certain thing happened before. If it didn't happen, the screen stays as it is. So addMessageTextKey() can be used to accomodate slight variations in the way a mission is completed.

The alternative way of having a mission screen with alternative text bits (and I use this way as well elsewhere), is to store some text bits in missionVariables, call the missionVariables in missiontext.plist, and then delete them again. Depending on the situation either way can make sense. (And the above example could easily be re-written in that way.) But the missionVariable way is a little more awkward (missiontext.plist cannot access script variables), especially if the script forgets to delete them after use, and they flood the save-file. Therefore addMessageTextKey() oftentimes is more elegant, and I wouldn't like to have it completely removed.

Re: RFC: revised mission screens for JavaScript

Posted: Thu Oct 08, 2009 9:33 pm
by JensAyton
Eric Walch wrote:
Probably there could be also a new function to signal it wants a new opportunity. This function than decides if it again and when gets a new missionScreenOpportunity.
e.g. in the current legacy version of thargoids wars there is not immediately a screen on docking but only after some time the player gets a message the station is under attack.
Also in ups I stop mission screens to allow players to examine the destination on the galactic maps and on detection of the player leaving the galactic map, I start showing screens again. That also would stay possible with a request for a new opportunity.

Giving oolite control over which script can display a message would certainly be a great improvement over the current situation were individual script decide this.
I’m not entirely sure what you mean by this. I have no intention of restricting mission screens to missionScreenOpportunity, it will just be used to signal a “normal” time for a mission screen so that scripts using the most common arrangement – mission screens just after docking – don’t all need the same logic.

Re: RFC: revised mission screens for JavaScript

Posted: Thu Oct 08, 2009 9:40 pm
by JensAyton
Commander McLane wrote:

Code: Select all

this.rippedOffByRenegades = function()
{
	// nothing happens if the player is fugitive
	if(player.bounty > 50) return
	if(!this.rippedOff)
	{
		mission.runMissionScreen("Anarchies_Renegade_Station_Rippedoff", null, "Anarchies_Renegade_Station_launch")
		missionVariables.offering = "ANARCHIES"
		// how much money have you got?
		if(player.credits < 10000)
		{
			mission.addMessageTextKey("Anarchies_Renegade_Station_Rippedoff_1")
			player.credits = 0
		}
		else
		{
			mission.addMessageTextKey("Anarchies_Renegade_Station_Rippedoff_2")
			player.credits -= 10000
		}
		this.rippedOff = true
	}
This was covered in my reply to Svengali, but here’s an explicit example:

Code: Select all

// nothing happens if the player is fugitive
if(player.bounty > 50) return;
if (!this.rippedOff)
{
    var text = expandDescription("Anarchies_Renegade_Station_Rippedoff");
    // how much money have you got?
    if(player.credits < 10000)
    {
        text += expandDescription("Anarchies_Renegade_Station_Rippedoff_1");
        player.credits = 0;
    }
    else
    {
        text += expandDescription("Anarchies_Renegade_Station_Rippedoff_2");
        player.credits -= 10000;
    }
    this.rippedOff = true;
    
    mission.newMissionScreen({ message: text, choiceKey: "Anarchies_Renegade_Station_launch" }, function(choice) { player.ship.launch(); });
}
There’s actually one problem here, which is that expandDescription() reads from descriptions.plist instead of missiontext.plist, but that’s trivially fixed by adding an expandMissionText().

Re: RFC: revised mission screens for JavaScript

Posted: Thu Oct 08, 2009 9:40 pm
by Eric Walch
Ahruman wrote:
I’m not entirely sure what you mean by this. I have no intention of restricting mission screens to missionScreenOpportunity, it will just be used to signal a “normal” time for a mission screen so that scripts using the most common arrangement – mission screens just after docking – don’t all need the same logic.
I was under the impression you would only allow mission screen access during that handler. When not, there is of course no reason to do a request.

And I assume legacy scripts tickle only start again after the missionScreenOpportunity is ready so there will be no conflict with those scripts.