Overriding core commands and verbs.

In my project the player can die, but instead of ending the game the player effectively becomes a ghost. He can walk around, look at things but cannot, for example, take things or smell things.

With core commands (i.e take) I can copy them to my project and add a check to the script.

With core verbs (i.e smell) I can copy them and make the script editable, in attributes and add a check there.

My question is: Is this the best way to achieve what I want or is there an easier way of overriding commands and verbs under certain conditions.


That seems like a fairly common way to do it. There are two other options that come to mind; which one is easiest will depend on your use cases.

  1. Commands that are in a room override global commands, but only apply when the player is in that room. So you could create a separate set of ghost commands for when the player is dead, and put them in an unreachable room somewhere (like a holding area)
    When the player dies, you would move those commands to be global (cmd.parent = null), and either move the standard commands to the holding area, or destroy them.

  2. You could give some commands an extra script attribute for its behaviour when the player is dead; call it ghost_script. Then when the player dies:

    foreach (cmd, AllCommands()) {
    if (HasScript (cmd, "ghost_script")) {
    cmd.script = cmd.ghost_script
    }
    }

  3. If you're just disabling commands:

    foreach (cmdname, Split("take;put;push;open;close;kick;use;eat")) {
    cmd = GetObject(cmdname)
    cmd.script => {
    msg ("You can't do that, you're dead.")
    }
    }

Which one works best for you will likely depend on how many commands are involved, and how much change you want to make to them.

If you can come back to life after being a ghost, you could save the original behaviour in a separate script attribute.
For example, when the player dies you could run:

foreach (cmdname, Split("take;put;push;open;close;kick;use;eat")) {
  cmd = GetObject(cmdname)
  cmd.alive_script = cmd.script
  cmd.script => {
    msg ("You can't do that, you're dead.")
  }
}

and when they come back to life:

foreach (cmd, AllCommands()) {
  if (HasScript (cmd, "alive_script")) {
    cmd.script = cmd.alive_script
  }
}

It's also worth noting that verbs are also commands: they just all have the same script (which is inherited from the defaultverb type), which just checks if the target object has a script for that verb, and runs it. So the above method with Split("list of command names") works fine with verbs. This could end up being a lot quicker than including a check on every object that has a verb.

If you're doing more fancy things - for example, a ghost might be able to see some kind of spiritual aura around an object as well as looking at it normally, you could add something like:

lookat.alive_script = lookat.script
lookat.script => {
  do (this, "alive_script", game.pov.currentcommandresolvedelements)
  if (HasScript (object, "ghostlook")) {
    do (object, "ghostlook")
  }
  else if (HasString (object, "ghostlook")) {
    msg (object.ghostlook)
  }
}

By using do (this, "alive_script", game.pov.currentcommandresolvedelements) you can execute extra functions before (or after) the command's default behaviour, such as displaying an extra description. And because it uses the same alive_script attribute as the script above, it will work like magic with the "coming back to life" script above.


Thanks mrangel, disabling commands is effectively what I'm doing so your third option looks like it will work best.

Thanks for the other examples.

do (this, "alive_script", game.pov.currentcommandresolvedelements)

I can definitely make use of this. thanks again.


Further to this:

Is it possible to override all commands/verbs when dealing with a specific object. For example: a poisonous snake will kill you if you interact with it in any way.

I could add a verb override for each verb in the objects verbs tab, commands are trickier. Is there an easy way using something similar to mrangel's solutions to my other query above?

Another possible solution would be a turnscript that actions before the players command is acted upon. Is this possible?


That sounds like it would be a useful feature to have; and should be relatively easy to implement in a way, if you don't mind editing one of the built-in functions. (In the desktop editor I believe you can copy the core functions so you can edit them). Off the top of my head:

Click to see old code. The solution in my next post is better
  <function name="AddToResolvedNames" parameters="var, result">
    if (TypeOf(result) = "object") {
      if (result.type = "object") {
        list add (game.pov.currentcommandresolvedobjects, result)
        if (HasAttribute (result, "limitcommands")) {
          if (not ListContains (result.limitcommands, game.pov.currentcommandpattern)) {
            if (not DictionaryContains (game.pov.currentcommandresolvedelements, "command")) {
              dictionary add (game.pov.currentcommandresolvedelements, "command", game.pov.currentcommandpattern)
            }
            if (HasObject (result, "defaultcommand")) {
              game.pov.currentcommandpattern = result.defaultcommand
            }
            else {
              game.pov.currentcommandpattern = result
            }
            if (HasString (result, "defaultcommandparameter")) {
              var = result.defaultcommandparameter
            }
          }
        }      }
    }
    else if (TypeOf(result) = "objectlist") {
      foreach (obj, result) {
        if (obj.type = "object") {
          list add (game.pov.currentcommandresolvedobjects, obj)
          if (HasAttribute (obj, "limitcommands")) {
            if (not ListContains (obj.limitcommands, game.pov.currentcommandpattern)) {
              if (not DictionaryContains (game.pov.currentcommandresolvedelements, "command")) {
                dictionary add (game.pov.currentcommandresolvedelements, "command", game.pov.currentcommandpattern)
              }
              if (HasObject (obj, "defaultcommand")) {
                game.pov.currentcommandpattern = obj.defaultcommand
              }
              else {
                game.pov.currentcommandpattern = obj
              }
              if (HasString (obj, "defaultcommandparameter")) {
                var = obj.defaultcommandparameter
              }
            }
          }        }
      }
    }
    if (DictionaryContains (game.pov.currentcommandresolvedelements, var)) {
      dictionary remove (game.pov.currentcommandresolvedelements, var)
    }    dictionary add(game.pov.currentcommandresolvedelements, var, result)
    ResolveNextName
  </function>

With that changed, you can then make a command or verb named something like "annoy", which will trigger an attack when used on the snake. If you don't want the player to have the option of deliberately annoying the snake, give the command the pattern (as a regular expression) ^$.

Then for the snake itself, set the attributes:

  • snake.limitcommands - an objectlist containing all the commands that the player can use normally (such as lookat or charm, for example)
  • snake.defaultcommand - the command which should be run instead of any command not on the limit list. In this case, annoy.
  • snake.defaultcommandparameter - the parameter which should be passed to the annoy command. This could be necessary because if the player types "put snake in vase", the snake will be in the variable object1; but if they type "put sword in snake", the snake will be in the variable object2. Setting snake.defaultcommandparameter to the string "object" means that the variable object will always refer to the snake.

(usual disclaimer: code off the top of my head, not actually tested because I can't do this on the web editor)


Thanks mrangel. I'm not exactly sure what is going on with your code and will take a bit of time to figure it out. One thing you have the limitcommands as an object list, that doesn't appear to be an option for an object attribute.

Thanks again, this gives me something to work with.


A better solution might be in the function ResolveNextName, look for this piece of code:

      if (HasScript(game.pov.currentcommandpattern, "script")) {
        // This is the bit that actually runs the commands
        do (game.pov.currentcommandpattern, "script", game.pov.currentcommandresolvedelements)
      }

And change to something like:

      if (HasAttribute (game.pov, "currentcommandresolvedobjects")) {
        if (not DictionaryContains (game.pov.currentcommandresolvedelements, "command")) {
          dictionary add (game.pov.currentcommandresolvedelements, "command", game.pov.currentcommandpattern)
        }
        foreach (obj, game.pov.currentcommandresolvedobjects) {
          if (HasScript (obj, "beforecommand")) {
            do (obj, "beforecommand", game.pov.currentcommandresolvedelements)
          }
        }
      }
      // check that 'beforecommand' scripts haven't cancelled the command
      if (HasObject (game.pov, "currentcommandpattern")) {
        if (HasScript(game.pov.currentcommandpattern, "script")) {
          // This is the bit that actually runs the commands
          do (game.pov.currentcommandpattern, "script", game.pov.currentcommandresolvedelements)
        }
      }

This would cause an object's beforecommand script attribute to be run before any command is used on it. This script can prevent the command from running by doing: game.pov.currentcommandpattern = null (or setting currentcommandpattern to a different command if you want to run something else instead).

This might be a better solution; checking through all the objects before running the command, rather than checking each as they're resolved.

Your snake's beforecommand script could check the variable command against a list of commands such as lookat and avoid if you want those to work normally, and do something else otherwise.


Brilliant. That new solution works. Thankyou.

Simply adding the beforecommand attribute to my snake and adding this script

if (not command=lookat) {
msg ("You die.")
game.pov.currentcommandpattern = null
}

allows all commands except examining to kill the player.


Great :)

I'm not 100% sure if there's any more changes necessary to make it work with multiple commands such as "take all"; I've not properly looked into how those are implemented. I think it should work, but not certain.


Ahh yes, take all and drop all don't work 100%. With my script as above take all acts as if it's taking the snake only. Not too much of an issue for now if I change my script to exclude take and drop

if (not command=lookat and not command=take and not command=drop) {
msg ("You die.")
game.pov.currentcommandpattern = null
}

It's easy to override take and drop from the Inventory tab.

And..

Being able to do something like:

if (command = read) {
game.pov.currentcommandpattern = lookat
}

is really useful.


In the case of the snake, I think that the player tries to pick up all of the items at once and the snake bites them, which distracts them from picking up anything else.

If the bite isn't fatal, then you could allow them to pick up the rest of the objects by doing something like:

if (not command=lookat) {
  msg ("The snake jumps up and bites you.")
  removed = false
  if (GetBoolean (command, "allow_all") or HasAttribute (command, "multiple")) {
    if (IsDefined ("object")) {
      if (TypeOf (object) = "list" or TypeOf (object) = "objectlist") {
        if (ListContains (object, this)) {
          list remove (object, this)
          removed = true
        }
      }
    }
  }
  if (not removed) {
    game.pov.currentcommandpattern = null
  }
}

So if the command has a list of objects to work on (like "take x, y, and z" or "take all"), it tries to remove the snake from the list and then execute the rest of the command normally. If it doesn't find the snake in the list, it cancels the command entirely.


Thanks again.

The simplest option here for me would be to exclude the object from take all, it does seem unfair to cause the player major issues when entering a fairly common command.

Another option, and one that my project takes is to make the item scenery, then take all is taken care of.

Obviously any new commands that allow all would have to use something like the option above.


Log in to post a reply.

Support

Forums