Saturday, April 5, 2014

Mystery of the Non-Vanishing Pretzel

You know that feeling when you reach into a bowl of pretzels and munch down, enjoying that savory jerky flavor. Then, when you reach into the bowl again, you find that the old pretzel that you just ate is still stuck to your hand...
Mmm... Delicious Magical Jerky Pretzels, Once You Pop, You Can't Stop
Okay, so it's another bug! Looking into the script _props1.lua, we see the following sequence is run when Guybrush eats the pretzel:
  • The pretzel is detached from the bowl
  • The pretzel is attached to Guybrush @ wrist_l 
  • When the eating chore finishes, the pretzel has set_wear_chore(nil) run on it
It looks like the call to set_wear_chore(nil) makes the actor invisible. Let's try it out on the retail copy, using the debug window. Let's see if we can make the drunk vanish:
  • drunk:set_wear_chore(nil)
Abracadabra! Where Did the Drunk Go?
Presto! He's gone! If you'd like to set him back, you can do this:
  • drunk:default()
So, what happens when we do the same with ResidualVM?

Maybe if he doesn't move he'll be invisible too!
So what is ResidualVM doing wrong? Let's examine the Lua for set_wear_chore, a method of actor in _actors.lua. There's a lot going on here, so I reset the scene and then started stepping through each command in set_wear_chore using the retail version until the drunk vanished again. Luckily, it turns out that it's first action, the call to stop_all_chores, that makes the actor vanish!

This method is from the actor object as well, and the code for it is also in _actors.lua. Luckily, it's just a simple call to the Lua function StopActorChores, which if we follow it through, simply calls the stop() method on every one of the actor's chores. Before we get much further, let's take a stop ourselves to review what Chores are.

Chores are instructions that describe what the actor is supposed to look like, or what the actor is supposed to do. As an example, you can make the drunk look like Meathook by doing the following from the console:
  • drunk:set_wear_chore("meathook.cos")
The Drunk becomes Meathook!
They usually also contain rules for behaviors, like when Guybrush eats a pretzel (to use a topical example):
  • guybrush:play_chore("eat_pretzel")
In the retail version, this runs the chore, but here, it doesn't. This is probably another issue that needs to be looked into!

So, what have we learned? When the method set_wear_chore is called with nil, the actor's costume is stopped as well, which results in the actor not being drawn. In ResidualVM, the actor's costume is stored in a separate place from other chores and isn't stopped when StopActorChores is called. How do we fix this?

I decided that it would be easiest to just store if the wearChore is active or not. I also added a check to see if the wearChore was active in the draw method in costume.cpp to prevent the object from being drawn if the wearChore is inactive. Finally, I added a method to allow setting and getting this state variable. This was pushed in PR #858.

No comments:

Post a Comment