diff --git a/common/emu_constants.cpp b/common/emu_constants.cpp index 7c084bc9d..4d5c40b64 100644 --- a/common/emu_constants.cpp +++ b/common/emu_constants.cpp @@ -226,12 +226,12 @@ std::string EQ::constants::GetLDoNThemeName(uint32 theme_id) const std::map& EQ::constants::GetFlyModeMap() { static const std::map flymode_map = { - { EQ::constants::GravityBehavior::Ground, "Ground" }, - { EQ::constants::GravityBehavior::Flying, "Flying" }, - { EQ::constants::GravityBehavior::Levitating, "Levitating" }, - { EQ::constants::GravityBehavior::Water, "Water" }, - { EQ::constants::GravityBehavior::Floating, "Floating" }, - { EQ::constants::GravityBehavior::LevitateWhileRunning, "Levitating While Running" }, + { GravityBehavior::Ground, "Ground" }, + { GravityBehavior::Flying, "Flying" }, + { GravityBehavior::Levitating, "Levitating" }, + { GravityBehavior::Water, "Water" }, + { GravityBehavior::Floating, "Floating" }, + { GravityBehavior::LevitateWhileRunning, "Levitating While Running" }, }; return flymode_map; } @@ -337,3 +337,48 @@ std::string EQ::constants::GetAccountStatusName(uint8 account_status) return status_name; } + +const std::map& EQ::constants::GetConsiderLevelMap() +{ + static const std::map consider_level_map = { + { ConsiderLevel::Ally, "Ally" }, + { ConsiderLevel::Warmly, "Warmly" }, + { ConsiderLevel::Kindly, "Kindly" }, + { ConsiderLevel::Amiably, "Amiably" }, + { ConsiderLevel::Indifferently, "Indifferently" }, + { ConsiderLevel::Apprehensively, "Apprehensively" }, + { ConsiderLevel::Dubiously, "Dubiously" }, + { ConsiderLevel::Threateningly, "Threateningly" }, + { ConsiderLevel::Scowls, "Scowls" } + }; + return consider_level_map; +} + +std::string EQ::constants::GetConsiderLevelName(uint8 faction_consider_level) +{ + auto consider_levels = EQ::constants::GetConsiderLevelMap(); + if (!consider_levels[faction_consider_level].empty()) { + return consider_levels[faction_consider_level]; + } + return std::string(); +} + +const std::map& EQ::constants::GetEnvironmentalDamageMap() +{ + static const std::map damage_type_map = { + { EnvironmentalDamage::Lava, "Lava" }, + { EnvironmentalDamage::Drowning, "Drowning" }, + { EnvironmentalDamage::Falling, "Falling" }, + { EnvironmentalDamage::Trap, "Trap" } + }; + return damage_type_map; +} + +std::string EQ::constants::GetEnvironmentalDamageName(uint8 damage_type) +{ + if (EQ::ValueWithin(damage_type, EnvironmentalDamage::Lava, EnvironmentalDamage::Trap)) { + auto damage_types = EQ::constants::GetEnvironmentalDamageMap(); + return damage_types[damage_type]; + } + return std::string(); +} diff --git a/common/emu_constants.h b/common/emu_constants.h index 20985378f..f6272274e 100644 --- a/common/emu_constants.h +++ b/common/emu_constants.h @@ -230,6 +230,13 @@ namespace EQ LevitateWhileRunning }; + enum EnvironmentalDamage : uint8 { + Lava = 250, + Drowning, + Falling, + Trap + }; + const char *GetStanceName(StanceType stance_type); int ConvertStanceTypeToIndex(StanceType stance_type); @@ -248,6 +255,12 @@ namespace EQ extern const std::map& GetAccountStatusMap(); std::string GetAccountStatusName(uint8 account_status); + extern const std::map& GetConsiderLevelMap(); + std::string GetConsiderLevelName(uint8 consider_level); + + extern const std::map& GetEnvironmentalDamageMap(); + std::string GetEnvironmentalDamageName(uint8 damage_type); + const int STANCE_TYPE_FIRST = stancePassive; const int STANCE_TYPE_LAST = stanceBurnAE; const int STANCE_TYPE_COUNT = stanceBurnAE; @@ -391,4 +404,16 @@ enum AugmentActions : int { Destroy }; +enum ConsiderLevel : uint8 { + Ally = 1, + Warmly, + Kindly, + Amiably, + Indifferently, + Apprehensively, + Dubiously, + Threateningly, + Scowls +}; + #endif /*COMMON_EMU_CONSTANTS_H*/ diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 872f89859..49d6a7454 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -2762,7 +2762,7 @@ struct EnvDamage2_Struct { /*0004*/ uint16 unknown4; /*0006*/ uint32 damage; /*0010*/ uint8 unknown10[12]; -/*0022*/ uint8 dmgtype; //FA = Lava; FC = Falling +/*0022*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0023*/ uint8 unknown2[4]; /*0027*/ uint16 constant; //Always FFFF /*0029*/ uint16 unknown29; diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 229a3b3e7..5fa9a8c5f 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -3061,7 +3061,7 @@ struct EnvDamage2_Struct { /*0006*/ uint32 damage; /*0010*/ float unknown10; // New to Underfoot - Seen 1 /*0014*/ uint8 unknown14[12]; -/*0026*/ uint8 dmgtype; // FA = Lava; FC = Falling +/*0026*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0027*/ uint8 unknown27[4]; /*0031*/ uint16 unknown31; // New to Underfoot - Seen 66 /*0033*/ uint16 constant; // Always FFFF diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 27365a36d..e068e94a9 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -3032,7 +3032,7 @@ struct EnvDamage2_Struct { /*0006*/ uint32 damage; /*0010*/ float unknown10; // New to Underfoot - Seen 1 /*0014*/ uint8 unknown14[12]; -/*0026*/ uint8 dmgtype; // FA = Lava; FC = Falling +/*0026*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0027*/ uint8 unknown27[4]; /*0031*/ uint16 unknown31; // New to Underfoot - Seen 66 /*0033*/ uint16 constant; // Always FFFF diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 37392fc18..c39b5b65f 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -2539,7 +2539,7 @@ struct EnvDamage2_Struct { /*0004*/ uint16 unknown4; /*0006*/ uint32 damage; /*0010*/ uint8 unknown10[12]; -/*0022*/ uint8 dmgtype; //FA = Lava; FC = Falling +/*0022*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0023*/ uint8 unknown2[4]; /*0027*/ uint16 constant; //Always FFFF /*0029*/ uint16 unknown29; diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 3129e3b0b..d750d24ad 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -2509,7 +2509,7 @@ struct EnvDamage2_Struct { /*0004*/ uint16 unknown4; /*0006*/ uint32 damage; /*0010*/ uint8 unknown10[12]; -/*0022*/ uint8 dmgtype; //FA = Lava; FC = Falling +/*0022*/ uint8 dmgtype; // FA = Lava, FB = Drowning, FC = Falling, FD = Trap /*0023*/ uint8 unknown2[4]; /*0027*/ uint16 constant; //Always FFFF /*0029*/ uint16 unknown29; diff --git a/common/ruletypes.h b/common/ruletypes.h index 6dec50fa3..37be4a438 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -192,6 +192,7 @@ RULE_INT(Character, MagicianTrackingDistanceMultiplier, 0, "If you want magician RULE_INT(Character, EnchanterTrackingDistanceMultiplier, 0, "If you want enchanters to be able to track, increase this above 0. 0 disables tracking packets.") RULE_INT(Character, BeastlordTrackingDistanceMultiplier, 0, "If you want beastlords to be able to track, increase this above 0. 0 disables tracking packets.") RULE_INT(Character, BerserkerTrackingDistanceMultiplier, 0, "If you want berserkers to be able to track, increase this above 0. 0 disables tracking packets.") +RULE_BOOL(Character, OnInviteReceiveAlreadyinGroupMessage, true, "If you want clients to receive a message when trying to invite a player into a group that is currently in another group.") RULE_CATEGORY_END() RULE_CATEGORY(Mercs) diff --git a/zone/aa.cpp b/zone/aa.cpp index a05f9dc47..3607385d2 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1239,10 +1239,6 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { if (!IsValidSpell(rank->spell)) { return; } - //do not allow AA to cast if your actively casting another AA. - if (rank->spell == casting_spell_id && rank->id == casting_spell_aa_id) { - return; - } if (!CanUseAlternateAdvancementRank(rank)) { return; @@ -1257,13 +1253,11 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { uint32 charges = 0; // We don't have the AA - if (!GetAA(rank_id, &charges)) { + if (!GetAA(rank_id, &charges)) return; - } //if expendable make sure we have charges - if (ability->charges > 0 && charges < 1) { + if (ability->charges > 0 && charges < 1) return; - } //check cooldown if (!p_timers.Expired(&database, rank->spell_type + pTimerAAStart, false)) { @@ -1280,28 +1274,31 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { Message(Chat::Red, "You can use this ability again in %u minute(s) %u seconds", aaremain_min, aaremain_sec); } + return; } - if (!IsCastWhileInvis(rank->spell)) { - CommonBreakInvisible(); + int timer_duration = rank->recast_time - GetAlternateAdvancementCooldownReduction(rank); + if (timer_duration < 0) { + timer_duration = 0; } + if (!IsCastWhileInvis(rank->spell)) + CommonBreakInvisible(); + if (spells[rank->spell].sneak && (!hidden || (hidden && (Timer::GetCurrentTime() - tmHidden) < 4000))) { MessageString(Chat::SpellFailure, SNEAK_RESTRICT); return; } // // Modern clients don't require pet targeted for AA casts that are ST_Pet - if (spells[rank->spell].target_type == ST_Pet || spells[rank->spell].target_type == ST_SummonedPet) { + if (spells[rank->spell].target_type == ST_Pet || spells[rank->spell].target_type == ST_SummonedPet) target_id = GetPetID(); - } // extra handling for cast_not_standing spells if (!IgnoreCastingRestriction(rank->spell)) { - if (GetAppearance() == eaSitting) { // we need to stand! + if (GetAppearance() == eaSitting) // we need to stand! SetAppearance(eaStanding, false); - } if (GetAppearance() != eaStanding) { MessageString(Chat::SpellFailure, STAND_TO_CAST); @@ -1318,33 +1315,20 @@ void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { if (!DoCastingChecksOnCaster(rank->spell)) { return; } - SpellFinished(rank->spell, entity_list.GetMob(target_id), EQ::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].resist_difficulty, false, -1, false, rank->id); + + if (!SpellFinished(rank->spell, entity_list.GetMob(target_id), EQ::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].resist_difficulty, false, -1, + rank->spell_type + pTimerAAStart, timer_duration, false, rank->id)) { + return; + } } - //Known issue: If you attempt to give a Bard an AA with a cast time, the cast timer will not display on the client (no live bard AA have cast time). else { - CastSpell(rank->spell, target_id, EQ::spells::CastingSlot::AltAbility, -1, -1, 0, -1, 0xFFFFFFFF, 0, nullptr, rank->id); + if (!CastSpell(rank->spell, target_id, EQ::spells::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, timer_duration, nullptr, rank->id)) { + return; + } } } } -void Client::SetAARecastTimer(AA::Rank *rank_in, int32 spell_id) { - - if (!rank_in) { - return; - } - - //calculate AA cooldown - int timer_duration = rank_in->recast_time - GetAlternateAdvancementCooldownReduction(rank_in); - - if (timer_duration <= 0) { - return; - } - - CastToClient()->GetPTimers().Start(rank_in->spell_type + pTimerAAStart, timer_duration); - CastToClient()->SendAlternateAdvancementTimer(rank_in->spell_type, 0, 0); - LogSpells("Spell [{}]: Setting AA reuse timer [{}] to [{}]", spell_id, rank_in->spell_type + pTimerAAStart, timer_duration); -} - int Mob::GetAlternateAdvancementCooldownReduction(AA::Rank *rank_in) { if(!rank_in) { return 0; @@ -1376,7 +1360,6 @@ int Mob::GetAlternateAdvancementCooldownReduction(AA::Rank *rank_in) { } void Mob::ExpendAlternateAdvancementCharge(uint32 aa_id) { - for (auto &iter : aa_ranks) { AA::Ability *ability = zone->GetAlternateAdvancementAbility(iter.first); if (ability && aa_id == ability->id) { diff --git a/zone/client.h b/zone/client.h index 5cbc203ac..6b8c1e1a1 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1495,8 +1495,6 @@ public: void SendItemRecastTimer(int32 recast_type, uint32 recast_delay = 0); void SetItemRecastTimer(int32 spell_id, uint32 inventory_slot); bool HasItemRecastTimer(int32 spell_id, uint32 inventory_slot); - void SetDisciplineRecastTimer(int32 spell_id); - void SetAARecastTimer(AA::Rank *rank_in, int32 spell_id); inline bool AggroMeterAvailable() const { return ((m_ClientVersionBit & EQ::versions::maskRoF2AndLater)) && RuleB(Character, EnableAggroMeter); } // RoF untested inline void SetAggroMeterLock(int in) { m_aggrometer.set_lock_id(in); } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 71af0465c..fcc2c76e2 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -5784,8 +5784,7 @@ void Client::Handle_OP_EndLootRequest(const EQApplicationPacket *app) void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app) { - if (!ClientFinishedLoading()) - { + if (!ClientFinishedLoading()) { SetHP(GetHP() - 1); return; } @@ -5795,38 +5794,46 @@ void Client::Handle_OP_EnvDamage(const EQApplicationPacket *app) DumpPacket(app); return; } + EnvDamage2_Struct* ed = (EnvDamage2_Struct*)app->pBuffer; - - int damage = ed->damage; - - if (ed->dmgtype == 252) { - - int mod = spellbonuses.ReduceFallDamage + itembonuses.ReduceFallDamage + aabonuses.ReduceFallDamage; - + auto damage = ed->damage; + if (ed->dmgtype == EQ::constants::EnvironmentalDamage::Falling) { + uint32 mod = spellbonuses.ReduceFallDamage + itembonuses.ReduceFallDamage + aabonuses.ReduceFallDamage; damage -= damage * mod / 100; } - if (damage < 0) + if (damage < 0) { damage = 31337; + } if (admin >= minStatusToAvoidFalling && GetGM()) { - Message(Chat::Red, "Your GM status protects you from %i points of type %i environmental damage.", ed->damage, ed->dmgtype); + Message( + Chat::Red, + fmt::format( + "Your GM status protects you from {} points of {} (Type {}) damage.", + ed->damage, + EQ::constants::GetEnvironmentalDamageName(ed->dmgtype), + ed->dmgtype + ).c_str() + ); SetHP(GetHP() - 1);//needed or else the client wont acknowledge return; - } - else if (GetInvul()) { - Message(Chat::Red, "Your invuln status protects you from %i points of type %i environmental damage.", ed->damage, ed->dmgtype); + } else if (GetInvul()) { + Message( + Chat::Red, + fmt::format( + "Your invulnerability protects you from {} points of {} (Type {}) damage.", + ed->damage, + EQ::constants::GetEnvironmentalDamageName(ed->dmgtype), + ed->dmgtype + ).c_str() + ); SetHP(GetHP() - 1);//needed or else the client wont acknowledge return; - } - else if (zone->GetZoneID() == 183 || zone->GetZoneID() == 184) { - // Hard coded tutorial and load zones for no fall damage + } else if (zone->GetZoneID() == Zones::TUTORIAL || zone->GetZoneID() == Zones::LOAD) { // Hard coded tutorial and load zones for no fall damage return; - } - else { + } else { SetHP(GetHP() - (damage * RuleR(Character, EnvironmentDamageMulipliter))); - - /* EVENT_ENVIRONMENTAL_DAMAGE */ int final_damage = (damage * RuleR(Character, EnvironmentDamageMulipliter)); std::string export_string = fmt::format( "{} {} {}", @@ -6953,6 +6960,13 @@ void Client::Handle_OP_GroupInvite2(const EQApplicationPacket *app) Invitee->CastToClient()->QueuePacket(app); } } + else { + if (RuleB(Character, OnInviteReceiveAlreadyinGroupMessage)) { + if (!Invitee->CastToClient()->MercOnlyOrNoGroup()) { + Message(Chat::LightGray, "%s is already in another group.", Invitee->GetCleanName()); + } + } + } } #ifdef BOTS else if (Invitee->IsBot()) { diff --git a/zone/command.cpp b/zone/command.cpp index 2eee4e1f1..36da9fc95 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -183,7 +183,7 @@ int command_init(void) command_add("findzone", "[search criteria] - Search database zones", AccountStatus::GMAdmin, command_findzone) || command_add("fixmob", "[race|gender|texture|helm|face|hair|haircolor|beard|beardcolor|heritage|tattoo|detail] [next|prev] - Manipulate appearance of your target", AccountStatus::QuestTroupe, command_fixmob) || command_add("flag", "[status] [acctname] - Refresh your admin status, or set an account's admin status if arguments provided", AccountStatus::Player, command_flag) || - command_add("flagedit", "- Edit zone flags on your target", AccountStatus::GMAdmin, command_flagedit) || + command_add("flagedit", "- Edit zone flags on your target. Use #flagedit help for more info.", AccountStatus::GMAdmin, command_flagedit) || command_add("flags", "- displays the flags of you or your target", AccountStatus::Player, command_flags) || command_add("flymode", "[0/1/2/3/4/5] - Set your or your player target's flymode to ground/flying/levitate/water/floating/levitate_running", AccountStatus::Guide, command_flymode) || command_add("fov", "- Check wether you're behind or in your target's field of view", AccountStatus::QuestTroupe, command_fov) || @@ -209,7 +209,7 @@ int command_init(void) command_add("hair", "- Change the hair style of your target", AccountStatus::QuestTroupe, command_hair) || command_add("haircolor", "- Change the hair color of your target", AccountStatus::QuestTroupe, command_haircolor) || command_add("haste", "[percentage] - Set your haste percentage", AccountStatus::GMAdmin, command_haste) || - command_add("hatelist", " - Display hate list for target.", AccountStatus::QuestTroupe, command_hatelist) || + command_add("hatelist", "- Display hate list for NPC.", AccountStatus::QuestTroupe, command_hatelist) || command_add("heal", "- Completely heal your target", AccountStatus::Steward, command_heal) || command_add("helm", "- Change the helm of your target", AccountStatus::QuestTroupe, command_helm) || command_add("help", "[search term] - List available commands and their description, specify partial command as argument to search", AccountStatus::Player, command_help) || @@ -252,14 +252,14 @@ int command_init(void) command_add("myskills", "- Show details about your current skill levels", AccountStatus::Player, command_myskills) || command_add("mysql", "[Help|Query] [SQL Query] - Mysql CLI, see 'Help' for options.", AccountStatus::GMImpossible, command_mysql) || command_add("mystats", "- Show details about you or your pet", AccountStatus::Guide, command_mystats) || - command_add("name", "[newname] - Rename your player target", AccountStatus::GMLeadAdmin, command_name) || + command_add("name", "[New Name] - Rename your player target", AccountStatus::GMLeadAdmin, command_name) || command_add("netstats", "- Gets the network stats for a stream.", AccountStatus::GMMgmt, command_netstats) || command_add("network", "- Admin commands for the udp network interface.", AccountStatus::GMImpossible, command_network) || command_add("npccast", "[targetname/entityid] [spellid] - Causes NPC target to cast spellid on targetname/entityid", AccountStatus::QuestTroupe, command_npccast) || command_add("npcedit", "[column] [value] - Mega NPC editing command", AccountStatus::GMAdmin, command_npcedit) || command_add("npceditmass", "[name-search] [column] [value] - Mass (Zone wide) NPC data editing command", AccountStatus::GMAdmin, command_npceditmass) || command_add("npcemote", "[message] - Make your NPC target emote a message.", AccountStatus::GMLeadAdmin, command_npcemote) || - command_add("npcloot", "[show/money/add/remove] [itemid/all/money: pp gp sp cp] - Manipulate the loot an NPC is carrying", AccountStatus::QuestTroupe, command_npcloot) || + command_add("npcloot", "- Manipulate the loot an NPC is carrying. Use #npcloot help for more information.", AccountStatus::QuestTroupe, command_npcloot) || command_add("npcsay", "[message] - Make your NPC target say a message.", AccountStatus::GMLeadAdmin, command_npcsay) || command_add("npcshout", "[message] - Make your NPC target shout a message.", AccountStatus::GMLeadAdmin, command_npcshout) || command_add("npcspawn", "[create/add/update/remove/delete] - Manipulate spawn DB", AccountStatus::GMAreas, command_npcspawn) || @@ -378,6 +378,7 @@ int command_init(void) command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", AccountStatus::QuestTroupe, command_trapinfo) || command_add("tune", "Calculate statistical values related to combat.", AccountStatus::GMAdmin, command_tune) || command_add("ucs", "- Attempts to reconnect to the UCS server", AccountStatus::Player, command_ucs) || + command_add("undye", "- Remove dye from all of your or your target's armor slots", AccountStatus::GMAdmin, command_undye) || command_add("undyeme", "- Remove dye from all of your armor slots", AccountStatus::Player, command_undyeme) || command_add("unfreeze", "- Unfreeze your target", AccountStatus::QuestTroupe, command_unfreeze) || command_add("unlock", "- Unlock the worldserver", AccountStatus::GMLeadAdmin, command_unlock) || diff --git a/zone/effects.cpp b/zone/effects.cpp index 98ebb32dd..aa41f35a0 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -800,15 +800,50 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) { return false; } - if (GetClass() == BARD && IsCasting() && spells[spell_id].cast_time == 0) { - if (DoCastingChecksOnCaster(spell_id)) { - SpellFinished(spell_id, entity_list.GetMob(target), EQ::spells::CastingSlot::Discipline); + bool instant_recast = true; + + if (spell.recast_time > 0) { + uint32 reduced_recast = spell.recast_time / 1000; + auto focus = GetFocusEffect(focusReduceRecastTime, spell_id); + // do stupid stuff because custom servers. + // we really should be able to just do the -= focus but since custom servers could have shorter reuse timers + // we have to make sure we don't underflow the uint32 ... + // and yes, the focus effect can be used to increase the durations (spell 38944) + if (focus > reduced_recast) { + reduced_recast = 0; + if (GetPTimers().Enabled((uint32)DiscTimer)) + GetPTimers().Clear(&database, (uint32)DiscTimer); + } + else { + reduced_recast -= focus; + } + + if (reduced_recast > 0) { + instant_recast = false; + + if (GetClass() == BARD && IsCasting() && spells[spell_id].cast_time == 0) { + if (DoCastingChecksOnCaster(spell_id)) { + SpellFinished(spell_id, entity_list.GetMob(target), EQ::spells::CastingSlot::Discipline, 0, -1, spells[spell_id].resist_difficulty, false, -1, (uint32)DiscTimer, reduced_recast, false); + } + } + else { + CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); + } + + SendDisciplineTimer(spells[spell_id].timer_id, reduced_recast); } } - else { - CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline); - } + if (instant_recast) { + if (GetClass() == BARD && IsCasting() && spells[spell_id].cast_time == 0) { + if (DoCastingChecksOnCaster(spell_id)) { + SpellFinished(spell_id, entity_list.GetMob(target), EQ::spells::CastingSlot::Discipline, 0, -1, spells[spell_id].resist_difficulty, false, -1, 0xFFFFFFFF, 0, false); + } + } + else { + CastSpell(spell_id, target, EQ::spells::CastingSlot::Discipline); + } + } return(true); } diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 67034c4ab..04a8654f1 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -8092,6 +8092,39 @@ XS(XS__getbodytypename) { } } +XS(XS__getconsiderlevelname); +XS(XS__getconsiderlevelname) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getconsiderlevelname(uint8 consider_level)"); + { + dXSTARG; + uint8 consider_level = (uint8) SvUV(ST(0)); + std::string consider_level_name = quest_manager.getconsiderlevelname(consider_level); + + sv_setpv(TARG, consider_level_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); + } +} + +XS(XS__getenvironmentaldamagename); +XS(XS__getenvironmentaldamagename) { + dXSARGS; + if (items != 1) + Perl_croak(aTHX_ "Usage: quest::getenvironmentaldamagename(uint8 damage_type)"); + + dXSTARG; + uint8 damage_type = (uint8) SvIV(ST(0)); + std::string environmental_damage_name = quest_manager.getenvironmentaldamagename(damage_type); + + sv_setpv(TARG, environmental_damage_name.c_str()); + XSprePUSH; + PUSHTARG; + XSRETURN(1); +} + /* This is the callback perl will look for to setup the quest package's XSUBs @@ -8376,6 +8409,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "getcharidbyname"), XS__getcharidbyname, file); newXS(strcpy(buf, "getclassname"), XS__getclassname, file); newXS(strcpy(buf, "getcleannpcnamebyid"), XS__getcleannpcnamebyid, file); + newXS(strcpy(buf, "getconsiderlevelname"), XS__getconsiderlevelname, file); newXS(strcpy(buf, "gethexcolorcode"), XS__gethexcolorcode, file); newXS(strcpy(buf, "getcurrencyid"), XS__getcurrencyid, file); newXS(strcpy(buf, "getexpmodifierbycharid"), XS__getexpmodifierbycharid, file); @@ -8398,6 +8432,7 @@ EXTERN_C XS(boot_quest) { newXS(strcpy(buf, "getcurrencyitemid"), XS__getcurrencyitemid, file); newXS(strcpy(buf, "getgendername"), XS__getgendername, file); newXS(strcpy(buf, "getdeityname"), XS__getdeityname, file); + newXS(strcpy(buf, "getenvironmentaldamagename"), XS__getenvironmentaldamagename, file); newXS(strcpy(buf, "getguildnamebyid"), XS__getguildnamebyid, file); newXS(strcpy(buf, "getguildidbycharid"), XS__getguildidbycharid, file); newXS(strcpy(buf, "getgroupidbycharid"), XS__getgroupidbycharid, file); diff --git a/zone/gm_commands/ai.cpp b/zone/gm_commands/ai.cpp index 93f9a057f..e7f686307 100755 --- a/zone/gm_commands/ai.cpp +++ b/zone/gm_commands/ai.cpp @@ -2,138 +2,262 @@ void command_ai(Client *c, const Seperator *sep) { - Mob *target = c->GetTarget(); + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #ai consider [Mob Name] - Show how an NPC considers to a mob"); + c->Message(Chat::White, "Usage: #ai faction [Faction ID] - Set an NPC's Faction ID"); + c->Message(Chat::White, "Usage: #ai guard - Save an NPC's guard spot to their current location"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Min X] [Max X] [Min Y] [Max Y] [Delay] [Minimum Delay] - Set an NPC's roambox using X and Y coordinates"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Roam Distance] [Delay] [Minimum Delay] - Set an NPC's roambox using roam distance"); + c->Message(Chat::White, "Usage: #ai spells [Spell List ID] - Set an NPC's Spell List ID"); + return; + } - if (strcasecmp(sep->arg[1], "factionid") == 0) { - if (target && sep->IsNumber(2)) { - if (target->IsNPC()) { - target->CastToNPC()->SetNPCFactionID(atoi(sep->arg[2])); - } - else { - c->Message(Chat::White, "%s is not an NPC.", target->GetName()); - } - } - else { - c->Message(Chat::White, "Usage: (targeted) #ai factionid [factionid]"); - } + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; } - else if (strcasecmp(sep->arg[1], "spellslist") == 0) { - if (target && sep->IsNumber(2) && atoi(sep->arg[2]) >= 0) { - if (target->IsNPC()) { - target->CastToNPC()->AI_AddNPCSpells(atoi(sep->arg[2])); - } - else { - c->Message(Chat::White, "%s is not an NPC.", target->GetName()); - } - } - else { - c->Message(Chat::White, "Usage: (targeted) #ai spellslist [npc_spells_id]"); - } + + auto target = c->GetTarget()->CastToNPC(); + + bool is_consider = !strcasecmp(sep->arg[1], "consider"); + bool is_faction = !strcasecmp(sep->arg[1], "faction"); + bool is_guard = !strcasecmp(sep->arg[1], "guard"); + bool is_roambox = !strcasecmp(sep->arg[1], "roambox"); + bool is_spells = !strcasecmp(sep->arg[1], "spells"); + + if ( + !is_consider && + !is_faction && + !is_guard && + !is_roambox && + !is_spells + ) { + c->Message(Chat::White, "Usage: #ai consider [Mob Name] - Show how an NPC considers to a mob"); + c->Message(Chat::White, "Usage: #ai faction [Faction ID] - Set an NPC's Faction ID"); + c->Message(Chat::White, "Usage: #ai guard - Save an NPC's guard spot to their current location"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Min X] [Max X] [Min Y] [Max Y] [Delay] [Minimum Delay] - Set an NPC's roambox using X and Y coordinates"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Roam Distance] [Delay] [Minimum Delay] - Set an NPC's roambox using roam distance"); + c->Message(Chat::White, "Usage: #ai spells [Spell List ID] - Set an NPC's Spell List ID"); + return; } - else if (strcasecmp(sep->arg[1], "con") == 0) { - if (target && sep->arg[2][0] != 0) { - Mob *tar2 = entity_list.GetMob(sep->arg[2]); - if (tar2) { + + if (is_consider) { + if (arguments == 2) { + auto mob_name = sep->arg[2]; + auto mob_to_consider = entity_list.GetMob(mob_name); + if (mob_to_consider) { + auto consider_level = static_cast(mob_to_consider->GetReverseFactionCon(target)); c->Message( Chat::White, - "%s considering %s: %i", - target->GetName(), - tar2->GetName(), - tar2->GetReverseFactionCon(target)); - } - else { - c->Message(Chat::White, "Error: %s not found.", sep->arg[2]); - } - } - else { - c->Message(Chat::White, "Usage: (targeted) #ai con [mob name]"); - } - } - else if (strcasecmp(sep->arg[1], "guard") == 0) { - if (target && target->IsNPC()) { - target->CastToNPC()->SaveGuardSpot(target->GetPosition()); - } - else { - c->Message( - Chat::White, - "Usage: (targeted) #ai guard - sets npc to guard the current location (use #summon to move)" - ); - } - } - else if (strcasecmp(sep->arg[1], "roambox") == 0) { - if (target && target->IsAIControlled() && target->IsNPC()) { - if ((sep->argnum == 6 || sep->argnum == 7 || sep->argnum == 8) && sep->IsNumber(2) && sep->IsNumber(3) && - sep->IsNumber(4) && sep->IsNumber(5) && sep->IsNumber(6)) { - uint32 tmp = 2500; - uint32 tmp2 = 2500; - if (sep->IsNumber(7)) { - tmp = atoi(sep->arg[7]); - } - if (sep->IsNumber(8)) { - tmp2 = atoi(sep->arg[8]); - } - target->CastToNPC()->AI_SetRoambox( - atof(sep->arg[2]), - atof(sep->arg[3]), - atof(sep->arg[4]), - atof(sep->arg[5]), - atof(sep->arg[6]), - tmp, - tmp2 + fmt::format( + "{} ({}) considers {} ({}) as {} ({}).", + target->GetCleanName(), + target->GetID(), + mob_to_consider->GetCleanName(), + mob_to_consider->GetID(), + EQ::constants::GetConsiderLevelName(consider_level), + consider_level + ).c_str() ); } - else if ((sep->argnum == 3 || sep->argnum == 4) && sep->IsNumber(2) && sep->IsNumber(3)) { - uint32 tmp = 2500; - uint32 tmp2 = 2500; + } else { + c->Message(Chat::White, "Usage: #ai consider [Mob Name] - Show how an NPC considers a mob"); + } + } else if (is_faction) { + if (sep->IsNumber(2)) { + auto faction_id = std::stoi(sep->arg[2]); + auto faction_name = content_db.GetFactionName(faction_id); + target->SetNPCFactionID(faction_id); + c->Message( + Chat::White, + fmt::format( + "{} ({}) is now on Faction {}.", + target->GetCleanName(), + target->GetID(), + ( + faction_name.empty() ? + std::to_string(faction_id) : + fmt::format( + "{} ({})", + faction_name, + faction_id + ) + ) + ).c_str() + ); + } else { + c->Message(Chat::White, "Usage: #ai faction [Faction ID] - Set an NPC's Faction ID"); + } + } else if (is_guard) { + auto target_position = target->GetPosition(); + + target->SaveGuardSpot(target_position); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has a guard spot of {:.2f}, {:.2f}, {:.2f} with a heading of {:.2f}.", + target->GetCleanName(), + target->GetID(), + target_position.x, + target_position.y, + target_position.z, + target_position.w + ).c_str() + ); + } else if (is_roambox) { + if (target->IsAIControlled()) { + if ( + arguments >= 6 && + arguments <= 8 && + sep->IsNumber(2) && + sep->IsNumber(3) && + sep->IsNumber(4) && + sep->IsNumber(5) && + sep->IsNumber(6) + ) { + auto distance = std::stof(sep->arg[2]); + auto min_x = std::stof(sep->arg[3]); + auto max_x = std::stof(sep->arg[4]); + auto min_y = std::stof(sep->arg[5]); + auto max_y = std::stof(sep->arg[6]); + + uint32 delay = 2500; + uint32 minimum_delay = 2500; + + if (sep->IsNumber(7)) { + delay = std::stoul(sep->arg[7]); + } + + if (sep->IsNumber(8)) { + minimum_delay = std::stoul(sep->arg[8]); + } + + target->CastToNPC()->AI_SetRoambox( + distance, + max_x, + min_x, + max_y, + min_y, + delay, + minimum_delay + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has a roambox from {}, {} to {}, {} with {} and {} and a distance of {}.", + target->GetCleanName(), + target->GetID(), + min_x, + min_y, + max_x, + max_y, + ( + delay ? + fmt::format( + "a delay of {} ({})", + ConvertMillisecondsToTime(delay), + delay + ): + "no delay" + ), + ( + minimum_delay ? + fmt::format( + "a minimum delay of {} ({})", + ConvertMillisecondsToTime(minimum_delay), + minimum_delay + ): + "no minimum delay" + ), + distance + ).c_str() + ); + } else if ( + arguments >= 3 && + arguments <= 4 && + sep->IsNumber(2) && + sep->IsNumber(3) + ) { + auto max_distance = std::stof(sep->arg[2]); + auto roam_distance_variance = std::stof(sep->arg[3]); + + uint32 delay = 2500; + uint32 minimum_delay = 2500; + if (sep->IsNumber(4)) { - tmp = atoi(sep->arg[4]); + delay = std::stoul(sep->arg[4]); } + if (sep->IsNumber(5)) { - tmp2 = atoi(sep->arg[5]); + minimum_delay = std::stoul(sep->arg[5]); } - target->CastToNPC()->AI_SetRoambox(atof(sep->arg[2]), atof(sep->arg[3]), tmp, tmp2); + + target->CastToNPC()->AI_SetRoambox( + max_distance, + roam_distance_variance, + delay, + minimum_delay + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has a roambox with a max distance of {} and a roam distance variance of {} with {} and {}.", + target->GetCleanName(), + target->GetID(), + max_distance, + roam_distance_variance, + ( + delay ? + fmt::format( + "a delay of {} ({})", + delay, + ConvertMillisecondsToTime(delay) + ): + "no delay" + ), + ( + minimum_delay ? + fmt::format( + "a minimum delay of {} ({})", + minimum_delay, + ConvertMillisecondsToTime(delay) + ): + "no minimum delay" + ) + ).c_str() + ); + } else { + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Min X] [Max X] [Min Y] [Max Y] [Delay] [Minimum Delay] - Set an NPC's roambox using X and Y coordinates"); + c->Message(Chat::White, "Usage: #ai roambox [Distance] [Roam Distance] [Delay] [Minimum Delay] - Set an NPC's roambox using roam distance"); } - else { - c->Message(Chat::White, "Usage: #ai roambox dist max_x min_x max_y min_y [delay] [mindelay]"); - c->Message(Chat::White, "Usage: #ai roambox dist roamdist [delay] [mindelay]"); + } else { + c->Message(Chat::White, "You must target an NPC with AI."); + } + } else if (is_spells) { + if (sep->IsNumber(2)) { + auto spell_list_id = std::stoul(sep->arg[2]); + if (spell_list_id >= 0) { + target->CastToNPC()->AI_AddNPCSpells(spell_list_id); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) is now using Spell List {}.", + target->GetCleanName(), + target->GetID(), + spell_list_id + ).c_str() + ); + } else { + c->Message(Chat::White, "Spell List ID must be greater than or equal to 0."); } + } else { + c->Message(Chat::White, "Usage: #ai spells [Spell List ID] - Set an NPC's Spell List ID"); } - else { - c->Message(Chat::White, "You need a AI NPC targeted"); - } - } - else if (strcasecmp(sep->arg[1], "stop") == 0 && c->Admin() >= commandToggleAI) { - if (target) { - if (target->IsAIControlled()) { - target->AI_Stop(); - } - else { - c->Message(Chat::White, "Error: Target is not AI controlled"); - } - } - else { - c->Message(Chat::White, "Usage: Target a Mob with AI enabled and use this to turn off their AI."); - } - } - else if (strcasecmp(sep->arg[1], "start") == 0 && c->Admin() >= commandToggleAI) { - if (target) { - if (!target->IsAIControlled()) { - target->AI_Start(); - } - else { - c->Message(Chat::White, "Error: Target is already AI controlled"); - } - } - else { - c->Message(Chat::White, "Usage: Target a Mob with AI disabled and use this to turn on their AI."); - } - } - else { - c->Message(Chat::White, "#AI Sub-commands"); - c->Message(Chat::White, " factionid"); - c->Message(Chat::White, " spellslist"); - c->Message(Chat::White, " con"); - c->Message(Chat::White, " guard"); } } diff --git a/zone/gm_commands/flagedit.cpp b/zone/gm_commands/flagedit.cpp index 7e0bff517..2b3cf746b 100755 --- a/zone/gm_commands/flagedit.cpp +++ b/zone/gm_commands/flagedit.cpp @@ -2,156 +2,289 @@ void command_flagedit(Client *c, const Seperator *sep) { - //super-command for editing zone flags - if (sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { - c->Message(Chat::White, "Syntax: #flagedit [lockzone|unlockzone|listzones|give|take]."); + int arguments = sep->argnum; + if (!arguments) { + auto flags_link = EQ::SayLinkEngine::GenerateQuestSaylink("#flags", false, "#flags"); c->Message( Chat::White, - "...lockzone [zone id/short] [flag name] - Set the specified flag name on the zone, locking the zone" + "Usage: #flagedit lock [Zone ID|Zone Short Name] [Flag Name] - Set the specified flag name on the zone, locking the zone" ); - c->Message(Chat::White, "...unlockzone [zone id/short] - Removes the flag requirement from the specified zone"); - c->Message(Chat::White, "...listzones - List all zones which require a flag, and their flag's name"); - c->Message(Chat::White, "...give [zone id/short] - Give your target the zone flag for the specified zone."); c->Message( Chat::White, - "...take [zone id/short] - Take the zone flag for the specified zone away from your target" + "Usage: #flagedit unlock [Zone ID|Zone Short Name] - Removes the flag requirement from the specified zone" + ); + c->Message( + Chat::White, + "Usage: #flagedit list - List all zones which require a flag, and their flag's name" + ); + c->Message( + Chat::White, + "Usage: #flagedit give [Zone ID|Zone Short Name] - Give your target the zone flag for the specified zone." + ); + c->Message( + Chat::White, + "Usage: #flagedit take [Zone ID|Zone Short Name] - Take the zone flag for the specified zone away from your target" + ); + c->Message( + Chat::White, + fmt::format( + "Note: Use {} to view the flags a player has.", + flags_link + ).c_str() ); - c->Message(Chat::White, "...Note: use #flags to view flags on a person"); return; } - if (!strcasecmp(sep->arg[1], "lockzone")) { - uint32 zoneid = 0; - if (sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if (zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); + bool is_give = !strcasecmp(sep->arg[1], "give"); + bool is_list = !strcasecmp(sep->arg[1], "list"); + bool is_lock = !strcasecmp(sep->arg[1], "lock"); + bool is_take = !strcasecmp(sep->arg[1], "take"); + bool is_unlock = !strcasecmp(sep->arg[1], "unlock"); + + if ( + !is_give && + !is_list && + !is_lock && + !is_take && + !is_unlock + ) { + auto flags_link = EQ::SayLinkEngine::GenerateQuestSaylink("#flags", false, "#flags"); + c->Message( + Chat::White, + "Usage: #flagedit lock [Zone ID|Zone Short Name] [Flag Name] - Set the specified flag name on the zone, locking the zone" + ); + c->Message( + Chat::White, + "Usage: #flagedit unlock [Zone ID|Zone Short Name] - Removes the flag requirement from the specified zone" + ); + c->Message( + Chat::White, + "Usage: #flagedit list - List all zones which require a flag, and their flag's name" + ); + c->Message( + Chat::White, + "Usage: #flagedit give [Zone ID|Zone Short Name] - Give your target the zone flag for the specified zone." + ); + c->Message( + Chat::White, + "Usage: #flagedit take [Zone ID|Zone Short Name] - Take the zone flag for the specified zone away from your target" + ); + c->Message( + Chat::White, + fmt::format( + "Note: Use {} to view the flags a player has.", + flags_link + ).c_str() + ); + return; + } + + if (is_give) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + std::string zone_long_name = ZoneLongName(zone_id); + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); } - } - if (zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); - return; - } - char flag_name[128]; - if (sep->argplus[3][0] == '\0') { - c->Message(Chat::Red, "flag name required. see help."); - return; - } - database.DoEscapeString(flag_name, sep->argplus[3], 64); - flag_name[127] = '\0'; - - std::string query = StringFormat( - "UPDATE zone SET flag_needed = '%s' " - "WHERE zoneidnumber = %d AND version = %d", - flag_name, zoneid, zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::Red, "Error updating zone: %s", results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::Yellow, "Success! Zone %s now requires a flag, named %s", ZoneName(zoneid), flag_name); - return; - } - - if (!strcasecmp(sep->arg[1], "unlockzone")) { - uint32 zoneid = 0; - if (sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if (zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); - } - } - - if (zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); - return; - } - - std::string query = StringFormat( - "UPDATE zone SET flag_needed = '' " - "WHERE zoneidnumber = %d AND version = %d", - zoneid, zone->GetInstanceVersion()); - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - c->Message(Chat::Yellow, "Error updating zone: %s", results.ErrorMessage().c_str()); - return; - } - - c->Message(Chat::Yellow, "Success! Zone %s no longer requires a flag.", ZoneName(zoneid)); - return; - } - - if (!strcasecmp(sep->arg[1], "listzones")) { - std::string query = "SELECT zoneidnumber, short_name, long_name, version, flag_needed " - "FROM zone WHERE flag_needed != ''"; - auto results = content_db.QueryDatabase(query); - if (!results.Success()) { - return; - } - - c->Message(Chat::White, "Zones which require flags:"); - for (auto row = results.begin(); row != results.end(); ++row) + target->SetZoneFlag(zone_id); c->Message( Chat::White, - "Zone %s (%s,%s) version %s requires key %s", - row[2], + fmt::format( + "{} now {} the flag for {} ({}).", + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ), + c == target ? "have" : "has", + zone_long_name, + zone_id + ).c_str() + ); + return; + } + } else if (is_list) { + std::string query = SQL( + SELECT long_name, zoneidnumber, version, flag_needed + FROM zone + WHERE flag_needed != '' + ORDER BY long_name ASC + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + return; + } + + std::string popup_text = ""; + + popup_text += ""; + + for (auto row : results) { + popup_text += fmt::format( + "", row[0], row[1], - row[3], - row[4] + ( + std::stoi(row[2]) != 0 ? + fmt::format( + "[Version {}]", + row[2] + ) : + "" + ), + row[3] + ); + } + + popup_text += "
ZoneFlag Required
{} ({}){}{}
"; + + c->SendPopupToClient( + "Zone Flags", + popup_text.c_str() + ); + + return; + } else if (is_lock) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + if (arguments < 3) { + c->Message( + Chat::White, + "Usage: #flagedit lock [Zone ID|Zone Short Name] [Flag Name] - Set the specified flag name on the zone, locking the zone" + ); + return; + } + + std::string flag_name = EscapeString(sep->argplus[3]); + std::string zone_long_name = ZoneLongName(zone_id); + + auto query = fmt::format( + SQL( + UPDATE zone + SET flag_needed = '{}' + WHERE zoneidnumber = {} AND version = {} + ), + flag_name, + zone_id, + zone->GetInstanceVersion() ); - return; - } - - if (!strcasecmp(sep->arg[1], "give")) { - uint32 zoneid = 0; - if (sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if (zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message( + Chat::White, + fmt::format( + "Error updating zone flag for {} ({}).", + zone_long_name, + zone_id + ).c_str() + ); + return; } - } - if (zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now requires a flag, named {}.", + zone_long_name, + zone_id, + flag_name + ).c_str() + ); return; } - - Mob *t = c->GetTarget(); - if (t == nullptr || !t->IsClient()) { - c->Message(Chat::Red, "client target required"); - return; - } - - t->CastToClient()->SetZoneFlag(zoneid); - return; - } - - if (!strcasecmp(sep->arg[1], "give")) { - uint32 zoneid = 0; - if (sep->arg[2][0] != '\0') { - zoneid = atoi(sep->arg[2]); - if (zoneid < 1) { - zoneid = ZoneID(sep->arg[2]); + } else if (is_take) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + std::string zone_long_name = ZoneLongName(zone_id); + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); } - } - if (zoneid < 1) { - c->Message(Chat::Red, "zone required. see help."); + + target->ClearZoneFlag(zone_id); + c->Message( + Chat::White, + fmt::format( + "{} no longer {} the flag for {} ({}).", + c == target ? + "You" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ), + c == target ? "have" : "has", + zone_long_name, + zone_id + ).c_str() + ); return; } + } else if (is_unlock) { + uint32 zone_id = ( + sep->IsNumber(2) ? + std::stoul(sep->arg[2]) : + ZoneID(sep->arg[2]) + ); + std::string zone_short_name = str_tolower(ZoneName(zone_id, true)); + bool is_unknown_zone = zone_short_name.find("unknown") != std::string::npos; + if (zone_id && !is_unknown_zone) { + std::string zone_long_name = ZoneLongName(zone_id); + auto query = fmt::format( + SQL( + UPDATE zone + SET flag_needed = '' + WHERE zoneidnumber = {} AND version = {} + ), + zone_id, + zone->GetInstanceVersion() + ); + auto results = content_db.QueryDatabase(query); + if (!results.Success()) { + c->Message( + Chat::White, + fmt::format( + "Error updating zone flag for {} ({}).", + zone_long_name, + zone_id + ).c_str() + ); + return; + } - Mob *t = c->GetTarget(); - if (t == nullptr || !t->IsClient()) { - c->Message(Chat::Red, "client target required"); + c->Message( + Chat::White, + fmt::format( + "{} ({}) no longer requires a flag.", + zone_long_name, + zone_id + ).c_str() + ); return; } - - t->CastToClient()->ClearZoneFlag(zoneid); - return; } - - c->Message(Chat::Yellow, "Invalid action specified. use '#flagedit help' for help"); } diff --git a/zone/gm_commands/hatelist.cpp b/zone/gm_commands/hatelist.cpp index 13006d47b..83fe75e20 100755 --- a/zone/gm_commands/hatelist.cpp +++ b/zone/gm_commands/hatelist.cpp @@ -2,14 +2,12 @@ void command_hatelist(Client *c, const Seperator *sep) { - Mob *target = c->GetTarget(); - if (target == nullptr) { - c->Message(Chat::White, "Error: you must have a target."); + if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); return; } - c->Message(Chat::White, "Display hate list for %s..", target->GetName()); + auto target = c->GetTarget(); + target->PrintHateListToClient(c); } - - diff --git a/zone/gm_commands/logs.cpp b/zone/gm_commands/logs.cpp index 16ba3439a..bda01a63a 100755 --- a/zone/gm_commands/logs.cpp +++ b/zone/gm_commands/logs.cpp @@ -5,97 +5,198 @@ extern WorldServer worldserver; void command_logs(Client *c, const Seperator *sep) { - int logs_set = 0; - if (sep->argnum > 0) { - /* #logs reload_all */ - if (strcasecmp(sep->arg[1], "reload_all") == 0) { - auto pack = new ServerPacket(ServerOP_ReloadLogs, 0); - worldserver.SendPacket(pack); - c->Message( - Chat::Red, - "Successfully sent the packet to world to reload log settings from the database for all zones" - ); - safe_delete(pack); - } - /* #logs list_settings */ - if (strcasecmp(sep->arg[1], "list_settings") == 0 || - (strcasecmp(sep->arg[1], "set") == 0 && strcasecmp(sep->arg[3], "") == 0)) { - c->Message(Chat::White, "[Category ID | console | file | gmsay | Category Description]"); - int redisplay_columns = 0; - for (int i = 0; i < Logs::LogCategory::MaxCategoryID; i++) { - if (redisplay_columns == 10) { - c->Message(Chat::White, "[Category ID | console | file | gmsay | Category Description]"); - redisplay_columns = 0; - } - c->Message( - 0, - StringFormat( - "--- %i | %u | %u | %u | %s", - i, - LogSys.log_settings[i].log_to_console, - LogSys.log_settings[i].log_to_file, - LogSys.log_settings[i].log_to_gmsay, - Logs::LogCategoryName[i] - ).c_str()); - redisplay_columns++; - } - } - /* #logs set */ - if (strcasecmp(sep->arg[1], "set") == 0) { - if (strcasecmp(sep->arg[2], "console") == 0) { - LogSys.log_settings[atoi(sep->arg[3])].log_to_console = atoi(sep->arg[4]); - logs_set = 1; - } - else if (strcasecmp(sep->arg[2], "file") == 0) { - LogSys.log_settings[atoi(sep->arg[3])].log_to_file = atoi(sep->arg[4]); - logs_set = 1; - } - else if (strcasecmp(sep->arg[2], "gmsay") == 0) { - LogSys.log_settings[atoi(sep->arg[3])].log_to_gmsay = atoi(sep->arg[4]); - logs_set = 1; - } - else { - c->Message( - Chat::White, - "--- #logs set [console|file|gmsay] - Sets log settings during the lifetime of the zone" - ); - c->Message(Chat::White, "--- #logs set gmsay 20 1 - Would output Quest errors to gmsay"); - } - if (logs_set == 1) { - c->Message(Chat::Yellow, "Your Log Settings have been applied"); - c->Message( - Chat::Yellow, - "Output Method: %s :: Debug Level: %i - Category: %s", - sep->arg[2], - atoi(sep->arg[4]), - Logs::LogCategoryName[atoi(sep->arg[3])] - ); - } - /* We use a general 'is_category_enabled' now, let's update when we update any output settings - This is used in hot places of code to check if its enabled in any way before triggering logs - */ - if (atoi(sep->arg[4]) > 0) { - LogSys.log_settings[atoi(sep->arg[3])].is_category_enabled = 1; - } - else { - LogSys.log_settings[atoi(sep->arg[3])].is_category_enabled = 0; - } - } + int arguments = sep->argnum; + if (!arguments) { + c->Message( + Chat::White, + "#logs list - Shows current log settings and categories loaded into the current process' memory for the first 50 log categories" + ); + c->Message( + Chat::White, + "#logs list [Start Category ID] - Shows current log settings and categories loaded into the current process' memory, only shows 50 at a time starting at specified Category ID" + ); + c->Message( + Chat::White, + "#logs reload - Reload all settings in world and all zone processes with what is defined in the database" + ); + c->Message( + Chat::White, + "#logs set [console|file|gmsay] [Category ID] [Debug Level (1-3)] - Sets log settings during the lifetime of the zone" + ); + return; } - else { - c->Message(Chat::White, "#logs usage:"); + + bool is_list = !strcasecmp(sep->arg[1], "list"); + bool is_reload = !strcasecmp(sep->arg[1], "reload"); + bool is_set = !strcasecmp(sep->arg[1], "set"); + + if (!is_list && !is_reload && !is_set) { c->Message( Chat::White, - "--- #logs reload_all - Reload all settings in world and all zone processes with what is defined in the database" + "#logs list - Shows current log settings and categories loaded into the current process' memory for the first 50 log categories" ); c->Message( Chat::White, - "--- #logs list_settings - Shows current log settings and categories loaded into the current process' memory" + "#logs list [Start Category ID] - Shows current log settings and categories loaded into the current process' memory, only shows 50 at a time starting at specified Category ID" ); c->Message( Chat::White, - "--- #logs set [console|file|gmsay] - Sets log settings during the lifetime of the zone" + "#logs reload - Reload all settings in world and all zone processes with what is defined in the database" ); + c->Message( + Chat::White, + "#logs set [console|file|gmsay] [Category ID] [Debug Level (1-3)] - Sets log settings during the lifetime of the zone" + ); + return; + } + + if (is_list) { + uint32 start_category_id = 1; + if (sep->IsNumber(2)) { + start_category_id = std::stoul(sep->arg[2]); + } + + uint32 max_category_id = (start_category_id + 49); + + std::string popup_text = ""; + + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + popup_text += ""; + + for (int index = start_category_id; index <= max_category_id; index++) { + if (index >= Logs::LogCategory::MaxCategoryID) { + max_category_id = (Logs::LogCategory::MaxCategoryID - 1); + break; + } + + popup_text += fmt::format( + "", + index, + Logs::LogCategoryName[index], + LogSys.log_settings[index].log_to_console, + LogSys.log_settings[index].log_to_file, + LogSys.log_settings[index].log_to_gmsay + ); + } + + popup_text += "
IDNameConsoleFileGM Say
{}{}{}{}{}
"; + + std::string popup_title = fmt::format( + "Log Settings [{} to {}]", + start_category_id, + max_category_id + ); + + c->SendPopupToClient( + popup_title.c_str(), + popup_text.c_str() + ); + + c->Message( + Chat::White, + fmt::format( + "Viewing log category settings from {} ({}) to {} ({}).", + Logs::LogCategoryName[start_category_id], + start_category_id, + Logs::LogCategoryName[max_category_id], + max_category_id + ).c_str() + ); + + int next_category_id = (max_category_id + 1); + if (next_category_id < Logs::LogCategory::MaxCategoryID) { + auto next_list_string = fmt::format( + "#logs list {}", + next_category_id + ); + + auto next_list_link = EQ::SayLinkEngine::GenerateQuestSaylink( + next_list_string, + false, + next_list_string + ); + + c->Message( + Chat::White, + fmt::format( + "To view the next 50 log settings, you can use {}.", + next_list_link + ).c_str() + ); + } + } else if (is_reload) { + auto pack = new ServerPacket(ServerOP_ReloadLogs, 0); + worldserver.SendPacket(pack); + c->Message( + Chat::White, + "Reloaded log settings worldwide." + ); + safe_delete(pack); + } else if (is_set) { + auto logs_set = false; + bool is_console = !strcasecmp(sep->arg[2], "console"); + bool is_file = !strcasecmp(sep->arg[2], "file"); + bool is_gmsay = !strcasecmp(sep->arg[2], "gmsay"); + + if (!is_console && !is_file && !is_gmsay) { + c->Message( + Chat::White, + "#logs set [console|file|gmsay] [Category ID] [Debug Level (1-3)] - Sets log settings during the lifetime of the zone" + ); + c->Message(Chat::White, "Example: #logs set gmsay 20 1 - Would output Quest errors to gmsay"); + return; + } + + logs_set = true; + + auto category_id = std::stoul(sep->arg[3]); + auto setting = std::stoul(sep->arg[4]); + + if (is_console) { + LogSys.log_settings[category_id].log_to_console = setting; + } else if (is_file) { + LogSys.log_settings[category_id].log_to_file = setting; + } else if (is_gmsay) { + LogSys.log_settings[category_id].log_to_gmsay = setting; + } + + if (logs_set) { + std::string popup_text = ""; + + popup_text += fmt::format( + "", + category_id + ); + + popup_text += fmt::format( + "", + Logs::LogCategoryName[category_id] + ); + + popup_text += fmt::format( + "", + sep->arg[2] + ); + + popup_text += fmt::format( + "", + setting + ); + + popup_text += "
ID{}
Category{}
Method{}
Setting{}
"; + + c->SendPopupToClient( + "Log Settings Applied", + popup_text.c_str() + ); + } + + LogSys.log_settings[category_id].is_category_enabled = setting ? 1 : 0; } } diff --git a/zone/gm_commands/name.cpp b/zone/gm_commands/name.cpp index fbe60f752..58dda8e75 100755 --- a/zone/gm_commands/name.cpp +++ b/zone/gm_commands/name.cpp @@ -2,29 +2,41 @@ void command_name(Client *c, const Seperator *sep) { - Client *target; - - if ((strlen(sep->arg[1]) == 0) || (!(c->GetTarget() && c->GetTarget()->IsClient()))) { - c->Message(Chat::White, "Usage: #name newname (requires player target)"); + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #name [New Name] - Rename your player target"); + return; } - else { - target = c->GetTarget()->CastToClient(); - char *oldname = strdup(target->GetName()); - if (target->ChangeFirstName(sep->arg[1], c->GetName())) { - c->Message(Chat::White, "Successfully renamed %s to %s", oldname, sep->arg[1]); - // until we get the name packet working right this will work - c->Message(Chat::White, "Sending player to char select."); - target->Kick("Name was changed"); - } - else { + + if (c->GetTarget() && c->GetTarget()->IsClient()) { + auto target = c->GetTarget()->CastToClient(); + + std::string new_name = sep->arg[1]; + std::string old_name = target->GetCleanName(); + + if (target->ChangeFirstName(new_name.c_str(), c->GetCleanName())) { c->Message( - Chat::Red, - "ERROR: Unable to rename %s. Check that the new name '%s' isn't already taken.", - oldname, - sep->arg[2] + Chat::White, + fmt::format( + "Successfully renamed {} to {}", + old_name, + new_name + ).c_str() + ); + + c->Message(Chat::White, "Sending player to char select."); + + target->Kick("Name was changed"); + } else { + c->Message( + Chat::White, + fmt::format( + "Unable to rename {}. Check that the new name '{}' isn't already taken.", + old_name, + new_name + ).c_str() ); } - free(oldname); } } diff --git a/zone/gm_commands/netstats.cpp b/zone/gm_commands/netstats.cpp index 6bd56f4fa..87fd11a4c 100755 --- a/zone/gm_commands/netstats.cpp +++ b/zone/gm_commands/netstats.cpp @@ -2,140 +2,168 @@ void command_netstats(Client *c, const Seperator *sep) { - if (c) { - auto client = c; - if (c->GetTarget() && c->GetTarget()->IsClient()) { - client = c->GetTarget()->CastToClient(); - } + bool is_full = !strcasecmp(sep->arg[1], "full"); + bool is_reset = !strcasecmp(sep->arg[1], "reset"); - if (strcasecmp(sep->arg[1], "reset") == 0) { - auto connection = c->Connection(); - c->Message(Chat::White, "Resetting client stats (packet loss will not read correctly after reset)."); - connection->ResetStats(); - return; - } - - auto connection = c->Connection(); - auto opts = connection->GetManager()->GetOptions(); - auto eqs_stats = connection->GetStats(); - auto &stats = eqs_stats.DaybreakStats; - auto now = EQ::Net::Clock::now(); - auto sec_since_stats_reset = std::chrono::duration_cast>( - now - stats.created - ).count(); - - c->Message(Chat::White, "Netstats:"); - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message( - Chat::White, - "Sent Bytes: %u (%.2f/sec)", - stats.sent_bytes, - stats.sent_bytes / sec_since_stats_reset - ); - c->Message( - Chat::White, - "Recv Bytes: %u (%.2f/sec)", - stats.recv_bytes, - stats.recv_bytes / sec_since_stats_reset - ); - c->Message( - Chat::White, "Bytes Before Encode (Sent): %u, Compression Rate: %.2f%%", stats.bytes_before_encode, - static_cast(stats.bytes_before_encode - stats.sent_bytes) / - static_cast(stats.bytes_before_encode) * 100.0 - ); - c->Message( - Chat::White, "Bytes After Decode (Recv): %u, Compression Rate: %.2f%%", stats.bytes_after_decode, - static_cast(stats.bytes_after_decode - stats.recv_bytes) / - static_cast(stats.bytes_after_decode) * 100.0 - ); - c->Message(Chat::White, "Min Ping: %u", stats.min_ping); - c->Message(Chat::White, "Max Ping: %u", stats.max_ping); - c->Message(Chat::White, "Last Ping: %u", stats.last_ping); - c->Message(Chat::White, "Average Ping: %u", stats.avg_ping); - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message( - Chat::White, - "(Realtime) Recv Packets: %u (%.2f/sec)", - stats.recv_packets, - stats.recv_packets / sec_since_stats_reset - ); - c->Message( - Chat::White, - "(Realtime) Sent Packets: %u (%.2f/sec)", - stats.sent_packets, - stats.sent_packets / sec_since_stats_reset - ); - c->Message(Chat::White, "(Sync) Recv Packets: %u", stats.sync_recv_packets); - c->Message(Chat::White, "(Sync) Sent Packets: %u", stats.sync_sent_packets); - c->Message(Chat::White, "(Sync) Remote Recv Packets: %u", stats.sync_remote_recv_packets); - c->Message(Chat::White, "(Sync) Remote Sent Packets: %u", stats.sync_remote_sent_packets); - c->Message( - Chat::White, - "Packet Loss In: %.2f%%", - 100.0 * (1.0 - static_cast(stats.sync_recv_packets) / - static_cast(stats.sync_remote_sent_packets))); - c->Message( - Chat::White, - "Packet Loss Out: %.2f%%", - 100.0 * (1.0 - static_cast(stats.sync_remote_recv_packets) / - static_cast(stats.sync_sent_packets))); - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message( - Chat::White, - "Resent Packets: %u (%.2f/sec)", - stats.resent_packets, - stats.resent_packets / sec_since_stats_reset - ); - c->Message( - Chat::White, - "Resent Fragments: %u (%.2f/sec)", - stats.resent_fragments, - stats.resent_fragments / sec_since_stats_reset - ); - c->Message( - Chat::White, - "Resent Non-Fragments: %u (%.2f/sec)", - stats.resent_full, - stats.resent_full / sec_since_stats_reset - ); - c->Message( - Chat::White, - "Dropped Datarate Packets: %u (%.2f/sec)", - stats.dropped_datarate_packets, - stats.dropped_datarate_packets / sec_since_stats_reset - ); - - if (opts.daybreak_options.outgoing_data_rate > 0.0) { - c->Message( - Chat::White, - "Outgoing Link Saturation %.2f%% (%.2fkb/sec)", - 100.0 * (1.0 - ((opts.daybreak_options.outgoing_data_rate - stats.datarate_remaining) / - opts.daybreak_options.outgoing_data_rate)), - opts.daybreak_options.outgoing_data_rate - ); - } - - if (strcasecmp(sep->arg[1], "full") == 0) { - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message(Chat::White, "Sent Packet Types"); - for (auto i = 0; i < _maxEmuOpcode; ++i) { - auto cnt = eqs_stats.SentCount[i]; - if (cnt > 0) { - c->Message(Chat::White, "%s: %u (%.2f / sec)", OpcodeNames[i], cnt, cnt / sec_since_stats_reset); - } - } - - c->Message(Chat::White, "--------------------------------------------------------------------"); - c->Message(Chat::White, "Recv Packet Types"); - for (auto i = 0; i < _maxEmuOpcode; ++i) { - auto cnt = eqs_stats.RecvCount[i]; - if (cnt > 0) { - c->Message(Chat::White, "%s: %u (%.2f / sec)", OpcodeNames[i], cnt, cnt / sec_since_stats_reset); - } - } - } - - c->Message(Chat::White, "--------------------------------------------------------------------"); + if (is_reset) { + auto connection = c->Connection(); + c->Message(Chat::White, "Resetting client stats (packet loss will not read correctly after reset)."); + connection->ResetStats(); + return; } + + auto connection = c->Connection(); + auto opts = connection->GetManager()->GetOptions(); + auto eqs_stats = connection->GetStats(); + auto &stats = eqs_stats.DaybreakStats; + auto now = EQ::Net::Clock::now(); + auto sec_since_stats_reset = std::chrono::duration_cast>( + now - stats.created + ).count(); + + std::string popup_text = ""; + + popup_text += fmt::format( + "", + stats.sent_bytes, + stats.sent_bytes / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.recv_bytes, + stats.recv_bytes / sec_since_stats_reset + ); + + popup_text += "

"; + + popup_text += fmt::format( + "", + stats.bytes_before_encode, + static_cast(stats.bytes_before_encode - stats.sent_bytes) / + static_cast(stats.bytes_before_encode) * 100.0 + ); + + popup_text += fmt::format( + "", + stats.bytes_after_decode, + static_cast(stats.bytes_after_decode - stats.recv_bytes) / + static_cast(stats.bytes_after_decode) * 100.0 + ); + + popup_text += "

"; + + popup_text += fmt::format("
", stats.min_ping); + popup_text += fmt::format("", stats.max_ping); + popup_text += fmt::format("", stats.last_ping); + popup_text += fmt::format("", stats.avg_ping); + + popup_text += "

"; + + popup_text += fmt::format( + "", + stats.recv_packets, + stats.recv_packets / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.sent_packets, + stats.sent_packets / sec_since_stats_reset + ); + + popup_text += "

"; + + popup_text += fmt::format("", stats.sync_recv_packets); + popup_text += fmt::format("", stats.sync_sent_packets); + popup_text += fmt::format("", stats.sync_remote_recv_packets); + popup_text += fmt::format("", stats.sync_remote_sent_packets); + + popup_text += "

"; + + popup_text += fmt::format( + "", + (100.0 * (1.0 - static_cast(stats.sync_recv_packets) / static_cast(stats.sync_remote_sent_packets))) + ); + + popup_text += fmt::format( + "", + (100.0 * (1.0 - static_cast(stats.sync_remote_recv_packets) / static_cast(stats.sync_sent_packets))) + ); + + popup_text += "

"; + + popup_text += fmt::format( + "
", + stats.resent_packets, + stats.resent_packets / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.resent_fragments, + stats.resent_fragments / sec_since_stats_reset + ); + + popup_text += fmt::format( + "", + stats.resent_full, + stats.resent_full / sec_since_stats_reset + ); + + popup_text += "

"; + + popup_text += fmt::format( + "", + stats.dropped_datarate_packets, + stats.dropped_datarate_packets / sec_since_stats_reset + ); + + if (opts.daybreak_options.outgoing_data_rate > 0.0) { + popup_text += fmt::format( + "", + (100.0 * (1.0 - ((opts.daybreak_options.outgoing_data_rate - stats.datarate_remaining) / opts.daybreak_options.outgoing_data_rate))), + opts.daybreak_options.outgoing_data_rate + ); + } + + if (is_full) { + popup_text += "

"; + + popup_text += ""; + for (auto i = 0; i < _maxEmuOpcode; ++i) { + auto cnt = eqs_stats.SentCount[i]; + if (cnt > 0) { + popup_text += fmt::format( + "", + OpcodeNames[i], + cnt, + cnt / sec_since_stats_reset + ); + } + } + + popup_text += "

"; + + popup_text += ""; + for (auto i = 0; i < _maxEmuOpcode; ++i) { + auto cnt = eqs_stats.RecvCount[i]; + if (cnt > 0) { + popup_text += fmt::format( + "", + OpcodeNames[i], + cnt, + cnt / sec_since_stats_reset + ); + } + } + } + + popup_text += "
Sent Bytes{} ({:.2f} Per Second)
Received Bytes{} ({:.2f} Per Second)
Bytes Before Encode (Sent){}Compression Rate{:.2f}%%
Bytes After Decode (Received){}Compression Rate{:.2f}%%
Min Ping{}
Max Ping{}
Last Ping{}
Average Ping{}
(Realtime) Received Packets{} ({:.2f} Per Second)
(Realtime) Sent Packets{} ({:.2f} Per Second)
(Sync) Received Packets{}
(Sync) Sent Packets{}
(Sync) Remote Received Packets{}
(Sync) Remote Sent Packets{}
Packet Loss In{:.2f}%%
Packet Loss Out{:.2f}%%
Resent Packets{} ({:.2f} Per Second)
Resent Fragments{} ({:.2f} Per Second)
Resent Non-Fragments{} ({:.2f} Per Second)
Dropped Datarate Packets{} ({:.2f} Per Second)
Outgoing Link Saturation{:.2f}%% ({:.2f}kb Per Second)
Sent Packet Types
{}{} ({:.2f} Per Second)
Received Packet Types
{}{} ({:.2f} Per Second)
"; + + c->SendPopupToClient( + "Network Statistics", + popup_text.c_str() + ); } diff --git a/zone/gm_commands/npcloot.cpp b/zone/gm_commands/npcloot.cpp index 6ebe557ac..9f95f56ff 100755 --- a/zone/gm_commands/npcloot.cpp +++ b/zone/gm_commands/npcloot.cpp @@ -1,104 +1,249 @@ #include "../client.h" #include "../corpse.h" +#include "../../common/data_verification.h" void command_npcloot(Client *c, const Seperator *sep) { - if (c->GetTarget() == 0) { - c->Message(Chat::White, "Error: No target"); - // #npcloot show + if (!c->GetTarget() || (!c->GetTarget()->IsNPC() && !c->GetTarget()->IsCorpse())) { + c->Message(Chat::White, "You must target an NPC or a Corpse to use this command."); + return; } - else if (strcasecmp(sep->arg[1], "show") == 0) { - if (c->GetTarget()->IsNPC()) { - c->GetTarget()->CastToNPC()->QueryLoot(c); - } - else if (c->GetTarget()->IsCorpse()) { - c->GetTarget()->CastToCorpse()->QueryLoot(c); - } - else { - c->Message(Chat::White, "Error: Target's type doesnt have loot"); - } + + int arguments = sep->argnum; + if (!arguments) { + c->Message(Chat::White, "Usage: #npcloot add [Item ID] [Charges] [Equip] [Augment 1 ID] [Augment 2 ID] [Augment 3 ID] [Augment 4 ID] [Augment 5 ID] [Augment 6 ID] - Adds the specified item to an NPC's loot"); + c->Message(Chat::White, "Usage: #npcloot money [Platinum] [Gold] [Silver] [Copper] - Set an NPC's current money"); + c->Message(Chat::White, "Usage: #npcloot remove [All|Item ID] - Remove loot from an NPC by ID or remove all loot"); + c->Message(Chat::White, "Usage: #npcloot show - Shows target NPC's or Corpse's current loot"); + return; } - // These 2 types are *BAD* for the next few commands - else if (c->GetTarget()->IsClient() || c->GetTarget()->IsCorpse()) { - c->Message(Chat::White, "Error: Invalid target type, try a NPC =)."); - // #npcloot add + + bool is_add = !strcasecmp(sep->arg[1], "add"); + bool is_money = !strcasecmp(sep->arg[1], "money"); + bool is_remove = !strcasecmp(sep->arg[1], "remove"); + bool is_show = !strcasecmp(sep->arg[1], "show"); + + if ( + !is_add && + !is_money && + !is_remove && + !is_show + ) { + c->Message(Chat::White, "Usage: #npcloot add [Item ID] [Charges] [Equip] [Augment 1 ID] [Augment 2 ID] [Augment 3 ID] [Augment 4 ID] [Augment 5 ID] [Augment 6 ID] - Adds the specified item to an NPC's loot"); + c->Message(Chat::White, "Usage: #npcloot money [Platinum] [Gold] [Silver] [Copper] - Set an NPC's current money"); + c->Message(Chat::White, "Usage: #npcloot remove [All|Item ID] - Remove loot from an NPC by ID or remove all loot"); + c->Message(Chat::White, "Usage: #npcloot show - Shows target NPC's or Corpse's current loot"); + return; } - else if (strcasecmp(sep->arg[1], "add") == 0) { - // #npcloot add item - if (c->GetTarget()->IsNPC() && sep->IsNumber(2)) { - uint32 item = atoi(sep->arg[2]); - if (database.GetItem(item)) { - if (sep->arg[3][0] != 0 && sep->IsNumber(3)) { - c->GetTarget()->CastToNPC()->AddItem(item, atoi(sep->arg[3]), 0); - } - else { - c->GetTarget()->CastToNPC()->AddItem(item, 1, 0); - } - c->Message(Chat::White, "Added item(%i) to the %s's loot.", item, c->GetTarget()->GetName()); - } - else { - c->Message(Chat::White, "Error: #npcloot add: Item(%i) does not exist!", item); - } + + if (is_add) { + if (!c->GetTarget()->IsNPC() || !sep->IsNumber(2)) { + c->Message(Chat::White, "Usage: #npcloot add [Item ID] [Charges] [Equip] [Augment 1 ID] [Augment 2 ID] [Augment 3 ID] [Augment 4 ID] [Augment 5 ID] [Augment 6 ID] - Adds the specified item to an NPC's loot"); + return; } - else if (!sep->IsNumber(2)) { - c->Message(Chat::White, "Error: #npcloot add: Itemid must be a number."); + + auto item_id = std::stoul(sep->arg[2]); + auto item_charges = sep->IsNumber(3) ? static_cast(std::stoul(sep->arg[3])) : 1; + bool equip_item = arguments >= 4 ? atobool(sep->arg[4]) : false; + auto augment_one_id = sep->IsNumber(5) ? std::stoul(sep->arg[5]) : 0; + auto augment_two_id = sep->IsNumber(6) ? std::stoul(sep->arg[6]) : 0; + auto augment_three_id = sep->IsNumber(7) ? std::stoul(sep->arg[7]) : 0; + auto augment_four_id = sep->IsNumber(8) ? std::stoul(sep->arg[8]) : 0; + auto augment_five_id = sep->IsNumber(9) ? std::stoul(sep->arg[9]) : 0; + auto augment_six_id = sep->IsNumber(10) ? std::stoul(sep->arg[10]) : 0; + + auto item_data = database.GetItem(item_id); + + if (!item_data) { + c->Message( + Chat::White, + fmt::format( + "Item ID {} could not be found", + item_id + ).c_str() + ); + return; } - else { - c->Message(Chat::White, "Error: #npcloot add: This is not a valid target."); + + c->GetTarget()->CastToNPC()->AddItem( + item_id, + item_charges, + equip_item, + augment_one_id, + augment_two_id, + augment_three_id, + augment_four_id, + augment_five_id, + augment_six_id + ); + + auto item = database.CreateItem( + item_id, + item_charges, + augment_one_id, + augment_two_id, + augment_three_id, + augment_four_id, + augment_five_id, + augment_six_id + ); + + EQ::SayLinkEngine linker; + linker.SetLinkType(EQ::saylink::SayLinkItemInst); + linker.SetItemInst(item); + + auto item_link = linker.GenerateLink(); + + c->Message( + Chat::White, + fmt::format( + "Added {} ({}) to {} ({}).", + item_link, + item_id, + c->GetTarget()->GetCleanName(), + c->GetTarget()->GetID() + ).c_str() + ); + } else if (is_money) { + if (!c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; } - } - // #npcloot remove - else if (strcasecmp(sep->arg[1], "remove") == 0) { - //#npcloot remove all - if (strcasecmp(sep->arg[2], "all") == 0) { - c->Message(Chat::White, "Error: #npcloot remove all: Not yet implemented."); - //#npcloot remove itemid + + auto target = c->GetTarget()->CastToNPC(); + + if (sep->IsNumber(2)) { + uint16 platinum = EQ::Clamp(std::stoi(sep->arg[2]), 0, 65535); + uint16 gold = sep->IsNumber(3) ? EQ::Clamp(std::stoi(sep->arg[3]), 0, 65535) : 0; + uint16 silver = sep->IsNumber(4) ? EQ::Clamp(std::stoi(sep->arg[4]), 0, 65535) : 0; + uint16 copper = sep->IsNumber(5) ? EQ::Clamp(std::stoi(sep->arg[5]), 0, 65535) : 0; + target->AddCash( + copper, + silver, + gold, + platinum + ); + + auto money_string = ( + ( + copper || + silver || + gold || + platinum + ) ? + ConvertMoneyToString( + platinum, + gold, + silver, + copper + ) : + "no money" + ); + + c->Message( + Chat::White, + fmt::format( + "{} ({}) now has {}.", + target->GetCleanName(), + target->GetID(), + money_string + ).c_str() + ); + } else { + c->Message(Chat::White, "Usage: #npcloot money [Platinum] [Gold] [Silver] [Copper] - Set an NPC's current money"); } - else { - if (c->GetTarget()->IsNPC() && sep->IsNumber(2)) { - uint32 item = atoi(sep->arg[2]); - c->GetTarget()->CastToNPC()->RemoveItem(item); - c->Message(Chat::White, "Removed item(%i) from the %s's loot.", item, c->GetTarget()->GetName()); - } - else if (!sep->IsNumber(2)) { - c->Message(Chat::White, "Error: #npcloot remove: Item must be a number."); - } - else { - c->Message(Chat::White, "Error: #npcloot remove: This is not a valid target."); - } + } else if (is_remove) { + if (!c->GetTarget()->IsNPC()) { + c->Message(Chat::White, "You must target an NPC to use this command."); + return; } - } - // #npcloot money - else if (strcasecmp(sep->arg[1], "money") == 0) { - if (c->GetTarget()->IsNPC() && sep->IsNumber(2) && sep->IsNumber(3) && sep->IsNumber(4) && sep->IsNumber(5)) { - if ((atoi(sep->arg[2]) < 34465 && atoi(sep->arg[2]) >= 0) && - (atoi(sep->arg[3]) < 34465 && atoi(sep->arg[3]) >= 0) && - (atoi(sep->arg[4]) < 34465 && atoi(sep->arg[4]) >= 0) && - (atoi(sep->arg[5]) < 34465 && atoi(sep->arg[5]) >= 0)) { - c->GetTarget()->CastToNPC()->AddCash( - atoi(sep->arg[5]), - atoi(sep->arg[4]), - atoi(sep->arg[3]), - atoi(sep->arg[2])); + + auto target = c->GetTarget()->CastToNPC(); + + bool is_remove_all = !strcasecmp(sep->arg[2], "all"); + if (is_remove_all) { + auto loot_list = target->GetLootList(); + auto total_item_count = 0; + for (const auto& item_id : loot_list) { + auto item_count = target->CountItem(item_id); + + target->RemoveItem(item_id); + c->Message( Chat::White, - "Set %i Platinum, %i Gold, %i Silver, and %i Copper as %s's money.", - atoi(sep->arg[2]), - atoi(sep->arg[3]), - atoi(sep->arg[4]), - atoi(sep->arg[5]), - c->GetTarget()->GetName()); + fmt::format( + "Removed {} {} ({}) from {} ({}).", + item_count, + database.CreateItemLink(item_id), + item_id, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + + total_item_count += item_count; } - else { - c->Message(Chat::White, "Error: #npcloot money: Values must be between 0-34465."); + + if (!total_item_count) { + c->Message( + Chat::White, + fmt::format( + "{} ({}) has no items to remove.", + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} Item{} removed from {} ({}).", + total_item_count, + total_item_count != 1 ? "s" : "", + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } + } else { + if (sep->IsNumber(2)) { + auto item_id = std::stoul(sep->arg[2]); + auto item_count = target->CountItem(item_id); + if (item_count) { + target->RemoveItem(item_id); + c->Message( + Chat::White, + fmt::format( + "Removed {} {} ({}) from {} ({}).", + item_count, + database.CreateItemLink(item_id), + item_id, + target->GetCleanName(), + target->GetID() + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "{} ({}) does not have any {} ({}).", + target->GetCleanName(), + target->GetID(), + database.CreateItemLink(item_id), + item_id + ).c_str() + ); + } + } else { + c->Message(Chat::White, "Usage: #npcloot remove [All|Item ID] - Remove loot from an NPC by ID or remove all loot"); } } - else { - c->Message(Chat::White, "Usage: #npcloot money platinum gold silver copper"); + } else if (is_show) { + if (c->GetTarget()->IsNPC()) { + c->GetTarget()->CastToNPC()->QueryLoot(c); + } else if (c->GetTarget()->IsCorpse()) { + c->GetTarget()->CastToCorpse()->QueryLoot(c); } } - else { - c->Message(Chat::White, "Usage: #npcloot [show/money/add/remove] [itemid/all/money: pp gp sp cp]"); - } } diff --git a/zone/gm_commands/timers.cpp b/zone/gm_commands/timers.cpp index d8e74a8b3..4e36661e4 100755 --- a/zone/gm_commands/timers.cpp +++ b/zone/gm_commands/timers.cpp @@ -2,21 +2,45 @@ void command_timers(Client *c, const Seperator *sep) { - if (!c->GetTarget() || !c->GetTarget()->IsClient()) { - c->Message(Chat::White, "Need a player target for timers."); - return; + auto target = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + target = c->GetTarget()->CastToClient(); } - Client *them = c->GetTarget()->CastToClient(); - std::vector > res; - them->GetPTimers().ToVector(res); + std::vector> timers; + target->GetPTimers().ToVector(timers); - c->Message(Chat::White, "Timers for target:"); + std::string popup_title = fmt::format( + "Recast Timers for {}", + c == target ? + "Yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ); - int r; - int l = res.size(); - for (r = 0; r < l; r++) { - c->Message(Chat::White, "Timer %d: %d seconds remain.", res[r].first, res[r].second->GetRemainingTime()); + std::string popup_text = ""; + + popup_text += ""; + + for (const auto& timer : timers) { + auto remaining_time = timer.second->GetRemainingTime(); + if (remaining_time) { + popup_text += fmt::format( + "", + timer.first, + ConvertSecondsToTime(remaining_time) + ); + } } + + popup_text += "
Timer IDRemaining
{}{}
"; + + c->SendPopupToClient( + popup_title.c_str(), + popup_text.c_str() + ); } diff --git a/zone/gm_commands/undye.cpp b/zone/gm_commands/undye.cpp index af07be7a7..e5430a02b 100755 --- a/zone/gm_commands/undye.cpp +++ b/zone/gm_commands/undye.cpp @@ -2,11 +2,23 @@ void command_undye(Client *c, const Seperator *sep) { + auto target = c; if (c->GetTarget() && c->GetTarget()->IsClient()) { - c->GetTarget()->CastToClient()->Undye(); + target = c->GetTarget()->CastToClient(); } - else { - c->Message(Chat::White, "ERROR: Client target required"); - } -} + target->Undye(); + c->Message( + Chat::White, + fmt::format( + "Undyed armor for {}.", + c == target ? + "yourself" : + fmt::format( + "{} ({})", + target->GetCleanName(), + target->GetID() + ) + ).c_str() + ); +} diff --git a/zone/gm_commands/undyeme.cpp b/zone/gm_commands/undyeme.cpp index b2e451202..8bd6924b1 100755 --- a/zone/gm_commands/undyeme.cpp +++ b/zone/gm_commands/undyeme.cpp @@ -2,9 +2,6 @@ void command_undyeme(Client *c, const Seperator *sep) { - if (c) { - c->Undye(); - c->Message(Chat::Red, "Dye removed from all slots. Please zone for the process to complete."); - } + c->Undye(); + c->Message(Chat::White, "Undyed armor for yourself."); } - diff --git a/zone/gm_commands/version.cpp b/zone/gm_commands/version.cpp index ecb9b74d4..cc5d0a531 100755 --- a/zone/gm_commands/version.cpp +++ b/zone/gm_commands/version.cpp @@ -2,9 +2,17 @@ void command_version(Client *c, const Seperator *sep) { - c->Message(Chat::White, "Current version information."); - c->Message(Chat::White, " %s", CURRENT_VERSION); - c->Message(Chat::White, " Compiled on: %s at %s", COMPILE_DATE, COMPILE_TIME); - c->Message(Chat::White, " Last modified on: %s", LAST_MODIFIED); + std::string popup_text = ""; + + popup_text += fmt::format("", CURRENT_VERSION); + popup_text += fmt::format("", COMPILE_DATE, COMPILE_TIME); + popup_text += fmt::format("", LAST_MODIFIED); + + popup_text += "
Version{}
Compiled{} {}
Last Modified{}
"; + + c->SendPopupToClient( + "Server Version Information", + popup_text.c_str() + ); } diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index 83ec481fa..75923c99e 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -645,15 +645,53 @@ bool HateList::IsHateListEmpty() { void HateList::PrintHateListToClient(Client *c) { - auto iterator = list.begin(); - while (iterator != list.end()) - { - struct_HateList *e = (*iterator); - c->Message(Chat::White, "- name: %s, damage: %d, hate: %d", - (e->entity_on_hatelist && e->entity_on_hatelist->GetName()) ? e->entity_on_hatelist->GetName() : "(null)", - e->hatelist_damage, e->stored_hate_amount); + if (list.size()) { + c->Message( + Chat::White, + fmt::format( + "Displaying hate list for {} ({}).", + hate_owner->GetCleanName(), + hate_owner->GetID() + ).c_str() + ); - ++iterator; + auto entity_number = 1; + for (const auto& hate_entity : list) { + if (hate_entity->entity_on_hatelist) { + c->Message( + Chat::White, + fmt::format( + "Hate Entity {} | Name: {} ({}) Damage: {} Hate: {}", + entity_number, + hate_entity->entity_on_hatelist->GetName(), + hate_entity->entity_on_hatelist->GetID(), + hate_entity->hatelist_damage, + hate_entity->stored_hate_amount + ).c_str() + ); + } else { + c->Message( + Chat::White, + fmt::format( + "Hate Entity {} | Damage: {} Hate: {}", + entity_number, + hate_entity->hatelist_damage, + hate_entity->stored_hate_amount + ).c_str() + ); + } + + entity_number++; + } + } else { + c->Message( + Chat::White, + fmt::format( + "{} ({}) has nothing on its hatelist.", + hate_owner->GetCleanName(), + hate_owner->GetID() + ).c_str() + ); } } diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 444f1ad03..db4394b0f 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -3367,6 +3367,14 @@ std::string lua_get_body_type_name(uint32 bodytype_id) { return quest_manager.getbodytypename(bodytype_id); } +std::string lua_get_consider_level_name(uint8 consider_level) { + return quest_manager.getconsiderlevelname(consider_level); +} + +std::string lua_get_environmental_damage_name(uint8 damage_type) { + return quest_manager.getenvironmentaldamagename(damage_type); +} + #define LuaCreateNPCParse(name, c_type, default_value) do { \ cur = table[#name]; \ if(luabind::type(cur) != LUA_TNIL) { \ @@ -3814,6 +3822,8 @@ luabind::scope lua_register_general() { luabind::def("get_faction_name", &lua_get_faction_name), luabind::def("get_language_name", &lua_get_language_name), luabind::def("get_body_type_name", &lua_get_body_type_name), + luabind::def("get_consider_level_name", &lua_get_consider_level_name), + luabind::def("get_environmental_damage_name", &lua_get_environmental_damage_name), /* Cross Zone diff --git a/zone/mob.h b/zone/mob.h index c2c28296f..ca38653fa 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -326,7 +326,7 @@ public: void CastedSpellFinished(uint16 spell_id, uint32 target_id, EQ::spells::CastingSlot slot, uint16 mana_used, uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0); bool SpellFinished(uint16 spell_id, Mob *target, EQ::spells::CastingSlot slot = EQ::spells::CastingSlot::Item, uint16 mana_used = 0, - uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false, int level_override = -1, bool from_casted_spell = false, uint32 aa_id = 0); + uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false, int level_override = -1, uint32 timer = 0xFFFFFFFF, uint32 timer_duration = 0, bool from_casted_spell = false, uint32 aa_id = 0); void SendBeginCast(uint16 spell_id, uint32 casttime); virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar, int reflect_effectiveness = 0, bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1, int32 duration_override = 0); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index 5bcbbfb14..02ffb60df 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -1052,6 +1052,10 @@ std::string QuestManager::getbodytypename(uint32 bodytype_id) { return EQ::constants::GetBodyTypeName(static_cast(bodytype_id)); } +std::string QuestManager::getconsiderlevelname(uint8 consider_level) { + return EQ::constants::GetConsiderLevelName(consider_level); +} + void QuestManager::safemove() { QuestManagerCurrentQuestVars(); if (initiator && initiator->IsClient()) @@ -3698,3 +3702,8 @@ const SPDat_Spell_Struct* QuestManager::getspell(uint32 spell_id) { } return nullptr; } + +std::string QuestManager::getenvironmentaldamagename(uint8 damage_type) { + std::string environmental_damage_name = EQ::constants::GetEnvironmentalDamageName(damage_type); + return environmental_damage_name; +} diff --git a/zone/questmgr.h b/zone/questmgr.h index 3bd3ddfe3..f859a012d 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -119,6 +119,7 @@ public: std::string getfactionname(int faction_id); std::string getlanguagename(int language_id); std::string getbodytypename(uint32 bodytype_id); + std::string getconsiderlevelname(uint8 consider_level); void safemove(); void rain(int weather); void snow(int weather); @@ -330,7 +331,8 @@ public: std::string getinventoryslotname(int16 slot_id); int getitemstat(uint32 item_id, std::string stat_identifier); int getspellstat(uint32 spell_id, std::string stat_identifier, uint8 slot = 0); - const SPDat_Spell_Struct *getspell(uint32 spell_id); + const SPDat_Spell_Struct *getspell(uint32 spell_id); + std::string getenvironmentaldamagename(uint8 damage_type); Client *GetInitiator() const; NPC *GetNPC() const; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index f3701f316..01520dc3e 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -8248,7 +8248,7 @@ void Mob::SendCastRestrictionMessage(int requirement_id, bool target_requirement */ - std::string msg = ""; + const char *msg = ""; if (target_requirement) { msg = "Your target does not meet the spell requirements. "; diff --git a/zone/spells.cpp b/zone/spells.cpp index b9b8a66ef..ee73add8c 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -209,6 +209,7 @@ bool Mob::CastSpell(uint16 spell_id, uint16 target_id, CastingSlot slot, if (item_slot && IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)) { if (!CheckItemRaceClassDietyRestrictionsOnCast(item_slot)) { + StopCastSpell(spell_id, send_spellbar_enable); return false; } } @@ -696,23 +697,30 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp Always check again on SpellOnTarget to account for AE checks. */ - + bool ignore_on_casting = false; bool ignore_if_npc_or_gm = false; if (!IsClient() || (IsClient() && CastToClient()->GetGM())) { ignore_if_npc_or_gm = true; } - if (check_on_casting && !spell_target){ + if (check_on_casting){ - if (IsGroupSpell(spell_id) || - spells[spell_id].target_type == ST_AEClientV1 || - spells[spell_id].target_type == ST_AECaster || - spells[spell_id].target_type == ST_Ring || - spells[spell_id].target_type == ST_Beam){ - return true; + if (!spell_target) { + if (IsGroupSpell(spell_id) || + spells[spell_id].target_type == ST_AEClientV1 || + spells[spell_id].target_type == ST_AECaster || + spells[spell_id].target_type == ST_Ring || + spells[spell_id].target_type == ST_Beam) { + return true; + } + else if (spells[spell_id].target_type == ST_Self) { + spell_target = this; + } } - else if (spells[spell_id].target_type == ST_Self) { - spell_target = this; + else { + if (IsGroupSpell(spell_id) && spell_target != this) { + ignore_on_casting = true; + } } } @@ -772,13 +780,20 @@ bool Mob::DoCastingChecksOnTarget(bool check_on_casting, int32 spell_id, Mob *sp Prevents buff from being cast based on tareget ing PC OR NPC (1 = PCs, 2 = NPCs) These target types skip pcnpc only check (according to dev quotes) */ - if (spells[spell_id].pcnpc_only_flag && spells[spell_id].target_type != ST_AETargetHateList && - spells[spell_id].target_type != ST_HateList) { - if (spells[spell_id].pcnpc_only_flag == 1 && !spell_target->IsClient() && !spell_target->IsMerc() && !spell_target->IsBot()) { - return false; - } - else if (spells[spell_id].pcnpc_only_flag == 2 && (spell_target->IsClient() || spell_target->IsMerc() || spell_target->IsBot())) { - return false; + if (!ignore_on_casting) { + if (spells[spell_id].pcnpc_only_flag && spells[spell_id].target_type != ST_AETargetHateList && spells[spell_id].target_type != ST_HateList) { + if (spells[spell_id].pcnpc_only_flag == 1 && !spell_target->IsClient() && !spell_target->IsMerc() && !spell_target->IsBot()) { + if (check_on_casting) { + Message(Chat::SpellFailure, "This spell only works on other PCs"); + } + return false; + } + else if (spells[spell_id].pcnpc_only_flag == 2 && (spell_target->IsClient() || spell_target->IsMerc() || spell_target->IsBot())) { + if (check_on_casting) { + Message(Chat::SpellFailure, "This spell only works on NPCs."); + } + return false; + } } } /* @@ -1271,7 +1286,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo item = CastToClient()->GetInv().GetItem(inventory_slot); //checked for in reagents and charges. if (CastToClient()->HasItemRecastTimer(spell_id, inventory_slot)) { MessageString(Chat::Red, SPELL_RECAST); - LogSpells("Casting of [{}] canceled: item spell reuse timer not expired", spell_id); + LogSpells("Casting of [{}] canceled: item or augment spell reuse timer not expired", spell_id); StopCasting(); return; } @@ -1586,7 +1601,7 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo } // we're done casting, now try to apply the spell - if(!SpellFinished(spell_id, spell_target, slot, mana_used, inventory_slot, resist_adjust, false,-1, true)) + if(!SpellFinished(spell_id, spell_target, slot, mana_used, inventory_slot, resist_adjust, false,-1, 0xFFFFFFFF, 0, true)) { LogSpells("Casting of [{}] canceled: SpellFinished returned false", spell_id); // most of the cases we return false have a message already or are logic errors that shouldn't happen @@ -2206,7 +2221,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce // if you need to abort the casting, return false bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, uint16 mana_used, uint32 inventory_slot, int16 resist_adjust, bool isproc, int level_override, - bool from_casted_spell, uint32 aa_id) + uint32 timer, uint32 timer_duration, bool from_casted_spell, uint32 aa_id) { Mob *ae_center = nullptr; @@ -2580,54 +2595,43 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui if (mgb) { SetMGB(false); } - - //all spell triggers use Item slot, but don't have an item associated. We don't need to check recast timers on these. - bool is_triggered_spell = false; - if (slot == CastingSlot::Item && inventory_slot == 0xFFFFFFFF) { - is_triggered_spell = true; - } - - if (IsClient() && !isproc && !is_triggered_spell) + /* + Set Recast Timer on spells. + */ + if(IsClient() && !isproc) { - //Set Item or Augment Click Recast Timer - if (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt) { - CastToClient()->SetItemRecastTimer(spell_id, inventory_slot); - } - //Set Discipline Recast Timer - else if (slot == CastingSlot::Discipline) { - if (spell_id == casting_spell_id || (GetClass() == BARD && spells[spell_id].cast_time == 0 && spell_id != casting_spell_id)) { - CastToClient()->SetDisciplineRecastTimer(spell_id); + if (slot == CastingSlot::AltAbility) { + if (!aa_id) { + aa_id = casting_spell_aa_id; + } + if (aa_id) { + AA::Rank *rank = zone->GetAlternateAdvancementRank(aa_id); + //handle expendable AA's + if (rank && rank->base_ability) { + ExpendAlternateAdvancementCharge(rank->base_ability->id); + } + //set AA recast timer + CastToClient()->SendAlternateAdvancementTimer(rank->spell_type, 0, 0); } } - //Set AA Recast Timer. - else if (slot == CastingSlot::AltAbility){ - uint32 active_aa_id = 0; - //aa_id is only passed directly into spellfinished when a bard is using AA while casting, this supports casting an AA while clicking an instant AA. - if (GetClass() == BARD && spells[spell_id].cast_time == 0 && aa_id) { - active_aa_id = aa_id; - } - else { - active_aa_id = casting_spell_aa_id; - } - - AA::Rank *rank = zone->GetAlternateAdvancementRank(active_aa_id); - - CastToClient()->SetAARecastTimer(rank, spell_id); - - if (rank && rank->base_ability) { - ExpendAlternateAdvancementCharge(rank->base_ability->id); - } + //handle bard AA and Discipline recast timers when singing + if (GetClass() == BARD && spell_id != casting_spell_id && timer != 0xFFFFFFFF) { + CastToClient()->GetPTimers().Start(timer, timer_duration); + LogSpells("Spell [{}]: Setting BARD custom reuse timer [{}] to [{}]", spell_id, casting_spell_timer, casting_spell_timer_duration); } - //Set Custom Recast Timer + //handles AA and Discipline recast timers else if (spell_id == casting_spell_id && casting_spell_timer != 0xFFFFFFFF) { - //aa new todo: aa expendable charges here CastToClient()->GetPTimers().Start(casting_spell_timer, casting_spell_timer_duration); LogSpells("Spell [{}]: Setting custom reuse timer [{}] to [{}]", spell_id, casting_spell_timer, casting_spell_timer_duration); } - //Set Spell Recast Timer - else if (spells[spell_id].recast_time > 1000 && !spells[spell_id].is_discipline) { - int recast = spells[spell_id].recast_time / 1000; + else if(spell_id == casting_spell_id && casting_spell_timer != 0xFFFFFFFF) + { + CastToClient()->GetPTimers().Start(casting_spell_timer, casting_spell_timer_duration); + LogSpells("Spell [{}]: Setting custom reuse timer [{}] to [{}]", spell_id, casting_spell_timer, casting_spell_timer_duration); + } + else if(spells[spell_id].recast_time > 1000 && !spells[spell_id].is_discipline) { + int recast = spells[spell_id].recast_time/1000; if (spell_id == SPELL_LAY_ON_HANDS) //lay on hands { recast -= GetAA(aaFervrentBlessing) * 420; @@ -2647,14 +2651,20 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, CastingSlot slot, ui } recast = std::max(recast, 0); } - + LogSpells("Spell [{}]: Setting long reuse timer to [{}] s (orig [{}])", spell_id, recast, spells[spell_id].recast_time); - + if (recast > 0) { CastToClient()->GetPTimers().Start(pTimerSpellStart + spell_id, recast); } } } + /* + Set Recast Timer on item clicks, including augmenets. + */ + if(IsClient() && (slot == CastingSlot::Item || slot == CastingSlot::PotionBelt)){ + CastToClient()->SetItemRecastTimer(spell_id, inventory_slot); + } if (IsNPC()) { CastToNPC()->AI_Event_SpellCastFinished(true, static_cast(slot)); @@ -2715,8 +2725,6 @@ bool Mob::ApplyBardPulse(int32 spell_id, Mob *spell_target, CastingSlot slot) { if (!SpellFinished(spell_id, spell_target, slot, spells[spell_id].mana, 0xFFFFFFFF, spells[spell_id].resist_difficulty)) { return false; } - - return true; } /////////////////////////////////////////////////////////////////////////////// @@ -3479,10 +3487,6 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes } } - if (!DoCastingChecksOnTarget(false, spell_id, spelltar)) { - return false; - } - EQApplicationPacket *action_packet = nullptr, *message_packet = nullptr; float spell_effectiveness; @@ -3576,6 +3580,11 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, int reflect_effectivenes mod_spell_cast(spell_id, spelltar, reflect_effectiveness, use_resist_adjust, resist_adjust, isproc); + if (!DoCastingChecksOnTarget(false, spell_id, spelltar)) { + safe_delete(action_packet); + return false; + } + // now check if the spell is allowed to land if (RuleB(Spells, EnableBlockedBuffs)) { // We return true here since the caster's client should act like normal @@ -6193,13 +6202,19 @@ bool Client::HasItemRecastTimer(int32 spell_id, uint32 inventory_slot) } if (aug->Click.Effect == spell_id) { - recast_delay = aug_i->GetItem()->RecastDelay; - recast_type = aug_i->GetItem()->RecastType; + if (aug_i->GetItem() && aug_i->GetItem()->RecastDelay > 0) { + recast_delay = aug_i->GetItem()->RecastDelay; + recast_type = aug_i->GetItem()->RecastType; + } break; } } } - + //do not check if item has no recast delay. + if (!recast_delay) { + return false; + } + //if time is not expired, then it exists and therefore we have a recast on this item. if (!CastToClient()->GetPTimers().Expired(&database, (pTimerItemStart + recast_type), false)) { return true; } @@ -6207,40 +6222,6 @@ bool Client::HasItemRecastTimer(int32 spell_id, uint32 inventory_slot) return false; } -void Client::SetDisciplineRecastTimer(int32 spell_id) { - - if (!IsValidSpell(spell_id)) { - return; - } - - if (spells[spell_id].recast_time == 0) { - return; - } - - pTimerType DiscTimer = pTimerDisciplineReuseStart + spells[spell_id].timer_id; - uint32 timer_duration = spells[spell_id].recast_time / 1000; - auto focus = GetFocusEffect(focusReduceRecastTime, spell_id); - - if (focus > timer_duration) { - timer_duration = 0; - if (GetPTimers().Enabled((uint32)DiscTimer)) { - GetPTimers().Clear(&database, (uint32)DiscTimer); - } - else { - timer_duration -= focus; - } - } - - if (timer_duration <= 0) { - return; - } - - CastToClient()->GetPTimers().Start((uint32)DiscTimer, timer_duration); - CastToClient()->SendDisciplineTimer(spells[spell_id].timer_id, timer_duration); - LogSpells("Spell [{}]: Setting disciple reuse timer [{}] to [{}]", spell_id, spells[spell_id].timer_id, timer_duration); -} - - void Mob::CalcDestFromHeading(float heading, float distance, float MaxZDiff, float StartX, float StartY, float &dX, float &dY, float &dZ) { if (!distance) { return; } @@ -6668,4 +6649,6 @@ bool Mob::CheckItemRaceClassDietyRestrictionsOnCast(uint32 inventory_slot) { } return(false); } + + return true; } diff --git a/zone/string_ids.h b/zone/string_ids.h index 55cec653c..d5b6c52f3 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -469,6 +469,7 @@ #define LEADER_OF_X_GUILD 12258 #define NOT_IN_A_GUILD 12259 #define TARGET_PLAYER_FOR_GUILD_STATUS 12260 +#define TARGET_ALREADY_IN_GROUP 12265 //% 1 is already in another group. #define GROUP_INVITEE_NOT_FOUND 12268 //You must target a player or use /invite to invite someone to your group. #define GROUP_INVITEE_SELF 12270 //12270 You cannot invite yourself. #define ALREADY_IN_PARTY 12272 //That person is already in your party.