Custom movements for vehicle?

I had a question a few weeks back about using a vehicle. Everyone was really helpful, and I'd like to ask for a little more help now that I've had some time to ruminate on what we talked about there.

I created a drive verb that works when the player is inside the car. It works, but it's kind of clunky - you have to get in the car and then inputdrive car to move the car to the next location. I could make drive apply if the player is outside the car and wants to go somewhere. That's a natural way of doing things; I wouldn't say "I'm going to get in my car and drive to the store" in my real life. I'd say "I'm going to drive to the store."

But there are things inside the car the player has to solve and find outside of using it as a vehicle, so it would be good for it to be clear that this is a place you can perform all the usual look and take actions by allowing the player to enter and leave the car without taking it anywhere. I'd also like them to be able to get in the car, look and take and then go somewhere without leaving the car.

What I really want is this: The player gets in the car. They see the description of the interior and find the objects hidden inside. Then they think, "I'd really like to go southeast." The car doesn't have a southeast exit, but the street the car is on does. The player inputs se and the car and its children are moved to the street to the southeast.

I have some pseudocode around this, a variation on the drive verb that already works:

street1 has exits E and SE. 
car.parent = street1

-----

if game.pov.parent = car and car.parent = street1 

get input

  if here = street1

      if (IsRegexMatch ("(east|e)", LCase (result))) {
        MoveObject (car, street2)
        }

      else if (IsRegexMatch ("(southeast|se)", LCase (result))) {
        MoveObject (car, street3)
        }

I guess my real problem that I've taken all the words to say is that I don't know where to put this code. I could make it a script on the street exits, but the player isn't on the street - they're in the car and don't have access to those exits. Does it need to be a modification on the whole idea of movement? That seems excessive. Is there anyone who has a minute to point me in the right direction?


What I really want is this: The player gets in the car. They see the description of the interior and find the objects hidden inside. Then they think, "I'd really like to go southeast." The car doesn't have a southeast exit, but the street the car is on does.

I suggested a way to do this in the previous thread; but slightly more complex than it needs to be because you had a "drive" verb which would output some nice flavour text depending on the current character as well as moving the car. I suggested a "drive" verb to start the car, after which the normal "go north" or "north" or "se" would change to moving the car.

Now I've thought about it again, the driving system I'd use is:

  • When player uses the commands "drive" or "drive car"
    • If the player is outside the car, they get in (displaying flavour text if appropriate)
    • If the car isn't started, start the car (displaying flavour text if appropriate)
    • If the car was already running, display a message telling the player to specify a direction
  • When player uses the commands "drive northwest" or similar
    • If the player is standing next to the car, then get in (displaying flavour text if appropriate)
    • If the player is in the car and it isn't started, start it (displaying flavour text if appropriate)
    • Move the car in the specified direction
  • When the player uses commands "go northwest", "northwest", or "nw"
    • If the player is outside the car, they're walking
    • If the player is in the car but it isn't started, start it (displaying appropriate flavour text)
    • Move the car in the specified direction
  • When the player types "leave car" or "go out"
    • Stop the car if it's started (displaying a message if appropriate)
    • Move the player outside the car
  • When the player types "stop car"
    • Stop the car if it's started (displaying a message if appropriate)

That should allow the player to enter commands quickly (such as just typing "se" while in the car), but still allows the display of messages like your "You settle behind the wheel, and Aziraphale nervously takes his place in the passenger seat." to add to the atmosphere. Really, these messages are the only reason we need to track if the car is started, but I think they will add a lot to the feel of the game.

There's quite a few options there, but that's how I would normally drive such a system; because it works intuitively with whatever commands the player is likely to try.
I'm thinking that rather than having a list of directions in the code, you would have exits with an attribute driving to indicate where the car can go. Those exits could be made invisible if the player can't also walk in that direction.

Would you like me to try putting together the actual code to do that? I think it's mostly pretty simple.


Oh, yes. I was planning on keeping the flavor text in. This was just the gist of what I was trying to do with the relevant movement.

The system you outlined makes sense, and it looks like something I could implement. I just don't know where to implement it. I'd love to see your take on it if you have time - that's very kind of you.


OK. First up, I'd give the car a script attribute to regenerate its exit list.
This handles the custom behaviour of the "go" verb. Basically, if the street the car is in contains a northward exit that goes to Brock Street, this will create a northward exit inside the car, with a script attribute that runs the "drive" verb.

<attr name="refresh_driving_exits" type="script">
  foreach (exit, AllExits()) {
    if (exit.parent = this and HasObject (exit, "driving_clone_of")) {
      destroy (exit.name)
    }
    else if (exit.parent = this.parent and GetBoolean (exit, "driving")) {
      newclone = CloneObjectAndMove (exit, this)
      newclone.visible = true
      newclone.driving_clone_of = exit
      newclone.script => {
        do (drive, "script", QuickParams ("object", this.driving_clone_of))
      }
    }
  }
</attr>

Then we have another script attribute for the car, forcing the description to be shown when you drive somewhere, even though the player's parent hasn't changed. I don't know if your game allows the car to move without the player inside it, but I'll provide messages for that just in case:

<attr name="changedparent" type="script">
  switch (game.pov.parent) {
    case (this) {
      // if you want a message to say "You drive carefully" or whatever, you can put it here.
      ShowRoomDescription()
    }
    case (this.parent) {
      // The car has just moved into the room where the player is
      msg (GetDisplayName (this) + " pulls up at the kerb beside you.")
    }
    case (oldvalue) {
      // The car has just left the room where the player is
      msg (GetDisplayName (this) + " drives off into the distance.")
    }
  }
</attr>

Then we have the car's description script; which needs to both display the description of the car, and describe where the car is. As this is called whenever the car moves and when the player gets into the car, we can also call refresh_driving_exits from here to ensure they are up to date:

do (this, "refresh_driving_exits")
if (GetBoolean (drive, "isactive")) {
  // If the player has just driven somewhere, it's redundant to display the detailed description of the car again, so we include a short one.
  msg ("You are in the car.")
}
else {
  msg ("Here's a proper description of the car, which will be displayed if the player types 'look' while in the car, or when they've just got in.")
}
msg ("The car is in "+GetDisplayName (this.parent) + ".")
// If the player types "drive north" when they're standing next to the car, we don't want to show the full
//   description of their current location again before driving off.
if (not GetBoolean (drive, "isentering")) {
  // If you've just driven to a new location, describe what's outside the car.
  // In the room descriptions, I'm providing a variable "vehicle" which can be accessed to get the car object.
  // So that in the description for a street, you could do something like
  //               As you {either IsDefined("vehicle"):drive:walk} closer, you can see...
  game.text_processor_variables = QuickParams ("vehicle", this)
  if (HasString (this.parent, "description")) {
    msg (this.parent.description)
  }
  else if (HasScript (this.parent, "description")) {
    do (this.parent, "description", game.text_processor_variables)
  }
}
drive.isactive = false
drive.isentering = false

And then the drive command itself.
I don't know if you have one car or many in your game; I've assumed the car has a boolean attribute is_car to let us find it.
I've also used the special changecommandscope script, so that the object in drive #object# can be either an exit or a car.

<command name="drive">
  <pattern type="string"><![CDATA[^drive( (?<object>.+?)( (?<object2>north|east|south|west|northeast|northwest|southeast|southwest|in|out|up|down|n|e|s|w|ne|nw|se|sw|o|u|d))?)?$]]></pattern>
  <scope>none</scope>
  <changecommandscope type="script">
    vehicle = null
    if (variable = "object1") {
      if (DictionaryContains (matched, "object")) {
        vehicle = DictionaryItem (matched, "object")
        if (GetBoolean (vehicle, "is_car")) {
          list add (items, vehicle)
        }
        else {
          vehicle = null
        }
      }
    }
    if (vehicle = null) {
      if (GetBoolean (game.pov.parent, "is_car")) {
        // If the player is in the car, allow "drive car"
        vehicle = game.pov.parent
      }
      else if (variable = "matched") {
        // If the player isn't in a car, look for cars in the same room as them
        vehicles = FilterByAttribute (ScopeReachable(), "is_car", true)
        // If there's a car here, look for exits
        if (ListCount (vehicles) = 1) {
          vehicle = ListItem (vehicles, 0)
        }
        else {
          // if there's more than one car in sight, use the player for scope so that "drive north"
          //    will result in "Which car do you want to drive?" rather than "I can't see a north"
          vehicle = game.pov
          foreach (car, vehicles) {
            list add (items, car)
          }
        }
      }
    }
    foreach (exit, AllExits()) {
      if (GetBoolean (exit, "driving") or GetBoolean (exit, "driving")) {
        if (exit.parent = vehicle.parent) {
          list add (items, exit)
        }
      }
    }
  </changecommandscope>
  <script>
    vehicle = null
    exit = null
    done = false
    if (IsDefined ("object")) {
      if (GetBoolean (object, "is_car")) {
        vehicle = object
      }
      else if (DoesInherit (object, "defaultexit")) {
        exit = object
      }
      else {
        // player attempted to drive an object other than a car
        msg ("You can't drive " + object.article + ".")
        done = true
      }
    }
    // allow 2 objects so the player can "drive car north"
    if (IsDefined ("object2")) {
      if (exit = null) {
        exit = object2
      }
      else {
        msg ("You can't drive in two directions at once.")
        done = true
      }
    }
    if (vehicle = null and not done) {
      if (GetBoolean (game.pov.parent, "is_car")) {
        vehicle = game.pov.parent
      }
      else {
        cars = FilterByAttribute (ScopeReachableNotHeld(), "is_car", true)
        if (ListCount (cars) = 0) {
          msg ("You haven't got a car!")
          done = true
        }
        else if (ListCount (cars) = 1) {
          vehicle = ListItem (cars, 0)
        }
        else {
          // There's more than one car here, and the player entered "drive <direction>" or just "drive"
          msg ("Which car do you want to drive?")
          done = true
        }
      }
    }
    if (not (done or vehicle = game.pov.parent)) {
      // player isn't in the car
      // if they've specified a destination, don't spill the full description of the car on entering
      // if they need to find a car key or something, include checks for that here
      this.isentering = true
      this.isactive = not (exit = null)
      game.pov.parent = vehicle
    }
    if (game.pov.parent = vehicle) {
      // You can use the "changedstarted" script to display flavour text for this
      vehicle.started = true

      // check for "started" again, in case the "changedstarted" script prevents the car from starting
      if (not done and GetBoolean (vehicle, "started")) {
        if (exit = null) {
          msg ("Which way do you want to drive?")
        }
        else if (not GetBoolean (exit, "driving")) {
          // Using the "driving" boolean to indicate that an exit is suitable for cars
          msg ("You can't drive that way.")
        }
        else {
          // successfully driving!
          this.isactive = true
          if (HasScript (exit, "drivescript")) {
            do (exit, "drivescript", QuickParams ("vehicle", vehicle))
          }
          else {
            vehicle.parent = exit.to
          }
          this.isactive = false
        }
    }
  </script>
</command>

(not 100% sure on the XML there; don't know if the <![CDATA[ inside the <pattern> element is necessary)

Then there's a few little things.

The car's "before enter" script:

  msg ("You get into the car")

(you could also include a check that the car isn't locked or whatever, and move the player out again. The "drive" command checks that the player is in the car after moving them into it, so if they're ejected it won't display anything else)

The car's "after leaving" script:

  this.started = false
  // can also include flavour text if you want

Flavour text for starting/stopping the car can go in the car's changedstarted script.

if (this.started) {
  msg ("You get behind the wheel and the car roars into life.")
}
else if (game.pov.parent = this) {
  msg ("You stop the engine and some flavour text happens.")
}
else {
  msg ("You remember to stop the engine before getting out of "+GetDisplayNameLink (this) + ".")
}

A stop verb for the car would probably be nice; but not essential. Always good to account for things the player might try, though.

This took way longer than I thought to type out. Ooops :S
Will polish it up a bit tomorrow

Using this script will respond to the following attributes:

  • driving - an exit with this attribute set to true can be driven through
  • visible - an exit with this attribute set to true can be walked through (or attempted)
  • drivescript - If set, driving through an exit will run this script instead of moving the car to the destination.
    • Within this script, you will have access to a variable vehicle referencing the car
    • The script should either do MoveObject (vehicle, this.to) or explain to the player why they failed to drive that way
    • Or move the car somewhere else if the exit doesn't always go the same way
  • is_car - a boolean attribute saying whether or not a given object is a car
    • If this was an actual library, you'd probably change GetBoolean (obj, "is_car") to DoesInherit (obj, "cartype")
  • started - a boolean which will be set to true when the car is started

Another thought pops into my head.

You could give the car a script attribute:

<changecommandscope type="script">
  list add (items, this)
</changecommandscope>

This means that the car is in scope when you're inside it; so you can do "look car" or any other verbs it has while inside it.

Then you could modify the scripts:

<onexit>
  this.started = false
  this.displayverbs = Split ("Look at;Drive;Enter")
</onexit>

<beforeenter>
  this.displayverbs = Split ("Look at;Drive;Leave")
</beforeenter>

<changedstarted type="script">
  if (this.started) {
    msg ("You get behind the wheel and the car roars into life.")
    this.displayverbs = Split("Look at;Stop;Leave")
  }
  else if (game.pov.parent = this) {
    this.displayverbs = Split("Look at;Drive;Leave")
  }
  else {
    msg ("You remember to stop the engine before getting out of "+GetDisplayNameLink (this) + ".")
  }
</changedstarted>

"Enter" verb:

if (game.pov.parent = this) {
  msg ("You're already in it.")
}
else {
  game.pov.parent = this
}

"Leave" verb:

if (game.pov.parent = this) {
  game.pov.parent = this.parent
}
else {
  msg ("You're not in the car.")
}

"Stop" verb:

if (GetBoolean (this, "started")) {
  msg ("You stop the engine and some flavour text happens.")
  this.started = false
}
else {
  msg ("The engine isn't running.")
}

Wow! That's a lot of work - thank you for looking at the problem for me. I'm going to take some time to study this and learn from it - I may be back with questions! - and then I'll try to work it into what I have. Thanks again!


This topic is now closed. Topics are closed after 60 days of inactivity.

Support

Forums