Clone NPC - Updating code to include clone.

Raist
28 Apr 2018, 12:27

Good day everyone,
I have run into an issue with clones that I cannot seem to get around. I have 5 different monsters, each has an Attack verb which displays the attack options when selected. Of course I don't want players to continue attacking a "dead" creature, so the verb itself checks to see if the monster's current_hp > 0 (if it is, then the attack options are displayed, otherwise a message is shown stating the monster is dead).

All of these monsters are stored in a room players cannot access. There is a spawn script set to run before the player enters each room, so any of the 5 different types of monsters could spawn in any of the rooms. The spawn is handled by using clones and then moving them to the room.

Here is the problem:
In order to check for the current_hp > 0, the object must be set to the correct monster. Cloned monsters are not changing the code to include their new name.

options = Split("<span id='spvf_1'>Vas Flam</span>;<span id='spcp_1'>Corp Por</span>;<span id='spkvv_1'>Kal Vas Vlam<span>;<span id='spvof_1'>Vas Ort Flam</span>", ";")
object = bat1
if (GetInt(object, "current_hp") > 0) {
  ShowMenu ("Attack:", options, false) {
    object = bat1
    switch (result) {
      case ("<span id='spvf_1'>Vas Flam</span>") {
        msg (CommandLink("vas flam " + object.alias + "", "Vas Flam the " + object.alias + "?"))
      }
      case ("<span id='spcp_1'>Corp Por</span>") {
        msg (CommandLink("corp por " + object.alias + "", "Corp Por the " + object.alias + "?"))
       }
      default {
        PrintCentered ("Your attack failed.")
      }
    }
  }
}
else {
  PrintCentered ("The poor " + object.alias + " has been killed already. You cannot kill it twice.")
}

When bat1 is cloned, it becomes bat6 (because bat2 through bat5 already exist). How do I update the object to adopt whatever the clone name is after it has been cloned?

Thanks,
Raist


K.V.
28 Apr 2018, 13:28

Quest should handle all of that automatically.

Is the problem in Quest, or Quest JS?


K.V.
28 Apr 2018, 13:41

If you are NOT planning to convert this with Quest JS, use this script for bat1:

        options = Split("<span id='spvf_1'>Vas Flam</span>;<span id='spcp_1'>Corp Por</span>;<span id='spkvv_1'>Kal Vas Vlam<span>;<span id='spvof_1'>Vas Ort Flam</span>", ";")
        if (GetInt(this, "current_hp") > 0) {
          ShowMenu ("Attack:", options, false) {
            switch (result) {
              case ("<span id='spvf_1'>Vas Flam</span>") {
                msg (CommandLink("vas flam " + GetDisplayAlias(this) + "", "Vas Flam the " + GetDisplayAlias(this) + "?"))
              }
              case ("<span id='spcp_1'>Corp Por</span>") {
                msg (CommandLink("corp por " + GetDisplayAlias(this) + "", "Corp Por the " + GetDisplayAlias(this) + "?"))
              }
              default {
                PrintCentered ("Your attack failed.")
              }
            }
          }
        }
        else {
          PrintCentered ("The poor " + GetDisplayAlias(this) + " has been killed already. You cannot kill it twice.")
        }

In a verb script, this will point to the object. (This will mess up when using Quest JS!!!)

Also, I use GetDisplayAlias(object) rather than object.alias, because GetDisplayAlias(object) will return the object's name if an alias is not set up. (This avoids possible errors.)


K.V.
28 Apr 2018, 13:50

For Quest JS, it needs to be scripted to find the object in the current command (this will still work in Quest, as well):

        // This is safe to find this object, because the pattern will definitely include 'object' and cannot include 'object1' and 'object2'
        object = DictionaryItem(game.pov.currentcommandresolvedelements, "object")
        options = Split("<span id='spvf_1'>Vas Flam</span>;<span id='spcp_1'>Corp Por</span>;<span id='spkvv_1'>Kal Vas Vlam<span>;<span id='spvof_1'>Vas Ort Flam</span>", ";")
        if (GetInt(object, "current_hp") > 0) {
        
          ShowMenu ("Attack:", options, false) {
            // Must also add this to ShowMenu because it does not recognize local variables outside of this block
            object = DictionaryItem(game.pov.currentcommandresolvedelements, "object")
            switch (result) {
              case ("<span id='spvf_1'>Vas Flam</span>") {
                msg (CommandLink("vas flam " + GetDisplayAlias(object) + "", "Vas Flam the " + GetDisplayAlias(object) + "?"))
              }
              case ("<span id='spcp_1'>Corp Por</span>") {
                msg (CommandLink("corp por " + GetDisplayAlias(object) + "", "Corp Por the " + GetDisplayAlias(object) + "?"))
              }
              default {
                PrintCentered ("Your attack failed.")
              }
            }
          }
        }
        else {
          PrintCentered ("The poor " + GetDisplayAlias(object) + " has been killed already. You cannot kill it twice.")
        }

The Pixie
28 Apr 2018, 14:30

For Quest JS, it needs to be scripted to find the object in the current command (this will still work in Quest, as well):

this is just a local variable. Is it not possible to create it before running any script in QuestJS?


K.V.
28 Apr 2018, 14:43

this is just a local variable. Is it not possible to create it before running any script in QuestJS?

I just sent you a detailed email regarding this, but for the sake of anyone else, this is only a problem in some scripts in Quest JS.

By the time the script gets to the script dictionary on the object, this points to the DOM object's main window object.

This is probably because of the way I am cloning objects in QuestJS. The function did not exist when I began trying to make QuestJS work.


Speaking of QuestJS, check this out, Pixie:

https://github.com/KVonGit/quest-js/wiki/Disambiguation-Menu-Fix


K.V.
28 Apr 2018, 14:49

Here's the detailed message I sent Pixie, in case anyone else is interested:

I first thought "this" was messing up simply because of the way JS uses "this", but "this" works fine some script attributes, such as look.

This is the order of operations when I try to give the stick to Ralph. Everything is okay until invoke calls the script from the object's giveto dictionary.

sendCommand/<

sendCommandInternal

HandleCommand

HandleSingleCommand

HandleSingleCommandPattern

ResolveNextName

AddToResolvedNames

ResolveNextName

AddToResolvedNames

ResolveNextName

runscriptattribute3

script

HandleGiveTo

invoke (At this point, "this" still points to object1 as it should.)

Ralph (At this point, "this" points to the DOM's window object)

MoveObject (Nothing happens in-game and no errors are thrown.)

set (The DOM's window object now shows object2 as its parent!)


K.V.
28 Apr 2018, 15:04

EDIT

I thought this only had issues when using clones and certain scripts in QuestJS, but it does not matter if the object is a clone. In a "giveto" script in a game converted by QuestJS, this will cause problem whether or not the object is a clone.


K.V.
28 Apr 2018, 15:41

In a game compiled with QuestJS, everything is in line until after invoke().

I added console.log() entries to HandleGiveTo, invoke, and the actual giveto script:
image


K.V.
28 Apr 2018, 15:54

Here's the bit that concerns this from HandleGiveTo:

var parameters = NewObjectDictionary();
dictionaryadd (parameters, "this", object1);
dictionaryadd (parameters, "object", object2);
// Added to debug
console.log("Runing HandleGiveTo");
console.log("object1");
console.log(object1);
console.log("object2");
console.log(object2);
console.log("parameters");
console.log(parameters);
// END OF ADDED TO DEBUG
invoke (ScriptDictionaryItem(object1.giveto, object2.name), parameters);
var handled = true;

K.V.
28 Apr 2018, 16:00

NOTE:

It may seem that I have derailed this thread, but Raist is the main person helping me out with QuestJS and is definitely using QuestJS to compile this game.


ALSO NOTE:

I think it should be questJs.


K.V.
28 Apr 2018, 16:15

EDIT

Again, I though this was only a clone-related issue, but this is not the case. It just depends on which script attribute, and I think what REALLY matters is if the attribute is a script dictionary.


K.V.
28 Apr 2018, 16:32

I tried putting this at the beginning of the giveto script:

this = ObjectDictionaryItem(game.pov.currentcommandresolvedelements,"object1")

That throws this error, which completely breaks the game on loading:

SyntaxError: missing variable name


BUT this makes it all okay:

obj = ObjectDictionaryItem(game.pov.currentcommandresolvedelements,"object1")
JS.console.log ("Running the 'giveto' script':")
msg ("You give "+obj.article+" to Ralph.")
JS.console.log (obj)
MoveObject (obj, Ralph)

image


Raist
28 Apr 2018, 16:39

You've basically run into the wall I found , KV. I'm following the logic of your derailment though (I am indeed compiling with questJS, folks, but this could still apply to those that are not).

Maybe we should step back and ask: What makes a Clone?

What exactly is the code in Quest doing? Is there a way that Quest can be set up to Find/Replace instances of bat1 with bat1_1 in all the code associated with the original target, when a clone is created, instead of what is currently happening (can that then be replicated in questJS)?

Quest is already providing bat6 (bat7, bat8, bat9, etc) as new clones are created, so some of that code already exists, it just needs to go deeper than just the object->name (I noticed that none of the objects the bats were carrying got cloned either). I suppose the catch would be if you have to include the children or if you can exclude them during a Find/Replace.


K.V.
28 Apr 2018, 17:07

I have none of these issues in Quest. You just have to use this instead of object in a verb's script for Quest to find the right object.

I have posted way too much stuff on this thread, so it is buried, but this is how to use this in the verb scripts (to handle original objects AND clones) in the normal version of Quest:

http://textadventures.co.uk/forum/quest/topic/trletgfuzu2uho13yfa_ug/clone-npc-updating-code-to-include-clone#03ec7f44-b686-44a3-be4d-7d7897fec9de


BUT, for the QuestJS conversion, try replacing your script with this, Raist (it should work in both Quest and a QuestJS compiled game):

http://textadventures.co.uk/forum/quest/topic/trletgfuzu2uho13yfa_ug/clone-npc-updating-code-to-include-clone#5a504633-5aae-4ae7-b2a2-5aa7df9d107c


https://github.com/KVonGit/quest-js/releases

EDIT

I am on 6.2.3-alpha2 right now. It clones objects inside of objects.

(Sorry, but QuestJS has recently become bleeding edge.)


K.V.
28 Apr 2018, 17:16

The only (minor) issue Quest has when cloning an object with child objects is when a child object does not have an alias. In these instances, the child object's alias will become the clone name (so a "note" in a box would become "note1").

An example game:

<!--Saved by Quest 5.8.6689.24908-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Give JS">
    <gameid>3c14ac9d-9851-4690-af4f-9b397b95f7cb</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <object name="stick">
      <inherit name="editor_object" />
      <take />
      <feature_usegive />
      <giveto type="scriptdictionary">
        <item key="Ralph">
          // obj = ObjectDictionaryItem(game.pov.currentcommandresolvedelements,"object1")
          JS.console.log ("Running the 'giveto' script':")
          msg ("You give "+this.article+" to Ralph.")
          JS.console.log (this)
          MoveObject (this, Ralph)
        </item>
      </giveto>
    </object>
    <object name="box">
      <inherit name="editor_object" />
      <inherit name="container_closed" />
      <feature_container />
      <listchildren />
      <take />
      <look type="script">
        msg ("It is "+GetDisplayName(this)+", silly!")
      </look>
      <object name="note">
        <inherit name="editor_object" />
        <take />
        <look>If you can read this, the clone function (kind of) works!</look>
      </object>
    </object>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <beforefirstenter type="script">
      CloneObjectAndMoveHere (stick)
      CloneObjectAndMoveHere (box)
    </beforefirstenter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Ralph">
      <inherit name="editor_object" />
      <inherit name="namedmale" />
      <alt type="stringlist">
        <value>rp</value>
      </alt>
    </object>
  </object>
</asl>

BUT giving each object an alias before cloning keeps this from happening.

Example game (I only added an alias to the note object):

<!--Saved by Quest 5.8.6689.24908-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Give JS">
    <gameid>3c14ac9d-9851-4690-af4f-9b397b95f7cb</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <object name="stick">
      <inherit name="editor_object" />
      <take />
      <feature_usegive />
      <giveto type="scriptdictionary">
        <item key="Ralph">
          // obj = ObjectDictionaryItem(game.pov.currentcommandresolvedelements,"object1")
          JS.console.log ("Running the 'giveto' script':")
          msg ("You give "+this.article+" to Ralph.")
          JS.console.log (this)
          MoveObject (this, Ralph)
        </item>
      </giveto>
    </object>
    <object name="box">
      <inherit name="editor_object" />
      <inherit name="container_closed" />
      <feature_container />
      <listchildren />
      <take />
      <look type="script">
        msg ("It is "+GetDisplayName(this)+", silly!")
      </look>
      <object name="note">
        <inherit name="editor_object" />
        <take />
        <look>If you can read this, the clone function (kind of) works!</look>
        <alias>note</alias>
      </object>
    </object>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <beforefirstenter type="script">
      CloneObjectAndMoveHere (stick)
      CloneObjectAndMoveHere (box)
    </beforefirstenter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Ralph">
      <inherit name="editor_object" />
      <inherit name="namedmale" />
      <alt type="stringlist">
        <value>rp</value>
      </alt>
    </object>
  </object>
</asl>

I like to put this in my start scripts, to cover any issues concerning the lack of an alias:

foreach (o, AllObjects()){
  if (not HasAttribute(o,"alias")){
    o.alias = o.name
  }
}

Raist
28 Apr 2018, 17:49

Okay that worked, aces KV ;)
Strange though, as I remember trying THIS before and I received an error about THIS being an invalid object. It might just have been the location THIS was in that made it invalid though. I did not realize at the time the variable was not global within the entire box of code (instead of just that one section), so I only had it in one spot possibly.

I am actually using THIS as the object to change how the bat looks when you look at it depending on what state it is in.


K.V.
28 Apr 2018, 17:59

You can use this in the "look" script in a game converted with QuestJS, but not on a "giveto" script. (I don't know why.)

This is what I go by:

If my script will be called during a command with only one object, I add this to the beginning of my script attribute and use obj in place of this:

obj = DictionaryItem(game.pov.currentcommandresolvedelements, "object")

If I know the command will have two objects (object1 and object2), I change that first line I add to this:

obj = DictionaryItem(game.pov.currentcommandresolvedelements, "object1")
obj2 = DictionaryItem(game.pov.currentcommandresolvedelements, "object2")

K.V.
28 Apr 2018, 19:08

UPDATE

I originally though this only had problems in games converted by QuestJS when dealing with clones, but this is not the case. (I have updated this thread accordingly.)

I now theorize that this only has problems in games converted by QuestJS when dealing with script dictionary attributes.


mrangel
28 Apr 2018, 21:20

KV: Is your "this" variable colliding with the JS "this"?
Sounds like an interesting problem, but I don't have the time at present. Promo is always a few days of panic for me.


K.V.
28 Apr 2018, 21:46

Yes it is, mrangel, but only when dealing with script dictionary attributes like "giveto". In fact, "giveto" is the only time I've seen this happen.

I am having fun trying to find a good fix, even if I'm failing miserably. (I'm learning other good stuff along the way. You know how it is.) There may not be a fix. It may just be that you can't use this in script dictionary attributes. (Sucks for cloners!)

JS decides this is the window object just before the very last script (which is the actual "giveto" script). (The window object's parent is Ralph! Pretty funny!)


I think this is the last major issue with QuestJS, with the exception of the in-game map, which isn't necessary for game play (but I will try to add the functionality in once everything else is in working order).


I also just found a really funny thing with lists. (It drove me insane at first, until I found what the actual issue was. Now, I think it's funny.)

How about an example?

list1 = Split("Dasher;Dancer;Prancer", ";")
list2 = Split("Vixen;Comet;Cupid", ";")

In Quest, we can do list1 + list2, which will produce: "Dasher;Dancer;Prancer;Vixen;Comet;Cupid"

BUT, when JS is in charge of concatenation, list1 + list2 will output: "Dasher,Dancer,PrancerVixen,Comet,Cupid", which is a STRING (dum, Dum, DUM!!!).

This messes up the disambiguation menu, but it's a one-line change in one function (ResolveNameFromList). One only need replace list1 + list2 with ListCombine(list1, list2) and all is well.

https://github.com/KVonGit/quest-js/wiki/Disambiguation-Menu-Fix


Whenever things calm down, and if you're also bored, you can check out what we've got going on in the fork: https://github.com/KVonGit/quest-js

Just like Quest, though, this won't run under anything but Windows, even with WINE in Linux. I am not sure about Macs.


mrangel
28 Apr 2018, 21:59

I can see why that would happen.
Not sure of an easy way to fix it, though.


K.V.
28 Apr 2018, 23:27

Well, if you didn't think of one, and neither Pixie nor Pertex has chimed in, I'm thinking that the author should avoid this in script dictionary attributes (such as "giveto").

I briefly thought of replacing "this" with the required code, but I decided against it before I even wrote any code.

I can check the scripts for the string "this" during conversion. I can even be sure I'm only checking a "giveto" script, but what if the script was like this:

msg ("\"Well, this is just fabulous!\"  The NPC seems overjoyed.  \"I mean this... this is just the best thing that ever happened to me!  Thank you, for this " + GetDisplayAlias(this) + "!")
MoveObject(this,npc)

I guess if someone is converting their game to a website, they're going to have to know how to do a little fiddling with code anyway...

The script can be edited in the Quest editor before publishing, or in the game.js file which QuestJS will output, or on the fly after play begins (as long the function isn't called during the start script).

Yeah... I think this will just be one of those things. I display a popup concerning this when opening QuestJS, so... it should be okay.


mrangel
30 Apr 2018, 16:47

I'm struggling to get my head around all the C# code. Are there any example converted games out there?

I think I see the problem; but not entirely sure. I'd like to see what a function that calls invoke with parameters looks like after JS conversion. (Is there an invoke function in your output game.js? If I'm reading the code correctly I'd expect there to be; I'd like to see that code)

(As far as I can understand, in Quest this is a variable, but is assigned . In javascript, this is a keyword that behaves like a veriable in a lot of cases, but really isn't one. In particular, it does not obey normal scope rules. If you're used to programming in C, it might be more practical to imagine that the this keyword is implemented as a compiler macro, and be suitably cautious when assigning it to anything)


K.V.
30 Apr 2018, 17:27

I'll post a game in an hour or two.


K.V.
30 Apr 2018, 19:17

It may be another hour before I get to my computer, but I will post code and theories.


K.V.
30 Apr 2018, 21:45

Okay...

Here is the .aslx, just for completion's sake:

<!--Saved by Quest 5.8.6689.24908-->
<asl version="550">
  <include ref="English.aslx" />
  <include ref="Core.aslx" />
  <game name="Give JS">
    <gameid>3c14ac9d-9851-4690-af4f-9b397b95f7cb</gameid>
    <version>1.0</version>
    <firstpublished>2018</firstpublished>
    <object name="stick">
      <inherit name="editor_object" />
      <take />
      <feature_usegive />
      <givesingle />
      <look type="script">
        msg ("You see nothing special about "+this.article+".")
      </look>
      <giveto type="scriptdictionary">
        <item key="Ralph">
          JS.console.log ("Running the 'giveto' script':")
          msg ("You give "+this.article+" to Ralph.")
          JS.console.log (this)
          MoveObject (this, Ralph)
        </item>
      </giveto>
    </object>
    <object name="box">
      <inherit name="editor_object" />
      <inherit name="container_closed" />
      <feature_container />
      <listchildren />
      <take />
      <feature_usegive />
      <givesingle />
      <look type="script">
        msg ("It is "+GetDisplayName(this)+", silly!")
      </look>
      <giveto type="scriptdictionary">
        <item key="Ralph">
          obj = This()
          msg ("You give "+obj.article+" to Ralph.")
          MoveObject (obj, Ralph)
        </item>
      </giveto>
      <object name="note">
        <inherit name="editor_object" />
        <take />
        <look>If you can read this, the clone function (kind of) works!</look>
        <alias>note</alias>
      </object>
    </object>
  </game>
  <object name="room">
    <inherit name="editor_room" />
    <beforefirstenter type="script">
      CloneObjectAndMoveHere (stick)
      CloneObjectAndMoveHere (box)
    </beforefirstenter>
    <object name="player">
      <inherit name="editor_object" />
      <inherit name="editor_player" />
    </object>
    <object name="Ralph">
      <inherit name="editor_object" />
      <inherit name="namedmale" />
      <inherit name="surface" />
      <alt type="stringlist">
        <value>rp</value>
      </alt>
      <feature_container />
      <contentsprefix>who is carrying</contentsprefix>
      <listchildren />
      <listchildrenprefix>He is carrying</listchildrenprefix>
    </object>
  </object>
  <function name="This" type="object">
    if (DictionaryContains(game.pov.currentcommandresolvedelements,"object")) {
      return (DictionaryItem(game.pov.currentcommandresolvedelements,"object"))
    }
    else if (DictionaryContains(game.pov.currentcommandresolvedelements,"object1")) {
      return (DictionaryItem(game.pov.currentcommandresolvedelements,"object1"))
    }
  </function>
</asl>

The stick has this in the giveto script, but the box is checking for object or object1 in game.currentcommandresolvedelements (this is the best workaround I could think of).

So, you can give the box to Ralph, but giving the stick to him will fail.

Also, when examining either object, I use a script with this in each one, just to show that it will work when the attribute is not a script dictionary.


Here is the game after converting it:

http://textadventures.co.uk/games/view/pn49r_pudugw57axvilpgg/givejs

If you enter MENU (or just click the button at the top-right to open the menu), you will see a button which will download all the game files in a ZIP.


mrangel
01 May 2018, 09:59

Also, when examining either object, I use a script with this in each one, just to show that it will work when the attribute is not a script dictionary.

I don't think it matters that it's a script dictionary. I think your implementation of invoke is broken.
Examining an object doesn't use invoke, it uses runscriptattribute2, which seems to be equivalent to Quest's do.

You have:

function invoke(script, parameters) {
    if (parameters) {
        script.apply(null, [parameters["result"]]);
    } else {
        script();
    }
}

This seems to be called by HandleGiveTo, so I'd expect it to be similar to Quest's invoke. But in this case, the parameters array contains two items: "this" and "object". invoke seems to ignore any parameter that isn't named "result".

If it's passing "object" but not "this", then I'm extremely confused.

I was expecting to see something more like:

function invoke(script, parameters) {
    if (parameters) {
        script.call(parameters["this"], parameters);
    } else {
        script();
    }
}

But the presence of "result" specifically makes me think that "invoke" has been turned into a special case for use with Ask, ShowMenu, etc … thus breaking any other cases where it might be used (such as useon, giveto, etc).

But… you said it's only "this" that is a problem. So what am I missing?


mrangel
01 May 2018, 11:39

OK ... a script attribute gets compiled to a Javascript function which takes a plain object as its parameter, and has a few lines stuck on the beginning to extract local variables out of that object. This effectively means that at the time it's compiled, a script attribute needs to know all the attributes that can be passed to it.

The runscriptattribute functions pass on a plain object in this way.
However, the function passed to ShowMenu expecting a single argument, the "result" variable.

So there's two different ways in which Quest scripts are converted to javascript functions, and they're not interoperable.

It's not just script dictionaries. The invoke (script, parameters) syntax discards all parameters not named "result".

Unless I'm really missing something, this is going to be a horrible job to fix. But it's going to keep causing problems right through development. I can see many issues, which QuestJS will be unable to deal with because it has no way of knowing what the player expects. The only way I can think of is to make the conversion of Quest script into JS functions consistent. And the only way I can see to do that is some rather hairy use of eval, to inspect the keys in the parameters dictionary at runtime.

I tried poking many different ways of doing this at compile time, and for all of them I found some edge case that I could reasonably expect a Quest user to do.

I'm thinking now of an invoke that looks more like…

function invoke (script, parameters) {
  if (parameters) {
    var fn = script.toString();
    if (fn.match(/^[\s\w]*\(\)\s*\{/)) {
      fn.replace(/^[\s\w]*\(\)/, "function ("+parameters.keys().join(",")+")");
      eval("("+fn+")")(parameters.values());
    } else {
      // It's a proper JS function that's expecting arguments
      script.call(parameters["this"] || null, parameters);
    }
  } else {
    script();
  }
}

Ugly, but it should work with all code that currently works in Quest.
(note - this will behave oddly with QuestJS ShowMenu at present; because the compiler currently handles the conversion of a script to JS differently if it's the script argument to ShowMenu)


K.V.
01 May 2018, 12:05

TypeError: parameters.keys is not a function


If I can get that to work, I think I can handle ShowMenu (maybe).


mrangel
01 May 2018, 13:55

Oh, I was being careless there trying to get my thoughts across. I assumed that the dictionary parameters was a plain object (which is the JS structure closest to a Quest dictionary). In which case keys() and values() will return arrays of keys and values respectively.

My version of invoke converts a JS function back to its scope, uses a regexp replace to change function () { to function (object1, object2) { based on the keys in the parameters dictionary, evals that function, and then calls the returned function with the values of the parameters object.

Wait... one silly error.
eval("("+fn+")")(parameters.values()); should be eval("("+fn+")").apply(null, parameters.values());

But you need to mess about a little to make 'this' work.

function invoke (script, parameters) {
  if (parameters) {
    var fn = script.toString();
    var call_this = null;
    if (parameters["this"]) {
      call_this = parameters["this"];
      delete parameters["this"];
    }
    if (fn.match(/^[\s\w]*\(\)\s*\{/)) {
      fn.replace(/^[\s\w]*\(\)/, "function ("+parameters.keys().join(",")+")");
      eval("("+fn+")").apply(call_this, parameters.values());
    } else {
      // It's a proper JS function that's expecting arguments
      script.call(call_this, parameters);
    }
  } else {
    script();
  }
}

(still needs tweaking to handle parameters properly. Maybe looping over it, if keys() and values() aren't working.

One thing I notice with the existing code is that:

ShowMenu ( ...other arguments....) {
  msg ("You chose "+result)
}

will be turned into:

ShowMenu (blah blah blah, function (result) {
  msg(result);
});

But

callback => {
  msg (result)
}
ShowMenu (arguments, callback)

will compile as (as far as I can tell)

var callback = function () {
  msg(result);
}
ShowMenu (blah blah blah, callback);

Which will not work, because 'result' is undefined within the function.


K.V.
01 May 2018, 13:58

Yeah... This stuff is crazy, huh, mrangel? (Perhaps that's why Alex never completed it!)


K.V.
04 May 2018, 00:22

I think this may be important code:

            public override void Save(GameWriter writer, Element element, string attribute, object value, bool isFinal)
            {
                IScript script = (IScript)value;
                string savedScript = script.Save(new Context());
                if (savedScript.Trim().Length > 0)
                {
                    // TO DO: Will need to extract variables for parameters to a "useanything" script in the same way

                    if (element.Type == ObjectType.Command)
                    {
                        List<string> variables = GetCommandPatternVariableNames(element);
                        string commandVariables = string.Empty;

                        foreach (string variable in variables)
                        {
                            commandVariables += string.Format("var {0} = parameters['{0}'];\n", variable);
                        }

                        savedScript = commandVariables + savedScript;
                        base.WriteAttribute(writer, element, attribute, string.Format("function(parameters) {{ {0} }}", savedScript), isFinal);
                    }
                    else
                    {
                        string parameters = string.Empty;
                        if (attribute.StartsWith("changed"))
                        {
                            parameters = "oldvalue";
                        }
                        base.WriteAttribute(writer, element, attribute, string.Format("function({1}) {{ {0} }}", savedScript, parameters), isFinal);
                    }
                }
            }

This maybe even more so:

        private class ScriptDictionarySaver : DictionarySaverBase<IScript>
        {
            public override Type AppliesTo
            {
                get { return typeof(QuestDictionary<IScript>); }
            }

            protected override string ValueSaver(IScript value)
            {
                return string.Format("function() {{ {0} }}", value == null ? string.Empty : value.Save(new Context()));
            }
        }

        private class ObjectReferenceSaver : FieldSaverBase
        {
            public override Type AppliesTo
            {
                get { return typeof(Element); }
            }

            public override void Save(GameWriter writer, Element element, string attribute, object value, bool isFinal)
            {
                Element reference = (Element)value;
                if (writer.IsElementWritten(reference))
                {
                    base.WriteAttribute(writer, element, attribute, ((Element)value).MetaFields[MetaFieldDefinitions.MappedName], isFinal);
                }
                else
                {
                    writer.AddPostElementScript(element, string.Format("objectReferences.push([\"{0}\", \"{1}\", \"{2}\"]);",
                        element.MetaFields[MetaFieldDefinitions.MappedName],
                        attribute,
                        reference.MetaFields[MetaFieldDefinitions.MappedName]));
                }
            }
        }

mrangel
04 May 2018, 01:18

Ugh ... I thought it might be doing that, but really hoped it wasn't.

So, the "do" and "invoke" methods only work with specific parameter names; and the names that work with each are different. It's a whole bunch of special cases stacked on top of each other; and worse, evaluated at a point where the full information isn't available.

Thanks, I know where I need to be looking now. I think I can see my way around that, but working in an unfamiliar language, so... don't hold your breath. I think it should be determining what parameters a script expects based on the script (referencing local variables) rather than the attribute name or element type.


K.V.
04 May 2018, 02:25

I think it should be determining what parameters a script expects based on the script (referencing local variables) rather than the attribute name or element type.

This is what I was thinking too.

Alex uses this to do it in the command's scripts (which explains why "this" works in normal script attributes):

List<string> variables = GetCommandPatternVariableNames(element);

I'm going to try adding this into the script dictionary bit.

What is there? ask, tell, use, give... I'm missing at least one...


Also, now that the text processor uses a dictionary, games made with the recent build of Quest don't work at all after the conversion.


Another ISSUE (all-caps) with QuestJS is how it will deal with errors in the game's code in the .quest file. Let's say you forget to add a closing quotation mark... Yeah. EVERYTHING that's written to the game.js file after that point is messed up. (I assume this is due to the way JS files are read by the browser.)

...and the lists in the panes and the functions which go along with then are different than what's in Quest.

...and anything added to playercore.js or desktopplayer.js throws an error. Quest depends on those files being global (or in Quest's directory, if that's the wrong terminology), so they aren't copied into the .quest file.


So...

First, I thought we could fix QuestJS, but now I'm thinking that even if we do get it working, we'll just have to keep applying patches every time something changes in Quest.

Maybe Alex was right to abandon the project. Heck, I think using NodeJS to parse the .aslx then converting that would be a better option. This would be cross-platform, and it would work pretty much just like QuestJS, except it would ACTUALLY work.

I had a half-fast prototype halfway written that I had packaged in Electron, but Electron apps are like 100MB when your source files are less than 100KB, so now I'm thinking maybe GTK might be the way to go. As long as it's something that can handle embedded Chromium so we can use the same GUI Quest has now in the end. (I figure once we have it where it will actually convert a game it would be easy to just make Quest write to websites, which the GTK Quest would run in your browser.)


mrangel
04 May 2018, 08:45

(which explains why "this" works in normal script attributes):

'this' is a special case, and isn't passed as a parameter. That line's responsible for the other parameters that work, though.

Quest's "do" command converts into a JS function which sets the context correctly, so you can pass 'this'. It will also pass other parameters correctly, if the script attribute is on a Command element and the parameters in the dictionary are the same as the named expressions in the command's pattern.

Quest's "invoke" command converts into a JS function which passes the single parameter "results" and discards the rest. If the script was originally defined using the variable => { syntax it won't be expecting any parameters, so won't even receive "result". If the script was created on the end of an Ask/ShowMenu/etc line, it will accept "result". If it was created as a script attribute on a command script, it will be expecting a dictionary of parameters rather than a single result.

So… if your params dictionary contains the key "result", then invoke (lookat.script, params) will give an error complaining that parameters is not a dictionary. That could be very confusing for any users who attempt it.

I'm still running the .cs code through in my head, jumping around between the different files to work out what it will do, as I can't run it on my system.
What I'm currently looking for is a piece of code which adds var statements to the JS. Unfortunately, flaky net connection currently prevents me from checking out the project. Looking at your converted game, it seems that "var" is inserted before the name of each local variable before it is first assigned. I'm hoping this is generated by the code that should be in the Context object, in which case we could build a list of local variables that are referenced anywhere in the script, and then use that list to load them all from the parameters dictionary at the start.


mrangel
04 May 2018, 16:46

(sorry, idiotic mistake in the JS quoted above;

parameters.keys() should be Object.keys(parameters). And the same for values().

If I can make sure there's no bugs in it, this alternate form of invoke (and a corresponding change to the two do functions) should make the code above (the special case for ShowMenu, the special case for commands, and all the cases that don't actually work) redundant. It's a lot easier to do this with JS code that knows what variables it has, rather than C# code that's pretty much guessing what variables the JS code might have.


K.V.
04 May 2018, 22:49

It's a lot easier to do this with JS code that knows what variables it has, rather than C# code that's pretty much guessing what variables the JS code might have.

Does the compiler need know to add the parameter to the script?

Right now, it prints this:

"giveto": {"Ralph": function(){ MoveObject(this,Ralph);}}

Doesn't it need to print this?

"giveto": {"Ralph": function(this){ MoveObject(this,Ralph);}}

...and if so, wouldn't this as a variable cause JS to throw an error, without running the script?

I don't know. "this" is confusing me when trying to think about it from two perspectives. I'm just throwing theories out there.


K.V.
04 May 2018, 23:07

I can change invoke() to this:

function invoke(script, parameters) {
	console.log(script.toString());
    if (parameters) {
        if(parameters["result"]){
			script.apply(null, [parameters["result"]]);
		}else if(parameters["this"]){
			script.apply(null, [parameters["this"]]);
		}
    } else {
        script();
    }
}

...and change the giveto script to this:

"giveto": {"Ralph": function(result) { console.log ("Running the 'giveto' script':")
OutputText ("You give "+result.article+" to Ralph.");
console.log (result)
MoveObject (result, _obj364); } },

I think every this in the script will have to be changed to something else for this to work.

When I change the code to this:

"giveto": {"Ralph": function(this) 

I get this error: SyntaxError: missing formal parameter


Changing it to this:

"giveto": {"Ralph": function(result) { var this = result; 

throws this: SyntaxError: missing variable name.


And, just to be thorough:

"giveto": {"Ralph": function(result) { this = result;

throws this: ReferenceError: invalid assignment left-hand side.


So, we'd have to use replace() on every instance of "this" which was a variable, but how could we tell that from dialog?

msg ("You give " + this.article + " to Ralph.<br/><br/>\"Thanks!\"  Ralph grins from ear to ear.  \"I've been looking all over for this!!!\"")
MoveObject(this, Ralph)

This is what makes me think this is a lost cause as far as ANYONE AND EVERYONE being able to convert a game with it. It seems that you'd have to know to not use this, which requires some dictionary knowledge when dealing with clones.


I'd still like to get it working besides this, though. And I think I have learned how from your suggestions.

More on this as it comes in.


mrangel
05 May 2018, 00:01

Stop trying to treat this as a variable.

In your "invoke" script, you'll see that the first parameter to script.apply() is null.
If you specify a non-null variable there (such as parameters["this"]), you will be able to use this within the function and it will work properly.

So your invoke function would be:

function invoke(script, parameters) {
	console.log(script.toString());
    if (parameters) {
        if(parameters["result"]){
			script.apply(null, [parameters["result"]]);
		} else if(parameters["this"]){
			script.apply(parameters["this"], [parameters]);
		}
    } else {
        script();
    }
}

mrangel
05 May 2018, 00:14

Does the compiler need know to add the parameter to the script?

No. But that's what it's currently trying to do, and failing.

Right now, it prints this:
"giveto": {"Ralph": function(){ MoveObject(this,Ralph);}}
Doesn't it need to print this?
"giveto": {"Ralph": function(this){ MoveObject(this,Ralph);}}
...and if so, wouldn't this as a variable cause JS to throw an error, without running the script?

Yes. What we want is for it to do that for every parameter except for this.

But the compiler cannot do that correctly. There is no way for it to determine what parameters will be passed to that functionAny attempt to make it work will result in more edge cases, and more specific variables that don't work. By trying to make the compiler generate those lines, you are adding a lot of complexity to handle a lot of different cases.

You could find the function which adds the var keyword to the function's local variables, get it to record a list of them in an internal data structure, and then use that list to determine the parameters for the generated JS function. That would be a lot better, but it would also be a lot of added complexity.

That's why I suggested modifying invoke so that it modifies the function when the code is running, so that the function accepts the parameters that were actually passed. This will generate error messages in the exact same circumstances Quest does. (assuming that the JS version of IsDefined works correctly)

Sorry, got to dash, RL calls.
You probably gather by now that I'm not very good at explaining what I mean in English.


K.V.
05 May 2018, 01:20

Ha!

You got it!

Changing only this fixed the giveto script!!!

function invoke(script, parameters) {
	console.log(script.toString());
    if (parameters) {
        if(parameters["result"]){
			script.apply(null, [parameters["result"]]);
		} else if(parameters["this"]){
			script.apply(parameters["this"], [parameters]);
		}
    } else {
        script();
    }
}

The "giveto" script, just like QuestJS printed it:

"giveto": {"Ralph": function() {console.log ("Running the 'giveto' script':")
OutputText ("You give "+this.article+" to Ralph.");
console.log (this)
MoveObject (this, _obj364); } },

...and I was thinking of this as a variable.

...but I am much better now!


K.V.
05 May 2018, 03:21

You rock, mrangel!!!


Hey, everybody!

Check out some of mrangel's books!!!

https://www.amazon.com/Angel-Wedge/e/B00N5Q5XIK/ref=dp_byline_cont_ebooks_1


K.V.
05 May 2018, 15:54

Hrmm...

// line 4511
"textprocessorcommands": {"if ": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_If (section, data)); }, "either ": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Either (section, data)); }, "here ": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Here (section, data)); }, "nothere ": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Here (section, data)); }, "popup:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Popup (section, data)); }, "i:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Format (section, data)); }, "b:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Format (section, data)); }, "s:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Format (section, data)); }, "u:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Format (section, data)); }, "colour:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Colour (section, data)); }, "color:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Colour (section, data)); }, "back:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Colour (section, data)); }, "object:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Object (section, data)); }, "command:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Command (section, data)); }, "page:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Command (section, data)); }, "exit:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Exit (section, data)); }, "once:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Once (section, data)); }, "notfirst:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_NotFirst (section, data)); }, "random:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Random (section, data)); }, "rndalt:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_RandomAlias (section, data)); }, "img:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Img (section, data)); }, "counter:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Counter (section, data)); }, "select:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Select (section, data)); }, "eval:": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Eval (section, data)); }, "=": function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Eval (section, data)); } }
};

Here's the invoke() I'm testing in this game:

// begins at line 1997
function invoke(script, parameters) {
    console.log(script.toString());
    if (parameters) {
        if (parameters["result"]) {
			console.log("result");
			console.log(parameters);
			console.log(parameters["result"]);
            script.apply(null, [parameters["result"]]);
        } else if (parameters["this"]) {
			console.log("this");
			console.log(parameters);
			console.log(parameters["this"]);
            script.apply(parameters["this"], [parameters]);
        } else if (parameters["section"]) {
			console.log("section");
			console.log(parameters);
			console.log(parameters["section"]);
            script.apply(parameters["section"], [parameters]);
        }
		else {
			console.log("else");
			console.log(parameters);
			console.log(parameters[Object.keys(parameters)[0]]);
            script.apply(parameters[Object.keys(parameters)[0]], [parameters]);
        }
    } else {
        script();
    }
}

And:

function() { set(_obj294, "textprocessorcommandresult", ProcessTextCommand_Object (section, data)); }
game.js:1981:5
section
game.js:1994:4
Object { section: "object:stick", data: {…} }
game.js:1995:4
object:stick

ReferenceError: section is not defined[Learn More] game.js:3511:1381
	object: file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:3511:1381
	invoke file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:1997:13
	ProcessTextCommand file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:11281:1
	ProcessTextSection file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:11266:13
	ProcessText file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:11230:12
	OutputText file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:11133:16
	ShowRoomDescription file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:10612:1
	OnEnterRoom/</</< file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:10716:1
	on_ready file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:2423:9
	OnEnterRoom/</< file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:10695:1
	on_ready file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:2423:9
	OnEnterRoom/< file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:10692:1
	on_ready file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:2423:9
	OnEnterRoom file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:10689:1
	StartGame/< file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:13570:1
	on_ready file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:2423:9
	StartGame file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:13566:1
	worldModelBeginGame file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:1562:5
	init file:///C:/Users/keega/Documents/qjs/580testtesttest/game.js:131:9
	onBodyLoad file:///C:/Users/keega/Documents/qjs/580testtesttest/index.html:29:13
	onload file:///C:/Users/keega/Documents/qjs/580testtesttest/index.html:1:1


mrangel
05 May 2018, 17:17

KV: I'm not sure why you're doing that.

I just wrote a big post, but under a lot of stress right now and it ended up sounding too condescending and argumentative, so I deleted that.

Can you tell me what's wrong with my method, please?
It's an interesting enough problem trying to make it work; but we seem to be aiming for different goals.

This is what I got so far, off the top of my head. After debugging and fixing the inevitable typos, this should make invoke work exactly as it does in Quest (including giving errors in the same circumstances). Unlike my previous attempt, it should work around the existing "special cases" in QuestJS.

function invoke (script, parameters) {
  if (parameters) {
    var apply_this = null;
    if (parameters["this"]) {
      apply_this = parameters["this"];
      delete parameters["this"];
    }

    var parsed = script.toString().match(/^\s*function \((.*?)\)\s*\{(.*)\}\s*$/);
    if (!parsed) {
      // FIXME: "script" parameter to invoke is not a script.
      // How does QuestJS do error handling?
    } elsif (parsed[2]) {
      var formal_params = parsed[1];
      if (formal_params) {
        formal_params = formal_params.split(/\s*,\s*/);
      } else {
        formal_params = Object.keys(parameters);
        script = eval("( function ("+formal_params.join()+") {"+parsed[2]+"\n})");
      }
      var actual_params = [];
      $.each(actual_params, function (i, key) {
        if ((key == "parameters") && !paramteres["parameters"]) {
          actual_params.push(parameters);
        } else {
          actual_params.push(parameters[key]);
        }
      });
      script.apply(apply_this, actual_params);
    }
  } else {
    // no parameters. Easy.
    script();
  }
}

K.V.
05 May 2018, 18:22

I just wrote a big post, but under a lot of stress right now and it ended up sounding too condescending and argumentative, so I deleted that.

Sorry about the stress.

Don't worry about feelings, because I'm cool, I promise. If I'm doing something that makes no sense, put me in check (unless that causes extra stress). Or ignore me. (Basically, my primary concern is to not create any stress.)


Can you tell me what's wrong with my method, please?

There are too many posts (which is my doing), and I didn't know which was the most recent method. (In fact, I thought I was barely changing your most recent method.)

I'm going to plug your code in now.

Sorry again, and don't hold back on me. I can take it. You can message me things you feel may be taken offensively, just so everyone else doesn't thing we're arguing or something. But I'm cool. I need to be yelled at sometimes. Unless that would stress you out further, in which case don't yell at me.

Ah... Now I'm just babbling. You get the drift. I appreciate the help, and I learn from it (sometimes slowly). I'm not saying don't help me, I'm just saying I'd rather it not be detrimental to your well-being. And that there is no need to be polite or politically correct when I need to be put in check.


EDIT

In case it helps to solve some mystery, I've been using this, which seems to work in almost every case, except for text processor stuff:

function invoke(script, parameters) {
    console.log(script.toString());
    if (parameters) {
        if (parameters["result"]) {
            script.apply(null, [parameters["result"]]);
        } else if (parameters[Object.keys(parameters)[0]]) {
			console.log(parameters[parameters[Object.keys(parameters)[0]]]);
            script.apply(parameters[parameters[Object.keys(parameters)[0]]], [parameters]);
        }
    } else {
        script();
    }
}

...and I'm off to try that code!


mrangel
05 May 2018, 19:42

*fingers crossed *

I hope that code isn't too hard to follow :S
The intention is that if you do something like:

params = NewDictionary()
dictionary add (params, "this", someObject)
dictionary add (params, "that", "What's that?")
dictionary add (params, "object", anotherObject)
invoke (someScript, params)

if will look at someScript, and search-replace function () { at the start with function (that, object) {.
Then it calls script.apply(someObject, ["What's that",anotherObject]) on the newly eval'ed script, so that "this", "that", and "object" within that script all have their correct meanings.

It's more complex than the original version I posted above, so that if the script already starts with function (some, params) it will (hopefully) pull the right items out of the parameters dictionary, stick them in the array, and pass them to the script. This is for the benefit of any scripts (such as the ShowMenu script) that already have formal parameters.

If this works properly (probably best to test it in a few contexts. Try creating script attributes, script dictionaries, and scripts created using the => operator, and then use invoke on all of them. With the parameters they expect, with the wrong parameters, with no parameters, and with extra parameters), then we should probably do the same to the two scripts that do maps onto, so they handle parameters that aren't necessarily the same as the command's arguments.

And once that works, it might be possible to remove some of the special cases (which I think will at least partially break the advanced scope scripts, and possibly other things as well)


K.V.
05 May 2018, 20:25

Ah, I see.

Making some progress now.

It writes the functions it converts like this:

function GetScore()
{
return (_obj414.score);
}

...so I had to change this line to this:
var parsed = script.toString().match(/^\s*function \((.*?)\)\n*\{(.*)\}\n*$/);

I may have messed it up, but this gets the closest to making things work. This is what prints to the screen:

You can see a {object:spellbook1} and the Glorious Lady {object:lady1} the Legendary Ranger.

You can go {exit:k14} to the teleporter.

The entire invoke() script:

EDITED (version 2)

function invoke (script, parameters) {
  if (parameters) {
    var apply_this = null;
    if (parameters["this"]) {
      script.apply(parameters["this"], [parameters]);
	  return;
    }
    var parsed = script.toString().match(/^(\s*|)function(\s*|)\((.*?)\)\s*\n*\{(.*)\}\n*$/);
    if (!parsed) {
      // FIXME: "script" parameter to invoke is not a script.
      // How does QuestJS do error handling?
	  console.log(script);
	  error ("FIXME: \"script\" parameter to invoke is not a script.");
    } else if (parsed[2]) {
      var formal_params = parsed[1];
      if (formal_params) {
        formal_params = formal_params.split(/\s*,\s*/);
      } else {
        formal_params = Object.keys(parameters);
        script = eval("( function ("+formal_params.join()+") {"+parsed[2]+"\n})");
      }
      var actual_params = [];
      $.each(actual_params, function (i, key) {
        if ((key == "parameters") && !paramteres["parameters"]) {
          actual_params.push(parameters);
        } else {
          actual_params.push(parameters[key]);
        }
      });
      script.apply(apply_this, actual_params);
    }
  } else {
    // no parameters. Easy.
    script();
  }
}

K.V.
05 May 2018, 22:32

Don't worry with this anymore, unless you just want to for yourself, mrangel.

Fixing each issue just leads to another problem.

I declare my fork of QuestJS officially abandoned (for at least 5 minutes).

Thank you for all of the help, mrangel. I do sincerely appreciate it, and I think I may have learned how to make a GTK app to do what QuestJS is supposed to be doing. (I'll post about that if it isn't just a pipe dream.)

Thanks so much, again! I really, really do appreciate it.


K.V.
06 May 2018, 03:47

For archival purposes

Here is a link to the entire JS file:

https://gist.github.com/KVonGit/c033fd1b60d7f12026563ac04de4d780

The 'core' of the file ends at line 3112. Everything after that is what is added by QuestJS during the conversion.


A link to either play online or download a ZIP containing all of the game's files.

http://media.textadventures.co.uk/games/86x5feL9bk_pCkQm90uRFQ/index.html