diff --git a/changelog.txt b/changelog.txt index a1a4e264c..33bf2de1a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,12 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- -== 21/01/2016 == -Uleat: Disabled RoF+ clients from augmentation items not in their possessions slots (0-29, 9999, 251-330) to abate an exploit in the current code +== 12/03/2016 == +Uleat: Added hack detection to trade code + - If illegal items are found in trade slots when the 'trade' button is clicked, the trade is cancelled and a message is sent to the offending player + - Future revisions will, at a minimum, log the player as a hacker once the quirks have been worked out + +== 12/01/2016 == +Uleat: Disabled RoF+ clients from augmenting items not in their possessions slots (0-29, 9999, 251-330) to abate an exploit in the current code == 10/17/2016 == Uleat: Moved namespace ItemField from item_instance.h to shareddb.cpp - the only place it is used diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 1d25e4d66..ffbcead70 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -1643,7 +1643,7 @@ struct LootingItem_Struct { /*002*/ uint32 looter; /*004*/ uint16 slot_id; /*006*/ uint8 unknown3[2]; -/*008*/ uint32 auto_loot; +/*008*/ int32 auto_loot; }; struct GuildManageStatus_Struct{ diff --git a/common/inventory_profile.cpp b/common/inventory_profile.cpp index 03b1df4e0..ae17a2a17 100644 --- a/common/inventory_profile.cpp +++ b/common/inventory_profile.cpp @@ -293,18 +293,13 @@ bool EQEmu::InventoryProfile::DeleteItem(int16 slot_id, uint8 quantity) } // Checks All items in a bag for No Drop -bool EQEmu::InventoryProfile::CheckNoDrop(int16 slot_id) { +bool EQEmu::InventoryProfile::CheckNoDrop(int16 slot_id, bool recurse) +{ ItemInstance* inst = GetItem(slot_id); - if (!inst) return false; - if (!inst->GetItem()->NoDrop) return true; - if (inst->GetItem()->ItemClass == 1) { - for (uint8 i = inventory::containerBegin; i < inventory::ContainerCount; i++) { - ItemInstance* bagitem = GetItem(InventoryProfile::CalcSlotId(slot_id, i)); - if (bagitem && !bagitem->GetItem()->NoDrop) - return true; - } - } - return false; + if (!inst) + return false; + + return (!inst->IsDroppable(recurse)); } // Remove item from bucket without memory delete diff --git a/common/inventory_profile.h b/common/inventory_profile.h index 336bb47d8..b9ae1db8c 100644 --- a/common/inventory_profile.h +++ b/common/inventory_profile.h @@ -133,7 +133,7 @@ namespace EQEmu bool DeleteItem(int16 slot_id, uint8 quantity = 0); // Checks All items in a bag for No Drop - bool CheckNoDrop(int16 slot_id); + bool CheckNoDrop(int16 slot_id, bool recurse = true); // Remove item from inventory (and take control of memory) ItemInstance* PopItem(int16 slot_id); diff --git a/common/item_instance.cpp b/common/item_instance.cpp index 268440dc8..a2860f3e5 100644 --- a/common/item_instance.cpp +++ b/common/item_instance.cpp @@ -539,16 +539,16 @@ EQEmu::ItemInstance* EQEmu::ItemInstance::GetOrnamentationAug(int32 ornamentatio } uint32 EQEmu::ItemInstance::GetOrnamentHeroModel(int32 material_slot) const { - uint32 HeroModel = 0; - if (m_ornament_hero_model > 0) - { - HeroModel = m_ornament_hero_model; - if (material_slot >= 0) - { - HeroModel = (m_ornament_hero_model * 100) + material_slot; - } - } - return HeroModel; + // Not a Hero Forge item. + if (m_ornament_hero_model == 0 || material_slot < 0) + return 0; + + // Item is using an explicit Hero Forge ID + if (m_ornament_hero_model >= 1000) + return m_ornament_hero_model; + + // Item is using a shorthand ID + return (m_ornament_hero_model * 100) + material_slot; } bool EQEmu::ItemInstance::UpdateOrnamentationInfo() { @@ -819,6 +819,32 @@ bool EQEmu::ItemInstance::IsSlotAllowed(int16 slot_id) const { else { return false; } } +bool EQEmu::ItemInstance::IsDroppable(bool recurse) const +{ + if (!m_item) + return false; + /*if (m_ornamentidfile) // not implemented + return false;*/ + if (m_attuned) + return false; + /*if (m_item->FVNoDrop != 0) // not implemented + return false;*/ + if (m_item->NoDrop == 0) + return false; + + if (recurse) { + for (auto iter : m_contents) { + if (!iter.second) + continue; + + if (!iter.second->IsDroppable(recurse)) + return false; + } + } + + return true; +} + void EQEmu::ItemInstance::Initialize(SharedDatabase *db) { // if there's no actual item, don't do anything if (!m_item) diff --git a/common/item_instance.h b/common/item_instance.h index 90dd1403f..6566fd77e 100644 --- a/common/item_instance.h +++ b/common/item_instance.h @@ -188,6 +188,8 @@ namespace EQEmu bool IsSlotAllowed(int16 slot_id) const; + bool IsDroppable(bool recurse = true) const; + bool IsScaling() const { return m_scaling; } bool IsEvolving() const { return (m_evolveLvl >= 1); } uint32 GetExp() const { return m_exp; } diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index 530d6baf1..ead2ce49a 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -1492,17 +1492,6 @@ namespace RoF FINISH_ENCODE(); } - ENCODE(OP_InterruptCast) - { - ENCODE_LENGTH_EXACT(InterruptCast_Struct); - SETUP_DIRECT_ENCODE(InterruptCast_Struct, structs::InterruptCast_Struct); - - OUT(spawnid); - OUT(messageid); - - FINISH_ENCODE(); - } - ENCODE(OP_ItemLinkResponse) { ENCODE_FORWARD(OP_ItemPacket); } ENCODE(OP_ItemPacket) diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index 7e0f48e12..4f4441884 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -1560,17 +1560,6 @@ namespace RoF2 FINISH_ENCODE(); } - ENCODE(OP_InterruptCast) - { - ENCODE_LENGTH_EXACT(InterruptCast_Struct); - SETUP_DIRECT_ENCODE(InterruptCast_Struct, structs::InterruptCast_Struct); - - OUT(spawnid); - OUT(messageid); - - FINISH_ENCODE(); - } - ENCODE(OP_ItemLinkResponse) { ENCODE_FORWARD(OP_ItemPacket); } ENCODE(OP_ItemPacket) diff --git a/common/patches/rof2_ops.h b/common/patches/rof2_ops.h index 1ca7063f4..2cd81aaee 100644 --- a/common/patches/rof2_ops.h +++ b/common/patches/rof2_ops.h @@ -85,7 +85,6 @@ E(OP_HPUpdate) E(OP_Illusion) E(OP_InspectBuffs) E(OP_InspectRequest) -E(OP_InterruptCast) E(OP_ItemLinkResponse) E(OP_ItemPacket) E(OP_ItemVerifyReply) diff --git a/common/patches/rof_ops.h b/common/patches/rof_ops.h index 45221cfc5..9030a480b 100644 --- a/common/patches/rof_ops.h +++ b/common/patches/rof_ops.h @@ -70,7 +70,6 @@ E(OP_HPUpdate) E(OP_Illusion) E(OP_InspectBuffs) E(OP_InspectRequest) -E(OP_InterruptCast) E(OP_ItemLinkResponse) E(OP_ItemPacket) E(OP_ItemVerifyReply) diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 879afc1d4..32c7c0d28 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -2021,7 +2021,7 @@ struct LootingItem_Struct { /*004*/ uint32 looter; /*008*/ uint16 slot_id; /*010*/ uint16 unknown10; -/*012*/ uint32 auto_loot; +/*012*/ int32 auto_loot; /*016*/ uint32 unknown16; /*020*/ }; diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index cd76218e0..fdcb03658 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -1666,7 +1666,7 @@ struct LootingItem_Struct { /*000*/ uint32 lootee; /*004*/ uint32 looter; /*008*/ uint32 slot_id; -/*012*/ uint32 auto_loot; +/*012*/ int32 auto_loot; /*016*/ uint32 unknown16; /*020*/ }; diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 9be93d49f..00fcd4ecf 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -1648,7 +1648,7 @@ struct LootingItem_Struct { /*002*/ uint32 looter; /*004*/ uint16 slot_id; /*006*/ uint8 unknown3[2]; -/*008*/ uint32 auto_loot; +/*008*/ int32 auto_loot; }; struct GuildManageStatus_Struct{ diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index 91921da2e..69988dd7c 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -1420,7 +1420,7 @@ struct LootingItem_Struct { /*002*/ uint32 looter; /*004*/ uint16 slot_id; /*006*/ uint8 unknown3[2]; -/*008*/ uint32 auto_loot; +/*008*/ int32 auto_loot; }; struct GuildManageStatus_Struct{ diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index ec279e56c..560a9378d 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -1707,7 +1707,7 @@ struct LootingItem_Struct { /*000*/ uint32 lootee; /*004*/ uint32 looter; /*008*/ uint32 slot_id; -/*012*/ uint32 auto_loot; +/*012*/ int32 auto_loot; /*016*/ uint32 unknown16; /*020*/ }; diff --git a/common/ruletypes.h b/common/ruletypes.h index 9a18ff063..dc1a31c1f 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -556,12 +556,13 @@ RULE_REAL(Bots, ManaRegen, 2.0) // Adjust mana regen for bots, 1 is fast and hig RULE_BOOL(Bots, PreferNoManaCommandSpells, true) // Give sorting priority to newer no-mana spells (i.e., 'Bind Affinity') RULE_BOOL(Bots, QuestableSpawnLimit, false) // Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl RULE_BOOL(Bots, QuestableSpells, false) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests. -RULE_INT(Bots, SpawnLimit, 71) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid +RULE_INT(Bots, SpawnLimit, 71) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group pseudo-raid (bots are not raidable at this time) RULE_BOOL(Bots, BotGroupXP, false) // Determines whether client gets xp for bots outside their group. RULE_BOOL(Bots, BotBardUseOutOfCombatSongs, true) // Determines whether bard bots use additional out of combat songs (optional script) RULE_BOOL(Bots, BotLevelsWithOwner, false) // Auto-updates spawned bots as owner levels/de-levels (false is original behavior) RULE_BOOL(Bots, BotCharacterLevelEnabled, false) // Enables required level to spawn bots RULE_INT(Bots, BotCharacterLevel, 0) // 0 as default (if level > this value you can spawn bots if BotCharacterLevelEnabled is true) +RULE_INT(Bots, CasterStopMeleeLevel, 13) // Level at which caster bots stop melee attacks RULE_CATEGORY_END() #endif diff --git a/common/shareddb.cpp b/common/shareddb.cpp index fc628ea6f..5205c97da 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -594,7 +594,7 @@ bool SharedDatabase::GetInventory(uint32 char_id, EQEmu::InventoryProfile *inv) inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentationIDFile(ornament_idfile); - inst->SetOrnamentHeroModel(ornament_hero_model); + inst->SetOrnamentHeroModel(item->HerosForgeModel); if (instnodrop || (((slot_id >= EQEmu::legacy::EQUIPMENT_BEGIN && slot_id <= EQEmu::legacy::EQUIPMENT_END) || @@ -730,7 +730,7 @@ bool SharedDatabase::GetInventory(uint32 account_id, char *name, EQEmu::Inventor inst->SetOrnamentIcon(ornament_icon); inst->SetOrnamentationIDFile(ornament_idfile); - inst->SetOrnamentHeroModel(ornament_hero_model); + inst->SetOrnamentHeroModel(item->HerosForgeModel); if (color > 0) inst->SetColor(color); diff --git a/common/spdat.cpp b/common/spdat.cpp index f22a82000..fd4e3c5ea 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -409,8 +409,19 @@ bool IsPartialCapableSpell(uint16 spell_id) if (spells[spell_id].no_partial_resist) return false; - if (IsPureNukeSpell(spell_id)) - return true; + // spell uses 600 (partial) scale if first effect is damage, else it uses 200 scale. + // this includes DoTs. no_partial_resist excludes spells like necro snares + for (int o = 0; o < EFFECT_COUNT; o++) { + auto tid = spells[spell_id].effectid[o]; + + if (IsBlankSpellEffect(spell_id, o)) + continue; + + if ((tid == SE_CurrentHPOnce || tid == SE_CurrentHP) && spells[spell_id].base[o] < 0) + return true; + + return false; + } return false; } diff --git a/common/spdat.h b/common/spdat.h index ab00451e0..6ca588f04 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -502,7 +502,7 @@ typedef enum { #define SE_HeadShotLevel 346 // implemented[AA] - HeadShot max level to kill #define SE_DoubleRangedAttack 347 // implemented - chance at an additional archery attack (consumes arrow) #define SE_LimitManaMin 348 // implemented -#define SE_ShieldEquipHateMod 349 // implemented[AA] Increase melee hate when wearing a shield. +#define SE_ShieldEquipDmgMod 349 // implemented[AA] Increase melee base damage (indirectly increasing hate) when wearing a shield. #define SE_ManaBurn 350 // implemented - Drains mana for damage/heal at a defined ratio up to a defined maximum amount of mana. //#define SE_PersistentEffect 351 // *not implemented. creates a trap/totem that casts a spell (spell id + base1?) when anything comes near it. can probably make a beacon for this //#define SE_IncreaseTrapCount 352 // *not implemented - looks to be some type of invulnerability? Test ITC (8755) @@ -519,7 +519,7 @@ typedef enum { #define SE_BandolierSlots 363 // *not implemented[AA] 'Battle Ready' expands the bandolier by one additional save slot per rank. #define SE_TripleAttackChance 364 // implemented #define SE_ProcOnSpellKillShot 365 // implemented - chance to trigger a spell on kill when the kill is caused by a specific spell with this effect in it (10470 Venin) -#define SE_ShieldEquipDmgMod 366 // implemented[AA] Damage modifier to melee if shield equiped. (base1 = dmg mod , base2 = ?) ie Shield Specialist AA +#define SE_GroupShielding 366 // *not implemented[AA] This gives you /shieldgroup #define SE_SetBodyType 367 // implemented - set body type of base1 so it can be affected by spells that are limited to that type (Plant, Animal, Undead, etc) //#define SE_FactionMod 368 // *not implemented - increases faction with base1 (faction id, live won't match up w/ ours) by base2 #define SE_CorruptionCounter 369 // implemented diff --git a/loginserver/config.cpp b/loginserver/config.cpp index ee17bdbed..5582e9754 100644 --- a/loginserver/config.cpp +++ b/loginserver/config.cpp @@ -144,7 +144,7 @@ void Config::Parse(const char *file_name) */ void Config::Tokenize(FILE *input, std::list &tokens) { - char c = fgetc(input); + auto c = fgetc(input); std::string lexeme; while(c != EOF) @@ -162,7 +162,7 @@ void Config::Tokenize(FILE *input, std::list &tokens) if(isalnum(c)) { - lexeme.append((const char *)&c, 1); + lexeme += c; c = fgetc(input); continue; } @@ -193,14 +193,14 @@ void Config::Tokenize(FILE *input, std::list &tokens) lexeme.clear(); } - lexeme.append((const char *)&c, 1); + lexeme += c; tokens.push_back(lexeme); lexeme.clear(); break; } default: { - lexeme.append((const char *)&c, 1); + lexeme += c; } } diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 19ef8d842..20111e850 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -150,7 +150,7 @@ OP_GMZoneRequest=0x62ac OP_GMZoneRequest2=0x7e1a OP_GMGoto=0x7d8e OP_GMSearchCorpse=0x357c -OP_GMHideMe=0x79c5 +OP_GMHideMe=0x2fab OP_GMDelCorpse=0x607e OP_GMApproval=0x6db5 OP_GMToggle=0x2097 diff --git a/utils/sql/git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql b/utils/sql/git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql deleted file mode 100644 index 4700d9abf..000000000 --- a/utils/sql/git/bots/optional/2014_03_31_BotLevelsWithOwnerRule.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (2, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (4, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (10, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); diff --git a/utils/sql/git/bots/optional/2014_03_31_bots_bot_levels_with_owner_rule.sql b/utils/sql/git/bots/optional/2014_03_31_bots_bot_levels_with_owner_rule.sql new file mode 100644 index 000000000..73a3c5b4c --- /dev/null +++ b/utils/sql/git/bots/optional/2014_03_31_bots_bot_levels_with_owner_rule.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:BotLevelsWithOwner', 'true', 'Auto-updates bots with ding.'); diff --git a/utils/sql/git/bots/optional/2016_12_09_bots_caster_stop_melee_level_rule.sql b/utils/sql/git/bots/optional/2016_12_09_bots_caster_stop_melee_level_rule.sql new file mode 100644 index 000000000..1e50c659e --- /dev/null +++ b/utils/sql/git/bots/optional/2016_12_09_bots_caster_stop_melee_level_rule.sql @@ -0,0 +1,2 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Bots:CasterStopMeleeLevel', '13', 'Level at which caster bots stop melee attacks'); + diff --git a/zone/attack.cpp b/zone/attack.cpp index b2f00e547..448bc94de 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1072,6 +1072,11 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b //if weapon damage > 0 then we know we can hit the target with this weapon //otherwise we cannot and we set the damage to -5 later on if(weapon_damage > 0){ + auto shield_inc = spellbonuses.ShieldEquipDmgMod + itembonuses.ShieldEquipDmgMod + aabonuses.ShieldEquipDmgMod; + if (shield_inc > 0 && HasShieldEquiped() && Hand == EQEmu::inventory::slotPrimary) { + weapon_damage = weapon_damage * (100 + shield_inc) / 100; + hate = hate * (100 + shield_inc) / 100; + } //Berserker Berserk damage bonus if(IsBerserk() && GetClass() == BERSERKER){ @@ -2291,11 +2296,6 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b // Spell Casting Subtlety etc int hatemod = 100 + other->spellbonuses.hatemod + other->itembonuses.hatemod + other->aabonuses.hatemod; - int32 shieldhatemod = other->spellbonuses.ShieldEquipHateMod + other->itembonuses.ShieldEquipHateMod + other->aabonuses.ShieldEquipHateMod; - - if (shieldhatemod && other->HasShieldEquiped()) - hatemod += shieldhatemod; - if(hatemod < 1) hatemod = 1; hate = ((hate * (hatemod))/100); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 760ca52dc..beae3995d 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -940,12 +940,8 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) case SE_ShieldBlock: newbon->ShieldBlock += base1; break; - case SE_ShieldEquipHateMod: - newbon->ShieldEquipHateMod += base1; - break; case SE_ShieldEquipDmgMod: - newbon->ShieldEquipDmgMod[0] += base1; - newbon->ShieldEquipDmgMod[1] += base2; + newbon->ShieldEquipDmgMod += base1; break; case SE_SecondaryDmgInc: newbon->SecondaryDmgInc = true; @@ -2655,13 +2651,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->ShieldBlock += effect_value; break; - case SE_ShieldEquipHateMod: - new_bonus->ShieldEquipHateMod += effect_value; - break; - case SE_ShieldEquipDmgMod: - new_bonus->ShieldEquipDmgMod[0] += effect_value; - new_bonus->ShieldEquipDmgMod[1] += base2; + new_bonus->ShieldEquipDmgMod += effect_value; break; case SE_BlockBehind: @@ -4555,19 +4546,10 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) itembonuses.DoubleRangedAttack = effect_value; break; - case SE_ShieldEquipHateMod: - spellbonuses.ShieldEquipHateMod = effect_value; - aabonuses.ShieldEquipHateMod = effect_value; - itembonuses.ShieldEquipHateMod = effect_value; - break; - case SE_ShieldEquipDmgMod: - spellbonuses.ShieldEquipDmgMod[0] = effect_value; - spellbonuses.ShieldEquipDmgMod[1] = effect_value; - aabonuses.ShieldEquipDmgMod[0] = effect_value; - aabonuses.ShieldEquipDmgMod[1] = effect_value; - itembonuses.ShieldEquipDmgMod[0] = effect_value; - itembonuses.ShieldEquipDmgMod[1] = effect_value; + spellbonuses.ShieldEquipDmgMod = effect_value; + aabonuses.ShieldEquipDmgMod = effect_value; + itembonuses.ShieldEquipDmgMod = effect_value; break; case SE_TriggerMeleeThreshold: diff --git a/zone/bot.cpp b/zone/bot.cpp index 133932df7..056a55289 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2391,7 +2391,7 @@ void Bot::AI_Process() { } } atCombatRange = true; - } else if(IsBotCaster() && GetLevel() > 12) { + } else if(IsBotCaster() && GetLevel() >= RuleI(Bots, CasterStopMeleeLevel)) { if(IsBotCasterCombatRange(GetTarget())) atCombatRange = true; } @@ -2440,7 +2440,7 @@ void Bot::AI_Process() { if(GetTarget()->GetHPRatio() <= 99.0f) BotRangedAttack(GetTarget()); } - else if(!IsBotArcher() && (!(IsBotCaster() && GetLevel() > 12)) && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead)) { + else if(!IsBotArcher() && (!(IsBotCaster() && GetLevel() >= RuleI(Bots, CasterStopMeleeLevel))) && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead)) { // we can't fight if we don't have a target, are stun/mezzed or dead.. // Stop attacking if the target is enraged if((IsEngaged() && !BehindMob(GetTarget(), GetX(), GetY()) && GetTarget()->IsEnraged()) || GetBotStance() == BotStancePassive) diff --git a/zone/client.h b/zone/client.h index 62a3770bc..2a13f129f 100644 --- a/zone/client.h +++ b/zone/client.h @@ -825,7 +825,7 @@ public: bool SummonItem(uint32 item_id, int16 charges = -1, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0, bool attuned = false, uint16 to_slot = EQEmu::inventory::slotCursor, uint32 ornament_icon = 0, uint32 ornament_idfile = 0, uint32 ornament_hero_model = 0); void SetStats(uint8 type,int16 set_val); void IncStats(uint8 type,int16 increase_val); - void DropItem(int16 slot_id); + void DropItem(int16 slot_id, bool recurse = true); int GetItemLinkHash(const EQEmu::ItemInstance* inst); // move to ItemData..or make use of the pre-calculated database field @@ -855,6 +855,7 @@ public: void SetConsumption(int32 in_hunger, int32 in_thirst); bool CheckTradeLoreConflict(Client* other); + bool CheckTradeNonDroppable(); void LinkDead(); void Insight(uint32 t_id); bool CheckDoubleAttack(); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 0d4294132..56936147d 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5003,6 +5003,9 @@ void Client::Handle_OP_CrashDump(const EQApplicationPacket *app) void Client::Handle_OP_CreateObject(const EQApplicationPacket *app) { + if (Log.log_settings[Logs::Inventory].is_category_enabled) + Log.Out(Logs::Detail, Logs::Inventory, "Handle_OP_CreateObject() [psize: %u] %s", app->size, DumpPacketToString(app).c_str()); + DropItem(EQEmu::inventory::slotCursor); return; } @@ -13287,7 +13290,6 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) other->trade->state = TradeCompleting; trade->state = TradeCompleting; - // should we do this for NoDrop items as well? if (CheckTradeLoreConflict(other) || other->CheckTradeLoreConflict(this)) { Message_StringID(13, TRADE_CANCEL_LORE); other->Message_StringID(13, TRADE_CANCEL_LORE); @@ -13296,6 +13298,26 @@ void Client::Handle_OP_TradeAcceptClick(const EQApplicationPacket *app) other->trade->Reset(); trade->Reset(); } + else if (CheckTradeNonDroppable()) { + Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + other->Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + this->FinishTrade(this); + other->FinishTrade(other); + other->trade->Reset(); + trade->Reset(); + Message(15, "Hacking activity detected in trade transaction."); + // TODO: query (this) as a hacker + } + else if (other->CheckTradeNonDroppable()) { + Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + other->Message_StringID(13, TRADE_HAS_BEEN_CANCELLED); + this->FinishTrade(this); + other->FinishTrade(other); + other->trade->Reset(); + trade->Reset(); + other->Message(15, "Hacking activity detected in trade transaction."); + // TODO: query (other) as a hacker + } else { // Audit trade to database for both trade streams other->trade->LogTrade(); @@ -14030,6 +14052,10 @@ void Client::Handle_OP_WearChange(const EQApplicationPacket *app) if (wc->spawn_id != GetID()) return; + // Hero Forge ID needs to be fixed here as RoF2 appears to send an incorrect value. + if (wc->hero_forge_model != 0 && wc->wear_slot_id >= 0 && wc->wear_slot_id < EQEmu::textures::weaponPrimary) + wc->hero_forge_model = GetHerosForgeModel(wc->wear_slot_id); + // we could maybe ignore this and just send our own from moveitem entity_list.QueueClients(this, app, true); return; diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 632f953d7..bb2db4777 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -393,6 +393,7 @@ bool Client::Process() { { EQEmu::ItemInstance *wpn = GetInv().GetItem(EQEmu::inventory::slotPrimary); TryWeaponProc(wpn, auto_attack_target, EQEmu::inventory::slotPrimary); + TriggerDefensiveProcs(auto_attack_target, EQEmu::inventory::slotPrimary, false); DoAttackRounds(auto_attack_target, EQEmu::inventory::slotPrimary); if (CheckAATimer(aaTimerRampage)) diff --git a/zone/command.cpp b/zone/command.cpp index 718feedcc..307b4e949 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -745,24 +745,15 @@ void command_wc(Client *c, const Seperator *sep) uint32 hero_forge_model = 0; uint32 wearslot = atoi(sep->arg[1]); + // Hero Forge if (sep->argnum > 2) { hero_forge_model = atoi(sep->arg[3]); - if (hero_forge_model > 0) - { - // Conversion to simplify the command arguments - // Hero's Forge model is actually model * 1000 + texture * 100 + wearslot - hero_forge_model *= 1000; - hero_forge_model += (atoi(sep->arg[2]) * 100); - hero_forge_model += wearslot; - // For Hero's Forge, slot 7 is actually for Robes, but it still needs to use slot 1 in the packet - if (wearslot == 7) - { - wearslot = 1; - } + if (hero_forge_model != 0 && hero_forge_model < 1000) { + // Shorthand Hero Forge ID. Otherwise use the value the user entered. + hero_forge_model = (hero_forge_model * 100) + wearslot; } - } /* // Leaving here to add color option to the #wc command eventually diff --git a/zone/common.h b/zone/common.h index fd4ea0a89..e621c336b 100644 --- a/zone/common.h +++ b/zone/common.h @@ -457,8 +457,7 @@ struct StatBonuses { int32 ItemATKCap; // Raise item attack cap int32 FinishingBlow[2]; // Chance to do a finishing blow for specified damage amount. uint32 FinishingBlowLvl[2]; // Sets max level an NPC can be affected by FB. (base1 = lv, base2= ???) - int32 ShieldEquipHateMod; // Hate mod when shield equiped. - int32 ShieldEquipDmgMod[2]; // Damage mod when shield equiped. 0 = damage modifier 1 = Unknown + int32 ShieldEquipDmgMod; // Increases weapon's base damage by base1 % when shield is equipped (indirectly increasing hate) bool TriggerOnValueAmount; // Triggers off various different conditions, bool to check if client has effect. int8 StunBashChance; // chance to stun with bash. int8 IncreaseChanceMemwipe; // increases chance to memory wipe diff --git a/zone/corpse.cpp b/zone/corpse.cpp index 34a2191b2..24c4b8220 100644 --- a/zone/corpse.cpp +++ b/zone/corpse.cpp @@ -36,7 +36,6 @@ Child of the Mob class. #include "../common/string_util.h" #include "../common/say_link.h" -#include "client.h" #include "corpse.h" #include "entity.h" #include "groups.h" @@ -1063,77 +1062,86 @@ void Corpse::MakeLootRequestPackets(Client* client, const EQApplicationPacket* a SendLootReqErrorPacket(client, LootResponse::LootAll); } -void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { - /* This gets sent no matter what as a sort of ACK */ - client->QueuePacket(app); +void Corpse::LootItem(Client *client, const EQApplicationPacket *app) +{ + auto lootitem = (LootingItem_Struct *)app->pBuffer; if (!loot_cooldown_timer.Check()) { + client->QueuePacket(app); SendEndLootErrorPacket(client); - //unlock corpse for others - if (this->being_looted_by = client->GetID()) { - being_looted_by = 0xFFFFFFFF; - } + // unlock corpse for others + if (IsBeingLootedBy(client)) + ResetLooter(); return; } /* To prevent item loss for a player using 'Loot All' who doesn't have inventory space for all their items. */ if (RuleB(Character, CheckCursorEmptyWhenLooting) && !client->GetInv().CursorEmpty()) { client->Message(13, "You may not loot an item while you have an item on your cursor."); + client->QueuePacket(app); SendEndLootErrorPacket(client); /* Unlock corpse for others */ - if (this->being_looted_by = client->GetID()) { - being_looted_by = 0xFFFFFFFF; - } + if (IsBeingLootedBy(client)) + ResetLooter(); return; } - LootingItem_Struct* lootitem = (LootingItem_Struct*)app->pBuffer; - - if (this->being_looted_by != client->GetID()) { + if (!IsBeingLootedBy(client)) { client->Message(13, "Error: Corpse::LootItem: BeingLootedBy != client"); + client->QueuePacket(app); SendEndLootErrorPacket(client); return; } - if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && (char_id != client->CharacterID() && client->Admin() < 150)) { + + if (IsPlayerCorpse() && !CanPlayerLoot(client->CharacterID()) && !become_npc && + (char_id != client->CharacterID() && client->Admin() < 150)) { client->Message(13, "Error: This is a player corpse and you dont own it."); + client->QueuePacket(app); SendEndLootErrorPacket(client); return; } + if (is_locked && client->Admin() < 100) { + client->QueuePacket(app); SendLootReqErrorPacket(client, LootResponse::SomeoneElse); client->Message(13, "Error: Corpse locked by GM."); return; } - if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && GetPlayerKillItem() == 0){ + + if (IsPlayerCorpse() && (char_id != client->CharacterID()) && CanPlayerLoot(client->CharacterID()) && + GetPlayerKillItem() == 0) { client->Message(13, "Error: You cannot loot any more items from this corpse."); + client->QueuePacket(app); SendEndLootErrorPacket(client); - being_looted_by = 0xFFFFFFFF; + ResetLooter(); return; } - const EQEmu::ItemData* item = 0; + + const EQEmu::ItemData *item = 0; EQEmu::ItemInstance *inst = 0; - ServerLootItem_Struct* item_data = nullptr, *bag_item_data[10]; + ServerLootItem_Struct *item_data = nullptr, *bag_item_data[10]; memset(bag_item_data, 0, sizeof(bag_item_data)); - if (GetPlayerKillItem() > 1){ + if (GetPlayerKillItem() > 1) { item = database.GetItem(GetPlayerKillItem()); - } - else if (GetPlayerKillItem() == -1 || GetPlayerKillItem() == 1){ - item_data = GetItem(lootitem->slot_id - EQEmu::legacy::CORPSE_BEGIN); //dont allow them to loot entire bags of items as pvp reward - } - else{ + } else if (GetPlayerKillItem() == -1 || GetPlayerKillItem() == 1) { + item_data = + GetItem(lootitem->slot_id - + EQEmu::legacy::CORPSE_BEGIN); // dont allow them to loot entire bags of items as pvp reward + } else { item_data = GetItem(lootitem->slot_id - EQEmu::legacy::CORPSE_BEGIN, bag_item_data); } - if (GetPlayerKillItem()<=1 && item_data != 0) { + if (GetPlayerKillItem() <= 1 && item_data != 0) { item = database.GetItem(item_data->item_id); } if (item != 0) { - if (item_data){ - inst = database.CreateItem(item, item_data ? item_data->charges : 0, item_data->aug_1, item_data->aug_2, item_data->aug_3, item_data->aug_4, item_data->aug_5, item_data->aug_6, item_data->attuned); - } - else { + if (item_data) { + inst = database.CreateItem(item, item_data ? item_data->charges : 0, item_data->aug_1, + item_data->aug_2, item_data->aug_3, item_data->aug_4, + item_data->aug_5, item_data->aug_6, item_data->attuned); + } else { inst = database.CreateItem(item); } } @@ -1141,8 +1149,9 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (client && inst) { if (client->CheckLoreConflict(item)) { client->Message_StringID(0, LOOT_LORE_ERROR); + client->QueuePacket(app); SendEndLootErrorPacket(client); - being_looted_by = 0; + ResetLooter(); delete inst; return; } @@ -1153,8 +1162,9 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (itm) { if (client->CheckLoreConflict(itm->GetItem())) { client->Message_StringID(0, LOOT_LORE_ERROR); + client->QueuePacket(app); SendEndLootErrorPacket(client); - being_looted_by = 0; + ResetLooter(); delete inst; return; } @@ -1165,21 +1175,32 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { char buf[88]; char q_corpse_name[64]; strcpy(q_corpse_name, corpse_name); - snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), EntityList::RemoveNumbers(q_corpse_name)); + snprintf(buf, 87, "%d %d %s", inst->GetItem()->ID, inst->GetCharges(), + EntityList::RemoveNumbers(q_corpse_name)); buf[87] = '\0'; std::vector args; args.push_back(inst); args.push_back(this); - parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args); + if (parse->EventPlayer(EVENT_LOOT, client, buf, 0, &args) != 0) { + lootitem->auto_loot = -1; + client->Message_StringID(CC_Red, LOOT_NOT_ALLOWED, inst->GetItem()->Name); + client->QueuePacket(app); + delete inst; + return; + } + // do we want this to have a fail option too? parse->EventItem(EVENT_LOOT, client, inst, this, buf, 0); + // safe to ACK now + client->QueuePacket(app); + if (!IsPlayerCorpse() && RuleB(Character, EnableDiscoveredItems)) { if (client && !client->GetGM() && !client->IsDiscovered(inst->GetItem()->ID)) client->DiscoverItem(inst->GetItem()->ID); } if (zone->adv_data) { - ServerZoneAdventureDataReply_Struct *ad = (ServerZoneAdventureDataReply_Struct*)zone->adv_data; + ServerZoneAdventureDataReply_Struct *ad = (ServerZoneAdventureDataReply_Struct *)zone->adv_data; if (ad->type == Adventure_Collect && !IsPlayerCorpse()) { if (ad->data_id == inst->GetItem()->ID) { zone->DoAdventureCountIncrease(); @@ -1188,11 +1209,10 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { } /* First add it to the looter - this will do the bag contents too */ - if (lootitem->auto_loot) { + if (lootitem->auto_loot > 0) { if (!client->AutoPutLootInInventory(*inst, true, true, bag_item_data)) client->PutLootInInventory(EQEmu::inventory::slotCursor, *inst, bag_item_data); - } - else { + } else { client->PutLootInInventory(EQEmu::inventory::slotCursor, *inst, bag_item_data); } @@ -1201,9 +1221,11 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { client->UpdateTasksForItem(ActivityLoot, item->ID); /* Remove it from Corpse */ - if (item_data){ - /* Delete needs to be before RemoveItem because its deletes the pointer for item_data/bag_item_data */ - database.DeleteItemOffCharacterCorpse(this->corpse_db_id, item_data->equip_slot, item_data->item_id); + if (item_data) { + /* Delete needs to be before RemoveItem because its deletes the pointer for + * item_data/bag_item_data */ + database.DeleteItemOffCharacterCorpse(this->corpse_db_id, item_data->equip_slot, + item_data->item_id); /* Delete Item Instance */ RemoveItem(item_data->lootslot); } @@ -1212,8 +1234,11 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { if (item->IsClassBag() && (GetPlayerKillItem() != -1 || GetPlayerKillItem() != 1)) { for (int i = EQEmu::inventory::containerBegin; i < EQEmu::inventory::ContainerCount; i++) { if (bag_item_data[i]) { - /* Delete needs to be before RemoveItem because its deletes the pointer for item_data/bag_item_data */ - database.DeleteItemOffCharacterCorpse(this->corpse_db_id, bag_item_data[i]->equip_slot, bag_item_data[i]->item_id); + /* Delete needs to be before RemoveItem because its deletes the pointer for + * item_data/bag_item_data */ + database.DeleteItemOffCharacterCorpse(this->corpse_db_id, + bag_item_data[i]->equip_slot, + bag_item_data[i]->item_id); /* Delete Item Instance */ RemoveItem(bag_item_data[i]); } @@ -1224,38 +1249,37 @@ void Corpse::LootItem(Client* client, const EQApplicationPacket* app) { SetPlayerKillItemID(0); } - /* Send message with item link to groups and such */ - EQEmu::SayLinkEngine linker; - linker.SetLinkType(EQEmu::saylink::SayLinkItemInst); - linker.SetItemInst(inst); + /* Send message with item link to groups and such */ + EQEmu::SayLinkEngine linker; + linker.SetLinkType(EQEmu::saylink::SayLinkItemInst); + linker.SetItemInst(inst); - auto item_link = linker.GenerateLink(); + auto item_link = linker.GenerateLink(); - client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, item_link.c_str()); + client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, item_link.c_str()); - if (!IsPlayerCorpse()) { + if (!IsPlayerCorpse()) { Group *g = client->GetGroup(); - if(g != nullptr) { - g->GroupMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, client->GetName(), item_link.c_str()); - } - else { + if (g != nullptr) { + g->GroupMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, + client->GetName(), item_link.c_str()); + } else { Raid *r = client->GetRaid(); - if(r != nullptr) { - r->RaidMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, client->GetName(), item_link.c_str()); + if (r != nullptr) { + r->RaidMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE, + client->GetName(), item_link.c_str()); } } } - } - else { + } else { SendEndLootErrorPacket(client); safe_delete(inst); return; } - if (IsPlayerCorpse()){ + if (IsPlayerCorpse()) { client->SendItemLink(inst); - } - else{ + } else { client->SendItemLink(inst, true); } diff --git a/zone/corpse.h b/zone/corpse.h index e83235c1e..49b1cba39 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -20,8 +20,8 @@ #define CORPSE_H #include "mob.h" +#include "client.h" -class Client; class EQApplicationPacket; class Group; class NPC; @@ -118,6 +118,7 @@ class Corpse : public Mob { inline bool IsLocked() { return is_locked; } inline void ResetLooter() { being_looted_by = 0xFFFFFFFF; } inline bool IsBeingLooted() { return (being_looted_by != 0xFFFFFFFF); } + inline bool IsBeingLootedBy(Client *c) { return being_looted_by == c->GetID(); } /* Mob */ void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); diff --git a/zone/forage.cpp b/zone/forage.cpp index aec4d6b73..cce6d9694 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -306,7 +306,13 @@ void Client::GoFish() const EQEmu::ItemData* food_item = database.GetItem(food_id); - Message_StringID(MT_Skills, FISHING_SUCCESS); + if (food_item->ItemType != EQEmu::item::ItemTypeFood) { + Message_StringID(MT_Skills, FISHING_SUCCESS); + } + else { + Message_StringID(MT_Skills, FISHING_SUCCESS_FISH_NAME, food_item->Name); + } + EQEmu::ItemInstance* inst = database.CreateItem(food_item, 1); if(inst != nullptr) { if(CheckLoreConflict(inst->GetItem())) diff --git a/zone/inventory.cpp b/zone/inventory.cpp index c5d7de0fe..416a97e28 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -594,10 +594,37 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, } // Drop item from inventory to ground (generally only dropped from SLOT_CURSOR) -void Client::DropItem(int16 slot_id) +void Client::DropItem(int16 slot_id, bool recurse) { - if(GetInv().CheckNoDrop(slot_id) && RuleI(World, FVNoDropFlag) == 0 || - RuleI(Character, MinStatusForNoDropExemptions) < Admin() && RuleI(World, FVNoDropFlag) == 2) { + Log.Out(Logs::General, Logs::Inventory, "'%s' (char_id: %u) Attempting to drop item from slot %i on the ground", + GetCleanName(), CharacterID(), slot_id); + + if(GetInv().CheckNoDrop(slot_id, recurse) && RuleI(World, FVNoDropFlag) == 0 || + RuleI(Character, MinStatusForNoDropExemptions) < Admin() && RuleI(World, FVNoDropFlag) == 2) + { + auto invalid_drop = m_inv.GetItem(slot_id); + if (!invalid_drop) { + Log.Out(Logs::General, Logs::Inventory, "Error in InventoryProfile::CheckNoDrop() - returned 'true' for empty slot"); + } + else { + if (Log.log_settings[Logs::Inventory].is_category_enabled) { + Log.Out(Logs::General, Logs::Inventory, "DropItem() Hack detected - full item parse:"); + Log.Out(Logs::General, Logs::Inventory, "depth: 0, Item: '%s' (id: %u), IsDroppable: %s", + (invalid_drop->GetItem() ? invalid_drop->GetItem()->Name : "null data"), invalid_drop->GetID(), invalid_drop->IsDroppable(false)); + + for (auto iter1 : *invalid_drop->GetContents()) { // depth 1 + Log.Out(Logs::General, Logs::Inventory, "-depth: 1, Item: '%s' (id: %u), IsDroppable: %s", + (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), iter1.second->IsDroppable(false)); + + for (auto iter2 : *iter1.second->GetContents()) { // depth 2 + Log.Out(Logs::General, Logs::Inventory, "--depth: 2, Item: '%s' (id: %u), IsDroppable: %s", + (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), iter2.second->IsDroppable(false)); + } + } + } + } + invalid_drop = nullptr; + database.SetHackerFlag(this->AccountName(), this->GetCleanName(), "Tried to drop an item on the ground that was nodrop!"); GetInv().DeleteItem(slot_id); return; @@ -606,12 +633,39 @@ void Client::DropItem(int16 slot_id) // Take control of item in client inventory EQEmu::ItemInstance *inst = m_inv.PopItem(slot_id); if(inst) { + if (Log.log_settings[Logs::Inventory].is_category_enabled) { + Log.Out(Logs::General, Logs::Inventory, "DropItem() Processing - full item parse:"); + Log.Out(Logs::General, Logs::Inventory, "depth: 0, Item: '%s' (id: %u), IsDroppable: %s", + (inst->GetItem() ? inst->GetItem()->Name : "null data"), inst->GetID(), inst->IsDroppable(false)); + + if (!inst->IsDroppable(false)) + Log.Out(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); + + for (auto iter1 : *inst->GetContents()) { // depth 1 + Log.Out(Logs::General, Logs::Inventory, "-depth: 1, Item: '%s' (id: %u), IsDroppable: %s", + (iter1.second->GetItem() ? iter1.second->GetItem()->Name : "null data"), iter1.second->GetID(), iter1.second->IsDroppable(false)); + + if (!iter1.second->IsDroppable(false)) + Log.Out(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); + + for (auto iter2 : *iter1.second->GetContents()) { // depth 2 + Log.Out(Logs::General, Logs::Inventory, "--depth: 2, Item: '%s' (id: %u), IsDroppable: %s", + (iter2.second->GetItem() ? iter2.second->GetItem()->Name : "null data"), iter2.second->GetID(), iter2.second->IsDroppable(false)); + + if (!iter2.second->IsDroppable(false)) + Log.Out(Logs::General, Logs::Error, "Non-droppable item being processed for drop by '%s'", GetCleanName()); + } + } + } + int i = parse->EventItem(EVENT_DROP_ITEM, this, inst, nullptr, "", slot_id); if(i != 0) { + Log.Out(Logs::General, Logs::Inventory, "Item drop handled by [EVENT_DROP_ITEM]"); safe_delete(inst); } } else { // Item doesn't exist in inventory! + Log.Out(Logs::General, Logs::Inventory, "DropItem() - No item found in slot %i", slot_id); Message(13, "Error: Item not found in slot %i", slot_id); return; } @@ -633,6 +687,8 @@ void Client::DropItem(int16 slot_id) entity_list.AddObject(object, true); object->StartDecay(); + Log.Out(Logs::General, Logs::Inventory, "Item drop handled ut assolet"); + safe_delete(inst); } diff --git a/zone/mob.cpp b/zone/mob.cpp index a39dcfa99..c47788ee5 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4668,9 +4668,6 @@ int16 Mob::GetMeleeDamageMod_SE(uint16 skill) dmg_mod += itembonuses.DamageModifier2[EQEmu::skills::HIGHEST_SKILL + 1] + spellbonuses.DamageModifier2[EQEmu::skills::HIGHEST_SKILL + 1] + aabonuses.DamageModifier2[EQEmu::skills::HIGHEST_SKILL + 1] + itembonuses.DamageModifier2[skill] + spellbonuses.DamageModifier2[skill] + aabonuses.DamageModifier2[skill]; - if (HasShieldEquiped() && !IsOffHandAtk()) - dmg_mod += itembonuses.ShieldEquipDmgMod[0] + spellbonuses.ShieldEquipDmgMod[0] + aabonuses.ShieldEquipDmgMod[0]; - if(dmg_mod < -100) dmg_mod = -100; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index a2a66f9ae..a2841ee8e 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2038,7 +2038,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove #endif if(IsNPC()) { if(GetLevel() <= 52) - CastToNPC()->Depop(); + CastToNPC()->Depop(true); else Message(13, "Your target is too high level to be affected by this spell."); } @@ -2982,8 +2982,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_FcHealAmtIncoming: case SE_LimitManaMax: case SE_DoubleRangedAttack: - case SE_ShieldEquipHateMod: case SE_ShieldEquipDmgMod: + case SE_GroupShielding: case SE_TriggerOnReqTarget: case SE_LimitRace: case SE_FcLimitUse: diff --git a/zone/spells.cpp b/zone/spells.cpp index 814f01be6..409a07f47 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -230,23 +230,6 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } } - // check for fizzle - // note that CheckFizzle itself doesn't let NPCs fizzle, - // but this code allows for it. - if(slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) - { - int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; - InterruptSpell(fizzle_msg, 0x121, spell_id); - - uint32 use_mana = ((spells[spell_id].mana) / 4); - Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: fizzled. %d mana has been consumed", use_mana); - - // fizzle 1/4 the mana away - SetMana(GetMana() - use_mana); - TryTriggerOnValueAmount(false, true); - return(false); - } - if (HasActiveSong() && IsBardSong(spell_id)) { Log.Out(Logs::Detail, Logs::Spells, "Casting a new song while singing a song. Killing old song %d.", bardsong); //Note: this does NOT tell the client @@ -366,6 +349,28 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, } casting_spell_aa_id = aa_id; + // check for fizzle + // note that CheckFizzle itself doesn't let NPCs fizzle, + // but this code allows for it. + if (slot < CastingSlot::MaxGems && !CheckFizzle(spell_id)) { + int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; + + uint32 use_mana = ((spells[spell_id].mana) / 4); + Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: fizzled. %d mana has been consumed", use_mana); + + // fizzle 1/4 the mana away + Mob::SetMana(GetMana() - use_mana); // We send StopCasting which will update mana + StopCasting(); + + Message_StringID(MT_SpellFailure, fizzle_msg); + entity_list.FilteredMessageClose_StringID( + this, true, 200, MT_SpellFailure, IsClient() ? FilterPCSpells : FilterNPCSpells, + fizzle_msg == MISS_NOTE ? MISSED_NOTE_OTHER : SPELL_FIZZLE_OTHER, GetName()); + + TryTriggerOnValueAmount(false, true); + return(false); + } + SaveSpellLoc(); Log.Out(Logs::Detail, Logs::Spells, "Casting %d Started at (%.3f,%.3f,%.3f)", spell_id, m_SpellLocation.x, m_SpellLocation.y, m_SpellLocation.z); diff --git a/zone/string_ids.h b/zone/string_ids.h index e1fb4de7c..f03d8f44d 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -46,6 +46,7 @@ #define FISHING_FAILED 168 //You didn't catch anything. #define FISHING_POLE_BROKE 169 //Your fishing pole broke! #define FISHING_SUCCESS 170 //You caught, something... +#define FISHING_SUCCESS_FISH_NAME 421 //You caught %1! #define FISHING_SPILL_BEER 171 //You spill your beer while bringing in your line. #define FISHING_LOST_BAIT 172 //You lost your bait! #define SPELL_FIZZLE 173 //Your spell fizzles! @@ -255,6 +256,7 @@ #define MEMBER_OF_YOUR_GUILD 1429 #define OFFICER_OF_YOUR_GUILD 1430 #define LEADER_OF_YOUR_GUILD 1431 +#define TRADE_HAS_BEEN_CANCELLED 1449 #define RECEIVED_PLATINUM 1452 //You receive %1 Platinum from %2. #define RECEIVED_GOLD 1453 //You receive %1 Gold from %2. #define RECEIVED_SILVER 1454 //You receive %1 Silver from %2. @@ -277,6 +279,7 @@ #define TRADESKILL_MISSING_COMPONENTS 3456 //Sorry, but you don't have everything you need for this recipe in your general inventory. #define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1! #define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end. +#define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1. #define NO_CAST_ON_PET 4045 //You cannot cast this spell on your pet. #define REWIND_WAIT 4059 //You must wait a bit longer before using the rewind command again. #define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can! diff --git a/zone/trading.cpp b/zone/trading.cpp index e150ceb12..85ed824b2 100644 --- a/zone/trading.cpp +++ b/zone/trading.cpp @@ -967,23 +967,37 @@ bool Client::CheckTradeLoreConflict(Client* other) { if (!other) return true; - // Move each trade slot into free inventory slot - for (int16 i = EQEmu::legacy::TRADE_BEGIN; i <= EQEmu::legacy::TRADE_END; i++){ - const EQEmu::ItemInstance* inst = m_inv[i]; - if (inst && inst->GetItem()) { - if (other->CheckLoreConflict(inst->GetItem())) - return true; - } + for (int16 index = EQEmu::legacy::TRADE_BEGIN; index <= EQEmu::legacy::TRADE_END; ++index) { + const EQEmu::ItemInstance* inst = m_inv[index]; + if (!inst || !inst->GetItem()) + continue; + + if (other->CheckLoreConflict(inst->GetItem())) + return true; } - for (int16 i = EQEmu::legacy::TRADE_BAGS_BEGIN; i <= EQEmu::legacy::TRADE_BAGS_END; i++){ - const EQEmu::ItemInstance* inst = m_inv[i]; + for (int16 index = EQEmu::legacy::TRADE_BAGS_BEGIN; index <= EQEmu::legacy::TRADE_BAGS_END; ++index) { + const EQEmu::ItemInstance* inst = m_inv[index]; + if (!inst || !inst->GetItem()) + continue; - if (inst && inst->GetItem()) { - if (other->CheckLoreConflict(inst->GetItem())) - return true; - } + if (other->CheckLoreConflict(inst->GetItem())) + return true; + } + + return false; +} + +bool Client::CheckTradeNonDroppable() +{ + for (int16 index = EQEmu::legacy::TRADE_BEGIN; index <= EQEmu::legacy::TRADE_END; ++index){ + const EQEmu::ItemInstance* inst = m_inv[index]; + if (!inst) + continue; + + if (!inst->IsDroppable()) + return true; } return false;