Browse Source

Albireo Update #2 (persisting spells cross zone between group members, self pet, and more)

- /spawn details now includes the stat / base stats (STR/AGI/STA/INT/WIS) of an entity (NPC/PET/Player)
- SetInfoStructUInt and GetInfoStructUInt now support interaction_flag, when not set we will default to 12 which is looking at other players nearby.
- Fixed NPCs / Pets having spell bonuses, base stats updated (sta/str/agi/wis/int)
- Deprecated GiveImmediateQuestReward
- With spell persistence (groups, direct target and self pet) added new rule options for cross zone buffs:
	RULE_INIT(R_Spells, EnableCrossZoneGroupBuffs, "0"); // enables/disables allowing cross zone group buffs
	RULE_INIT(R_Spells, EnableCrossZoneTargetBuffs, "0"); // enables/disables allowing cross zone target buffs
Image 3 years ago
parent
commit
3d3ea08dde

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

@@ -4098,6 +4098,16 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 				details3 += "Speed:	" + to_string(spawn->GetSpeed()) + "\n";
 				details3 += "BaseSpeed:	" + to_string(spawn->GetBaseSpeed()) + "\n";
 
+				if(spawn->IsEntity())
+				{
+					Entity* ent = (Entity*)spawn;
+					details3 += "STR / STRBase:	" + to_string(ent->GetInfoStruct()->get_str()) + " / " + to_string(ent->GetInfoStruct()->get_str_base()) + "\n";
+					details3 += "AGI / AGIBase:	" + to_string(ent->GetInfoStruct()->get_agi()) + " / " + to_string(ent->GetInfoStruct()->get_agi_base()) + "\n";
+					details3 += "STA / STABase:	" + to_string(ent->GetInfoStruct()->get_sta()) + " / " + to_string(ent->GetInfoStruct()->get_sta_base()) + "\n";
+					details3 += "INT / INTBase:	" + to_string(ent->GetInfoStruct()->get_intel()) + " / " + to_string(ent->GetInfoStruct()->get_intel_base()) + "\n";
+					details3 += "WIS / WISBase:	" + to_string(ent->GetInfoStruct()->get_wis()) + " / " + to_string(ent->GetInfoStruct()->get_wis_base()) + "\n";
+				}
+
 				string details4;
 				if (spawn->IsEntity()) {
 					details4 += "Facial Hair Type:	" + to_string(((Entity*)spawn)->GetFacialHairType()) + "\n";

+ 32 - 19
EQ2/source/WorldServer/Entity.cpp

@@ -109,7 +109,7 @@ Entity::~Entity(){
 		DeleteSpellEffects();
 }
 
-void Entity::DeleteSpellEffects()
+void Entity::DeleteSpellEffects(bool removeClient)
 {
 	map<LuaSpell*,bool> deletedPtrs;
 
@@ -119,7 +119,7 @@ void Entity::DeleteSpellEffects()
 			{
 				if(deletedPtrs.find(GetInfoStruct()->spell_effects[i].spell) == deletedPtrs.end())
 				{
-					lua_interface->RemoveSpell(GetInfoStruct()->maintained_effects[i].spell, IsPlayer() ? false:  true);
+					lua_interface->RemoveSpell(GetInfoStruct()->maintained_effects[i].spell, false, removeClient, "", removeClient);
 					if (IsPlayer())
 						GetInfoStruct()->maintained_effects[i].icon = 0xFFFF;
 
@@ -132,11 +132,6 @@ void Entity::DeleteSpellEffects()
 		}
 		if(GetInfoStruct()->spell_effects[i].spell_id != 0xFFFFFFFF)
 		{
-			if(deletedPtrs.find(GetInfoStruct()->spell_effects[i].spell) == deletedPtrs.end())
-			{
-				lua_interface->RemoveSpell(GetInfoStruct()->spell_effects[i].spell, IsPlayer() ? false:  true);
-				deletedPtrs[GetInfoStruct()->spell_effects[i].spell] = true;
-			}
 			GetInfoStruct()->spell_effects[i].spell_id = 0xFFFFFFFF;
 			GetInfoStruct()->spell_effects[i].spell = nullptr;
 		}
@@ -289,6 +284,8 @@ void Entity::MapInfoStruct()
 	
 	get_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::get_no_interrupt, &info_struct);
 
+	get_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::get_interaction_flag, &info_struct);
+
 
 /** SETS **/
 	set_string_funcs["name"] = l::bind(&InfoStruct::set_name, &info_struct, l::_1);
@@ -434,6 +431,8 @@ void Entity::MapInfoStruct()
 	
 	set_int8_funcs["no_interrupt"] = l::bind(&InfoStruct::set_no_interrupt, &info_struct, l::_1);
 
+	set_int8_funcs["interaction_flag"] = l::bind(&InfoStruct::set_interaction_flag, &info_struct, l::_1);
+
 }
 
 bool Entity::HasMoved(bool include_heading){
@@ -816,7 +815,7 @@ void Entity::AddMaintainedSpell(LuaSpell* luaspell){
 	}
 }
 
-void Entity::AddSpellEffect(LuaSpell* luaspell){
+void Entity::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){
 	if (!luaspell || !luaspell->caster)
 		return;
 
@@ -827,7 +826,11 @@ void Entity::AddSpellEffect(LuaSpell* luaspell){
 		GetZone()->RemoveTargetFromSpell(old_effect->spell, this);
 		RemoveSpellEffect(old_effect->spell);
 	}
-	effect = GetFreeSpellEffectSlot();
+	
+	LogWrite(SPELL__DEBUG, 0, "Spell", "%s AddSpellEffect %s (%u).", spell->GetName(), GetName(), GetID());
+	
+	if(!effect)
+		effect = GetFreeSpellEffectSlot();
 
 	if(effect){
 		MSpellEffects.writelock(__FUNCTION__, __LINE__);
@@ -837,6 +840,8 @@ void Entity::AddSpellEffect(LuaSpell* luaspell){
 		effect->total_time = spell->GetSpellDuration()/10;
 		if (spell->GetSpellData()->duration_until_cancel)
 			effect->expire_timestamp = 0xFFFFFFFF;
+		else if(override_expire_time)
+			effect->expire_timestamp = Timer::GetCurrentTime2() + override_expire_time;
 		else
 			effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100);
 		effect->icon = spell->GetSpellData()->icon;
@@ -889,6 +894,8 @@ void Entity::RemoveSpellEffect(LuaSpell* spell) {
 			found = true;
 	}
 	if (found) {
+		LogWrite(SPELL__DEBUG, 0, "Spell", "%s RemoveSpellEffect %s (%u).", spell->spell->GetName(), GetName(), GetID());
+		GetZone()->GetSpellProcess()->RemoveTargetFromSpell(spell, this);
 		memset(&GetInfoStruct()->spell_effects[44], 0, sizeof(SpellEffects));
 		GetInfoStruct()->spell_effects[44].spell_id = 0xFFFFFFFF;
 		changed = true;
@@ -1147,11 +1154,11 @@ void Entity::CalculateBonuses(){
 	stats.clear();
 	ItemStatsValues* values = equipment_list.CalculateEquipmentBonuses(this);
 	CalculateSpellBonuses(values);
-	info->add_sta(values->sta);
-	info->add_str(values->str);
-	info->add_agi(values->agi);
-	info->add_wis(values->wis);
-	info->add_intel(values->int_);
+	info->add_sta((float)values->sta);
+	info->add_str((float)values->str);
+	info->add_agi((float)values->agi);
+	info->add_wis((float)values->wis);
+	info->add_intel((float)values->int_);
 
 	info->add_disease(values->vs_disease);
 	info->add_divine(values->vs_divine);
@@ -1433,6 +1440,8 @@ void Entity::AddSpellBonus(LuaSpell* spell, int16 type, float value, int64 class
 	bonus->tier = spell ? spell->spell->GetSpellTier() : 0;
 	bonus_list.Add(bonus);
 
+	if(IsNPC())
+		CalculateBonuses();
 }
 
 BonusValues* Entity::GetSpellBonus(int32 spell_id) {
@@ -1464,6 +1473,9 @@ void Entity::RemoveSpellBonus(const LuaSpell* spell){
 		if(itr.value->luaspell == spell)
 			bonus_list.Remove(itr.value, true);
 	}
+	
+	if(IsNPC())
+		CalculateBonuses();
 }
 
 void Entity::CalculateSpellBonuses(ItemStatsValues* stats){
@@ -1715,7 +1727,8 @@ void Entity::DismissPet(Entity* pet, bool from_death, bool spawnListLocked) {
 	}
 
 	if (pet->GetPetType() == PET_TYPE_CHARMED) {
-		PetOwner->SetCharmedPet(0);
+		if(PetOwner)
+			PetOwner->SetCharmedPet(0);
 
 		if (!from_death) {
 			// set the pet flag to false, owner to 0, and give the mob its old brain back
@@ -1727,15 +1740,15 @@ void Entity::DismissPet(Entity* pet, bool from_death, bool spawnListLocked) {
 			pet->SetDismissing(false);
 		}
 	}
-	else if (pet->GetPetType() == PET_TYPE_COMBAT)
+	else if (PetOwner && pet->GetPetType() == PET_TYPE_COMBAT)
 		PetOwner->SetCombatPet(0);
-	else if (pet->GetPetType() == PET_TYPE_DEITY)
+	else if (PetOwner && pet->GetPetType() == PET_TYPE_DEITY)
 		PetOwner->SetDeityPet(0);
-	else if (pet->GetPetType() == PET_TYPE_COSMETIC)
+	else if (PetOwner && pet->GetPetType() == PET_TYPE_COSMETIC)
 		PetOwner->SetCosmeticPet(0);
 
 	// if owner is player and no combat pets left reset the pet info
-	if (PetOwner->IsPlayer()) {
+	if (PetOwner && PetOwner->IsPlayer()) {
 		if (!PetOwner->GetPet() && !PetOwner->GetCharmedPet())
 			((Player*)PetOwner)->ResetPetInfo();
 	}

+ 12 - 2
EQ2/source/WorldServer/Entity.h

@@ -256,6 +256,8 @@ struct InfoStruct{
 		flying_type_ = 0;
 
 		no_interrupt_ = 0;
+
+		interaction_flag_ = 0;
 	}
 
 
@@ -407,6 +409,8 @@ struct InfoStruct{
 		flying_type_ = oldStruct->get_flying_type();
 
 		no_interrupt_ = oldStruct->get_no_interrupt();
+
+		interaction_flag_ = oldStruct->get_interaction_flag();
 	}
 
 	//mutable std::shared_mutex mutex_;
@@ -569,6 +573,8 @@ struct InfoStruct{
 
 	int8	get_no_interrupt() { std::lock_guard<std::mutex> lk(classMutex); return no_interrupt_; }
 
+	int8	get_interaction_flag() { std::lock_guard<std::mutex> lk(classMutex); return interaction_flag_; }
+
 	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; }
@@ -814,6 +820,8 @@ struct InfoStruct{
 
 	void	set_no_interrupt(int8 value) { std::lock_guard<std::mutex> lk(classMutex); no_interrupt_ = value; }
 
+	void	set_interaction_flag(int8 value) { std::lock_guard<std::mutex> lk(classMutex); interaction_flag_ = value; }
+
 	void	ResetEffects(Spawn* spawn)
 	{
 		for(int i=0;i<45;i++){
@@ -981,6 +989,8 @@ private:
 	int8			flying_type_;
 
 	int8			no_interrupt_;
+
+	int8			interaction_flag_;
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		classMutex;
 };
@@ -1086,11 +1096,11 @@ public:
 	Entity();
 	virtual ~Entity();
 
-	void DeleteSpellEffects();
+	void DeleteSpellEffects(bool removeClient = false);
 	void MapInfoStruct();
 	virtual float GetDodgeChance();
 	virtual void AddMaintainedSpell(LuaSpell* spell);
-	virtual void AddSpellEffect(LuaSpell* spell);
+	virtual void AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0);
 	virtual void RemoveMaintainedSpell(LuaSpell* spell);
 	virtual void RemoveSpellEffect(LuaSpell* spell);
 	virtual bool HasActiveMaintainedSpell(Spell* spell, Spawn* target);

+ 12 - 59
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -349,6 +349,7 @@ int EQ2Emu_lua_SetVisualFlag(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	if (spawn) {
 		spawn->vis_changed = true;
+		spawn->GetZone()->AddChangedSpawn(spawn);
 	}
 	return 0;
 }
@@ -361,6 +362,7 @@ int EQ2Emu_lua_SetInfoFlag(lua_State* state) {
 	Spawn* spawn = lua_interface->GetSpawn(state);
 	if (spawn) {
 		spawn->info_changed = true;
+		spawn->GetZone()->AddChangedSpawn(spawn);
 	}
 	return 0;
 }
@@ -3719,6 +3721,7 @@ int EQ2Emu_lua_AddQuestStepKill(lua_State* state) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
 			quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
 		}
+		safe_delete(ids);
 	}
 	return 0;
 }
@@ -3753,6 +3756,7 @@ int EQ2Emu_lua_AddQuestStepChat(lua_State* state) {
 			if(client)
 				quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
 		}
+		safe_delete(ids);
 	}
 	return 0;
 }
@@ -3787,6 +3791,7 @@ int EQ2Emu_lua_AddQuestStepObtainItem(lua_State* state) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
 			quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
 		}
+		safe_delete(ids);
 	}
 	return 0;
 }
@@ -3893,6 +3898,7 @@ int EQ2Emu_lua_AddQuestStepSpell(lua_State* state) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
 			quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
 		}
+		safe_delete(ids);
 	}
 	return 0;
 }
@@ -3927,6 +3933,7 @@ int EQ2Emu_lua_AddQuestStepCraft(lua_State* state) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
 			quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
 		}
+		safe_delete(ids);
 	}
 	return 0;
 }
@@ -3961,6 +3968,7 @@ int EQ2Emu_lua_AddQuestStepHarvest(lua_State* state) {
 			Client* client = quest->GetPlayer()->GetZone()->GetClientBySpawn(quest->GetPlayer());
 			quest->GetPlayer()->GetZone()->SendQuestUpdates(client);
 		}
+		safe_delete(ids);
 	}
 	return 0;
 }
@@ -4066,61 +4074,6 @@ int EQ2Emu_lua_UpdateQuestZone(lua_State* state) {
 	return 0;
 }
 
-int EQ2Emu_lua_GiveImmediateQuestReward(lua_State* state) {
-	if (!lua_interface)
-		return 0;
-	Quest* quest = lua_interface->GetQuest(state);
-	Spawn* playerSpawn = lua_interface->GetSpawn(state, 2);
-	int32 coin = lua_interface->GetInt32Value(state, 3);
-	int32 status_points = lua_interface->GetInt32Value(state, 4);	
-	string rewards_str = lua_interface->GetStringValue(state, 5);
-	string select_rewards_str = lua_interface->GetStringValue(state, 6);
-	string factions_map_str = lua_interface->GetStringValue(state, 7);
-	string text = lua_interface->GetStringValue(state, 8);
-	if (playerSpawn && playerSpawn->IsPlayer()) {
-		Player* player = (Player*)playerSpawn;
-		Client* client = player->GetZone()->GetClientBySpawn(player);
-		if (client) {
-			vector<Item*> reward_items;
-			vector<Item*> selectable_reward_items;
-			if (rewards_str.length() > 0) {
-				map<unsigned int, unsigned short> rewards = ParseIntMap(rewards_str);
-				map<unsigned int, unsigned short>::iterator itr;
-				for (itr = rewards.begin(); itr != rewards.end(); itr++) {
-					if (itr->first > 0) {
-						Item* item = new Item(master_item_list.GetItem(itr->first));
-						if (item) {
-							if (itr->second > 0)
-								item->details.count = itr->second;
-							reward_items.push_back(item);
-						}
-					}
-				}
-			}
-			if (select_rewards_str.length() > 0) {
-				map<unsigned int, unsigned short> rewards = ParseIntMap(select_rewards_str);
-				map<unsigned int, unsigned short>::iterator itr;
-				for (itr = rewards.begin(); itr != rewards.end(); itr++) {
-					if (itr->first > 0) {
-						Item* item = new Item(master_item_list.GetItem(itr->first));
-						if (item) {
-							if (itr->second > 0)
-								item->stack_count = itr->second;
-							selectable_reward_items.push_back(item);
-						}
-					}
-				}
-			}
-			map<unsigned int, signed int> faction_rewards = ParseSInt32Map(factions_map_str);
-			const char* reward_type = "Quest Reward!";
-			if (!quest)
-				reward_type = "Reward!";
-			client->DisplayQuestRewards(quest, coin, &reward_items, &selectable_reward_items, &faction_rewards, reward_type, status_points, text.c_str());
-		}
-	}
-	return 0;
-}
-
 int EQ2Emu_lua_GiveQuestReward(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -5878,7 +5831,7 @@ int EQ2Emu_lua_AddToWard(lua_State* state) {
 	if (zone->GetSpawnByID(spell->targets.at(0))->IsEntity()) {
 		Entity* target = (Entity*)zone->GetSpawnByID(spell->targets.at(0));
 		ward = target->GetWard(spell->spell->GetSpellID());
-		if (ward) {
+		if (target && ward) {
 			ward->DamageLeft += amount;
 			if (ward->DamageLeft > ward->BaseDamage)
 				ward->DamageLeft = ward->BaseDamage;
@@ -8264,7 +8217,7 @@ int EQ2Emu_lua_SetVision(lua_State* state) {
 		ZoneServer* zone = spell->caster->GetZone();
 		for (int8 i = 0; i < spell->targets.size(); i++) {
 			Spawn* target = zone->GetSpawnByID(spell->targets.at(i));
-			if (target->IsEntity()) {
+			if (target && target->IsEntity()) {
 				((Entity*)target)->GetInfoStruct()->set_vision(vision);
 				if (target->IsPlayer())
 					((Player*)target)->SetCharSheetChanged(true);
@@ -8338,7 +8291,7 @@ int EQ2Emu_lua_BreatheUnderwater(lua_State* state) {
 		ZoneServer* zone = spell->caster->GetZone();
 		for (int8 i = 0; i < spell->targets.size(); i++) {
 			Spawn* target = zone->GetSpawnByID(spell->targets.at(i));
-			if (target->IsEntity()) {
+			if (target && target->IsEntity()) {
 				((Entity*)target)->GetInfoStruct()->set_breathe_underwater(breatheUnderwater);
 				if (target->IsPlayer())
 					((Player*)target)->SetCharSheetChanged(true);
@@ -10959,7 +10912,7 @@ int EQ2Emu_lua_SetAlignment(lua_State* state) {
 		ZoneServer* zone = spell->caster->GetZone();
 		for (int8 i = 0; i < spell->targets.size(); i++) {
 			Spawn* target = zone->GetSpawnByID(spell->targets.at(i));
-			if (target->IsEntity()) {
+			if (target && target->IsEntity()) {
 				((Entity*)target)->GetInfoStruct()->set_alignment((sint8)alignment);
 				if (target->IsPlayer())
 					((Player*)target)->SetCharSheetChanged(true);

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

@@ -273,7 +273,6 @@ int EQ2Emu_lua_AddQuestStepCompleteAction(lua_State* state);
 int EQ2Emu_lua_AddQuestStepProgressAction(lua_State* state);
 int EQ2Emu_lua_SetQuestCompleteAction(lua_State* state);
 int EQ2Emu_lua_GiveQuestReward(lua_State* state);
-int EQ2Emu_lua_GiveImmediateQuestReward(lua_State* state);
 int EQ2Emu_lua_UpdateQuestTaskGroupDescription(lua_State* state);
 int EQ2Emu_lua_UpdateQuestStepDescription(lua_State* state);
 int EQ2Emu_lua_UpdateQuestDescription(lua_State* state);

+ 21 - 1
EQ2/source/WorldServer/LuaInterface.cpp

@@ -22,6 +22,7 @@
 #include "WorldDatabase.h"
 #include "SpellProcess.h"
 #include "../common/Log.h"
+#include "World.h"
 
 #ifndef WIN32
     #include <stdio.h>
@@ -34,6 +35,7 @@
 #endif
 
 extern WorldDatabase database;
+extern ZoneList zone_list;
 
 
 LuaInterface::LuaInterface() {
@@ -277,6 +279,8 @@ bool LuaInterface::LoadLuaSpell(const char* name) {
 		spell->damage_remaining = 0;
 		spell->effect_bitmask = 0;
 		spell->restored = false;
+		spell->initial_caster_char_id = 0;
+		spell->initial_target_char_id = 0;
 
 		MSpells.lock();
 		if (spells.count(lua_script) > 0) {
@@ -751,7 +755,22 @@ void LuaInterface::RemoveSpell(LuaSpell* spell, bool call_remove_function, bool
 			continue;
 
 		((Entity*)target)->RemoveProc(0, spell);
+		((Entity*)target)->RemoveSpellEffect(spell);
+		((Entity*)target)->RemoveSpellBonus(spell);
 	}
+
+	multimap<int32,int8>::iterator entries;
+	for(entries = spell->char_id_targets.begin(); entries != spell->char_id_targets.end(); entries++)
+	{
+		Client* tmpClient = zone_list.GetClientByCharID(entries->first);
+		if(tmpClient && tmpClient->GetPlayer())
+		{
+			tmpClient->GetPlayer()->RemoveProc(0, spell);
+			tmpClient->GetPlayer()->RemoveSpellEffect(spell);
+			tmpClient->GetPlayer()->RemoveSpellBonus(spell);
+		}
+	}
+	spell->char_id_targets.clear(); // TODO: reach out to those clients in different
 	spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 
 	// we need to make sure all memory is purged for a copied spell, its only used once
@@ -1037,7 +1056,6 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "AddQuestStepProgressAction", EQ2Emu_lua_AddQuestStepProgressAction);
 	lua_register(state, "SetQuestCompleteAction", EQ2Emu_lua_SetQuestCompleteAction);
 	lua_register(state, "GiveQuestReward", EQ2Emu_lua_GiveQuestReward);
-	lua_register(state, "GiveImmediateQuestReward", EQ2Emu_lua_GiveImmediateQuestReward);
 	lua_register(state, "UpdateQuestStepDescription", EQ2Emu_lua_UpdateQuestStepDescription);
 	lua_register(state, "UpdateQuestDescription", EQ2Emu_lua_UpdateQuestDescription);
 	lua_register(state, "UpdateQuestZone", EQ2Emu_lua_UpdateQuestZone);
@@ -1783,6 +1801,8 @@ LuaSpell* LuaInterface::GetSpell(const char* name)  {
 		new_spell->initial_target = 0;
 		new_spell->spell = 0;
 		new_spell->restored = false;
+		new_spell->initial_caster_char_id = 0;
+		new_spell->initial_target_char_id = 0;
 		return new_spell;
 	}
 	else{

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

@@ -72,8 +72,11 @@ struct OptionWindowOption {
 
 struct LuaSpell{
 	Entity*			caster;
+	int32			initial_caster_char_id;
 	int32			initial_target;
+	int32			initial_target_char_id;
 	vector<int32>	targets;
+	multimap<int32, int8> char_id_targets;
 	Spell*			spell;
 	lua_State*		state;
 	string			file_name;

+ 47 - 35
EQ2/source/WorldServer/Player.cpp

@@ -1065,8 +1065,7 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("mitigation_max2", info_struct->get_max_mitigation());
 		packet->setDataByName("mitigation_base2", info_struct->get_mitigation_base());
 
-		if (version < 1096)
-			packet->setDataByName("weight", info_struct->get_weight());
+		packet->setDataByName("weight", info_struct->get_weight());
 		packet->setDataByName("max_weight", info_struct->get_max_weight());
 		packet->setDataByName("unknownint32a", 777777);
 		packet->setDataByName("unknownint32b", 666666);
@@ -3126,7 +3125,8 @@ void Player::AddMaintainedSpell(LuaSpell* luaspell){
 		effect->target_type = target_type;
 
 		effect->spell = luaspell;
-		luaspell->slot_pos = effect->slot_pos;
+		if(!luaspell->slot_pos)
+			luaspell->slot_pos = effect->slot_pos;
 		effect->spell_id = spell->GetSpellData()->id;
 		LogWrite(PLAYER__DEBUG, 5, "Player", "AddMaintainedSpell Spell ID: %u, req concentration: %u", spell->GetSpellData()->id, spell->GetSpellData()->req_concentration);
 		effect->icon = spell->GetSpellData()->icon;
@@ -3142,7 +3142,7 @@ void Player::AddMaintainedSpell(LuaSpell* luaspell){
 		charsheet_changed = true;
 	}
 }
-void Player::AddSpellEffect(LuaSpell* luaspell){
+void Player::AddSpellEffect(LuaSpell* luaspell, int32 override_expire_time){
 	if(!luaspell || !luaspell->caster)
 		return;
 
@@ -3163,6 +3163,8 @@ void Player::AddSpellEffect(LuaSpell* luaspell){
 		effect->total_time = spell->GetSpellDuration()/10;
 		if (spell->GetSpellData()->duration_until_cancel)
 			effect->expire_timestamp = 0xFFFFFFFF;
+		else if(override_expire_time)
+			effect->expire_timestamp = Timer::GetCurrentTime2() + override_expire_time;
 		else
 			effect->expire_timestamp = Timer::GetCurrentTime2() + (spell->GetSpellDuration()*100);
 		effect->icon = spell->GetSpellData()->icon;
@@ -6374,17 +6376,18 @@ void Player::SaveSpellEffects()
 	for(int i = 0; i < 45; i++) {
 		if(info->spell_effects[i].spell_id != 0xFFFFFFFF)
 		{
-			Spawn* spawn = GetZone()->GetSpawnByID(info->spell_effects[i].spell->initial_target);
-
+			Spawn* spawn = nullptr;
 			int32 target_char_id = 0;
-			if(spawn && spawn->IsPlayer())
+			if(info->spell_effects[i].spell->initial_target_char_id != 0)
+				target_char_id = info->spell_effects[i].spell->initial_target_char_id;
+			else if((spawn = GetZone()->GetSpawnByID(info->spell_effects[i].spell->initial_target)) != nullptr && spawn->IsPlayer())
 				target_char_id = ((Player*)spawn)->GetCharacterID();
 
 			int32 timestamp = 0xFFFFFFFF;
 			if(!info->spell_effects[i].spell->spell->GetSpellData()->duration_until_cancel)
 				timestamp = info->spell_effects[i].expire_timestamp - Timer::GetCurrentTime2();
 			
-			int32 caster_char_id = (info->spell_effects[i].caster && info->spell_effects[i].caster->IsPlayer()) ? ((Player*)info->spell_effects[i].caster)->GetCharacterID() : 0;
+			int32 caster_char_id = info->spell_effects[i].spell->initial_caster_char_id;
 
 			if(caster_char_id == 0)
 				continue;
@@ -6398,34 +6401,20 @@ void Player::SaveSpellEffects()
 			info->spell_effects[i].total_time, timestamp, database.getSafeEscapeString(info->spell_effects[i].spell->file_name.c_str()).c_str(), info->spell_effects[i].spell->spell->IsCopiedSpell(), GetCharacterID(), 
 			info->spell_effects[i].spell->damage_remaining, info->spell_effects[i].spell->effect_bitmask, info->spell_effects[i].spell->num_triggers, info->spell_effects[i].spell->had_triggers, info->spell_effects[i].spell->cancel_after_all_triggers,
 			info->spell_effects[i].spell->crit, info->spell_effects[i].spell->last_spellattack_hit, info->spell_effects[i].spell->interrupted, info->spell_effects[i].spell->resisted, (info->maintained_effects[i].expire_timestamp) == 0xFFFFFFFF ? "" : database.getSafeEscapeString(spellProcess->SpellScriptTimerCustomFunction(info->spell_effects[i].spell).c_str()).c_str());
-			
-		/*	info->spell_effects[i].spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
-			std::string insertTargets = string("insert into character_spell_effect_targets (caster_char_id, target_char_id, target_type, db_effect_type, spell_id, effect_slot, slot_pos) values ");
-			bool firstTarget = true;
-			for (int8 t = 0; t < info->spell_effects[i].spell->targets.size(); t++) {
-					Spawn* spawn = GetZone()->GetSpawnByID(info->spell_effects[i].spell->targets.at(t));
-					if(spawn && spawn->IsPlayer())
-					{
-						if(!firstTarget)
-							insertTargets.append(", ");
-
-						insertTargets.append("(" + caster_char_id + ", " + target_char_id + ", " + "0" + ", " std::to_string(DB_TYPE_SPELLEFFECTS) + ", " + info->spell_effects[i].spell_id + ", " + i + ", " + info->spell_effects[i].spell->slot_pos + ")");
-						firstTarget = false;
-					}
-			}
-			info->spell_effects[i].spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
-			if(!firstTarget)
-			{
-				Query targetSave;
-				targetSave.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, insertTarget.c_str());
-			}*/
 		}
 		if (i < NUM_MAINTAINED_EFFECTS && info->maintained_effects[i].spell_id != 0xFFFFFFFF){
 			Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->initial_target);
 
 			int32 target_char_id = 0;
-			if(spawn && spawn->IsPlayer())
+		
+			if(info->maintained_effects[i].spell->initial_target_char_id != 0)
+				target_char_id = info->maintained_effects[i].spell->initial_target_char_id;
+			else if(!info->maintained_effects[i].spell->initial_target)
+				target_char_id = GetCharacterID();
+			else if(spawn && spawn->IsPlayer())
 				target_char_id = ((Player*)spawn)->GetCharacterID();
+			else if (spawn && spawn->IsPet() && ((Entity*)spawn)->GetOwner() == (Entity*)this)
+				target_char_id = 0xFFFFFFFF;
 
 			int32 caster_char_id = (info->maintained_effects[i].spell->caster && info->maintained_effects[i].spell->caster->IsPlayer()) ? ((Player*)info->maintained_effects[i].spell->caster)->GetCharacterID() : 0;
 			
@@ -6446,11 +6435,19 @@ void Player::SaveSpellEffects()
 			bool firstTarget = true;
 			map<int32, bool> targetsInserted;
 			for (int8 t = 0; t < info->maintained_effects[i].spell->targets.size(); t++) {
-					Spawn* spawn = GetZone()->GetSpawnByID(info->maintained_effects[i].spell->targets.at(t));
-					if(spawn && spawn->IsPlayer())
+				int32 spawn_id = info->maintained_effects[i].spell->targets.at(t);
+					Spawn* spawn = GetZone()->GetSpawnByID(spawn_id);
+					LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %u to identify for spell %s", GetName(), spawn_id, info->maintained_effects[i].spell->spell->GetName());
+					if(spawn && (spawn->IsPlayer() || spawn->IsPet()))
 					{
-						int32 tmpCharID = ((Player*)spawn)->GetCharacterID();
-
+						int32 tmpCharID = 0;
+						
+						if(spawn->IsPlayer())
+							tmpCharID = ((Player*)spawn)->GetCharacterID();
+						else if (spawn->IsPet() && ((Entity*)spawn)->GetOwner() == (Entity*)this)
+						{
+							tmpCharID = 0xFFFFFFFF;
+						}
 						if(targetsInserted.find(tmpCharID) != targetsInserted.end())
 							continue;
 
@@ -6459,12 +6456,27 @@ void Player::SaveSpellEffects()
 						
 						targetsInserted.insert(make_pair(tmpCharID, true));
 
+
+						LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %s (%u) added to spell %s", GetName(), spawn ? spawn->GetName() : "NA", tmpCharID, info->maintained_effects[i].spell->spell->GetName());
 						insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(tmpCharID) + ", " + "0" + ", " + 
 						std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) + 
-						", " + std::to_string(info->maintained_effects[i].spell->slot_pos) + ")");
+						", " + std::to_string(info->maintained_effects[i].slot_pos) + ")");
 						firstTarget = false;
 					}
 			}
+			multimap<int32,int8>::iterator entries;
+			for(entries = info->maintained_effects[i].spell->char_id_targets.begin(); entries != info->maintained_effects[i].spell->char_id_targets.end(); entries++)
+			{
+				if(!firstTarget)
+					insertTargets.append(", ");
+
+				LogWrite(SPELL__DEBUG, 0, "Spell", "%s has target %s (%u) added to spell %s", GetName(), spawn ? spawn->GetName() : "NA", entries->first, info->maintained_effects[i].spell->spell->GetName());
+				insertTargets.append("(" + std::to_string(caster_char_id) + ", " + std::to_string(entries->first) + ", " + "0" + ", " + 
+				std::to_string(DB_TYPE_MAINTAINEDEFFECTS) + ", " + std::to_string(info->maintained_effects[i].spell_id) + ", " + std::to_string(i) + 
+				", " + std::to_string(info->maintained_effects[i].slot_pos) + ")");
+
+				firstTarget = false;
+			}
 			info->maintained_effects[i].spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 			if(!firstTarget)
 			{

+ 1 - 1
EQ2/source/WorldServer/Player.h

@@ -431,7 +431,7 @@ public:
 		tutorial_step = val;
 	}
 	void	AddMaintainedSpell(LuaSpell* spell);
-	void	AddSpellEffect(LuaSpell* spell);
+	void	AddSpellEffect(LuaSpell* spell, int32 override_expire_time = 0);
 	void	RemoveMaintainedSpell(LuaSpell* spell);
 	void	RemoveSpellEffect(LuaSpell* spell);
 	bool	HasActiveMaintainedSpell(Spell* spell, Spawn* target);

+ 21 - 8
EQ2/source/WorldServer/PlayerGroups.cpp

@@ -24,8 +24,11 @@ along with EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
 #include "Spells.h"
 #include "LuaInterface.h"
 #include "Bots/Bot.h"
+#include "SpellProcess.h"
+#include "Rules/Rules.h"
 
 extern ZoneList	zone_list;
+extern RuleManager rule_manager;
 
 /******************************************************** PlayerGroup ********************************************************/
 
@@ -564,6 +567,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 	PlayerGroup* group = nullptr;
 	Player* caster = nullptr;
 	vector<int32> new_target_list;
+	vector<int32> char_list;
 	Client* client = nullptr;
 	bool has_effect = false;
 	vector<BonusValues*>* sb_list = nullptr;
@@ -614,6 +618,7 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 					(spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE || spell->GetSpellData()->target_type == SPELL_TARGET_RAID_AE)) {
 
 					luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
+					luaspell->char_id_targets.clear();
 
 					for (target_itr = group->GetMembers()->begin(); target_itr != group->GetMembers()->end(); target_itr++) {
 						group_member = (*target_itr)->member;
@@ -632,7 +637,8 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 							has_effect = true;
 
 						// Check if player is within range of the caster
-						if (group_member->GetZone() != caster->GetZone() || caster->GetDistance(group_member) > spell->GetSpellData()->radius) {
+						if (!rule_manager.GetGlobalRule(R_Spells, EnableCrossZoneGroupBuffs)->GetInt8() && 
+								(group_member->GetZone() != caster->GetZone() || caster->GetDistance(group_member) > spell->GetSpellData()->radius)) {
 							if (has_effect) {
 								group_member->RemoveSpellEffect(luaspell);
 								group_member->RemoveSpellBonus(luaspell);
@@ -659,13 +665,20 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 							continue;
 						}
 
-						//this group member is a target of the spell
-						new_target_list.push_back(group_member->GetID());
+						if(group_member->GetZone() != caster->GetZone())
+						{
+							if(group_member->IsPlayer())
+								luaspell->char_id_targets.insert(make_pair(((Player*)group_member)->GetCharacterID(), 0));			
+						}
+						else
+						{
+							//this group member is a target of the spell
+							new_target_list.push_back(group_member->GetID());
+						}
 
 						if (has_effect)
 							continue;
 
-
 						pet = 0;
 						charmed_pet = 0;
 
@@ -674,11 +687,11 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 							charmed_pet = group_member->GetCharmedPet();
 						}
 
-						group_member->AddSpellEffect(luaspell);
+						group_member->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0);
 						if (pet)
-							pet->AddSpellEffect(luaspell);
+							pet->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0);
 						if (charmed_pet)
-							charmed_pet->AddSpellEffect(luaspell);
+							charmed_pet->AddSpellEffect(luaspell, luaspell->timer.GetRemainingTime() != 0 ? luaspell->timer.GetRemainingTime() : 0);
 
 						if (pet)
 							new_target_list.push_back(pet->GetID());
@@ -714,8 +727,8 @@ void PlayerGroupManager::UpdateGroupBuffs() {
 						}
 					}
 
-					new_target_list.push_back(caster->GetID());
 					luaspell->targets.swap(new_target_list);
+					SpellProcess::AddSelfAndPet(luaspell, caster);
 					luaspell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
 					new_target_list.clear();
 				}

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

@@ -328,6 +328,8 @@ void RuleManager::Init()
 	RULE_INIT(R_Spells, DefaultFizzleChance, "10.0"); // default percentage x / 100, eg 10% is 10.0
 	RULE_INIT(R_Spells, FizzleMaxSkill, "1.2"); // 1.0 is 100%, 1.2 is 120%, so you get 120% your max skill against a spell, no fizzle
 	RULE_INIT(R_Spells, FizzleDefaultSkill, ".2"); // offset against MaxSkill to average out to 100%, default of .2f so we don't go over the threshold if no skill
+	RULE_INIT(R_Spells, EnableCrossZoneGroupBuffs, "0"); // enables/disables allowing cross zone group buffs
+	RULE_INIT(R_Spells, EnableCrossZoneTargetBuffs, "0"); // enables/disables allowing cross zone target buffs
 
 	RULE_INIT(R_Expansion, GlobalExpansionFlag, "0");
 	RULE_INIT(R_Expansion, GlobalHolidayFlag, "0");

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

@@ -177,6 +177,8 @@ enum RuleType {
 	DefaultFizzleChance,
 	FizzleMaxSkill,
 	FizzleDefaultSkill,
+	EnableCrossZoneGroupBuffs,
+	EnableCrossZoneTargetBuffs,
 
 	/* ZONE TIMERS */
 	RegenTimer,

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

@@ -2291,8 +2291,13 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	packet->setDataByName("difficulty", appearance.encounter_level); //6);
 	packet->setDataByName("unknown6", 1);
 	packet->setDataByName("heroic_flag", appearance.heroic_flag);
-	if (!IsObject() && !IsGroundSpawn() && !IsWidget() && !IsSign())
-		packet->setDataByName("interaction_flag", 12); //this makes NPCs head turn to look at you
+	if (IsNPC() && !IsPet())
+	{
+		if(((Entity*)this)->GetInfoStruct()->get_interaction_flag())
+			packet->setDataByName("interaction_flag", ((Entity*)this)->GetInfoStruct()->get_interaction_flag()); //this makes NPCs head turn to look at you (12)
+		else
+			packet->setDataByName("interaction_flag", 12); //turn head since no other value is set
+	}
 
 	packet->setDataByName("class", appearance.adventure_class);
 

+ 86 - 27
EQ2/source/WorldServer/SpellProcess.cpp

@@ -392,7 +392,7 @@ bool SpellProcess::DeleteCasterSpell(LuaSpell* spell, string reason, bool removi
 			
 			spell->caster->RemoveProc(0, spell);
 			spell->caster->RemoveMaintainedSpell(spell);
-			CheckRemoveTargetFromSpell(spell, false);
+			CheckRemoveTargetFromSpell(spell, removing_all_spells, removing_all_spells);
 			ZoneServer* zone = spell->caster->GetZone();
 			spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
 			for (int32 i = 0; i < spell->targets.size(); i++) {
@@ -514,8 +514,10 @@ bool SpellProcess::CastInstant(Spell* spell, Entity* caster, Entity* target, boo
 	}
 
 	lua_spell->caster = caster;
+	lua_spell->initial_caster_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; 
 	lua_spell->spell = spell;
 	lua_spell->initial_target = target->GetID();
+	lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
 	GetSpellTargets(lua_spell);
 
 	if (!lua_spell->spell->IsCopiedSpell())
@@ -903,10 +905,12 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 		int8 target_type = spell->GetSpellData()->target_type;
 
 		lua_spell->caster = caster;
+		lua_spell->initial_caster_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0; 
 		lua_spell->spell = spell;
 
 		int32 target_id = target->GetID();
 		lua_spell->initial_target = target_id;
+		lua_spell->initial_target_char_id = (target && target->IsPlayer()) ? ((Player*)target)->GetCharacterID() : 0;
 
 		if (!harvest_spell)
 			GetSpellTargets(lua_spell);
@@ -1036,17 +1040,20 @@ void SpellProcess::ProcessSpell(ZoneServer* zone, Spell* spell, Entity* caster,
 							if(client)
 								UnlockSpell(client, conflictSpell->spell);
 						}
+						DeleteSpell(lua_spell);
 						return;
 					}
 					else if(lua_spell->spell->GetSpellData()->spell_type == SPELL_TYPE_DEBUFF)
 					{
 						SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell);
+						DeleteSpell(lua_spell);
 						return;
 					}
 				}
 				else
 				{
 					SpellCannotStack(zone, client, lua_spell->caster, lua_spell, conflictSpell);
+					DeleteSpell(lua_spell);
 					return;
 				}	
 			}
@@ -1943,6 +1950,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								secondary_target = target;
 								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());
 								GetPlayerGroupTargets((Player*)target, caster, luaspell);
 
@@ -1951,6 +1959,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());
 								GetPlayerGroupTargets((Player*)secondary_target, caster, luaspell);
 							}
@@ -1964,6 +1973,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						if (!target->IsPet() || (target->IsPet() && ((NPC*)target)->GetOwner()->IsNPC())) {
 							target = caster;
 							luaspell->initial_target = caster->GetID();
+							luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0;
 						}
 					}
 				}
@@ -1971,6 +1981,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());
 					}
 				}
@@ -1999,14 +2010,17 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 					{
 						// add self
 						target = NULL;
-						luaspell->targets.push_back(caster->GetID());
-						luaspell->initial_target = 0;
+						AddSelfAndPet(luaspell, caster);
+
+						luaspell->initial_target = caster->GetID();
+						luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0;
 					}
 				}
 				else // default self cast for group/raid AE
 				{
 					target = caster;
 					luaspell->initial_target = caster->GetID();
+					luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0;
 				}
 				// spell target versus self cast
 			}
@@ -2014,11 +2028,13 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 			{
 				target = caster;
 				luaspell->initial_target = caster->GetID();
+				luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0;
 			}
 		}
 		else if (target_type == SPELL_TARGET_SELF){
 			target = caster;
 			luaspell->initial_target = caster->GetID();
+			luaspell->initial_target_char_id = (caster && caster->IsPlayer()) ? ((Player*)caster)->GetCharacterID() : 0;
 		}
 
 		//if using implied target, target = the implied target
@@ -2026,6 +2042,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 		{
 			target = secondary_target;
 			luaspell->initial_target = secondary_target->GetID();
+			luaspell->initial_target_char_id = (secondary_target && secondary_target->IsPlayer()) ? ((Player*)secondary_target)->GetCharacterID() : 0;
 		}
 
 		luaspell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
@@ -2062,15 +2079,27 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								// get group member player info
 								Entity* group_member = (*itr)->member;
 
+								LogWrite(SPELL__DEBUG, 0, "Player", "%s is group member for spell %s", group_member->GetName(), luaspell->spell->GetName());
 								// if the group member is in the casters zone, and is alive
-								if (group_member->GetZone() == luaspell->caster->GetZone() && group_member->Alive()) {
-									luaspell->targets.push_back(group_member->GetID());
-									if (group_member->HasPet()) {
-										Entity* pet = group_member->GetPet();
-										if (!pet)
-											pet = group_member->GetCharmedPet();
-										if (pet)
-											luaspell->targets.push_back(pet->GetID());
+								
+								if( group_member->Alive())
+								{
+									if(group_member->GetZone() != caster->GetZone())
+									{
+										if(group_member->IsPlayer())
+											luaspell->char_id_targets.insert(make_pair(((Player*)group_member)->GetCharacterID(), 0));
+									}
+									else if (group_member->GetZone() == luaspell->caster->GetZone()) {
+										luaspell->targets.push_back(group_member->GetID());
+										if (group_member->HasPet()) {
+											Entity* pet = group_member->GetPet();
+											if (!pet)
+												pet = group_member->GetCharmedPet();
+											if (pet)
+												luaspell->targets.push_back(pet->GetID());
+												
+											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());
+										}
 									}
 								}
 							}
@@ -2080,7 +2109,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
 					}
 					else
-						luaspell->targets.push_back(caster->GetID()); // else caster is not in a group, thus alone
+						AddSelfAndPet(luaspell, caster); // else caster is not in a group, thus alone
 				}
 				else if (caster->IsNPC()) // caster is NOT a player
 				{
@@ -2108,7 +2137,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						}
 					}
 					else
-						luaspell->targets.push_back(caster->GetID());
+						AddSelfAndPet(luaspell, caster);
 
 					safe_delete(group);
 				} // end is player
@@ -2119,10 +2148,7 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 			luaspell->targets.push_back(caster->GetID()); // if spell is SELF, return caster
 
 		else if (target_type == SPELL_TARGET_CASTER_PET && caster && caster->IsEntity() && ((Entity*)caster)->HasPet()) {
-			if (((Entity*)caster)->GetPet())
-				luaspell->targets.push_back(((Entity*)caster)->GetPet()->GetID());
-			if (((Entity*)caster)->GetCharmedPet())
-				luaspell->targets.push_back(((Entity*)caster)->GetCharmedPet()->GetID());
+			AddSelfAndPet(luaspell, caster, true);
 		}
 
 		else if (target_type == SPELL_TARGET_ENEMY && target && target->Alive()) // if target is enemy, and is alive
@@ -2158,13 +2184,13 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 								luaspell->targets.push_back(target->GetID()); // return the target
 							}
 							else
-								luaspell->targets.push_back(caster->GetID()); // else return the caster
+								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
 						else
-							luaspell->targets.push_back(caster->GetID()); // else return the caster
+							AddSelfAndPet(luaspell, caster);
 					}
 					else if (target->IsNPC())
 						luaspell->targets.push_back(target->GetID()); // return target for single spell
@@ -2193,8 +2219,9 @@ void SpellProcess::GetSpellTargets(LuaSpell* luaspell)
 						{
 							Spawn* group_member = *itr;
 
-							// 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
-							if (group_member->IsNPC() && group_member->Alive() && !((Entity*)group_member)->IsAOEImmune() && (!((Entity*)group_member)->IsMezzed() || group_member == target))
+							// 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());
 
 							// note: this should generate some hate towards the caster
@@ -2314,7 +2341,7 @@ void SpellProcess::GetPlayerGroupTargets(Player* target, Spawn* caster, LuaSpell
 						info->client->GetPlayer()->GetZone() == ((Player*)target)->GetZone() && info->client->GetPlayer()->Alive()
 						&& (bypassRangeChecks || caster->GetDistance((Entity*)info->client->GetPlayer()) <= luaspell->spell->GetSpellData()->range))
 					{
-						luaspell->targets.push_back(info->client->GetPlayer()->GetID());
+						AddSelfAndPet(luaspell, info->client->GetPlayer());
 					}
 				}
 				group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
@@ -2328,7 +2355,10 @@ void SpellProcess::GetSpellTargetsTrueAOE(LuaSpell* luaspell) {
 		if (luaspell->caster->HasTarget() && luaspell->caster->GetTarget() != luaspell->caster){
 			//Check if the caster has an implied target
 			if (luaspell->caster->GetDistance(luaspell->caster->GetTarget()) <= luaspell->spell->GetSpellData()->radius)
+			{
 				luaspell->initial_target = luaspell->caster->GetTarget()->GetID();
+				luaspell->initial_target_char_id = (luaspell->caster->GetTarget() && luaspell->caster->GetTarget()->IsPlayer()) ? ((Player*)luaspell->caster->GetTarget())->GetCharacterID() : 0;
+			}
 		}
 		int32 ignore_target = 0;
 		vector<Spawn*> spawns = luaspell->caster->GetZone()->GetAttackableSpawnsByDistance(luaspell->caster, luaspell->spell->GetSpellData()->radius);
@@ -2497,6 +2527,7 @@ void SpellProcess::RemoveTargetFromSpell(LuaSpell* spell, Spawn* target){
 	if (!spell || !target)
 		return;
 
+	LogWrite(SPELL__DEBUG, 0, "Spell", "%s RemoveTargetFromSpell %s (%u).", spell->spell->GetName(), target->GetName(), target->GetID());
 	MRemoveTargetList.writelock(__FUNCTION__, __LINE__);
 
 	if (!remove_target_list[spell])
@@ -2506,7 +2537,7 @@ void SpellProcess::RemoveTargetFromSpell(LuaSpell* spell, Spawn* target){
 	MRemoveTargetList.releasewritelock(__FUNCTION__, __LINE__);
 }
 
-void SpellProcess::CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete){
+void SpellProcess::CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete, bool removing_all_spells){
 	if (!spell)
 		return;
 
@@ -2529,19 +2560,34 @@ void SpellProcess::CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete
 						remove_spawn = spell->caster->GetZone()->GetSpawnByID((*remove_target_itr));
 						if (remove_spawn) {
 							spell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
-							for (target_itr = targets->begin(); target_itr != targets->end(); target_itr++) {
-								((Entity*)remove_spawn)->RemoveProc(0, spell);
-								((Entity*)remove_spawn)->RemoveMaintainedSpell(spell);
 
+							if(remove_spawn && remove_spawn->IsPlayer())
+							{
+								multimap<int32,int8>::iterator entries;
+								while((entries = spell->char_id_targets.find(((Player*)remove_spawn)->GetCharacterID())) != spell->char_id_targets.end())
+								{
+									spell->char_id_targets.erase(entries);
+								}
+							}
+							for (target_itr = targets->begin(); target_itr != targets->end(); target_itr++) {
 								if (remove_spawn->GetID() == (*target_itr)) {
+									((Entity*)remove_spawn)->RemoveProc(0, spell);
+									((Entity*)remove_spawn)->RemoveMaintainedSpell(spell);
+									LogWrite(SPELL__DEBUG, 0, "Spell", "%s CheckRemoveTargetFromSpell %s (%u).", spell->spell->GetName(), remove_spawn->GetName(), remove_spawn->GetID());
 									targets->erase(target_itr);
 									if (remove_spawn->IsEntity())
+									{
+										if(!removing_all_spells && remove_spawn->IsPlayer())
+										{
+											spell->char_id_targets.insert(make_pair(((Player*)remove_spawn)->GetCharacterID(),0));
+										}
 										((Entity*)remove_spawn)->RemoveEffectsFromLuaSpell(spell);
+									}
 									break;
 								}
 							}
 							spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
-							if (targets->size() == 0 && allow_delete) {
+							if (targets->size() == 0 && spell->char_id_targets.size() == 0 && allow_delete) {
 								should_delete = true;
 								break;
 							}
@@ -2688,4 +2734,17 @@ void SpellProcess::AddActiveSpell(LuaSpell* spell)
 {
 	if(!active_spells.count(spell))
 		active_spells.Add(spell);
+}
+
+void SpellProcess::AddSelfAndPet(LuaSpell* spell, Spawn* caster, bool onlyPet)
+{
+	if(!onlyPet)
+		spell->targets.push_back(caster->GetID());
+	
+	if(caster->IsEntity() && ((Entity*)caster)->HasPet() && ((Entity*)caster)->GetPet())
+		spell->targets.push_back(((Entity*)caster)->GetPet()->GetID());
+	if(caster->IsEntity() && ((Entity*)caster)->HasPet() && ((Entity*)caster)->GetCharmedPet())
+		spell->targets.push_back(((Entity*)caster)->GetCharmedPet()->GetID());
+	if(!onlyPet && caster->IsEntity() && ((Entity*)caster)->IsPet() && ((Entity*)caster)->GetOwner())
+		spell->targets.push_back(((Entity*)caster)->GetOwner()->GetID());
 }

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

@@ -363,7 +363,7 @@ public:
 	MutexList<LuaSpell*>* GetActiveSpells() { return &active_spells; }
 
 	void RemoveTargetFromSpell(LuaSpell* spell, Spawn* target);
-	void CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete = true);
+	void CheckRemoveTargetFromSpell(LuaSpell* spell, bool allow_delete = true, bool removing_all_spells = false);
 
 	/// <summary>Adds a solo HO to the SpellProcess</summary>
 	/// <param name='client'>The client who is starting the HO</param>
@@ -388,6 +388,7 @@ public:
 	bool ProcessSpell(LuaSpell* spell, bool first_cast = true, const char* function = 0, SpellScriptTimer* timer = 0);
 
 	void AddActiveSpell(LuaSpell* spell);
+	static void AddSelfAndPet(LuaSpell* spell, Spawn* caster, bool onlyPet=false);
 private:
 	Mutex MSpellProcess;
 	MutexMap<Entity*,Spell*> spell_que;

+ 1 - 1
EQ2/source/WorldServer/World.cpp

@@ -2478,4 +2478,4 @@ Map* World::GetMap(std::string zoneFile, int32 client_version)
 
 	MWorldMaps.releasereadlock();
 	return nullptr;
-}
+}

+ 255 - 46
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -3598,10 +3598,10 @@ void WorldDatabase::UpdateStartingZone(int32 char_id, int8 class_id, int8 race_i
 	int8 deity = create->getType_int8_ByName("deity"); // aka 'alignment' for early DOF, 0 = evil, 1 = good
 	int32 startingZoneRuleFlag = rule_manager.GetGlobalRule(R_World, StartingZoneRuleFlag)->GetInt32();
 	
-	if((startingZoneRuleFlag == 1 || startingZoneRuleFlag == 2) && choice > 1)
+	if((startingZoneRuleFlag == 1 || startingZoneRuleFlag == 2) && packetVersion > 546)
 	{
-		LogWrite(PLAYER__INFO, 0, "Player", "Starting zone rule flag %u override choice %u to deity value of %u", startingZoneRuleFlag, choice, deity);
-		choice = deity; // inherit deity to know starting choice is 'good' or evil
+		LogWrite(PLAYER__INFO, 0, "Player", "Starting zone rule flag %u override choice %u to deity value of 0", startingZoneRuleFlag, choice);
+		choice = 0;
 	}
 	
 	LogWrite(PLAYER__INFO, 0, "Player", "Adding default zone for race: %i, class: %i for char_id: %u (choice: %i), deity(alignment): %u, version: %u.", race_id, class_id, char_id, choice, deity, packetVersion);
@@ -7354,13 +7354,14 @@ int32 WorldDatabase::CreateSpiritShard(const char* name, int32 level, int8 race,
 
 void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int8 db_spell_type) 
 {
-		SpellProcess* spellProcess = client->GetCurrentZone()->GetSpellProcess();
+	SpellProcess* spellProcess = client->GetCurrentZone()->GetSpellProcess();
+	Player* player = client->GetPlayer();
 
 		if(!spellProcess)
 			return;
 	DatabaseResult result;
 
-	Player* player = client->GetPlayer();
+	multimap<LuaSpell*, Entity*> restoreSpells;
 	// Use -1 on type and subtype to turn the enum into an int and make it a 0 index
 	if (!database_new.Select(&result, "SELECT name, caster_char_id, target_char_id, target_type, spell_id, effect_slot, slot_pos, icon, icon_backdrop, conc_used, tier, total_time, expire_timestamp, lua_file, custom_spell, damage_remaining, effect_bitmask, num_triggers, had_triggers, cancel_after_triggers, crit, last_spellattack_hit, interrupted, resisted, custom_function FROM character_spell_effects WHERE charid = %u and db_effect_type = %u", char_id, db_spell_type)) {
 		LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
@@ -7413,9 +7414,9 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 		
 		bool isMaintained = false;
 		bool isExistingLuaSpell = false;
-		MaintainedEffects* effect;
+		MaintainedEffects* effect = nullptr;
 		Client* tmpCaster = nullptr;
-		if(caster_char_id == player->GetCharacterID() && target_char_id == player->GetCharacterID() && (effect = player->GetMaintainedSpell(spell_id)) != nullptr)
+		if(caster_char_id == player->GetCharacterID() && (target_char_id == 0xFFFFFFFF || target_char_id == player->GetCharacterID()) && (effect = player->GetMaintainedSpell(spell_id)) != nullptr)
 		{
 			safe_delete(lua_spell);
 			lua_spell = effect->spell;
@@ -7427,25 +7428,22 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 		else if ( caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr 
 					 && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(spell_id)) != nullptr)
 		{
-			if(tmpCaster->GetCurrentZone() != client->GetCurrentZone())
-			{
-				LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), characters in different zones, cannot assign maintained spell.", spell_id, tier, lua_file.c_str());
-				safe_delete(lua_spell);
-				continue;
-			}
-			else if(effect->spell)
+			if(effect->spell && effect->spell_id == spell_id)
 			{
 				safe_delete(lua_spell);
 				effect->spell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
-				effect->spell->targets.push_back(client->GetPlayer()->GetID());
+				if(tmpCaster->GetCurrentZone() == player->GetZone())
+					effect->spell->targets.push_back(client->GetPlayer()->GetID());
 				effect->spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
 				lua_spell = effect->spell;
 				spell = effect->spell->spell;
 				isExistingLuaSpell = true;
+				isMaintained = true;
+				LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), effect spell id %u maintained spell recovered from %s", spell_id, tier, spell_name, effect ? effect->spell_id : 0, (tmpCaster && tmpCaster->GetPlayer()) ? tmpCaster->GetPlayer()->GetName() : "?");
 			}
 			else
 			{
-				LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), something went wrong loading another characters maintained spell.", spell_id, tier, lua_file.c_str());
+				LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: GetSpell(%u, %u, '%s'), something went wrong loading another characters maintained spell. Effect has spell id %u", spell_id, tier, lua_file.c_str(), effect ? effect->spell_id : 0);
 				safe_delete(lua_spell);
 				continue;
 			}
@@ -7456,7 +7454,7 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 
 			lua_interface->AddCustomSpell(lua_spell);
 		}
-		else
+		else if(db_spell_type == DB_TYPE_MAINTAINEDEFFECTS)
 		{
 			safe_delete(lua_spell);
 			lua_spell = lua_interface->GetSpell(spell->GetSpellData()->lua_script.c_str());
@@ -7483,26 +7481,95 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 			timer->customFunction = string(custom_function); // TODO
 			timer->spell = lua_spell;
 			timer->caster = (caster_char_id == player->GetCharacterID()) ? player->GetID() : 0;
-			timer->target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0;
+			
+			if(target_char_id == 0xFFFFFFFF && player->HasPet())
+				timer->target = player->GetPet()->GetID();
+			else
+				timer->target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0;
+
+			if(!timer->target && target_char_id)
+			{
+				Client* tmpClient = zone_list.GetClientByCharID(target_char_id);
+				if(tmpClient && tmpClient->GetPlayer() && tmpClient->GetPlayer()->GetZone() == player->GetZone())
+					timer->target = tmpClient->GetPlayer()->GetID();
+			}
 		}
 		
-		lua_spell->crit = crit;
-		lua_spell->damage_remaining = damage_remaining;
-		lua_spell->effect_bitmask = effect_bitmask;
-		lua_spell->had_dmg_remaining = (damage_remaining>0) ? true : false;
-		lua_spell->had_triggers = had_triggers;
-		lua_spell->initial_target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0;
-		lua_spell->interrupted = interrupted;
-		lua_spell->last_spellattack_hit = last_spellattack_hit;
-		lua_spell->num_triggers = num_triggers;
+		if(!isExistingLuaSpell)
+		{
+			lua_spell->crit = crit;
+			lua_spell->damage_remaining = damage_remaining;
+			lua_spell->effect_bitmask = effect_bitmask;
+			lua_spell->had_dmg_remaining = (damage_remaining>0) ? true : false;
+			lua_spell->had_triggers = had_triggers;
+			lua_spell->initial_caster_char_id = caster_char_id; 
+			lua_spell->initial_target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0;
+			lua_spell->initial_target_char_id = target_char_id;
+			lua_spell->interrupted = interrupted;
+			lua_spell->last_spellattack_hit = last_spellattack_hit;
+			lua_spell->num_triggers = num_triggers;
+		}
+
+		if(lua_spell->initial_target == 0 && target_char_id == 0xFFFFFFFF && player->HasPet())
+		{
+			lua_spell->initial_target = player->GetPet()->GetID();
+			lua_spell->initial_target_char_id = target_char_id;
+		}
 		//lua_spell->num_calls  ??
 		//if(target_char_id == player->GetCharacterID())
 		//	lua_spell->targets.push_back(player->GetID());
 		
 		if(db_spell_type == DB_TYPE_SPELLEFFECTS)
 		{
+			if (caster_char_id != player->GetCharacterID() && lua_spell->spell->GetSpellData()->group_spell && lua_spell->spell->GetSpellData()->friendly_spell)
+				{
+					if(!isExistingLuaSpell)
+						safe_delete(lua_spell);
+					continue;
+				}
+			
 			player->MSpellEffects.writelock();
-			info->spell_effects[effect_slot].caster = (caster_char_id == player->GetCharacterID()) ? player : 0;
+			LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id);
+
+			if(lua_spell->caster && (rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() || (!rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() && lua_spell->caster->GetZone() == player->GetZone())))
+				info->spell_effects[effect_slot].caster = lua_spell->caster;
+			else if(caster_char_id != player->GetCharacterID())
+			{
+				Client* tmpCaster = zone_list.GetClientByCharID(caster_char_id);
+				if(tmpCaster)
+				{
+					if((rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() || (!rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() && lua_spell->caster->GetZone() == player->GetZone())))
+					{
+						info->spell_effects[effect_slot].caster = tmpCaster->GetPlayer();
+						LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, found player %s.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, tmpCaster->GetPlayer()->GetName());
+					}
+					else
+					{
+						LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, found player %s, SKIPPED due to R_Spells, EnableCrossZoneTargetBuffs.", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, tmpCaster->GetPlayer()->GetName());
+						if(!isExistingLuaSpell)
+						{
+							safe_delete(lua_spell);
+						}
+						else
+						{
+							lua_spell->char_id_targets.insert(make_pair(player->GetCharacterID(),0));
+						}
+						player->MSpellEffects.releasewritelock();
+						continue;
+					}
+
+				}
+			}
+			else if(caster_char_id == player->GetCharacterID())
+				info->spell_effects[effect_slot].caster = player;
+			else
+			{					
+				LogWrite(LUA__ERROR, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s lua_spell caster %s (%u), caster char id: %u, failed to find caster will delete: %u", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, isExistingLuaSpell);
+				if(!isExistingLuaSpell)
+					safe_delete(lua_spell);
+				continue;
+			}
+			
 			info->spell_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp;
 			info->spell_effects[effect_slot].icon = icon;
 			info->spell_effects[effect_slot].icon_backdrop = icon_backdrop;
@@ -7510,11 +7577,24 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 			info->spell_effects[effect_slot].tier = tier;
 			info->spell_effects[effect_slot].total_time = total_time;
 			info->spell_effects[effect_slot].spell = lua_spell;
-			lua_spell->caster = player; // TODO: get actual player
+			printf("SlotPos: %u %s\n",slot_pos,spell->GetName());
+			multimap<int32,int8>::iterator entries;
+			while((entries = lua_spell->char_id_targets.find(player->GetCharacterID())) != lua_spell->char_id_targets.end())
+			{
+				lua_spell->char_id_targets.erase(entries);
+			}
+			lua_spell->slot_pos = slot_pos;
+			if(!isExistingLuaSpell)
+				lua_spell->caster = player; // TODO: get actual player
+
 			player->MSpellEffects.releasewritelock();
 
 			if(!isMaintained)
 				spellProcess->ProcessSpell(lua_spell, true, "cast", timer);
+				else
+				{
+					// track target id when caster isnt in zone somehow
+				}
 		}
 		else if ( db_spell_type == DB_TYPE_MAINTAINEDEFFECTS )
 		{
@@ -7530,18 +7610,59 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 					if(spell_id != in_spell_id)
 						continue;
 					
-					Client* client2 = zone_list.GetClientByCharID(target_char);
+					int32 idToAdd = 0;
+
+					if(target_char == 0xFFFFFFFF)
+					{
+						if( player->HasPet() )
+						{
+							idToAdd = player->GetPet()->GetID();
+							restoreSpells.insert(make_pair(lua_spell, player->GetPet()));
+						}
+					}
+					else
+					{
+						Client* client2 = zone_list.GetClientByCharID(target_char);
+						if(client2 && client2->GetPlayer() && client2->GetCurrentZone() == client->GetCurrentZone())
+						{
+							idToAdd = client2->GetPlayer()->GetID();
+							if(client != client2)
+								restoreSpells.insert(make_pair(lua_spell, client2->GetPlayer()));
+							
+							multimap<int32,int8>::iterator entries;
+							while((entries = lua_spell->char_id_targets.find(target_char)) != lua_spell->char_id_targets.end())
+							{
+								lua_spell->char_id_targets.erase(entries);
+							}
+						}
+						else
+						{
+							lua_spell->char_id_targets.insert(make_pair(target_char,0));
+						}
+					}
+					
+					if(!idToAdd)
+						continue;
+					
 					lua_spell->MSpellTargets.writelock(__FUNCTION__, __LINE__);
-					if(client2 && client2->GetPlayer() && client2->GetCurrentZone() == client->GetCurrentZone())
-						lua_spell->targets.push_back(client2->GetPlayer()->GetID());
+					lua_spell->targets.push_back(idToAdd);
 					lua_spell->MSpellTargets.releasewritelock(__FUNCTION__, __LINE__);
 				}
 			}
-
+			
+			Client* tmpClient = 0;
+			int32 targetID = 0;
+			if(target_char_id == 0xFFFFFFFF && player->HasPet())
+				targetID = player->GetPet()->GetID();
+			else if(target_char_id == player->GetCharacterID())
+				targetID = player->GetID();
+			else if((tmpClient = zone_list.GetClientByCharID(target_char_id)) != nullptr && tmpClient->GetPlayer())
+				targetID = tmpClient->GetPlayer()->GetID();
+			
 			info->maintained_effects[effect_slot].conc_used = conc_used;
 			strncpy(info->maintained_effects[effect_slot].name, spell_name, 60);
 			info->maintained_effects[effect_slot].slot_pos = slot_pos;
-			info->maintained_effects[effect_slot].target = (target_char_id == player->GetCharacterID()) ? player->GetID() : 0;
+			info->maintained_effects[effect_slot].target = targetID;
 			info->maintained_effects[effect_slot].target_type = target_type;
 			info->maintained_effects[effect_slot].expire_timestamp = Timer::GetCurrentTime2() + expire_timestamp;
 			info->maintained_effects[effect_slot].icon = icon;
@@ -7550,9 +7671,18 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 			info->maintained_effects[effect_slot].tier = tier;
 			info->maintained_effects[effect_slot].total_time = total_time;
 			info->maintained_effects[effect_slot].spell = lua_spell;
-			lua_spell->caster = player;
+			if(!isExistingLuaSpell)
+				lua_spell->caster = player;
 			player->MMaintainedSpells.releasewritelock();
-
+		
+			LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s process spell caster %s (%u), caster char id: %u, target id %u (%s).", lua_spell->spell->GetName(), lua_spell->caster ? lua_spell->caster->GetName() : "", 
+											lua_spell->caster ? lua_spell->caster->GetID() : 0, caster_char_id, targetID, tmpClient ? tmpClient->GetPlayer()->GetName() : "");
+			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->ProcessSpell(lua_spell, true, "cast", timer);
 		}
 		if(!isExistingLuaSpell && expire_timestamp != 0xFFFFFFFF && !isMaintained)
@@ -7562,20 +7692,99 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 			lua_spell->timer.Start();
 		}
 
-		if(lua_spell->spell->GetSpellData()->det_type)
+		if(target_char_id == player->GetCharacterID() && lua_spell->spell->GetSpellData()->det_type)
 			player->AddDetrimentalSpell(lua_spell, expire_timestamp);
-
-		if(timer)
-			spellProcess->AddSpellScriptTimer(timer);
 		
-		lua_spell->num_calls = 1;
-		lua_spell->restored = true;
+		if(!isExistingLuaSpell)
+		{
+			if(timer)
+				spellProcess->AddSpellScriptTimer(timer);
+			
+			lua_spell->num_calls = 1;
+			lua_spell->restored = true;
+		}
+		
 		if(!lua_spell->resisted && (lua_spell->spell->GetSpellDuration() > 0 || lua_spell->spell->GetSpellData()->duration_until_cancel))
 				spellProcess->AddActiveSpell(lua_spell);
 
-		if (num_triggers > 0)
-			ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, num_triggers, 0);
-		if (damage_remaining > 0)
-			ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, damage_remaining, 1);
+		if ( db_spell_type == DB_TYPE_MAINTAINEDEFFECTS )
+		{
+			if (num_triggers > 0)
+				ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, num_triggers, 0);
+			if (damage_remaining > 0)
+				ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, damage_remaining, 1);
+		}
 	}
+
+	if(!rule_manager.GetGlobalRule(R_Spells,EnableCrossZoneTargetBuffs)->GetInt8() && db_spell_type == DB_TYPE_SPELLEFFECTS)
+	{
+		DatabaseResult targets;
+		if (database_new.Select(&targets, "SELECT caster_char_id, target_type, spell_id from character_spell_effect_targets where target_char_id = %u", player->GetCharacterID())) {
+			while (targets.Next()) {
+				int32 caster_char_id = targets.GetInt32Str("caster_char_id");
+				int16 target_type = targets.GetInt32Str("target_type");
+				int32 in_spell_id = targets.GetInt32Str("spell_id");
+					Client* tmpCaster = nullptr;
+					MaintainedEffects* effect = nullptr;
+					if ( caster_char_id != player->GetCharacterID() && (tmpCaster = zone_list.GetClientByCharID(caster_char_id)) != nullptr 
+								&& tmpCaster->GetCurrentZone() == player->GetZone() && tmpCaster->GetPlayer() && (effect = tmpCaster->GetPlayer()->GetMaintainedSpell(in_spell_id)) != nullptr)
+					{
+						if(!player->GetSpellEffect(effect->spell_id, tmpCaster->GetPlayer()))
+						{
+							if(effect->spell->initial_target_char_id == player->GetCharacterID())
+								effect->spell->initial_target = player->GetID();
+							restoreSpells.insert(make_pair(effect->spell, player));
+						}
+					}
+				}
+			}
+	}
+	
+	multimap<LuaSpell*, Entity*>::const_iterator itr;
+
+	for (itr = restoreSpells.begin(); itr != restoreSpells.end(); itr++)
+	{
+		LuaSpell* tmpSpell = itr->first;
+		Entity* target = itr->second;
+		if(!target)
+		{
+			target = client->GetPlayer()->GetPet();
+			if(!target)
+				continue;
+		}
+
+		Entity* caster = tmpSpell->caster;
+		if(!caster)
+			caster = client->GetPlayer();
+		
+		if(caster != target && caster->GetPet() != target && 
+			tmpSpell->spell->GetSpellData()->group_spell && tmpSpell->spell->GetSpellData()->friendly_spell && (caster->group_id == 0 || target->group_id == 0 || caster->group_id != target->group_id))
+		{
+			LogWrite(LUA__WARNING, 0, "LUA", "WorldDatabase::LoadCharacterSpellEffects: %s player no longer grouped with %s to reload bonuses for spell %s.", target->GetName(), caster ? caster->GetName() : "?", tmpSpell->spell->GetName());
+			continue;
+		}
+
+		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());
+		
+		target->AddSpellEffect(tmpSpell, tmpSpell->timer.GetRemainingTime() != 0 ? tmpSpell->timer.GetRemainingTime() : 0);
+		vector<BonusValues*>* sb_list = caster->GetAllSpellBonuses(tmpSpell);
+		for (int32 x = 0; x < sb_list->size(); x++) {
+			BonusValues* bv = sb_list->at(x);
+			target->AddSpellBonus(tmpSpell, bv->type, bv->value, bv->class_req, bv->race_req, bv->faction_req);
+		}
+		sb_list->clear();
+		safe_delete(sb_list);
+		// look for a skill bonus on the caster's spell
+		if(caster->IsPlayer())
+		{
+			SkillBonus* sb = ((Player*)caster)->GetSkillBonus(tmpSpell->spell->GetSpellID());
+			if (sb) {
+				map<int32, SkillBonusValue*>::iterator itr_skills;
+				for (itr_skills = sb->skills.begin(); itr_skills != sb->skills.end(); itr_skills++)
+				target->AddSkillBonus(sb->spell_id, (*itr_skills).second->skill_id, (*itr_skills).second->value);
+			}
+		}
+
+	}
+
 }

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

@@ -3018,6 +3018,8 @@ void ZoneServer::RemoveClient(Client* client)
 		LogWrite(MISC__TODO, 1, "TODO", "Put Player Online Status updates in a timer eventually\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
 		database.ToggleCharacterOnline(client, 0);
 		
+		client->GetPlayer()->DeleteSpellEffects(true);
+		
 		RemoveSpawn(client->GetPlayer(), false);
 		connected_clients.Remove(client, true, DisconnectClientTimer); // changed from a hardcoded 30000 (30 sec) to the DisconnectClientTimer rule
 	}