Search This Blog

I'm a One-Woman QA Department

Things have been slow working from home lately and I finally decided I was utterly fed up with... well, pretty much everything that's wrong with Creatures 2. So with the help of Bella the Beta Norn, I set out to fix as much of it as possible, which turned out to actually be quite a lot. 

You're gonna need a bigger better boat.

I began with my archnemesis: the boats. I've previously expressed my gripes with the boats, especially the light ocean one:

"Not only do creatures constantly get stuck in it, not only are the controls located where you can't click on them when the boat is occupied, but the boat itself is completely pointless because it leads to a small, dead end patch of island with no food on it."

I initially "solved" this problem by just deleting the boat, but did not release a COB for it because the hard part would be re-creating the boat. I started work on that, and then I figured, the dark ocean boat isn't so bad but it's still an annoyance, so I should go ahead and fix that too. So at that point, why settle for just deleting the light ocean boat? If I'm replacing the dark ocean boat, I might as well replace the other one too.

The scriptorium makes the event scripts for any official object trivial to obtain, but with COBs like this, the hard part is always in restoring the original object as part of the COB removal script - while event scripts are easy to rip from the game, the injection/creation script is generally a mystery, and that's where a lot of important values are set: initial values for object variables, attr and bhvr values, and various physics traits. With complex objects like the boats, you need to create the parts in the correct order, too.

If you're lucky, there's an official COB that already deletes the object, and has a removal script that restores it, which reveals the creation process. But most of the time, you don't have that luxury. You have to backwards engineer the object, which involves a lot of extracting information, and guessing at those values you can't pull from the game.

The initial framework for building a vehicle, with all the commands that will need values to be set, looks like this. As you can see, there are a lot of blanks!

new: vhcl ???? ?? ??
setv cls2 ? ? ?
setv accg ??
setv aero ??
setv rest ??
setv attr ??
setv grav ??
bhvr ? ?
tick ????
new: part N ? ? ? ?
pose ?
...
part 0
mvto ?? ??
cabn ? ? ? ?
spot N ? ? ? ?
...
knob ? ?
...
image file, number of images, first image
family, genus, species
acceleration under gravity
aerodynamics
bounciness
attributes
affected by gravity
click behavior, creature behavior
how often to call timer script
relative x, relative y, image offset, plane
initial pose


initial x, initial y
sides of cabin space
sides of clickable part

function number, what spot to assign to

The new: vhcl line needs an image file, the number of images, and which image to start at, all of which can be found by just using a sprite editor to open files from the game until you find the one you're looking for. We can find out the class info from the "what am I looking at" script I've mentioned before. In fact, we can modify it to give us even more info, just by adding lines of dde: putv whatever, so we can fill in the setv values too. I haven't found a way to pull bhvr values, but you can generally guess at what they are by observing the object's behavior. That gets us about halfway there.

And since we can pull the class info, we can open existing scripts for the object, which we can look through for more clues. By looking at the other scripts, you might be able to find a place where it's re-setting the tick value, and by deciphering references to different parts, you can determine what each part is. By taking a screenshot and measuring where certain sprites are you can determine the coordinates and initial pose of each part, etc, and by comparing the base part coordinates to a map, you can identify the coordinates for the object as a whole. 

The cabn value is unfortunately guesswork, as are the spot values and their knob associations, once again achieved by observing behavior and eyeballing locations. The plane each sprite is on is also guesswork, best found by comparing creation scripts for similar objects and by good old-fashioned trial and error (in my case, moving things back one plane at a time until the boat and water layered properly). Now we've filled in our script to recreate the original boat.

new: vhcl wob_ 15 0
setv cls2 3 6 1
setv accg 10
setv aero 20
setv rest 40
setv attr 12
setv grav 0
bhvr 1 3
tick 1200
new: part 0 0 0 0 7900
new: part 1 0 73 1 7997
new: part 2 49 91 2 7998
pose 8
new: part 3 60 21 12 4000
pose 2
part 0
mvto 6930 575
cabn 10 0 145 107
spot 0 60 21 112 85
knob 0 0
knob 1 0
knob 3 0

After doing the same for the other boat, we can move on to actually creating new stuff! Recreating the originals doesn't just give us something to put in the remove COB script, it also gives us a baseline for behavior for our replacement, such as the physics values and plane. In this case, I also have a new sprite (which I shamelessly stole from Creatures 1; it looks surprisingly C2-like). Again, measuring sprites relative to each other in paint and comparing to a map gives us relative and world-based coordinates. A look in the scriptorium gives us an idea which classifiers are already taken, so we can pick one that's empty. Then once the whole thing is built, we can make any desired changes to behaviors and traits.

In this case, I have four parts (base, boat, left button, and right button), which have two spots defined (again, left and right buttons). By contrast, the default C2 boat has only one spot (that awkwardly placed lever), which is assigned to the knob values of 0 (creature push), 1 (creature pull), and 3 (hand push), with no 4 (hand pull). I just assigned push to one button and pull to the other, and then split the actual "go left" and "go right" code into two other scripts. 

new: vhcl boat 11 0
setv cls2 3 6 10
setv accg 10
setv aero 20
setv rest 40
setv attr 12
setv grav 0
bhvr 1 3
tick 100
new: part 0 0 0 0 7900
new: part 1 0 0 1 7997
new: part 2 8 61 7 7998
new: part 3 136 61 9 7998
part 0
mvto 7071 595
setv ov00 0
setv ov01 0
cabn 35 0 123 93
spot 0 8 61 25 77
spot 1 136 61 153 77
knob 0 0
knob 1 1
knob 3 0
knob 4 1

Note: The snippet shown is just for the light ocean boat; there's also a nearly-identical section to create the dark ocean boat, just at different coordinates. Both are considered instances of the same object, and functionality for them just uses a location check to determine which boat is which.

The push and pull code then just becomes a simple logic framework to determine which of the directional scripts to call, with 300 being right, and 301 being left. The hand can actively choose a direction by choosing a button, even if the boat is already moving, but for creatures, both pulling and pushing only work when the boat is docked and will automatically determine which direction to move in.

scrp 3 6 10 1|2
 doif from eq pntr
  mesg writ ownr 301|300
 else
  targ ownr
  doif ov00 eq 0
   doif posl lt 4000
    doif posl le 680
     mesg writ ownr 300
    else
     mesg writ ownr 301
    endi
   else
    doif posl le 6800
     mesg writ ownr 300
    else
     mesg writ ownr 301
    endi
   endi
  endi
 endi
endm
Push (left button)|Pull (right button)
if the hand pushed the button
{go left|right
}otherwise, be context sensitive
{select boat
 if boat is not currently moving
 {if it's the dark ocean boat
  {if it's on the left
   {go right
   }otherwise
   {go left
   }
 }otherwise (it's the light ocean boat)
  {if it's on the left
   {go right
   }otherwise
   {go left
   }
  }
 }
}
end

The actual movement code lives in its own custom scripts, to avoid spaghetti code. Technically, looking at this code a few days later, even this still has some unnecessary redundancy; behavior that is the same for both boats could have been placed outside the boat-determination doif instead of replicated for both cases. Ah, well!

scrp 3 6 10 300|301
doif ov00 eq 0
 gpas
endi
setv ov00 1
doif posl lt 4000
 part 2
 pose 0|1
 part 3
 pose 1|0
 part 1
 sndl padl
 anim [012345R]|[543210R]
 loop
  setv velx 5|-5
  wait 3
 untl posr ge 1119|le 297
 stpc
 part 3|2
 pose 0
 part 1
 anim [0]
 setv velx 0
else
 (same code, diff posr check)
endi
setv ov00 0
dpas
etch 4 0 0
 setv vely -60
 setv velx 0
next
endm
Right|Left script
if boat not currently moving
{pick up passengers
}
boat is now moving
if dark ocean boat
{select left button
 turn off|on light
 select right button
 turn on|off light
 select boat sprite
 start paddle sound
 animate wheel CW|CCW
 do this
 {start moving 
  wait momentarily before posr check
 }until boat reaches other side
 stop sound
 select right|left button
 turn off light
 select boat sprite
 stop animation
 stop movement
}otherwise (light ocean boat)
{...
}
boat is not in motion
drop passengers
for each creature touching boat
{give upward velocity (eject)
 no sideways velocity
}
end

Finally, I made some changes to the timer script. Originally, tick was set to 1200, but as you might have noticed in my install script, I changed it to only 100. I also added a counter variable ov01, to track how many times the timer script runs; this effectively allows us to have multiple timers running at once.

The default C2 boats cross the ocean on their own when the timer goes off at 1200 ticks. I added a doif statement that fires every 12th call to contain the same directional travel logic as used in the push/pull scripts on the original timing, but on the 11 other calls, the script can do something else. In this case, even though this smaller boat tucks under the dock nicely so creatures are less likely to fall in, I added code to eject anybody who might be stuck inside.

scrp 3 6 10 9
 doif ov00 eq 0
  addv ov01 1
  doif ov01 ge 12
   setv ov01 0
   doif...
    (boat direction logic)
   endi
  else
   etch 4 0 0
    doif tcar eq ownr
     setv vely -60
     setv velx 0
    endi
   next
   dpas
  endi
 endi
endm
Timer script
if boat not moving
{increment counter
 if counter >= 12
 {reset counter
  boat motion logic
  {...
  }
 }otherwise
 {for each creature touching boat
  {if it's carried by boat
   {give upward velocity (eject)
    no sideways velocity
   }
  }
  drop passengers
 }
}
end

And so I defeated my old enemy, the boat. But I wasn't done yet, not by a long shot! If I was restoring the light ocean boat, I didn't want creatures getting stuck on the dead end with no food. 

Download Better Boats

Extra Masham Berries (Coconuts)

The simplest thing to do would be to add some coconuts to the left side of the island. Reverse engineering the properties of the coconut was simple enough, following the same process as with the boats. For their behavior, I cracked open their code, but try as I might, I could not locate the script responsible for respawning them once they've been eaten. My best guess is that there's a loop ever statement in the injection/creation script that somehow tracks this. That code is off-limits to me, but I did determine that if I added additional coconuts to the world, they didn't seem to count for respawn purposes. 

That meant I could safely add a completely separate respawn mechanism of my own design for the additional masham berries; I just gave the additional berries a special object variable that flags them as existing on the left side of the island. Then I added some additional logic to the scripts that delete coconuts from the world to check for this flag variable and manually replace the additional coconuts. The remove COB script likewise checks for this flag variable when deleting coconuts.

Download Extra Masham Berries

Dock Safety Fences

But I still wasn't satisfied with the situation. Creatures may no longer get stuck in boats but they can still easily fall into the sea, and even with the pufferfish, I still have to rescue them frequently. Surely I could do something about that! In this case, I made a brand new COB, but with some functionality inspired by an official COB.

I referenced the functionality of the electric gate COB that comes with the game. Obviously my fences didn't need to be as complex, as they don't need to handle different directional settings, but I wanted to see how the object prevented creatures from passing it. It turned out to be quite simple: if the creature's posx (horizontal center) is in a certain range, it forcibly sets the creature's pose to face back the way it came and starts the walk animation. From the CAOS guide entry on anim:

At first the pose and anim lines running on creatures baffled me. I couldn't figure out how to determine what number corresponded to what pose. Turns out, the answer is in the poses tab of the genetics kit.

So with the animations demystified, I was able to adapt what I saw in the official COB to my own barriers. I placed the safety rails and set an object variable ov00 that assigns an X coordinate for each one to consider as its edge, and another object variable ov01 that records an X velocity that should be applied for the given rail if a creature bumps into it.

scrp 2 9 38 9
setv va00 ov00
addv va00 20
setv va01 ov00
subv va01 20
setv va02 ov01
etch 4 0 0
 doif posx ge va01
  doif posx le va00
   setv va03 targ
   doif tcar eq 0
    targ va03
    setv velx va02
    doif va02 gt 0
     pose 59
    else
     pose 60
    endi
    anim [061062063063R]
   endi
  endi
 endi
next
endm
Timer script
(set va00 and va01
 ...to represent a range
 ...of X coordinates around
 ...the defined edge.)
record the defined velocity
for each creature touching rail
{if it is within the...
 {... X coordinate range
  {record creature
   if it's not being carried
   {target creature
    apply defined velocity
    if that velocity is positive
    {face right (59)
    }else
    {face left (60)
    }
    walking animation (61, 62, 63, 64)
   }
  }
 }
}
end

The bit that applies the code only if the creature isn't carried is important, because that effectively ensures that creatures riding boats can pass through the barrier without problems. Because it's such a broad bit of code, I don't expect it to have problems interacting with the default boats, but to be honest I only tested it with my better boats.

Download Safety Rails

Move To Ocean

Of course, if I've roped off the docks to prevent creatures from falling in, that means I'm gonna have a tough time getting Mernorns into the ocean! I decided to just bypass the issue entirely by writing a COB that, when injected, teleports your selected creature into the light blue ocean. Problem solved!

Download Move to Ocean

Push to Eat Fix

Was I done yet? No way! I still wanted to look into my theory of why Norns have so much trouble eating in Creatures 2:

"I opened up the neuroscience kit to watch her decisions unfolding, and I saw something interesting: she continually alternated between push and pull, but rarely get or eat... Then, an idea occurred to me. Do creatures actually know to get food before they push food? What if they just walk over and try to push food, and actually picking it up isn't an intentional action but part of the creature-side script when pushing the object? If picking a pushed object up is supposed to be handled in the object script, is it missing from many C2 items? It would certainly explain the C2 Norns' trouble with eating!"

It turns out I was definitely on the right track, but actually overestimating the game. I had, until now, been operating under the assumption that the game treats pushing food as being the same as eating it. Nope! Turns out that while the eat scripts were well-defined, edible objects did not have a push script or creature-side push script defined at all. The one exception was the pear fruit, which bizarrely had a push script that doses creatures with "Reward Echo"...?

So I deleted that script and injected a three-liner fix for each category of edible item.

scrp 2 N 0 17
mesg writ ownr 28
endm
creature-side push script for all members of genus N
call creature-side eat script
end

Yep. It's that simple. How did this game release in this condition, and why am I apparently the first person to figure this out more than 20 years later?! If anybody else has discovered and fixed this, I certainly couldn't find it documented anywhere! At any rate, Bella the Beta Norn started eating much better when she could do so via push, so that's a victory I guess!

Download Push To Eat Fix

And I still have a few things on my to-do list, but I figured this was enough for one post!

2 comments:

  1. Push and eat are intentionally seperate actions. I think I would also suggest that pushing food should give an "eat food" suggestion to the norn.

    ReplyDelete
    Replies
    1. That's fair, and I get the intent behind that, from a human perspective. But it obviously didn't work out, given what I saw going on in the creatures' decision chart, and frankly, there's really no reason they NEED to be separate. Edible objects don't generally have separate push functionality, and pushable objects generally don't have eat functionality. They are two separate verbs that essentially mean "use the primary function of this thing," so why add unnecessary complexity?

      Delete