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;
 }
-void LoginAccount::removeCharacter(char* name){
+void LoginAccount::removeCharacter(char* name, int16 version){
 	vector<CharSelectProfile*>::iterator iter;
 	CharSelectProfile* profile = 0;
 	EQ2_16BitString temp;
@@ -45,8 +45,13 @@ void LoginAccount::removeCharacter(char* name){
 		profile = *iter;
 		temp = profile->packet->getType_EQ2_16BitString_ByName("name");
 		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;
 		}
 	}

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

@@ -34,7 +34,7 @@ public:
 		charlist.push_back(profile);
 	}
 	void removeCharacter(PacketStruct* profile);
-	void removeCharacter(char* name);
+	void removeCharacter(char* name, int16 version);
 	void serializeCharacter(uchar* buffer, CharSelectProfile* profile);
 
 	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++){
 		character = *itr;
 		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;
 		num_characters++;		
 		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");
 					if(database.VerifyDelete(acct_id, char_id, name.data.c_str())){
 						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);
 						if(world_server != NULL)
 							world_server->SendDeleteCharacter ( char_id , acct_id );
@@ -389,7 +389,7 @@ bool Client::Process() {
 
 					EQ2Packet* outapp = response->serialize();
 					QueuePacket(outapp);
-
+					
 					this->SendCharList();
 				}
 				safe_delete(request);
@@ -651,6 +651,7 @@ void Client::SendWorldList(){
 	EQ2Packet* dupe = pack->Copy();
 	DumpPacket(dupe->pBuffer,dupe->size);
 	QueuePacket(dupe);
+
 	SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags
 	return;
 }
@@ -670,7 +671,7 @@ void Client::WorldResponse(int32 worldid, int8 response, char* ip_address, int32
 		if(response == PLAY_ERROR_CHAR_NOT_LOADED){
 			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())){
-				GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str());
+				GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion());
 			}
 		}
 		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;
 	float chance = 80 + bonus; //80% base chance that the victim will get hit (plus bonus)
 	sint16 roll_chance = 100;
+	if(is_caster_spell) {
+		chance += CalculateLevelStatBonus(GetWis());
+	}
 	if(skill)
 		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);
 
+		// calculated in Entity::CalculateBonuses
 		float dodgeChance = entity_victim->GetInfoStruct()->get_avoidance_base();
 		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
 	}
 	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) {
 			return DAMAGE_PACKET_RESULT_RESIST; //successfully resisted	
 		}
@@ -1599,7 +1603,7 @@ float Entity::CalculateAttackSpeedMod(){
 	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) {
 		LogWrite(COMBAT__ERROR, 0, "Proc", "Entity::AddProc called with an invalid type.");
 		return;
@@ -1616,6 +1620,11 @@ void Entity::AddProc(int8 type, float chance, Item* item, LuaSpell* spell) {
 	proc->item = item;
 	proc->spell = spell;
 	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);
 	MProcList.releasewritelock(__FUNCTION__, __LINE__);
 }
@@ -1663,8 +1672,12 @@ bool Entity::CastProc(Proc* proc, int8 type, Spawn* target) {
 		return false;
 	}
 
-	lua_getglobal(state, "proc");
-
+	if(proc->extended_version) {
+		lua_getglobal(state, "proc_ext");
+	}
+	else {
+		lua_getglobal(state, "proc");
+	}
 	if (item_proc) {
 		num_args++;
 		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, target);
 	lua_interface->SetInt32Value(state, type);
+	lua_interface->SetInt32Value(state, proc->damage_type);
 
 	/*
 	Add spell data from db in case of a spell proc here...
@@ -1722,20 +1736,31 @@ void Entity::CheckProcs(int8 type, Spawn* target) {
 		return;
 	}
 
-	float roll = MakeRandomFloat(0, 100);
-
 	vector<Proc*> tmpList;
 
 	MProcList.readlock(__FUNCTION__, __LINE__);
 	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);
-		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();
 			tmpProc->chance = proc->chance;
 			tmpProc->item = proc->item;
 			tmpProc->spell = proc->spell;
 			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);
 		}
 	}
@@ -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.
 			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);
 	}
 
@@ -1855,8 +1880,9 @@ sint32 Entity::CalculateDamageAmount(Spawn* target, sint32 damage, int8 base_typ
 	}
 
 	// 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);
+	}
 			
 	// Potency mod
 	damage = CalculateFormulaByStat(damage, ITEM_STAT_POTENCY);

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

@@ -1617,14 +1617,13 @@ void Entity::CalculateBonuses(){
 
 	MStats.lock();
 	float defenseStat = stats[ITEM_STAT_DEFENSE];
+	float baseAvoidanceStat = stats[ITEM_STAT_BASEAVOIDANCEBONUS];
 	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);
 
-	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);
 
@@ -1640,6 +1639,12 @@ void Entity::CalculateBonuses(){
 	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() {
 	if (IsPlayer()) {
 		int32 prev_weight = GetInfoStruct()->get_weight();

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

@@ -1298,6 +1298,11 @@ struct Proc {
 	Item*		item;
 	float		chance;
 	int32		spellid;
+	int8		health_ratio;
+	bool		below_health;
+	bool		target_health;
+	int8		damage_type;
+	bool		extended_version;
 };
 
 #define PROC_TYPE_OFFENSIVE				1
@@ -1396,6 +1401,7 @@ public:
 	float 	CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase);
 	float 	GetRuleSkillMaxBonus();
 	void 	CalculateBonuses();
+	float	CalculateLevelStatBonus(int16 stat_value);
 	void 	CalculateApplyWeight();
 	void 	SetRegenValues(int16 effective_level);
 	float 	CalculateBonusMod();
@@ -1818,7 +1824,7 @@ public:
 	/// <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='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>
 	/// <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_WEAPON_SKILLS, std::string("weapon skills"));
 	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)

+ 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_WEAPON_SKILLS			132
 #define ITEM_STAT_POWER_COST_REDUCTION	133
+#define ITEM_STAT_SPELL_AVOIDANCE		134
 
 #define ITEM_STAT_VS_PHYSICAL			200
 #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);
 	LuaSpell* spell = 0;
 
+	if (!item)
+		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;
@@ -8510,9 +8513,62 @@ int EQ2Emu_lua_AddProc(lua_State* state) {
 		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)
 		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) {
 		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;
@@ -8530,7 +8586,7 @@ int EQ2Emu_lua_AddProc(lua_State* state) {
 			if (!target || !target->IsEntity())
 				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__);
 	}

+ 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_AddProc(lua_State* state);
+int EQ2Emu_lua_AddProcExt(lua_State* state);
 int EQ2Emu_lua_RemoveProc(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, "RemoveSkill", EQ2Emu_lua_RemoveSkill);
 	lua_register(state, "AddProc", EQ2Emu_lua_AddProc);
+	lua_register(state, "AddProcExt", EQ2Emu_lua_AddProcExt);
 	lua_register(state, "RemoveProc", EQ2Emu_lua_RemoveProc);
 	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);
 	else if (version >= 1096)
 		total_bytes = sizeof(Player_Update1096);
-	else if (version <= 283)
+	else if (version <= 373)
 		total_bytes = sizeof(Player_Update283);
 	else
 		total_bytes = sizeof(Player_Update);
@@ -3416,6 +3416,12 @@ void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version
 		x_speed = update->speed_x;
 		y_speed = update->speed_y;
 		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)
 			SetPitch(180 + update->pitch);
 	}
@@ -3446,6 +3452,7 @@ void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version
 	}
 	
 	SetHeading((sint16)(direction1 * 64), (sint16)(direction2 * 64));
+	
 	if (activity != last_movement_activity) {
 		switch(activity) {
 			case UPDATE_ACTIVITY_RUNNING:
@@ -3489,7 +3496,6 @@ void Player::PrepareIncomingMovementPacket(int32 len, uchar* data, int16 version
 		
 		last_movement_activity = activity;
 	}
-
 	//Player is riding a lift, update lift XYZ offsets and the lift's spawn pointer
 	if (activity & UPDATE_ACTIVITY_RIDING_BOAT) {
 		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;
 	InfoStruct* info = GetInfoStruct();
 	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();
 	if(update_stats) {
 		client->GetPlayer()->SetHP(GetTotalHP());
@@ -7355,4 +7360,39 @@ void Player::ProcessSpawnRangeUpdates() {
 		}
 		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);
 	void	SetSpawnInRangeList(int32 spawn_id, bool in_range);
 	void	ProcessSpawnRangeUpdates();
+	void	CalculatePlayerHPPower(int16 new_level = 0);
 	Mutex MPlayerQuests;
 	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, 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, StartHPBase, "40");
+	RULE_INIT(R_Player, StartPowerBase, "45");
+	RULE_INIT(R_Player, StartHPLevelMod, "2.0");
+	RULE_INIT(R_Player, StartPowerLevelMod, "2.1");
 	
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");

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

@@ -93,6 +93,10 @@ enum RuleType {
 	TraitTrainingSelectLevel,
 	TraitRaceSelectLevel,
 	TraitCharacterSelectLevel,
+	StartHPBase,
+	StartPowerBase,
+	StartHPLevelMod,
+	StartPowerLevelMod,
 
 	/* PVP */
 	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;
 		packet->setDataByName("pos_speed", pos_packet_speed);
 		packet->setDataByName("pos_side_speed", side_speed);
-		if(pos_packet_speed != 0 || side_speed != 0) {
-			movement_mode = 2;
-		}
 	}
 	else if (bSendSpeed) {
 		sint16 side_speed = GetSpeed() * speed_multiplier;
@@ -2291,7 +2288,7 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b
 	
 	
 	if (IsNPC() || IsPlayer()) {
-		packet->setDataByName("pos_move_type", World::newValue);
+		packet->setDataByName("pos_move_type", 25);
 	}
 	else if (IsWidget() || IsSign()) {
 		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)) {
 		model_type = 4034;
 	}
+	else if(version <= 561 && model_type == 7039) { // goblin
+	
+		model_type = 145;
+	}
 	packet->setDataByName("model_type", model_type);
 	if (appearance.soga_model_type == 0)
 		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();
 
-			// 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()->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_deflection_base(result.GetInt16Str("deflection"));
 			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
 			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()->SetDeity(atoi(row[10]));
 		client->GetPlayer()->SetLevel(atoi(row[11]));
+		
 		client->GetPlayer()->SetGender(atoi(row[12]));
 		client->GetPlayer()->SetTradeskillClass(atoi(row[13]));
 		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__);
 
-	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()->SetHP(GetPlayer()->GetTotalHP());
 	GetPlayer()->SetPower(GetPlayer()->GetTotalPower());
@@ -8818,6 +8815,10 @@ void Client::SendRepairList() {
 }
 
 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();
 	if (spawn) {
 
@@ -8873,7 +8874,7 @@ void Client::ShowLottoWindow() {
 			//	packet->setArrayDataByName("quantity", item->details.count);
 			packet->setArrayDataByName("stack_size2", item->details.count);
 			packet->setArrayDataByName("description", item->description.c_str());
-			if (GetVersion() <= 561) {
+			if (GetVersion() <= 546) {
 				packet->setDataByName("type", 128);
 			}
 			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
 		StopSpawnScriptTimer(spawn, "");
 	}
+	
+	if(spawn->IsEntity()) {
+		ClearHate((Entity*)spawn);
+	}
+
 	RemoveDamagedSpawn(spawn);
 	spawn->SendSpawnChanges(false);
 	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_timer" Type="int32" Size="1" />
 <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="reset_appearance" 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="unknown" Type="int8" Size="8" />
 </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="camp_desktop" Type="int8" Size="1" />
 </Struct>
@@ -7886,12 +7886,14 @@ to zero and treated like placeholders." />
 	<Data ElementName="level" Type="int8" Size="1" />
 	<Data ElementName="admin_level" 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="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="zone" Type="char" Size="80" />
-	<Data ElementName="unknown6" Type="int8" Size="28" />	
+	<Data ElementName="unknown3" Type="int8" Size="28" />	
 </Data>
 <Data ElementName="unknown10" Type="int8" />
 </Struct>