Browse Source

Procyon Update

- Fix #360 exposed cheek_type, chin_type, ear_type, eye_brow_type, eye_type, lip_type, nose_type, body_age, body_size to spawn set

all but body_age and body_size have three indexes (0-2):

/spawn set command_name [value] [index]

value is a signed integer (can be negative/positive per the requirements)

index is between 0-2, sets R/G/B fields respectively in the database (thats how it loads up)

- Fix #359 better handling of MMovementLocations

- Fix #353 we now properly update the windows against your inventory

- "obtained" lua function now called when item is looted (previously only done when item is 'added' to the player)

- Fix #342 - lock down fuel components (the resulting messaging may need to be corrected, but the concept is in)

- Fix #361 - expansion/holiday flag triggering on a spawn no longer throws 'error adding spawn'.  Now has a warning message instead.  Also did some other messages to try to reduce 'error' based logging as to not confuse real errors with just server settings/unrelated noise in the database.
Image 2 years ago
parent
commit
ecdcdfd123

+ 8 - 0
EQ2/source/WorldServer/Bots/BotDB.cpp

@@ -379,6 +379,14 @@ void WorldDatabase::LoadBotAppearance(Bot* bot) {
 			bot->features.soga_model_color = color;
 			break;
 		}
+		case APPEARANCE_SBS: {
+			bot->features.soga_body_size = color.red;
+			break;
+		}
+		case APPEARANCE_SBA: {
+			bot->features.soga_body_age = color.red;
+			break;
+		}
 		}
 	}
 }

+ 394 - 3
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -47,6 +47,7 @@ along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 #include "../classes.h"
 #include "../Transmute.h"
 #include "../Zone/SPGrid.h"
+#include "../Bots/Bot.h"
 
 extern WorldDatabase database;
 extern MasterSpellList master_spell_list;
@@ -177,6 +178,25 @@ Commands::Commands(){
 	spawn_set_values["soga_hair_highlight"] = SPAWN_SET_SOGA_HAIR_HIGHLIGHT;
 	spawn_set_values["soga_model_color"] = SPAWN_SET_SOGA_MODEL_COLOR;
 	spawn_set_values["soga_eye_color"] = SPAWN_SET_SOGA_EYE_COLOR;
+	
+	spawn_set_values["cheek_type"] = SPAWN_SET_CHEEK_TYPE;
+	spawn_set_values["chin_type"] = SPAWN_SET_CHIN_TYPE;
+	spawn_set_values["ear_type"] = SPAWN_SET_EAR_TYPE;
+	spawn_set_values["eye_brow_type"] = SPAWN_SET_EYE_BROW_TYPE;
+	spawn_set_values["eye_type"] = SPAWN_SET_EYE_TYPE;
+	spawn_set_values["lip_type"] = SPAWN_SET_LIP_TYPE;
+	spawn_set_values["nose_type"] = SPAWN_SET_NOSE_TYPE;
+	spawn_set_values["body_size"] = SPAWN_SET_BODY_SIZE;
+	spawn_set_values["body_age"] = SPAWN_SET_BODY_AGE;
+	spawn_set_values["soga_cheek_type"] = SPAWN_SET_SOGA_CHEEK_TYPE;
+	spawn_set_values["soga_chin_type"] = SPAWN_SET_SOGA_CHIN_TYPE;
+	spawn_set_values["soga_ear_type"] = SPAWN_SET_SOGA_EAR_TYPE;
+	spawn_set_values["soga_eye_brow_type"] = SPAWN_SET_SOGA_EYE_BROW_TYPE;
+	spawn_set_values["soga_eye_type"] = SPAWN_SET_SOGA_EYE_TYPE;
+	spawn_set_values["soga_lip_type"] = SPAWN_SET_SOGA_LIP_TYPE;
+	spawn_set_values["soga_nose_type"] = SPAWN_SET_SOGA_NOSE_TYPE;
+	spawn_set_values["soga_body_size"] = SPAWN_SET_SOGA_BODY_SIZE;
+	spawn_set_values["soga_body_age"] = SPAWN_SET_SOGA_BODY_AGE;
 
 	zone_set_values["expansion_id"] = ZONE_SET_VALUE_EXPANSION_ID;
 	zone_set_values["name"] = ZONE_SET_VALUE_NAME;
@@ -215,7 +235,7 @@ int32 Commands::GetSpawnSetType(string val){
 	return 0xFFFFFFFF;
 }
 
-bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update, bool temporary, string* temp_value){
+bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update, bool temporary, string* temp_value, int8 index){
 	if(!target)
 		return false;
 	int32 val = 0;
@@ -673,12 +693,168 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 				target->SetAAXPRewards(atoul(value));
 				break;
 			}
+			case SPAWN_SET_CHEEK_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.cheek_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.cheek_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_CHIN_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.chin_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.chin_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_EAR_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.ear_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.ear_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_EYE_BROW_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.eye_brow_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.eye_brow_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_EYE_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.eye_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.eye_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_LIP_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.lip_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.lip_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_NOSE_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.nose_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.nose_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_BODY_SIZE:{
+				if(target->IsEntity()){
+					sprintf(tmp, "%i", ((Entity*)target)->features.body_size);
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.body_size = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_BODY_AGE:{
+				if(target->IsEntity()){
+					sprintf(tmp, "%i", ((Entity*)target)->features.body_age);
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.body_age = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_CHEEK_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_cheek_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_cheek_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_CHIN_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_chin_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_chin_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_EAR_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_ear_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_ear_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_EYE_BROW_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_eye_brow_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_eye_brow_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_EYE_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_eye_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_eye_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_LIP_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_lip_type[index]);
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_lip_type[index] = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_BODY_SIZE:{
+				if(target->IsEntity()){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_body_size);
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.soga_body_size = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_BODY_AGE:{
+				if(target->IsEntity()){
+					sprintf(tmp, "%i", ((Entity*)target)->features.soga_body_age);
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.soga_body_age = new_value;
+					((Entity*)target)->info_changed = true;
+				}
+				break;
+			}
 
 			if(temp_value)
 				*temp_value = string(tmp);
 		}
 	}
-	else{
+	else{ 
+		/**** NOT TEMPORARY ELSE STATEMENT ****/
+		/**** MUST RE-DEFINE WHAT IS ALREADY IN THE IF TEMPORARY BLOCK HERE ****/
+
 		switch(type){
 		case SPAWN_SET_VALUE_MERCHANT_MIN_LEVEL: {
 			target->SetMerchantLevelRange(atoul(value), target->GetMerchantMaxLevel());
@@ -1160,11 +1336,183 @@ bool Commands::SetSpawnCommand(Client* client, Spawn* target, int8 type, const c
 				}
 				break;
 			}
+			
+			case SPAWN_SET_CHEEK_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.cheek_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "cheek_type", ((Entity*)target)->features.cheek_type[0], ((Entity*)target)->features.cheek_type[1], ((Entity*)target)->features.cheek_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_CHIN_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.chin_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "chin_type", ((Entity*)target)->features.chin_type[0], ((Entity*)target)->features.chin_type[1], ((Entity*)target)->features.chin_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_EAR_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.ear_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "ear_type", ((Entity*)target)->features.ear_type[0], ((Entity*)target)->features.ear_type[1], ((Entity*)target)->features.ear_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_EYE_BROW_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.eye_brow_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "eye_brow_type", ((Entity*)target)->features.eye_brow_type[0], ((Entity*)target)->features.eye_brow_type[1], ((Entity*)target)->features.eye_brow_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_EYE_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.eye_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "eye_type", ((Entity*)target)->features.eye_type[0], ((Entity*)target)->features.eye_type[1], ((Entity*)target)->features.eye_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_LIP_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.lip_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "lip_type", ((Entity*)target)->features.lip_type[0], ((Entity*)target)->features.lip_type[1], ((Entity*)target)->features.lip_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_NOSE_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.nose_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "nose_type", ((Entity*)target)->features.nose_type[0], ((Entity*)target)->features.nose_type[1], ((Entity*)target)->features.nose_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_BODY_SIZE:{
+				if(target->IsEntity()){
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.body_size = new_value;
+					UpdateDatabaseAppearance(client, target, "body_size", ((Entity*)target)->features.body_size, 0, 0);
+				}
+				break;
+			}
+			case SPAWN_SET_BODY_AGE:{
+				if(target->IsEntity()){
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.body_age = new_value;
+					UpdateDatabaseAppearance(client, target, "body_age", ((Entity*)target)->features.body_age, 0, 0);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_CHEEK_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_cheek_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "soga_cheek_type", ((Entity*)target)->features.soga_cheek_type[0], ((Entity*)target)->features.soga_cheek_type[1], ((Entity*)target)->features.soga_cheek_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_CHIN_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_chin_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "soga_chin_type", ((Entity*)target)->features.soga_chin_type[0], ((Entity*)target)->features.soga_chin_type[1], ((Entity*)target)->features.soga_chin_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_EAR_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_ear_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "soga_ear_type", ((Entity*)target)->features.soga_ear_type[0], ((Entity*)target)->features.soga_ear_type[1], ((Entity*)target)->features.soga_ear_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_EYE_BROW_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_eye_brow_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "soga_eye_brow_type", ((Entity*)target)->features.soga_eye_brow_type[0], ((Entity*)target)->features.soga_eye_brow_type[1], ((Entity*)target)->features.soga_eye_brow_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_EYE_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_eye_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "soga_eye_type", ((Entity*)target)->features.soga_eye_type[0], ((Entity*)target)->features.soga_eye_type[1], ((Entity*)target)->features.soga_eye_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_LIP_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_lip_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "soga_lip_type", ((Entity*)target)->features.soga_lip_type[0], ((Entity*)target)->features.soga_lip_type[1], ((Entity*)target)->features.soga_lip_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_NOSE_TYPE:{
+				if(target->IsEntity() && index < 3){
+					sint8 new_value = atoi(value);
+					((Entity*)target)->features.soga_nose_type[index] = new_value;
+					UpdateDatabaseAppearance(client, target, "soga_nose_type", ((Entity*)target)->features.soga_nose_type[0], ((Entity*)target)->features.soga_nose_type[1], ((Entity*)target)->features.soga_nose_type[2]);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_BODY_SIZE:{
+				if(target->IsEntity()){
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.body_size = new_value;
+					UpdateDatabaseAppearance(client, target, "body_size", ((Entity*)target)->features.body_size, 0, 0);
+				}
+				break;
+			}
+			case SPAWN_SET_SOGA_BODY_AGE:{
+				if(target->IsEntity()){
+					int8 new_value = atoul(value);
+					((Entity*)target)->features.body_age = new_value;
+					UpdateDatabaseAppearance(client, target, "body_age", ((Entity*)target)->features.body_age, 0, 0);
+				}
+				break;
+			}
 		}
 	}
 	return true;
 }
 
+void Commands::UpdateDatabaseAppearance(Client* client, Spawn* target, string fieldName, sint8 r, sint8 g, sint8 b)
+{
+	Query replaceQuery;
+
+	if(target->IsBot())
+	{
+		Bot* bot = (Bot*)target;
+		if(bot->BotID)
+			database.SaveBotFloats(bot->BotID, fieldName.c_str(), r, g, b);
+	}
+	else if(target->IsPlayer())
+	{
+		replaceQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from char_colors where char_id=%u and type='%s'", ((Player*)target)->GetCharacterID(), fieldName.c_str());
+		replaceQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into char_colors set char_id=%u, type='%s', red=%i, green=%i, blue=%i", ((Player*)target)->GetCharacterID(), fieldName.c_str(), r, g, b);
+	}
+	else
+	{
+		replaceQuery.AddQueryAsync(0, &database, Q_DELETE, "delete from npc_appearance where spawn_id=%u and type='%s'", target->GetDatabaseID(), fieldName.c_str());
+		replaceQuery.AddQueryAsync(0, &database, Q_INSERT, "insert into npc_appearance set spawn_id=%u, type='%s', red=%i, green=%i, blue=%i", target->GetDatabaseID(), fieldName.c_str(), r, g, b);
+	}
+
+	((Entity*)target)->changed = true;
+	((Entity*)target)->info_changed = true;
+	if(target->GetZone())
+		target->GetZone()->AddChangedSpawn(target);
+}
+
 /* The zone object will be NULL if the zone is not currently running.  We pass both of these in so we can update 
    the database fields always and also update the zone in memory if it's running. */
 bool Commands::SetZoneCommand(Client* client, int32 zone_id, ZoneServer* zone, int8 type, const char* value) {
@@ -4258,7 +4606,17 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					else
 					{
 						string name = string(spawn->GetName());
-						if(SetSpawnCommand(client, spawn, set_type, sep->argplus[1]))
+						bool customSetSpawn = false;
+						if(set_type >= SPAWN_SET_CHEEK_TYPE && set_type <= SPAWN_SET_SOGA_NOSE_TYPE)
+						{
+							int8 index = sep->IsNumber(2) ? atoul(sep->arg[2]) : 0;
+
+							// override the standard setspawncommand, we pass arguments different!
+							if(SetSpawnCommand(client, spawn, set_type, sep->arg[1], true, false, nullptr, index))
+								customSetSpawn = true;
+						}
+
+						if(customSetSpawn || SetSpawnCommand(client, spawn, set_type, sep->argplus[1]))
 						{
 							if (set_type == SPAWN_SET_VALUE_EXPANSION_FLAG || set_type == SPAWN_SET_VALUE_HOLIDAY_FLAG)
 							{
@@ -4288,6 +4646,24 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 								case SPAWN_SET_VALUE_HOLIDAY_FLAG:
 								case SPAWN_SET_VALUE_FACTION:
 								case SPAWN_SET_AAXP_REWARDS:
+								case SPAWN_SET_CHEEK_TYPE:
+								case SPAWN_SET_CHIN_TYPE:
+								case SPAWN_SET_EAR_TYPE:
+								case SPAWN_SET_EYE_BROW_TYPE:
+								case SPAWN_SET_EYE_TYPE:
+								case SPAWN_SET_LIP_TYPE:
+								case SPAWN_SET_NOSE_TYPE:
+								case SPAWN_SET_BODY_SIZE:
+								case SPAWN_SET_BODY_AGE:
+								case SPAWN_SET_SOGA_CHEEK_TYPE:
+								case SPAWN_SET_SOGA_CHIN_TYPE:
+								case SPAWN_SET_SOGA_EAR_TYPE:
+								case SPAWN_SET_SOGA_EYE_BROW_TYPE:
+								case SPAWN_SET_SOGA_EYE_TYPE:
+								case SPAWN_SET_SOGA_LIP_TYPE:
+								case SPAWN_SET_SOGA_NOSE_TYPE:
+								case SPAWN_SET_SOGA_BODY_SIZE:
+								case SPAWN_SET_SOGA_BODY_AGE:
 								{
 									// not applicable already ran db command
 									break;
@@ -6026,6 +6402,11 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 
 			if(item)
 			{
+				if(item->details.item_locked)
+				{
+					client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot destroy the item in use.");
+					return;
+				}
 				if(item->GetItemScript() && lua_interface)
 					lua_interface->RunItemScript(item->GetItemScript(), "destroyed", item, client->GetPlayer());
 				int32 bag_id = item->details.inv_slot_id;
@@ -6048,6 +6429,11 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 			int8 charges = atoi(sep->arg[4]);
 			Item* item = client->GetPlayer()->item_list.GetItemFromIndex(from_index);
 
+			if(item->details.item_locked)
+			{
+				client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot move the item in use.");
+				return;
+			}
 			if(bag_id == -4 && !client->GetPlayer()->item_list.SharedBankAddAllowed(item))
 			{
 				client->SimpleMessage(CHANNEL_COLOR_RED, "That item (or an item inside) cannot be shared.");
@@ -6143,6 +6529,11 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 				int16 index = atoi(sep->arg[1]);
 				Item* item = client->GetPlayer()->item_list.GetItemFromIndex(index);
 				if (item) {
+					if(item->details.item_locked)
+					{
+						client->SimpleMessage(CHANNEL_COLOR_RED, "You cannot unpack the item in use.");
+						return;
+					}
 					//	client->GetPlayer()->item_list.DestroyItem(index);
 					if (item->item_sets.size() > 0) {
 						for (int32 i = 0; i < item->item_sets.size(); i++) {

+ 21 - 1
EQ2/source/WorldServer/Commands/Commands.h

@@ -278,7 +278,8 @@ class Commands{
 public:
 	Commands();
 	~Commands();
-	bool SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update = true, bool temporary = false, string* temp_value = 0);
+	bool SetSpawnCommand(Client* client, Spawn* target, int8 type, const char* value, bool send_update = true, bool temporary = false, string* temp_value = 0, int8 index = 0);
+	void UpdateDatabaseAppearance(Client* client, Spawn* target, string fieldName, sint8 r, sint8 g, sint8 b);
 	bool SetZoneCommand(Client* client, int32 zone_id, ZoneServer* zone, int8 type, const char* value);
 	RemoteCommands* GetRemoteCommands() { return remote_commands; }
 	void	Process(int32 index, EQ2_16BitString* command_parms, Client* client, Spawn* targetOverride=NULL);
@@ -541,6 +542,25 @@ private:
 #define SPAWN_SET_SOGA_MODEL_COLOR						82
 #define SPAWN_SET_SOGA_EYE_COLOR						83
 
+#define SPAWN_SET_CHEEK_TYPE							84
+#define SPAWN_SET_CHIN_TYPE								85
+#define SPAWN_SET_EAR_TYPE								86
+#define SPAWN_SET_EYE_BROW_TYPE							87
+#define SPAWN_SET_EYE_TYPE								88
+#define SPAWN_SET_LIP_TYPE								89
+#define SPAWN_SET_NOSE_TYPE								90
+#define SPAWN_SET_BODY_SIZE								91
+#define SPAWN_SET_BODY_AGE								92
+#define SPAWN_SET_SOGA_CHEEK_TYPE						93
+#define SPAWN_SET_SOGA_CHIN_TYPE						94
+#define SPAWN_SET_SOGA_EAR_TYPE							95
+#define SPAWN_SET_SOGA_EYE_BROW_TYPE					96
+#define SPAWN_SET_SOGA_EYE_TYPE							97
+#define SPAWN_SET_SOGA_LIP_TYPE							98
+#define SPAWN_SET_SOGA_NOSE_TYPE						99
+#define SPAWN_SET_SOGA_BODY_SIZE						100
+#define SPAWN_SET_SOGA_BODY_AGE							101
+
 #define ZONE_SET_VALUE_EXPANSION_ID			0
 #define ZONE_SET_VALUE_NAME					1
 #define ZONE_SET_VALUE_FILE					2

+ 2 - 0
EQ2/source/WorldServer/Entity.cpp

@@ -3164,6 +3164,8 @@ void Entity::CustomizeAppearance(PacketStruct* packet) {
 
 	features.body_size = body_size;
 	features.body_age = body_age;
+	features.soga_body_size = body_size;
+	features.soga_body_age = body_age;
 	info_changed = true;
 	changed = true;
 }

+ 1 - 0
EQ2/source/WorldServer/GroundSpawn.h

@@ -49,6 +49,7 @@ public:
 		new_spawn->SetCollectionSkill(collection_skill.c_str());
 		SetQuestsRequired(new_spawn);
 		new_spawn->forceMapCheck = forceMapCheck;
+		new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
 		return new_spawn;
 	}
 	bool IsGroundSpawn(){ return true; }

+ 4 - 0
EQ2/source/WorldServer/Items/Items.cpp

@@ -3476,6 +3476,9 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
 		else
 			menu_data += ITEM_MENU_TYPE_DISPLAY_CHARGES;
 	}
+	if(item->details.item_locked) {
+		menu_data += ITEM_MENU_TYPE_BROKEN; // broken is also used to lock item during crafting
+	}
 	// Added the if (overflow) so mouseover examines work properly
 	if (overflow)
 		packet->setSubstructArrayDataByName("items", "unique_id", item->details.item_id, 0, i);
@@ -3776,6 +3779,7 @@ bool EquipmentItemList::AddItem(int8 slot, Item* item){
 		if (curItem) // existing item in slot
 		{
 			MEquipmentItems.unlock();
+			LogWrite(ITEM__ERROR, 0, "Items", "%s: Error in AddItem, curItem %s in slot %u, cannot put %s in slot.", __FUNCTION__, curItem->name.c_str(), slot, item->name.c_str());
 			return false;
 		}
 		

+ 1 - 0
EQ2/source/WorldServer/Items/Items.h

@@ -675,6 +675,7 @@ struct ItemCore{
 	int32	unique_id;
 	int8	num_free_slots;
 	int16	recommended_level;
+	bool	item_locked;
 };
 #pragma pack()
 struct ItemStat{

+ 2 - 0
EQ2/source/WorldServer/Items/ItemsDB.cpp

@@ -106,6 +106,7 @@ void WorldDatabase::LoadDataFromRow(MYSQL_ROW row, Item* item)
 	item->generic_info.offers_quest_id = atoul(row[27]);
 	item->generic_info.part_of_quest_id = atoul(row[28]);
 	item->details.recommended_level = atoi(row[29]);
+	item->details.item_locked = false;
 	item->generic_info.adventure_default_level = atoi(row[30]);
 	item->generic_info.max_charges = atoi(row[31]);
 	item->generic_info.display_charges = atoi(row[32]);
@@ -267,6 +268,7 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
 	item->generic_info.offers_quest_id			= result->GetInt32Str("offers_quest_id");
 	item->generic_info.part_of_quest_id			= result->GetInt32Str("part_of_quest_id");
 	item->details.recommended_level				= result->GetInt16Str("recommended_level");
+	item->details.item_locked					= false;
 	item->generic_info.adventure_default_level	= result->GetInt16Str("adventure_default_level");
 	item->generic_info.max_charges				= result->GetInt16Str("max_charges");
 	item->generic_info.display_charges			= result->GetInt8Str("display_charges");

+ 1 - 0
EQ2/source/WorldServer/Items/Items_CoE.h

@@ -474,6 +474,7 @@ struct ItemCore{
 	int32	unique_id;
 	int8	num_free_slots;
 	int16	recommended_level;
+	bool	item_locked;
 };
 #pragma pack()
 struct ItemStat{

+ 1 - 0
EQ2/source/WorldServer/Items/Items_DoV.h

@@ -568,6 +568,7 @@ struct ItemCore{
 	int32	unique_id;
 	int8	num_free_slots;
 	int16	recommended_level;
+	bool	item_locked;
 };
 #pragma pack()
 struct ItemStat{

+ 23 - 4
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -416,13 +416,18 @@ int EQ2Emu_lua_SpawnSet(lua_State* state) {
 	bool temporary_flag = true;
 	
 	int8 num_args = (int8)lua_interface->GetNumberOfArgs(state);
-
+	int8 index = 0;
+	
 	if(num_args >= 5)
+	{
 		temporary_flag = lua_interface->GetBooleanValue(state, 5); // this used to be false, but no one bothered to set it temporary, we don't need to update the DB
+		
+		index = lua_interface->GetInt8Value(state, 6);
+	}
 	
 	int32 type = commands.GetSpawnSetType(variable);
 	if (type != 0xFFFFFFFF && value.length() > 0 && spawn)
-		commands.SetSpawnCommand(0, spawn, type, value.c_str(), !no_update, temporary_flag);
+		commands.SetSpawnCommand(0, spawn, type, value.c_str(), !no_update, temporary_flag, nullptr, index);
 	return 0;
 }
 
@@ -5309,6 +5314,14 @@ int EQ2Emu_lua_SpawnByLocationID(lua_State* state) {
 		else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_SIGN)
 			spawn = zone->AddSignSpawn(location, location->entities[0]);
 
+		if(spawn && spawn->IsOmittedByDBFlag())
+		{
+			LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met.", location->entities[0]->spawn_id);
+			safe_delete(spawn);
+			spawn = 0;
+			return 0;
+		}
+
 		if (spawn) {
 			const char* script = 0;
 			for (int x = 0; x < 3; x++) {
@@ -5334,7 +5347,7 @@ int EQ2Emu_lua_SpawnByLocationID(lua_State* state) {
 			return 1;
 		}
 		else {
-			LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn to zone");
+			LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by location id to zone %s with location id %u.", zone->GetZoneName(), location_id);
 			safe_delete(spawn);
 		}
 	}
@@ -9913,6 +9926,12 @@ int EQ2Emu_lua_SpawnGroupByID(lua_State* state) {
 			else if (location->entities[0]->spawn_type == SPAWN_ENTRY_TYPE_SIGN)
 				spawn = zone->AddSignSpawn(location, location->entities[0]);
 
+			if(spawn && spawn->IsOmittedByDBFlag())
+			{
+				LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met (LUA SpawnGroupByID).", location->entities[0]->spawn_id);
+				safe_delete(spawn);
+				continue;
+			}
 			if (spawn) {
 				const char* script = 0;
 				for (int x = 0; x < 3; x++) {
@@ -9938,7 +9957,7 @@ int EQ2Emu_lua_SpawnGroupByID(lua_State* state) {
 				group.push_back(spawn);
 			}
 			else {
-				LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn to zone");
+				LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by group id to zone %s with location id %u.", zone->GetZoneName(), group_id);
 				safe_delete(spawn);
 			}
 		}

+ 1 - 0
EQ2/source/WorldServer/NPC.cpp

@@ -101,6 +101,7 @@ NPC::NPC(NPC* old_npc){
 		SetSoundsDisabled(old_npc->IsSoundsDisabled());
 		SetFlyingCreature();
 		SetWaterCreature();
+		SetOmittedByDBFlag(old_npc->IsOmittedByDBFlag());
 	}
 }
 

+ 1 - 0
EQ2/source/WorldServer/Object.cpp

@@ -91,5 +91,6 @@ Object*	Object::Copy(){
 	new_spawn->SetTransporterID(GetTransporterID());
 	new_spawn->SetDeviceID(GetDeviceID());
 	new_spawn->SetSoundsDisabled(IsSoundsDisabled());
+	new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
 	return new_spawn;
 }

+ 4 - 0
EQ2/source/WorldServer/Player.cpp

@@ -2213,6 +2213,10 @@ bool Player::AddItemToBank(Item* item) {
 	return false;
 }
 EQ2Packet* Player::SendInventoryUpdate(int16 version) {
+	// assure any inventory updates are reflected in sell window
+	if(GetClient() && GetClient()->GetMerchantTransaction())
+		GetClient()->SendSellMerchantList();
+	
 	return item_list.serialize(this, version);
 }
 EQ2Packet* Player::MoveInventoryItem(sint32 to_bag_id, int16 from_index, int8 new_slot, int8 charges, int8 appearance_type, int16 version) {

+ 1 - 1
EQ2/source/WorldServer/Rules/RulesDB.cpp

@@ -96,7 +96,7 @@ void WorldDatabase::LoadRuleSetDetails(RuleSet *rule_set) {
 	if (res) {
 		while ((row = mysql_fetch_row(res))) {
 			if (!(rule = rule_set->GetRule(row[0], row[1]))) {
-				LogWrite(RULESYS__ERROR, 0, "Rules", "Unknown rule with category '%s' and type '%s'", row[0], row[1]);
+				LogWrite(RULESYS__WARNING, 0, "Rules", "Unknown rule with category '%s' and type '%s'", row[0], row[1]);
 				continue;
 			}
 			LogWrite(RULESYS__DEBUG, 5, "Rules", "---Setting rule category '%s', type '%s' to value: %s", row[0], row[1], row[2]);

+ 1 - 0
EQ2/source/WorldServer/Sign.cpp

@@ -146,6 +146,7 @@ Sign* Sign::Copy(){
 	new_spawn->SetIncludeLocation(include_location);
 	new_spawn->SetTransporterID(GetTransporterID());
 	new_spawn->SetSoundsDisabled(IsSoundsDisabled());
+	new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
 	return new_spawn;
 }
 

+ 18 - 6
EQ2/source/WorldServer/Spawn.cpp

@@ -128,6 +128,7 @@ Spawn::Spawn(){
 	appearance_equipment_list.SetAppearanceType(1);
 	is_transport_spawn = false;
 	rail_id = 0;
+	is_omitted_by_db_flag = false;
 }
 
 Spawn::~Spawn(){
@@ -2457,7 +2458,11 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 		packet->setColorByName("soga_hair_face_highlight_color", entity->features.soga_hair_face_highlight_color);
 		packet->setColorByName("soga_hair_highlight", entity->features.soga_hair_highlight_color);
 
+		packet->setDataByName("body_size", entity->features.body_size);
 		packet->setDataByName("body_age", entity->features.body_age);
+
+		packet->setDataByName("soga_body_size", entity->features.soga_body_size);
+		packet->setDataByName("soga_body_age", entity->features.soga_body_age);
 	}
 	else {
 		EQ2_Color empty;
@@ -2822,12 +2827,14 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 			MovementData* data = movement_loop[movement_index];
 			// need to resume our movement
 			if(resume_movement){
-				if (movement_locations){
+				if (movement_locations && MMovementLocations){
+					MMovementLocations->writelock(__FUNCTION__, __LINE__);
 					while (movement_locations->size()){
 						safe_delete(movement_locations->front());
 						movement_locations->pop_front();
 					}
 					movement_locations->clear();
+					MMovementLocations->releasewritelock(__FUNCTION__, __LINE__);
 				}
 
 				data = movement_loop[movement_index];
@@ -2907,12 +2914,17 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 							FaceTarget(data->x, data->z);
 						// If 0 delay at location get and set data for the point after it
 						if(data->delay == 0 && movement_loop.size() > 0){
-							while (movement_locations->size()){
-								safe_delete(movement_locations->front());
-								movement_locations->pop_front();
+							if(movement_locations && MMovementLocations)
+							{
+								MMovementLocations->writelock(__FUNCTION__, __LINE__);
+								while (movement_locations->size()){
+									safe_delete(movement_locations->front());
+									movement_locations->pop_front();
+								}
+								// clear current target locations
+								movement_locations->clear();
+								MMovementLocations->releasewritelock(__FUNCTION__, __LINE__);
 							}
-							// clear current target locations
-							movement_locations->clear();
 							// get the data for the location after out new location
 							int16 tmp_index = movement_index+1;
 							MovementData* data2 = 0;

+ 14 - 1
EQ2/source/WorldServer/Spawn.h

@@ -1121,8 +1121,16 @@ public:
 	void CalculateNewFearpoint();
 
 	void StopMoving() {
-		if (movement_locations)
+		if (movement_locations && MMovementLocations)
+		{
+			MMovementLocations->writelock(__FUNCTION__, __LINE__);
+			while (movement_locations->size()){
+				safe_delete(movement_locations->front());
+				movement_locations->pop_front();
+			}
 			movement_locations->clear();
+			MMovementLocations->releasewritelock(__FUNCTION__, __LINE__);
+		}
 	}
 
 	int16 pos_packet_size;
@@ -1219,6 +1227,10 @@ public:
 	void AddRailPassenger(int32 char_id);
 	void RemoveRailPassenger(int32 char_id);
 	vector<Spawn*> GetPassengersOnRail();
+
+	void SetOmittedByDBFlag(bool val) { is_omitted_by_db_flag = val; }
+	bool IsOmittedByDBFlag() { return is_omitted_by_db_flag; }
+	
 protected:
 
 	bool	has_quests_required;
@@ -1323,6 +1335,7 @@ private:
 	sint64 rail_id;
 	map<int32, bool> rail_passengers;
 	mutex m_RailMutex;
+	bool is_omitted_by_db_flag; // this particular spawn is omitted by an expansion or holiday flag
 };
 
 #endif

+ 47 - 7
EQ2/source/WorldServer/Tradeskills/Tradeskills.cpp

@@ -242,6 +242,36 @@ void TradeskillMgr::BeginCrafting(Client* client, vector<int32> components) {
 		return;
 	}
 
+	// TODO: use the vecotr to lock inventory slots
+	vector<int32>::iterator itr;
+	bool missingItem = false;
+	int32 itemid = 0;
+	vector<Item*> tmpItems;
+	for (itr = components.begin(); itr != components.end(); itr++) {
+		itemid = *itr;
+		Item* item = client->GetPlayer()->item_list.GetItemFromID(itemid);
+
+		if(!item)
+		{
+			missingItem = true;
+			break;
+		}
+		
+		item->details.item_locked = true;
+		tmpItems.push_back(item);
+	}
+
+	if (missingItem) {
+		LogWrite(TRADESKILL__ERROR, 0, "Recipe", "Recipe (%u) player missing item %u",itemid);
+		vector<Item*>::iterator itemitr;
+		for (itemitr = tmpItems.begin(); itemitr != tmpItems.end(); itemitr++) {
+			Item* tmpItem = *itemitr;
+			tmpItem->details.item_locked = false;
+		}
+		ClientPacketFunctions::StopCrafting(client);
+		return;
+	}
+
 	ClientPacketFunctions::SendItemCreationUI(client, recipe);
 	Tradeskill* tradeskill = new Tradeskill;
 	tradeskill->player = client->GetPlayer();
@@ -261,13 +291,10 @@ void TradeskillMgr::BeginCrafting(Client* client, vector<int32> components) {
 	// Unlock TS Spells and lock all others
 	client->GetPlayer()->UnlockTSSpells();
 
-	// TODO: use the vecotr to lock inventory slots
-	/*vector<Item*>::iterator itr;
-	for (itr = components.begin(); itr != components.end(); itr++) {
-		Item* item = *itr;
-		//client->GetPlayer()->SendInventoryUpdate
-		item->details.inv_slot_id;
-	}*/
+	client->ClearSentItemDetails();
+	EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
+	if (outapp)
+		client->QueuePacket(outapp);
 }
 
 void TradeskillMgr::StopCrafting(Client* client, bool lock) {
@@ -306,6 +333,7 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 			m_tradeskills.releasewritelock(__FUNCTION__, __LINE__);
 		return;
 	}
+	bool updateInvReq = false;
 	// cycle through the list of used items and remove them
 	for (itr = tradeskill->usedComponents.begin(); itr != tradeskill->usedComponents.end(); itr++, i++) {
 		// get the quantity to remove, first item in the vectore is always the primary, last is always the fuel
@@ -326,10 +354,15 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 		int32 itmid = *itr;
 		item = client->GetPlayer()->item_list.GetItemFromID(itmid);
 		if (item && item->details.count <= qty)
+		{
+			item->details.item_locked = false;
 			client->GetPlayer()->item_list.RemoveItem(item);
+		}
 		else if(item) {
 			item->details.count -= qty;
+			item->details.item_locked = false;
 			item->save_needed = true;
+			updateInvReq = true;
 		}
 		else
 		{
@@ -338,6 +371,13 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 		}
 	}
 
+	if(updateInvReq)
+	{
+		EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
+		if (outapp)
+			client->QueuePacket(outapp);
+	}
+
 	item = 0;
 	qty = recipe->GetFuelComponentQuantity();	
 	item_id = recipe->components[5][0];

+ 44 - 10
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -646,6 +646,10 @@ int8 WorldDatabase::GetAppearanceType(string type){
 		ret = APPEARANCE_MC;
 	else if (type == "soga_model_color")
 		ret = APPEARANCE_SMC;
+	else if (type == "soga_body_size")
+		ret = APPEARANCE_SBS;
+	else if (type == "soga_body_age")
+		ret = APPEARANCE_SBA;
 	return ret;
 }
 
@@ -902,6 +906,14 @@ int32 WorldDatabase::LoadAppearances(ZoneServer* zone, Client* client){
 				entity->features.soga_model_color = color;
 				break;
 			}
+			case APPEARANCE_SBS: {
+				entity->features.soga_body_size = color.red;
+				break;
+			}
+			case APPEARANCE_SBA: {
+				entity->features.soga_body_age = color.red;
+				break;
+			}
 		}
 		entity->info_changed = true;
 	}
@@ -933,13 +945,15 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 		*/
 		int32 npcXpackFlag = atoul(row[75]);
 		int32 npcHolidayFlag = atoul(row[76]);
-		if (!CheckExpansionFlags(zone, npcXpackFlag) || !CheckHolidayFlags(zone, npcHolidayFlag))
-			continue;
 
 		id = atoul(row[0]);
 		if(zone->GetNPC(id, true))
 			continue;
 		npc = new NPC();
+		
+		if (!CheckExpansionFlags(zone, npcXpackFlag) || !CheckHolidayFlags(zone, npcHolidayFlag))
+			npc->SetOmittedByDBFlag(true);
+
 		npc->SetDatabaseID(id);
 		strcpy(npc->appearance.name, row[1]);
 		vector<EntityCommand*>* primary_command_list = zone->GetEntityCommandList(atoul(row[9]));
@@ -1207,13 +1221,15 @@ void WorldDatabase::LoadSigns(ZoneServer* zone){
 	while(result && (row = mysql_fetch_row(result))){
 		int32 signXpackFlag = atoul(row[28]);
 		int32 signHolidayFlag = atoul(row[29]);
-		if (!CheckExpansionFlags(zone, signXpackFlag) || !CheckHolidayFlags(zone, signHolidayFlag))
-			continue;
 
 		id = atoul(row[0]);
 		if(zone->GetSign(id, true))
 			continue;
 		sign = new Sign();
+		
+		if (!CheckExpansionFlags(zone, signXpackFlag) || !CheckHolidayFlags(zone, signHolidayFlag))
+			sign->SetOmittedByDBFlag(true);
+
 		sign->SetDatabaseID(id);
 		strcpy(sign->appearance.name, row[1]);
 		sign->appearance.model_type = atoi(row[2]);
@@ -1294,13 +1310,15 @@ void WorldDatabase::LoadWidgets(ZoneServer* zone){
 	while(result && (row = mysql_fetch_row(result))){
 		int32 widgetXpackFlag = atoul(row[33]);
 		int32 widgetHolidayFlag = atoul(row[34]);
-		if (!CheckExpansionFlags(zone, widgetXpackFlag) || !CheckHolidayFlags(zone, widgetHolidayFlag))
-			continue;
 
 		id = atoul(row[0]);
 		if(zone->GetWidget(id, true))
 			continue;
 		widget = new Widget();
+
+		if (!CheckExpansionFlags(zone, widgetXpackFlag) || !CheckHolidayFlags(zone, widgetHolidayFlag))
+			widget->SetOmittedByDBFlag(true);
+
 		widget->SetDatabaseID(id);
 		strcpy(widget->appearance.name, row[1]);
 		widget->appearance.model_type = atoi(row[2]);
@@ -1398,13 +1416,15 @@ void WorldDatabase::LoadObjects(ZoneServer* zone){
 
 		int32 objXpackFlag = atoul(row[19]);
 		int32 objHolidayFlag = atoul(row[20]);
-		if (!CheckExpansionFlags(zone, objXpackFlag) || !CheckHolidayFlags(zone, objHolidayFlag))
-			continue;
 
 		id = atoul(row[0]);
 		if(zone->GetObject(id, true))
 			continue;
 		object = new Object();
+		
+		if (!CheckExpansionFlags(zone, objXpackFlag) || !CheckHolidayFlags(zone, objHolidayFlag))
+			object->SetOmittedByDBFlag(true);
+
 		object->SetDatabaseID(id);
 		strcpy(object->appearance.name, row[1]);
 		vector<EntityCommand*>* primary_command_list = zone->GetEntityCommandList(atoul(row[4]));
@@ -1472,13 +1492,15 @@ void WorldDatabase::LoadGroundSpawns(ZoneServer* zone){
 
 		int32 gsXpackFlag = atoul(row[21]);
 		int32 gsHolidayFlag = atoul(row[22]);
-		if (!CheckExpansionFlags(zone, gsXpackFlag) || !CheckHolidayFlags(zone, gsHolidayFlag))
-			continue;
 
 		id = atoul(row[0]);
 		if(zone->GetGroundSpawn(id, true))
 			continue;
 		spawn = new GroundSpawn();
+		
+		if (!CheckExpansionFlags(zone, gsXpackFlag) || !CheckHolidayFlags(zone, gsHolidayFlag))
+			spawn->SetOmittedByDBFlag(true);
+
 		spawn->SetDatabaseID(id);
 		spawn->forceMapCheck = true;
 
@@ -6962,6 +6984,10 @@ void WorldDatabase::LoadAppearance(ZoneServer* zone, int32 spawn_id) {
 			case APPEARANCE_SOGA_U13:{
 				break;
 			}
+			case APPEARANCE_BODY_AGE: {
+				entity->features.body_age = color.red;
+				break;
+			}
 			case APPEARANCE_MC:{
 				entity->features.model_color = color;
 				break;
@@ -6970,6 +6996,14 @@ void WorldDatabase::LoadAppearance(ZoneServer* zone, int32 spawn_id) {
 				entity->features.soga_model_color = color;
 				break;
 			}
+			case APPEARANCE_SBS: {
+				entity->features.soga_body_size = color.red;
+				break;
+			}
+			case APPEARANCE_SBA: {
+				entity->features.soga_body_age = color.red;
+				break;
+			}
 		}
 	}
 

+ 2 - 0
EQ2/source/WorldServer/WorldDatabase.h

@@ -103,6 +103,8 @@ using namespace std;
 #define APPEARANCE_BODY_AGE		47
 #define APPEARANCE_MC			48
 #define APPEARANCE_SMC			49
+#define APPEARANCE_SBS			50
+#define APPEARANCE_SBA			51
 
 #define CHAR_PROPERTY_SPEED			"modify_speed"
 #define CHAR_PROPERTY_FLYMODE		"modify_flymode"

+ 10 - 9
EQ2/source/WorldServer/client.cpp

@@ -2439,6 +2439,10 @@ bool Client::HandleLootItem(Spawn* entity, Item* item) {
 				guild->AddNewGuildEvent(type, "%s has looted the %s %s", Timer::GetUnixTimeStamp(), true, player->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str());
 				guild->SendMessageToGuild(type, "%s has looted the %s %s", player->GetName(), adjective, item->CreateItemLink(GetVersion()).c_str());
 			}
+
+			if (item->GetItemScript() && lua_interface)
+				lua_interface->RunItemScript(item->GetItemScript(), "obtained", item, player);
+			
 			CheckPlayerQuestsItemUpdate(item);
 			return true;
 		}
@@ -6534,6 +6538,11 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) {
 		if (!item)
 			item = player->item_list.GetItemFromID(item_id);
 		if (item && master_item) {
+			if(item->details.item_locked)
+			{
+				SimpleMessage(CHANNEL_COLOR_RED, "You cannot sell the item in use.");
+				return;
+			}
 			int32 sell_price = (int32)(master_item->sell_price * multiplier);
 			if (sell_price > item->sell_price)
 				sell_price = item->sell_price;
@@ -6593,7 +6602,6 @@ void Client::SellItem(int32 item_id, int16 quantity, int32 unique_id) {
 			if (outapp)
 				QueuePacket(outapp);
 			
-			SendSellMerchantList();
 			if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK))
 				SendBuyBackList();
 		}
@@ -6653,15 +6661,12 @@ void Client::BuyBack(int32 item_id, int16 quantity) {
 				}
 				AddItem(item);
 				itemAdded = true;
-				//EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
-				//if(outapp)
-					//QueuePacket(outapp);
+				
 				if (removed) {
 					database.DeleteBuyBack(GetCharacterID(), closest->item_id, closest->quantity, closest->price);
 					safe_delete(closest);
 				}
 				
-				SendSellMerchantList();
 				if (!(spawn->GetMerchantType() & MERCHANT_TYPE_NO_BUY_BACK))
 					SendBuyBackList();
 			}
@@ -6745,11 +6750,7 @@ void Client::BuyItem(int32 item_id, int16 quantity) {
 							world.DecreaseMerchantQuantity(spawn->GetMerchantID(), item_id, quantity);
 							SendBuyMerchantList();
 						}
-						//EQ2Packet* outapp = player->SendInventoryUpdate(GetVersion());
-						//if(outapp)
-						//	QueuePacket(outapp);
 						
-						SendSellMerchantList();
 						if (spawn->GetMerchantType() & MERCHANT_TYPE_LOTTO)
 							PlayLotto(total_buy_price, item->details.item_id);
 					}

+ 29 - 7
EQ2/source/WorldServer/zoneserver.cpp

@@ -2142,9 +2142,16 @@ Spawn* ZoneServer::ProcessSpawnLocation(SpawnLocation* spawnlocation, bool respa
 			if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
 				database.GetHouseSpawnInstanceData(this, spawn);
 
-			if (!spawn)
+			if(spawn && spawn->IsOmittedByDBFlag())
 			{
-				LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn to zone");
+				LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id);
+				safe_delete(spawn);
+				spawn = 0;
+				continue;
+			}
+			else if (!spawn)
+			{
+				LogWrite(ZONE__ERROR, 0, "Zone", "Error adding spawn by spawn location to zone %s with location id %u, spawn id %u, spawn type %u.", GetZoneName(), spawnlocation->entities[i]->spawn_location_id, spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_type);
 				continue;
 			}
 
@@ -2202,6 +2209,14 @@ Spawn* ZoneServer::ProcessInstanceSpawnLocation(SpawnLocation* spawnlocation, ma
 				(spawnTime = database.CheckSpawnRemoveInfo(instSignSpawns,spawnlocation->entities[i]->spawn_location_id)) > 0)
 				spawn = AddSignSpawn(spawnlocation, spawnlocation->entities[i]);
 
+			if(spawn && spawn->IsOmittedByDBFlag())
+			{
+				LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) in spawn location id %u was skipped due to a missing expansion / holiday flag being met (ZoneServer::ProcessInstanceSpawnLocation)", spawnlocation->entities[i]->spawn_id, spawnlocation->entities[i]->spawn_location_id);
+				safe_delete(spawn);
+				spawn = 0;
+				continue;
+			}
+
 			if (GetInstanceType() == PERSONAL_HOUSE_INSTANCE)
 				database.GetHouseSpawnInstanceData(this, spawn);
 
@@ -2494,7 +2509,7 @@ void ZoneServer::DeterminePosition(SpawnLocation* spawnlocation, Spawn* spawn){
 NPC* ZoneServer::AddNPCSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
 	LogWrite(SPAWN__TRACE, 1, "Spawn", "Enter %s", __FUNCTION__);
 	NPC* npc = GetNewNPC(spawnentry->spawn_id);
-	if(npc){
+	if(npc && !npc->IsOmittedByDBFlag()){
 		DeterminePosition(spawnlocation, npc);
 		npc->SetDatabaseID(spawnentry->spawn_id);
 		npc->SetSpawnLocationID(spawnentry->spawn_location_id);
@@ -2852,7 +2867,7 @@ void ZoneServer::AddTransporter(LocationTransportDestination* loc) {
 Sign* ZoneServer::AddSignSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
 	LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
 	Sign* sign = GetNewSign(spawnentry->spawn_id);
-	if(sign){
+	if(sign && !sign->IsOmittedByDBFlag()){
 		DeterminePosition(spawnlocation, sign);
 		sign->SetDatabaseID(spawnentry->spawn_id);
 		sign->SetSpawnLocationID(spawnentry->spawn_location_id);
@@ -2875,7 +2890,7 @@ Sign* ZoneServer::AddSignSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnen
 Widget* ZoneServer::AddWidgetSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
 	LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
 	Widget* widget = GetNewWidget(spawnentry->spawn_id);
-	if(widget){
+	if(widget && !widget->IsOmittedByDBFlag()){
 		DeterminePosition(spawnlocation, widget);
 		widget->SetDatabaseID(spawnentry->spawn_id);
 		widget->SetSpawnLocationID(spawnentry->spawn_location_id);
@@ -2905,7 +2920,7 @@ Widget* ZoneServer::AddWidgetSpawn(SpawnLocation* spawnlocation, SpawnEntry* spa
 Object* ZoneServer::AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
 	LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
 	Object* object = GetNewObject(spawnentry->spawn_id);
-	if(object){
+	if(object && !object->IsOmittedByDBFlag()){
 		DeterminePosition(spawnlocation, object);
 		object->SetDatabaseID(spawnentry->spawn_id);
 		object->SetSpawnLocationID(spawnentry->spawn_location_id);
@@ -2928,7 +2943,7 @@ Object* ZoneServer::AddObjectSpawn(SpawnLocation* spawnlocation, SpawnEntry* spa
 GroundSpawn* ZoneServer::AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry* spawnentry){
 	LogWrite(SPAWN__TRACE, 0, "Spawn", "Enter %s", __FUNCTION__);
 	GroundSpawn* spawn = GetNewGroundSpawn(spawnentry->spawn_id);
-	if(spawn){
+	if(spawn && !spawn->IsOmittedByDBFlag()){
 		DeterminePosition(spawnlocation, spawn);
 		spawn->SetDatabaseID(spawnentry->spawn_id);
 		spawn->SetSpawnLocationID(spawnentry->spawn_location_id);
@@ -6982,6 +6997,13 @@ Spawn* ZoneServer::GetSpawn(int32 id){
 			LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Database inserted ground spawn (%u) but was still unable to retrieve it!", id);
 	}
 
+	if(ret && ret->IsOmittedByDBFlag())
+	{
+		LogWrite(SPAWN__WARNING, 0, "Spawn", "Spawn (%u) was skipped due to a missing expansion / holiday flag being met.", id);
+		safe_delete(ret);
+		ret = 0;
+	}
+
 	if(ret)
 		ret->SetID(Spawn::NextID());
 	return ret;

+ 2 - 0
EQ2/source/common/EQ2_Common_Structs.h

@@ -118,6 +118,8 @@ struct CharFeatures{
 	sint8				soga_lip_type[3];
 	sint8				soga_chin_type[3];
 	sint8				soga_nose_type[3];
+	sint8				soga_body_size;
+	sint8				soga_body_age;
 	int16				soga_hair_type;
 	int16				soga_hair_face_type;
 	int16				combat_voice;