Browse Source

- Fix #554 - Weight support
RULE_INIT(R_Player, MaxWeightStrengthMultiplier, "2.0"); // multiplier for strength to add to max weight, eg 25 str * 2.0 = 50 max weight + base weight
RULE_INIT(R_Player, BaseWeight, "50"); // base weight per class, added to max weight with the strength multiplier
RULE_INIT(R_Player, WeightPercentImpact, "0.01"); // overweight in stone speed impact (.01 = 1% per 1 stone)
RULE_INIT(R_Player, WeightPercentCap, "0.95"); // cap total impact for being overweight (.95 = 95%)
RULE_INIT(R_Player, CoinWeightPerHundred, "100.0"); // coin weight per stone, 100.0 = 100 coins per 1 stone
RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off
- Fix a crash during zone shutdown and player pointer being destroyed with ~Client before removal from spawn_list (that is handled with ZoneServer, not individual client)
- Fix consistent calculate bonus being called, only called on spell bonus/removal (not each char sheet update)
- Added a debug spell log message so we can see what lua spell is being called before potential LUA crashes/panics
- Log message to point out bad loading of non-existent character ids in guild members

Emagi 3 weeks ago
parent
commit
1c94d68116

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

@@ -6796,6 +6796,7 @@ void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteComma
 				}
 				client->GetPlayer()->item_list.DestroyItem(index);
 				client->GetPlayer()->UpdateInventory(bag_id);
+				client->GetPlayer()->CalculateApplyWeight();
 			}
 		}
 		else if(sep->arg[4][0] && strncasecmp("move", sep->arg[0], 4) == 0 && sep->IsNumber(1) && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4))

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

@@ -1386,7 +1386,7 @@ void Entity::CalculateBonuses(){
 	MStats.lock();
 	stats.clear();
 	MStats.unlock();
-
+		
 	ItemStatsValues* values = equipment_list.CalculateEquipmentBonuses(this);
 	CalculateSpellBonuses(values);
 	
@@ -1560,10 +1560,60 @@ void Entity::CalculateBonuses(){
 	info->set_avoidance_display(total_avoidance);
 
 	SetRegenValues(effective_level);
-
+	
+	CalculateApplyWeight();
+	
 	safe_delete(values);
 }
 
+void Entity::CalculateApplyWeight() {
+	if (IsPlayer()) {
+		int32 prev_weight = GetInfoStruct()->get_weight();
+		int32 inv_weight = ((Player*)this)->item_list.GetWeight();
+		
+		// calculate coin
+		int32 coin_copper = GetInfoStruct()->get_coin_copper();
+		int32 coin_silver = GetInfoStruct()->get_coin_silver();
+		int32 coin_gold = GetInfoStruct()->get_coin_gold();
+		int32 coin_plat = GetInfoStruct()->get_coin_plat();
+		
+		float weight_per_hundred = rule_manager.GetGlobalRule(R_Player, CoinWeightPerHundred)->GetFloat();
+		if(weight_per_hundred < 0.0f) {
+			weight_per_hundred = 0.0f;
+		}
+		int32 total_weight = (int32)((double)coin_copper / weight_per_hundred) + (double)((double)coin_silver / weight_per_hundred) + (double)((double)coin_gold / weight_per_hundred) + (double)((double)coin_plat / weight_per_hundred);
+		
+		total_weight += (int32)((double)inv_weight / 10.0);
+		
+		GetInfoStruct()->set_weight(total_weight);
+		
+		SetSpeedMultiplier(GetHighestSnare());
+		((Player*)this)->SetSpeed(GetSpeed());
+		if(((Player*)this)->GetClient()) {
+			((Player*)this)->GetClient()->SendControlGhost();
+		}
+		info_changed = true;
+		changed = true;
+		AddChangedZoneSpawn();
+		((Player*)this)->SetCharSheetChanged(true);
+	}
+	int32 max_weight = 0;
+	float weight_str_multiplier = rule_manager.GetGlobalRule(R_Player, MaxWeightStrengthMultiplier)->GetFloat();
+	int32 base_weight = rule_manager.GetGlobalRule(R_Player, BaseWeight)->GetInt32();
+	if(weight_str_multiplier < 0.0f) {
+		weight_str_multiplier = 0.0f;
+	}
+	
+	if(GetInfoStruct()->get_str() <= 0.0f) {
+		max_weight = base_weight; // rule for base strength
+	}
+	else {
+		max_weight = (int32)((double)GetInfoStruct()->get_str() * weight_str_multiplier); // rule multipler for strength
+		max_weight += base_weight; // rule for base strength
+	}
+	GetInfoStruct()->set_max_weight(max_weight);
+}
+
 void Entity::SetRegenValues(int16 effective_level)
 {
 	bool classicRegen = rule_manager.GetGlobalRule(R_Spawn, ClassicRegen)->GetBool();
@@ -1699,7 +1749,7 @@ void Entity::AddSpellBonus(LuaSpell* spell, int16 type, float value, int64 class
 	bonus->tier = (spell && spell->spell) ? spell->spell->GetSpellTier() : 0;
 	bonus_list.Add(bonus);
 
-	if(IsNPC())
+	if(IsNPC() || IsPlayer())
 		CalculateBonuses();
 }
 
@@ -1734,7 +1784,7 @@ void Entity::RemoveSpellBonus(const LuaSpell* spell, bool remove_all){
 		bonus_list.Remove(itr.value, true);
 	}
 	
-	if(IsNPC())
+	if(IsNPC() || IsPlayer())
 		CalculateBonuses();
 }
 
@@ -2795,17 +2845,35 @@ void Entity::SetSnareValue(LuaSpell* spell, float snare_val) {
 float Entity::GetHighestSnare() {
 	// For simplicity this will return the highest snare value, which is actually the lowest value
 	float ret = 1.0f;
-
+	float weight_diff = 0.0f;
+	if (IsPlayer() && rule_manager.GetGlobalRule(R_Player, WeightInflictsSpeed)->GetBool()) {
+		float weight_pct_impact = rule_manager.GetGlobalRule(R_Player, WeightPercentImpact)->GetFloat();
+		float weight_pct_cap = rule_manager.GetGlobalRule(R_Player, WeightPercentCap)->GetFloat();
+		if(weight_pct_impact > 1.0f) {
+			weight_pct_impact = 1.0f;
+		}
+		if(weight_pct_cap < weight_pct_impact) {
+			weight_pct_impact = weight_pct_cap;
+		}
+		int32 weight = GetInfoStruct()->get_weight();
+		int32 max_weight = GetInfoStruct()->get_max_weight();
+		if(weight > max_weight) {
+			int32 diff = weight - max_weight;
+			weight_diff = (float)diff * weight_pct_impact; // percentage impact rule on weight "per stone", default 1%
+			if(weight_diff > weight_pct_cap) // cap weight impact rule
+				weight_diff = weight_pct_cap; // cap weight impact rule
+		}
+	}
 	if (snare_values.size() == 0)
-		return ret;
+		return ((ret - weight_diff) < 0.0f ) ? 0.0f : (ret - weight_diff);
 
 	map<LuaSpell*, float>::iterator itr;
 	for (itr = snare_values.begin(); itr != snare_values.end(); itr++) {
 		if (itr->second < ret)
 			ret = itr->second;
 	}
-
-	return ret;
+	
+	return ((ret - weight_diff) < 0.0f ) ? 0.0f : (ret - weight_diff);
 }
 
 bool Entity::IsSnared() {

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

@@ -1353,16 +1353,17 @@ public:
 	EquipmentItemList* GetEquipmentList();
 	EquipmentItemList* GetAppearanceEquipmentList();
 
-	bool IsEntity(){ return true; }
-	float CalculateSkillStatChance(char* skill, int16 item_stat, float max_cap = 0.0f, float modifier = 0.0f, bool add_to_skill = false);
-	float CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase);
-	float GetRuleSkillMaxBonus();
-	void CalculateBonuses();
-	void SetRegenValues(int16 effective_level);
-	float CalculateBonusMod();
-	float CalculateDPSMultiplier();
-	float CalculateCastingSpeedMod();
-
+	bool 	IsEntity(){ return true; }
+	float 	CalculateSkillStatChance(char* skill, int16 item_stat, float max_cap = 0.0f, float modifier = 0.0f, bool add_to_skill = false);
+	float 	CalculateSkillWithBonus(char* skillName, int16 item_stat, bool chance_skill_increase);
+	float 	GetRuleSkillMaxBonus();
+	void 	CalculateBonuses();
+	void 	CalculateApplyWeight();
+	void 	SetRegenValues(int16 effective_level);
+	float 	CalculateBonusMod();
+	float 	CalculateDPSMultiplier();
+	float 	CalculateCastingSpeedMod();
+	
 	InfoStruct* GetInfoStruct();
 
 	int16	GetStr();

+ 3 - 1
EQ2/source/WorldServer/Guilds/GuildDB.cpp

@@ -77,8 +77,10 @@ int32 WorldDatabase::LoadGuildMembers(Guild* guild) {
 
 	while (result && (row = mysql_fetch_row(result))) {
 		char_id = atoul(row[0]);
-		if (!(name = GetCharacterName(char_id)))
+		if (!(name = GetCharacterName(char_id))) {
+			LogWrite(GUILD__ERROR, 0, "Guilds", "WorldDatabase::LoadGuildMembers Cannot find guild member with character id %u.", char_id);
 			continue;
+		}
 
 		GuildMember* gm = new GuildMember;
 		gm->character_id = char_id;

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

@@ -3391,6 +3391,20 @@ int16 PlayerItemList::GetNumberOfItems(){
 	return ret;
 }
 
+int32 PlayerItemList::GetWeight(){
+	int32 ret = 0;
+	MPlayerItems.readlock(__FUNCTION__, __LINE__);
+	for(int16 i = 0; i < indexed_items.size(); i++){
+		Item* item = indexed_items[i];
+		if (item) {
+			ret += item->generic_info.weight;
+		}
+	}
+	MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+	return ret;
+}
+
+
 bool PlayerItemList::MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges){
 	MPlayerItems.writelock(__FUNCTION__, __LINE__);
 	Item* item_from = indexed_items[from_index];
@@ -4167,6 +4181,18 @@ int8 EquipmentItemList::GetNumberOfItems(){
 	return ret;
 }
 
+int32 EquipmentItemList::GetWeight(){
+	int32 ret = 0;
+	MEquipmentItems.lock();
+	for(int8 i=0;i<NUM_SLOTS;i++){
+		if(items[i]) {
+			ret += items[i]->generic_info.weight;
+		}
+	}
+	MEquipmentItems.unlock();
+	return ret;
+}
+
 void EquipmentItemList::SetItem(int8 slot_id, Item* item, bool locked){
 	if(!locked)
 		MEquipmentItems.lock();

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

@@ -1096,6 +1096,7 @@ public:
 	bool  AssignItemToFreeSlot(Item* item);
 	int16 GetNumberOfFreeSlots();
 	int16 GetNumberOfItems();
+	int32 GetWeight();
 	bool  HasFreeSlot();
 	bool  HasFreeBagSlot();
 	void DestroyItem(int16 index);
@@ -1168,6 +1169,7 @@ public:
 
 	bool	HasItem(int32 id);
 	int8	GetNumberOfItems();
+	int32	GetWeight();
 	Item*	GetItemFromUniqueID(int32 item_id);
 	Item*	GetItemFromItemID(int32 item_id);
 	void	SetItem(int8 slot_id, Item* item, bool locked = false);

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

@@ -639,6 +639,9 @@ bool LuaInterface::CallSpellProcess(LuaSpell* spell, int8 num_parameters, std::s
 	MSpells.lock();
 	current_spells[spell->state] = spell;
 	MSpells.unlock();
+	
+	LogWrite(SPELL__DEBUG, 0, "Spell", "%s LuaInterface::CallSpellProcess spell %s (%u) function %s, caster %s.", spell->spell ? spell->spell->GetName() : "UnknownUnset", spell->spell ? spell->spell->GetSpellID() : 0, customFunction.c_str(), spell->caster->GetName());
+	
 	if(lua_pcall(spell->state, num_parameters, 0, 0) != 0){
 		LogError("Error running function '%s' in %s: %s", customFunction.c_str(), spell->spell->GetName(), lua_tostring(spell->state, -1));
 		lua_pop(spell->state, 1);

+ 6 - 20
EQ2/source/WorldServer/Player.cpp

@@ -353,7 +353,6 @@ float PlayerInfo::GetBindZoneHeading(){
 }
 
 PacketStruct* PlayerInfo::serialize2(int16 version){
-	player->CalculateBonuses();
 	PacketStruct* packet = configReader.getStruct("WS_CharacterSheet", version);
 	if(packet){
 		//TODO: 2021 FIX THIS CASTING
@@ -638,8 +637,6 @@ void PlayerInfo::SetAccountAge(int32 age){
 }
 
 EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyValue) {
-	player->CalculateBonuses();
-
 	PacketStruct* packet = configReader.getStruct("WS_CharacterSheet", version);
 	//0-69, locked screen movement
 	//30-69 normal movement
@@ -979,9 +976,6 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 		packet->setDataByName("unknown168", 168);
 		packet->setDataByName("decrease_falling_dmg", 169);
 
-
-		info_struct->set_max_weight(200);
-
 		if (version <= 546) {
 			packet->setDataByName("exp_yellow", info_struct->get_xp_yellow() / 10);
 			packet->setDataByName("exp_blue", info_struct->get_xp_blue()/10);
@@ -1208,19 +1202,8 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
 			size = Pack(tmp, changes, size, size, version, reverse);
 		}
 
-		if (version >= 546)
-		{
-			PacketStruct* control_packet = configReader.getStruct("WS_SetControlGhost", version);
-			if (control_packet) {
-				control_packet->setDataByName("spawn_id", 0xFFFFFFFF);
-				control_packet->setDataByName("speed", player->GetSpeed());
-				control_packet->setDataByName("air_speed", player->GetAirSpeed());
-				control_packet->setDataByName("size", 0.51);
-				Client* client = player->GetClient();
-				if (client)
-					client->QueuePacket(control_packet->serialize());
-				safe_delete(control_packet);
-			}
+		if (version >= 546 && player->GetClient()) {
+			player->GetClient()->SendControlGhost();
 		}
 
 		EQ2Packet* ret_packet = new EQ2Packet(OP_UpdateCharacterSheetMsg, tmp, size);
@@ -1980,10 +1963,13 @@ bool Player::AddItem(Item* item, AddItemType type) {
 		}
 		else if (item_list.AssignItemToFreeSlot(item)) {
 			item->save_needed = true;
+			CalculateApplyWeight();
 			return true;
 		}
-		else if (item_list.AddOverflowItem(item))
+		else if (item_list.AddOverflowItem(item)) {
+			CalculateApplyWeight();
 			return true;
+		}
 	}
 	return false;
 }

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

@@ -220,6 +220,12 @@ void RuleManager::Init()
 	RULE_INIT(R_Player, SwimmingSkillMinBreathLength, "30");
 	RULE_INIT(R_Player, SwimmingSkillMaxBreathLength, "1000");
 	RULE_INIT(R_Player, AutoSkillUpBaseSkills, "0"); // when set to 1 we auto skill to max value on levelling up for armor,shield,class,weapon skills
+	RULE_INIT(R_Player, MaxWeightStrengthMultiplier, "2.0"); // multiplier for strength to add to max weight, eg 25 str * 2.0 = 50 max weight + base weight
+	RULE_INIT(R_Player, BaseWeight, "50"); // base weight per class, added to max weight with the strength multiplier
+	RULE_INIT(R_Player, WeightPercentImpact, "0.01"); // overweight in stone speed impact (.01 = 1% per 1 stone)
+	RULE_INIT(R_Player, WeightPercentCap, "0.95"); // cap total impact for being overweight (.95 = 95%)
+	RULE_INIT(R_Player, CoinWeightPerHundred, "100.0"); // coin weight per stone, 100.0 = 100 coins per 1 stone
+	RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off
 	
 	/* PVP */
 	RULE_INIT(R_PVP, AllowPVP, "0");

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

@@ -80,6 +80,12 @@ enum RuleType {
 	SwimmingSkillMinBreathLength,
 	SwimmingSkillMaxBreathLength,
 	AutoSkillUpBaseSkills,
+	MaxWeightStrengthMultiplier,
+	BaseWeight,
+	WeightPercentImpact,
+	WeightPercentCap,
+	CoinWeightPerHundred,
+	WeightInflictsSpeed,
 
 	/* PVP */
 	AllowPVP,

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

@@ -1784,8 +1784,6 @@ bool SpellProcess::CastProcessedSpell(LuaSpell* spell, bool passive, bool in_her
 			}
 			if (i == 0 && !spell->spell->GetSpellData()->not_maintained) {
 				spell->caster->AddMaintainedSpell(spell);
-				//((Entity*)target)->AddMaintainedSpell(spell);
-				LogWrite(SPELL__DEBUG, 0, "Spell", "AddMaintained on %s", ((Entity*)target)->GetName());
 			}
 			
 			SpellEffects* effect = ((Entity*)target)->GetSpellEffect(spell->spell->GetSpellID());

+ 20 - 25
EQ2/source/WorldServer/client.cpp

@@ -294,8 +294,6 @@ void Client::RemoveClientFromZone() {
 	safe_delete_array(incoming_paperdoll.image_bytes);
 
 	MDeletePlayer.writelock(__FUNCTION__, __LINE__);
-	if (player && !player->GetPendingDeletion())
-		safe_delete(player);
 	player = nullptr;
 	MDeletePlayer.releasewritelock(__FUNCTION__, __LINE__);
 	
@@ -672,17 +670,7 @@ void Client::HandlePlayerRevive(int32 point_id)
 		safe_delete(packet);
 	}
 
-	packet = configReader.getStruct("WS_SetControlGhost", GetVersion());
-	if (packet)
-	{
-		packet->setDataByName("spawn_id", 0xFFFFFFFF);
-		packet->setDataByName("speed", 32);
-		packet->setDataByName("unknown2", 0);
-		//DoF Merge: old value and speed wasn't included
-		//packet->setDataByName("unknown2", 255);
-		QueuePacket(packet->serialize());
-		safe_delete(packet);
-	}
+	SendControlGhost();
 
 	packet = configReader.getStruct("WS_SetPOVGhostCmd", GetVersion());
 	if (packet)
@@ -713,6 +701,20 @@ void Client::HandlePlayerRevive(int32 point_id)
 	m_resurrect.releasewritelock(__FUNCTION__, __LINE__);
 }
 
+void Client::SendControlGhost(int32 send_id, int8 unknown2) {
+	PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", GetVersion());
+	if (packet) {
+		packet->setDataByName("spawn_id", send_id);
+		packet->setDataByName("speed", GetPlayer()->GetSpeed());
+		packet->setDataByName("size", 0.51);
+		packet->setDataByName("unknown2", unknown2);
+		packet->setDataByName("air_speed", player->GetAirSpeed());
+		EQ2Packet* app = packet->serialize();
+		QueuePacket(app);
+		safe_delete(packet);
+	}
+}
+
 void Client::SendCharInfo() {
 	EQ2Packet* app;
 	
@@ -722,18 +724,8 @@ void Client::SendCharInfo() {
 
 	SendCharPOVGhost();
 
-	PacketStruct* packet = configReader.getStruct("WS_SetControlGhost", GetVersion());
-	if (packet) {
-		packet->setDataByName("spawn_id", player->GetIDWithPlayerSpawn(player));
-		packet->setDataByName("size", .56);
-		packet->setDataByName("unknown2", 255);
-		packet->setDataByName("speed", player->GetSpeed());
-		packet->setDataByName("air_speed", player->GetAirSpeed());
-		EQ2Packet* app = packet->serialize();
-		QueuePacket(app);
-		safe_delete(packet);
-
-	}
+	SendControlGhost(player->GetIDWithPlayerSpawn(player), 255);
+	
 	if (version <= 283) {
 		//le: hack to allow client time to zone in, it gets stuck on Loading UI Resources if we go too fast, need to figure it out.  Probably something it doesnt like with ExamineInfo packets
 		Sleep(2000);
@@ -1566,6 +1558,7 @@ bool Client::HandlePacket(EQApplicationPacket* app) {
 		LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): OP_DoneLoadingEntityResourcesMsg", opcode, opcode);
 		if (!IsReadyForSpawns())
 			SetReadyForSpawns(true);
+		player->CalculateApplyWeight();
 		SendCharInfo();
 		pos_update.Start();
 		quest_pos_timer.Start();
@@ -7583,6 +7576,8 @@ bool Client::RemoveItem(Item* item, int16 quantity, bool force_override_no_delet
 			lua_interface->SetLuaUserDataStale(item);
 			safe_delete(item);
 		}
+
+		GetPlayer()->CalculateApplyWeight();
 		return true;
 	}
 

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

@@ -175,6 +175,7 @@ public:
 	void	SendZoneInfo();
 	void	SendZoneSpawns();
 	void	HandleVerbRequest(EQApplicationPacket* app);
+	void	SendControlGhost(int32 send_id=0xFFFFFFFF, int8 unknown2=0);
 	void	SendCharInfo();
 	void	SendLoginDeniedBadVersion();
 	void	SendCharPOVGhost();