Log in

View Full Version : Armor Tint Deluxe


Shendare
06-21-2009, 06:54 PM
Okay, I wasn't satisfied with a single armor_tint value for an NPC's entire outfit. I wanted the ability to specify colors for individual armor pieces, so I could create colored armor uniforms for guards and such, potentially replacing old low-poly guards with Luclin models.

It wasn't too horribly difficult. :D

http://www.jondjackson.net/eq/emu/images/armortint2.png http://www.jondjackson.net/eq/emu/images/armortint3.png

SQL change:

ALTER TABLE `npc_types` ADD COLUMN `armortint_id` INTEGER UNSIGNED NOT NULL DEFAULT 0 AFTER `drakkin_details`;


SQL table addition:

CREATE TABLE `npc_types_tint` (
`id` int unsigned NOT NULL DEFAULT '0',
`red1h` tinyint unsigned NOT NULL DEFAULT '0',
`grn1h` tinyint unsigned NOT NULL DEFAULT '0',
`blu1h` tinyint unsigned NOT NULL DEFAULT '0',
`red2c` tinyint unsigned NOT NULL DEFAULT '0',
`grn2c` tinyint unsigned NOT NULL DEFAULT '0',
`blu2c` tinyint unsigned NOT NULL DEFAULT '0',
`red3a` tinyint unsigned NOT NULL DEFAULT '0',
`grn3a` tinyint unsigned NOT NULL DEFAULT '0',
`blu3a` tinyint unsigned NOT NULL DEFAULT '0',
`red4b` tinyint unsigned NOT NULL DEFAULT '0',
`grn4b` tinyint unsigned NOT NULL DEFAULT '0',
`blu4b` tinyint unsigned NOT NULL DEFAULT '0',
`red5g` tinyint unsigned NOT NULL DEFAULT '0',
`grn5g` tinyint unsigned NOT NULL DEFAULT '0',
`blu5g` tinyint unsigned NOT NULL DEFAULT '0',
`red6l` tinyint unsigned NOT NULL DEFAULT '0',
`grn6l` tinyint unsigned NOT NULL DEFAULT '0',
`blu6l` tinyint unsigned NOT NULL DEFAULT '0',
`red7f` tinyint unsigned NOT NULL DEFAULT '0',
`grn7f` tinyint unsigned NOT NULL DEFAULT '0',
`blu7f` tinyint unsigned NOT NULL DEFAULT '0',
`red8x` tinyint unsigned NOT NULL DEFAULT '0',
`grn8x` tinyint unsigned NOT NULL DEFAULT '0',
`blu8x` tinyint unsigned NOT NULL DEFAULT '0',
`red9x` tinyint unsigned NOT NULL DEFAULT '0',
`grn9x` tinyint unsigned NOT NULL DEFAULT '0',
`blu9x` tinyint unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;


Notes:

- I don't know if the table specs (engine, row_format, etc.) are the best ones to use. They were just the defaults used with my MySQL installation.

- Field numberings are Helm, Chest, Arms, Bracers, Gloves, Legs, then Face. The letters after the numbers can help if you're editing the table data directly. 8 and 9 are for Primary and Secondary weapon slots, and as far as I can tell are unused by the client at this time. They can be left at 0.

Server Code changes:

eq_packet_structs.h - Line 1868

...
/*085*/ uint8 haircolor; // Verified
- /*085*/ uint8 beard; // Verified
+ /*086*/ uint8 beard; // Verified
/*087*/ uint8 beardcolor; // Verified
/*088*/ uint32 drakkin_heritage; // Temp Placeholder until field is identified in SoF
/*092*/ uint32 drakkin_tattoo; // Temp Placeholder until field is identified in SoF
/*096*/ uint32 drakkin_details; // Temp Placeholder until field is identified in SoF
- /*100*/ uint32 armor_tint; // Temp Placeholder until field is identified in SoF
- /*104*/ uint8 eyecolor1; // Temp Placeholder until field is identified in SoF
- /*105*/ uint8 eyecolor2; // Temp Placeholder until field is identified in SoF
- /*106*/ uint8 unknown106[150]; //was uint8 unknown021[168];
+ /*100*/ uint32 armor_tint[MAX_MATERIALS]; // Temp Placeholder until field is identified in SoF
+ /*136*/ uint8 eyecolor1; // Temp Placeholder until field is identified in SoF
+ /*137*/ uint8 eyecolor2; // Temp Placeholder until field is identified in SoF
+ /*138*/ uint8 unknown138[118]; //was uint8 unknown106[150]; //was uint8 unknown021[168];
/*256*/
};
...


mob.h - Line 374

...
int32 in_drakkin_details,
- int32 in_armor_tint,
+ int32 in_armor_tint[MAX_MATERIALS],
int8 in_aa_title,
...


mob.h - Line 535

...
inline int8 GetDrakkinDetails() const { return drakkin_details; }
- inline int32 GetArmorTint() const { return armor_tint; }
+ inline int32 GetArmorTint(int8 i) const { return armor_tint[(i < MAX_MATERIALS) ? i : 0]; }
inline int8 GetClass() const { return class_; }
...


mob.h - Line 726

...
void TryDotCritical(int16 spell_id, Mob *caster, int &damage);

- void SendIllusionPacket(int16 in_race, int8 in_gender = 0xFF, int16 in_texture = 0xFFFF, int16 in_helmtexture = 0xFFFF, int8 in_haircolor = 0xFF, int8 in_beardcolor = 0xFF, int8 in_eyecolor1 = 0xFF, int8 in_eyecolor2 = 0xFF, int8 in_hairstyle = 0xFF, int8 in_luclinface = 0xFF, int8 in_beard = 0xFF, int8 in_aa_title = 0xFF, int32 in_drakkin_heritage = 0xFFFFFFFF, int32 in_drakkin_tattoo = 0xFFFFFFFF, int32 in_drakkin_details = 0xFFFFFFFF, int32 in_armor_tint = 0xFFFFFFFF);
+ void SendIllusionPacket(int16 in_race, int8 in_gender = 0xFF, int16 in_texture = 0xFFFF, int16 in_helmtexture = 0xFFFF, int8 in_haircolor = 0xFF, int8 in_beardcolor = 0xFF, int8 in_eyecolor1 = 0xFF, int8 in_eyecolor2 = 0xFF, int8 in_hairstyle = 0xFF, int8 in_luclinface = 0xFF, int8 in_beard = 0xFF, int8 in_aa_title = 0xFF, int32 in_drakkin_heritage = 0xFFFFFFFF, int32 in_drakkin_tattoo = 0xFFFFFFFF, int32 in_drakkin_details = 0xFFFFFFFF, int32* in_armor_tint = 0);

static int32 GetAppearanceValue(EmuAppearance iAppearance);
...


mob.h - Line 1162

...
int32 drakkin_details;
- int32 armor_tint;
+ int32 armor_tint[MAX_MATERIALS];

int8 aa_title;
...


mob.cpp - Line 89

...
int32 in_drakkin_details,
- int32 in_armor_tint,
+ int32 in_armor_tint[MAX_MATERIALS],

int8 in_aa_title,
...


mob.cpp - Line 187

...
drakkin_details = in_drakkin_details;
- armor_tint = in_armor_tint;
attack_speed = 0;
...


mob.cpp - Line 249

...
}

+ for (i = 0; i < MAX_MATERIALS; i++)
+ {
+ if (in_armor_tint)
+ {
+ armor_tint[i] = in_armor_tint[i];
+ }
+ else
+ {
+ armor_tint[i] = 0;
+ }
+ }

delta_heading = 0;
...


mob.cpp - Line 792

...
ns->spawn.equipment[i] = GetEquipmentMaterial(i);
- if (armor_tint)
- {
- ns->spawn.colors[i].color = armor_tint;
- }
+ if (armor_tint[i])
+ {
+ ns->spawn.colors[i].color = armor_tint[i];
+ }
else
{
ns->spawn.colors[i].color = GetEquipmentColor(i);
}
}
...


mob.cpp - Line 1164

...
- void Mob::SendIllusionPacket(int16 in_race, int8 in_gender, int16 in_texture, int16 in_helmtexture, int8 in_haircolor, int8 in_beardcolor, int8 in_eyecolor1, int8 in_eyecolor2, int8 in_hairstyle, int8 in_luclinface, int8 in_beard, int8 in_aa_title, int32 in_drakkin_heritage, int32 in_drakkin_tattoo, int32 in_drakkin_details, int32 in_armor_tint) {
+ void Mob::SendIllusionPacket(int16 in_race, int8 in_gender, int16 in_texture, int16 in_helmtexture, int8 in_haircolor, int8 in_beardcolor, int8 in_eyecolor1, int8 in_eyecolor2, int8 in_hairstyle, int8 in_luclinface, int8 in_beard, int8 in_aa_title, int32 in_drakkin_heritage, int32 in_drakkin_tattoo, int32 in_drakkin_details, int32* in_armor_tint) {
...


mob.cpp - Line 1209

...
this->helmtexture = in_helmtexture;

+ int i;

if (in_race > 12 && in_race != 128 && in_race != 130 && in_race != 330 && in_race != 522) {
...


mob.cpp - Line 1223

...
this->drakkin_details = 0xFFFFFFFF;
- this->armor_tint = 0xFFFFFFFF;
+ for (i = 0; i < MAX_MATERIALS; i++)
+ {
+ this->armor_tint[i] = 0xFFFFFFFF;
+ }

}
...


mob.cpp - Line 1282

...
this->drakkin_details = in_drakkin_details;

- if (in_armor_tint == 0xFFFFFFFF)
- this->armor_tint = GetArmorTint();
- else
- this->armor_tint = in_armor_tint;

+ for (i = 0; i < MAX_MATERIALS; i++)
+ {
+ if ((in_armor_tint) && (in_armor_tint[i]))
+ this->armor_tint[i] = in_armor_tint[i];
+ else
+ this->armor_tint[i] = GetArmorTint(i);
+ }

}
...


mob.cpp - Line 1308

...
this->drakkin_details = CastToClient()->GetBaseDetails();
- this->armor_tint = 0xFFFFFFFF;
+ for (i = 0; i < MAX_MATERIALS; i++)
+ {
+ this->armor_tint[i] = 0xFFFFFFFF;
+ }
}
...


mob.cpp - Line 1334

...
is->drakkin_details = this->drakkin_details;
- is->armor_tint = this->armor_tint;
+ for (i = 0; i < MAX_MATERIALS; i++)
+ {
+ is->armor_tint[i] = this->armor_tint[i];
+ }

DumpPacket(outapp);
...


npc.cpp - Line 97

...
d->drakkin_details,
- d->armor_tint,
+ (int32*)d->armor_tint,
0,
...


zonedump.h - Line 89

...
int32 drakkin_details;
- int32 armor_tint;
+ int32 armor_tint[MAX_MATERIALS];
// int8 aa_title; ////not loaded from DB
...


zonedb.cpp - Line 1053

...
"npc_types.drakkin_details,"
+ "npc_types.armortint_id,"
"npc_types.armortint_red,"
...


zonedb.cpp - Line 1144

...
tmpNPCType->drakkin_details = atoi(row[r++]);

- tmpNPCType->armor_tint = (atoi(row[r++]) & 0xFF) << 16;
- tmpNPCType->armor_tint |= (atoi(row[r++]) & 0xFF) << 8;
- tmpNPCType->armor_tint |= (atoi(row[r++]) & 0xFF);
- tmpNPCType->armor_tint |= (tmpNPCType->armor_tint) ? (0xFF << 24) : 0;

+ uint32 armor_tint_id = atoi(row[r++]);
+ tmpNPCType->armor_tint[0] = (atoi(row[r++]) & 0xFF) << 16;
+ tmpNPCType->armor_tint[0] |= (atoi(row[r++]) & 0xFF) << 8;
+ tmpNPCType->armor_tint[0] |= (atoi(row[r++]) & 0xFF);
+ tmpNPCType->armor_tint[0] |= (tmpNPCType->armor_tint[0]) ? (0xFF << 24) : 0;
+
+ int i;
+ if (armor_tint_id > 0)
+ {
+ if (tmpNPCType->armor_tint[0] == 0)
+ {
+ char at_errbuf[MYSQL_ERRMSG_SIZE];
+ char *at_query = NULL;
+ MYSQL_RES *at_result = NULL;
+ MYSQL_ROW at_row;
+
+ MakeAnyLenString(&at_query,
+ "SELECT "
+ "red1h,grn1h,blu1h,"
+ "red2c,grn2c,blu2c,"
+ "red3a,grn3a,blu3a,"
+ "red4b,grn4b,blu4b,"
+ "red5g,grn5g,blu5g,"
+ "red6l,grn6l,blu6l,"
+ "red7f,grn7f,blu7f,"
+ "red8x,grn8x,blu8x,"
+ "red9x,grn9x,blu9x "
+ "FROM npc_types_tint WHERE id=%d", armor_tint_id);
+
+ if (RunQuery(at_query, strlen(at_query), at_errbuf, &at_result))
+ {
+ if ((at_row = mysql_fetch_row(at_result)))
+ {
+ for (i = 0; i < MAX_MATERIALS; i++)
+ {
+ tmpNPCType->armor_tint[i] = atoi(at_row[i * 3]) << 16;
+ tmpNPCType->armor_tint[i] |= atoi(at_row[i * 3 + 1]) << 8;
+ tmpNPCType->armor_tint[i] |= atoi(at_row[i * 3 + 2]);
+ tmpNPCType->armor_tint[i] |= (tmpNPCType->armor_tint[i]) ? (0xFF << 24) : 0;
+ }
+ }
+ else
+ {
+ armor_tint_id = 0;
+ }
+ }
+ else
+ {
+ armor_tint_id = 0;
+ }
+
+ if (at_result)
+ {
+ mysql_free_result(at_result);
+ }
+
+ safe_delete_array(at_query);
+ }
+ else
+ {
+ armor_tint_id = 0;
+ }
+ }
+
+ if (armor_tint_id == 0)
+ {
+ for (i = 1; i < MAX_MATERIALS; i++)
+ {
+ tmpNPCType->armor_tint[i] = tmpNPCType->armor_tint[0];
+ }
+ }

tmpNPCType->see_invis = atoi(row[r++])==0?false:true; // Set see_invis flag
...


I think that got everything.

Shendare
06-21-2009, 07:30 PM
Forgot to clarify. This is the order of priority for armor tints:

1. armortint_red, _green, and _blue in npc_types. If any of these values is non-zero, the tint is applied to all armor pieces, ignoring armortint_id and individual armor piece coloring.

2. armortint_id. If the RGB values in #1 are all 0 but armortint_id is specified, the values from the npc_types_tint table with the corresponding id are used.

3. armor piece tint. If the RGB values in 1 are zero and armortint_id is 0 (or a corresponding record in npc_types_tint cannot be found), or if the red/grn/blu values for a particular armor piece in npc_types_tint are all 0, the tint on the actual armor piece item is displayed.

Hope that clears up any potential confusion.

ChaosSlayerZ
06-21-2009, 08:16 PM
AWSOME...

stupid question but to say to truly replicate the old felguard - he actual needs to wear CHAIN arms - how would you achieve that?

ALSO:

"Helm, Chest, Arms, Bracers, Gloves, Legs, then Face."

Face? but no Boots?

Shendare
06-21-2009, 09:09 PM
Err... I actually typed that? LOL.

Feet. The 'F' is for feet.

And really, mixing armor types doesn't work out well. The armor meshes were designed to match up together, and they look out of place when mixed and matched. Better to stick with all chain, all plate, all leather, or all cloth.

trevius
06-21-2009, 09:32 PM
That is really awesome, Shendare! ChaosSlayer, I think Shendare meant to say "foot", not "face", since helm was already listed and that would be the same as face.

I will try to get this added ASAP. One thing I wanted to mention is that I don't think armor tint can be sent with an illusion packet, but it should be able to be sent with a wearchange packet. So, I need to go through and write a command or 2 that will use the wearchange packets for changing armor tints or weapons without having to repop the zone/NPC. I don't think it will be too hard to get that coded and I will probably start soon after I get AAs for SoF all figured out and finalized. I will probably try to add in a quest command or 2 for handling armor changes Then we would be able to change out armor on playable races, or we could even swap out weapons on other races mid-fight. Might be cool to have guards that normally have no weapons showing and then they whip them out when they aggro on something, or anything along those lines.

I really like how you made a separate table for handling the new armor settings. I am surprised Live doesn't have anything like this, but maybe they will steal the idea when they see it :P

I do think we could also include a material field for each slot in that table. Then, we could have it send the material for each slot so you could have NPCs with mismatched set pieces like ChaosSlayer suggested.

This is some nice stuff, Shendare :) I don't know why, but I have been really happy with the illusion and spawn struct stuff we have figure out so far. I guess they are just things that have always bothered me just enough to stay on my radar. It is really nice to finally be working all of this stuff out so it never has to be messed with again other than maybe adding in more commands to use them.

ChaosSlayerZ
06-21-2009, 09:42 PM
yeah I kind of figured it was a mistype, since helm was allreday listed, and for a moment I was wondering - "what? he managed to tint the FACE too?" :D

As far as diffirent materials go - didn't just recently someone wrote a perl script which can ultra randomize npc when it spawns? Like face, hair, and specificly armor by slot? Or it was just something someone wanted to do?


I am surprised Live doesn't have anything like this, but maybe they will steal the idea when they see it :P


LIVE sucks =) Honestly when I look back at early EQ days it kills me how UNCREATIVE the original team was. Some basic natural things which are directly obvious took them years to put in, and not untill SOE took over..

Shendare
06-21-2009, 09:45 PM
Yeah, it's cool, Trev!

I'm not sure exactly what you're unsure of regarding the illusion packets, but the code there in mob.cpp from lines 1164-1350 or so is in SendIllusionPacket().

I can change an NPC's race and gender in-game and the armor tints carry over to the new model, and it was my understanding that you use SendIllusionPacket when changing these in-game.

trevius
06-21-2009, 10:40 PM
Shendare, I added the armor tint stuff to that command because there is a large part of the illusion packet that we don't have identified yet and I assume that part of it may for armor and tint. I tried to identify those parts and was able to find some helms (some of which showed up as sideway), but I could never exactly figure out what was going on. It is probably because the test I was using is your test code, but it is in int8 and I assume armor would need int32 instead. I just never tried it to see if that was the case.

On a side note, I also noticed that most races cannot change their hair style when using illusion packets. I have no idea why this is, but I can only get Drakkin, Humans, and Female Ogres to change their hairstyle using Illusion packets. The other races all just set their hair to 0 or something. This one has stumped me and may just be an issue with the client (I really only use SoF), so I just gave up on the hair thing.

I will just have to mess with the wearchange packets and get something similar setup for them. I was able to get it to keep the current texture they had on by just not sending anything for the texture/helm fields of the illusion struct. To be able to change armor without repops, it would probably take wearchanges to do it.

ChaosSlayer, it was me that recently added the #randomfeatures command. But, that command only randomizes facial features like hair, beard, haircolor, tattoo and so on. It doesn't randomize armor, but that is a command I would like to add at some point. I wouldn't want it to make completely random armor for entire sets, but maybe using some sort of logic that has chances to make certain random changes that might look good together. The basic idea would be for it to be able to make armor sets similar to the guard one that Shendare posted in the first post. It would definitely take some playing around to make it output halfway decent sets most of the time.

I also really need to create a way to update an NPCs features and armor set/tint. Maybe the #npcspawn command can be expanded with a new option that would let you save the current race, gender, texture, features, armor and armor tint and so on of the current NPC you have targeted. So, you could use the randomizing commands until you found one that looked right and then just save it as it is.

I really have to at least knock out the SoF AA stuff first and maybe the newly added SoF item stats as well. So, I am not sure when I will be able to get to that. I will definitely try to get this tested and added to the code soon if someone doesn't beat me to it.

ChaosSlayerZ
06-21-2009, 11:42 PM
ChaosSlayer, it was me that recently added the #randomfeatures command. But, that command only randomizes facial features like hair, beard, haircolor, tattoo and so on. It doesn't randomize armor, but that is a command I would like to add at some point. I wouldn't want it to make completely random armor for entire sets, but maybe using some sort of logic that has chances to make certain random changes that might look good together. The basic idea would be for it to be able to make armor sets similar to the guard one that Shendare posted in the first post. It would definitely take some playing around to make it output halfway decent sets most of the time.



Trev if this case, what we realy need is something like:

quest::npcarmor(slot, material);

so its not random, but rather specific by user wish.
If you do want it random you just do RND on material for specific/all slots

then we don't need to have to put it into DB

trevius
06-22-2009, 05:16 AM
ChaosSlayer, before I add another random command, I would definitely add the direct command for changing stuff like that. The random command would just be another bonus command to go along with it. First would be one for changing tint, then maybe one for changing the material of each slot, and yet another for changing weapons out. Last and most importantly is to create a command that let's a dev save the current looks from in game. Actual # commands would be added first and then those could be copied to create quest:: commands as well whenever.

trevius
06-22-2009, 05:58 AM
This has now been added to Revision 702 on the SVN.

This feature is awesome BTW! Playing with it shows that it has a ton of potential. The only thing I might add in soon is a simple notes field for the table so you can name each armor set. This way, you can easily have different colored sets and just name them to use them anywhere. It would make managing a large amount of sets quite a bit easier.

Shendare
06-22-2009, 10:39 AM
Agreed, a name field is a very good idea! Don't know how I missed it. :)

So_1337
06-22-2009, 10:52 AM
I just wanted to take a minute and chime in to say how amazing your work has been. Thanks for this splendid ability to add some real customization =)

Shendare
06-22-2009, 11:32 AM
Thanks, it's good to feel appreciated. :D

steve
06-22-2009, 01:43 PM
Thanks, it's good to feel appreciated. :D

I have to second his remarks. Your additions to the project have been invaluable. If anyone's earned the rank of Developer, it's you!

Shendare
06-22-2009, 08:26 PM
Nah, they start giving me a title and they'll expect me to figure out how to work the SVN for pushing my own code, and that'll get scary. :P

trevius
06-22-2009, 08:44 PM
The SVN makes things so easy :) It's only scary the first time. After that, it's just awesome. As long as you learn a couple of suggestions to follow before doing commits, it is pretty straight forward.

gaeorn
06-22-2009, 08:47 PM
You can also set up your own subversion repository to give you time to get used to how to use it.

Shendare
06-22-2009, 09:27 PM
Believe me, I am more than happy posting my diffs for someone else to test and commit, for as long as someone is willing to do it.

It's always good to have someone else to beta test one's code anyway.

trevius
06-23-2009, 08:29 AM
Well, I finally started messing with the wearchange packets and it was a big success. The only thing I added to the SVN so far is that #texture will now let NPCs and PCs retain their armor tint, but it proves that using wearchange will work perfectly for handling tint from in-game without requiring repops.

The only thing not working perfectly with the adjustments to #texture is that "#texture 255" is supposed to reset the target back to their original texture and it doesn't yet. I need to figure out the best way to pull that info from the database. That shouldn't be too hard, but I am not going to worry about it for a while. I may get back to it eventually, but it isn't a big deal IMO.

The only issue left with illusion packets is that they don't send size yet. I am pretty sure I know where size is in the structures, and I think it is a float. I might be able to get that figured out. If so, I will probably try to change the last argument on SendIllusionPacket from armor_tint to size. I don't think there is any reason to have armor tint in there after all, so I might as well swap it out for size while it is being removed. This is another issue that isn't very high priority, but I might get to it eventually.

Next, I will probably change the #wc command so it will update all clients instead of just the person using the command. Currently, only the one using the command actually sees the change happen and everyone else just sees the target as the same it was before.

Since that will let people change individual armor pieces on NPCs, the next thing would be to maybe add extra arguments for color for the slot being set. Then, you would use something like "#wc 1 3 200 0 100 and it would change slot 1 to plate and set the color to 200 0 100 in RGB values. It would probably also be good to have a command that will let you change an entire set tint at once, so that will probably get added soon too. I might even try to add one that lets you set tint values similar to how #fixmob works. So, it might increase or decrease part of the tint by a certain amount. Most likely, I would set it in increments of 10. Then, you could have hotkeys that do "#settint red next" to have it increase the red tint by 10. Then, just have hotkeys for each color for prev and next and you could come up with custom colors quickly.