Browse Source

Fix #527, blue and debt xp, Fix #523 db translation of emotes table to action_state, action_state_str added to spawns_npc table

Emagi 1 year ago
parent
commit
b924495ba3

File diff suppressed because it is too large
+ 43 - 0
DB/updates/emotes.sql


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

@@ -342,6 +342,10 @@ void Entity::MapInfoStruct()
 	get_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::get_engaged_encounter, &info_struct);
 	
 	get_int8_funcs["first_world_login"] = l::bind(&InfoStruct::get_first_world_login, &info_struct);
+	
+	get_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::get_reload_player_spells, &info_struct);
+	
+	get_string_funcs["action_state"] = l::bind(&InfoStruct::get_action_state, &info_struct);
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -529,6 +533,10 @@ void Entity::MapInfoStruct()
 	set_int8_funcs["engaged_encounter"] = l::bind(&InfoStruct::set_engaged_encounter, &info_struct, l::_1);
 	
 	set_int8_funcs["first_world_login"] = l::bind(&InfoStruct::set_first_world_login, &info_struct, l::_1);
+	
+	set_int8_funcs["reload_player_spells"] = l::bind(&InfoStruct::set_reload_player_spells, &info_struct, l::_1);
+	
+	set_string_funcs["action_state"] = l::bind(&InfoStruct::set_action_state, &info_struct, l::_1);
 
 }
 

+ 11 - 1
EQ2/source/WorldServer/Entity.h

@@ -278,6 +278,8 @@ struct InfoStruct{
 		
 		first_world_login_ = 0;
 		reload_player_spells_ = 0;
+		
+		action_state_ = std::string("");
 	}
 
 
@@ -467,6 +469,8 @@ struct InfoStruct{
 		
 		first_world_login_ = oldStruct->get_first_world_login();
 		reload_player_spells_ = oldStruct->get_reload_player_spells();
+		
+		action_state_ = oldStruct->get_action_state();
 
 	}
 	//mutable std::shared_mutex mutex_;
@@ -675,6 +679,8 @@ struct InfoStruct{
 	
 	int8	get_reload_player_spells() { std::lock_guard<std::mutex> lk(classMutex); return reload_player_spells_; }
 	
+	std::string get_action_state() { std::lock_guard<std::mutex> lk(classMutex); return action_state_; }
+	
 	void	set_name(std::string value) { std::lock_guard<std::mutex> lk(classMutex); name_ = value; }
 	
 	void	set_deity(std::string value) { std::lock_guard<std::mutex> lk(classMutex); deity_ = value; }
@@ -779,7 +785,7 @@ struct InfoStruct{
 	void	set_xp(int32 value) { std::lock_guard<std::mutex> lk(classMutex); xp_ = value; }
 	void	set_xp_needed(int32 value) { std::lock_guard<std::mutex> lk(classMutex); xp_needed_ = value; }
 
-	void	set_xp_debt(float value) { std::lock_guard<std::mutex> lk(classMutex); xp_debt_ = value; }
+	void	set_xp_debt(float value) { std::lock_guard<std::mutex> lk(classMutex); if(std::isnan(value)) value = 0.0f; xp_debt_ = value; }
 
 	void	set_xp_yellow(int16 value) { std::lock_guard<std::mutex> lk(classMutex); xp_yellow_ = value; }
 	void	set_xp_blue(int16 value) { std::lock_guard<std::mutex> lk(classMutex); xp_blue_ = value; }
@@ -967,6 +973,8 @@ struct InfoStruct{
 	
 	void	set_reload_player_spells(int8 value) { std::lock_guard<std::mutex> lk(classMutex); reload_player_spells_ = value; }
 
+	void	set_action_state(std::string value) { std::lock_guard<std::mutex> lk(classMutex); action_state_ = value; }
+	
 	void	ResetEffects(Spawn* spawn)
 	{
 		for(int i=0;i<45;i++){
@@ -1174,6 +1182,8 @@ private:
 	int8			first_world_login_;
 	int8			reload_player_spells_;
 	
+	std::string		action_state_;
+	
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
 };

+ 12 - 7
EQ2/source/WorldServer/Player.cpp

@@ -259,9 +259,14 @@ PlayerInfo* Player::GetPlayerInfo(){
 void PlayerInfo::CalculateXPPercentages(){
 	int32 xp_needed = info_struct->get_xp_needed();
 	if(xp_needed > 0){
-		float percentage = ((double)info_struct->get_xp() / xp_needed) * 1000;
-		info_struct->set_xp_yellow((int16)percentage);
-		info_struct->set_xp_blue((int16)(percentage-info_struct->get_xp_yellow())*1000);
+		double div_percent = ((double)info_struct->get_xp() / xp_needed) * 100.0;
+		int16 percentage = (int16)(div_percent) * 10;
+		double whole, fractional = 0.0;
+		fractional = std::modf(div_percent, &whole);
+		info_struct->set_xp_yellow(percentage);
+		info_struct->set_xp_blue((int16)(fractional * 1000));
+		
+		// vitality bars probably need a revisit
 		info_struct->set_xp_blue_vitality_bar(0);
 		info_struct->set_xp_yellow_vitality_bar(0);
 		if(player->GetXPVitality() > 0){
@@ -759,7 +764,9 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		}
 		else
 		{
-			packet->setDataByName("exp_debt", (int16)(info_struct->get_xp_debt()/10.0f));//95= 9500% //confirmed DoV
+			double currentPctOfLevel = (double)info_struct->get_xp() / (double)info_struct->get_xp_needed();
+			double neededPctAdvanceOutOfDebt = (currentPctOfLevel + ((double)info_struct->get_xp_debt() / 100.0)) * 1000.0;
+			packet->setDataByName("exp_debt", (int16)(neededPctAdvanceOutOfDebt));//95= 9500% //confirmed DoV
 		}
 		
 		packet->setDataByName("current_trade_xp", info_struct->get_ts_xp());// confirmed DoV
@@ -2441,8 +2448,6 @@ void Player::RemovePlayerSkill(int32 skill_id, bool save) {
 	Skill* skill = skill_list.GetSkill(skill_id);
 	if (skill)
 		RemoveSkillFromDB(skill, save);
-	
-	safe_delete(skill);
 }
 
 void Player::RemoveSkillFromDB(Skill* skill, bool save) {
@@ -3970,7 +3975,7 @@ void Player::CalculateOfflineDebtRecovery(int32 unix_timestamp)
 	if(unix_timestamp < 1 || xpDebt == 0.0f)
 		return;
 
-	uint32 diff = (Timer::GetCurrentTime2() - unix_timestamp)/1000;
+	uint32 diff = (Timer::GetUnixTimeStamp() - unix_timestamp)/1000;
 	
 	float recoveryDebtPercentage = rule_manager.GetGlobalRule(R_Combat, ExperienceDebtRecoveryPercent)->GetFloat()/100.0f;
 	int32 recoveryPeriodSeconds = rule_manager.GetGlobalRule(R_Combat, ExperienceDebtRecoveryPeriod)->GetInt32();

+ 28 - 12
EQ2/source/WorldServer/Skills.cpp

@@ -47,6 +47,7 @@ Skill::Skill(){
 	skill_type = 0;
 	display = 0;
 	save_needed = false;
+	active_skill = true;
 }
 
 Skill::Skill(Skill* skill){
@@ -60,6 +61,7 @@ Skill::Skill(Skill* skill){
 	name = skill->name;
 	description = skill->description;
 	save_needed = false;
+	active_skill = true;
 }
 
 map<int32, Skill*>* MasterSkillList::GetAllSkills(){
@@ -145,6 +147,7 @@ PlayerSkillList::~PlayerSkillList(){
 }
 
 void PlayerSkillList::AddSkill(Skill* new_skill){
+	std::unique_lock lock(MPlayerSkills);
 	Skill* tmpSkill = nullptr;
 	if(skills.count(new_skill->skill_id)) {
 		tmpSkill = skills[new_skill->skill_id];
@@ -154,12 +157,15 @@ void PlayerSkillList::AddSkill(Skill* new_skill){
 		lua_interface->SetLuaUserDataStale(tmpSkill);
 		safe_delete(tmpSkill);
 	}
+	name_skill_map.clear();
 }
 
 void PlayerSkillList::RemoveSkill(Skill* skill) {
+	std::unique_lock lock(MPlayerSkills);
 	if (skill) {
 		lua_interface->SetLuaUserDataStale(skill);
-		skills.erase(skill->skill_id);
+		skill->active_skill = false;
+		name_skill_map.clear();
 	}
 }
 
@@ -199,11 +205,13 @@ void PlayerSkillList::IncreaseAllSkillCaps(int16 value){
 }
 
 bool PlayerSkillList::HasSkill(int32 skill_id){
-	return (skills.count(skill_id) > 0);
+	std::shared_lock lock(MPlayerSkills);
+	return (skills.count(skill_id) > 0 && skills[skill_id]->active_skill);
 }
 
 Skill* PlayerSkillList::GetSkill(int32 skill_id){
-	if(skills.count(skill_id) > 0)
+	std::shared_lock lock(MPlayerSkills);
+	if(skills.count(skill_id) > 0 && skills[skill_id]->active_skill)
 		return skills[skill_id];
 	else
 		return 0;
@@ -337,20 +345,27 @@ int16 PlayerSkillList::CalculateSkillMaxValue(int32 skill_id, int16 max_val) {
 }
 
 EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){
+	std::shared_lock lock(MPlayerSkills);
 	PacketStruct* packet = configReader.getStruct("WS_UpdateSkillBook", version);
 	if(packet){
+			int16 skill_count = 0;
+			map<int32, Skill*>::iterator itr;
+			for (itr = skills.begin(); itr != skills.end(); itr++) {
+				if (itr->second && itr->second->active_skill)
+					skill_count++;
+			}
 			int16 size = 0;
 			if (version > 546) {
-				size = 21 * skills.size() + 8;
+				size = 21 * skill_count + 8;
 			}
 			else if (version <= 283) {
-				size = 12 * skills.size() + 6;
+				size = 12 * skill_count + 6;
 			}
 			else if (version <= 546) {
-				size = 21 * skills.size() + 7;
+				size = 21 * skill_count + 7;
 			}
 			
-			if (skills.size() > packet_count) {
+			if (skill_count > packet_count) {
 			uchar* tmp = 0;
 			if (orig_packet) {
 				tmp = new uchar[size];
@@ -367,15 +382,14 @@ EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){
 			xor_packet = new uchar[size];
 			memset(xor_packet, 0, size);
 		}
-		packet_count = skills.size();
+		packet_count = skill_count;
 		orig_packet_size = size;
-		packet->setArrayLengthByName("skill_count", skills.size());
-		map<int32, Skill*>::iterator itr;
+		packet->setArrayLengthByName("skill_count", skill_count);
 		Skill* skill = 0;
 		int32 i=0;
 		for(itr = skills.begin(); itr != skills.end(); itr++){
 			skill = itr->second;
-			if(skill){
+			if(skill && skill->active_skill){
 				int16 skill_max_with_bonuses = CalculateSkillMaxValue(skill->skill_id, skill->max_val);
 				int16 skill_with_bonuses = int(CalculateSkillValue(skill->skill_id, skill->current_val));
 				packet->setArrayDataByName("skill_id", skill->skill_id, i);
@@ -406,8 +420,8 @@ EQ2Packet* PlayerSkillList::GetSkillPacket(int16 version){
 				
 				packet->setArrayDataByName("current_val", current_val, i);
 				packet->setArrayDataByName("base_val", current_val, i);
+				i++;
 			}
-			i++;
 		}
 		int8 offset = 1;
 		if (version <= 283)
@@ -437,6 +451,7 @@ bool PlayerSkillList::CheckSkillIncrease(Skill* skill){
 }
 
 Skill* PlayerSkillList::GetSkillByName(const char* name){
+	std::shared_lock lock(MPlayerSkills);
 	if(name_skill_map.size() == 0){
 		map<int32, Skill*>::iterator itr;
 		Skill* skill = 0;
@@ -452,6 +467,7 @@ Skill* PlayerSkillList::GetSkillByName(const char* name){
 }
 
 vector<Skill*>* PlayerSkillList::GetSaveNeededSkills(){
+	std::shared_lock lock(MPlayerSkills);
 	vector<Skill*>* ret = new vector<Skill*>;
 	map<int32, Skill*>::iterator itr;
 	for(itr = skills.begin(); itr != skills.end(); itr++){

+ 5 - 0
EQ2/source/WorldServer/Skills.h

@@ -21,6 +21,9 @@
 #define __EQ2_SKILLS_H__
 
 #include <map>
+#include <mutex>
+#include <shared_mutex>
+
 #include "../common/ConfigReader.h"
 #include "../common/types.h"
 #include "MutexMap.h"
@@ -97,6 +100,7 @@ public:
 	EQ2_16BitString name;
 	EQ2_16BitString description;
 	bool			save_needed;
+	bool			active_skill;
 	int			CheckDisarmSkill(int16 targetLevel, int8 chest_difficulty=0);
 };
 
@@ -161,6 +165,7 @@ public:
 	void ResetPackets();
 private:
 	volatile bool has_updates;
+	mutable std::shared_mutex MPlayerSkills;
 	Mutex MSkillUpdates;
 	int16 packet_count;
 	uchar* orig_packet;

+ 15 - 2
EQ2/source/WorldServer/Spawn.cpp

@@ -33,6 +33,7 @@
 #include "Bots/Bot.h"
 #include "Zone/raycast_mesh.h"
 #include "RaceTypes/RaceTypes.h"
+#include "VisualStates.h"
 
 extern ConfigReader configReader;
 extern RuleManager rule_manager;
@@ -40,6 +41,7 @@ extern World world;
 extern ZoneList zone_list;
 extern MasterRaceTypeList race_types_list;
 extern LuaInterface* lua_interface;
+extern VisualStates visual_states;
 
 Spawn::Spawn(){ 
 	loot_coins = 0;
@@ -2403,8 +2405,19 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 
 	if (GetTempActionState() >= 0)
 		packet->setDataByName("action_state", GetTempActionState());
-	else
-		packet->setDataByName("action_state", appearance.action_state);
+	else {
+		Client* client = spawn->GetClient();
+		int16 action_state = appearance.action_state;
+		if(IsEntity() && client) {
+			std::string actionState = ((Entity*)this)->GetInfoStruct()->get_action_state();
+			if(actionState.size() > 0) {
+				Emote* emote = visual_states.FindEmote(actionState, client->GetVersion());
+				if(emote != NULL)
+					action_state = emote->GetVisualState();
+			}
+		}
+		packet->setDataByName("action_state", action_state);
+	}
 	
 	bool scaredOfPlayer = false;
 	

+ 2 - 2
EQ2/source/WorldServer/SpellProcess.cpp

@@ -1282,8 +1282,8 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 			LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target Enemy (%s) and Max AE Targets = 0.", spell->GetName(), target->GetName());
 
 		
-			if(spell->GetSpellData()->friendly_spell && (caster->IsPlayer() || caster->IsBot()) && (target->IsPlayer() || target->IsBot()) && 
-			   ((Entity*)target)->GetInfoStruct()->get_engaged_encounter() && 
+			if((spell->GetSpellData()->friendly_spell && caster != target && (caster->IsPlayer() || caster->IsBot()) && (target->IsPlayer() || target->IsBot()) && 
+			   ((Entity*)target)->GetInfoStruct()->get_engaged_encounter()) && 
 				((!((Entity*)caster)->GetGroupMemberInfo() || !((Entity*)target)->GetGroupMemberInfo()) || 
 				(((Entity*)caster)->GetGroupMemberInfo()->group_id != ((Entity*)target)->GetGroupMemberInfo()->group_id))) {
 				LogWrite(SPELL__DEBUG, 1, "Spell", "%s: Target (%s) is engaged in combat and cannot be assisted.", spell->GetName(), target->GetName());

+ 12 - 2
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -937,7 +937,7 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 	NPC* npc = 0;
 	int32 id = 0;
 	int32 total = 0;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players\n"
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.expansion_flag, s.holiday_flag, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, npc.water_type, npc.flying_type, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n"
 													"FROM spawn s\n"
 													"INNER JOIN spawn_npcs npc\n"
 													"ON s.id = npc.spawn_id\n"
@@ -1099,6 +1099,11 @@ void WorldDatabase::LoadNPCs(ZoneServer* zone){
 		
 		npc->SetScaredByStrongPlayers(atoul(row[85]));
 		
+		if(row[86]){
+			std::string action_state_str = std::string(row[86]);
+			npc->GetInfoStruct()->set_action_state(action_state_str);
+		}
+		
 		zone->AddNPC(id, npc);
 		total++;
 		LogWrite(NPC__DEBUG, 5, "NPC", "---Loading NPC: '%s' (%u)", npc->appearance.name, id);
@@ -6751,7 +6756,7 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
 	int32 id = 0;
 	DatabaseResult result;
 										
-	database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players\n"
+	database_new.Select(&result, "SELECT npc.spawn_id, s.name, npc.min_level, npc.max_level, npc.enc_level, s.race, s.model_type, npc.class_, npc.gender, s.command_primary, s.command_secondary, s.show_name, npc.min_group_size, npc.max_group_size, npc.hair_type_id, npc.facial_hair_type_id, npc.wing_type_id, npc.chest_type_id, npc.legs_type_id, npc.soga_hair_type_id, npc.soga_facial_hair_type_id, s.attackable, s.show_level, s.targetable, s.show_command_icon, s.display_hand_icon, s.hp, s.power, s.size, s.collision_radius, npc.action_state, s.visual_state, npc.mood_state, npc.initial_state, npc.activity_status, s.faction_id, s.sub_title, s.merchant_id, s.merchant_type, s.size_offset, npc.attack_type, npc.ai_strategy+0, npc.spell_list_id, npc.secondary_spell_list_id, npc.skill_list_id, npc.secondary_skill_list_id, npc.equipment_list_id, npc.str, npc.sta, npc.wis, npc.intel, npc.agi, npc.heat, npc.cold, npc.magic, npc.mental, npc.divine, npc.disease, npc.poison, npc.aggro_radius, npc.cast_percentage, npc.randomize, npc.soga_model_type, npc.heroic_flag, npc.alignment, npc.elemental, npc.arcane, npc.noxious, s.savagery, s.dissonance, npc.hide_hood, npc.emote_state, s.prefix, s.suffix, s.last_name, s.disable_sounds, s.merchant_min_level, s.merchant_max_level, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, npc.scared_by_strong_players, npc.action_state_str\n"
 								 "FROM spawn s\n"
 								 "INNER JOIN spawn_npcs npc\n"
 								 "ON npc.spawn_id = s.id\n"
@@ -6889,6 +6894,11 @@ bool WorldDatabase::LoadNPC(ZoneServer* zone, int32 spawn_id) {
 		
 		npc->SetScaredByStrongPlayers(result.GetInt32(81));
 		
+		if(!result.IsNull(82)){
+			std::string action_state_str = std::string(result.GetString(82));
+			npc->GetInfoStruct()->set_action_state(action_state_str);
+		}
+		
 		zone->AddNPC(id, npc);
 
 		//skipped spells/skills/equipment as it is all loaded, the following rely on a spawn to load

+ 4 - 2
server/SpawnStructs.xml

@@ -537,9 +537,11 @@
 <Data ElementName="soga_hair_color1" Type="EQ2_Color" /> <!-- 648 -->
 <Data ElementName="soga_hair_color2" Type="EQ2_Color" /> <!-- 651 -->
 <Data ElementName="soga_hair_highlight" Type="EQ2_Color" /> <!-- 654 -->
-<Data ElementName="unknown" Type="int8" Size="6" /> <!-- 657 -->
+<Data ElementName="combat_voice" Type="int16" Size="1" />
+<Data ElementName="emote_voice" Type="int16" Size="1" />
+<Data ElementName="unknown" Type="int8" Size="2" /> <!-- 657 -->
 <Data ElementName="flags" Type="int8" Size="1" /><!-- 663 1 == invisible, 2 == show_hood, 8 == crouch -->
-<Data ElementName="unknown" Type="int8" Size="3" /> <!-- 664 -->
+<Data ElementName="unknown2" Type="int8" Size="3" /> <!-- 664 -->
 <Data ElementName="temporary_scale" Type="float" Size="1" /> <!-- 667 -->
 <Data ElementName="name" Type="char" Size="64" /> <!-- 671 -->
 <Data ElementName="last_name" Type="char" Size="64" /> <!-- 735 -->

+ 1 - 0
server/WorldStructs.xml

@@ -9351,6 +9351,7 @@ to zero and treated like placeholders." />
 	<Data ElementName="status2" Type="int32" Size="1" />	
 </Data>
 <Data ElementName="type" Type="int8" /> <!-- 0==buy, 1==sell, 16==repair, 128==goblin game -->
+<Data ElementName="unknown" Type="int8" Size="2" />
 </Struct>
 <Struct Name="WS_UpdateMerchant" ClientVersion="547" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateMerchantCmd">
 <Data ElementName="spawn_id" Type="int32" />

Some files were not shown because too many files changed in this diff