On the subject of TSC

Feb 21, 2009 at 12:04 AM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
Okay, pretty sure I already posted a topic about this, but this really deserves its own topic, so don't you dare merge this, sp, andwhy. Okay, heres the problem.

I'm making some additional TSC commands, since the lack of them was proving fairly annoying, and ran into a few problems.

One problem is the preparation steps; every single TSC command goes through a series of prep steps before starting the rest of the command, and I was wondering why this isn't built into the command, and what kinds of prep the different commands do.

Next is the problem of space. adding additional TSC commands into the exe can screw up offsets, making sues and cave editor angry at it. Hijacking existing commands can lead to problems, such as not having those commands to use in the future. Is there any way to read from an external file, and find what the TSC command should do from that? If so, how?

For the more specific problems:
I'm trying to implement a few new health commands, and some of them aren't working for no apparent reason. For example:

Code:
push ebp 
mov ebp,esp 
mov [0049E6D0],[ebp+0008]
pop ebp
ret

that command, ml= doesn't work, and I don't know why.

nextly, I'm trying to implement an MLS and MLL, save and load functions for the max heath, useful for two playable characters. Problem is that I don't know where to save the data to (would the "this program can't run in dos mode" work) and as I said before, there isn't nearly enough space in the exe.

sshgsi? shining? rune? cookie? someone else?
 
Feb 21, 2009 at 6:13 PM
In front of a computer
"Man, if only I had an apple..."
Join Date: Mar 1, 2008
Location: Grasstown
Posts: 1435
I don't really know anything about this, but would it be possible to shunt the TSC to a dll?
 
Feb 21, 2009 at 6:44 PM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
I was thinking about something like that, but I don't know how to get resources from an external file.
 
Feb 21, 2009 at 8:04 PM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
Geez...this is tricky. From what I've seen of the TSC parser the code seems to be a real mess. I think the way it works is it that it's a huge chain of if/else type constructs that decodes each letter, for example,

PHP:
if (cmd_name[0]=='A')
{
	if (cmd_name[1]=='E')
	{
		if (cmd_name[2]=='+')
		{
			get_parameters();
			ae_plus();
		}
		else if (cmd_name[2]=='-')
		{
			get_parameters();
			ae_minus();
		}
	}
	else if (cmd_name[2]=='R')
	{
		...
	}
	...
}

...and so on through the whole list of supported commands. It makes it very confusing to read through it. I assume this function was generated automatically, as it would be one hell of a nasty repetitive function in source.

I'm not sure that that's how it works, but it's how it seems from what I saw while I was in there. I was mostly looking for what the commands did, not how the parser worked. I do know that the script is not pre-parsed or compiled into any kind of bytecode. It is decrypted out of the .tsc and then interpreted exactly as it sits.

(I'm just telling that stuff to save you time in digging into the code, in case you hadn't already figured it out).

Now, to try to answer your question, I haven't tried to do it, so I don't know for sure. I do know that there are several commands that are supported in the parser but aren't ever used in the game. They are: <CAT, <ECJ, <INP, <MP+, <SK-, and <SNP. You could place your code over one of these functions.

Now as far as saving, that's a tricky one. If you were really tight with the assembly, you might be able to just call fopen etc straight there in the function. Some of the TSC functions are long enough that this seems like it might be practicable. You can get the call address for fopen from the function that loads 290.rec, or from the function that checks for (C)Pixel at the end of the bitmaps. I have the offset to it somewhere, but it shouldn't be too hard to find. Failing that, I think Celtic Minstrel has a good idea about linking in an external dll. I think you would have to edit the PE to do it, but if you could get your own .dll to load when the program comes up, you could just call into the dll and have all the space in the world to do whatever you wanted to the game, and you could write in C.

A third option would be to somehow encode the data you want to save into the data the game saves and loads anyway:

* Perhaps the save-file structure contains pad bytes which are fwrite'd/fread'd intact to/from the profile.dat but are never accessed (I believe it does. If you check "Profile.txt" it mentions several bytes that are "Reserved" or "Unknown" but they look to me like compiler-inserted alignment bytes).

* You could do something with setting/clearing unused flags. Like take flags 7000-7007 for example, they're boolean values so they're saved as single bits in the file. Encode your data across those flags and voila, you have one byte of nonvolatile storage.

* Some of the variables in profile.dat are much wider than they need to be. For example, "player direction" and "curweaponslot" are both DWORD's, and so is the ID # for every item in your inventory. Save your data in the most significant bytes, then hack the loader to mask off the value so it doesn't screw up the original purpose of the variable.

On another angle, if you're not going to use the time-trial functionality of 290.rec, you could just save your data in there. The loading method of that file was pretty confusing, though.
 
Feb 21, 2009 at 8:30 PM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
Ok, one more response.

Lace said:
Every single TSC command goes through a series of prep steps before starting the rest of the command, and I was wondering why this isn't built into the command, and what kinds of prep the different commands do.
This particular program isn't into ideas like abstraction and code re-use. Get used to it. Maybe Pixel uses a lot of macros, maybe he just codes that way. There's a lot of stuff you'll find that seems like it would have been a whole lot simpler to make it more of a global generic thing. For example, every NPC has a "velocity" or "inertia" value associated with it which tells what direction and speed it's currently heading in. But instead of having some sort of global rule that at the end of every tick, all the objects XY coordinates are incremented by their inertia values, this is done BY EACH OBJECT, inside EVERY single AI function. Not saying that's "wrong", just...

I don't know what the prep code does, except I know there is one function that is sort of like atoi() which pulls the value of the parameter out of the string and returns it in eax.

adding additional TSC commands into the exe can screw up offsets, making sues and cave editor angry at it.
It shouldn't, if you just write over the existing code and don't move anything around. If it does though, either write a patch program or script that will apply your changes to the executable every time it's run (then your editor will still see the stock exe) or just fix the editor. CaveEdit is open source. It's written in C++.

Code:
push ebp 
mov ebp,esp 
mov [0049E6D0],[ebp+0008]
pop ebp
ret

that command, ml= doesn't work, and I don't know why.
I take it this is a hack on "ML+" which is supposed to SET your max health instead of incrementing it.

* Breakpoint on the above quoted code. Does it run when the script containing it executes?

* Does [ebp+8] really contain the value you expected?

* AFTER execution of the script, breakpoint and inspect the value of 49e6d0. Is it still the value it should be?

* Is 49e6d0 really the max health value? Try changing this variable manually with the debugger and seeing if it affects your max health.

* Is there some sort of update function that needs to be called in order to change the status bar etc after 49e6d0 is changed?

* What does the code for "ML+" look like? Is this it? ML= should be just like ML+ except for a single opcode change, right?

* Is there more than one max health variable? There may be a "displayed" and an "actual" value, or the address you're looking at may be getting updated from some master copy in a player structure somewhere, and it's really just a temp variable for (some function).
 
Feb 21, 2009 at 8:53 PM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
wow, thanks for responding. I'll try some of the things you mentioned soon, just wanted to respond to your second post, because I'm fairly familiar with ML+.

here's the ML+ code:
Code:
00419CB0 | push ebp
00419CB1 | mov ebp,esp
00419CB3 | movsx eax,word ptr [ebp+0008]
00419CB7 | movsx ecx,word ptr [0049E6D0]
00419CBE | add ecx,eax
00419CC0 | mov [0049E6D0],ecx
00419CC7 | movsx edx,word ptr [0049E6D0]
00419CCE | cmp edx,000000E8
00419CD4 | jle 00419CDF
00419CD6 | mov word ptr [0049E6D0],00E8
00419CDF | movsx eax,word ptr [ebp+0008]
00419CE3 | movsx ecx,word ptr [0049E6CC]
00419CEA | add ecx,eax
00419CEC | mov [0049E6CC],ecx
00419CF3 | movsx edx,word ptr [0049E6CC]
00419CFA | mov [0049E6D4],edx
00419D00 | pop ebp
00419D01 | ret

there are four pointers in this code these are:
[ebp+0008], [0049E6CC], [0049E6D4], and [0049E6D0].
I don't know what d4 is, but it seems to limit how much health you can have before the health bar overflows, I've ignored this in the past with some success, d0 is the max health, cc is the current health, so I'm assuming that ebp+08 is the location of the read in data, although I'm not quite sure. The reason, my ML= code is not the same as this is because of how completely un-optimized pixels code was. some of the code is redundant or useless, and the whole above thing could be condensed down a lot, however, for changing one line of code, I could just do:

00419CB0 | push ebp
00419CB1 | mov ebp,esp
00419CB3 | movsx eax,word ptr [ebp+0008]
00419CB7 | movsx ecx,word ptr [0049E6D0]
00419CBE | mov ecx,eax
00419CC0 | mov [0049E6D0],ecx
00419CC7 | movsx edx,word ptr [0049E6D0]
00419CCE | cmp edx,000000E8
00419CD4 | jle 00419CDF
00419CD6 | mov word ptr [0049E6D0],00E8
00419CDF | movsx eax,word ptr [ebp+0008]
00419CE3 | movsx ecx,word ptr [0049E6CC]
00419CEA | mov ecx,eax
00419CEC | mov [0049E6CC],ecx
00419CF3 | movsx edx,word ptr [0049E6CC]
00419CFA | mov [0049E6D4],edx
00419D00 | pop ebp
00419D01 | ret

okay, I'll try that, then get back to you about the other stuff.
lace
 
Feb 22, 2009 at 12:44 AM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
You know how when you get hit, the bar turns yellow to show how much you got hurt, then after a moment, the health kind of "ticks away" until you're left with however much? Well, 49e6d4 is the hp that's currently shown on the bar. I call it "displayed_health". 49e6cc is how much hp you really have. I call that "actual_health". You can see the code that handles this at 41a29b.

So let's get this function out of that assembly back into some C so it's easier to follow. I would translate it as follows (assuming that D0 really is your max hp value, I don't know that for sure):

Code:
void ml_plus(unsigned short add_amount)
{
p.max_health += add_amount;
if (p.max_health > 232)
{
p.max_health = 232;
}

p.actual_health += add_amount;
p.displayed_health = p.actual_health;
}

Two things I note curious about this function:

* Firstly, 232? Why 232? Is that the width of the health bar in pixels, maybe?

* While there's bounds checking on your MAX health, there is no bounds checking on your CURRENT hp value. So theoretically, after calling this function, it would be possible to end up with more HP than 232. I assume in that case, some other part of the code would very quickly yank it back down to max_health.

So anyway, that's why you've been able to ignore displayed_health (49e6d4). If you check the offset I showed you that handles it, you'll see that if (actual_health > displayed_health), then displayed_health is immediately set to actual_health (this is for if you say, pick up a heart).

I presume that your changes would in fact work, of course you don't really need the bounds checking in that case, but we're going for a one-opcode change, and that should work. I would condense it thusly:

Code:
push ebp
mov ebp,esp
movsx eax,word ptr [ebp+0008]
mov [0049E6D0],eax
mov [0049E6CC],eax
mov [0049E6D4],eax
pop ebp
ret

(If you're really tight on space, you could leave off "push ebp", "mov ebp, esp", and "pop ebp", and just read the input value straight from "esp". What would it be then, "esp+0004"?

Keep in mind two things here:

* Firstly, note that [ebp+8] is a WORD sized pointer (an "unsigned short" in C, in otherwords it is only 16-bits long). You _NEED_ the movsx here to properly clear the upper bytes of eax. (If you just used mov, the upper 16-bits of eax would be left with whatever garbage values were randomly in them before). Probably every TSC function takes a short, if you think about it, because of the 4-char limit no script command can take a numerical value larger than 9999, so a short is appropriate.

* 2nd, I don't note if TSC functions are supposed to have a return value or not. It seems to be acting like it's void, but just in case, I chose eax as the register to transfer the data (the calling convention is that the return value of functions is returned in eax). If the original function did have a return value, it would have always returned the same as whatever parameter you passed in. So just to be safe, I thought it might be a good idea to put the parameter into eax before returning.
 
Feb 22, 2009 at 8:56 PM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
I feel like I owe you a very, very long response because of all that useful information, so I'll try the best I can. There are two ways to find out if d0 is the max health, one of them is simple process of elimination, the other is testing. I chose to test it, a while back, cause it's more my style. This was another hack on ML+, Being:
Code:
  push ebp		
mov ecx,[0049E6CC]
add ecx,ecx
mov [0049E6D0],ecx
mov [0049E6CC],ecx
ret

This doubles the current health and sets it to the max health, and since it works, and since cc is the current health, d0 has to be the max health. 232 is the width of the health bar (full) +1. Interestingly, although this function is presumably supposed to stop the health from overflowing, the cap is, as far as I can tell, what causes the health to overflow. Go Figure.

Okay, this probably won't be as long as I hoped, so I might as well ask some questions, both fairly unrelated.

I - Why is ebp pushed and popped from the stack in every command? whenever it's assigned a value, it's reverted back to what it was before. The only reason I can think of is that it is used in other programs, and CS doesn't want to mess them up.

II - Where is the graphics code for the item screen? I want to stop the flickering of the selected item.

Okay, thanks a llama for your help. (Yes! A whole llama! ^^)
Lace
 
Feb 23, 2009 at 4:14 AM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
Ok ebp: This is generated by the compiler around every function. It's a pretty standard mechanism in any program and has to do with passing parameters into functions. Are you familiar with how the parameters are passed on the stack?

Say I'm foo(), and I want to call thingy(), passing it x and y, so it can do it's thingy on them and let me know the result.

Just for an example:
Code:
void foo(void)
{
...
thingy(x, y);
...
do_really_cool_thing_with_x;	// this is awesome!
do_fairly_cool_thing_with_y;	// need this later for bar()
...
return;
}

int thingy(int x, int y)
{
x += y;
x = x * 2;
return x;
}

X and Y would normally be passed from foo() to thingy() on the stack, like this:

Code:
push x			// store "x" for thingy()
push y			// store "y" for thingy()
call thingy		// do it!
add esp, 8		// take "x" and "y" back off the stack

"thingy" is supposed to set "eax" to it's return value (X in this example) before it returns.

So now we're in thingy() and the stack looks like this:

Code:
return_address [i](points back to foo())[/i]
value_of_y
value_of_x
...

Since thingy() can't pop y and x off the stack without first popping return_address, which would mean it then had no way to get back to foo(), it will instead access value_of_y and value_of_x by "peeking" back on the stack with a reference like [esp+4] or [esp+8], which looks at value_of_y or value_of_x respectively.

Well, the problem with that is, what if thingy() needed to use PUSH or POP for some reason. Say thingy() needed to call some other function, and pass it X and Y. The instant thingy() calls PUSH, it's going to screw up the relative positions of X and Y, and now [esp+4] points at the return address, not Y anymore. To avoid the confusing nightmare of having to keep track of all that, most compilers reserve ebp to be used as a sort of second stack pointer. At entry to every function the first thing they do is freeze the position of esp into ebp. Then they can use the stack as the stack like normal, and if they ever need to access their arguments, they can do it easily just by offsetting from ebp (so instead of [esp+4], they can now access Y through [ebp+4], which is the exact same thing except that it won't be screwed up if a PUSH or whatever moves esp around during thingy()). The reason they have to PUSH it first then POP it before return, is so thingy() doesn't screw up ebp for foo(), since foo() is also counting on always being to access it's parameters through ebp.

Oh, and every time Windows does a task switch ("pre-empts" a process for multitasking, momentarily giving CPU time to another running process) the kernel saves all the registers, then when it comes back to that program, it sets them back as they were before execution, so the program doesn't even know it had left. Since every thread effectively gets it's own set of registers, CS couldn't possibly screw up ebp for any other programs. But it could screw up ebp royally for itself, which is why the compiler is so attentive about saving ebp on entry to every function. In fact, it's so "paranoid" about it that sometimes it will even do it on functions that never touch ebp, are only 3 or 4 instructions long, and don't take any parameters.

Number II

I don't know. BUT, I don't think it should be very hard to do at all. What program are you using for debugging? This is how I would find it:

We need an "entry point" or "foot in the door" to get us into the general vicinity of code that has to do with inventory. To do this, one approach I use often if I've never reached that area of a program before is to use a GameShark-like cheat-finding program (I use a program called GameShock) to locate a memory address used by the code we're interested in, then setting a memory breakpoint on access/write to that address (usually Write yields more interesting results).

GameShock works by process of elimination: you tell it you want to find an "unknown value", and it scans the memory of your program and takes a snapshot of the entire memory contents. Then you go back to your program, and you do something that you think will say, make the interesting variable bigger. Go back to GameShock and push "Increased", and now every memory address that hasn't gotten bigger is dropped from the list. Do this around 10 or so times mixing it up with Increased and Decreased and you'll often zero right in on the variable you're looking for.

Once you think you've found it tell GameShock you want to set it as a "cheat" and just freeze the variable at whatever value it's currently at. Go back to CS and make sure that the thing you were interested in, is now screwed up (because it can't write to it's variable any more). If this works, turn the "cheat" off and copy the memory address GameShock found, set a Write breakpoint on it in your debugger, and you'll be taken straight to the section you want. Finding that, just read up and down through the assembly, maybe experiment a little, till you understand it good enough to know how to change whatever you want.

In this case, the thing I would be looking for in GameShock is the position of the "cursor", or which item you have selected. Because it's easily moveable around (incrementing and decrementing it's variable), it should be much easier to find then directly going after the flashing state.

As for the flashing state, knowing Pixel, I would guess, that this works based on a timer, which is incremented every frame, and the "bright" or "dim" is decided by taking either the modulus of this timer, or right-shifting it by around 3 or 4 bits then testing the least-significant bit of the result.

It's also possible that every time the timer reaches around 5 or 6, it toggles the frame, then resets itself. In this case, the most likely method of toggling the flashy state is adding 1 to the current frame, then setting it to zero if it becomes greater than 1 (he had a macro that did this). It's also possible to do a toggle by XOR'ing the current value with 1, or by subtracting it from 1, but while they're popular with some programmers, I haven't seen Pixel use those latter methods very often.
 
Feb 24, 2009 at 1:23 AM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
So it's just a barrier to stop nested commands from bleeding into each other?
That makes sense. Thanks for the tip on GameShock, right now I just pick apart the code from offsets other people post. Sort of annoying, cause I always have to ask if there isn't an offset I can trace through the code.
 
Feb 24, 2009 at 12:27 PM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
Essentially yes. It's not just TSC-related; every function in the whole game--in fact almost every program on your computer--does it. But yes, it's to keep nested functions from screwing up their callers. There are other ways to find offsets too but that is the main one I use. If you get into looking at the NPC's/enemies there is a table of function pointers you can pull out of the game which has the address of the code for every object in the game. That is very useful, too.
 
Feb 26, 2009 at 10:29 PM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
TEH AWESOME!!!

okay, thanks for the help.
I'm working on the save and load functions right now, but her'es an interesting tidbit I found:
004156D7 mov ecx,[0049E63C]
004156DD and ecx,00000100
004156E3 je 0041571F
004156E5 mov [ebp-0014],00000196
004156EC mov [ebp-000C],000002FF
004156F3 mov [ebp-001C],00000028
004156FA mov [ebp-0010],00000010
00415701 mov [ebp-0028],00000280
00415708 mov [ebp-0024],0000002A
0041570F mov [ebp-0018],00000010
00415716 mov [ebp-0020],00000019
0041571D jmp 00415757
0041571F mov [ebp-0014],0000032C
00415726 mov [ebp-000C],000005FF
0041572D mov [ebp-001C],00000050
00415734 mov [ebp-0010],00000020
0041573B mov [ebp-0028],00000500
00415742 mov [ebp-0024],00000055
00415749 mov [ebp-0018],00000020
00415750 mov [ebp-0020],00000033

basically, this is the underwater movement check. If you are underwater, your movement is slower. the possibilities for this are not quite endless, but are very cool, you could have the mimiga mask make you more agile, to make you seem more like a different character, you could make underwater have less or no gravity (space), utilize the water check for something else, like acid, have a suit of armor that you wear decrease your speed, or even add another check to the code. Another idea that comes to mind is in wtf story, you're never going to get life ups, but you could get agility ups by hacking the ml+ command, making you faster and with an epic jump height. Yes, this is a very nice piece of code. I hope you likes.
 
Feb 27, 2009 at 2:10 AM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
Smart is dead.
 
Feb 27, 2009 at 8:08 PM
Cold Agony of Resolute Vacuum
"Heavy swords for sale. Suitable for most RPG Protagonists. Apply now!"
Join Date: Jan 1, 2008
Location: Elsewhere
Posts: 1973
Lace is so smart.
S-M-R-T
Lace isn't dumb.
S-M-R-T
 
Feb 27, 2009 at 8:59 PM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
thank you, and I lost the game.
maybe smart was just taking a rest.
 
Mar 2, 2009 at 11:08 PM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
Hi Lace, yes, I know about that! I've been there too. FYI, the meanings of the variables in that section are as follows (this is for "normal" physics).

p.walkspeed = 0x032c;
p.fallspeed = 0x5ff;

p.fallaccel = 0x50;
p.jumpfallaccel = 0x20;

p.walkaccel = 0x55;
p.jumpwalkaccel = 0x20;

p.decelspeed = 0x33;
p.jumpvelocity = 0x500;

The "walkspeeds" and "fallspeeds" are the maximum velocity you can have. The "accel" speeds are how quickly you will reach that velocity (are added to your current speed every frame up until you get to maxspeed). decelspeed is your friction constant when you are on the ground. jumpvelocity is subtracted from your Y inertia when you first push jump.

I also have written here that the "effective" walk speed is actually only 0x30e, I didn't figure out exactly why but it may have to do with the order of application of the constants or something.

The deceleration constant p.decelspeed is applied at all times when you are touching the ground, so if you are walking this is in addition to the walk accel. Thus effectively your walkaccel is only 0x22 (It does +0x55, then -0x33). *1

Jump gravity p.jumpfallspeed is applied instead of fallspeed anytime the Jump key is down and you are moving upwards (it does not check whether you are actually jumping). *2

These constants of course are all bit-shifted by the coordinate scale factor of 9, and applied once per tick at 50fps. Thus a value of 512 means you will move 50 pixels per second.

*1 This is a good thing to know during the first Core battle in Almond. When he tries to blow you against the wall, he pushes against your X inertia with a accelerative force of 0x20. This is less than the p.decelspeed of 0x33, and exactly equal to the p.jumpwalkaccel of 0x20. Therefore, if you stay on the ground, he cannot push you at all because because you have friction against the ground of 0x33. But if you jump eg to avoid one of the shots, you will lose the 0x33 deceleration, and be pushed with 0x20. If you push full against the current, you can cancel it out from pushing you faster, but you will not gain any ground against whatever speed it's already got on you.

*2 This is how the fans work in e.g. Grasstown. You can ride them higher by pushing Jump because when you push the jump key it activates jump gravity, and then you are only falling into them with 0x20 instead of 0x55. You can also see an odd effect here if you find some other way to be moving upwards without jumping. For example, the "hurt hop" when you get hit by an enemy. If you press JUMP right after getting hit, you will fly a lot higher than you're supposed to.
 
Mar 2, 2009 at 11:11 PM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
You know there's a whole 'nother set of character sprites for the Mimiga mask, right? You could completely replace them with a totally different character, if you wanted to do like you said. Then change the check to check against the player equipped byte instead of the status byte, so it activates the other set of physics when you have the mask. hang on...ok that's 49e650--the equipped byte that is.
 
Mar 2, 2009 at 11:15 PM
Junior Member
"It's dangerous to go alone!"
Join Date: Jan 3, 2009
Location:
Posts: 36
Oh yeah, so what method did you decide to go with to save your data?
 
Mar 3, 2009 at 12:21 AM
Been here way too long...
"Life begins and ends with Nu."
Join Date: Jan 4, 2008
Location: Lingerie, but also, like, fancy curtains
Posts: 3054
hey sshsigi, thanks for the info. I was experimenting to see what they each did, but couldn't quite figure it out. to save my data, I was aiming on using some free code within the executable (would it get erased when the game is closed?), as I couldn't figure out how to save to an external file.

thanks for all the help you've given me on this.
lace
 
Mar 3, 2009 at 12:36 AM
Banned
"Bleep, Bloop, Bleep, Bloop"
Join Date: Mar 1, 2009
Location:
Posts: 1586
Age: 28
Now you go and make something amazing for us, mmm'kay?
 
Top