NPC Guide - by RuneLancer
A few notes.
Offsets in the game start at 0x00400000. I've written them down as the actual offset you'd find in the file using a hex editor, but always keep that in mind if you're altering pointers or trying to follow a pointer somewhere and end up going past the end of the file.
The pointer table is located at 0x0098548. The boss pointer table is RIGHT after it, located at 0x0098AEC.
The display rects are all 32 bit values MOVed into the stack. I've written them down as a group of 16 bit values to save space and for convinience. If you plan on editing them, make sure you keep in mind the fact these are NOT data, but rather code: you won't find a series of 32 bit values one after the other, but a series of instructions moving 32 bit values into the stack one after the other. Dust off your assembly knowhow and don't make a mess of the code. :)
Adding frames requires altering the code. This is not something I'm going to explain here, because it's largely a case by case thing. You will have to make heavy alterations to the code because it involves adding 25-30 bytes of code per frame, not including the actual code that puts the enemy in a state where the new frame is shown.
The states are for use with script commands. I haven't tested all of these - I've just based myself on what they do in the code. States that don't have any noticeable effect or that should only be messed around with internally have not been mentionned. It is up to you to experiment and dig through the code if the effect you're trying to acheive isn't mentionned here - in a worse-case scenario, chances are the effect you're looking for CAN be acheived but might required a bit of tweaking. These cases are beyond the scope of this document.
Last but not least, if you're an assembly hacker worth his/her salt, you shouldn't be afraid to screw around with the code. Get yourself a real-time machine code debugger and see what you can do. The best hacks are the ones that show off your skill and knowledge so get your hands dirty and make something really unique out of your hack. :)
----------------------------------------------------------------------------------------------------
Display Rects
000 [0x0000] Nothing
0026536 { 0x0000, 0x0000, 0x0010, 0x0010 }
001 [0x0001] Weapon Energy
0026892 { 0x0000, 0x0010, 0x0010, 0x0020 }
00268AE { 0x0010, 0x0010, 0x0020, 0x0020 }
00268CA { 0x0020, 0x0010, 0x0030, 0x0020 }
00268E6 { 0x0030, 0x0010, 0x0040, 0x0020 }
0026902 { 0x0040, 0x0010, 0x0050, 0x0020 }
002691E { 0x0050, 0x0010, 0x0060, 0x0020 }
002693A { 0x0000, 0x0000, 0x0000, 0x0000 }
Note that larger shards are calculated on the fly by adding 16 and 32 to their tops and bottoms.
002 [0x0002] Behemoth
0026AF9 { 0x0020, 0x0000, 0x0040, 0x0018 }
0026B15 { 0x0000, 0x0000, 0x0020, 0x0018 }
0026B31 { 0x0020, 0x0000, 0x0040, 0x0018 }
0026B4D { 0x0040, 0x0000, 0x0060, 0x0018 }
0026B69 { 0x0060, 0x0000, 0x0080, 0x0018 }
0026B85 { 0x0080, 0x0000, 0x00A0, 0x0018 }
0026BA1 { 0x00A0, 0x0000, 0x00C0, 0x0018 }
0026BBD { 0x0020, 0x0018, 0x0040, 0x0030 }
0026BE5 { 0x0000, 0x0018, 0x0020, 0x0030 }
0026C0D { 0x0020, 0x0018, 0x0040, 0x0030 }
0026C35 { 0x0040, 0x0018, 0x0060, 0x0030 }
0026C5D { 0x0060, 0x0018, 0x0080, 0x0030 }
0026C85 { 0x0080, 0x0018, 0x00A0, 0x0030 }
0026CAD { 0x00A0, 0x0018, 0x00C0, 0x0030 }
015 [0x000F] Treasure Chest
0029BF6 { 0x00F0, 0x0000, 0x0100, 0x0010 }
0029C12 { 0x0100, 0x0000, 0x0110, 0x0010 }
0029C2E { 0x0110, 0x0000, 0x0120, 0x0010 }
016 [0x0010] Save Point
0029E09 { 0x0060, 0x0010, 0x0070, 0x0020 }
0029E25 { 0x0070, 0x0010, 0x0080, 0x0020 }
0029E41 { 0x0080, 0x0010, 0x0090, 0x0020 }
0029E5D { 0x0090, 0x0010, 0x00A0, 0x0020 }
0029E79 { 0x00A0, 0x0010, 0x00B0, 0x0020 }
0029E95 { 0x00B0, 0x0010, 0x00C0, 0x0020 }
0029EB1 { 0x00C0, 0x0010, 0x00D0, 0x0020 }
0029ECD { 0x00D0, 0x0010, 0x00E0, 0x0020 }
025 [0x0019] Elevator
002B286 { 0x0100, 0x0040, 0x0120, 0x0050 }
002B2A2 { 0x0100, 0x0050, 0x0120, 0x0060 }
035 [0x0023] Mannan
002CEBC { 0x0060, 0x0040, 0x0078, 0x0060 }
002CED8 { 0x0078, 0x0040, 0x0090, 0x0060 }
002CEF4 { 0x0090, 0x0040, 0x00a8, 0x0060 }
002CF10 { 0x00a8, 0x0040, 0x00c0, 0x0060 }
002CF2C { 0x0060, 0x0060, 0x0078, 0x0080 }
002CF48 { 0x0078, 0x0060, 0x0090, 0x0080 }
002CF64 { 0x0090, 0x0060, 0x00a8, 0x0080 }
002CF80 { 0x00a8, 0x0060, 0x00c0, 0x0080 }
052 [0x0034] Sitting Blue Bot
00030786 { 0x00F0, 0x0060, 0x0100, 0x0070 }
076 [0x004C] Calculated Animation
NONE
077 [0x004D] Sandaim the Farmer
0036696 { 0x0000, 0x0010, 0x0030, 0x0030 }
00366B2 { 0x0030, 0x0010, 0x0060, 0x0030 }
00366CE { 0x0060, 0x0010, 0x0090, 0x0030 }
078 [0x004E] Pot
00367E6 { 0x00A0, 0x0030, 0x00B0, 0x0040 }
0036802 { 0x00B0, 0x0030, 0x00C0, 0x0040 }
094 [0x005E] Kulala (Giant Jelly)
003A226 { 0x0110, 0x0000, 0x0140, 0x0018 }
003A242 { 0x0110, 0x0018, 0x0140, 0x0030 }
003A25E { 0x0110, 0x0030, 0x0140, 0x0048 }
003A27A { 0x0110, 0x0048, 0x0140, 0x0060 }
003A296 { 0x0110, 0x0060, 0x0140, 0x0078 }
103 [0x0067] Mannan Projectile
003B5F6 { 0x00C0, 0x0060, 0x00D0, 0x0078 }
003B612 { 0x00D0, 0x0060, 0x00E0, 0x0078 }
003B62E { 0x00E0, 0x0060, 0x00F0, 0x0078 }
003B64A { 0x00C0, 0x0078, 0x00D0, 0x0090 }
003B666 { 0x00D0, 0x0078, 0x00E0, 0x0090 }
003B682 { 0x00E0, 0x0078, 0x00F0, 0x0090 }
124 [0x007C] Sunstones
003FEF6 { 0x00A0, 0x0000, 0x00C0, 0x0020 }
003FF12 { 0x00C0, 0x0000, 0x00E0, 0x0020 }
216 [0x00D8] Pixel the Cat
000517F6 { 0x0100, 0x00C0, 0x0110, 0x00D8 }
----------------------------------------------------------------------------------------------------
NPC Pseudocode
052 [0x0034] Sitting Blue Bot
Select FrameRect (0)
077 [0x004D] Sandaim the Farmer
if (ScriptState = 0x00) Goto A
if (ScriptState = 0x01) Goto B
if (ScriptState = 0x02) Goto C
Goto D
A ScriptState = 0x01
FrameID = 0x00
FrameNum = 0x00
B if (F_0040F350(0x00, 0x78) != 0x0A) Goto D
ScriptState = 0x02
ScriptTimer = 0x00
FrameID = 0x01
Goto D
C ScriptTimer = ScriptTimer + 0x01
If (ScriptTimer <= 0x08) Goto D
ScriptState = 0x01
FrameID = 0x00
D If (Direction != 0x00)
Select Frame (FrameID)
Else
Select Frame (3)
078 [0x004E] Pot
If (Direction != 0x00)
Select Frame (0)
else
Select Frame (1)
----------------------------------------------------------------------------------------------------
Creating an NPC
I mentionned in the introduction that I wouldn't be dealing with actual assembly here and that such a thing was beyond the scope of the document. This is not entirely true - as a parting gift, I'll explain the basics behind enemy creation. There are a few ways to do this, and you don't have to follow the order I do things all the time.
First, like any assembly function, you must move esp into ebp after pushing it on the stack.
PUSH ebp
MOV ebp, esp
At the end of your function, you'll want to pop ebp after moving it back into esp.
MOV esp, ebp
POP ebp
RET
Now you get to sandwich your code between these two chunks. First thing you want to do is define the frames you'll be using. This isn't too hard: subtract 0x10 from esp for each frame you'll be adding. Then just shove each frame onto the stack. Here's an example from the pot (entity 0x4E)...
SUB esp, 0x20
MOV [ebp - 0x20], 0x000000A0
MOV [ebp - 0x1C], 0x00000030
MOV [ebp - 0x18], 0x000000B0
MOV [ebp - 0x14], 0x00000040
MOV [ebp - 0x10], 0x000000B0
MOV [ebp - 0x0C], 0x00000030
MOV [ebp - 0x08], 0x000000C0
MOV [ebp - 0x04], 0x00000040
Now you can do whatever you want. The pot entity mentionned above just tests the direction of the event and picks either of its two frames for display accordingly, then returns. Other entities may have more complicated behavior. You might want to make yourself a pointer table, jump to the appropriate bit of code based on the event's state, do your thing, then jump to some rendering code.
The rendering code just involves picking a frame based on conditions you decide on and setting the rect. You don't HAVE to conform to the aforementionned placing of a frame on the stack, but it's probably the easiest way to do this. You could also read it from the exe directly or calculate it on the fly (as some entities do.) Either way you need to set the rect, which is as easy as MOVing values into the appropriate offsets.
The usual calculation, when there are multiple frames, is somewhat as follows.
MOV edx, [ebp + 0x08]
MOV eax, [edx + 0x68]
shr eax, 0x04
lea ecx, [ebp + eax - 0x20]
MOV edx, [ebp + 0x08]
ADD edx, 0x54
MOV eax, [ecx]
MOV [edx], eax
MOV eax, [ecx + 0x04]
MOV [edx + 0x04], eax
MOV eax, [ecx + 0x08]
MOV [edx + 0x08], eax
MOV ecx, [ecx + 0x0c]
MOV [edx + 0x0c], ecx
Once that's done, the function caller will shift everything off to other functions which will handle the display and whatnot. At this point you no longer have to worry - the game engine handles the rest. To sum things up, you need to at least specify the display rect before your function exits - everything else is up to you.
Handy Offsets
Here are a few offsets you may want to keep track of. The function is always passed a pointer to the start of the object (+ 0x00) so you can just toy around with what's on the stack to access these. The length, I believe, is 0xAC bytes. Many of these seem to be conventions and could PROBABLY be used for whatever you want. Experiment. The ones with an (N) in front of their descriptions are NOT conventions and are definately known to use these values elsewhere in the code, so don't turn them into temporary storage locations.
Event.InUse +0x00 (N) Set to 0x00000080 if in use, and to 0x00000000 if free.
Event.X +0x08 (N) The X position.
Event.Y +0x0C (N) The Y position.
Event.MoveX +0x10 X delta. Some NPCs use this to determine by how much the X is incremented/decremented.
Event.MoveY +0x14 Y delta. Some NPCs use this to determine by how much the Y is incremented/decremented.
Event.NPCID +0x28 (N) ID of the entity.
Event.Direction +0x4C (N) The direction the entity faces. Generally 0 (left) or 2 (right.)
Event.Display_L +0x54 (N) The display bounding box (Left.)
Event.Display_U +0x58 (N) The display bounding box (Up.)
Event.Display_R +0x5C (N) The display bounding box (Right.)
Event.Display_D +0x60 (N) The display bounding box (Left.)
Event.FrameNum +0x64 A counter used to figure out which frame to display.
Event.FrameID +0x68 Which frame to actually display.
Event.ObjectTimer +0x6C A timer (user-incremented) to handle the object's behavior. The weapon energy (0x02) for
instance stops existing once this elapses.
Event.ScriptState +0x74 Which state the script is in. You can use this in conjunction with a pointer table.
Event.ScriptTimer +0x78 A timer (user-incremented) to handle states. The elevator (0x19) for instace controls its
movements with this.
Handy Functions
NPCs will sometimes call some important functions as part of their AI. Here's a quick list of the most common functions you can get away with using anywhere in the code. The order of the parameters is the order you should PUSH them in.
00420640 - Play Sound Effect
[04] Mode
[08] ID
This plays sound effect "ID." The mode can either be 0x00, 0x01, or 0xFF. I'm not sure what's what with the modes at the moment, but most sounds use 0x01. Chances are that's all you'll need.
0046EFD0 - Spawn New Entity
[04] Base Slot
[08] ?
[0C] Direction
[10] Move Y
[14] Move X
[18] Y
[1C] X
[20] ID
Creates a brand new entity. The entity will begin looking for a free slot starting at the base slot (most enemies use 0x0100 when spawning a projectile.) It is given a direction, initial velocity, position, and ID according to what's passed to it as parameters. I have no clue what the second parameter is - I suspect it may be the entity's state, but I can't confirm this currently.