Browse Source

Login Updates:
- LoginServer will no longer reuse character cache for Isle of Refuge, KoS, DoF clients. This should assure we only give a fresh set of characters on login.
- Temporary workaround to just put characters as deleted/offline in older clients (KoS and earlier). Have to revisit Issue #526 at a later time.

World Updates:
- Fix #216 AddProcExt(Spawn, type, damage_type, chance, hp_ratio, below_health, target_health, item, use_all_spell_targets)
hp_ratio is a unsigned integer 0 = disabled, 1+ enabled. below_health = true means we check health hp_ratio, otherwise we check equal to or above. target_health = true means we check the targets health, not ourselves

which calls in a spell script:
function proc_ext(Caster, Spawn, type, damage_type, SpellDataArguments)

- Fixed aggro not clearing on a spawn when a player leaves zone
- Fixed spell avoidance properly using the victim/targets skill, not the attackers.
- Fix #194 ITEM_STAT_BASEAVOIDANCEBONUS implemented. Type 1 item stat ITEM_STAT_SPELL_AVOIDANCE added (134) will map to text "spell avoidance" for item stat.
- Fix #569 support for DoF and KoS clients to use the goblin gambler. Isle of Refuge is March 2005, too early to support (Oct 2005 feature).
insert into opcodes set version_range1=540,version_range2=546,name='OP_Lottery',opcode=495,table_data_version=1;
insert into opcodes set version_range1=560,version_range2=561,name='OP_Lottery',opcode=503,table_data_version=1;
* Disabled for Isle of Refuge client, feature was not supported until Oct 2005.

- KoS and earlier clients: hardcoded goblin model from 7039 -> 145 (the newer model doesn't exist in these clients). Helps you see the goblin gambler.
- Isle of Refuge /who reply struct now mostly accurate (doesn't display commoner, shows unskilled like tradeskill instead..)

Stats:
- Intelligence now increases spell damage
- Wisdom now increases chance to resist (if target) or chance to land (if caster)
- Agility previously increased dodge skill, now 2x more effective
- Consolidated HP/Power defaults for players so we can modify via new rules
RULE_INIT(R_Player, StartHPBase, "40");
RULE_INIT(R_Player, StartPowerBase, "45");
RULE_INIT(R_Player, StartHPLevelMod, "2.0");
RULE_INIT(R_Player, StartPowerLevelMod, "2.1");

Emagi 3 months ago
parent
commit
f5685e3918

+ 8 - 3
EQ2/source/LoginServer/LoginAccount.cpp

@@ -37,7 +37,7 @@ CharSelectProfile* LoginAccount::getCharacter(char* name){
 	}
 	}
 	return 0;
 	return 0;
 }
 }
-void LoginAccount::removeCharacter(char* name){
+void LoginAccount::removeCharacter(char* name, int16 version){
 	vector<CharSelectProfile*>::iterator iter;
 	vector<CharSelectProfile*>::iterator iter;
 	CharSelectProfile* profile = 0;
 	CharSelectProfile* profile = 0;
 	EQ2_16BitString temp;
 	EQ2_16BitString temp;
@@ -45,8 +45,13 @@ void LoginAccount::removeCharacter(char* name){
 		profile = *iter;
 		profile = *iter;
 		temp = profile->packet->getType_EQ2_16BitString_ByName("name");
 		temp = profile->packet->getType_EQ2_16BitString_ByName("name");
 		if(strcmp(temp.data.c_str(), name)==0){
 		if(strcmp(temp.data.c_str(), name)==0){
-			safe_delete(*iter);
-			charlist.erase(iter);
+			if(version <= 561) {
+				profile->deleted = true; // workaround for char select crash on old clients
+			}
+			else {
+				safe_delete(*iter);
+				charlist.erase(iter);
+			}
 			return;
 			return;
 		}
 		}
 	}
 	}

+ 1 - 1
EQ2/source/LoginServer/LoginAccount.h

@@ -34,7 +34,7 @@ public:
 		charlist.push_back(profile);
 		charlist.push_back(profile);
 	}
 	}
 	void removeCharacter(PacketStruct* profile);
 	void removeCharacter(PacketStruct* profile);
-	void removeCharacter(char* name);
+	void removeCharacter(char* name, int16 version);
 	void serializeCharacter(uchar* buffer, CharSelectProfile* profile);
 	void serializeCharacter(uchar* buffer, CharSelectProfile* profile);
 
 
 	void flushCharacters ( );
 	void flushCharacters ( );

+ 11 - 1
EQ2/source/LoginServer/PacketHeaders.cpp

@@ -64,7 +64,17 @@ void LS_CharSelectList::loadData(int32 account, vector<CharSelectProfile*> charl
 	for(itr = charlist.begin();itr != charlist.end();itr++){
 	for(itr = charlist.begin();itr != charlist.end();itr++){
 		character = *itr;
 		character = *itr;
 		int32 serverID = character->packet->getType_int32_ByName("server_id");
 		int32 serverID = character->packet->getType_int32_ByName("server_id");
-		if(serverID == 0 || !world_list.FindByID(serverID))
+		if(character->deleted) { // workaround for old clients <= 561 that crash if you delete a char (Doesn't refresh the char panel correctly)
+			character->packet->setDataByName("name", "(deleted)");
+			character->packet->setDataByName("charid", 0xFFFFFFFF);
+			character->packet->setDataByName("name", 0xFFFFFFFF);
+			character->packet->setDataByName("server_id", 0xFFFFFFFF);
+			character->packet->setDataByName("created_date", 0xFFFFFFFF);
+			character->packet->setDataByName("unknown1", 0xFFFFFFFF);
+			character->packet->setDataByName("unknown2", 0xFFFFFFFF);
+			character->packet->setDataByName("flags", 0xFF);
+		}
+		else if(serverID == 0 || !world_list.FindByID(serverID))
 			continue;
 			continue;
 		num_characters++;		
 		num_characters++;		
 		character->SaveData(version);
 		character->SaveData(version);

+ 4 - 3
EQ2/source/LoginServer/client.cpp

@@ -374,7 +374,7 @@ bool Client::Process() {
 					int32 server_id = request->getType_int32_ByName("server_id");
 					int32 server_id = request->getType_int32_ByName("server_id");
 					if(database.VerifyDelete(acct_id, char_id, name.data.c_str())){
 					if(database.VerifyDelete(acct_id, char_id, name.data.c_str())){
 						response->setDataByName("response", 1);
 						response->setDataByName("response", 1);
-						GetLoginAccount()->removeCharacter((char*)name.data.c_str());
+						GetLoginAccount()->removeCharacter((char*)name.data.c_str(), GetVersion());
 						LWorld* world_server = world_list.FindByID(server_id);
 						LWorld* world_server = world_list.FindByID(server_id);
 						if(world_server != NULL)
 						if(world_server != NULL)
 							world_server->SendDeleteCharacter ( char_id , acct_id );
 							world_server->SendDeleteCharacter ( char_id , acct_id );
@@ -389,7 +389,7 @@ bool Client::Process() {
 
 
 					EQ2Packet* outapp = response->serialize();
 					EQ2Packet* outapp = response->serialize();
 					QueuePacket(outapp);
 					QueuePacket(outapp);
-
+					
 					this->SendCharList();
 					this->SendCharList();
 				}
 				}
 				safe_delete(request);
 				safe_delete(request);
@@ -651,6 +651,7 @@ void Client::SendWorldList(){
 	EQ2Packet* dupe = pack->Copy();
 	EQ2Packet* dupe = pack->Copy();
 	DumpPacket(dupe->pBuffer,dupe->size);
 	DumpPacket(dupe->pBuffer,dupe->size);
 	QueuePacket(dupe);
 	QueuePacket(dupe);
+
 	SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags
 	SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags
 	return;
 	return;
 }
 }
@@ -670,7 +671,7 @@ void Client::WorldResponse(int32 worldid, int8 response, char* ip_address, int32
 		if(response == PLAY_ERROR_CHAR_NOT_LOADED){
 		if(response == PLAY_ERROR_CHAR_NOT_LOADED){
 			string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID());
 			string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID());
 			if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())){
 			if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())){
-				GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str());
+				GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion());
 			}
 			}
 		}
 		}
 		FatalError(response);
 		FatalError(response);

+ 39 - 13
EQ2/source/WorldServer/Combat.cpp

@@ -798,6 +798,9 @@ int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHi
 	Entity* entity_victim = (Entity*)victim;
 	Entity* entity_victim = (Entity*)victim;
 	float chance = 80 + bonus; //80% base chance that the victim will get hit (plus bonus)
 	float chance = 80 + bonus; //80% base chance that the victim will get hit (plus bonus)
 	sint16 roll_chance = 100;
 	sint16 roll_chance = 100;
+	if(is_caster_spell) {
+		chance += CalculateLevelStatBonus(GetWis());
+	}
 	if(skill)
 	if(skill)
 		roll_chance -= skill->current_val / 10;
 		roll_chance -= skill->current_val / 10;
 
 
@@ -874,6 +877,7 @@ int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHi
 
 
 		skill = entity_victim->GetSkillByName("Defense", true);
 		skill = entity_victim->GetSkillByName("Defense", true);
 
 
+		// calculated in Entity::CalculateBonuses
 		float dodgeChance = entity_victim->GetInfoStruct()->get_avoidance_base();
 		float dodgeChance = entity_victim->GetInfoStruct()->get_avoidance_base();
 		if(dodgeChance > 0.0f)
 		if(dodgeChance > 0.0f)
 		{
 		{
@@ -888,11 +892,11 @@ int8 Entity::DetermineHit(Spawn* victim, int8 type, int8 damage_type, float ToHi
 			return DAMAGE_PACKET_RESULT_MISS; //successfully avoided
 			return DAMAGE_PACKET_RESULT_MISS; //successfully avoided
 	}
 	}
 	else{
 	else{
-		skill = entity_victim->GetSkillByName("Spell Avoidance", true);
-		if(skill)
-			chance -= skill->current_val / 10;
-
-		LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellAvoidChance: fchance %f", chance);
+		float focus_skill_with_bonus = entity_victim->CalculateSkillWithBonus("Spell Avoidance", ITEM_STAT_SPELL_AVOIDANCE, true);
+		int16 effective_level = entity_victim->GetInfoStruct()->get_effective_level() != 0 ? entity_victim->GetInfoStruct()->get_effective_level() : entity_victim->GetLevel();
+		focus_skill_with_bonus += entity_victim->CalculateLevelStatBonus(entity_victim->GetWis());
+		chance -= ((focus_skill_with_bonus)/10);
+		LogWrite(COMBAT__DEBUG, 9, "Combat", "SpellAvoidChance: fchance %f, focus with skill bonus %f", chance, focus_skill_with_bonus);
 		if(rand()%roll_chance >= chance) {
 		if(rand()%roll_chance >= chance) {
 			return DAMAGE_PACKET_RESULT_RESIST; //successfully resisted	
 			return DAMAGE_PACKET_RESULT_RESIST; //successfully resisted	
 		}
 		}
@@ -1599,7 +1603,7 @@ float Entity::CalculateAttackSpeedMod(){
 	return 1;
 	return 1;
 }
 }
 
 
-void Entity::AddProc(int8 type, float chance, Item* item, LuaSpell* spell) {
+void Entity::AddProc(int8 type, float chance, Item* item, LuaSpell* spell, int8 damage_type, int8 hp_ratio, bool below_health, bool target_health, bool extended_version) {
 	if (type == 0) {
 	if (type == 0) {
 		LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::AddProc called with an invalid type.");
 		LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::AddProc called with an invalid type.");
 		return;
 		return;
@@ -1616,6 +1620,11 @@ void Entity::AddProc(int8 type, float chance, Item* item, LuaSpell* spell) {
 	proc->item = item;
 	proc->item = item;
 	proc->spell = spell;
 	proc->spell = spell;
 	proc->spellid = spell->spell->GetSpellID();
 	proc->spellid = spell->spell->GetSpellID();
+	proc->health_ratio = hp_ratio;
+	proc->below_health = below_health;
+	proc->damage_type = damage_type;
+	proc->target_health = target_health;
+	proc->extended_version = extended_version;
 	m_procList[type].push_back(proc);
 	m_procList[type].push_back(proc);
 	MProcList.releasewritelock(__FUNCTION__, __LINE__);
 	MProcList.releasewritelock(__FUNCTION__, __LINE__);
 }
 }
@@ -1663,8 +1672,12 @@ bool Entity::CastProc(Proc* proc, int8 type, Spawn* target) {
 		return false;
 		return false;
 	}
 	}
 
 
-	lua_getglobal(state, "proc");
-
+	if(proc->extended_version) {
+		lua_getglobal(state, "proc_ext");
+	}
+	else {
+		lua_getglobal(state, "proc");
+	}
 	if (item_proc) {
 	if (item_proc) {
 		num_args++;
 		num_args++;
 		lua_interface->SetItemValue(state, proc->item);
 		lua_interface->SetItemValue(state, proc->item);
@@ -1673,6 +1686,7 @@ bool Entity::CastProc(Proc* proc, int8 type, Spawn* target) {
 	lua_interface->SetSpawnValue(state, this);
 	lua_interface->SetSpawnValue(state, this);
 	lua_interface->SetSpawnValue(state, target);
 	lua_interface->SetSpawnValue(state, target);
 	lua_interface->SetInt32Value(state, type);
 	lua_interface->SetInt32Value(state, type);
+	lua_interface->SetInt32Value(state, proc->damage_type);
 
 
 	/*
 	/*
 	Add spell data from db in case of a spell proc here...
 	Add spell data from db in case of a spell proc here...
@@ -1722,20 +1736,31 @@ void Entity::CheckProcs(int8 type, Spawn* target) {
 		return;
 		return;
 	}
 	}
 
 
-	float roll = MakeRandomFloat(0, 100);
-
 	vector<Proc*> tmpList;
 	vector<Proc*> tmpList;
 
 
 	MProcList.readlock(__FUNCTION__, __LINE__);
 	MProcList.readlock(__FUNCTION__, __LINE__);
 	for (int8 i = 0; i < m_procList[type].size(); i++) {
 	for (int8 i = 0; i < m_procList[type].size(); i++) {
+		// roll per proc, not overall
+		float roll = MakeRandomFloat(0, 100);
 		Proc* proc = m_procList[type].at(i);
 		Proc* proc = m_procList[type].at(i);
-		if (roll <= proc->chance)
+		if (roll <= proc->chance && 
+			((!proc->extended_version || proc->health_ratio == 0) || 
+			(proc->below_health && !proc->target_health && proc->health_ratio < (int8)GetIntHPRatio()) ||
+			(!proc->below_health && !proc->target_health && proc->health_ratio >= (int8)GetIntHPRatio()) ||
+			(target && proc->below_health && proc->target_health && proc->health_ratio < (int8)target->GetIntHPRatio()) ||
+			(target && !proc->below_health && proc->target_health && proc->health_ratio >= (int8)target->GetIntHPRatio()))
+			)
 		{
 		{
 			Proc* tmpProc = new Proc();
 			Proc* tmpProc = new Proc();
 			tmpProc->chance = proc->chance;
 			tmpProc->chance = proc->chance;
 			tmpProc->item = proc->item;
 			tmpProc->item = proc->item;
 			tmpProc->spell = proc->spell;
 			tmpProc->spell = proc->spell;
 			tmpProc->spellid = proc->spellid;
 			tmpProc->spellid = proc->spellid;
+			tmpProc->damage_type = proc->damage_type;
+			tmpProc->health_ratio = proc->health_ratio;
+			tmpProc->below_health = proc->below_health;
+			tmpProc->target_health = proc->target_health;
+			tmpProc->extended_version = proc->extended_version;
 			tmpList.push_back(tmpProc);
 			tmpList.push_back(tmpProc);
 		}
 		}
 	}
 	}
@@ -1834,7 +1859,7 @@ sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_typ
 			Spell double cast: A straight damage modifier, the more you can get the better. You won't be able to get very much of this.
 			Spell double cast: A straight damage modifier, the more you can get the better. You won't be able to get very much of this.
 			Makes the spell cast twice with some limitations.
 			Makes the spell cast twice with some limitations.
 		**/
 		**/
-
+		damage = damage + damage * CalculateLevelStatBonus(GetInt()); // lvl 70 * 1200 int = 84000, log10f(84000) = 4.924 / 50.0f = 0.09848
 		damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_DAMAGE);
 		damage = CalculateFormulaByStat(damage, ITEM_STAT_SPELL_DAMAGE);
 	}
 	}
 
 
@@ -1855,8 +1880,9 @@ sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_typ
 	}
 	}
 
 
 	// combat abilities only bonus
 	// combat abilities only bonus
-	if(damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE)
+	if(damage_type <= DAMAGE_PACKET_DAMAGE_TYPE_PIERCE) {
 		damage = CalculateFormulaByStat(damage, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE);
 		damage = CalculateFormulaByStat(damage, ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE);
+	}
 			
 			
 	// Potency mod
 	// Potency mod
 	damage = CalculateFormulaByStat(damage, ITEM_STAT_POTENCY);
 	damage = CalculateFormulaByStat(damage, ITEM_STAT_POTENCY);

+ 9 - 4
EQ2/source/WorldServer/Entity.cpp

@@ -1617,14 +1617,13 @@ void Entity::CalculateBonuses(){
 
 
 	MStats.lock();
 	MStats.lock();
 	float defenseStat = stats[ITEM_STAT_DEFENSE];
 	float defenseStat = stats[ITEM_STAT_DEFENSE];
+	float baseAvoidanceStat = stats[ITEM_STAT_BASEAVOIDANCEBONUS];
 	MStats.unlock();
 	MStats.unlock();
 	
 	
-	float dodge_pct = CalculateSkillStatChance("Defense", ITEM_STAT_DODGECHANCE, 100.0f, defenseStat);
+	float dodge_pct = (baseAvoidanceStat/100.0f) + CalculateSkillStatChance("Defense", ITEM_STAT_DODGECHANCE, 100.0f, defenseStat);
 	dodge_pct += dodge_pct * (info->get_cur_avoidance()/100.0f);
 	dodge_pct += dodge_pct * (info->get_cur_avoidance()/100.0f);
 
 
-	float dodge_actual = 0.0f;
-	if(full_pct_hit > 0.0f)
-		dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + (log10f(effective_level * GetAgi()) / 100.0f);
+	float dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + CalculateLevelStatBonus(GetAgi());
 
 
 	info->set_avoidance_base(dodge_actual);
 	info->set_avoidance_base(dodge_actual);
 
 
@@ -1640,6 +1639,12 @@ void Entity::CalculateBonuses(){
 	safe_delete(values);
 	safe_delete(values);
 }
 }
 
 
+float Entity::CalculateLevelStatBonus(int16 stat_value) {
+	int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
+	float result = (log10f(effective_level * stat_value) / 50.0f); // todo: break this down by stat type and give independent modifiers
+	return result;
+}
+
 void Entity::CalculateApplyWeight() {
 void Entity::CalculateApplyWeight() {
 	if (IsPlayer()) {
 	if (IsPlayer()) {
 		int32 prev_weight = GetInfoStruct()->get_weight();
 		int32 prev_weight = GetInfoStruct()->get_weight();

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

@@ -1298,6 +1298,11 @@ struct Proc {
 	Item*		item;
 	Item*		item;
 	float		chance;
 	float		chance;
 	int32		spellid;
 	int32		spellid;
+	int8		health_ratio;
+	bool		below_health;
+	bool		target_health;
+	int8		damage_type;
+	bool		extended_version;
 };
 };
 
 
 #define PROC_TYPE_OFFENSIVE				1
 #define PROC_TYPE_OFFENSIVE				1
@@ -1396,6 +1401,7 @@ public:
 	float 	CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase);
 	float 	CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase);
 	float 	GetRuleSkillMaxBonus();
 	float 	GetRuleSkillMaxBonus();
 	void 	CalculateBonuses();
 	void 	CalculateBonuses();
+	float	CalculateLevelStatBonus(int16 stat_value);
 	void 	CalculateApplyWeight();
 	void 	CalculateApplyWeight();
 	void 	SetRegenValues(int16 effective_level);
 	void 	SetRegenValues(int16 effective_level);
 	float 	CalculateBonusMod();
 	float 	CalculateBonusMod();
@@ -1818,7 +1824,7 @@ public:
 	/// <param name='chance'>The percent chance the proc has to go off</param>
 	/// <param name='chance'>The percent chance the proc has to go off</param>
 	/// <param name='item'>The item the proc is coming from if any</param>
 	/// <param name='item'>The item the proc is coming from if any</param>
 	/// <param name='spell'>The spell the proc is coming from if any</param>
 	/// <param name='spell'>The spell the proc is coming from if any</param>
-	void AddProc(int8 type, float chance, Item* item = 0, LuaSpell* spell = 0);
+	void AddProc(int8 type, float chance, Item* item = 0, LuaSpell* spell = 0, int8 damage_type = 0, int8 hp_ratio = 0, bool below_health = false, bool target_health = false, bool extended_version = false);
 
 
 	/// <summary>Removes a proc from the list of current procs</summary>
 	/// <summary>Removes a proc from the list of current procs</summary>
 	/// <param name='item'>Item the proc is from</param>
 	/// <param name='item'>Item the proc is from</param>

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

@@ -78,6 +78,7 @@ MasterItemList::MasterItemList(){
 	AddMappedItemStat(ITEM_STAT_TRAPPING, std::string("trapping"));
 	AddMappedItemStat(ITEM_STAT_TRAPPING, std::string("trapping"));
 	AddMappedItemStat(ITEM_STAT_WEAPON_SKILLS, std::string("weapon skills"));
 	AddMappedItemStat(ITEM_STAT_WEAPON_SKILLS, std::string("weapon skills"));
 	AddMappedItemStat(ITEM_STAT_POWER_COST_REDUCTION, std::string("power cost reduction"));
 	AddMappedItemStat(ITEM_STAT_POWER_COST_REDUCTION, std::string("power cost reduction"));
+	AddMappedItemStat(ITEM_STAT_SPELL_AVOIDANCE, std::string("spell avoidance"));
 }
 }
 
 
 void MasterItemList::AddMappedItemStat(int32 id, std::string lower_case_name)
 void MasterItemList::AddMappedItemStat(int32 id, std::string lower_case_name)

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

@@ -413,6 +413,7 @@ extern MasterItemList master_item_list;
 #define ITEM_STAT_TRAPPING  			131
 #define ITEM_STAT_TRAPPING  			131
 #define ITEM_STAT_WEAPON_SKILLS			132
 #define ITEM_STAT_WEAPON_SKILLS			132
 #define ITEM_STAT_POWER_COST_REDUCTION	133
 #define ITEM_STAT_POWER_COST_REDUCTION	133
+#define ITEM_STAT_SPELL_AVOIDANCE		134
 
 
 #define ITEM_STAT_VS_PHYSICAL			200
 #define ITEM_STAT_VS_PHYSICAL			200
 #define ITEM_STAT_VS_HEAT				201 //elemental
 #define ITEM_STAT_VS_HEAT				201 //elemental

+ 57 - 1
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -8500,6 +8500,9 @@ int EQ2Emu_lua_AddProc(lua_State* state) {
 	bool use_all_spelltargets = (lua_interface->GetInt8Value(state, 5) == 1);
 	bool use_all_spelltargets = (lua_interface->GetInt8Value(state, 5) == 1);
 	LuaSpell* spell = 0;
 	LuaSpell* spell = 0;
 
 
+	if (!item)
+		spell = lua_interface->GetCurrentSpell(state);
+	
 	if (!spawn && (!spell || !use_all_spelltargets)) {
 	if (!spawn && (!spell || !use_all_spelltargets)) {
 		lua_interface->LogError("%s: LUA AddProc command error: spawn is not valid", lua_interface->GetScriptName(state));
 		lua_interface->LogError("%s: LUA AddProc command error: spawn is not valid", lua_interface->GetScriptName(state));
 		return 0;
 		return 0;
@@ -8510,9 +8513,62 @@ int EQ2Emu_lua_AddProc(lua_State* state) {
 		return 0;
 		return 0;
 	}
 	}
 
 
+	if (!item && !spell) {
+		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 && spell->caster && spell->caster->GetZone() && use_all_spelltargets) {
+		Spawn* target;
+		spell->MSpellTargets.readlock(__FUNCTION__, __LINE__);
+		for (int8 i = 0; i < spell->targets.size(); i++) {
+			target = spell->caster->GetZone()->GetSpawnByID(spell->targets.at(i));
+			if (!target || !target->IsEntity())
+				continue;
+
+			((Entity*)target)->AddProc(type, chance, item, spell);
+		}
+		spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
+	}
+	else
+		((Entity*)spawn)->AddProc(type, chance, item, spell);
+
+	return 0;
+}
+
+int EQ2Emu_lua_AddProcExt(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* spawn = lua_interface->GetSpawn(state);
+	int8 type = lua_interface->GetInt8Value(state, 2);
+	int8 damage_type = lua_interface->GetInt8Value(state, 3);
+	float chance = lua_interface->GetFloatValue(state, 4);
+	int8 hp_ratio = lua_interface->GetInt8Value(state, 5);
+	bool below_health = lua_interface->GetBooleanValue(state, 6);
+	bool target_health = lua_interface->GetBooleanValue(state, 7);
+	Item* item = lua_interface->GetItem(state, 8);
+	bool use_all_spelltargets = (lua_interface->GetInt8Value(state, 9) == 1);
+	bool extended_version = true;
+	LuaSpell* spell = 0;
+
 	if (!item)
 	if (!item)
 		spell = lua_interface->GetCurrentSpell(state);
 		spell = lua_interface->GetCurrentSpell(state);
 
 
+	if (!spawn && (!spell || !use_all_spelltargets)) {
+		lua_interface->LogError("%s: LUA AddProc command error: spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	if ((!spell || use_all_spelltargets) && spawn && !spawn->IsEntity()) {
+		lua_interface->LogError("%s: LUA AddProc command error: spawn is not a valid entity", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
 	if (!item && !spell) {
 	if (!item && !spell) {
 		lua_interface->LogError("%s: LUA AddProc command error: can only use with an item provided or inside a spell script", lua_interface->GetScriptName(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;
 		return 0;
@@ -8530,7 +8586,7 @@ int EQ2Emu_lua_AddProc(lua_State* state) {
 			if (!target || !target->IsEntity())
 			if (!target || !target->IsEntity())
 				continue;
 				continue;
 
 
-			((Entity*)target)->AddProc(type, chance, item, spell);
+			((Entity*)target)->AddProc(type, chance, item, spell, damage_type, hp_ratio, below_health, target_health, extended_version);
 		}
 		}
 		spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 		spell->MSpellTargets.releasereadlock(__FUNCTION__, __LINE__);
 	}
 	}

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

@@ -408,6 +408,7 @@ int EQ2Emu_lua_RemoveSkill(lua_State* state);
 int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state);
 int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state);
 
 
 int EQ2Emu_lua_AddProc(lua_State* state);
 int EQ2Emu_lua_AddProc(lua_State* state);
+int EQ2Emu_lua_AddProcExt(lua_State* state);
 int EQ2Emu_lua_RemoveProc(lua_State* state);
 int EQ2Emu_lua_RemoveProc(lua_State* state);
 int EQ2Emu_lua_Knockback(lua_State* state);
 int EQ2Emu_lua_Knockback(lua_State* state);
 
 

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

@@ -1323,6 +1323,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "IncreaseSkillCapsByType", EQ2Emu_lua_IncreaseSkillCapsByType);
 	lua_register(state, "IncreaseSkillCapsByType", EQ2Emu_lua_IncreaseSkillCapsByType);
 	lua_register(state, "RemoveSkill", EQ2Emu_lua_RemoveSkill);
 	lua_register(state, "RemoveSkill", EQ2Emu_lua_RemoveSkill);
 	lua_register(state, "AddProc", EQ2Emu_lua_AddProc);
 	lua_register(state, "AddProc", EQ2Emu_lua_AddProc);
+	lua_register(state, "AddProcExt", EQ2Emu_lua_AddProcExt);
 	lua_register(state, "RemoveProc", EQ2Emu_lua_RemoveProc);
 	lua_register(state, "RemoveProc", EQ2Emu_lua_RemoveProc);
 	lua_register(state, "Knockback", EQ2Emu_lua_Knockback);
 	lua_register(state, "Knockback", EQ2Emu_lua_Knockback);
 
 

+ 45 - 5
EQ2/source/WorldServer/Player.cpp

@@ -3326,7 +3326,7 @@ void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version
 		total_bytes = sizeof(Player_Update1144);
 		total_bytes = sizeof(Player_Update1144);
 	else if (version >= 1096)
 	else if (version >= 1096)
 		total_bytes = sizeof(Player_Update1096);
 		total_bytes = sizeof(Player_Update1096);
-	else if (version <= 283)
+	else if (version <= 373)
 		total_bytes = sizeof(Player_Update283);
 		total_bytes = sizeof(Player_Update283);
 	else
 	else
 		total_bytes = sizeof(Player_Update);
 		total_bytes = sizeof(Player_Update);
@@ -3416,6 +3416,12 @@ void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version
 		x_speed = update->speed_x;
 		x_speed = update->speed_x;
 		y_speed = update->speed_y;
 		y_speed = update->speed_y;
 		z_speed = update->speed_z;
 		z_speed = update->speed_z;
+		appearance.pos.X2 = update->orig_x;
+		appearance.pos.Y2 = update->orig_y;
+		appearance.pos.Z2 = update->orig_z;
+		appearance.pos.X3 = update->orig_x2;
+		appearance.pos.Y3 = update->orig_y2;
+		appearance.pos.Z3 = update->orig_z2;
 		if (update->pitch != 0)
 		if (update->pitch != 0)
 			SetPitch(180 + update->pitch);
 			SetPitch(180 + update->pitch);
 	}
 	}
@@ -3446,6 +3452,7 @@ void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version
 	}
 	}
 	
 	
 	SetHeading((sint16)(direction1 * 64), (sint16)(direction2 * 64));
 	SetHeading((sint16)(direction1 * 64), (sint16)(direction2 * 64));
+	
 	if (activity != last_movement_activity) {
 	if (activity != last_movement_activity) {
 		switch(activity) {
 		switch(activity) {
 			case UPDATE_ACTIVITY_RUNNING:
 			case UPDATE_ACTIVITY_RUNNING:
@@ -3489,7 +3496,6 @@ void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version
 		
 		
 		last_movement_activity = activity;
 		last_movement_activity = activity;
 	}
 	}
-
 	//Player is riding a lift, update lift XYZ offsets and the lift's spawn pointer
 	//Player is riding a lift, update lift XYZ offsets and the lift's spawn pointer
 	if (activity & UPDATE_ACTIVITY_RIDING_BOAT) {
 	if (activity & UPDATE_ACTIVITY_RIDING_BOAT) {
 		Spawn* boat = 0;
 		Spawn* boat = 0;
@@ -7120,8 +7126,7 @@ void Player::SetMentorStats(int32 effective_level, int32 target_char_id, bool up
 		client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id = target_char_id;
 		client->GetPlayer()->GetGroupMemberInfo()->mentor_target_char_id = target_char_id;
 	InfoStruct* info = GetInfoStruct();
 	InfoStruct* info = GetInfoStruct();
 	info->set_effective_level(effective_level);
 	info->set_effective_level(effective_level);
-	client->GetPlayer()->SetTotalHPBase(effective_level * effective_level * 2 + 40);
-	client->GetPlayer()->SetTotalPowerBase((sint32)(effective_level * effective_level * 2.1 + 45));
+	CalculatePlayerHPPower(effective_level);
 	client->GetPlayer()->CalculateBonuses();
 	client->GetPlayer()->CalculateBonuses();
 	if(update_stats) {
 	if(update_stats) {
 		client->GetPlayer()->SetHP(GetTotalHP());
 		client->GetPlayer()->SetHP(GetTotalHP());
@@ -7355,4 +7360,39 @@ void Player::ProcessSpawnRangeUpdates() {
 		}
 		}
 		spawn_itr++;
 		spawn_itr++;
 	}
 	}
-}
+}
+
+void Player::CalculatePlayerHPPower(int16 new_level) {
+	if(IsPlayer()) {
+		int16 effective_level = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
+		if(new_level < 1) {
+			new_level = effective_level;
+		}
+		
+		float hp_rule_mod = rule_manager.GetGlobalRule(R_Player, StartHPLevelMod)->GetFloat();
+		float power_rule_mod = rule_manager.GetGlobalRule(R_Player, StartPowerLevelMod)->GetFloat();
+		
+		sint32 base_hp = rule_manager.GetGlobalRule(R_Player, StartHPBase)->GetFloat();
+		sint32 base_power = rule_manager.GetGlobalRule(R_Player, StartPowerBase)->GetSInt32();
+		
+		sint32 new_hp = (sint32)((float)new_level * (float)new_level * hp_rule_mod + base_hp);
+		sint32 new_power = (sint32)((float)new_level * (float)new_level * power_rule_mod + base_power);
+		
+		if(new_hp < 1) {
+			LogWrite(PLAYER__WARNING, 0, "Player", "Player HP Calculation for %s too low at level %u due to ruleset, StartPowerLevelMod %f, BasePower %i", GetName(), new_level, hp_rule_mod, base_hp);
+			new_hp = 1;
+		}
+		if(new_power < 1) {
+			LogWrite(PLAYER__WARNING, 0, "Player", "Player Power Calculations for %s too low at level %u due to ruleset, StartPowerLevelMod %f, BasePower %i", GetName(), new_level, power_rule_mod, base_power);
+			new_power = 1;
+		}
+		
+		SetTotalHPBase(new_hp);
+		SetTotalHPBaseInstance(new_hp); // we need the hp base to override the instance as the new default
+		
+		SetTotalPowerBase(new_power);
+		SetTotalPowerBaseInstance(new_power); // we need the hp base to override the instance as the new default
+		
+		LogWrite(PLAYER__INFO, 0, "Player", "Player %s: Level %u, Set Base HP %i, Set Base Power: %i", GetName(), new_level, power_rule_mod, base_power);
+	}
+}

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

@@ -1102,6 +1102,7 @@ public:
 	bool	IsSpawnInRangeList(int32 spawn_id);
 	bool	IsSpawnInRangeList(int32 spawn_id);
 	void	SetSpawnInRangeList(int32 spawn_id, bool in_range);
 	void	SetSpawnInRangeList(int32 spawn_id, bool in_range);
 	void	ProcessSpawnRangeUpdates();
 	void	ProcessSpawnRangeUpdates();
+	void	CalculatePlayerHPPower(int16 new_level = 0);
 	Mutex MPlayerQuests;
 	Mutex MPlayerQuests;
 	float   pos_packet_speed;
 	float   pos_packet_speed;
 	
 	

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

@@ -233,6 +233,10 @@ void RuleManager::Init()
 	RULE_INIT(R_Player, TraitTrainingSelectLevel, "10"); // x levels to receive new trait of focus
 	RULE_INIT(R_Player, TraitTrainingSelectLevel, "10"); // x levels to receive new trait of focus
 	RULE_INIT(R_Player, TraitRaceSelectLevel, "10"); // x levels to receive new trait of focus
 	RULE_INIT(R_Player, TraitRaceSelectLevel, "10"); // x levels to receive new trait of focus
 	RULE_INIT(R_Player, TraitCharacterSelectLevel, "10"); // x levels to receive new trait of focus
 	RULE_INIT(R_Player, TraitCharacterSelectLevel, "10"); // x levels to receive new trait of focus
+	RULE_INIT(R_Player, StartHPBase, "40");
+	RULE_INIT(R_Player, StartPowerBase, "45");
+	RULE_INIT(R_Player, StartHPLevelMod, "2.0");
+	RULE_INIT(R_Player, StartPowerLevelMod, "2.1");
 	
 	
 	/* PVP */
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");
 	RULE_INIT(R_PVP, AllowPVP, "0");

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

@@ -93,6 +93,10 @@ enum RuleType {
 	TraitTrainingSelectLevel,
 	TraitTrainingSelectLevel,
 	TraitRaceSelectLevel,
 	TraitRaceSelectLevel,
 	TraitCharacterSelectLevel,
 	TraitCharacterSelectLevel,
+	StartHPBase,
+	StartPowerBase,
+	StartHPLevelMod,
+	StartPowerLevelMod,
 
 
 	/* PVP */
 	/* PVP */
 	AllowPVP,
 	AllowPVP,

+ 5 - 4
EQ2/source/WorldServer/Spawn.cpp

@@ -2277,9 +2277,6 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 		sint16 side_speed = player->GetSideSpeed() * speed_multiplier;
 		sint16 side_speed = player->GetSideSpeed() * speed_multiplier;
 		packet->setDataByName("pos_speed", pos_packet_speed);
 		packet->setDataByName("pos_speed", pos_packet_speed);
 		packet->setDataByName("pos_side_speed", side_speed);
 		packet->setDataByName("pos_side_speed", side_speed);
-		if(pos_packet_speed != 0 || side_speed != 0) {
-			movement_mode = 2;
-		}
 	}
 	}
 	else if (bSendSpeed) {
 	else if (bSendSpeed) {
 		sint16 side_speed = GetSpeed() * speed_multiplier;
 		sint16 side_speed = GetSpeed() * speed_multiplier;
@@ -2291,7 +2288,7 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 	
 	
 	
 	
 	if (IsNPC() || IsPlayer()) {
 	if (IsNPC() || IsPlayer()) {
-		packet->setDataByName("pos_move_type", World::newValue);
+		packet->setDataByName("pos_move_type", 25);
 	}
 	}
 	else if (IsWidget() || IsSign()) {
 	else if (IsWidget() || IsSign()) {
 		packet->setDataByName("pos_move_type", 11);
 		packet->setDataByName("pos_move_type", 11);
@@ -2396,6 +2393,10 @@ void Spawn::InitializeInfoPacketData(Player* spawn, PacketStruct* packet) {
 	if(version <= 373 && (model_type == 5864 || model_type == 5865 || model_type == 4015)) {
 	if(version <= 373 && (model_type == 5864 || model_type == 5865 || model_type == 4015)) {
 		model_type = 4034;
 		model_type = 4034;
 	}
 	}
+	else if(version <= 561 && model_type == 7039) { // goblin
+	
+		model_type = 145;
+	}
 	packet->setDataByName("model_type", model_type);
 	packet->setDataByName("model_type", model_type);
 	if (appearance.soga_model_type == 0)
 	if (appearance.soga_model_type == 0)
 		packet->setDataByName("soga_model_type", model_type);
 		packet->setDataByName("soga_model_type", model_type);

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

@@ -1655,12 +1655,20 @@ bool WorldDatabase::LoadCharacterStats(int32 id, int32 account_id, Client* clien
 		{
 		{
 			InfoStruct* info = client->GetPlayer()->GetInfoStruct();
 			InfoStruct* info = client->GetPlayer()->GetInfoStruct();
 
 
-			// must have totals up top before we set the current 'hp' / 'power'
-			client->GetPlayer()->SetTotalHP(result.GetSInt32Str("max_hp"));
-			client->GetPlayer()->SetTotalPower(result.GetSInt32Str("max_power"));
+			// we need stats assigned before we try to assign hp/power
+			info->set_str_base(result.GetInt16Str("str"));
+			info->set_sta_base(result.GetInt16Str("sta"));
+			info->set_agi_base(result.GetInt16Str("agi"));
+			info->set_wis_base(result.GetInt16Str("wis"));
+			info->set_intel_base(result.GetInt16Str("intel"));
+			info->set_sta(info->get_sta_base());
+			info->set_agi(info->get_agi_base());
+			info->set_str(info->get_str_base());
+			info->set_wis(info->get_wis_base());
+			info->set_intel(info->get_intel_base());
 			
 			
-			client->GetPlayer()->SetTotalHPBase(client->GetPlayer()->GetTotalHP());
-			client->GetPlayer()->SetTotalPowerBase(client->GetPlayer()->GetTotalPower());
+			// must have totals up top before we set the current 'hp' / 'power'
+			client->GetPlayer()->CalculatePlayerHPPower();
 			
 			
 			client->GetPlayer()->SetHP(result.GetSInt32Str("hp"));
 			client->GetPlayer()->SetHP(result.GetSInt32Str("hp"));
 			client->GetPlayer()->SetPower(result.GetSInt32Str("power"));
 			client->GetPlayer()->SetPower(result.GetSInt32Str("power"));
@@ -1675,11 +1683,6 @@ bool WorldDatabase::LoadCharacterStats(int32 id, int32 account_id, Client* clien
 			info->set_parry_base(result.GetInt16Str("parry"));
 			info->set_parry_base(result.GetInt16Str("parry"));
 			info->set_deflection_base(result.GetInt16Str("deflection"));
 			info->set_deflection_base(result.GetInt16Str("deflection"));
 			info->set_block_base(result.GetInt16Str("block"));
 			info->set_block_base(result.GetInt16Str("block"));
-			info->set_str_base(result.GetInt16Str("str"));
-			info->set_sta_base(result.GetInt16Str("sta"));
-			info->set_agi_base(result.GetInt16Str("agi"));
-			info->set_wis_base(result.GetInt16Str("wis"));
-			info->set_intel_base(result.GetInt16Str("intel"));
 
 
 			// old resist types
 			// old resist types
 			info->set_heat_base(result.GetInt16Str("heat"));
 			info->set_heat_base(result.GetInt16Str("heat"));
@@ -1803,6 +1806,7 @@ bool WorldDatabase::loadCharacter(const char* ch_name, int32 account_id, Client*
 		client->GetPlayer()->SetAdventureClass(atoi(row[9]));
 		client->GetPlayer()->SetAdventureClass(atoi(row[9]));
 		client->GetPlayer()->SetDeity(atoi(row[10]));
 		client->GetPlayer()->SetDeity(atoi(row[10]));
 		client->GetPlayer()->SetLevel(atoi(row[11]));
 		client->GetPlayer()->SetLevel(atoi(row[11]));
+		
 		client->GetPlayer()->SetGender(atoi(row[12]));
 		client->GetPlayer()->SetGender(atoi(row[12]));
 		client->GetPlayer()->SetTradeskillClass(atoi(row[13]));
 		client->GetPlayer()->SetTradeskillClass(atoi(row[13]));
 		client->GetPlayer()->SetTSLevel(atoi(row[14]));
 		client->GetPlayer()->SetTSLevel(atoi(row[14]));

+ 6 - 5
EQ2/source/WorldServer/client.cpp

@@ -5207,10 +5207,7 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) {
 
 
 	LogWrite(MISC__TODO, 1, "TODO", "Get new HP/POWER/stat based on default values from DB\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
 	LogWrite(MISC__TODO, 1, "TODO", "Get new HP/POWER/stat based on default values from DB\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
 
 
-	GetPlayer()->SetTotalHPBase(new_level * new_level * 2 + 40);
-	GetPlayer()->SetTotalHPBaseInstance(GetPlayer()->GetTotalHPBase()); // we need the hp base to override the instance as the new default
-	GetPlayer()->SetTotalPowerBase((sint32)(new_level * new_level * 2.1 + 45));
-	GetPlayer()->SetTotalPowerBaseInstance(GetPlayer()->GetTotalPowerBase()); // we need the hp base to override the instance as the new default
+	GetPlayer()->CalculatePlayerHPPower();
 	GetPlayer()->CalculateBonuses();
 	GetPlayer()->CalculateBonuses();
 	GetPlayer()->SetHP(GetPlayer()->GetTotalHP());
 	GetPlayer()->SetHP(GetPlayer()->GetTotalHP());
 	GetPlayer()->SetPower(GetPlayer()->GetTotalPower());
 	GetPlayer()->SetPower(GetPlayer()->GetTotalPower());
@@ -8818,6 +8815,10 @@ void Client::SendRepairList() {
 }
 }
 
 
 void Client::ShowLottoWindow() {
 void Client::ShowLottoWindow() {
+	if(GetVersion() <= 373) {
+		SimpleMessage(CHANNEL_COLOR_RED, "This client does not support the gambler UI, only Desert of Flames or later client.");
+		return;
+	}
 	Spawn* spawn = GetMerchantTransaction();
 	Spawn* spawn = GetMerchantTransaction();
 	if (spawn) {
 	if (spawn) {
 
 
@@ -8873,7 +8874,7 @@ void Client::ShowLottoWindow() {
 			//	packet->setArrayDataByName("quantity", item->details.count);
 			//	packet->setArrayDataByName("quantity", item->details.count);
 			packet->setArrayDataByName("stack_size2", item->details.count);
 			packet->setArrayDataByName("stack_size2", item->details.count);
 			packet->setArrayDataByName("description", item->description.c_str());
 			packet->setArrayDataByName("description", item->description.c_str());
-			if (GetVersion() <= 561) {
+			if (GetVersion() <= 546) {
 				packet->setDataByName("type", 128);
 				packet->setDataByName("type", 128);
 			}
 			}
 			else {
 			else {

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

@@ -6578,6 +6578,11 @@ void ZoneServer::RemoveSpawnSupportFunctions(Spawn* spawn, bool lock_spell_proce
 	if(!shutdown) { // in case of shutdown, DeleteData(true) handles the cleanup later via DeleteSpawnScriptTimers
 	if(!shutdown) { // in case of shutdown, DeleteData(true) handles the cleanup later via DeleteSpawnScriptTimers
 		StopSpawnScriptTimer(spawn, "");
 		StopSpawnScriptTimer(spawn, "");
 	}
 	}
+	
+	if(spawn->IsEntity()) {
+		ClearHate((Entity*)spawn);
+	}
+
 	RemoveDamagedSpawn(spawn);
 	RemoveDamagedSpawn(spawn);
 	spawn->SendSpawnChanges(false);
 	spawn->SendSpawnChanges(false);
 	RemoveChangedSpawn(spawn);
 	RemoveChangedSpawn(spawn);

+ 1 - 1
server/LoginStructs.xml

@@ -621,7 +621,7 @@ to zero and treated like placeholders." />
 <Data ElementName="parental_control_flag" Type="int8" Size="1" />
 <Data ElementName="parental_control_flag" Type="int8" Size="1" />
 <Data ElementName="parental_control_timer" Type="int32" Size="1" />
 <Data ElementName="parental_control_timer" Type="int32" Size="1" />
 <Data ElementName="unknown2" Type="int8" Size="8" />
 <Data ElementName="unknown2" Type="int8" Size="8" />
-<Data ElementName="account_id" Type="int32" Size="1" />
+<Data ElementName="cache_setting_account_id" Type="int32" Size="1" /> <!-- setting this to the account id will attempt to show/reuse cached entries in the login cache based on the account id, 0 means it will work on a fresh set of a character list -->
 <Data ElementName="unknown3" Type="EQ2_16Bit_String" Size="1" />
 <Data ElementName="unknown3" Type="EQ2_16Bit_String" Size="1" />
 <Data ElementName="reset_appearance" Type="int8" Size="1" />
 <Data ElementName="reset_appearance" Type="int8" Size="1" />
 <Data ElementName="do_not_force_soga" Type="int8" Size="1" />
 <Data ElementName="do_not_force_soga" Type="int8" Size="1" />

+ 5 - 3
server/WorldStructs.xml

@@ -459,7 +459,7 @@ to zero and treated like placeholders." />
 <Data ElementName="char_name" Type="EQ2_16Bit_String" />
 <Data ElementName="char_name" Type="EQ2_16Bit_String" />
 <Data ElementName="unknown" Type="int8" Size="8" />
 <Data ElementName="unknown" Type="int8" Size="8" />
 </Struct>
 </Struct>
-<Struct Name="WS_RequestCamp" ClientVersion="" OpcodeName="OP_RequestCampMsg">
+<Struct Name="WS_RequestCamp" ClientVersion="1" OpcodeName="OP_RequestCampMsg">
 <Data ElementName="quit" Type="int8" Size="1" />
 <Data ElementName="quit" Type="int8" Size="1" />
 <Data ElementName="camp_desktop" Type="int8" Size="1" />
 <Data ElementName="camp_desktop" Type="int8" Size="1" />
 </Struct>
 </Struct>
@@ -7886,12 +7886,14 @@ to zero and treated like placeholders." />
 	<Data ElementName="level" Type="int8" Size="1" />
 	<Data ElementName="level" Type="int8" Size="1" />
 	<Data ElementName="admin_level" Type="int8" Size="1" />
 	<Data ElementName="admin_level" Type="int8" Size="1" />
 	<Data ElementName="class" Type="int8" Size="1" />	
 	<Data ElementName="class" Type="int8" Size="1" />	
+	<Data ElementName="ts_level" Type="int8" Size="1" />
+	<Data ElementName="ts_class" Type="int8" Size="1" />
 	<Data ElementName="race" Type="int8" Size="1" />
 	<Data ElementName="race" Type="int8" Size="1" />
 	<Data ElementName="flags" Type="int8" Size="1" />
 	<Data ElementName="flags" Type="int8" Size="1" />
-	<Data ElementName="unknown1" Type="int8" Size="3" />
+	<Data ElementName="unknown1" Type="int8" Size="1" />
 	<Data ElementName="char_account_id" Type="int32" />		
 	<Data ElementName="char_account_id" Type="int32" />		
 	<Data ElementName="zone" Type="char" Size="80" />
 	<Data ElementName="zone" Type="char" Size="80" />
-	<Data ElementName="unknown6" Type="int8" Size="28" />	
+	<Data ElementName="unknown3" Type="int8" Size="28" />	
 </Data>
 </Data>
 <Data ElementName="unknown10" Type="int8" />
 <Data ElementName="unknown10" Type="int8" />
 </Struct>
 </Struct>