diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 319517499..89e0126a8 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -340,6 +340,7 @@ N(OP_MOTD), N(OP_MoveCoin), N(OP_MoveDoor), N(OP_MoveItem), +N(OP_MoveMultipleItems), N(OP_MoveLogDisregard), N(OP_MoveLogRequest), N(OP_MultiLineMsg), diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 28905dd4a..5aa11977f 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -1827,6 +1827,20 @@ struct MoveItem_Struct /*0028*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0012*/ InventorySlot_Struct to_slot; +/*0024*/ uint32 number_in_stack; +/*0028*/ uint8 unknown[8]; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ uint32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 2a1eab3f0..9d87bd83a 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -52,6 +52,25 @@ struct EnterWorld_Struct { struct WorldObjectsSent_Struct { }; +// yep, even SoF had a version of the new inventory system, used by OP_MoveMultipleItems +struct InventorySlot_Struct +{ +/*000*/ int32 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Trade = 3, World = 4, Limbo = 5 +/*004*/ int32 Slot; +/*008*/ int32 SubIndex; +/*012*/ int32 AugIndex; +/*016*/ int32 Unknown01; +}; + +// unsure if they have a version of this, completeness though +struct TypelessInventorySlot_Struct +{ +/*000*/ int32 Slot; +/*004*/ int32 SubIndex; +/*008*/ int32 AugIndex; +/*012*/ int32 Unknown01; +}; + /* Name Approval Struct */ /* Len: */ /* Opcode: 0x8B20*/ @@ -1557,6 +1576,19 @@ struct MoveItem_Struct /*0012*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0020*/ uint32 number_in_stack; // so the amount we are moving from the source +/*0024*/ InventorySlot_Struct to_slot; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ uint32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index 56213854a..e52b7628c 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -48,6 +48,23 @@ struct EnterWorld_Struct { /*068*/ uint32 return_home; // 01 on "Return Home", 00 if not }; +// yep, even tit had a version of the new inventory system, used by OP_MoveMultipleItems +struct InventorySlot_Struct +{ +/*000*/ int32 Type; // Worn and Normal inventory = 0, Bank = 1, Shared Bank = 2, Trade = 3, World = 4, Limbo = 5 +/*004*/ int32 Slot; +/*008*/ int32 SubIndex; // no aug index in Tit +/*012*/ int32 Unknown01; +}; + +// unsure if they have a version of this, completeness though +struct TypelessInventorySlot_Struct +{ +/*000*/ int32 Slot; +/*004*/ int32 SubIndex; // no aug index in Tit +/*008*/ int32 Unknown01; +}; + /* Name Approval Struct */ /* Len: */ /* Opcode: 0x8B20*/ @@ -1329,6 +1346,19 @@ struct MoveItem_Struct /*0012*/ }; +struct MultiMoveItemSub_Struct +{ +/*0000*/ InventorySlot_Struct from_slot; +/*0016*/ uint32 number_in_stack; // so the amount we are moving from the source +/*0020*/ InventorySlot_Struct to_slot; +}; + +struct MultiMoveItem_Struct +{ +/*0000*/ uint32 count; +/*0004*/ MultiMoveItemSub_Struct moves[0]; +}; + // // from_slot/to_slot // -1 - destroy diff --git a/common/ruletypes.h b/common/ruletypes.h index c3a8fd5dc..ed5ae8e23 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -44,6 +44,7 @@ RULE_INT(Character, DeathExpLossMaxLevel, 255) // Any level greater than this wi RULE_INT(Character, DeathItemLossLevel, 10) RULE_INT(Character, DeathExpLossMultiplier, 3) //Adjust how much exp is lost RULE_BOOL(Character, UseDeathExpLossMult, false) //Adjust to use the above multiplier or to use code default. +RULE_BOOL(Character, UseOldRaceRezEffects, false) // older clients had ID 757 for races with high starting STR, but it doesn't seem used anymore RULE_INT(Character, CorpseDecayTimeMS, 10800000) RULE_INT(Character, CorpseResTimeMS, 10800000) // time before cant res corpse(3 hours) RULE_BOOL(Character, LeaveCorpses, true) diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index f3d4249c0..074d5751d 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -248,6 +248,7 @@ OP_AutoAttack=0x0d14 OP_AutoAttack2=0x3912 OP_Consume=0x4692 OP_MoveItem=0x62a2 +OP_MoveMultipleItems=0x55ef OP_DeleteItem=0x3eb5 OP_DeleteCharge=0x2d5b OP_ItemPacket=0x5e0e diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 420fcec70..dd58b3c11 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -247,6 +247,7 @@ OP_AutoAttack=0x109d OP_AutoAttack2=0x3526 OP_Consume=0x4b70 OP_MoveItem=0x32ee +OP_MoveMultipleItems=0x5623 OP_DeleteItem=0x18ad OP_DeleteCharge=0x01b8 OP_ItemPacket=0x368e diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 0e8e36e02..5913670ae 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -241,6 +241,7 @@ OP_AutoAttack=0x3d86 # C OP_AutoAttack2=0x4ca1 # C OP_Consume=0x7ce4 # C OP_MoveItem=0x7f56 # C +OP_MoveMultipleItems=0x4572 OP_DeleteItem=0x36f8 # C OP_DeleteCharge=0x1df9 # C OP_ItemPacket=0x34f8 # C diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index cae177bfc..6360617a2 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -237,6 +237,7 @@ OP_AutoAttack=0x3427 #Trevius 01/20/09 OP_AutoAttack2=0x6017 #Trevius 01/20/09 OP_Consume=0x729a #Trevius 02/08/09 OP_MoveItem=0x14B3 #Trevius 02/08/09 +OP_MoveMultipleItems=0x2d3e OP_DeleteItem=0x7DD4 #Xinu 03/08/09 0x41EE 0x018E 0x070C OP_DeleteCharge=0x32e2 #Trevius 03/23/09 OP_ItemPacket=0x78Cd #Trevius 02/08/09 diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index 23f07109f..27aa18376 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -198,6 +198,7 @@ OP_Split=0x4848 # ShowEQ 10/27/05 OP_Surname=0x4668 # ShowEQ 10/27/05 OP_ClearSurname=0x6cdb OP_MoveItem=0x420f # ShowEQ 10/27/05 +OP_MoveMultipleItems=0x463b OP_FaceChange=0x0f8e # ShowEQ 10/27/05 OP_ItemPacket=0x3397 # ShowEQ 10/27/05 OP_ItemLinkResponse=0x667c # ShowEQ 10/27/05 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 6d8064029..a96b4208a 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -251,6 +251,7 @@ OP_AutoAttack=0x1df9 # C OP_AutoAttack2=0x517b # C OP_Consume=0x24c5 # V OP_MoveItem=0x2641 # C +OP_MoveMultipleItems=0x40e8 OP_DeleteItem=0x66e0 # C OP_DeleteCharge=0x4ca1 # C OP_ItemPacket=0x7b6e # C diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index c9b6a7b78..11dd1b315 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -369,6 +369,7 @@ 9113|2017_07_19_show_name.sql|SHOW COLUMNS FROM `npc_types` LIKE 'show_name'|empty| 9114|2017_07_22_aura.sql|SHOW TABLES LIKE 'auras'|empty| 9115|2017_10_28_traps.sql|SHOW COLUMNS FROM `traps` LIKE 'triggered_number'|empty| +9116|2017_12_16_GroundSpawn_Respawn_Timer.sql|SHOW COLUMNS FROM `ground_spawns` WHERE Field = 'respawn_timer' AND Type = 'int(11) unsigned'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2017_12_16_GroundSpawn_Respawn_Timer.sql b/utils/sql/git/required/2017_12_16_GroundSpawn_Respawn_Timer.sql new file mode 100644 index 000000000..f7c8b6a31 --- /dev/null +++ b/utils/sql/git/required/2017_12_16_GroundSpawn_Respawn_Timer.sql @@ -0,0 +1,2 @@ +ALTER TABLE `ground_spawns` MODIFY `respawn_timer` int(11) unsigned NOT NULL default 300; +UPDATE `ground_spawns` SET `respawn_timer` = `respawn_timer` / 1000; diff --git a/zone/attack.cpp b/zone/attack.cpp index d12d8d36c..88712a08b 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -852,16 +852,56 @@ int Mob::ACSum() return ac; } +int Mob::GetBestMeleeSkill() + { + int bestSkill=0; + EQEmu::skills::SkillType meleeSkills[]= + { EQEmu::skills::Skill1HBlunt, + EQEmu::skills::Skill1HSlashing, + EQEmu::skills::Skill2HBlunt, + EQEmu::skills::Skill2HSlashing, + EQEmu::skills::SkillHandtoHand, + EQEmu::skills::Skill1HPiercing, + EQEmu::skills::Skill2HPiercing, + EQEmu::skills::SkillCount + }; + int i; + + for (i=0; meleeSkills[i] != EQEmu::skills::SkillCount; ++i) { + int value; + value = GetSkill(meleeSkills[i]); + bestSkill = std::max(value, bestSkill); + } + + return bestSkill; + } + int Mob::offense(EQEmu::skills::SkillType skill) { int offense = GetSkill(skill); - int stat_bonus = 0; - if (skill == EQEmu::skills::SkillArchery || skill == EQEmu::skills::SkillThrowing) - stat_bonus = GetDEX(); - else - stat_bonus = GetSTR(); + int stat_bonus = GetSTR(); + + switch (skill) { + case EQEmu::skills::SkillArchery: + case EQEmu::skills::SkillThrowing: + stat_bonus = GetDEX(); + break; + + // Mobs with no weapons default to H2H. + // Since H2H is capped at 100 for many many classes, + // lets not handicap mobs based on not spawning with a + // weapon. + // + // Maybe we tweak this if Disarm is actually implemented. + + case EQEmu::skills::SkillHandtoHand: + offense = GetBestMeleeSkill(); + break; + } + if (stat_bonus >= 75) offense += (2 * stat_bonus - 150) / 3; + offense += GetATK(); return offense; } @@ -2676,9 +2716,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b // owner must get on list, but he's not actually gained any hate yet if (!owner->GetSpecialAbility(IMMUNE_AGGRO)) { - hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic); if (owner->IsClient() && !CheckAggro(owner)) owner->CastToClient()->AddAutoXTarget(this); + hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic); } } } @@ -5250,19 +5290,30 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) // extra off hand non-sense, can only double with skill of 150 or above // or you have any amount of GiveDoubleAttack if (candouble && hand == EQEmu::inventory::slotSecondary) - candouble = GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 || (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; + candouble = + GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 || + (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0; if (candouble) { CheckIncreaseSkill(EQEmu::skills::SkillDoubleAttack, target, -10); if (CheckDoubleAttack()) { Attack(target, hand, false, false, IsFromSpell); + + // Modern AA description: Increases your chance of ... performing one additional hit with a 2-handed weapon when double attacking by 2%. + if (hand == EQEmu::inventory::slotPrimary) { + auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + + itembonuses.ExtraAttackChance; + if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance)) + Attack(target, hand, false, false, IsFromSpell); + } + // you can only triple from the main hand if (hand == EQEmu::inventory::slotPrimary && CanThisClassTripleAttack()) { CheckIncreaseSkill(EQEmu::skills::SkillTripleAttack, target, -10); if (CheckTripleAttack()) { Attack(target, hand, false, false, IsFromSpell); auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + - itembonuses.FlurryChance; + itembonuses.FlurryChance; if (flurrychance && zone->random.Roll(flurrychance)) { Attack(target, hand, false, false, IsFromSpell); if (zone->random.Roll(flurrychance)) @@ -5273,12 +5324,6 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) } } } - - if (hand == EQEmu::inventory::slotPrimary) { - auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance; - if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance)) - Attack(target, hand, false, false, IsFromSpell); - } } bool Mob::CheckDualWield() diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 3e1b812ce..74fc49ed8 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -291,6 +291,7 @@ void MapOpcodes() ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest; ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin; ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem; + ConnectedOpcodes[OP_MoveMultipleItems] = &Client::Handle_OP_MoveMultipleItems; ConnectedOpcodes[OP_OpenContainer] = &Client::Handle_OP_OpenContainer; ConnectedOpcodes[OP_OpenGuildTributeMaster] = &Client::Handle_OP_OpenGuildTributeMaster; ConnectedOpcodes[OP_OpenInventory] = &Client::Handle_OP_OpenInventory; @@ -9856,6 +9857,11 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app) return; } +void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app) +{ + Kick(); // TODO: lets not desync though +} + void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app) { // Does not exist in Ti client diff --git a/zone/client_packet.h b/zone/client_packet.h index c63a57825..1e7981ed9 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -204,6 +204,7 @@ void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app); void Handle_OP_MoveCoin(const EQApplicationPacket *app); void Handle_OP_MoveItem(const EQApplicationPacket *app); + void Handle_OP_MoveMultipleItems(const EQApplicationPacket *app); void Handle_OP_OpenContainer(const EQApplicationPacket *app); void Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app); void Handle_OP_OpenInventory(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index a0bdeead2..57dc58195 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -269,8 +269,16 @@ bool Client::Process() { } } - if (force_spawn_updates && mob != this && distance <= client_update_range) - mob->SendPositionUpdateToClient(this); + if (force_spawn_updates && mob != this) { + + if (mob->is_distance_roamer) { + mob->SendPositionUpdateToClient(this); + continue; + } + + if (distance <= client_update_range) + mob->SendPositionUpdateToClient(this); + } } } @@ -1055,7 +1063,8 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I SetMana(0); SetHP(GetMaxHP()/5); int rez_eff = 756; - if (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE) + if (RuleB(Character, UseOldRaceRezEffects) && + (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE)) rez_eff = 757; SpellOnTarget(rez_eff, this); // Rezz effects } diff --git a/zone/command.cpp b/zone/command.cpp index 164e0f750..615431ec0 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -376,6 +376,7 @@ int command_init(void) command_add("task", "(subcommand) - Task system commands", 150, command_task) || command_add("tattoo", "- Change the tattoo of your target (Drakkin Only)", 80, command_tattoo) || command_add("tempname", "[newname] - Temporarily renames your target. Leave name blank to restore the original name.", 100, command_tempname) || + command_add("petname", "[newname] - Temporarily renames your pet. Leave name blank to restore the original name.", 100, command_petname) || command_add("texture", "[texture] [helmtexture] - Change your or your target's appearance, use 255 to show equipment", 10, command_texture) || command_add("time", "[HH] [MM] - Set EQ time", 90, command_time) || command_add("timers", "- Display persistent timers for target", 200, command_timers) || @@ -4168,6 +4169,26 @@ void command_tempname(Client *c, const Seperator *sep) } } +void command_petname(Client *c, const Seperator *sep) +{ + Mob *target; + target = c->GetTarget(); + + if(!target) + c->Message(0, "Usage: #petname newname (requires a target)"); + else if(target->IsPet() && (target->GetOwnerID() == c->GetID()) && strlen(sep->arg[1]) > 0) + { + char *oldname = strdup(target->GetName()); + target->TempName(sep->arg[1]); + c->Message(0, "Renamed %s to %s", oldname, sep->arg[1]); + free(oldname); + } + else { + target->TempName(); + c->Message(0, "Restored the original name"); + } +} + void command_npcspecialattk(Client *c, const Seperator *sep) { if (c->GetTarget()==0 || c->GetTarget()->IsClient() || strlen(sep->arg[1]) <= 0 || strlen(sep->arg[2]) <= 0) diff --git a/zone/command.h b/zone/command.h index dca70d767..a27f173fc 100644 --- a/zone/command.h +++ b/zone/command.h @@ -287,6 +287,7 @@ void command_synctod(Client *c, const Seperator *sep); void command_task(Client *c, const Seperator *sep); void command_tattoo(Client *c, const Seperator *sep); void command_tempname(Client *c, const Seperator *sep); +void command_petname(Client *c, const Seperator *sep); void command_testspawn(Client *c, const Seperator *sep); void command_testspawnkill(Client *c, const Seperator *sep); void command_texture(Client *c, const Seperator *sep); diff --git a/zone/effects.cpp b/zone/effects.cpp index 2741a924b..6b0c2a2ad 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -611,6 +611,10 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { return false; } + // the client does this check before calling CastSpell, should prevent discs being eaten + if (spell.buffdurationformula != 0 && spell.targettype == ST_Self && HasDiscBuff()) + return false; + //Check the disc timer pTimerType DiscTimer = pTimerDisciplineReuseStart + spell.EndurTimerIndex; if(!p_timers.Expired(&database, DiscTimer, false)) { // lets not set the reuse timer in case CastSpell fails (or we would have to turn off the timer, but CastSpell will set it as well) diff --git a/zone/exp.cpp b/zone/exp.cpp index 7d9a02fa8..1fd8ea0a3 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -327,8 +327,7 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { aatotalmod *= zone->newzone_data.zone_exp_multiplier; } - - + // Shouldn't race not affect AA XP? if(RuleB(Character,UseRaceClassExpBonuses)) { if(GetBaseRace() == HALFLING){ @@ -340,6 +339,12 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { } } + // why wasn't this here? Where should it be? + if(zone->IsHotzone()) + { + aatotalmod += RuleR(Zone, HotZoneBonus); + } + if(RuleB(Zone, LevelBasedEXPMods)){ if(zone->level_exp_mod[GetLevel()].ExpMod){ add_exp *= zone->level_exp_mod[GetLevel()].ExpMod; diff --git a/zone/mob.cpp b/zone/mob.cpp index 66e221b0b..322bc7658 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -75,7 +75,6 @@ Mob::Mob(const char* in_name, uint32 in_drakkin_tattoo, uint32 in_drakkin_details, EQEmu::TintProfile in_armor_tint, - uint8 in_aa_title, uint8 in_see_invis, // see through invis/ivu uint8 in_see_invis_undead, @@ -91,24 +90,24 @@ Mob::Mob(const char* in_name, uint8 in_handtexture, uint8 in_legtexture, uint8 in_feettexture - ) : + ) : attack_timer(2000), attack_dw_timer(2000), ranged_timer(2000), tic_timer(6000), mana_timer(2000), spellend_timer(0), - rewind_timer(30000), //Timer used for determining amount of time between actual player position updates for /rewind. + rewind_timer(30000), bindwound_timer(10000), stunned_timer(0), spun_timer(0), bardsong_timer(6000), gravity_timer(1000), viral_timer(0), - m_FearWalkTarget(-999999.0f,-999999.0f,-999999.0f), + m_FearWalkTarget(-999999.0f, -999999.0f, -999999.0f), m_TargetLocation(glm::vec3()), m_TargetV(glm::vec3()), - flee_timer(FLEE_CHECK_TIMER), + flee_timer(FLEE_CHECK_TIMER), m_Position(position), tmHidden(-1), mitigation_ac(0), @@ -119,47 +118,46 @@ Mob::Mob(const char* in_name, position_update_melee_push_timer(1000) { targeted = 0; - tar_ndx=0; - tar_vector=0; + tar_ndx = 0; + tar_vector = 0; currently_fleeing = false; - last_z = 0; - last_major_update_position = m_Position; + is_distance_roamer = false; AI_Init(); SetMoving(false); - moved=false; + moved = false; m_RewindLocation = glm::vec3(); _egnode = nullptr; - name[0]=0; - orig_name[0]=0; - clean_name[0]=0; - lastname[0]=0; - if(in_name) { - strn0cpy(name,in_name,64); - strn0cpy(orig_name,in_name,64); + name[0] = 0; + orig_name[0] = 0; + clean_name[0] = 0; + lastname[0] = 0; + if (in_name) { + strn0cpy(name, in_name, 64); + strn0cpy(orig_name, in_name, 64); } - if(in_lastname) - strn0cpy(lastname,in_lastname,64); - cur_hp = in_cur_hp; - max_hp = in_max_hp; - base_hp = in_max_hp; - gender = in_gender; - race = in_race; - base_gender = in_gender; - base_race = in_race; - class_ = in_class; - bodytype = in_bodytype; + if (in_lastname) + strn0cpy(lastname, in_lastname, 64); + cur_hp = in_cur_hp; + max_hp = in_max_hp; + base_hp = in_max_hp; + gender = in_gender; + race = in_race; + base_gender = in_gender; + base_race = in_race; + class_ = in_class; + bodytype = in_bodytype; orig_bodytype = in_bodytype; - deity = in_deity; - level = in_level; + deity = in_deity; + level = in_level; orig_level = in_level; - npctype_id = in_npctype_id; - size = in_size; - base_size = size; - runspeed = in_runspeed; + npctype_id = in_npctype_id; + size = in_size; + base_size = size; + runspeed = in_runspeed; // neotokyo: sanity check if (runspeed < 0 || runspeed > 20) runspeed = 1.25f; @@ -172,7 +170,8 @@ Mob::Mob(const char* in_name, fearspeed = 0.625f; base_fearspeed = 25; // npcs - } else { + } + else { base_walkspeed = base_runspeed * 100 / 265; walkspeed = ((float)base_walkspeed) * 0.025f; base_fearspeed = base_runspeed * 100 / 127; @@ -184,7 +183,7 @@ Mob::Mob(const char* in_name, current_speed = base_runspeed; - m_PlayerState = 0; + m_PlayerState = 0; // sanity check @@ -196,8 +195,8 @@ Mob::Mob(const char* in_name, m_Light.Type[EQEmu::lightsource::LightActive] = m_Light.Type[EQEmu::lightsource::LightInnate]; m_Light.Level[EQEmu::lightsource::LightActive] = m_Light.Level[EQEmu::lightsource::LightInnate]; - texture = in_texture; - helmtexture = in_helmtexture; + texture = in_texture; + helmtexture = in_helmtexture; armtexture = in_armtexture; bracertexture = in_bracertexture; handtexture = in_handtexture; @@ -205,21 +204,21 @@ Mob::Mob(const char* in_name, feettexture = in_feettexture; multitexture = (armtexture || bracertexture || handtexture || legtexture || feettexture); - haircolor = in_haircolor; - beardcolor = in_beardcolor; - eyecolor1 = in_eyecolor1; - eyecolor2 = in_eyecolor2; - hairstyle = in_hairstyle; - luclinface = in_luclinface; - beard = in_beard; - drakkin_heritage = in_drakkin_heritage; - drakkin_tattoo = in_drakkin_tattoo; - drakkin_details = in_drakkin_details; + haircolor = in_haircolor; + beardcolor = in_beardcolor; + eyecolor1 = in_eyecolor1; + eyecolor2 = in_eyecolor2; + hairstyle = in_hairstyle; + luclinface = in_luclinface; + beard = in_beard; + drakkin_heritage = in_drakkin_heritage; + drakkin_tattoo = in_drakkin_tattoo; + drakkin_details = in_drakkin_details; attack_speed = 0; attack_delay = 0; slow_mitigation = 0; - findable = false; - trackable = true; + findable = false; + trackable = true; has_shieldequiped = false; has_twohandbluntequiped = false; has_twohanderequipped = false; @@ -230,19 +229,19 @@ Mob::Mob(const char* in_name, SpellPowerDistanceMod = 0; last_los_check = false; - if(in_aa_title>0) - aa_title = in_aa_title; + if (in_aa_title > 0) + aa_title = in_aa_title; else - aa_title =0xFF; - AC = in_ac; - ATK = in_atk; - STR = in_str; - STA = in_sta; - DEX = in_dex; - AGI = in_agi; - INT = in_int; - WIS = in_wis; - CHA = in_cha; + aa_title = 0xFF; + AC = in_ac; + ATK = in_atk; + STR = in_str; + STA = in_sta; + DEX = in_dex; + AGI = in_agi; + INT = in_int; + WIS = in_wis; + CHA = in_cha; MR = CR = FR = DR = PR = Corrup = 0; ExtraHaste = 0; @@ -263,8 +262,8 @@ Mob::Mob(const char* in_name, hidden = false; improved_hidden = false; invulnerable = false; - IsFullHP = (cur_hp == max_hp); - qglobal=0; + IsFullHP = (cur_hp == max_hp); + qglobal = 0; spawned = false; InitializeBuffSlots(); @@ -305,7 +304,7 @@ Mob::Mob(const char* in_name, logging_enabled = false; isgrouped = false; israidgrouped = false; - + IsHorse = false; entity_id_being_looted = 0; @@ -376,13 +375,13 @@ Mob::Mob(const char* in_name, } destructibleobject = false; - wandertype=0; - pausetype=0; + wandertype = 0; + pausetype = 0; cur_wp = 0; m_CurrentWayPoint = glm::vec4(); cur_wp_pause = 0; - patrol=0; - follow=0; + patrol = 0; + follow = 0; follow_dist = 100; // Default Distance for Follow no_target_hotkey = false; flee_mode = false; @@ -396,7 +395,7 @@ Mob::Mob(const char* in_name, rooted = false; charmed = false; has_virus = false; - for (i=0; i= (100 * 100)) { entity_list.QueueClients(this, app, true, true); last_major_update_position = m_Position; + is_distance_roamer = true; } else { entity_list.QueueCloseClients(this, app, true, RuleI(Range, MobPositionUpdates), nullptr, false); @@ -1480,6 +1480,11 @@ void Mob::SendPositionUpdate(uint8 iSendToSelf) { CastToClient()->FastQueuePacket(&app, false); } } + else if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) { + entity_list.QueueClients(this, app, true, true); + last_major_update_position = m_Position; + is_distance_roamer = true; + } else { entity_list.QueueCloseClients(this, app, (iSendToSelf == 0), RuleI(Range, MobPositionUpdates), nullptr, false); } @@ -3444,6 +3449,19 @@ void Mob::SetTarget(Mob* mob) { this->GetTarget()->SendHPUpdate(false, true); } +// For when we want a Ground Z at a location we are not at yet +// Like MoveTo. +float Mob::FindDestGroundZ(glm::vec3 dest, float z_offset) +{ + float best_z = BEST_Z_INVALID; + if (zone->zonemap != nullptr) + { + dest.z += z_offset; + best_z = zone->zonemap->FindBestZ(dest, nullptr); + } + return best_z; +} + float Mob::FindGroundZ(float new_x, float new_y, float z_offset) { float ret = BEST_Z_INVALID; diff --git a/zone/mob.h b/zone/mob.h index 058774c54..deb24f640 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -162,6 +162,8 @@ public: inline virtual bool IsMob() const { return true; } inline virtual bool InZone() const { return true; } + bool is_distance_roamer; + //Somewhat sorted: needs documenting! //Attack @@ -199,6 +201,7 @@ public: void ApplyMeleeDamageMods(uint16 skill, int &damage, Mob * defender = nullptr, ExtraAttackOptions *opts = nullptr); int ACSum(); int offense(EQEmu::skills::SkillType skill); + int GetBestMeleeSkill(); void CalcAC() { mitigation_ac = ACSum(); } int GetACSoftcap(); double GetSoftcapReturns(); @@ -278,6 +281,7 @@ public: float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false, int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false, int level_override = -1); + int GetResist(uint8 resist_type); int ResistPhysical(int level_diff, uint8 caster_level); int ResistElementalWeaponDmg(const EQEmu::ItemInstance *item); int CheckBaneDamage(const EQEmu::ItemInstance *item); @@ -349,6 +353,7 @@ public: virtual int GetMaxSongSlots() const { return 0; } virtual int GetMaxDiscSlots() const { return 0; } virtual int GetMaxTotalSlots() const { return 0; } + bool HasDiscBuff(); virtual uint32 GetFirstBuffSlot(bool disc, bool song); virtual uint32 GetLastBuffSlot(bool disc, bool song); virtual void InitializeBuffSlots() { buffs = nullptr; current_buff_count = 0; } @@ -954,7 +959,8 @@ public: void SendTo(float new_x, float new_y, float new_z); void SendToFixZ(float new_x, float new_y, float new_z); float GetZOffset() const; - void FixZ(int32 z_find_offset = 5); + void FixZ(int32 z_find_offset = 5); + float GetFixedZ(glm::vec3 position, int32 z_find_offset = 5); void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false); inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; } inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; } @@ -1108,8 +1114,6 @@ public: int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); - float last_z; - // Bots HealRotation methods #ifdef BOTS bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); } @@ -1225,7 +1229,8 @@ protected: uint32 npctype_id; glm::vec4 m_Position; /* Used to determine when an NPC has traversed so many units - to send a zone wide pos update */ - glm::vec4 last_major_update_position; + glm::vec4 last_major_update_position; + int animation; // this is really what MQ2 calls SpeedRun just packed like (int)(SpeedRun * 40.0f) float base_size; float size; @@ -1267,6 +1272,7 @@ protected: virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; } void CalculateNewFearpoint(); float FindGroundZ(float new_x, float new_y, float z_offset=0.0); + float FindDestGroundZ(glm::vec3 dest, float z_offset=0.0); glm::vec3 UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached); void PrintRoute(); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index fb8faebf0..e42e4f617 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -503,7 +503,7 @@ void NPC::AI_Start(uint32 iMoveDelay) { AIautocastspell_timer->Disable(); } else { AIautocastspell_timer = std::unique_ptr(new Timer(750)); - AIautocastspell_timer->Start(RandomTimer(0, 15000), false); + AIautocastspell_timer->Start(RandomTimer(0, 300), false); } if (NPCTypedata) { @@ -1582,18 +1582,22 @@ void NPC::AI_DoMovement() { } this->FixZ(); - SendPosition(); //kick off event_waypoint arrive char temp[16]; sprintf(temp, "%d", cur_wp); parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0); - // start moving directly to next waypoint if we're at a 0 pause waypoint and we didn't get quest halted. - if (!AI_walking_timer->Enabled()) + // No need to move as we are there. Next loop will + // take care of normal grids, even at pause 0. + // We do need to call and setup a wp if we're cur_wp=-2 + // as that is where roamer is unset and we don't want + // the next trip through to move again based on grid stuff. + doMove = false; + if (cur_wp == -2) { AI_SetupNextWaypoint(); - else - doMove = false; + } + // wipe feign memory since we reached our first waypoint if(cur_wp == 1) ClearFeignMemory(); @@ -2593,7 +2597,7 @@ void NPC::AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType, // If we're going from an empty list, we need to start the timer if (AIspells.size() == 1) - AIautocastspell_timer->Start(RandomTimer(0, 15000), false); + AIautocastspell_timer->Start(RandomTimer(0, 300), false); } void NPC::RemoveSpellFromNPCList(int16 spell_id) diff --git a/zone/object.cpp b/zone/object.cpp index d8db2f1d8..0ba5f9fa3 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -70,7 +70,7 @@ Object::Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& object, //creating a re-ocurring ground spawn. Object::Object(const EQEmu::ItemInstance* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,uint32 respawntimer) - : respawn_timer(respawntimer), decay_timer(300000) + : respawn_timer(respawntimer * 1000), decay_timer(300000) { user = nullptr; diff --git a/zone/spells.cpp b/zone/spells.cpp index c5a5a0b48..8117a8018 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3190,6 +3190,12 @@ uint32 Client::GetLastBuffSlot(bool disc, bool song) return GetCurrentBuffSlots(); } +bool Mob::HasDiscBuff() +{ + int slot = GetFirstBuffSlot(true, false); + return buffs[slot].spellid != SPELL_UNKNOWN; +} + // returns the slot the buff was added to, -1 if it wasn't added due to // stacking problems, and -2 if this is not a buff // if caster is null, the buff will be added with the caster level being @@ -4431,6 +4437,36 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) return false; } +int Mob::GetResist(uint8 resist_type) +{ + switch(resist_type) + { + case RESIST_FIRE: + return GetFR(); + case RESIST_COLD: + return GetCR(); + case RESIST_MAGIC: + return GetMR(); + case RESIST_DISEASE: + return GetDR(); + case RESIST_POISON: + return GetPR(); + case RESIST_CORRUPTION: + return GetCorrup(); + case RESIST_PRISMATIC: + return (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5; + case RESIST_CHROMATIC: + return std::min({GetFR(), GetCR(), GetMR(), GetDR(), GetPR()}); + case RESIST_PHYSICAL: + if (IsNPC()) + return GetPhR(); + else + return 0; + default: + return 0; + } +} + // // Spell resists: // returns an effectiveness index from 0 to 100. for most spells, 100 means @@ -4514,68 +4550,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use return 100; } - int target_resist; - switch(resist_type) - { - case RESIST_FIRE: - target_resist = GetFR(); - break; - case RESIST_COLD: - target_resist = GetCR(); - break; - case RESIST_MAGIC: - target_resist = GetMR(); - break; - case RESIST_DISEASE: - target_resist = GetDR(); - break; - case RESIST_POISON: - target_resist = GetPR(); - break; - case RESIST_CORRUPTION: - target_resist = GetCorrup(); - break; - case RESIST_PRISMATIC: - target_resist = (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5; - break; - case RESIST_CHROMATIC: - { - target_resist = GetFR(); - int temp = GetCR(); - if(temp < target_resist) - { - target_resist = temp; - } + int target_resist = GetResist(resist_type); - temp = GetMR(); - if(temp < target_resist) - { - target_resist = temp; - } - - temp = GetDR(); - if(temp < target_resist) - { - target_resist = temp; - } - - temp = GetPR(); - if(temp < target_resist) - { - target_resist = temp; - } + // JULY 24, 2002 changes + int level = GetLevel(); + if (IsPetOwnerClient() && caster->IsNPC() && !caster->IsPetOwnerClient()) { + auto owner = GetOwner(); + if (owner != nullptr) { + target_resist = std::max(target_resist, owner->GetResist(resist_type)); + level = owner->GetLevel(); } - break; - case RESIST_PHYSICAL: - { - if (IsNPC()) - target_resist = GetPhR(); - else - target_resist = 0; - } - default: - - target_resist = 0; } //Setup our base resist chance. @@ -4584,7 +4568,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use //Adjust our resist chance based on level modifiers uint8 caster_level = level_override > 0 ? level_override : caster->GetLevel(); - int temp_level_diff = GetLevel() - caster_level; + int temp_level_diff = level - caster_level; //Physical Resists are calclated using their own formula derived from extensive parsing. if (resist_type == RESIST_PHYSICAL) { @@ -4593,7 +4577,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use else { - if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff)) + if(IsNPC() && level >= RuleI(Casting,ResistFalloff)) { int a = (RuleI(Casting,ResistFalloff)-1) - caster_level; if(a > 0) @@ -4606,7 +4590,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } } - if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15) + if(IsClient() && level >= 21 && temp_level_diff > 15) { temp_level_diff = 15; } @@ -4622,16 +4606,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use level_mod = -level_mod; } - if(IsNPC() && (caster_level - GetLevel()) < -20) + if(IsNPC() && (caster_level - level) < -20) { level_mod = 1000; } //Even more level stuff this time dealing with damage spells - if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17) + if(IsNPC() && IsDamageSpell(spell_id) && level >= 17) { int level_diff; - if(GetLevel() >= RuleI(Casting,ResistFalloff)) + if(level >= RuleI(Casting,ResistFalloff)) { level_diff = (RuleI(Casting,ResistFalloff)-1) - caster_level; if(level_diff < 0) @@ -4641,7 +4625,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } else { - level_diff = GetLevel() - caster_level; + level_diff = level - caster_level; } level_mod += (2 * level_diff); } @@ -4752,17 +4736,17 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if(IsNPC()) { - if(GetLevel() > caster_level && GetLevel() >= 17 && caster_level <= 50) + if(level > caster_level && level >= 17 && caster_level <= 50) { partial_modifier += 5; } - if(GetLevel() >= 30 && caster_level < 50) + if(level >= 30 && caster_level < 50) { partial_modifier += (caster_level - 25); } - if(GetLevel() < 15) + if(level < 15) { partial_modifier -= 5; } @@ -4770,9 +4754,9 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if(caster->IsNPC()) { - if((GetLevel() - caster_level) >= 20) + if((level - caster_level) >= 20) { - partial_modifier += (GetLevel() - caster_level) * 1.5; + partial_modifier += (level - caster_level) * 1.5; } } diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index ca0d24175..7517b7fb8 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -176,9 +176,15 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot) cur_wp = -2; // flag as quest controlled w/no grid Log(Logs::Detail, Logs::AI, "MoveTo %s without a grid.", to_string(static_cast(position)).c_str()); } + + glm::vec3 dest(position); + + m_CurrentWayPoint = position; + m_CurrentWayPoint.z = GetFixedZ(dest); + if (saveguardspot) { - m_GuardPoint = position; + m_GuardPoint = m_CurrentWayPoint; if (m_GuardPoint.w == 0) m_GuardPoint.w = 0.0001; //hack to make IsGuarding simpler @@ -189,7 +195,6 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot) Log(Logs::Detail, Logs::AI, "Setting guard position to %s", to_string(static_cast(m_GuardPoint)).c_str()); } - m_CurrentWayPoint = position; cur_wp_pause = 0; pLastFightingDelayMoving = 0; if (AI_walking_timer->Enabled()) @@ -838,49 +843,61 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) { } } -void Mob::FixZ(int32 z_find_offset /*= 5*/) +float Mob::GetFixedZ(glm::vec3 dest, int32 z_find_offset) { - BenchTimer timer; timer.reset(); + float new_z = dest.z; - if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && (flymode != 1 && flymode != 2)) + if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && + (flymode != 1 && flymode != 2)) { - if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || - (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) + if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() + || (zone->HasWaterMap() && + !zone->watermap->InWater(glm::vec3(m_Position)))) { /* Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors */ - float new_z = this->FindGroundZ(m_Position.x, m_Position.y, z_find_offset); - new_z += this->GetZOffset(); + new_z = this->FindDestGroundZ(dest, z_find_offset); + if (new_z != BEST_Z_INVALID) + { + new_z += this->GetZOffset(); - auto duration = timer.elapsed(); - - Log( - Logs::Moderate, - Logs::FixZ, - "Mob::FixZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", - this->GetCleanName(), - new_z, - m_Position.x, - m_Position.y, - m_Position.z, - duration - ); - - if ((new_z > -2000) && new_z != BEST_Z_INVALID) { - if (RuleB(Map, MobZVisualDebug)) - this->SendAppearanceEffect(78, 0, 0, 0, 0); - - m_Position.z = new_z; + // If bad new Z restore old one + if (new_z < -2000) { + new_z = m_Position.z; + } } - else { - if (RuleB(Map, MobZVisualDebug)) - this->SendAppearanceEffect(103, 0, 0, 0, 0); + } - Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", this->GetCleanName(), std::abs(m_Position.z - new_z)); - } + auto duration = timer.elapsed(); - last_z = m_Position.z; + Log(Logs::Moderate, Logs::FixZ, + "Mob::GetFixedZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf", + this->GetCleanName(), new_z, dest.x, dest.y, dest.z, duration); + } + + return new_z; +} + +void Mob::FixZ(int32 z_find_offset /*= 5*/) +{ + glm::vec3 current_loc(m_Position); + float new_z = GetFixedZ(current_loc, z_find_offset); + + if (new_z != m_Position.z) + { + if ((new_z > -2000) && new_z != BEST_Z_INVALID) { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(78, 0, 0, 0, 0); + + m_Position.z = new_z; + } + else { + if (RuleB(Map, MobZVisualDebug)) + this->SendAppearanceEffect(103, 0, 0, 0, 0); + + Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", + this->GetCleanName(), std::abs(m_Position.z - new_z)); } } }