Custom movements for vehicle?

Juniper200
16 Nov 2019, 12:36I 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?
mrangel
16 Nov 2019, 13:07What 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.

Juniper200
16 Nov 2019, 20:57Oh, 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.
mrangel
17 Nov 2019, 00:04OK. 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 <
Juniper200
17 Nov 2019, 19:10Wow! 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!