- 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
This commit is contained in:
Emagi 2024-04-03 09:10:06 -04:00
parent 5a791384a9
commit 1c94d68116
13 changed files with 161 additions and 66 deletions

View file

@ -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))

View file

@ -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() {

View file

@ -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();

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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");

View file

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

View file

@ -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());

View file

@ -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;
}

View file

@ -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();