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 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 |
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.
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.
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.
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!
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!
And I still have a few things on my to-do list, but I figured this was enough for one post!
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.
ReplyDeleteThat'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