EQEmulator Forums

EQEmulator Forums (https://www.eqemulator.org/forums/index.php)
-   Development::Development (https://www.eqemulator.org/forums/forumdisplay.php?f=590)
-   -   SoF Spawn_Struct Hacking (https://www.eqemulator.org/forums/showthread.php?t=28176)

Shendare 05-10-2009 05:53 PM

SoF Spawn_Struct Hacking
 
I'm new to the PEQ scene, and would like to find ways to contribute.

I thought I might get my feet wet looking into the server code by trying to figure out some of the unknown fields in Spawn_Struct like haircolor, beardcolor, etc.

I found the place in SoF.cpp where such tinkering should go, inside ENCODE(OP_ZoneSpawns) starting around line 757, and knew there needed to be a better way to test things than coding values into random unknown fields and re-compiling for each test.

I put together a little bit of hack code that allows you to push test values into any of the unknown fields of the Spawn_Struct struct at run-time using a test NPC's lastname.

Code:

                //Hack Test for finding more fields in the Struct:
                if (emu->lastName[0] == '*') // Test NPC!
                {
                        char code = emu->lastName[1];
                        char* sep = (char*)memchr(&emu->lastName[2], '=', 10);
                        uint32 ofs;
                        uint32 val;
                        uint8 rnd = rand() & 0x0F;
                        if (sep == NULL)
                        {
                                ofs = 0;
                                if ((emu->lastName[2] < '0') || (emu->lastName[2] > '9'))
                                {
                                        val = rnd;
                                }
                                else
                                {
                                        val = atoi(&emu->lastName[2]);
                                }
                        }
                        else
                        {
                                sep[0] = NULL;
                                ofs = atoi(&emu->lastName[2]);
                                sep[0] = '=';
                                if ((sep[1] < '0') || (sep[1] > '9'))
                                {
                                        val = rnd;
                                }
                                else
                                {
                                        val = atoi(&sep[1]);
                                }
                        }

                        char hex[] = "0123456789ABCDEF";
                        size_t len = strlen(emu->lastName);
                       
                        eq->lastName[len + 0] = ' ';
                        eq->lastName[len + 1] = code;
                        eq->lastName[len + 2] = '0' + ((ofs / 1000) % 10);
                        eq->lastName[len + 3] = '0' + ((ofs / 100) % 10);
                        eq->lastName[len + 4] = '0' + ((ofs / 10) % 10);
                        eq->lastName[len + 5] = '0' + (ofs % 10);
                        eq->lastName[len + 6] = '=';
                        eq->lastName[len + 7] = '0' + ((val / 100) % 10);
                        eq->lastName[len + 8] = '0' + ((val / 10) % 10);
                        eq->lastName[len + 9] = '0' + (val % 10);
                        eq->lastName[len + 10] = 0x00;

                        switch (code)
                        {
                                case 'a':
                                        eq->unknown0001[ofs % 4] = val; break;
                                case 'b':
                                        eq->unknown0006 = val; break;
                                case 'c':
                                        eq->unknown0008 = val; break;
                                case 'd':
                                        eq->unknown0011[ofs % 6] = val; break;
                                case 'e':
                                        eq->unknown0009 = val; break;
                                case 'f':
                                        eq->unknown0010[ofs % 4] = val; break;
                                case 'g':
                                        eq->unknown0048[ofs % 4] = val; break;
                                case 'h':
                                        eq->unknown0059 = val; break;
                                case 'i':
                                        eq->unknown0074[ofs % 24] = val; break;
                                case 'j':
                                        eq->unknown0077[ofs % 7] = val; break;
                                case 'k':
                                        eq->unknown00771[ofs % 184] = val; break;
                                case 'l':
                                        eq->unknown00772[ofs % 10] = val; break;
                                case 'm':
                                        eq->unknown0079[ofs % 123] = val; break;
                                case 'n':
                                        eq->unknown0080[ofs % 10] = val; break;
                                case 'o':
                                        eq->unknown0106[ofs % 4] = val; break;
                                case 'p':
                                        eq->unknown0107[ofs % 5] = val; break;
                                case 'q':
                                        eq->unknown01071[ofs % 5] = val; break;
                                case 'r':
                                        eq->unknown0108[ofs % 6] = val; break;
                                case 's':
                                        eq->unknown01082[ofs % 6] = val; break;
                                case 't':
                                        eq->unknown0110[ofs % 20] = val; break;
                                case 'u':
                                        eq->unknown01101[ofs % 21] = val; break;
                                case 'v':
                                        eq->unknown0154[ofs % 4] = val; break;
                                case 'w':
                                        eq->unknown0155[ofs % 4] = val; break;
                                case 'x':
                                        eq->unknown0156[ofs % 4] = val; break;
                                case 'y':
                                        eq->unknown0157[ofs % 4] = val; break;
                                case 'z':
                                        eq->unknown0158[ofs % 4] = val; break;
                                case 'A':
                                        eq->unknown0159[ofs % 4] = val; break;
                                case 'B':
                                        eq->unknown0160[ofs % 4] = val; break;
                                case 'C':
                                        eq->unknown0161[ofs % 4] = val; break;
                                case 'D':
                                        eq->unknown0162[ofs % 4] = val; break;
                                case 'E':
                                        eq->unknown0163[ofs % 4] = val; break;
                                case 'F':
                                        eq->unknown0263[ofs % 2] = val; break;
                                case 'G':
                                        eq->unknown0281 = val; break;
                                case 'H':
                                        eq->unknown0308[ofs % 4] = val; break;
                                case 'I':
                                        eq->unknown0309[ofs % 11] = val; break;
                                case 'J':
                                        eq->unknown442[ofs % 8] = val; break;
                                case 'K':
                                        eq->unknown0760 = val; break;
                                case 'L':
                                        eq->unknown0761 = val; break;
                                case 'M':
                                        eq->unknown0762 = val; break;
                                case 'O':
                                        eq->unknown0763 = val; break;
                                case 'P':
                                        eq->unknown0764 = val; break;
                                case 'Q':
                                        eq->unknown0496[ofs % 2] = val; break;
                                case 'X':
                                        ((uint8*)eq)[ofs % 897] = val; break;
                                case 'Z':
                                        eq->size = (float)val; break; // Test w/ size.
                        }

To use the code, simply target any NPC in-game and change its last name to follow the special formula:

*(Code)[Optional-Offset=](Value)

I've been testing on Exterminator Valern just inside Felwithe, so I can target him and type "#npcedit lastname *Z2", then "#repop" (I've hotkeyed that one), and Valern will respawn with a size of 2, instead of his default 6!

You'll also get debug output tacked to the end of the lastname letting you know exactly what the hack-code interpreted from your formula. The debug output is in the format "(Code)(4-digit Offset)=(3-digit Value)". Because the Z code for size doesn't use an offset, the debug output for the previous example will be "Z0000=002".

If you want the code to pick a random value to put in the specified field, use any non-numeric character for the value. I generally use '?'. So, you could type "#npcedit lastname *Z?" and repop and the NPC will be a random size from 0-15 every time you repop.

For the code character, all alpha digits from a-z and A-Q map to specific sets of unknown fields in the Spawn_Struct. If you want to push the value 10 into unknown0110[3], for example, you would make the NPC's lastname "*t3=10" and repop, and there you go!

The real catch-all hack code is 'X'. If you specify X as the code character, you can push any value into any offset of Spawn_Struct. For example, looking in SoF_structs.h you see that gender is at offset 22 in the Spawn_Struct. If you specify "#npcedit lastname *X22=1", the NPC will repop as a female.

Anyway, I thought this might be useful for anyone else who feels like poking around in Spawn_Struct to try to figure out some of these many unknown fields as well!

- Shendare

trevius 05-10-2009 06:57 PM

Woah, thanks! I know Derision has some ways to read through the IDA output from the eqgame.exe that he can use to find many fields. I am not quite that knowledgeable about the ASM code. You have no idea how much of a pain it was to do the field finding stuff I have done for it so far lol. I wish I had this code from the start! I will definitely give it a try.

The SoF spawn struct to this point is pretty much chaotic. The unknown fields are essentially all just spacers at this point. We do not know the exact size of any particular unknown for sure. I had just been breaking them down and testing each section 1 by 1. It is hard to know what fields should be int8, in16 or int32 or maybe char.

I don't know how much of a pain it would be, but possibly if Derision has the time, maybe he could go through and break down the unknown parts of the structure into the proper sized fields. I am pretty sure he can do this just by reading the ASM code. Not that I want to give him more work, or offer him up to do something for us, but I think he is the only one in EQEmu who knows enough to actually do it. I may be wrong, but this step might not be too bad to do by just going down the ASM code.

Then, once the fields are broken down into the proper sizes and places, we can use this testing code to isolate what each field actually does. With the fields all in the right place, this step should be able to be done by just about anyone. I bet we can find new things that we didn't even know existed by doing this lol. Maybe we need to make a list of exactly what we are missing and also new stuff that we might expect to find. That way, we know exactly what to look for when testing this stuff so we do the appropriate test for each change we try.

Without having the proper field sizes and placement, the only other way I can think to do this properly would be to break down each unknown into separate int8s. That would mean a few hundred unknown int8 fields. It would also mean adjusting the case switching on that test code, but it wouldn't be too rough to do if necessary.

Shendare 05-10-2009 07:15 PM

Well, with just poking random 0-15 values into individual unknown bytes one by one over the course of the last hour, I've so far been able to isolate FlyMode.

It's in unknown00771[1], byte offset 174. So, if you use the lastname code *k1=2 or *X174=2 the NPC will have levitation FlyMode on.

I'm still poking away. I'm mostly through unknown0079[] so far, which probably puts me 2/3 through the unknowns!

Shendare 05-10-2009 07:37 PM

Woohoo!

hairstyle = unknown01082[4], Offset 533
haircolor = unknown01082[5], Offset 534
flymode = unknown00771[1], Offset 174

Shendare 05-10-2009 08:25 PM

Fixing hairstyle and haircolor in the struct definition and adding the eq->hairstyle = emu->hairstyle entries for the two into the .cpp code fixes NPCs, but turns my PC bald. Incorrect field location in the player profile?

trevius 05-10-2009 09:39 PM

It may depend on what exactly you are filling into those unknowns. Probably best to convert each one into separate int8s and see what happens. It could be that they are int32 and setting more than 1 of the bytes is making a number that is out of the range of the hair options, setting them to default of being bald. Purely guess at this point until I can do testing later.

Also, for your testing, I didn't think about it before, but it would have been best if you were using a Drakkin (race 522) for finding spawn struct stuff. I am sure they have a decent piece of the struct just for them and the new heritage settings they have.

Shendare 05-11-2009 01:17 AM

IsStatue = unknown0006, Offset 6 (Some models have a "Statue" animation that makes the NPC larger and striking a pose.)

DrakkinHeritage = unknown0011[3], Offset 14 (Body coloring, available colors at character creation dependent on player class)

DrakkinWhite = unknown0011[4], Offset 15 (Nonzero yields a white haired, white tattooed, white face-spotted Drakkin)

DrakkinWhite2 = unknown0011[5], Offset 16 (Acts like DrakkinWhite for all values 0-15 except 0, 2, 8, and 10)

NOTE: It's possible DrakkinWhite and DrakkinWhite2 somehow interact with DrakkinHeritage, and it's possible there's some 16-bit workings going on in the field causing the weirdness. Dunno. I can only test push 8-bit values at the moment.

DrakkinTattoo = unknown0079[0], Offset 369

Targetable11 = unknown0156[3], Offset 625 (If this field is set to 11, the NPC is untargetable. Weird, I know. Totally found it by accident!)

I'm still looking for beard color and Drakkin facial dots.

Also, I found the bug with my bald player character. This block of code in mob.cpp at line # 724 was messing things up. It assumes a zero value for the appearance fields is invalid, and sets such zero fields to 0xFF instead.

Code:

        ns->spawn.haircolor = haircolor ? haircolor : 0xFF;
        ns->spawn.beardcolor = beardcolor ? beardcolor : 0xFF;
        ns->spawn.eyecolor1 = eyecolor1 ? eyecolor1 : 0xFF;
        ns->spawn.eyecolor2 = eyecolor2 ? eyecolor2 : 0xFF;
        ns->spawn.hairstyle = hairstyle ? hairstyle : 0xFF;
        ns->spawn.face = luclinface;
        ns->spawn.beard = beard ? beard : 0xFF;

The problem is, zero is perfectly valid. It's generally the first available appearance option for each field! Zero is the matted-hair for high elves, the dark brown hair color for races that can use it, the moustache face for humans, etc. Setting them to 255 actually SET them to an invalid value, making anyone with a zero value for the fields bald or incorrectly colored.

I simply commented out the 0xFF stuff, and things started working normally again.

Code:

        ns->spawn.haircolor = haircolor; // ? haircolor : 0xFF;
        ns->spawn.beardcolor = beardcolor; // ? beardcolor : 0xFF;
        ns->spawn.eyecolor1 = eyecolor1; // ? eyecolor1 : 0xFF;
        ns->spawn.eyecolor2 = eyecolor2; // ? eyecolor2 : 0xFF;
        ns->spawn.hairstyle = hairstyle; // ? hairstyle : 0xFF;
        ns->spawn.face = luclinface;
        ns->spawn.beard = beard; // ? beard : 0xFF;

Maybe they were put in place to correct a render problem in a pre-SoF client. I tried messing around with it for a couple of minutes with the Titanium client, but I couldn't even get beards to load with Titanium to test with. Hair color and hair style seemed to work properly with the 0 values, so the 0xFF stuff might have been implemented for a pre-Titanium client, though that would mean this bug would have been around for a long time.

trevius 05-11-2009 03:39 AM

Quote:

Originally Posted by Shendare (Post 169500)
Targetable11 = unknown0156[3], Offset 625 (If this field is set to 11, the NPC is untargetable. Weird, I know. Totally found it by accident!)

That is interesting, because body type 11 is untargetable. So, my guess is that our current field for bodytype is probably incorrect. I got most of the fields originally from SEQ and they really only care about a few key parts of that structure. This probably explains why certain NPCs show up on SoF that shouldn't. Or at least partially explains that possibly.

Quote:

Originally Posted by Shendare (Post 169500)
Also, I found the bug with my bald player character. This block of code in mob.cpp at line # 724 was messing things up. It assumes a zero value for the appearance fields is invalid, and sets such zero fields to 0xFF instead.

Code:

        ns->spawn.haircolor = haircolor ? haircolor : 0xFF;
        ns->spawn.beardcolor = beardcolor ? beardcolor : 0xFF;
        ns->spawn.eyecolor1 = eyecolor1 ? eyecolor1 : 0xFF;
        ns->spawn.eyecolor2 = eyecolor2 ? eyecolor2 : 0xFF;
        ns->spawn.hairstyle = hairstyle ? hairstyle : 0xFF;
        ns->spawn.face = luclinface;
        ns->spawn.beard = beard ? beard : 0xFF;

The problem is, zero is perfectly valid. It's generally the first available appearance option for each field! Zero is the matted-hair for high elves, the dark brown hair color for races that can use it, the moustache face for humans, etc. Setting them to 255 actually SET them to an invalid value, making anyone with a zero value for the fields bald or incorrectly colored.

I simply commented out the 0xFF stuff, and things started working normally again.

Code:

        ns->spawn.haircolor = haircolor; // ? haircolor : 0xFF;
        ns->spawn.beardcolor = beardcolor; // ? beardcolor : 0xFF;
        ns->spawn.eyecolor1 = eyecolor1; // ? eyecolor1 : 0xFF;
        ns->spawn.eyecolor2 = eyecolor2; // ? eyecolor2 : 0xFF;
        ns->spawn.hairstyle = hairstyle; // ? hairstyle : 0xFF;
        ns->spawn.face = luclinface;
        ns->spawn.beard = beard; // ? beard : 0xFF;

Maybe they were put in place to correct a render problem in a pre-SoF client. I tried messing around with it for a couple of minutes with the Titanium client, but I couldn't even get beards to load with Titanium to test with. Hair color and hair style seemed to work properly with the 0 values, so the 0xFF stuff might have been implemented for a pre-Titanium client, though that would mean this bug would have been around for a long time.

This is an interesting find and might be a great breakthrough for handling face settings. Definitely worth looking into.

I am going to have to give this code a try. It beats the crap out of changing whole chunks of the struct to a certain value, compiling, starting the server, logging in and hopefully seeing a difference, and then starting the whole process over again lol.

trevius 05-11-2009 05:52 AM

LOL, I got it setup and playing with it now. Just found some weird effect with "i5" as the last name. It creates a rain cloud above the spawn and rains on them for a few seconds. Also, other ones like i10, i11, i2, and more have really cool effects as well. I am guessing you need spell effects on to see these, but they are things I have never seen before :P Not sure exactly what they are for other than maybe having invisible NPCs use them to create visual effects maybe. Definitely loving the new way of handling this testing.

Also, after trying X613=11, I verified that the current setting for bodytype works, but I wonder why they have 2 fields for it, since X625=11 makes them untargetable as well.

Quote:

DrakkinHeritage = unknown0011[3], Offset 14 (Body coloring, available colors at character creation dependent on player class)

DrakkinWhite = unknown0011[4], Offset 15 (Nonzero yields a white haired, white tattooed, white face-spotted Drakkin)

DrakkinWhite2 = unknown0011[5], Offset 16 (Acts like DrakkinWhite for all values 0-15 except 0, 2, 8, and 10)
Yeah, 14, and 15 definitely seem to be an int16 for heritage. Not sure why 16 effects it too, but maybe it is an int32 and only accept certain ranges.

Found Drakkin face spikes are 882 and they range from 0 to 7. Also note that Heritage ranges from 0 to 6.

770 appears to be an int32 that if you set anything in (other than 0), the NPC will not be visible. It will still be targetable with /target, but that seems to be the only way.

740 puts an exclamation point in front of the name of the NPC.

656 is invisible/sneaking

Shendare 05-11-2009 12:12 PM

Yeah, I was messing with 770 a little and found that my client crashed if I attacked the NPC while it was invisible.

Good find on the special i-codes. I totally forgot I'd turned off spell effects for something or other. I totally missed them because of that!

The only thing I haven't found that I've been looking for is beard color, and I'm kicking myself for not having the foresight to make sure my test NPCs had beards while I was looking for hairstyle and haircolor. Bonehead.

I also figured out that some races (high elves for one) get their facial hair not from the beard field, but from a tens-digit offset to the face field. This also appears to be the way Barbarians get their woads.

Checks for these should probably be put into the OP_ZoneSpawns area there to check the race and (if necessary) dynamically adjust the eq->face field by (beard * 10) for beard-face races. I'm guessing we'll have to add some fields to npc_types to allow for face decorations like woads (if race == Barb, face += face_decoration*10) and Drakkin spikes, as well as Drakkin tattoos and heritage.

Shendare 05-11-2009 12:24 PM

Quote:

Originally Posted by trevius (Post 169504)
...
740 puts an exclamation point in front of the name of the NPC.
...

Oh my god, I think you found Doug! I remember from years and years ago after a patch, people were finding unresponsive naked human male models in random places in the game with only an exclamation point as their name. The EQ team responded to the community's questions with the response that these were test characters that aren't normally visible, and they'd figure out what broke in the patch that allowed people to see them. The team revealed the little tidbit that they had taken to calling these default human models "Doug", so whenever someone in the game found an unresponsive naked human male NPC that probably shouldn't be there (or should look different), they reported a "Doug".

I wonder if offset 740 marks the character as an EQ dev test character, and the original Doug didn't actually have "!" as its name, but had a blank name with the equivalent of Flag 740 set. That just blows my mind, and totally brings back some old EQ nostalgia. LOL.

- Shendare

trevius 05-11-2009 06:00 PM

Yeah, woads being set by factor of 10 on the face setting is known and in the wiki I believe. Shouldn't be too hard to find beard color and such once test is done with an NPC that has a beard set on them since beard is already identified.

I was thinking about the storm cloud that spawns from i5 and I actually recall something in either BoT or PoStorms about storm clouds spawning over your head and you have to move or something or something bad happens. Not too clear on the details, but it occurred to me that this is probably a case of when those effects are used.

I bet we could apply your same code to figure out more structure related stuff. Like, we could modify it for the player profile by using something that is easily changeable like currency. Just have it so that if the character has 1337 copper on their character, it will go into test mode. Then, use the plat and gold on that character to set the offset (number of plat) and what to set that offset to (number of gold). It would require zoning each time to test the changes, but it is at least a possible option to consider if we need it. Most of the player profile should be pretty good already, but if we still need to find anything in it, this would probably work.

One thing we need to get working better cosmetically is the character select screen. Unfortunately I think the client limits names during creation to only include letters. So either we would need to use letters and convert them or maybe use stats, but stats would probably be even trickier. Maybe for testing, the first 3 letters could be Z (Zzz) to designate that it is a test, and the 4th letter could be the field in the struct and the 5th would be the value to set it at. This might get a bit confusing, but the char select struct isn't overly large so it shouldn't be too much work to get it all figured out. Can't really set offsets since it is a variable length structure. I think the struct is already pretty close, so maybe finding what is throwing it off would make an easy fix to align everything back up again. There are only a few unknown fields in that struct, so I am sure those are Drakkin related, or at least most of them probably are.

Maybe for char select, we could use 1337 speak lettering in place of numbers to make it easier to set.

0 = o (O)
1 = i (I)
2 = z (Z)
3 = e (E)
4 = a (A)
5 = s (S)
6 = g (G)
7 = t (T)
8 = b (B)
9 = q (Q)

So, if you wanted to set field "a" (level) to 25, you would name the character "Zzzazs". Or if you wanted to set field "b" (haircolor) to 10, you would name the character Zzzbio.

This isn't quite as critical as the spawn struct, but everything that gets fixed makes SoF that much closer to being fully completed :)

Shendare 05-11-2009 06:56 PM

Sounds interesting! I'm all for getting SoF working better.

I did notice I left a quickie hack in the code that should be cleaned up.

This line...

Code:

  size_t len = strlen(emu->lastName);
...should be moved up to this block, which should be changed to refer to it...

Code:

  char code = emu->lastName[1];

+ size_t len = strlen(emu->lastName);
- char* sep = (char*)memchr(&emu->lastName[2], '=', 10);
+ char* sep = (char*)memchr(&emu->lastName[2], '=', len - 2);

  uint32 ofs;

This prevents reading beyond the end of lastName in case it's fewer than 10 characters including the null.

trevius 05-12-2009 07:45 AM

I finally identified beard color as offset 373. Got all of the new fields in and adjusted the struct to have the proper offset labels as well as correctly renamed all of the unknowns. Seems to work perfectly for NPCs now. I also added in your change to mob.cpp for facial features, but that might require further testing to verify if it is a good change or not (though it makes sense to me). And, I added in your testing code in place of the sloppy hack I had commented out before lol. It is commented out, but I figure it might be useful to others doing struct work, so it may be a good idea to keep it in there as commented out. I put this up on the SVN on R501 and gave you (shendare) credit for it since you did most of it.

I think that pretty much finalizes the spawn struct, or at least very close now. Still need to figure out a few things for players, but that shouldn't be too hard I think. I put temp hacks in for the drakkin extra features so they get set in the encode to whatever face is set to. At least this will let some variety into drakkin NPCs (and PCs for now as well).

Shendare 05-12-2009 09:25 AM

I'm glad to see that BeardColor gets applied to the face-based facial hair as well as the beard field mesh facial hair.

I dinked around a little with player appearance as well, but found that I had to modify some things. SoF's FaceChange_Struct is 24 bytes now instead of 7, assumedly to account for Tattoo, DrakkinFaceSpikes, and possibly DrakkinHeritage (though that's read-only in face changes). The rest must be reserved space.

I also noticed a block in Client::Handle_OP_FaceChange (or whatever it was called) that did something similar to the 0xFF problem above, except this time it's changing zero values to 99. That'll have to be taken out to properly handle face changes as well.

Then it's just a matter of making sure things are applied in PlayerProfile properly.

trevius 05-12-2009 04:57 PM

Yeah, I am not quite sure why they have it changing the values at all, but it seems to change them to 99 or 255 in some cases. As far as I can tell, we shouldn't be changing anything and just save the actual value and call the actual value. The conversions it is doing is almost certainly the cause of baldness and why faces have had so many issues in the past.

I know KLS did quite a bit of work on facial features in the past and I am wondering if she has any input on why those values are changed like that before I start making changes so that it uses the actual value instead of all of this converting.

Oh yeah, Drakkin NPCs look much cooler now that they aren't all exactly the same.

Another thing I wanted to mention is that a while back, I got the illusion struct all identified other than the Drakkin specific stuff. It would be nice if we could get facechange and other facial features added into the illusion function since they currently are not. I think the problem is that Titanium doesn't have the illusion struct filled out fully, so no one bothered to finish the function for illusion to add in the other features. It would also be very cool to have armor stuff (texture and tint) in the illusion function. Then, when someone clicks off an illusion, they would actually look exactly like they did before the illusion was cast instead of just returning to the same race and armor texture. I am pretty sure everything is in the illusion struct and we just need to find the armor and Drakkin stuff to completely finalize it. It wouldn't be hard to finalize the struct, but the code to handle all of it might take a bit of work to do. It would be awesome to expand the #fixmob functionality to work with faces, hairstyle, haircolor, eyecolor, beard, beardcolor, armor tint, and the new Drakkin features. Then, you could really fine tune an NPC in realtime before creating it in the database. Otherwise, it means a database change and a repop to see how it looks. Much quicker and easier to just have hotkeys and spam through the different looks until you find one you like.

trevius 05-12-2009 08:01 PM

Here are at least a couple places where we can try removing the 99 change for features set to 0 and see if that corrects the issues with bald players and features not showing up correctly once and for all:


zone/client_packet.cpp
Code:

void Client::Handle_OP_FaceChange(const EQApplicationPacket *app)
{
        if (app->size != sizeof(FaceChange_Struct)) {
                LogFile->write(EQEMuLog::Error, "Invalid size for OP_FaceChange: Expected: %i, Got: %i",
                        sizeof(FaceChange_Struct), app->size);
                return;
        }

        // Notify other clients in zone
        entity_list.QueueClients(this, app, false);

        FaceChange_Struct* fc = (FaceChange_Struct*)app->pBuffer;
        m_pp.haircolor        = fc->haircolor;
        m_pp.beardcolor        = fc->beardcolor;
        m_pp.eyecolor1        = fc->eyecolor1;
        m_pp.eyecolor2        = fc->eyecolor2;
        m_pp.hairstyle        = fc->hairstyle;
        m_pp.face                = fc->face;
// vesuvias - appearence fix
        m_pp.beard                = fc->beard;

if (fc->face == 0)      {m_pp.face = 99;}
        if (fc->eyecolor1 == 0)  {m_pp.eyecolor1 = 99;}
        if (fc->eyecolor2 == 0)  {m_pp.eyecolor2 = 99;}
        if (fc->hairstyle == 0)  {m_pp.hairstyle = 99;}
        if (fc->haircolor == 0)  {m_pp.haircolor = 99;}
        if (fc->beard == 0)      {m_pp.beard = 99;}
        if (fc->beardcolor == 0) {m_pp.beardcolor = 99;}

        Save();
        Message_StringID(13,FACE_ACCEPTED);
        //Message(13, "Facial features updated.");
        return;
}

world/client.cpp
Code:

bool Client::OPCharCreate(char *name, CharCreate_Struct *cc)
{
        PlayerProfile_Struct pp;
        ExtendedProfile_Struct ext;
        Inventory inv;
        time_t bday = time(NULL);
        char startzone[50]={0};
        uint32 i;
        struct in_addr        in;

                       
        int stats_sum = cc->STR + cc->STA + cc->AGI + cc->DEX +
                cc->WIS + cc->INT + cc->CHA;

        in.s_addr = GetIP();
        clog(WORLD__CLIENT,"Character creation request from %s LS#%d (%s:%d) : ", GetCLE()->LSName(), GetCLE()->LSID(), inet_ntoa(in), GetPort());
        clog(WORLD__CLIENT,"Name: %s", name);
        clog(WORLD__CLIENT,"Race: %d  Class: %d  Gender: %d  Deity: %d  Start zone: %d",
                cc->race, cc->class_, cc->gender, cc->deity, cc->start_zone);
        clog(WORLD__CLIENT,"STR  STA  AGI  DEX  WIS  INT  CHA    Total");
        clog(WORLD__CLIENT,"%3d  %3d  %3d  %3d  %3d  %3d  %3d    %3d",
                cc->STR, cc->STA, cc->AGI, cc->DEX, cc->WIS, cc->INT, cc->CHA,
                stats_sum);
        clog(WORLD__CLIENT,"Face: %d  Eye colors: %d %d", cc->face, cc->eyecolor1, cc->eyecolor2);
        clog(WORLD__CLIENT,"Hairstyle: %d  Haircolor: %d", cc->hairstyle, cc->haircolor);
        clog(WORLD__CLIENT,"Beard: %d  Beardcolor: %d", cc->beard, cc->beardcolor);

        // validate the char creation struct
        if(!CheckCharCreateInfo(cc))
        {
                clog(WORLD__CLIENT_ERR,"CheckCharCreateInfo did not validate the request (bad race/class/stats)");
                return false;
        }

        // Convert incoming cc_s to the new PlayerProfile_Struct
        memset(&pp, 0, sizeof(PlayerProfile_Struct));        // start building the profile
       
        InitExtendedProfile(&ext);
       
        strncpy(pp.name, name, 63);
        // clean the capitalization of the name
#if 0        // on second thought, don't - this will just make the creation fail
// because the name won't match what was already reserved earlier
        for (i = 0; pp.name[i] && i < 63; i++)
        {
                if(!isalpha(pp.name[i]))
                        return false;
                pp.name[i] = tolower(pp.name[i]);
        }
        pp.name[0] = toupper(pp.name[0]);       
#endif

        pp.race                                = cc->race;
        pp.class_                        = cc->class_;
        pp.gender                        = cc->gender;
        pp.deity                        = cc->deity;
        pp.STR                                = cc->STR;
        pp.STA                                = cc->STA;
        pp.AGI                                = cc->AGI;
        pp.DEX                                = cc->DEX;
        pp.WIS                                = cc->WIS;
        pp.INT                                = cc->INT;
        pp.CHA                                = cc->CHA;
        pp.face                                = cc->face;
        pp.eyecolor1        = cc->eyecolor1;
        pp.eyecolor2        = cc->eyecolor2;
        pp.hairstyle        = cc->hairstyle;
        pp.haircolor        = cc->haircolor;
        pp.beard                        = cc->beard;
        pp.beardcolor        = cc->beardcolor;

if (cc->face == 0)      {pp.face = 99;}
        if (cc->eyecolor1 == 0)  {pp.eyecolor1 = 99;}
        if (cc->eyecolor2 == 0)  {pp.eyecolor2 = 99;}
        if (cc->hairstyle == 0)  {pp.hairstyle = 99;}
        if (cc->haircolor == 0)  {pp.haircolor = 99;}
        if (cc->beard == 0)      {pp.beard = 99;}
        if (cc->beardcolor == 0) {pp.beardcolor = 99;}

        pp.birthday                = bday;
        pp.lastlogin        = bday;
        pp.level                        = 1;
        pp.points                        = 5;
        pp.cur_hp                        = 1000; // 1k hp during dev only
        //what was the point of this? zone dosent handle this:
        //pp.expAA                        = 0xFFFFFFFF;

        pp.hunger_level = 6000;
        pp.thirst_level = 6000;


        // FIXME: FV roleplay, database goodness...

        // Racial Languages
        SetRacialLanguages( &pp ); // bUsh
        SetRaceStartingSkills( &pp ); // bUsh
        SetClassStartingSkills( &pp ); // bUsh
        pp.skills[SENSE_HEADING] = 200;
        // Some one fucking fix this to use a field name. -Doodman
        //pp.unknown3596[28] = 15; // @bp: This is to enable disc usage
//        strcpy(pp.servername, WorldConfig::get()->ShortName.c_str());
                       

        for(i = 0; i < MAX_PP_SPELLBOOK; i++)
                pp.spell_book[i] = 0xFFFFFFFF;

        for(i = 0; i < MAX_PP_MEMSPELL; i++)
                pp.mem_spells[i] = 0xFFFFFFFF;

        for(i = 0; i < BUFF_COUNT; i++)
                pp.buffs[i].spellid = 0xFFFF;

       
        //was memset(pp.unknown3704, 0xffffffff, 8);
        //but I dont think thats what you really wanted to do...
        //memset is byte based
       
        //If server is PVP by default, make all character set to it.
        pp.pvp = database.GetServerType() == 1 ? 1 : 0;                       
               
        // if there's a startzone variable put them in there
        if(database.GetVariable("startzone", startzone, 50))
        {
                clog(WORLD__CLIENT,"Found 'startzone' variable setting: %s", startzone);
                pp.zone_id = database.GetZoneID(startzone);
                if(pp.zone_id)
                        database.GetSafePoints(pp.zone_id, &pp.x, &pp.y, &pp.z);
                else
                        clog(WORLD__CLIENT_ERR,"Error getting zone id for '%s'", startzone);
        }
        else        // otherwise use normal starting zone logic
        {
                if(!SoFClient)
                        database.GetStartZone(&pp, cc);
                else
                        database.GetStartZoneSoF(&pp, cc);
        }

        if(!pp.zone_id)
        {
                pp.zone_id = 1;                // qeynos
                pp.x = pp.y = pp.z = -1;
        }

        if(!pp.binds[0].zoneId)
        {
                pp.binds[0].zoneId = pp.zone_id;
                pp.binds[0].x = pp.x;
                pp.binds[0].y = pp.y;
                pp.binds[0].z = pp.z;
                pp.binds[0].heading = pp.heading;
        }

               
        clog(WORLD__CLIENT,"Current location: %s  %0.2f, %0.2f, %0.2f",
                database.GetZoneName(pp.zone_id), pp.x, pp.y, pp.z);
        clog(WORLD__CLIENT,"Bind location: %s  %0.2f, %0.2f, %0.2f",
                database.GetZoneName(pp.binds[0].zoneId), pp.binds[0].x, pp.binds[0].y, pp.binds[0].z);


        // Starting Items inventory
        database.SetStartingItems(&pp, &inv, pp.race, pp.class_, pp.deity, pp.zone_id, pp.name, GetAdmin());
                       
                       
        // now we give the pp and the inv we made to StoreCharacter
        // to see if we can store it
        if (!database.StoreCharacter(GetAccountID(), &pp, &inv, &ext))
        {
                clog(WORLD__CLIENT_ERR,"Character creation failed: %s", pp.name);
                return false;
        }
        else
        {
                clog(WORLD__CLIENT,"Character creation successful: %s", pp.name);
                return true;
        }
}

worlddb.cpp
Code:

void WorldDatabase::GetCharSelectInfo(int32 account_id, CharacterSelect_Struct* cs) {
        char errbuf[MYSQL_ERRMSG_SIZE];
        char* query = 0;
        MYSQL_RES *result;
        MYSQL_ROW row;
        Inventory *inv;
       
        for (int i=0; i<10; i++) {
                strcpy(cs->name[i], "<none>");
                cs->zone[i] = 0;
                cs->level[i] = 0;
            cs->tutorial[i] = 0;
                cs->gohome[i] = 0;
        }
       
        int char_num = 0;
        unsigned long* lengths;
       
        // Populate character info
        if (RunQuery(query, MakeAnyLenString(&query, "SELECT name,profile,zonename,class,level FROM character_ WHERE account_id=%i order by name limit 10", account_id), errbuf, &result)) {
                safe_delete_array(query);
                while ((row = mysql_fetch_row(result))) {
                        lengths = mysql_fetch_lengths(result);
                        ////////////
                        ////////////        This is the current one, the other are for converting
                        ////////////
                        if ((lengths[1] == sizeof(PlayerProfile_Struct))) {
                                strcpy(cs->name[char_num], row[0]);
                                PlayerProfile_Struct* pp = (PlayerProfile_Struct*)row[1];
                                uint8 clas = atoi(row[3]);
                                uint8 lvl = atoi(row[4]);
                               
                                // Character information
                                if(lvl == 0)
                                        cs->level[char_num]                                = pp->level;        //no level in DB, trust PP
                                else
                                        cs->level[char_num]                                = lvl;
                                if(clas == 0)
                                        cs->class_[char_num]                        = pp->class_;        //no class in DB, trust PP
                                else
                                        cs->class_[char_num]                        = clas;
                                cs->race[char_num]                                = pp->race;
                                cs->gender[char_num]                        = pp->gender;
                                cs->deity[char_num]                                = pp->deity;
                                cs->zone[char_num]                                = GetZoneID(row[2]);
                                cs->face[char_num]                                = pp->face;
                                cs->haircolor[char_num]                = pp->haircolor;
                                cs->beardcolor[char_num]        = pp->beardcolor;
                                cs->eyecolor2[char_num]        = pp->eyecolor2;
                                cs->eyecolor1[char_num]        = pp->eyecolor1;
                                cs->hair[char_num]                                = pp->hairstyle;
                                cs->beard[char_num]                                = pp->beard;
if (pp->face == 99)      {cs->face[char_num] = 0;}
if (pp->eyecolor1 == 99)  {cs->eyecolor1[char_num] = 0;}
if (pp->eyecolor2 == 99)  {cs->eyecolor2[char_num] = 0;}
if (pp->hairstyle == 99)  {cs->hair[char_num] = 0;}
if (pp->haircolor == 99)  {cs->haircolor[char_num] = 0;}
if (pp->beard == 99)      {cs->beard[char_num] = 0;}
if (pp->beardcolor == 99) {cs->beardcolor[char_num] = 0;}
                               
                                if(RuleB(World, EnableTutorialButton) && (lvl <= RuleI(World, MaxLevelForTutorial)))
                                        cs->tutorial[char_num] = 1;

                                if(RuleB(World, EnableReturnHomeButton)) {
                                        int now = time(NULL);
                                        if((now - pp->lastlogin) >= RuleI(World, MinOfflineTimeToReturnHome))
                                                cs->gohome[char_num] = 1;
                                }

                                // Character's equipped items
                                // @merth: Haven't done bracer01/bracer02 yet.
                                // Also: this needs a second look after items are a little more solid
                                // NOTE: items don't have a color, players MAY have a tint, if the
                                // use_tint part is set.  otherwise use the regular color
                                inv = new Inventory;
                                if(GetInventory(account_id, cs->name[char_num], inv))
                                {
                                        for (uint8 material = 0; material <= 8; material++)
                                        {
                                                uint32 color;
                                                ItemInst *item = inv->GetItem(Inventory::CalcSlotFromMaterial(material));
                                                if(item == 0)
                                                        continue;

                                                cs->equip[char_num][material] = item->GetItem()->Material;

                                                if(pp->item_tint[material].rgb.use_tint)        // they have a tint (LoY dye)
                                                        color = pp->item_tint[material].color;
                                                else        // no tint, use regular item color
                                                        color = item->GetItem()->Color;

                                                cs->cs_colors[char_num][material].color = color;

                                                // the weapons are kept elsewhere
                                                if ((material==MATERIAL_PRIMARY) || (material==MATERIAL_SECONDARY))
                                                {
                                                        if(strlen(item->GetItem()->IDFile) > 2) {
                                                                int32 idfile=atoi(&item->GetItem()->IDFile[2]);
                                                                if (material==MATERIAL_PRIMARY)
                                                                        cs->primary[char_num]=idfile;
                                                                else
                                                                        cs->secondary[char_num]=idfile;
                                                        }
                                                }
                                        }
                                }
                                else
                                {
                                        printf("Error loading inventory for %s\n", cs->name[char_num]);
                                }
                                safe_delete(inv);       
                                if (++char_num > 10)
                                        break;
                        }
                        else
                        {
                                cout << "Got a bogus character (" << row[0] << ") Ignoring!!!" << endl;
                                cout << "PP length ="<<lengths[1]<<" but PP should be "<<sizeof(PlayerProfile_Struct)<<endl;
                                //DeleteCharacter(row[0]);
                        }
                }
                mysql_free_result(result);
        }
        else
        {
                cerr << "Error in GetCharSelectInfo query '" << query << "' " << errbuf << endl;
                safe_delete_array(query);
                return;
        }
       
        return;
}

mob.cpp
Code:

void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
{
        int i;

        strcpy(ns->spawn.name, name);
        if(IsClient())
                strncpy(ns->spawn.lastName,lastname,sizeof(lastname));
        ns->spawn.heading        = FloatToEQ19(heading);
        ns->spawn.x                        = FloatToEQ19(x_pos);//((sint32)x_pos)<<3;
        ns->spawn.y                        = FloatToEQ19(y_pos);//((sint32)y_pos)<<3;
        ns->spawn.z                        = FloatToEQ19(z_pos);//((sint32)z_pos)<<3;
        ns->spawn.spawnId        = GetID();
        ns->spawn.curHp        = (sint16)GetHPRatio();
        ns->spawn.max_hp        = 100;                //this field needs a better name
        ns->spawn.race                = race;
        ns->spawn.runspeed        = runspeed;
        ns->spawn.walkspeed        = runspeed * 0.5f;
        ns->spawn.class_        = class_;
        ns->spawn.gender        = gender;
        ns->spawn.level                = level;
        ns->spawn.deity                = deity;
        ns->spawn.animation        = 0;
        ns->spawn.findable        = findable?1:0;
// vesuvias - appearence fix
        ns->spawn.light                = light;


        ns->spawn.invis                = (invisible || hidden) ? 1 : 0;        // TODO: load this before spawning players
        ns->spawn.NPC                = IsClient() ? 0 : 1;
        ns->spawn.petOwnerId        = ownerid;

        ns->spawn.haircolor = haircolor ? haircolor : 0xFF;
        ns->spawn.beardcolor = beardcolor ? beardcolor : 0xFF;
        ns->spawn.eyecolor1 = eyecolor1 ? eyecolor1 : 0xFF;
        ns->spawn.eyecolor2 = eyecolor2 ? eyecolor2 : 0xFF;
        ns->spawn.hairstyle = hairstyle ? hairstyle : 0xFF;
        ns->spawn.face = luclinface;
        ns->spawn.beard = beard ? beard : 0xFF;
        ns->spawn.equip_chest2  = texture;

//        ns->spawn.invis2 = 0xff;//this used to be labeled beard.. if its not FF it will turn
                                                                  //mob invis

        if(helmtexture && helmtexture != 0xFF)
        {
                //ns->spawn.equipment[MATERIAL_HEAD] = helmtexture;
                ns->spawn.helm=helmtexture;
        } else {
                //ns->spawn.equipment[MATERIAL_HEAD] = 0;
                ns->spawn.helm = 0;
        }
       
        ns->spawn.guildrank        = 0xFF;
        ns->spawn.size                        = size;
        ns->spawn.bodytype = bodytype;
        // The 'flymode' settings have the following effect:
        // 0 - Mobs in water sink like a stone to the bottom
        // 1 - Same as #flymode 1
        // 2 - Same as #flymode 2
        // 3 - Mobs in water do not sink. A value of 3 in this field appears to be the default setting for all mobs
        //    (in water or not) according to 6.2 era packet collects.
        if(IsClient())
                ns->spawn.flymode = 0;
        else
                ns->spawn.flymode = 3;
       
        ns->spawn.lastName[0] = '\0';
       
        strncpy(ns->spawn.lastName, lastname, sizeof(lastname));

        for(i = 0; i < MAX_MATERIALS; i++)
        {
                ns->spawn.equipment[i] = GetEquipmentMaterial(i);
                ns->spawn.colors[i].color = GetEquipmentColor(i);
        }
       
        memset(ns->spawn.set_to_0xFF, 0xFF, sizeof(ns->spawn.set_to_0xFF));
       
}

It makes sense that NPCs load perfectly as they are set because they don't do these weird conversions. So, I think it is logical to conclude that the 99 and the 0xFF settings that are getting done are to blame for PCs not showing up properly. Seems to me like things were being overcomplicated when they should just save the number that is set and use that same number when loading the settings. The bald characters and the white hair issues should both be caused by the 99 and 0xFF settings, so removing that should correct them.

Shendare 05-13-2009 12:07 AM

Changes needed to implement face changing in SoF:

Code:

// eq_packet_structs.h
struct FaceChange_Struct {
/*000*/        int8        haircolor;
/*001*/        int8        beardcolor;
/*002*/        int8        eyecolor1;
/*003*/        int8        eyecolor2;
/*004*/        int8        hairstyle;
/*005*/        int8        beard;
/*006*/        int8        face;
/*007*/ int8        unknown07;
/*008*/ int32        heritage;
/*012*/ int32        tattoo;
/*016*/ int32        details;
/*020*/ int32        unknown;
};

Code:

// client_packet.cpp - Client::Handle_OP_FaceChange()
void Client::Handle_OP_FaceChange(const EQApplicationPacket *app)
{
  // [snip] ...
       
        // FaceChange sends 0xFF as code for "Field Not Applicable For This Race"
        // Except Drakkin fields, which are always 0 for non-Drakkin.
        // Better to store 0 in DB than 0xFF in case race changes,
        //  because then field may become applicable again!
        m_pp.hair = (fc->hair == 0xFF) ? 0 : fc->hair;
        m_pp.haircolor = (fc->haircolor == 0xFF) ? 0 : fc->haircolor;
        m_pp.beard = (fc->beard == 0xFF) ? 0 : fc->beard;
        m_pp.beardcolor = (fc->beardcolor == 0xFF) ? 0 : fc->beard;

        switch (m_pp.race)
        {
                case BARBARIAN:
                        m_pp.tattoo = (fc->face / 10);
                        m_pp.face = (fc->face % 10);
                        break;
                case ERUDITE:
                        m_pp.hair = (fc->face / 10);
                        m_pp.face = (fc->face % 10);
                        break;
                case HIGHELF:
                case DARKELF:
                case HALFELF:
                        m_pp.beard = (fc->face / 10);
                        m_pp.face = (fc->face % 10);
                        break;
                case
        }
}

Code:

// SoF.cpp - ENCODE(OP_ZoneSpawns)
ENCODE(OP_ZoneSpawns)
{
  // [snip] ...
       
        eq->tattoo = emu->tattoo; // Assuming Tattoo field is added to DB for Draks & Barbs
        eq->details = emu->details; // Assuming Details field is added to DB for Draks
        eq->heritage = emu->heritage; // Assuming heritage field is added to DB for Draks
        eq->face = emu->face;

        // Now adjust Face field to account for features encoded into it by client for rendering
        switch (emu->race)
        {
          case BARBARIAN:
                        eq->face += (emu->tattoo * 10); // Tattoo will be ignored by client for Barbs, Tattoo maps to Face field.
                        break;
                case ERUDITE:
                        eq->face += (emu->hair * 10); // Hair will be ignored by client for Eruds, Hair maps to Face field
                        break;
                case HIGHELF:
                case DARKELF:
                case HALFELF:
                        eq->face += (emu->beard * 10); // Beard will be ignored by client for elves, Beard maps to Face field
                        break;
        }

        // ... [snip]
}


trevius 05-13-2009 04:27 AM

Identified the Drakkin portion of the Character Select Struct:

Code:

struct CharacterSelectEntry_Struct {
/*0000*/        uint8 level;                                //
/*0001*/        uint8 haircolor;                        //
/*0002*/        uint8 gender;                                //
/*0003*/        char name[1];                                //variable length, edi+0
/*0000*/        uint8 beard;                                //
/*0000*/        uint8 hair;                                        //
/*0000*/        uint8 face;                                        //
/*0000*/        CharSelectEquip        equip[9];
/*0000*/        uint32 secondary;                        //
/*0000*/        uint32 primary;                                //
/*0000*/        uint8 u15;                                        // 0xff
/*0000*/        uint32 deity;                                //
/*0000*/        uint16 zone;                                //
/*0000*/        uint16 instance;
/*0000*/        uint8 gohome;                                //
/*0000*/        uint8 u19;                                        // 0xff
/*0000*/        uint32 race;                                //
/*0000*/        uint8 tutorial;                                //
/*0000*/        uint8 class_;                                //
/*0000*/        uint8 eyecolor1;                        //
/*0000*/        uint8 beardcolor;                        //
/*0000*/        uint8 eyecolor2;                        //
/*0000*/        uint32 drakkin_heritage;        // Drakkin Heritage
/*0000*/        uint32 drakkin_tattoo;                // Drakkin Tattoo
/*0000*/        uint32 drakkin_details;                // Drakkin Details (Facial Spikes)
};


Shendare 05-13-2009 11:15 PM

Sweeeeeet.

Just as a heads up, client_packet.cpp is still scrambling features that are set to 0. Commenting out these lines fixes the problem and allows all appearance settings set with the Face change button to stick between game sessions!

client_packet.cpp, lines 4684-4691
Code:

        /*
        if (fc->face == 0)      {m_pp.face = 99;}
        if (fc->eyecolor1 == 0)  {m_pp.eyecolor1 = 99;}
        if (fc->eyecolor2 == 0)  {m_pp.eyecolor2 = 99;}
        if (fc->hairstyle == 0)  {m_pp.hairstyle = 99;}
        if (fc->haircolor == 0)  {m_pp.haircolor = 99;}
        if (fc->beard == 0)      {m_pp.beard = 99;}
        if (fc->beardcolor == 0) {m_pp.beardcolor = 99;}
        */

Drakkin features excluded from full functionality, of course, until we can get the fields added to the DB.

trevius 05-14-2009 03:45 AM

Yeah, there are still a few places that do that conversion. I am trying to figure out a couple of things before I change anymore facial feature stuff on the SVN. As it is, the one change to stop them from setting to 0xFF that I put on is probably already causing feature issues again. I am not quite sure what the issue is atm, so holding off on more changes for now so that if a revert is required, it won't be hard to do it.

Between SoF and Titanium, it seems like features act a bit differently. I also noticed that Titanium does an encode on the spawn packet and it does that conversion from 99 back to 0 again.

I may be wrong, but from looking at the code, it seems like the whole reason to do the 99 and 0xFF conversion stuff was because in the past it may have been required for NPCs to have those fields set to FF in the spawn struct if the race didn't get those features. I don't think that is the case anymore. As far as I can tell, NPC facial features work perfectly no matter if we use the 99 and 0xFF stuff or not.

Anyway, I am still trying to figure out exactly what is happening to cause the weird issues with features. I still think things are being way overcomplicated and if they are simplified, should work just fine. So far in SoF, I think all features work perfectly with the exception of hair. For some reason, Hairstyle doesn't want to play friendly at the character select screen in SoF. Apparently when I create a character, no matter what I set hair to, it either isn't saving the field properly, or isn't pulling it properly. It may be something to do with the field being named "hair" instead of "hairstyle" in the character_select structure. That is the only field that doesn't match up with the names in the player profile structure and coincidentally is the only field that is giving me problems in SoF character select.

If I can't make some good breakthrough on it tonight, I might just have to revert the change I already put into mob.cpp the other night, at least for now.

trevius 05-14-2009 07:40 AM

Ok, I think I got it mostly figured out and working. So far, it seems like titanium character select is 100% accurate, but in game, beards aren't working quite properly though everything else seems good as far as I can tell.

On SoF, it is almost the opposite; character select shows all characters as bald (after they have logged in game at least once). But, in game on SoF, all facial features seem to be 100% and facechange also seems to work 100% as far as I have tested.

I really have no clue yet why hair isn't working properly in SoF character select. It just doesn't make sense that it shows hair properly before logging in for the first time and then not again after that. In game is the most important part, so it is good that it is working properly at least.

I find it interesting that beards are what seem to be having trouble in Titanium now. In the past, it was always hair issues, so for it to switch and now be a beard issue is pretty odd.

My best guess is that some struct stuff must be off, causing the wrong values to get saved to the wrong place. If something is off on SoF, I think it almost has to be the hairstyle field in the character select (and maybe character create) struct. And if it is a struct issue on Titanium causing Titanium issues, it seems like it might be the facechange struct and maybe even the spawn struct.

I have the changes I made so far up on the SVN. I did quite a few changes, but most of it was just renaming "hair" to "hairstyle" for the character select struct and cleaning up code a little here and there. The actual changes that effect how facial features work weren't that big of changes, so it would be easy to revert them if needed.

I think as long as we keep looking into it, we can get this issue resolved the right way once and for all. Once the current features are working properly, we can start trying to get the new Drakkin features added in. I just didn't want to try to add in new stuff into a hack. Would rather have it done the right way :)

***EDIT***

Actually, after thinking about it, I noticed that sometimes hairstyle would show up in SoF on character select, but only if I set the hair color really low (light brown or so). I then noticed that one of them wasn't even using the right hairstyle that I had set. Figured out that the issue was with the character select struct on SoF having haircolor and hairstyle in the reverse positions. I swapped them and now SoF is working 100% for facial features as far as I can tell. I also had to swap them in character creation, but it all seems to work flawless now! Ready for Drakkin features now, I think. But, I am going to see if I can get Titanium fixed fully first if possible.

Shendare 05-14-2009 11:22 AM

LOL, exciting times.

I just wish there were more hours in the day. Work's been keeping me too busy to be able to mess with things for more than an hour or so a day.

trevius 05-14-2009 09:31 PM

After what I have seen in my testing so far, and after thinking about it more last night before bed, I am suspecting that the issue with Titanium is that one of the facial feature fields are off in one or more than one of the Titanium structures. My biggest suspects are the Player Profile and the Spawn structure. I am 99% positive that SoF now has the proper field identification for all facial feature related structures. So, I should be able to use SoF as a reference for what NPCs should look like when the fields are correct. Then, if needed, I can use Shendare's test code for the Titanium spawn struct and see if we might have some fields backwards or in the wrong place. My guess is that beard and beard color might be reversed in the spawn struct or player profile or both.


All times are GMT -4. The time now is 06:28 PM.

Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.