Browse Source

- ZoneServer::PlayAnimation Fix #395 - spawn id 0 check to avoid bad packets, also client versioning methodology added, no longer creating a packet for each client
- SpellProcess::DeleteCasterSpell now unshades/ungreys a spell that was in maintained effects when cancelled. This is for Fix #383 in relation to pet spells, but extends to any endless duration maintained spells.
- Fix #315 Implemented /cancel_effect [spell_id] - need DB query update: update commands set handler=530 where command='cancel_effect';
- Spell bonuses should properly be removed when spell is removed from a entity/spawn (char sheet stats should update and bonuses re-calculated)
- Sanity checks on /cancel_maintained so you cannot overflow the array and crash the server
- Resisted spells should no longer add control effects or do other spell like impacts when the cast is complete, this is based on the SpellDamage/Attack (that resists, then additional effects will be ignored in the cast function)
- Root, stun and mesmerize now hold the NPC in place
- We purge their previous movement plans to avoid wandering pointlessly when an NPC is stunned, mesmerized or rooted
- starting_languages update
R_World StartingZoneLanguages
Value 0: Does a match specific to race id (ignores starting_zone in the SQL query, so it consumes all) and inserts the languages applicable
Value 1: Looks for all matching to starting_city, which is based on the client see starting_zones for examples. Also wildcard for starting city of 0 and race id match.
update starting_languages set race=2 where race=3 and language_id=3; #dwarf (2) language id of 3 is not erudite race (3)

Emagi 1 year ago
parent
commit
4b322d60ae

+ 1 - 0
DB/updates/canceleffect_command_june_28_2022.sql

@@ -0,0 +1 @@
+update commands set handler=530 where command='cancel_effect';

+ 3 - 0
DB/updates/starting_zone_alignment_june_26_2022.sql

@@ -0,0 +1,3 @@
+alter table starting_zones add column start_alignment tinyint(3) signed not null default 0;
+update starting_zones set start_alignment = 1 where choice = 1 or choice = 4 or choice = 32;
+alter table characters add column alignment tinyint(3) signed not null default 0;

+ 33 - 10
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -5606,6 +5606,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			client->GetPlayer()->MentorTarget();
 			break;
 		}
+		case COMMAND_CANCEL_EFFECT	: { Command_CancelEffect(client, sep); break; }
 		default: 
 		{
 			LogWrite(COMMAND__WARNING, 0, "Command", "Unhandled command: %s", command->command.data.c_str());
@@ -5799,17 +5800,15 @@ void Commands::Command_CancelMaintained(Client* client, Seperator* sep)
 	if (sep && sep->arg[0] && sep->IsNumber(0)) 
 	{
 		int32 spell_index = atoul(sep->arg[0]);
+		if(spell_index > 29)
+			return;
+		
+		client->GetPlayer()->MMaintainedSpells.readlock(__FUNCTION__, __LINE__);
 		MaintainedEffects mEffects = client->GetPlayer()->GetInfoStruct()->maintained_effects[spell_index];
-		//Spell* spell = master_spell_list.GetSpell(mEffects.spell_id, mEffects.tier);
-
-	//	if (spell && spell->GetSpellData()->friendly_spell)  -- NOTE::You can cancel hostile maintained spells, 
-		                                                     // just not spelleffects/dets - Foof
-		//{
-			if (!client->GetPlayer()->GetZone()->GetSpellProcess()->DeleteCasterSpell(mEffects.spell, "canceled", false, true))
-				client->Message(CHANNEL_COLOR_RED, "The maintained spell could not be cancelled.");
-	//	}
-		//else
-			//client->Message(CHANNEL_COLOR_RED, "You can only cancel friendly spells!");
+		client->GetPlayer()->MMaintainedSpells.releasereadlock(__FUNCTION__, __LINE__);
+		
+		if (!client->GetPlayer()->GetZone()->GetSpellProcess()->DeleteCasterSpell(mEffects.spell, "canceled", false, true))
+			client->Message(CHANNEL_COLOR_RED, "The maintained spell could not be cancelled.");
 	}
 }
 
@@ -11712,3 +11711,27 @@ Player* player = client->GetPlayer();
 	client->SimpleMessage(CHANNEL_NARRATIVE, "tired");
 	return;
 }
+
+/* 
+	Function: Command_CancelEffect()
+	Purpose	: Cancels (good) effect spells
+	Example	: /cancel_effect spell_id - would cancel the spell with the <spell_id> value in spell effects list
+*/ 
+void Commands::Command_CancelEffect(Client* client, Seperator* sep)
+{
+	if (sep && sep->arg[0] && sep->IsNumber(0)) 
+	{
+		int32 spell_id = atoul(sep->arg[0]);
+		
+		SpellEffects* effect = client->GetPlayer()->GetSpellEffect(spell_id);
+		if(!effect || effect->spell->spell->GetSpellData()->det_type) {
+			return;
+		}
+		
+		MaintainedEffects* meffect = effect->caster->GetMaintainedSpell(spell_id);
+		
+		if (!meffect || !meffect->spell || !meffect->spell->caster || 
+		!meffect->spell->caster->GetZone()->GetSpellProcess()->DeleteCasterSpell(meffect->spell, "canceled", false, true, client->GetPlayer()))
+			client->Message(CHANNEL_COLOR_RED, "The spell effect could not be cancelled.");
+	}
+}

+ 4 - 0
EQ2/source/WorldServer/Commands/Commands.h

@@ -445,6 +445,8 @@ public:
 	void Command_Bot_Inv(Client* client, Seperator* sep);
 	void Command_Bot_Settings(Client* client, Seperator* sep);
 	void Command_Bot_Help(Client* client, Seperator* sep);
+	
+	void Command_CancelEffect(Client* client, Seperator* sep);
 
 	// AA Commands
 	void Get_AA_Xml(Client* client, Seperator* sep);
@@ -926,6 +928,8 @@ private:
 #define COMMAND_MENTOR		           	528
 #define COMMAND_UNMENTOR		        529
 
+#define COMMAND_CANCEL_EFFECT			530
+
 
 #define GET_AA_XML						750
 #define ADD_AA							751

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

@@ -1025,7 +1025,12 @@ void Entity::RemoveSpellEffect(LuaSpell* spell) {
 		changed = true;
 		info_changed = true;
 		AddChangedZoneSpawn();
+		
+		if(IsPlayer()) {
+			((Player*)this)->SetCharSheetChanged(true);
+		}
 	}
+
 	MSpellEffects.releasewritelock(__FUNCTION__, __LINE__);
 }
 
@@ -1702,6 +1707,11 @@ void Entity::RemoveMezSpell(LuaSpell* spell) {
 			((Player*)this)->SetPlayerControlFlag(1, 16, false);
 			if (!IsRooted())
 				((Player*)this)->SetPlayerControlFlag(1, 8, false);
+		}		
+		
+		if(!IsPlayer()) {
+			GetZone()->movementMgr->StopNavigation((Entity*)this);
+			((Spawn*)this)->StopMovement();
 		}
 	}
 }
@@ -1808,6 +1818,11 @@ void Entity::RemoveStunSpell(LuaSpell* spell) {
 				((Player*)this)->SetPlayerControlFlag(1, 8, false);
 			if (!IsStifled() && !IsFeared())
 				GetZone()->UnlockAllSpells((Player*)this);
+		}		
+		
+		if(!IsPlayer()) {
+			GetZone()->movementMgr->StopNavigation((Entity*)this);
+			((Spawn*)this)->StopMovement();
 		}
 	}
 }
@@ -2548,6 +2563,11 @@ void Entity::RemoveRootSpell(LuaSpell* spell) {
 			// GetHighestSnare() will return 1.0f if no snares returning the spawn to full speed
 			SetSpeedMultiplier(GetHighestSnare());
 		}
+	
+		if(!IsPlayer()) {
+			GetZone()->movementMgr->StopNavigation((Entity*)this);
+			((Spawn*)this)->StopMovement();
+		}
 	}
 }
 

+ 98 - 13
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -1331,8 +1331,9 @@ int EQ2Emu_lua_SpellHeal(lua_State* state) {
 		return 0;
 
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
-	if (!luaspell)
+	if(!luaspell || luaspell->resisted) {
 		return 0;
+	}
 	Spawn* caster = luaspell->caster;
 	string heal_type = lua_interface->GetStringValue(state);//power, heal ect
 	int32 min_heal = lua_interface->GetInt32Value(state, 2);
@@ -1378,8 +1379,9 @@ int EQ2Emu_lua_SpellHealPct(lua_State* state) {
 		return 0;
 
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
-	if (!luaspell)
+	if(!luaspell || luaspell->resisted) {
 		return 0;
+	}
 	Spawn* caster = luaspell->caster;
 	string heal_type = lua_interface->GetStringValue(state);//power, heal ect
 	float percentage = lua_interface->GetFloatValue(state, 2);
@@ -1690,6 +1692,11 @@ int EQ2Emu_lua_AddHate(lua_State* state) {
 	sint32 amount = lua_interface->GetSInt32Value(state, 3);
 	bool send_packet = lua_interface->GetInt8Value(state, 4) == 1 ? true : false;
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
+	
+	if(!luaspell || luaspell->resisted) {
+		return 0;
+	}
+	
 	if (entity && entity->IsEntity() && amount != 0) {
 		if (luaspell) {
 			ZoneServer* zone = luaspell->caster->GetZone();
@@ -1809,8 +1816,9 @@ int EQ2Emu_lua_SpellDamage(lua_State* state) {
 		return 0;
 	Spawn* target = lua_interface->GetSpawn(state);
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
-	if (!luaspell)
+	if(!luaspell || luaspell->resisted) {
 		return 0;
+	}
 	Spawn* caster = luaspell->caster;
 	sint32 type = lua_interface->GetSInt32Value(state, 2);
 	int32 min_damage = lua_interface->GetInt32Value(state, 3);
@@ -2236,6 +2244,10 @@ int EQ2Emu_lua_AddSpellBonus(lua_State* state) {
 	const int16 type = lua_interface->GetInt16Value(state, 2);
 	const float value = lua_interface->GetFloatValue(state, 3);
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
+	
+	if(!luaspell || luaspell->resisted) {
+		return 0;
+	}
 
 	int64 class_req = 0;
 	int32 class_id = 0;
@@ -2325,6 +2337,10 @@ int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state) {
 		lua_interface->LogError("%s: LUA AddSpawnSpellBonus command error: can only be used in a spell script", lua_interface->GetScriptName(state));
 		return 0;
 	}
+	
+	if(luaspell->resisted) {
+		return 0;
+	}
 
 	int32 class_req = 0;
 	vector<int16> faction_req;
@@ -2423,6 +2439,9 @@ int EQ2Emu_lua_AddSkillBonus(lua_State* state) {
 	if (value != 0) {
 		int32 spell_id = 0;
 		if (luaspell && luaspell->spell && luaspell->caster) {
+			if(luaspell->resisted) {
+				return 0;
+			}
 			spell_id = luaspell->spell->GetSpellID();
 			ZoneServer* zone = luaspell->caster->GetZone();
 			Spawn* target = 0;
@@ -2481,6 +2500,9 @@ int EQ2Emu_lua_RemoveSkillBonus(lua_State* state) {
 	if (spawn && spawn->IsPlayer()) {
 		int32 spell_id = 0;
 		if (luaspell && luaspell->spell) {
+			if(luaspell->resisted) {
+				return 0;
+			}
 			spell_id = luaspell->spell->GetSpellID();
 			ZoneServer* zone = luaspell->caster->GetZone();
 			Spawn* target = 0;
@@ -2531,6 +2553,11 @@ int EQ2Emu_lua_AddControlEffect(lua_State* state) {
 	int8 type = lua_interface->GetInt32Value(state, 2);
 	bool only_add_spawn = lua_interface->GetInt8Value(state, 3) == 1;
 	LuaSpell* luaspell = lua_interface->GetCurrentSpell(state);
+	
+	if(luaspell && luaspell->resisted) {
+		return 0;
+	}
+	
 	if (!only_add_spawn && luaspell && luaspell->spell && luaspell->caster && type != 0) {
 		ZoneServer* zone = luaspell->caster->GetZone();
 		Spawn* target = 0;
@@ -4877,7 +4904,10 @@ int EQ2Emu_lua_Charm(lua_State* state) {
 		lua_interface->LogError("%s: LUA Charm command error: Spell is not valid, charm can only be used in spell scripts.", lua_interface->GetScriptName(state));
 		return 0;
 	}
-
+	
+	if(luaspell->resisted) {
+		return 0;
+	}
 	if (owner && pet && owner->IsEntity() && pet->IsNPC()) {
 		((Entity*)owner)->SetCharmedPet((Entity*)pet);
 		pet->SetPet(true);
@@ -5152,7 +5182,11 @@ int EQ2Emu_lua_SummonPet(lua_State* state) {
 		lua_interface->LogError("%s: LUA SummonPet command error: valid spell not found, SummonPet can only be used in spell scripts", lua_interface->GetScriptName(state));
 		return 0;
 	}
-
+	
+	if(luaspell->resisted) {
+		return 0;
+	}
+	
 	// Get a pointer to a spawn with the given DB ID and check if the pointer is valid
 	Spawn* pet = spawn->GetZone()->GetSpawn(pet_id);
 	if (!pet) {
@@ -5281,6 +5315,10 @@ int EQ2Emu_lua_SummonDeityPet(lua_State* state) {
 		lua_interface->LogError("%s: LUA SummonDeityPet command error: valid spell not found, SummonDeityPet can only be used in spell scripts", lua_interface->GetScriptName(state));
 		return 0;
 	}
+	
+	if(luaspell->resisted) {
+		return 0;
+	}
 
 	Spawn* pet = spawn->GetZone()->GetSpawn(pet_id);
 	if (!pet) {
@@ -5368,7 +5406,11 @@ int EQ2Emu_lua_SummonCosmeticPet(lua_State* state) {
 		lua_interface->LogError("%s: LUA SummonCosmeticPet command error: valid spell not found, SummonCosmeticPet can only be used in spell scripts", lua_interface->GetScriptName(state));
 		return 0;
 	}
-
+	
+	if(luaspell->resisted) {
+		return 0;
+	}
+	
 	Spawn* pet = spawn->GetZone()->GetSpawn(pet_id);
 	if (!pet) {
 		lua_interface->LogError("%s: LUA SummonCosmeticPet command error: Could not find spawn with id of %u.", lua_interface->GetScriptName(state), pet_id);
@@ -6067,7 +6109,11 @@ int EQ2Emu_lua_AddWard(lua_State* state) {
 	int32 maxHitCount = lua_interface->GetInt32Value(state, 8);
 
 	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
-
+	
+	if(!spell || spell->resisted) {
+		return 0;
+	}
+	
 	bool ward_was_added = false;
 
 	ZoneServer* zone = spell->caster->GetZone();
@@ -6136,7 +6182,10 @@ int EQ2Emu_lua_AddToWard(lua_State* state) {
 	int32 amount = lua_interface->GetInt32Value(state);
 	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
 	WardInfo* ward = 0;
-
+	
+	if(!spell || spell->resisted) {
+		return 0;
+	}
 	ZoneServer* zone = spell->caster->GetZone();
 	spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
 	if (zone->GetSpawnByID(spell->targets.at(0))->IsEntity()) {
@@ -6234,7 +6283,11 @@ int EQ2Emu_lua_RemoveWard(lua_State* state) {
 		return 0;
 
 	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
-
+	
+	if(!spell) {
+		return 0;
+	}
+	
 	ZoneServer* zone = spell->caster->GetZone();
 	Spawn* target = 0;
 	spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
@@ -7611,6 +7664,10 @@ int EQ2Emu_lua_SummonDumbFirePet(lua_State* state) {
 		lua_interface->LogError("%s: LUA SummonDumbFirePet command error: valid spell not found, SummonPet can only be used in spell scripts", lua_interface->GetScriptName(state));
 		return 0;
 	}
+	
+	if(luaspell->resisted) {
+		return 0;
+	}
 
 	// Get a pointer to a spawn with the given DB ID and check if the pointer is valid
 	Spawn* pet = spawn->GetZone()->GetSpawn(pet_id);
@@ -8038,7 +8095,11 @@ int EQ2Emu_lua_AddProc(lua_State* state) {
 		lua_interface->LogError("%s: LUA AddProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(state));
 		return 0;
 	}
-
+	
+	if(spell && spell->resisted) {
+		return 0;
+	}
+	
 	if (spell && use_all_spelltargets) {
 		Spawn* target;
 		spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
@@ -8796,6 +8857,10 @@ int EQ2Emu_lua_AddThreatTransfer(lua_State* state) {
 		lua_interface->LogError("%s: LUA AddThreatTransfer command error: can only be used in a spell script", lua_interface->GetScriptName(state));
 		return 0;
 	}
+	
+	if(spell->resisted) {
+		return 0;
+	}
 
 	if (((Entity*)caster)->GetThreatTransfer()) {
 		return 0;
@@ -8851,7 +8916,11 @@ int EQ2Emu_lua_CureByType(lua_State* state) {
 	string cure_name = lua_interface->GetStringValue(state, 3);
 	int8 cure_level = lua_interface->GetInt8Value(state, 4);
 	Spawn* target = lua_interface->GetSpawn(state, 5);
-
+	
+	if(!spell || spell->resisted) {
+		return 0;
+	}
+	
 	if (target) {
 		if (!target->IsEntity()) {
 			lua_interface->LogError("%s: LUA CureByType command error: spawn override must be entity if used", lua_interface->GetScriptName(state));
@@ -8889,7 +8958,11 @@ int EQ2Emu_lua_CureByControlEffect(lua_State* state) {
 		lua_interface->LogError("%s: LUA CureByControlEffect command error: can only be used in a spell script", lua_interface->GetScriptName(state));
 		return 0;
 	}
-
+	
+	if(spell->resisted) {
+		return 0;
+	}
+	
 	int8 cure_count = lua_interface->GetInt8Value(state);
 	int8 cure_type = lua_interface->GetInt8Value(state, 2);
 	string cure_name = lua_interface->GetStringValue(state, 3);
@@ -9195,6 +9268,10 @@ int EQ2Emu_lua_AddImmunitySpell(lua_State* state) {
 		lua_interface->LogError("%s: LUA AddImmunitySpell command error: This must be used in a spellscript", lua_interface->GetScriptName(state));
 		return 0;
 	}
+	
+	if(spell->resisted) {
+		return 0;
+	}
 
 	if (spawn) {
 		if (!spawn->IsEntity()) {
@@ -9264,7 +9341,11 @@ int EQ2Emu_lua_SetSpellSnareValue(lua_State* state) {
 		lua_interface->LogError("%s: LUA SetSpellSnareValue command error: This can only be used in a spell script!", lua_interface->GetScriptName(state));
 		return 0;
 	}
-
+	
+	if(spell->resisted) {
+		return 0;
+	}
+	
 	float snare = lua_interface->GetFloatValue(state);
 	Spawn* spawn = lua_interface->GetSpawn(state, 2);
 
@@ -10439,6 +10520,10 @@ int EQ2Emu_lua_Evac(lua_State* state) {
 	else {
 
 		LuaSpell* spell = lua_interface->GetCurrentSpell(state);
+		
+		if(!spell)
+			return 0;
+		
 		ZoneServer* zone = spell->caster->GetZone();
 
 		float x = spell->caster->GetZone()->GetSafeX();

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

@@ -76,6 +76,7 @@ struct LuaSpell{
 	int32			initial_target;
 	int32			initial_target_char_id;
 	vector<int32>	targets;
+	vector<int32>	removed_targets; // previously cancelled, expired, used, so on
 	multimap<int32, int8> char_id_targets;
 	Spell*			spell;
 	lua_State*		state;

+ 7 - 3
EQ2/source/WorldServer/PlayerGroups.cpp

@@ -691,10 +691,14 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 						client = (*target_itr)->client;
 
 						has_effect = false;
-
-						if (group_member->GetSpellEffect(spell->GetSpellID(), caster))
+						
+						if (group_member->GetSpellEffect(spell->GetSpellID(), caster)) {
 							has_effect = true;
-
+						}
+						if(!has_effect && (std::find(luaspell->removed_targets.begin(), 
+							luaspell->removed_targets.end(), group_member->GetID()) != luaspell->removed_targets.end())) {
+							continue;
+						}
 						// Check if player is within range of the caster
 						if (!rule_manager.GetGlobalRule(R_Spells, EnableCrossZoneGroupBuffs)->GetInt8() && 
 								(group_member->GetZone() != caster->GetZone() || caster->GetDistance(group_member) > spell->GetSpellData()->radius)) {

+ 13 - 1
EQ2/source/WorldServer/Spawn.cpp

@@ -2791,6 +2791,18 @@ void Spawn::ProcessMovement(bool isSpawnListLocked){
 
 	if (EngagedInCombat())
 	{
+		if(IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted())) {
+			SetAppearancePosition(GetX(),GetY(),GetZ());
+			if ( IsEntity() )
+				((Entity*)this)->SetSpeed(0.0f);
+			
+			SetSpeed(0.0f);
+			position_changed = true;
+			changed = true;
+			GetZone()->AddChangedSpawn(this);
+			StopMovement();
+			return;
+		}
 		int locations = 0;
 		if (movement_locations && MMovementLocations)
 		{
@@ -3088,7 +3100,7 @@ bool Spawn::IsRunning(){
 }
 
 void Spawn::RunToLocation(float x, float y, float z, float following_x, float following_y, float following_z){
-	if(IsPauseMovementTimerActive())
+	if(IsPauseMovementTimerActive() || (IsEntity() && (((Entity*)this)->IsMezzedOrStunned() || ((Entity*)this)->IsRooted())))
 		return;
 	
 	if(!IsWidget())

+ 92 - 44
EQ2/source/WorldServer/SpellProcess.cpp

@@ -282,8 +282,9 @@ void SpellProcess::Process(){
 	MSpellProcess.unlock();
 }
 bool SpellProcess::IsReady(Spell* spell, Entity* caster){
-	if(caster->IsCasting())
+	if(caster->IsCasting()) {
 		return false;
+	}
 	bool ret = true;	
 	RecastTimer* recast_timer = 0;
 	MutexList<RecastTimer*>::iterator itr = recast_timers.begin();
@@ -370,13 +371,40 @@ bool SpellProcess::DeleteCasterSpell(Spawn* caster, Spell* spell, string reason)
 	return ret;
 }
 
-bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removing_all_spells, bool lock_spell_process){
+bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removing_all_spells, bool lock_spell_process, Spawn* remove_target){
 	if(lock_spell_process)
 		MSpellProcess.lock();
 
 	bool ret = false;
 	Spawn* target = 0;
+	bool target_valid = false;
 	if(spell) {
+		
+		spell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
+		if(remove_target && spell->targets.size() > 1) {
+			for (int32 i = 0; i < spell->targets.size(); i++) {		
+				if(remove_target->GetID() == spell->targets.at(i)) {
+					if(remove_target->IsEntity()){
+						spell->removed_targets.push_back(remove_target->GetID());
+						((Entity*)remove_target)->RemoveProc(0, spell);
+						((Entity*)remove_target)->RemoveSpellEffect(spell);
+						((Entity*)remove_target)->RemoveSpellBonus(spell);
+						if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel))
+							((Entity*)remove_target)->RemoveDetrimentalSpell(spell);
+					}
+					spell->targets.erase(spell->targets.begin()+i, spell->targets.begin()+i+1);
+					target_valid = true;
+					break;
+				}
+			}
+			spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
+			if(lock_spell_process) {
+				MSpellProcess.unlock();
+			}
+			return target_valid;
+		}
+		spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
+		
 		if (active_spells.count(spell) > 0)
 			active_spells.Remove(spell);
 		if (spell->caster) {
@@ -398,11 +426,13 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removi
 				{
 					CheckRecast(spell->spell, spell->caster);
 					if (spell->caster && spell->caster->IsPlayer())
-						SendSpellBookUpdate(spell->caster->GetZone()->GetClientBySpawn(spell->caster));
+						SendSpellBookUpdate(((Player*)spell->caster)->GetClient());
 				}
 			}
-			if(IsReady(spell->spell, spell->caster) && spell->caster->IsPlayer())
+			if(IsReady(spell->spell, spell->caster) && spell->caster->IsPlayer()) {
 				((Player*)spell->caster)->UnlockSpell(spell->spell);
+				SendSpellBookUpdate(((Player*)spell->caster)->GetClient());
+			}
 			
 			spell->caster->RemoveProc(0, spell);
 			spell->caster->RemoveMaintainedSpell(spell);
@@ -412,12 +442,16 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removi
 			for (int32 i = 0; i < spell->targets.size(); i++) {
 				target = zone->GetSpawnByID(spell->targets.at(i));
 				if(target && target->IsEntity()){
+					spell->removed_targets.push_back(target->GetID());
+					((Entity*)target)->RemoveProc(0, spell);
 					((Entity*)target)->RemoveSpellEffect(spell);
+					((Entity*)target)->RemoveSpellBonus(spell);
 					if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel))
 						((Entity*)target)->RemoveDetrimentalSpell(spell);
 				}
 				else{
 					spell->caster->RemoveSpellEffect(spell);
+					spell->caster->RemoveSpellBonus(spell);
 					if(spell->spell->GetSpellData()->det_type > 0 && (spell->spell->GetSpellDuration() > 0 || spell->spell->GetSpellData()->duration_until_cancel))
 						spell->caster->RemoveDetrimentalSpell(spell);
 				}
@@ -963,9 +997,7 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		if (!harvest_spell)
 			GetSpellTargets(lua_spell);
 		else{
-			lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
-			lua_spell->targets.push_back(target_id);
-			lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
+			AddLuaSpellTarget(lua_spell, target_id);
 		}
 			
 
@@ -1999,7 +2031,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 									target = secondary_target;
 									luaspell->initial_target = target->GetID();
 									luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
-									luaspell->targets.push_back(target->GetID());
+									AddLuaSpellTarget(luaspell, target->GetID());
 								}
 								else if (secondary_target && secondary_target->IsPet() && ((NPC*)secondary_target)->GetOwner()->IsPlayer())
 									implied = true;
@@ -2008,7 +2040,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 							{
 								luaspell->initial_target = target->GetID();
 								luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
-								luaspell->targets.push_back(target->GetID());
+								AddLuaSpellTarget(luaspell, target->GetID());
 							}
 						}
 					}   // if spell is not friendly
@@ -2025,7 +2057,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								implied = true;
 								luaspell->initial_target = target->GetID();
 								luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
-								luaspell->targets.push_back(target->GetID());
+								AddLuaSpellTarget(luaspell, target->GetID());
 								GetPlayerGroupTargets((Player*)target, caster, luaspell);
 
 							}
@@ -2034,7 +2066,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								implied = true;
 								luaspell->initial_target = secondary_target->GetID();
 								luaspell->initial_target_char_id = (secondary_target && secondary_target->IsPlayer()) ? ((Player*)secondary_target)->GetCharacterID() : 0;
-								luaspell->targets.push_back(secondary_target->GetID());
+								AddLuaSpellTarget(luaspell, secondary_target->GetID());
 								GetPlayerGroupTargets((Player*)secondary_target, caster, luaspell);
 							}
 						}
@@ -2055,7 +2087,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 					if (target->IsPlayer() && ((Entity*)caster)->AttackAllowed((Entity*)target)) {
 						luaspell->initial_target = target->GetID();
 						luaspell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
-						luaspell->targets.push_back(target->GetID());
+						AddLuaSpellTarget(luaspell, target->GetID());
 					}
 				}
 			}
@@ -2093,13 +2125,13 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								// if NPC group member is (still) an NPC (wtf?) and is alive, send the NPC group member back as a successful target of non-friendly spell group_member->Alive()
 								if (group_member->GetZone() == caster->GetZone() && 
 								group_member->IsNPC() && group_member->Alive() && !((Entity*)group_member)->IsAOEImmune() && (!((Entity*)group_member)->IsMezzed() || group_member == target))
-									luaspell->targets.push_back(group_member->GetID());
+									AddLuaSpellTarget(luaspell, group_member->GetID());
 
 								// note: this should generate some hate towards the caster
 							}
 						} // end is spawngroup
 						else
-							luaspell->targets.push_back(target->GetID()); // return single target NPC for non-friendly spell
+							AddLuaSpellTarget(luaspell, target->GetID()); // return single target NPC for non-friendly spell
 					}
 					else if(data->friendly_spell)
 					{
@@ -2152,7 +2184,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 			if (data->icon_backdrop == 316) // single target in a group/raid
 			{
 				if (target)
-					luaspell->targets.push_back(target->GetID());
+					AddLuaSpellTarget(luaspell, target->GetID(), false);
 			}
 			// is friendly
 			else if (data->friendly_spell) 
@@ -2194,13 +2226,13 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 										SpellProcess::AddSelfAndPetToCharTargets(luaspell, group_member);		
 									}
 									else if (group_member->GetZone() == luaspell->caster->GetZone()) {
-										luaspell->targets.push_back(group_member->GetID());
+										AddLuaSpellTarget(luaspell, group_member->GetID(), false);
 										if (group_member->HasPet()) {
 											Entity* pet = group_member->GetPet();
 											if (!pet)
 												pet = group_member->GetCharmedPet();
 											if (pet)
-												luaspell->targets.push_back(pet->GetID());
+												AddLuaSpellTarget(luaspell, pet->GetID(), false);
 												
 											LogWrite(SPELL__DEBUG, 0, "Player", "%s added a pet %s (%u) for spell %s", group_member->GetName(), pet ? pet->GetName() : "", pet ? pet->GetID() : 0, luaspell->spell->GetName());
 										}
@@ -2228,14 +2260,14 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 							Spawn* group_member = *itr;
 
 							if (group_member->IsNPC() && group_member->Alive()){
-								luaspell->targets.push_back(group_member->GetID());
+								AddLuaSpellTarget(luaspell, group_member->GetID(), false);
 								if (((Entity*)group_member)->HasPet()){
 									Entity* pet = ((Entity*)group_member)->GetPet();
 									if (pet)
-									    luaspell->targets.push_back(pet->GetID());
+										AddLuaSpellTarget(luaspell, pet->GetID(), false);
 									pet = ((Entity*)group_member)->GetCharmedPet();
 									if (pet)
-										luaspell->targets.push_back(pet->GetID());
+										AddLuaSpellTarget(luaspell, pet->GetID(), false);
 								}
 							}
 						}
@@ -2249,7 +2281,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 		} // end is Group AE
 
 		else if (target_type == SPELL_TARGET_SELF && caster)
-			luaspell->targets.push_back(caster->GetID()); // if spell is SELF, return caster
+			AddLuaSpellTarget(luaspell, caster->GetID(), false); // if spell is SELF, return caster
 
 		else if (target_type == SPELL_TARGET_CASTER_PET && caster && caster->IsEntity() && ((Entity*)caster)->HasPet()) {
 			AddSelfAndPet(luaspell, caster, true);
@@ -2268,14 +2300,14 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 					{
 						// if caster is in a group, and target is a player and targeted player is a group member
 						if (((Player*)caster)->GetGroupMemberInfo() && (target->IsPlayer() || target->IsBot() || target->IsPet()) && ((Player*)caster)->IsGroupMember((Entity*)target))
-							luaspell->targets.push_back(target->GetID()); // return the target
+							AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target
 						else
-							luaspell->targets.push_back(caster->GetID()); // else return the caster
+							AddLuaSpellTarget(luaspell, caster->GetID(), false); // else return the caster
 					}
 					else if (target->IsPlayer() || target->IsBot()) // else it is not raid, group only or group spell
-						luaspell->targets.push_back(target->GetID()); // return target for single spell
+						AddLuaSpellTarget(luaspell, target->GetID(), false); // return target for single spell
 					else if ((luaspell->targets.size() < 1) || (!target->IsPet() || (((Entity*)target)->GetOwner() && !((Entity*)target)->GetOwner()->IsPlayer()))) 
-						luaspell->targets.push_back(caster->GetID()); // and if no target, cast on self
+						AddLuaSpellTarget(luaspell, caster->GetID(), false); // and if no target, cast on self
 				}
 				else if (caster->IsNPC()) // caster is an NPC
 				{
@@ -2285,24 +2317,24 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						if (caster->IsBot() && (target->IsBot() || target->IsPlayer())) {
 							GroupMemberInfo* gmi = ((Entity*)caster)->GetGroupMemberInfo();
 							if (gmi && target->IsEntity() && world.GetGroupManager()->IsInGroup(gmi->group_id, (Entity*)target)) {
-								luaspell->targets.push_back(target->GetID()); // return the target
+								AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target
 							}
 							else
 								AddSelfAndPet(luaspell, caster);
 						}
 						// if NPC caster is in a group, and target is a player and targeted player is a group member
 						else if (((NPC*)caster)->HasSpawnGroup() && target->IsNPC() && ((NPC*)caster)->IsInSpawnGroup((NPC*)target))
-							luaspell->targets.push_back(target->GetID()); // return the target
+							AddLuaSpellTarget(luaspell, target->GetID(), false); // return the target
 						else
 							AddSelfAndPet(luaspell, caster);
 					}
 					else if (target->IsNPC())
-						luaspell->targets.push_back(target->GetID()); // return target for single spell
+						AddLuaSpellTarget(luaspell, target->GetID(), false); // return target for single spell
 					else {
 						if (caster->IsBot() && (target->IsBot() || target->IsPlayer()))
-							luaspell->targets.push_back(target->GetID());
+							AddLuaSpellTarget(luaspell, target->GetID(), false);
 						else
-							luaspell->targets.push_back(caster->GetID()); // and if no target, cast on self
+							AddLuaSpellTarget(luaspell, caster->GetID(), false); // and if no target, cast on self
 					}
 				} // end is player
 			} // end is friendly
@@ -2326,13 +2358,13 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 							// if NPC group member is (still) an NPC (wtf?) and is alive, send the NPC group member back as a successful target of non-friendly spell group_member->Alive()
 							if (group_member->GetZone() == caster->GetZone() && 
 							group_member->IsNPC() && group_member->Alive() && !((Entity*)group_member)->IsAOEImmune() && (!((Entity*)group_member)->IsMezzed() || group_member == target))
-								luaspell->targets.push_back(group_member->GetID());
+								AddLuaSpellTarget(luaspell, group_member->GetID(), false);
 
 							// note: this should generate some hate towards the caster
 						}
 					} // end is spawngroup
 					else
-						luaspell->targets.push_back(target->GetID()); // return single target NPC for non-friendly spell
+						AddLuaSpellTarget(luaspell, target->GetID(), false); // return single target NPC for non-friendly spell
 
 					safe_delete(group);
 				} // end is NPC
@@ -2360,7 +2392,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								// if the group member is in the same zone as caster, and group member is alive, and group member is within distance
 								if (group_member && group_member->GetZone() == caster->GetZone() && group_member->Alive() && caster->GetDistance(group_member) <= data->range
 									&& (group_member == target || !group_member->IsAOEImmune()))
-									luaspell->targets.push_back(group_member->GetID()); // add as target
+									AddLuaSpellTarget(luaspell, group_member->GetID(), false); // add as target
 							}
 							group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
 						}
@@ -2368,11 +2400,11 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
 					}
 					else
-						luaspell->targets.push_back(target->GetID()); // player not in group
+						AddLuaSpellTarget(luaspell, target->GetID(), false); // player not in group
 				} // end is caster player or npc
 			}
 			else if (target)
-				luaspell->targets.push_back(target->GetID()); // is not friendly nor a group spell
+				AddLuaSpellTarget(luaspell, target->GetID(), false); // is not friendly nor a group spell
 		}
 		//Rez spells
 		else if(target && target_type == SPELL_TARGET_ENEMY_CORPSE){
@@ -2380,7 +2412,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 			if(data->friendly_spell){
 				//target is player
 				if(target->IsPlayer()){
-					luaspell->targets.push_back(target->GetID());
+					AddLuaSpellTarget(luaspell, target->GetID(), false);
 				}
 			}
 		}
@@ -2407,7 +2439,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								group_member = (*itr)->member;
 								//Check if group member is in the same zone in range of the spell and dead
 								if (group_member && group_member->GetZone() == target->GetZone() && !group_member->Alive() && target->GetDistance(group_member) <= data->radius) {
-									luaspell->targets.push_back(group_member->GetID());
+									AddLuaSpellTarget(luaspell, group_member->GetID(), false);
 								}
 							}
 							group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
@@ -2416,7 +2448,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
 					}
 					else
-						luaspell->targets.push_back(target->GetID());
+						AddLuaSpellTarget(luaspell, target->GetID(), false);
 				}
 			}
 		}
@@ -2480,7 +2512,7 @@ void SpellProcess::GetSpellTargetsTrueAOE(LuaSpell* luaspell) {
 			if (i == 0){
 				if (luaspell->initial_target && luaspell->caster->GetID() != luaspell->initial_target){
 					//this is the "Direct" target and aoe can't be avoided
-					luaspell->targets.push_back(luaspell->initial_target);
+					AddLuaSpellTarget(luaspell, luaspell->initial_target, false);
 					ignore_target = luaspell->initial_target;
 				}
 				if (luaspell->targets.size() >= luaspell->spell->GetSpellData()->max_aoe_targets)
@@ -2498,7 +2530,7 @@ void SpellProcess::GetSpellTargetsTrueAOE(LuaSpell* luaspell) {
 				//If this spawn is immune to aoe, continue
 				if (((Entity*)spawn)->IsAOEImmune() || ((Entity*)spawn)->IsMezzed())
 					continue;
-				luaspell->targets.push_back(spawn->GetID());
+				AddLuaSpellTarget(luaspell, spawn->GetID(), false);
 			}
 
 			if (luaspell->targets.size() >= luaspell->spell->GetSpellData()->max_aoe_targets)
@@ -2864,14 +2896,14 @@ void SpellProcess::AddActiveSpell(LuaSpell* spell)
 void SpellProcess::AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet)
 {
 	if(!onlyPet)
-		spell->targets.push_back(self->GetID());
+		AddLuaSpellTarget(spell, self->GetID(), false);
 	
 	if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetPet())
-		spell->targets.push_back(((Entity*)self)->GetPet()->GetID());
+		AddLuaSpellTarget(spell, ((Entity*)self)->GetPet()->GetID(), false);
 	if(self->IsEntity() && ((Entity*)self)->HasPet() && ((Entity*)self)->GetCharmedPet())
-		spell->targets.push_back(((Entity*)self)->GetCharmedPet()->GetID());
+		AddLuaSpellTarget(spell, ((Entity*)self)->GetCharmedPet()->GetID(), false);
 	if(!onlyPet && self->IsEntity() && ((Entity*)self)->IsPet() && ((Entity*)self)->GetOwner())
-		spell->targets.push_back(((Entity*)self)->GetOwner()->GetID());
+		AddLuaSpellTarget(spell, ((Entity*)self)->GetOwner()->GetID(), false);
 }
 
 void SpellProcess::AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet)
@@ -2892,4 +2924,20 @@ void SpellProcess::AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bo
 
 void SpellProcess::DeleteActiveSpell(LuaSpell* spell) {
 	active_spells.Remove(spell);
+}
+
+bool SpellProcess::AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_spell_targets) {
+	bool ret = false;
+	if(lock_spell_targets)
+		lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
+	
+	if(std::find(lua_spell->removed_targets.begin(), lua_spell->removed_targets.end(), id) == lua_spell->removed_targets.end()) {
+		lua_spell->targets.push_back(id);
+		ret = true;
+	}
+	
+	if(lock_spell_targets)
+		lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
+	
+	return ret;
 }

+ 2 - 1
EQ2/source/WorldServer/SpellProcess.h

@@ -262,7 +262,7 @@ public:
 
 	/// <summary>Remove the given spell from the ZpellProcess</summary>
 	/// <param name='spell'>LuaSpell to remove</param>
-	bool DeleteCasterSpell(LuaSpell* spell, string reason="", bool removing_all_spells = false, bool lock_spell_process = false);
+	bool DeleteCasterSpell(LuaSpell* spell, string reason="", bool removing_all_spells = false, bool lock_spell_process = false, Spawn* remove_target = nullptr);
 
 	/// <summary>Interrupt the spell</summary>
 	/// <param name='interrupt'>InterruptStruct that contains all the info</param>
@@ -395,6 +395,7 @@ public:
 	static void AddSelfAndPet(LuaSpell* spell, Spawn* self, bool onlyPet=false);
 	static void AddSelfAndPetToCharTargets(LuaSpell* spell, Spawn* caster, bool onlyPet=false);
 	void DeleteActiveSpell(LuaSpell* spell);
+	static bool AddLuaSpellTarget(LuaSpell* lua_spell, int32 id, bool lock_spell_targets = true);
 private:
 	Mutex MSpellProcess;
 	MutexMap<Entity*,Spell*> spell_que;

+ 19 - 25
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -7599,10 +7599,8 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 			if(effect->spell && effect->spell_id == spell_id)
 			{
 				safe_delete(lua_spell);
-				effect->spell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
 				if(tmpCaster->GetCurrentZone() == player->GetZone())
-					effect->spell->targets.push_back(client->GetPlayer()->GetID());
-				effect->spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
+					spellProcess->AddLuaSpellTarget(effect->spell, client->GetPlayer()->GetID());
 				lua_spell = effect->spell;
 				spell = effect->spell->spell;
 				isExistingLuaSpell = true;
@@ -7883,7 +7881,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 			if(tmpClient && lua_spell->initial_target_char_id == tmpClient->GetCharacterID())
 			{
 				lua_spell->initial_target = tmpClient->GetPlayer()->GetID();
-				lua_spell->targets.push_back(lua_spell->initial_target);
+				spellProcess->AddLuaSpellTarget(lua_spell, lua_spell->initial_target, false);
 			}
 			
 			spellProcess->ProcessSpell(lua_spell, true, "cast", timer);
@@ -7990,9 +7988,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 
 		LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s using caster %s to reload bonuses for spell %s.", player->GetName(), caster ? caster->GetName() : "?", tmpSpell->spell->GetName());
 		
-		tmpSpell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
-		tmpSpell->targets.push_back(target->GetID());
-		tmpSpell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
+		spellProcess->AddLuaSpellTarget(tmpSpell, target->GetID());
 
 		target->AddSpellEffect(tmpSpell, tmpSpell->timer.GetRemainingTime() != 0 ? tmpSpell->timer.GetRemainingTime() : 0);
 		vector<BonusValues*>* sb_list = caster->GetAllSpellBonuses(tmpSpell);
@@ -8033,31 +8029,29 @@ void WorldDatabase::UpdateStartingLanguage(int32 char_id, uint8 race_id, int32 s
 {
 	int8 rule = rule_manager.GetGlobalRule(R_World, StartingZoneLanguages)->GetInt8();
 	//this should never need to be done but lets make sure we got all the stuff we need. char_id at min since used for both.
-	if(!char_id || rule == 0 && !race_id ||rule == 1 && !starting_city)
+	if(!char_id || (rule == 0 && !race_id) || (rule == 1 && !starting_city))
 		return;
 
+	std::string query_string = "SELECT language_id FROM starting_languages ";
 	//check the rule, and that they gave us a race, if so use default entries to match live.
 	if(rule == 0 && race_id >= 0) {
-		Query query;
-		LogWrite(PLAYER__DEBUG, 0, "Player", "Adding default Languages for race: %i, for char_id: %u. No Custom Used.", race_id, char_id);
-		query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%i,(SELECT language_id FROM starting_languages WHERE race=%i))",char_id, race_id);
-		return;
+		query_string.append("WHERE race=" + std::to_string(race_id));
 	}
 	//if we have a starting city supplied, and the rule is set to use it, deal with it
-	if(rule == 1) {
-		Query query;
-		MYSQL_ROW row;
-		MYSQL_RES* result = query.RunQuery2(Q_SELECT,"SELECT language_id from starting_languages where starting_city=%i",starting_city);
-		LogWrite(PLAYER__DEBUG, 0, "Player", "Adding Custom Languages for starting_city: %i.", starting_city);
-		if (result)	{
-			if (mysql_num_rows(result) > 0)	{
-				while (result && (row = mysql_fetch_row(result))){
-				//add custom languages to the character_languages db.
-					query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%i,%i)",char_id, atoi(row[0]));
-				}
+	else if(rule == 1) {
+		query_string.append("WHERE starting_city=" + std::to_string(starting_city) + " or (starting_city=0 and race_id=" + std::to_string(race_id));
+	}
+	
+	Query query;
+	MYSQL_ROW row;
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT,query_string.c_str());
+	LogWrite(PLAYER__DEBUG, 0, "Player", "Adding Languages for starting_city: %i based on rule R_World:StartingZoneLanguages value %u", starting_city, rule);
+	if (result)	{
+		if (mysql_num_rows(result) > 0)	{
+			while (result && (row = mysql_fetch_row(result))){
+			//add custom languages to the character_languages db.
+				query.RunQuery2(Q_INSERT, "INSERT IGNORE INTO character_languages (char_id, language_id) VALUES (%u,%u)",char_id, atoul(row[0]));
 			}
 		}
-	
 	}
-return;
 }

+ 15 - 6
EQ2/source/WorldServer/zoneserver.cpp

@@ -6762,6 +6762,7 @@ void ZoneServer::PlayAnimation(Spawn* spawn, int32 visual_state, Spawn* spawn2,
 				packet->setDataByName("anim_type", visual_state);
 				client->QueuePacket(packet->serialize());
 			}
+			safe_delete(packet);
 			return;
 		}
 		if(hide_type == 2)
@@ -6777,16 +6778,24 @@ void ZoneServer::PlayAnimation(Spawn* spawn, int32 visual_state, Spawn* spawn2,
 			continue;
 		if(exclude_spawn == client->GetPlayer())
 			continue;
-
-		packet = configReader.getStruct("WS_CannedEmote", client->GetVersion());
-		if (packet) {
-			packet->setDataByName("spawn_id", client->GetPlayer()->GetIDWithPlayerSpawn(spawn));
-			packet->setDataByName("anim_type", visual_state);
-			client->QueuePacket(packet->serialize());
+		if(!client->IsReadyForUpdates()) // client is not in world yet so we shouldn't be sending animations of spawns yet
+			continue;
+		
+		if(!packet || packet->GetVersion() != client->GetVersion()) {
 			safe_delete(packet);
+			packet = configReader.getStruct("WS_CannedEmote", client->GetVersion());
+		}
+		if (packet) {
+			int32 spawn_id = client->GetPlayer()->GetIDWithPlayerSpawn(spawn);
+			if(spawn_id) {
+				packet->setDataByName("spawn_id", spawn_id);
+				packet->setDataByName("anim_type", visual_state);
+				client->QueuePacket(packet->serialize());
+			}
 		}
 	}
 	MClientList.releasereadlock(__FUNCTION__, __LINE__);
+	safe_delete(packet);
 }
 
 vector<Spawn*> ZoneServer::GetSpawnsByID(int32 id) {