diff --git a/changelog.txt b/changelog.txt index 2dca36203..6c42a23ca 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,43 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 06/12/2015 == +Uleat: Adjusted SessionStats to better reflect a sister implementation + +== 06/07/2015 == +Uleat: Implemented optional rule for using disenchanted bags. Action triggers at the same point that temporary items are removed. +Optional SQL: utils/sql/git/optional/2015_06_07_TransformSummonedBagsRule.sql +mackal: changes to AA packets since more fields have been identified +mackal: fix exploit with expendable AAs punching holes in the aa_array and staying around longer than they are welcomed + +== 05/25/2015 == +Akkadius: Implemented disjointed zone based time, this can be triggered via quest methods +Akkadius: Added parameter to LUA and Perl method settime(hour, minute, [update_world = true]) + - If update_world is false, the zone will then unsubscribe itself from regular worldserver time synchronizations + - Basically this localizes the zones time and keeps it from syncing with world updates +Akkadius: Added DB ver 9082 with update to add npc_types texture columns if table does not currently have them + +== 05/22/2015 == +Uleat: Added null-term declaration for character names in ENCODE(OP_CharInfo) - where appropriate + +== 05/20/2015 == +demonstar55: Bard instrument mods should be more consistent with live. Zoning will keep instrument mod for long duration buffs (selo's) + Still need to have procs/doom effects to inherit the instrument mods from their source buff/whatever + +== 05/18/2015 == +KLS: Changed how fishing locates water to hopefully be a bit more accurate at the expense of a bit more cpu power per line cast. + +== 05/15/2015 == +Uleat: Added check to EntityList::CheckSpawnQueue() to bypass dereference if returned iterator is npc_list.end() - should fix the debug assertion failure crash + +== 04/30/2015 == +demonstar55: Implement mob and client melee push + You can set Combat:MeleePush to false to turn off or change Combat:MeleePushChance to increase the chance an NPC can be pushed + PCs are always pushed, need to do more testing to verify. + +== 04/22/2015 == +Uleat: Probable fix for 'Debug Assertion Failure' in Client::GarbleMessage() when calling the 'isalpha' macro. +ref: https://connect.microsoft.com/VisualStudio/feedback/details/932876/calling-isdigit-with-a-signed-char-1-results-in-a-assert-failure-in-debug-compiles + == 03/29/2015 == Secrets: Identified the Target Ring fields for RoF/RoF2. Secrets: Added a perl accessor for the last target ring position received from the client. Usage: $client->GetTargetRingX(), $client->GetTargetRingY(), $client->GetTargetRingZ() @@ -34,7 +72,7 @@ Notes: == 02/23/2015 == Noudess: Allow for a rule to set starting swimming && SenseHeading value. -I moved the swimming override to char create instead of setting it +I moved the swimming override to char create instead of setting it every time a char enters a zone. Also added rules to not ignore, but rather forrce sense heading packets to be @@ -102,7 +140,7 @@ Uleat: Removed 'limbo' from the 'HasItem' series of checks - including lore chec Uleat: Updated command #iteminfo to show light source information and a few other things == 02/05/2015 == -Trevius: Fixed Environmental Damage for RoF2. +Trevius: Fixed Environmental Damage for RoF2. == 02/03/2015 == Trevius: Crashfix for TempName() when numbers are passed at the end of the name. @@ -125,19 +163,19 @@ Akkadius: Add Packet Logging Categories - 39 - Packet: Server -> Client - Logs::Server_Client_Packet - 40 - Packet: Client -> Server Unhandled - Logs::Client_Server_Packet_Unhandled See: http://wiki.eqemulator.org/p?Logging_System_Overhaul#packet-logging - + == 01/31/2015 == Trevius: Fixed FindGroundZ() and GetGroundZ() to once again utilize the X and Y arguments that are passed to them. == 01/30/2015 == Akkadius: Implemented event type "EVENT_ENVIRONMENTAL_DAMAGE" - - This event triggers when taking any sort of environmental damage. Example use: + - This event triggers when taking any sort of environmental damage. Example use: sub EVENT_ENVIRONMENTAL_DAMAGE{ quest::debug("EVENT_ENVIRONMENTAL_DAMAGE"); quest::debug("env_damage is " . $env_damage); quest::debug("env_damage_type is " . $env_damage_type); quest::debug("env_final_damage is " . $env_final_damage); - } + } Result: (Test falling in Velks): http://i.imgur.com/tPRL7yL.png - Implemented LUA counterpart of this same implementation above Akkadius (Bobaski): Add PoK New Merchant sql/git/optional/2015_01_30_poknowledge_spell_vendors.sql @@ -156,7 +194,7 @@ Akkadius: Added Logs::DebugQuest category per request from Trevius (Great idea) quest::debug("This is a test debug message, level 1", 1); quest::debug("This is a test debug message, level 2", 2); quest::debug("This is a test debug message, level 3", 3); - + Result: http://i.imgur.com/6VoafGE.png - Uses traditional logging system to output this category - Required MySQL Source in Database version 9070 @@ -187,7 +225,7 @@ Notes: == 01/22/2015 == Akkadius: Massive Log System overhaul, see: http://wiki.eqemulator.org/p?Logging_System_Overhaul&frm=Main - + == 01/21/2015 == Uleat: Added `light` field to npc_types load query (all six clients tested positive for functionality.) Note: This only affects 'innate' light. Equipment (other) light is still in-work. @@ -229,7 +267,7 @@ Uleat: Added text link translators for OP_Emote Uleat: Added text link translators for OP_FormattedMessage == 01/08/2015 == -Trevius: Added some extra checks and clean-up related to Groups and Mercenaries. +Trevius: Added some extra checks and clean-up related to Groups and Mercenaries. == 01/07/2015 == Uleat: Excluded text link body from message scrambling in Client::GarbleMessage() @@ -401,10 +439,10 @@ Akkadius: Created database revision define, this is located in version.h in comm # empty = If the query results in no results # not_empty = If the query is not empty # 4 = Text to match - - The manifest contains all database updates 'Required' to be made to the schema, and it will contain a working backport all the way back to SVN - + - The manifest contains all database updates 'Required' to be made to the schema, and it will contain a working backport all the way back to SVN - currently it is tested and backported through the beginning of our Github repo - On world bootup or standalone run of db_update.pl, users will be prompted with a simple menu that we will expand upon later: - + ============================================================ EQEmu: Automatic Database Upgrade Check ============================================================ @@ -433,7 +471,7 @@ Database Management Menu (Please Select): Akkadius: Created db_update.pl, placed in utils/scripts folder, used for the automatic database update routine (Linux/Windows) - db_update.pl script created db_version table if not created, if old one is present it will remove it Akkadius: Created db_dumper.pl, placed in utils/scripts folder, used for the automatic database update routine backups and standalone backups (Linux/Windows) -Akkadius: World will now check the db_update.pl script on bootup, if the db_update.pl script is not present, it will fetch it remotely before running - +Akkadius: World will now check the db_update.pl script on bootup, if the db_update.pl script is not present, it will fetch it remotely before running - when db_update.pl is done running, world will continue with bootup == 11/15/2014 == @@ -611,10 +649,10 @@ Akkadius: Removed #refundaa Akkadius: Removed a lot of debug code for blob conversion Akkadius: Changed status logging for loads/saves to Debug category -== 09/21/2014 == +== 09/21/2014 == Akkadius: Player Profile Blob to Database Conversion - Summary: HUGE difference in database speeds reads/writes and 1:10 datasize difference - - The new character storage engine unlike the character_ table before, is able to properly index data and make use of + - The new character storage engine unlike the character_ table before, is able to properly index data and make use of proper MySQL/MariaDB caching optimizations and performance has increased phenominally PERFORMANCE AND STATISTICS FIGURES (Varies on hardware): - EZ Server Character data size of 2.6GB `character_` table alone now takes up approx 600MB @@ -660,7 +698,7 @@ Akkadius: Player Profile Blob to Database Conversion SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); - SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); + SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color); SaveCharacterSkill(uint32 character_id, uint32 skill_id, uint32 value); SaveCharacterLanguage(uint32 character_id, uint32 lang_id, uint32 value); SaveCharacterDisc(uint32 character_id, uint32 slot_id, uint32 disc_id); @@ -671,7 +709,7 @@ Akkadius: Player Profile Blob to Database Conversion - Deletes: DeleteCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); DeleteCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); - DeleteCharacterDisc(uint32 character_id, uint32 slot_id); + DeleteCharacterDisc(uint32 character_id, uint32 slot_id); DeleteCharacterBandolier(uint32 character_id, uint32 band_id); DeleteCharacterLeadershipAAs(uint32 character_id); - Now occur all over the code and only trigger when necessary @@ -684,17 +722,17 @@ Akkadius: Player Profile Blob to Database Conversion - NOTE: These amount of excessive saves have caused scalability issues that cause the `character_` table to hang which causes process hangs that affect the whole server because of the slowness of the `character_` table and the blob not allowing any indexing to occur - All functions that once depended on the `character_` table are now rewritten to appropriately read from the `character_data` table - - Database query errors that occur during conversion or from and load/save/delete character functions are now leveraged via ThrowDBError and logs now go to + - Database query errors that occur during conversion or from and load/save/delete character functions are now leveraged via ThrowDBError and logs now go to Server_Folder_Root/eqemu_query_error_log.txt (You cannot log errors natively through MySQL) - DBASYNC IS NOW COMPLETELY REMOVED - This was mainly for Character data async loads/saves and merchantlist loads - - Side implementations: + - Side implementations: Perl Exports: - quest::crosszonesetentityvariablebynpctypeid(npctype_id, id, m_var) - Sets entity variables world wide with specified npctype_id - quest::crosszonesignalnpcbynpctypeid(npctype_id, data) - Signals all NPC entities world wide with specified npctype_id - $client->GetTaskActivityDoneCount(TaskID, ActivityID) - Gets task activity done count by task id and activity id for client entity - + VIEW TABLE SIZE AFTER CONVERT: - + SELECT CONCAT(table_schema, '.', table_name) as table_name, CONCAT(ROUND(table_rows / 1000000, 2), 'M') rows, CONCAT(ROUND(data_length / ( 1024 * 1024 * 1024 ), 2), 'G') DATA, @@ -821,10 +859,10 @@ Akkadius: Changed all QS Error related logging to 'QUERYSERV__ERROR' Akkadius: (Natedog) (Crash Fix) Legacy MySQL bug revert for loading AA's COALESCE( from COALESCE ( Akkadius: Implemented Perl Quest objects (LUA still needed to be exported): - quest::qs_send_query("MySQL query") - Will send a raw query to the QueryServ process, useful for custom logging - - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` + - quest::qs_player_event(char_id, event_desc); - Will process a quest type event to table `qs_player_events` Akkadius: Added MySQL Tables - `qs_player_aa_rate_hourly` - - `qs_player_events` + - `qs_player_events` - Source table structures from: - utils\sql\git\queryserv\required\08_23_2014_player_events_and_player_aa_rate_hourly To get the complete QueryServ schema, source from here: @@ -833,7 +871,7 @@ Akkadius: Added rules for each logging type, source rules here with them enabled - utils\sql\git\queryserv\required\Complete_QueryServ_Rules_Enabled.sql Akkadius: Spawn related logging cleanup Akkadius: General code cleanup -Akkadius: More to come for QueryServ +Akkadius: More to come for QueryServ == 08/22/2014 == Uleat: Rework of Trade::FinishedTrade() and Trade::ResetTrade() to parse items a little more intelligently. @@ -877,8 +915,8 @@ Uleat (Kingly_Krab): Fix for bot chest armor graphic glitch. (fix also caused Ro == 08/02/2014 == Kayen: Implemented spell_news fields -- npc_no_los (check if LOS is required for spells) -- InCombat, OutofCombat - Used together to restrict spells to only be cast while +- npc_no_los (check if LOS is required for spells) +- InCombat, OutofCombat - Used together to restrict spells to only be cast while in/out of combat (beneficial) or if target is in/out of combat (detrimental). -min_dist, min_dist_mod, max_dist, max_dist_mod - Scales spell power based on targets distance from caster. *This will require further work to fully implement but will work with 90% of live spells as is. @@ -911,7 +949,7 @@ KLS: Changes to CMake build == 07/10/2014 == -Kayen: Updated table npc_spells to now support defensive and ranged procs. +Kayen: Updated table npc_spells to now support defensive and ranged procs. Note: Proc rate modifier work as it does for spell effects (ie 200 = 200% baseline chance modifier) Table is also now contains 12 AI spell casting variables that can be set to fine tune casting behaviors per spell set. Global default rules have also been added that can further fine tune all content if no specific variables are set. @@ -941,7 +979,7 @@ Param2: Percent Chance to Hit modifier Param3: Percent Total Damage modifier Kayen: Updated to Chance to Hit code with how bonuses are applied to be consistent for all effects. -Added field to npc_types 'Avoidance' which will modify chance to avoid melee +Added field to npc_types 'Avoidance' which will modify chance to avoid melee Added rules to set max and min chance to hit from melee/ranged (Default 95% / 5%) Required SQL: utils/sql/git/required/2014_07_10_npc_spells.sql @@ -955,7 +993,7 @@ Sympathetic foci on items with proc rate mod will now benefit from that modifier Sympathetic foci can now be placed on AA's (This should always be slot1 in the AA) Kayen: Implemented SE_IllusionPersistence- Allows illusions to last until you die or the illusion is forcibly removed. Kayen: Added rule 'PreNerftBardAEDot' for SE_BardAEDot to allow it to once again do damage to moving targets. (Set to true) -Kayen: Completely revised SE_SkillProc, SE_LimitToSkill, SE_SkillProcSuccess to overall just work better and more accurately, AA support. +Kayen: Completely revised SE_SkillProc, SE_LimitToSkill, SE_SkillProcSuccess to overall just work better and more accurately, AA support. Required SQL: utils/sql/git/required/2014_07_04_AA_Update.sql @@ -998,7 +1036,7 @@ Optional SQL: utils/sql/git/optiional/2014_06_29_HeadShotRules.sql == 06/17/2014 == Kayen: Implemented SE_AStacker, SE_BStacker, SE_CStacker, SE_DStacker. -These effects when present in buffs prevent each other from stacking, +These effects when present in buffs prevent each other from stacking, Any effect with B prevents A, C prevents B, D prevents C. Kayen: Implemented SE_DamageModifier2 (Stacks with SE_DamageModifier, mods damage by skill type) Kayen: Implemented SE_AddHatePct (Modifies +/- your total hate on NPC by percent) @@ -1048,7 +1086,7 @@ KLS: Implemented new map code based on some of Derision's earlier work. Old map == 04/27/2014 == Kayen: Implemented new table 'npc_spells_effects' and 'npc_spells_effects_entires'. Implemented new field in 'npc_spell_effects_id' in npc_types. - + These are used to directly apply spell effect bonuses to NPC's without requirings spells/buffs. Example: Allow an npc to spawn with an innate 50 pt damage shield and a 5% chance to critical hit. @@ -1065,7 +1103,7 @@ cavedude: Added strict column to spawn_events which will prevent an event from e cavedude: Prevented disabled or strict spawn_events from enabling when the zone first boots. cavedude: Fixed the quest function toggle_spawn_event under Perl. -If you're using the quest function toggle_spawn_event (worked on Lua only) it has changed syntax to: +If you're using the quest function toggle_spawn_event (worked on Lua only) it has changed syntax to: toggle_spawn_event(int event_id, bool enable, bool strict, bool reset_base) Required SQL: utils/sql/git/required/2014_04_25_spawn_events.sql @@ -1130,31 +1168,31 @@ Notes: See this thread for more information and to provide feedback: http://www.eqemulator.org/forums/showthread.php?p=229328#post229328 == 04/05/2014 == -Akkadius: Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality - for custom servers that have balanced their content around having more than 1 aug proc on weapons. By having this rule set to 'false' you revert this functionality. +Akkadius: Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality + for custom servers that have balanced their content around having more than 1 aug proc on weapons. By having this rule set to 'false' you revert this functionality. This rule is set to 'true' by default as the original functionality from Live was intended to be Akkadius: (Performance Adjustment) Removed AsyncLoadVariables from InterserverTimer.Check() in both zone and world. By watching the MySQL general.log file on mass zone idle activity, you can see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading - variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and + variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval. Secrets: Added a rule to enable multiple procs from the same weapon's other slots if a proc is deemed to trigger, Defaults to true. If Combat:OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not. This is for some servers that may want to have as many procs triggering from weapons as possible in a single round. - + Optional SQL: utils/sql/git/optional/2014_04_05_ProcRules.sql - + == 04/04/2014 == Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing. - SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations. + SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations. Required SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql == 04/03/2014 == -Kayen: Implemented live like spell projectiles (ie. Mage Bolts). +Kayen: Implemented live like spell projectiles (ie. Mage Bolts). Optional SQL: utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql Note: The rules in this SQL are for setting the item id for the graphic used by the projectile on different clients. - + == 04/01/2014 == demonstar55: Implemented ability for a merchant to open and close shop. Lua quest functions: e.self:MerchantOpenShop() and e.self:MerchantCloseShop() @@ -1260,7 +1298,7 @@ cavedude: Exported TrainDisc to Lua. Kayen: Implemented SE_FrenziedDevestation - increase critical spell chacnce and 2x mana cost for DD spells Kayen: Fixed SE_SpellProcChance - Now works on spell dervived procs cavedude: Added two new NPC special_abilities. ALWAYS_FLEE, which forces the NPC to always flee ignoring FleeIfNotAlone and FLEE_PERCENT which allows you to change the HP an individual NPC will flee at. If no value is set, the rule is used as normal. -cavedude: Fixed an issue where rectangular roamboxes could cause an NPC to get stuck on a single coord. +cavedude: Fixed an issue where rectangular roamboxes could cause an NPC to get stuck on a single coord. cavedude: Added a new roambox column, mindelay allowing you to have more control over the roambox delay. Uleat: Fix for 'sqrt' failure on vs2010 clients image: Added idle zone timer to save CPU cycles. @@ -1284,7 +1322,7 @@ Required SQL: utils/sql/git/2014_02_20_buff_updates.sql == 02/18/2014 == Kayen: Implemented SE_TriggerOnReqCaster - triggers a spell which a certain criteria are met (below X amount of hp,mana,end, number of pets on hatelist) Kayen: Implemented SE_ImprovedTaunt - Locks Aggro On Caster and Decrease other Players Aggro by X% on NPC targets below level Y -Kayen: Fixed an error where SE_ChangeAggro was adding its bonus x 2 for spell generated aggro. (this applies also to spell casting subtlety AA reduction) +Kayen: Fixed an error where SE_ChangeAggro was adding its bonus x 2 for spell generated aggro. (this applies also to spell casting subtlety AA reduction) == 02/14/2014 == Kayen: Fixes for buffs not fading under certain conditions in revised numhits system, and other fixes. @@ -1349,9 +1387,9 @@ Kayen: Changed SE_MitigateMeleeDamageSP -> SE_MeleeThresholdGuard Kayen: Implemented SE_SpellThresholdGuard (Partial Spell Rune that only is lowered if spell hits are over X amount of damage) Kayen: Implemented SE_TriggerSpellThreshold (implemented Trigger effect on X amount of spell damage taken) Kayen: Changed SE_ReduceHealing -> SE_FcHealAmtIncoming (focus limited Add/Remove amount of healing on target by X amount) -Kayen: Change SE_CriticalHealChance2 -> SE_CriticalHealDecay -Kayen: Change SE_CriticalHealOverTime2 -> SE_CriticalRegenDecay -Kayen: Implemented SE_CriticalDotDecay +Kayen: Change SE_CriticalHealChance2 -> SE_CriticalHealDecay +Kayen: Change SE_CriticalHealOverTime2 -> SE_CriticalRegenDecay +Kayen: Implemented SE_CriticalDotDecay Note: 'Decay' effects means the chance to critical decays based on the level of the spell using the effect (like focus decay) Kayen: Implemented SE_FfLimitUseMin (focus limit to require a min amount of numhits value) Kayen: Implemented SE_FcLimitUse (focus to increases numhits count by percent) @@ -1374,14 +1412,14 @@ demonstar55: Stuns from beneficial spells (Harvest) ignore immunity demonstar55: Added classes_required to merchantlist (same bitmask as items) == 12/24/2013 == -Secrets (Akkadius): Perl $client->SilentMessage("Message"); addition, this is a pre-req for a Perl plugin I've shared with EQEmu. This function essentially mimics a player speaking with an NPC - which is used in popup window responses -Secrets: Added functionality to Perl for $client->PlayMP3("name of file"). +Secrets (Akkadius): Perl $client->SilentMessage("Message"); addition, this is a pre-req for a Perl plugin I've shared with EQEmu. This function essentially mimics a player speaking with an NPC - which is used in popup window responses +Secrets: Added functionality to Perl for $client->PlayMP3("name of file"). Usage varies, but typically you can place an MP3/WAV/XMI in the EQDir//sounds, pfs, s3d, or root client folder and it will play through this Perl function. Example, $client->PlayMP3("combattheme1.mp3") or $client->PlayMP3("TUTBTrade1.mp3") - All clients except Secrets of Faydwer and 6.2 have their opcodes identified for this function. The struct + supported params is the same throughout versions. + All clients except Secrets of Faydwer and 6.2 have their opcodes identified for this function. The struct + supported params is the same throughout versions. Use $client->PlayMP3 with an invalid sound file to stop playback or simply wait for it to end. KLS: Added functionality to Lua for Client:PlayMP3(filename) KLS: Added functionality to Lua for Client:SendMarqueeMessage(type, priority/opacity, fade_in_time_ms, fade_out_time_ms, duration_ms, msg) - + == 12/16/2013 == Kayen: Implemented SE_ArcheryDoubleAttack (Chance to do an extra archery attack) Kayen: Implemented SE_ShieldEquipDmgMod (Increase damage in primary hand if shield equiped) @@ -1601,7 +1639,7 @@ Param2: Percent of a normal attack damage to deal (default: 100) Param3: Flat damage bonus to add to the rampage attack (default: 0) Param4: Ignore % armor for this attack (default 0) Param5: Ignore flat armor for this attack (default 0) -Param6: Percent of npc's natual crit that can go toward this rampage (default: 100) +Param6: Percent of npc's natual crit that can go toward this rampage (default: 100) Param7: Flat crit bonus on top of npc's natual crit that can go toward this attack (default 0) SPECATK_AREA_RAMPAGE = 4 @@ -1611,7 +1649,7 @@ Param2: Percent of a normal attack damage to deal (default: 100) Param3: Flat damage bonus to add to the rampage attack (default: 0) Param4: Ignore % armor for this attack (default 0) Param5: Ignore flat armor for this attack (default 0) -Param6: Percent of npc's natual crit that can go toward this rampage (default: 100) +Param6: Percent of npc's natual crit that can go toward this rampage (default: 100) Param7: Flat crit bonus on top of npc's natual crit that can go toward this attack (default 0) SPECATK_FLURRY = 5 @@ -1621,7 +1659,7 @@ Param2: Percent of a normal attack damage to deal (default: 100) Param3: Flat damage bonus to add to the flurry attack (default: 0) Param4: Ignore % armor for this attack (default 0) Param5: Ignore flat armor for this attack (default 0) -Param6: Percent of npc's natual crit that can go toward this attack (default: 100) +Param6: Percent of npc's natual crit that can go toward this attack (default: 100) Param7: Flat crit bonus on top of npc's natual crit that can go toward this attack (default 0) Ex: Normal Flurry with 25% proc rate and 100% crit chance that ignores 500 armor. @@ -1708,7 +1746,7 @@ Upgrade notes: -Some item quests have changed in a subtle way, though it's unlikely any quests are impacted and the thread has more information if you found any of your quests broke. As far as I know for example: PEQ didn't have to update any of its nearly 70 item quests. -Cazic Touch (982) no longer shouts the name of the thing it is targeting without a script. -EVENT_DEATH now triggers before the death is complete. For the old functionality you may use EVENT_DEATH_COMPLETE. It might be a good idea to replace all EVENT_DEATH with EVENT_DEATH_COMPLETE in existing spells. - + We sought to minimize changes required but it's still a bit disruptive so take a few minutes when upgrading to make sure everything is correct. Most notably quest::clearhandin was used in some popular plugins to avoid a dupe involved with its code and now that it's gone those will not function if fixes are not applied. == 06/16/2013 == @@ -1805,7 +1843,7 @@ demonstar55: Fixed stacking issues with SE_Limit* (ex. Unholy Aura Discipline an == 03/18/2013 == Bad_Captain: Fixed zone crash due to merc focus effects & tribute. -Bad_Captain: Fixed merc aggro issues when client in melee range & spell recast timers. +Bad_Captain: Fixed merc aggro issues when client in melee range & spell recast timers. Bad_Captain: Added melee DPS spells/disciplines & support. == 03/17/2013 == @@ -1818,9 +1856,9 @@ Derision: Fixed a couple of memory leaks in Rez code. == 03/14/2013 == JJ: (NatedogEZ) Fix for hate list random never selecting last member of hate list. -Bad_Captain: Fixed Merc spell recast timers. +Bad_Captain: Fixed Merc spell recast timers. Bad_Captain: Changed how Mercs add mobs to their hate lists (should prevent IsEngaged() issues). -Bad_Captain: Initial Caster DPS Merc spell casting AI, including initial Merc stance implementation. +Bad_Captain: Initial Caster DPS Merc spell casting AI, including initial Merc stance implementation. Bad_Captain: Mercs now suspend when their owner dies to prevent them being bugged (until it can be fixed). OPTIONAL SQL: 2013_03_14_Merc_Spells.sql @@ -1854,7 +1892,7 @@ REQUIRED SQL: 2013_03_1_Merc_Rules_and_Equipment.sql KLS: Changed how shared memory works: Instead of System V/windows pagefile shared memory we now have shared memory that's backed by the filesystem. What that means is basically instead of EMuSharedMem(shared library) we now have shared_memory(executable), shared memory will be persistent between runs until you delete or reload it using the shared_memory executable. - + STEPS FOR PEOPLE WHO CAN'T BE BOTHERED TO FIGURE IT OUT: 1) Create a directory in the place you run world/zone named shared and make sure files can write there. 2) Run the shared_memory executable from the same place you run world/zone (it's basically doing the loading we would do on startup so will take a moment). @@ -1913,7 +1951,7 @@ Uleat: Changed conversion of bot armor colors from long to unsigned long. Conver cavedude00: Added heading to start_zones Uleat: Fixed the 'nude' bot issue. Mob::texture was not set to the appropriate value and forcing an unclad body model. Uleat: Fixed the show/hide helm feature. Added rebroadcast of packet so that changes take place immediately instead of after zoning. -KLS: Addressed several (completely stupid and inexcusable) bugs in the avoidance code that made it impossible to dodge and parry in certain situations. +KLS: Addressed several (completely stupid and inexcusable) bugs in the avoidance code that made it impossible to dodge and parry in certain situations. As a note: please don't touch the avoidance code if you don't know what you're doing, seriously. Required SQL: utils/sql/svn/2482_required_start_zones.sql @@ -2000,7 +2038,7 @@ Uleat: Fixed a corpse looting issue where the power source item (slot 9999) was Uleat: Power Source items will now report in 'worn' instead of 'inv' when using #peekinv. == 01/20/2013 == -KLS: intN types have changed to more closely reflect C99 and C++11 types: +KLS: intN types have changed to more closely reflect C99 and C++11 types: intN was an unsigned int of N bits -> it is now a signed int of N bits. sintN was a signed int of N bits -> it has been removed in favor of intN. uintN is still unsigned. @@ -2054,7 +2092,7 @@ Trevius: RoF: Disciplines now update without zoning. == 01/12/2013 == Derision: RoF: Personal Tribute and the Pet Buff Window now work. -Derision: Fixed potential crash in SendPetBuffsToClient. +Derision: Fixed potential crash in SendPetBuffsToClient. Derision: RoF: Accounted for the fact the Duplicate Lore item message now includes the item's name. Trevius: RoF: The Task Selector Window is now functional. @@ -2084,7 +2122,7 @@ REQUIRED SQL: utils/sql/svn/2383_required_group_ismerc.sql -- adds ismerc col OPTIONAL SQL: utils/sql/svn/2380_optional_merc_rules.sql -- Contains rules for mercs including rule to enable mercs OPTIONAL SQL: utils/sql/svn/2380_optional_merc_merchant_npctypes_update.sql -- Contains npc_types & spawn updates for merc merchants in PoK OPTIONAL SQL: utils/sql/svn/2380_optional_merc_data.sql -- Contains basic merc data, template info, & merc merchant entries -OPTIONAL SQL: utils/sql/svn/mercs.sql -- Contains merc stats & armor - to be replaced as needed with updated stats, spells, etc. Allows a complete resourcing of file +OPTIONAL SQL: utils/sql/svn/mercs.sql -- Contains merc stats & armor - to be replaced as needed with updated stats, spells, etc. Allows a complete resourcing of file == 01/07/2013 == Trevius: RoF: /who and /who all now function properly. @@ -2241,7 +2279,7 @@ Secrets/Akkadius: Implemented the customary ability to scale an NPC's spell dama This will allow an NPC to do for example 150% of the damage of their damage spells if spellscale is set to '150' 'healscale' field needs to be set to affect heals in a similar manner Both of these can also be accessed via ModifyNPCStat through 'healscale' and 'spellscale' - + REQUIRED SQL: utils/sql/svn/2283_required_npc_changes.sql ==12/06/2012== @@ -2368,7 +2406,7 @@ Kayen: Fix for crippling blow chance, other minor fixes related to bonuses. ==09/20/2012== Kayen: AA dbase table fixes - Archery Mastery, Fury of Magic. -REQUIRED SQL: utils/sql/svn/2215_required_aa_updates +REQUIRED SQL: utils/sql/svn/2215_required_aa_updates ==09/19/2012== KLS: Faction mods (Race, Class, Deity) can now be added on the fly, without adding database columns or editing the code. Source in the database change, run utils/factionmod.pl, and then drop the faction_list table to convert. @@ -2390,7 +2428,7 @@ Kayen: Removed SE_Twinproc - This is not an actual spell effect, Twin proc is no Kayen: Implemented SE_TwoHandBluntBlock - Chance to block when using two hand blunt weapon (similiar to shield block). Kayen: SE_NegateEffect will now negate all AA, item and spell bonuses for the specified effects. Kayen: Added support to allow for certain bonuses to now properly be calculated when cast as debuffs (ie decrease chance to critical hit) -Kayen: Implemented rule to allow certain bonuses to be calculated by adding together the value from each item, instead of taking the highest value. +Kayen: Implemented rule to allow certain bonuses to be calculated by adding together the value from each item, instead of taking the highest value. *Ie. Add together all worn cleave effects, ferocity effects ect. OPTIONAL SQL: utils/sql/svn/2209_optional_additive_bonus_rule.sql (disabled by default) @@ -2417,7 +2455,7 @@ references: http://www.eqemulator.org/forums/showthread.php?t=35604 - CSD Support Patch http://www.eqemulator.org/forums/showthread.php?t=35629 - CSD Bugged Corpse Patch http://www.eqemulator.org/forums/showthread.php?t=35699 - CSD Bandolier Patch - + cavedude: (demonstar55) Damage shields by default will no longer count towards EXP gain. (Rule also added to change this behaviour.) cavedude: (demonstar55) Extended targets should now clear when aggro is lost using skills. cavedude: (demonstar55) AAs with shorter reuse timers should now reset if the cast failed (interrupted.) @@ -2441,7 +2479,7 @@ ENC AA: Total Domination now implemented with bonus SE_CharmBreakChance. SK AA: Soul Abrasion now implemented with bonus SE_ImprovedDamage2. *Many fixes to previously implemented AA effects. -REQUIRED SQL: utils/sql/svn/2208_required_aa_updates +REQUIRED SQL: utils/sql/svn/2208_required_aa_updates OPTIONAL SQL: utils/sql/svn/2208_optional_aa_stacking_rule.sql (If false will disable AA stacking for all clients) OPTIONAL SQL: utils/sql/svn/2208_optional_EnableSoulAbrasionAA *If using an older server spell file (pre SOF), will need to run this to correctly populate the 'spellgroups' field. @@ -2495,11 +2533,11 @@ Kayen: Taunt skill updated to work consistent with live. *Taunt success chance should be accurate to live. Penalty of not being at max skill lv can be adjusted with (RULE: Combat:TauntSkillFalloff) *Live messages for taunt success and failure are now implemented. (Note: Only NPC races that can 'talk' will say the success message). Bad_Captain: Bots- Fixed an issue when with bot spell timers that could lead to a crash. -Kayen: Fixed SQL files from rev2185+ that were not saved as .sql +Kayen: Fixed SQL files from rev2185+ that were not saved as .sql -OPTIONAL SQL: utils/sql/svn/2189_optional_taunt_rules +OPTIONAL SQL: utils/sql/svn/2189_optional_taunt_rules OPTIONAL SQL: utils/sql/svn/2185_optional_NPCFlurryChance_rule (run this again) - + ==08/16/2012== Kayen: Complete revision of the Death Save and Divine Save effects and related bonuses to be consistent with live. *Death Save (Death Pact/DI) will no longer fire on death. It should only fire when less 15% HP but not killed. @@ -2509,7 +2547,7 @@ Kayen: Complete revision of the Death Save and Divine Save effects and related b do a portion of the original heal value. (Ie DI=8000HP with ToTD-3(60%) your heal will be 8000*0.6 = 4800HP) *Implemented functionality for later expansion Death Save effects (Divine Intercession ect) These can add heal value to the base heal which is limited by level (ie Heal 10,0000+8000 if client less then level 80) - + *Divine Save (AA Unfailing Divinity) only fire upon death of client with this effect and is independent of Death Save effect. *Increased ranks of AA only increase the chance of firing upon death. Heal value does NOT increase with rank. *Upon firing you will still recieve the Divine Aura like buff, which now also correctly removes all determental effects. @@ -2531,7 +2569,7 @@ DRU AA: Enchanted root/Viscid Root (was implemented completely wrong), now uses Added RULE: RootBreakFromSpells - Baseline is/was set at 20% chance from nukes. -REQUIRED SQL: utils/sql/svn/2188_required_aa_updates +REQUIRED SQL: utils/sql/svn/2188_required_aa_updates OPTIONAL SQL: utils/sql/svn/2188_optional_miscspelleffect_rules ==08/13/2012== @@ -2543,10 +2581,10 @@ Kayen: Minor update to duel wield, chance bonuses will be correctly applied to b Kayen: Implemented Perl MOB Quest Object SetFlurryChance(value) (ie 50 = 50% chance for NPC flurry if special atk "F") Kayen: Implemented Perl MOB Quest Object GetFlurryChance(value) returns flurry chance. Kayen: Added rule to adjust server wide flurry chance (Default = 20%) *Perl object will override this. - + Following AA effects are now implemented as bonuses: You must update your 'aa_effects' table with 'required_aa_updates.sql' for these to work. MISC AA: Dead Aim, Precision of the Hunter, Scout's Efficiency will now calculate from bonus SE_Accuracy. -MISC AA: Combat Agility line will now calculate from bonus SE_AvoidMeleeChance. (Now implemented through SoD) +MISC AA: Combat Agility line will now calculate from bonus SE_AvoidMeleeChance. (Now implemented through SoD) MISC AA: Combat Stability line will now calculate from bonus SE_CombatStability. (Now implemented through SoD) MISC AA: Double Riposte, Return Kick now calculated from bonus SE_GiveDoubleRiposte. (New AA's implemented that use this) MISC AA: Natural Durability, should be working correctly now from bonus SE_MaxHP. @@ -2556,33 +2594,33 @@ MISC AA: Pet AA's that give flurry chance will now be applied by bonus, SE_PetFl MAG AA: Elemental Agility was incorrectly giving melee mitigation instead of avoidance, to now use SE_PetAvoidance. MAG AA: Elemental Durability will now add to pet max hp with bonus, SE_PetMaxHP. -REQUIRED SQL: utils/sql/svn/2185_required_aa_updates +REQUIRED SQL: utils/sql/svn/2185_required_aa_updates OPTIONAL SQL: utils/sql/svn/2185_optional_NPCFlurryChance_rule ==08/12/2012== Bad_Captain: Fixed an issue when using bots where you would not get xp when your pet did most of the damage and you are not grouped. -Bad_Captain: Added rule to enable receiving xp from bots not in your group. Defaults to false. +Bad_Captain: Added rule to enable receiving xp from bots not in your group. Defaults to false. -OPTIONAL SQL: utils/sql/svn/2183_optional_bot_xp_rule.sql +OPTIONAL SQL: utils/sql/svn/2183_optional_bot_xp_rule.sql ==08/08/2012== -Kayen: Updates to critical hit calcuations to be consistent with live. - *Baseline critical rate is determined by DEX stat (255 dex = 2%),this baseline is then modified by item,spell and AA critical chance bonuses. +Kayen: Updates to critical hit calcuations to be consistent with live. + *Baseline critical rate is determined by DEX stat (255 dex = 2%),this baseline is then modified by item,spell and AA critical chance bonuses. *Pet critical baseline is determined by the rate from AA effects that give pets the ability to critical. (Can not crit w/o this effect) -Kayen: Slay Undead effect will now be working consistent with live. - *Slay rate is no longer effected by critical hit rate, it uses it's own predetermined rates from the spell effect data. - *Damage modification will now be much closer to that of lives utilizing the spell effects damage modifier. +Kayen: Slay Undead effect will now be working consistent with live. + *Slay rate is no longer effected by critical hit rate, it uses it's own predetermined rates from the spell effect data. + *Damage modification will now be much closer to that of lives utilizing the spell effects damage modifier. *Example(AA Slay Undead I - Rate: 225 (2.25%) Damage Mod: 680%) *Note that that spell effects using Slay Undead (Holyforge) will stack with AA and increase the damage and the rate. -Kayen: Crippling Blow's derived from spell effects will now be calculated consistent with live. - *Cippling blow chance is determined by modifying your critical hit chance. - *Example (Savage Onslaught - 200% chance to crippling blow) If you have a base chance to critical hit of 10% +Kayen: Crippling Blow's derived from spell effects will now be calculated consistent with live. + *Cippling blow chance is determined by modifying your critical hit chance. + *Example (Savage Onslaught - 200% chance to crippling blow) If you have a base chance to critical hit of 10% and you score a critical hit, you will then have a 20% chance to crippling blow. Kayen: Fixed effect for AA 'War Cry' will now provide group fear immunity for duration. Kayen: Few new AA's added including shaman Ancestral Aid and Spirit Channeling. - + Following AA effects are now implemented as bonuses: You must update your 'aa_effects' table with 'required_aa_updates.sql' for these to work. -MISC AA: 'Combat Fury', 'Fury of the Ages' will now calculate from bonus SE_CriticalHitChance. +MISC AA: 'Combat Fury', 'Fury of the Ages' will now calculate from bonus SE_CriticalHitChance. MISC AA: 'Veteran's Wrath' will now calculate from bonus SE_CriticalDmgMod. MISC AA: All AA that give pet criticals will be calculated from SE_PetCriticalHit. PAL AA: 'Slay Undead', 'Vanquish Undead' will now calculate from bonus SE_SlayUndead. (Holyforge now works correctly) @@ -2593,7 +2631,7 @@ REQUIRED SQL: utils/sql/svn/2178_required_aa_updates Kayen: Fixes to rev2176 - Double Attack, EndlessQuiver, Run Speed. REQUIRED SQL: utils/sql/svn/2176_required_aa_updates will need to be applied again for Run Speed fix. -==08/05/2012== +==08/05/2012== Kayen: Complete revision of how double attack chance is calculate to be consistent with live. Kayen: Berserker 'Frenzy' skill attack will now function as it does on live. Gives a chance for 1-3 attacks using frenzy skill specific damage. *Note: This will be a considerable nerf to bersekers because it was previously coded to give actual melee rounds using weapon damage. @@ -2601,19 +2639,19 @@ Kayen: Implemented a few miscellaneous new AA's, mostly from DODH. (These will s Kayen: Added almost all remaining spell effects into spdat.h (~90% defined). Kayen: Alternate Advancement Update: In the process of coverting most of the hard coded AA data out of the source and into the 'aa_effects' table using live data. These effects are then reimplemented using the bonus system in the broadest possible way, most of these effects will -also be useable as regular spell/item effects. This will allow developers in the future to adjust,implement and customize AA effects -without requiring source changes. Beware in doing this, many AA effects that were previously either implemented incorrectly or with values +also be useable as regular spell/item effects. This will allow developers in the future to adjust,implement and customize AA effects +without requiring source changes. Beware in doing this, many AA effects that were previously either implemented incorrectly or with values not consistent with live data will be adjusted to be as accurate as possible. Following AA effects are now implemented as bonuses: You must update your 'aa_effects' table with 'required_aa_updates.sql' for these to work. -MISC AA: All innate run speed AA's have been converted to use bonus SE_BaseMovementSpeed. +MISC AA: All innate run speed AA's have been converted to use bonus SE_BaseMovementSpeed. MISC AA: All bind wound related AA have been converted to use bonuses, SE_ImprovedBindWound, SE_MaxBindWound. MISC AA: Shield Block will now be calculated using bonuses, SE_ShieldBlock. MISC AA: Sinister Strike will now correctly allow your off hand weapon to recieve a weapon damage bonus, SE_SecondaryDmgInc. MISC AA: Strengthened Strike/Vicious Smash/Kick Mastery will now accurately to live add skill damage to respective special abilities. MISC AA: StrikeThrough, Tactical Mastery - will now be calculated from bonus, SE_Strikethrough2. MISC AA: Ferocity, Knight's Advantage ect - will now be calculated from bonus SE_DoubleAttackChance. -MISC AA: Harmonious Attacks, Bestial Frenzy - will allow double attack chance using bonus SE_GiveDoubleAttack (any class can be given this) +MISC AA: Harmonious Attacks, Bestial Frenzy - will allow double attack chance using bonus SE_GiveDoubleAttack (any class can be given this) MISC AA: Weapon Affinity, will now use SE_ProcChance. MISC AA: PunishingBlade, SpeedoftheKnight will now allow an extra 2 Handed attack using bonus, SE_ExtraAttackChance. BRD AA: Fleet of Foot will now correctly allow bards to run over the speed cap using, SE_IncreaseRunSpeedCap. @@ -2625,42 +2663,42 @@ RNG AA: Endless Quiver - Implemented as SE_ConsumeProjectile which gives a perc RNG AA: Archery Mastery- Implemented as SE_ArcheryDamageModifier which gives a percent increase to archery attacks. BER AA: Throwing Mastery - Implemented using SE_DamageModifier. BER AA: Blur of Axes, Vicious Frenzy - will now correctly add skill damage to 'Frenzy' skill attacks. -ROG AA: Triple Backstab, SiezedOpportunity, Chaotic Stab - as bonus, SE_TripleBackstab, SE_FrontalBackstabChance,SE_FrontalBackstabMinDmg. +ROG AA: Triple Backstab, SiezedOpportunity, Chaotic Stab - as bonus, SE_TripleBackstab, SE_FrontalBackstabChance,SE_FrontalBackstabMinDmg. REQUIRED SQL: utils/sql/svn/2176_required_aa_updates Optional SQL: utils/sql/svn/2176_optional_FrenzyBonus_rule Optional SQL: utils/sql/svn/2176_optional_aa_expansion_sof_fix (Allow AA to show proper expansion in SOF+ clients) -==08/01/2012== +==08/01/2012== Bad_Captain: Fixed bot compile issue introduced in Rev 2171. Bad_Captain: Integrated Kayen's skill attack code with bots' code, as well as other changes from Rev 2171. ==07/31/2012== Akkadius: (KLS) Fix for global_player.pl synchronization. There was an issue where certain subroutines were not passing as global -==07/26/2012== +==07/26/2012== Kayen: Fixed: SE_ImmuneFleeing - Will now disable fleeing if used after mob begins to run, will not effect fleeing from fear. -Kayen: Implemented Perl MOB Quest Object SetDisableMelee(1=Disabled) - Prevents the ability to auto attack. +Kayen: Implemented Perl MOB Quest Object SetDisableMelee(1=Disabled) - Prevents the ability to auto attack. Kayen: Implemented Perl MOB Quest Object IsMeleeDisabled() Sorvani: corrected build name for queryserv project in all the various build types. - -==07/24/2012== + +==07/24/2012== Kayen: Implemented: SE_HundredHands (incorrectly marked as already implemented) - Increases/Decrease actual weapon delay by % of value. Kayen: Implemented: Missing modifications from the Archery/Throw damage pathways. *SkillAmount/SkillDamageTaken mods, ability to Block/Dodge/Parry ranged attacks (Can not riposte), other new focus/mod effects. -Kayen: Fixed: aaThrowingMastery will no longer be applied 2x per throw. +Kayen: Fixed: aaThrowingMastery will no longer be applied 2x per throw. Kayen: Implemented: Damage bonus to skill attacks from specific armor slot AC (ie KICK from BOOT AC) can adjust with rule 'SpecialAttackACBonus' Kayen: SE_MinDamageModifier should now apply to skill specific effects and to special attacks. Kayen: Implemented: Complete revision of SE_SkillAttack. (+ required various fixes/adjustments to functions related to special attacks) *This spell effect performs a physical attack from a specific skill with a set weapon damage value and chance to hit modifier. *Attacks will now calculate correctly and use their actual respected pathways utilizing all skill specific mods/bonus. -Kayen: Implemented Perl MOB Quest Object DoMeleeSkillAttackDmg(target, weapon_damage, skill, chance_mod, focus, CanRiposte) -Kayen: Implemented Perl MOB Quest Object DoArcheryAttackDmg(target,NULL,NULL, weapon_damage, chance_mod, focus) -Kayen: Implemented Perl MOB Quest Object DoThrowingAttackDmg(target,NULL,NULL, weapon_damage, chance_mod, focus) +Kayen: Implemented Perl MOB Quest Object DoMeleeSkillAttackDmg(target, weapon_damage, skill, chance_mod, focus, CanRiposte) +Kayen: Implemented Perl MOB Quest Object DoArcheryAttackDmg(target,NULL,NULL, weapon_damage, chance_mod, focus) +Kayen: Implemented Perl MOB Quest Object DoThrowingAttackDmg(target,NULL,NULL, weapon_damage, chance_mod, focus) Optional SQL: utils/sql/svn/2171_optional_SpecialAttackACBonus_rule - + ==07/18/2012== Kayen: Implemented NPC Special Attack 'K'. Immune to Dispell. Kayen: Fixed SE_RangedProc to be applied after zoning, calculate proc rate correctly, utilize numhits. @@ -2675,7 +2713,7 @@ Kayen: Implemented: SE_SpellProcChance, SE_CharmBreakChance, SE_BalanceMana, SE_ SE_CriticalHealOverTime2, SE_CriticalHealChance2, SE_SkillDamageAmount2, SE_LimitSpellSkill, SE_LimitClass SE_LimitExcludeSkill, SE_ShieldBlock, SE_BlockBehind Kayen: Implemented: SE_Empathy - A focus limited debuff which causes (spells/skill attacks) cast on target to do +X amount more damage. - *This spell effect is often limited using SE_LimitSpellSkill. + *This spell effect is often limited using SE_LimitSpellSkill. *(Ie. If empathy value is 1000 and limited to evocation, all nukes on target using evoc. will get +1000 damage) Kayen: Implemented: SE_SpellPowerIncrease - Can be used to apply a worn/buff focus effect with limits for... 1) Additional bard instrument modifiers. @@ -2683,16 +2721,16 @@ Kayen: Implemented: SE_SpellPowerIncrease - Can be used to apply a worn/buff foc 3) Increase effectiviess of casted/disc Skill Attacks. 4) General use stackable Healing/Damage focus. 5) Increase the value of melee runes by focus amount. -Kayen: SE_HPtoMana will no longer drain your HP before the spell is finished casting. Effect now handled as a bonus using best value. -Kayen: SE_SpellVulunerability will now correctly calculate and apply the highest value if target has mulitple effects. -Kayen: Fixed an issue where DOTs cast by an NPC on another NPC would not generate hate per tick. Improved how we calc damage from DOTs. +Kayen: SE_HPtoMana will no longer drain your HP before the spell is finished casting. Effect now handled as a bonus using best value. +Kayen: SE_SpellVulunerability will now correctly calculate and apply the highest value if target has mulitple effects. +Kayen: Fixed an issue where DOTs cast by an NPC on another NPC would not generate hate per tick. Improved how we calc damage from DOTs. Kayen: SE_BlockSpellEffect was implemented incorrectly and has been revised to SE_NegateSpellEffect. *It is not meant to block buffs, rather it negates the specific spell bonuses or effects from buffs you already have. *Currently functional with any effect that is handled under bonuses, and focus effects. Kayen: Vastly improved how we handle melee/spell runes/partial mitigation runes ect. Should be significantly more efficient. *Melee/Spell Mitigation runes and ManaAbsorbPercentDamage effects will now use the best mitigation value if multiple effects. Kayen: Improved the process we use to get spell/item focus effects. An initial check if the client has a specific focusType -is done while checking for item/spell bonuses. GetFocusEffect will no longer check all inventory and buff slots every cast for the +is done while checking for item/spell bonuses. GetFocusEffect will no longer check all inventory and buff slots every cast for the focusType if that focusType doesn't exist on the client. This should improve performance since these checks are done 10-20x per cast/proc. ==07/15/2012== @@ -2702,7 +2740,7 @@ Secrets: Fixed an issue with saylinks above 255 characters. If you do want to us Bad_Captain: Fixed another bug that allowed pets to steal xp when using bots. Bad_Captain: Bots: Persisting spell & discipline timers. Bad_Captain: Bots: Bot pets will no longer continuously try to get behind a mob if they are tanking. -Bad_Captain: Bots: Fixed potential duplicate lore item bug when trading with bots. +Bad_Captain: Bots: Fixed potential duplicate lore item bug when trading with bots. Bad_Captain: Bots: Fixed bot compile issue from Rev 2160. Bad_Captain: Bots: Fixed multiple issues with #bot spawn and #bot botgroup load (c0ncrete). Bad_Captain: Bots: Implemented new #bot defensive command for Warriors and Knights. Includes most of the code required to implement disciplines for bots. Requires disc AI & disc lists. @@ -2725,9 +2763,9 @@ Kayen: Updated spdat.h with many new live spell effects that were previously 'un ==07/05/2012== Kayen: Implemented: SE_Manaburn: Drains mana for damage/heal at a defined ratio up to a defined maximum amount of mana. -Kayen: Implemented: SE_CastonNumHitFade: Casts a spell when a buff fades due to its numhits being depleted. +Kayen: Implemented: SE_CastonNumHitFade: Casts a spell when a buff fades due to its numhits being depleted. Kayen: Implemented: support for all remaining live spell effects that use 'numhits'. - Significantly optimized how all spell effects that utilize 'numhits' are handled throughout the source. + Significantly optimized how all spell effects that utilize 'numhits' are handled throughout the source. Kayen: Fixed: SE_DefensiveProc - Proc rate will now be calculated similiar to live. *Spell specific rate modifcations will now work. Kayen: Added Rule: Combat:AvgDefProcsPerMinute (Default = 2.0) Determine defensive procs per minute. Kayen: Added Rule: Combat:DefProcPerMinAgiContrib (Default = 0.075) Determines how much agility effects proc rate. @@ -2735,9 +2773,9 @@ Kayen: Added Rule: Combat:DefProcPerMinAgiContrib (Default = 0.075) Determines h Optional SQL: utils/sql/svn/2159_optional_defensiveproc_rules ==06/29/2012== -Kayen: Implemented Perl NPC Quest Object GetSlowMitigation() -Kayen: Implemented Perl NPC Quest Object GetAttackSpeed() -Kayen: Implemented Perl NPC Quest Object GetAccuracyRating() +Kayen: Implemented Perl NPC Quest Object GetSlowMitigation() +Kayen: Implemented Perl NPC Quest Object GetAttackSpeed() +Kayen: Implemented Perl NPC Quest Object GetAccuracyRating() Kayen: Fixed: Slow Mitigation (was not loading from dbase),optimized its application in source and added lives slow mitigation messages. Slow mitigation Messages: 'slighty' 0.00 - 0.25 'partial' 0.25-0.75 'mostly' 0.75-1 Kayen: Implemented: SE_AttackSpeed4: 'Inhibit Melee' effect works different then regular haste/slow effect @@ -2748,13 +2786,13 @@ Kayen: Added Rule: Spells:CharismaEffectiveness (Default = 10) Deterimes how muc Kayen: Added Rule: Spells:CharmBreakCheckChance (Default = 25) Determines percent chance for a charm break check to occur each buff tick. Kayen: Reworked code to that handles charm breaks/lull to be accurate to live and more functional in general. 1) Each charm buff tick there is a default 25% [Rule.CharmBreakCheckChance] chance to trigger a spell resistance check. - 2) Spell resistance check then adds an additional bonus resist modification based casters Charisma at a default + 2) Spell resistance check then adds an additional bonus resist modification based casters Charisma at a default ratio of 10 CHA per -1 resist mod [Rule.CharismaEffectiveness] 3) If resisted (ie Charm is to break) Total Domination AA is then applied to give another chance to maintain the charm. Kayen: Implemented: SE_AdditionalHeal: Focus Effect that adds an additional heal amount to the casted spell. Kayen: Implemented: SE_CastOnCure: Casts a spell on cured target. Kayen: Implemented: SE_CastOnCurer: Casts a spell on the curer of the target. -Kayen: Modified: Mob::TryFadeEffect to avoid interaction wtih Twinproc effect. +Kayen: Modified: Mob::TryFadeEffect to avoid interaction wtih Twinproc effect. cavedude: (Uleat) Multiple changes and additions to doors, per the forums. Optional SQL: utils/sql/svn/2156_optional_charm_break_rule.sql @@ -2779,8 +2817,8 @@ Kayen: Implemented SE_FF_Damage_Amount - Focus/Buff that adds damage to the cast Kayen: Adjusted SE_SpellDamage to be handled properly as a focus effect. Kayen: Extra damage from Focuses is now calculated correctly for DoTs Kayen: Implemented Perl NPC Quest Object SetSpellFocusDMG(focus amount) - Focus all npc direct/dot damage spells by value -Kayen: Implemented Perl NPC Quest Object SetSpellFocusHeal(focus amount) - Focus all npc healing spells by value -Kayen: Implemented Perl Mob Quest Object ModSkillDmgTaken(skill_num,value) - Set a weakness/bonus of weapon attacks to a Mob +Kayen: Implemented Perl NPC Quest Object SetSpellFocusHeal(focus amount) - Focus all npc healing spells by value +Kayen: Implemented Perl Mob Quest Object ModSkillDmgTaken(skill_num,value) - Set a weakness/bonus of weapon attacks to a Mob - Example: mob can be set to take 5% more damage from blunt weapons - ModSkillDmgTaken(0,5) - Stacks with spell/item bonuses - Setting skill to -1 will effect all skills Kayen: Implemented Perl Mob Quest Object GetModSkillDmgTaken(skill_num) - Returns only the quest skill mod for specified skill @@ -2792,7 +2830,7 @@ Kayen: Implemented Perl Mob Quest Object ModVulnerability(resist type, value) - - Example: mob can be set to take 5% more damage from Fire spells (2,5); - Applied effect will stack with spell buff effects. - If a resist type SPECIFIC and a resist type ALL mod are applied to the same MOB, the SPECIFIC value will be used if spell cast on MOB is the same resist type. -Kayen: Implemented Perl Mob Quest Object GetModVulnerability(resist type) - Return the quest applied value for each resist (ALL = -1) +Kayen: Implemented Perl Mob Quest Object GetModVulnerability(resist type) - Return the quest applied value for each resist (ALL = -1) Optional SQL: utils/sql/svn/2154_optional_rule_spell_procs_resists_falloff.sql @@ -2852,12 +2890,12 @@ Trevius: VoA - The AA Window now populated AAs. AA hotkeys can be created, but sorvani: Bard AE DoTs should now be affected by mods correctly. ==04/19/2012== -cavedude: quest::buryplayercorpse will now despawn corpses in zones other than the client's current location, preventing dupes. +cavedude: quest::buryplayercorpse will now despawn corpses in zones other than the client's current location, preventing dupes. cavedude: Added quest::summonallplayercorpses (same syntax as quest::summonburriedplayercorpse) which will depop and summon all of the player's corpses, buried or not. cavedude: Fixed a bug that caused some summoned corpses to use a static 5 minute decay timer. cavedude: Changed player_corpses_backup schema to match player_corpses. If the rule is enabled, corpse backups will now be created for players that are high enough level to drop items to their corpses. cavedude: Added a rule to determine whether or not to remove player corpse backups older than 2 weeks. -cavedude: #corpse now has options to depop a single player corpse, or every corpse belonging to a single player. +cavedude: #corpse now has options to depop a single player corpse, or every corpse belonging to a single player. cavedude: You can now specify if a faction hit is temporary (is removed when player camps/zones) or whether or not to display the faction hit to the player in-game. If temp in npc_faction_entries is set to: 0 (Default): Faction is permanent, player recieves a message. (Same functionality as we had previously.) @@ -2865,7 +2903,7 @@ If temp in npc_faction_entries is set to: 2: Faction is temporary, player recieves a message. 3: Faction is permanent, but player does not recieve a message. cavedude: Added an optional argument to quest::faction to utilize the functionality and values listed above. -cavedude: Added minlevel and maxlevel to lootdrop_entries. The player credited with the kill (most hate) has their level checked against both columns, and if they are lower than the specified minlevel, or higer than the max, that item entry if it exists is removed from the NPC before it becomes a corpse. +cavedude: Added minlevel and maxlevel to lootdrop_entries. The player credited with the kill (most hate) has their level checked against both columns, and if they are lower than the specified minlevel, or higer than the max, that item entry if it exists is removed from the NPC before it becomes a corpse. cavedude: You can now specify if a NPC automatically repops (rerolls against their spawngroup) or depops after the reverse spawn timer is up. If despawn in spawngroup is set to: 0 (Default): Do not depop or repop, no depop timer is set. (Same functionality as we had previously.) @@ -2958,10 +2996,10 @@ JJ: Removed additional library and include directories from windows projects as Sorvani: Resurrections effects will again be applied to characters who are in a zone where combat is not allowed (GL, PoK etc) when they receive a rez. ==03/19/2012== -Bad_Captain: Bots: A few fixed for #bot stance command +Bad_Captain: Bots: A few fixed for #bot stance command ==03/19/2012== -Bad_Captain: Bots: Implemented bot stances. See forum post. +Bad_Captain: Bots: Implemented bot stances. See forum post. Required SQL: utils/sql/svn/2107_required_bot_stances.sql @@ -3045,11 +3083,11 @@ Reccomended SQL: (ON QueryServ's database, NOT the main db) utils/sql/queryserve Secrets: v90 toolset bugs reported on forums. In order to use Perl 5.14, you MUST upgrade to VS2010 (preferrably ultimate), if you do not have vs2010, it will default to Perl 5.10 and you will be unable to use the much more stable Perl 5.14, an upgrade is reccomended. Secrets: Fixed Windows x64 rulesys bug where it would store them as an x64 version of an int (long long) and mess up the ordering of pointers, resulting in certain rules not loading properly. NOTE: For those having issues compiling vs2010, there will be a guide up shortly to help you set up your dev environment. In the meantime, make sure your "Additional Include" and "Additional Library" paths -are correct and pointing at the right location. Failure to do so will result in mysql header/lib and perl header/lib issues. If you are looking for zlib x64, it's in SVN. +are correct and pointing at the right location. Failure to do so will result in mysql header/lib and perl header/lib issues. If you are looking for zlib x64, it's in SVN. ==11/30/2011== Secrets: Implemented Visual Studio 11 and Visual Studio 10 project/sln files. -Secrets: Added x64 configuration settings to VS11 and VS10 project files/SLN files. +Secrets: Added x64 configuration settings to VS11 and VS10 project files/SLN files. Secrets: Changed WIN32 define to _WINDOWS across the board. In the case where WIN32 is needed over WIN64 (ie; assembly references) there is still WIN32 defines and a new WIN64 define. Secrets: Upgraded ActiveState Perl lib define to 5.14 -- others still work but it is strongly reccomended to use 5.14 as it contains less memory leaks. Secrets: Added Zlib 1.2.3 x64 to the SVN for use with the windows solution files. @@ -3084,7 +3122,7 @@ Lerxst: Fixed crash when calculating random focus effect from an augment when th Also added some bulletproofing in case that isn't the only place an invalid spell id might be passed. ==11/16/2011== -Akkadius: Added the ability to specify doors/objects/ground spawns to load for all versions of the same zone regardless by setting the value to -1. +Akkadius: Added the ability to specify doors/objects/ground spawns to load for all versions of the same zone regardless by setting the value to -1. This reduces serious redundancy of copying the same data over and over for instances. Required SQL: @@ -3292,8 +3330,8 @@ Congdar: update check for Lore ==07/17/2011== Congdar: (pfyon, Criimson)Various bot tweaks Caryatis: Updated My/Showstats window. - -Required SQL: + +Required SQL: utils/sql/svn/1974_required_bot_spells_update ==07/16/2011== @@ -3435,9 +3473,9 @@ JJ: Implemented New Tanaan Crafting Mastery tradeskill AA. Each rank allows an a Note: For servers with players who have tradeskills already above the limit without previously purchasing these AAs will freeze the chance to increase until they purchase the proper amount of NTCM AAs. ==05/24/2011== -KLS: Changed Mob::NPCSpecialAttacks(atk, perm) to Mob::NPCSpecialAttacks(atk, perm, [reset = 1], [remove = 0]). -This should allow one to add and remove flags individually without having to reset everything each time. -ex: +KLS: Changed Mob::NPCSpecialAttacks(atk, perm) to Mob::NPCSpecialAttacks(atk, perm, [reset = 1], [remove = 0]). +This should allow one to add and remove flags individually without having to reset everything each time. +ex: $npc->NPCSpecialAttacks(RQ, 0); //would enable the npc to rampage and quad. $npc->NPCSpecialAttacks(S, 0, 0); //Would enable the NPC to summon as well as rampage and quad by telling it to set S but don't reset the earlier flags. @@ -3449,11 +3487,11 @@ $npc->NPCSpecialAttacks(S, 0, 0, 1); //Would enable the NPC rampage and quad by JJ: (Akkadius) Fixed camera shake usage output. ==05/22/2011== -KLS and Co: +KLS and Co: -All liquid should count for skill ups. --Finished unified quest interface... this is fairly large and will probably have a few problems here and there; report them to me and ill fix them asap. -The goal behind the system is to allow more than one scripting system to work at a time (though with limited interaction due to pre-existing implementation limitation). -This was a feature requested by the Dalaya community as they plan to merge back to the eqemu codebase and clients but are stuck with an old parser and thousands of files +-Finished unified quest interface... this is fairly large and will probably have a few problems here and there; report them to me and ill fix them asap. +The goal behind the system is to allow more than one scripting system to work at a time (though with limited interaction due to pre-existing implementation limitation). +This was a feature requested by the Dalaya community as they plan to merge back to the eqemu codebase and clients but are stuck with an old parser and thousands of files that can't be realistically rewritten in a short time frame. ==05/20/2011== @@ -3482,7 +3520,7 @@ Optional SQL: utils/sql/svn/1889_optional_skill_cap_rule.sql ==05/02/2011== Secrets: Added OP_CameraEffect for Titanium. -Secrets: Added commands: #reloadallrules, #reloadrulesworld, and #camerashake. These default to +Secrets: Added commands: #reloadallrules, #reloadrulesworld, and #camerashake. These default to Secrets: Added optional "global" flag as item 5 in the $mob->CameraEffect() quest object. This does #camerashake, but in quest form. Secrets: #reloadallrules reloads rules in every single zone plus world. #reloadrulesworld reloads the rules in world only. Secrets: #camerashake shakes the camera in every zone with required args intensity and duration. @@ -3802,7 +3840,7 @@ utils/sql/svn/1747_optional_HoT_zone_and_zonepoints.sql For spawns etc, go to the PEQ logs repo for data collected by robregen. -Optional SQL: +Optional SQL: utils/sql/svn/1750_optional_sql_reflect_rule.sql @@ -3815,7 +3853,7 @@ Caryatis: (bad_captain) Removed the level bonus granted to standing mana regen, Caryatis: (bad_captain) Bots have been updated, see forums for complete details(new command: #bot showstats). Caryatis: (Secrets) Haste values can exceed 127%(ie Can o' Whoop Ass). -Optional SQL: +Optional SQL: utils/sql/svn/1746_optional_sql_bot_manaregen ==11/22/2010== @@ -3832,10 +3870,10 @@ Caryatis: Fix for Healrate effect Caryatis: Implemented ManaAbsorbPercentDamage, ReduceSkillTimer, HpToMana and LimitSpellGroup effects. Caryatis: Updated DamageModifier effect to be more robust. -Required SQL: +Required SQL: utils/sql/svn/1737_required_sql_rule_and_aa_update -Optional SQL: +Optional SQL: utils/sql/svn/1736_optional_sql_feral_swipe ==11/14/2010== @@ -3884,7 +3922,7 @@ Caryatis: hStr = Increases endurance pool, endurance regen(25), and the maximum Caryatis: hSta = Increases hit point pool, hit point regen(25), and the maximum amount of hit point regen a character can have(25). Also increases endurance pool, endurance regen(25), and the maximum amount of endurance regen a character can have(25). Caryatis: hAgi = Increases endurance pool, endurance regen(25), and the maximum amount of endurance regen a character can have(25). Also increases the chance to dodge an attack(25), grants a bonus to defense skill(10). Caryatis: hDex = Increases endurance pool, endurance regen(25), and the maximum amount of endurance regen a character can have(25). Also increases damage done by ranged attacks(1), improves chance to successfully assassinate or headshot(10), and improves the chance to riposte, block, and parry incoming attacks(25). -Caryatis: hCha = Improves reaction rolls with some NPCs(25) and increases the amount of faction you gain or lose when faction is adjusted(5). +Caryatis: hCha = Improves reaction rolls with some NPCs(25) and increases the amount of faction you gain or lose when faction is adjusted(5). Caryatis: AA Focus revamped to support new effects Caryatis: Disciplines will no longer be dispelled @@ -3894,7 +3932,7 @@ utils/sql/svn/1719_optional_triggerOnCastAAs.sql ==11/09/2010== Caryatis: Implemented MaxHPChange, SkillDmgTaken, Endurance Pool and Stun Resist. -Optional SQL: +Optional SQL: utils/sql/svn/1717_optional_rule_bash_stun_chance.sql ==11/07/2010== @@ -4073,9 +4111,9 @@ Trevius: (Secrets) Added quest::creategroundobjectfrommodel(modelname, x, y, z, Trevius: (Secrets) quest::creategroundobject(itemid, x, y, z, heading) now returns object ID. Trevius: (Secrets) Added Object List Iteration functions GetObjectByDBID(id), GetObjectByID(id), GetObjectList(). Trevius: (Secrets) exported Objects to Perl with the following commands: - IsGroundSpawn(), Close(), Delete(reset_state=false), StartDecay(), DeleteItem(index), IsObject(), - Save(), SetID(set_id), ClearUser(), DBID(), GetID(), GetX(), GetY(), GetZ(), GetHeading(), VarSave(), - GetType(), SetType(type), GetIcon(), SetIcon(icon), GetItemID(), SetItemID(itemid), SetLocation(x, y, z), + IsGroundSpawn(), Close(), Delete(reset_state=false), StartDecay(), DeleteItem(index), IsObject(), + Save(), SetID(set_id), ClearUser(), DBID(), GetID(), GetX(), GetY(), GetZ(), GetHeading(), VarSave(), + GetType(), SetType(type), GetIcon(), SetIcon(icon), GetItemID(), SetItemID(itemid), SetLocation(x, y, z), SetX(XPos), SetY(YPos), SetZ(ZPos), SetHeading(heading), SetModelName(name), GetModelName(), Repop(), Depop(). ==07/25/2010== @@ -4396,7 +4434,7 @@ KLS: Other merges from spell branch, some more to come. Tell me if there are an ==04/15/2010== cavedude (Leere): Increased the number of tradeskill favorites that can be stored client side to 500. -cavedude (Leere): Fixed avgcoin. +cavedude (Leere): Fixed avgcoin. cavedude (renoofturks): Fixes to SK harm touch. cavedude: Lowered snare movement speed when fleeing to 41% or higher to allow "Snare" to prevent movement at all levels. gaeorn: multiple login server support in world. protocol to update login server account information from within game. "trusted" field for world accounts in loginserver to limit where account updates can come from. in eqemu_config.xml, use to specify first of multiple login servers. increment the number to specify additional login servers. NOTE: be sure to replace the entry or you will be limited to just one login server. MULTIPLE LOGIN SERVERS WILL NOT WORK WITH MINILOGIN. @@ -5181,7 +5219,7 @@ Derision: Fix for mobs in pathing enabled zones fleeing at low health at full ru Derision: Made rule Map:UseClosestZ default to false. Only set true if using azone2 generated maps (specifically those for EQG zones). realityincarnate: Starting cities are now tracked for newly created characters. realityincarnate: Enabled the /setstartcity command for characters without a start city assigned. -realityincarnate: Added $client->SetStartZone and $client->GetStartZone perl commands. +realityincarnate: Added $client->SetStartZone and $client->GetStartZone perl commands. KLS: Petitions will assign id based on character id instead of this stupid desyncing junk. KLS: Max NPC name length increased to 50 characters up from 30. KLS: Save on zone success will now commit immediately instead of possibly delaying. Hopefully will help with some of the situations where people zone to bad locations. @@ -5244,7 +5282,7 @@ KLS: Implemented OP_ClearSurname KLS: (gaeorn) Several 64-bit compile and runtime fixes KLS: (gaeorn) NPC wander back rules. Trevius: #texture will now allow player races to retain armor tint for both PCs and NPCs. -realityincarnate: bug fix for changing max_hp with the modifynpcstat command +realityincarnate: bug fix for changing max_hp with the modifynpcstat command Required SQL: /utils/sql/svn/704_rules.sql @@ -5328,7 +5366,7 @@ Warning: If you do not have a spells_new table yet, you must create one and load After creating the table, you can import or export your spells from the spells_us.txt file by using the scripts: import_spells.pl export_spells.pl - + The easiest way to import your spell file to the table is to move the import_spells.pl file to your main server directory where your spells_us.txt file and your eqemu_config.xml file are. Then run the following command at a command prompt: perl import_spells.pl @@ -5393,7 +5431,7 @@ KLS: Added support for item clicklevel and item clicklevel2 Trevius: Increased spellbook from 400 to 480 in the Player Profile for use with SoF (60 page spell book) Trevius: SoF - Added new functions to SoF.cpp for converting Slot IDs between Titanium and SoF Trevius: SoF - Corrected the location of the spellbook field in the Player Profile to fix an issue with loading spells -WildcardX: Check in of the start of what will become the new BOTS subsystem/framework. This is far from done so just continue to use the existing EQBOTS code you have been using. +WildcardX: Check in of the start of what will become the new BOTS subsystem/framework. This is far from done so just continue to use the existing EQBOTS code you have been using. KLS: Change to dangerous item inst aug creation, hopefully addresses segfault on 64 bit linux. KLS: Hopefully fix for pets not giving adventure credit to players in ldon instances. @@ -5539,7 +5577,7 @@ KLS: Added Rules: (Character, UseXPConScaling), (Character, LightBlueModifier), Derision: Bandolier bug fix. Derision: Equipped items that should confer an extra potion belt slot now do so in Titanium and the 6.2 client (was already working in SoF). Angelox: Bots- (Congdar) fixed Bot illusion / change form spells as to who is affected. -Angelox: Bots- added check to Bot pacify for casting from a distance. +Angelox: Bots- added check to Bot pacify for casting from a distance. ==05/18/2009== @@ -5620,7 +5658,7 @@ Derision: Fixed a bug where a mob aggroed would sometimes appear to run past it' Trevius: (Erde) The Web Tool now shows the name of the process running each Dynamic zone (example: dynamic_01) ==05/06/2009== -Angelox: Bots: Added command '#bot shrinkme' requires Shaman or Beastlord (defaults to Shaman). +Angelox: Bots: Added command '#bot shrinkme' requires Shaman or Beastlord (defaults to Shaman). Derision: Added redux_aa2, redux_rate2 fields to aa_actions. Derision: Improved Hasty Exit should now reduce the reuse time of Escape. Derision: The reuse timer in the AA window now shows the reuse time reduced by applicable AAs. @@ -5681,7 +5719,7 @@ Cripp: Fix for the web interface for those using Perl 5.10. Congdar: Bots - randomized face/hair etc. so they don't all look the same. Fixed Bard AE songs. ==4/30/2009== -Congdar: Bots - bots can now use bows, new command '#bot archery'. Added Ranger archery AA's. Reduced chat mana spam. Tweaked spell ai. Fixed memory leak. Updated '#bot corpse summon'. +Congdar: Bots - bots can now use bows, new command '#bot archery'. Added Ranger archery AA's. Reduced chat mana spam. Tweaked spell ai. Fixed memory leak. Updated '#bot corpse summon'. Derision: SoF - AAs affecting stats now show the correct stats in the client. Derision: Tweaked base resists to match the client. gatorman: Fix for QuickSummoning AA (to include Call of the Hero) @@ -5700,7 +5738,7 @@ Derision: SoF: Ranged attack animations. Derision: When shooting a bow, there is no longer a superfluous 1HS animation. ==4/23/2009== -Trevius: (realityincarnate) Added new quest command quest::varlink(item_id) for putting item links into variables. +Trevius: (realityincarnate) Added new quest command quest::varlink(item_id) for putting item links into variables. Derision: Fix for Tradeskill combines where a LORE ingredient is returned. Derision: Fix for pet names containing spaces losing the space after zoning/camping. Derision: Fixed bug where Return Home sent players bound in Grobb to Qeynos/Unknown Zone. @@ -5719,7 +5757,7 @@ Wolftousen: Rage Volley no longer requires you to have an thrown weapon in your Wolftousen: Rage Volley now uses the proper damage calculation and is not based on the item you have in the ranged slot Wolftousen: Rave Volley can no longer be dodged/blocked/parried/reposted. Wolftousen: Procs from Buffs have been tweaked to go off more often Wolftousen: Players will now receive the "proper" bonus HP for stamina above 255. -Wolftousen: Knight class Tactical Mastery AA was implemented and should now give the strike through message +Wolftousen: Knight class Tactical Mastery AA was implemented and should now give the strike through message renoofturks: Created rule Aggro:StunAggroMod to dial in on aggro of stun based attacks. cavedude: Reverse DS and some DS will now cause aggro on intial cast. realityincarnate: Controllable boats should now work. Please see: http://eqemulator.net/forums/showthread.php?p=167892#post167892 for additonal information. @@ -5895,7 +5933,7 @@ cavedude: Corrected ZEM for AAs. cavedude: (Thanks to demonstar55) Pet Affinity will no longer effect charmed pets. cavedude: (realityincarnate) Bard songs that require instruments will now require them. -Please note: XP gain has pretty much been overhauled. You may need tweak the multiplier rules for your server. +Please note: XP gain has pretty much been overhauled. You may need tweak the multiplier rules for your server. Optional SQL: @@ -6009,7 +6047,7 @@ cybernine186: Optional system to ensure GMs are logging on from a known IP. Trevious: SoF - Drakkin now gain stats from items and weapons now work for combat Trevious: SoF - Drakkin now start with Common Tongue, Dragon and Elder Dragon Languages maxed -Required SQL: utils/sql/svn/340_gm_ips.sql +Required SQL: utils/sql/svn/340_gm_ips.sql ==02/16/2009== Trevius: SoF - The Item Struct should be aligned almost perfectly now. @@ -6120,9 +6158,9 @@ Note: You must move the patch_SoF.conf file from /utils into your server directo ==01/31/2009== Derision: Tweaks to temp merchant list window updates. -cavedude00: Renamed the AugSlotUnk items columns to AugSlotVisible. +cavedude00: Renamed the AugSlotUnk items columns to AugSlotVisible. -Required SQL: utils/sql/svn/292_augslots.sql +Required SQL: utils/sql/svn/292_augslots.sql ==01/29/2009== KLS: VC71 solution files. @@ -6132,7 +6170,7 @@ Angelox: Bots: Added a directory with the BOT Makefiles for Windows and Linux. ==01/27/2009== Derision: Bazaar bug fix. -Angelox: Bots: Added command '#bot runeme' (Enchanter Rune spells) +Angelox: Bots: Added command '#bot runeme' (Enchanter Rune spells) Optional SQL: utils/sql/svn/285_optional_bot_spell_update.sql (removes auto-runes) @@ -6144,7 +6182,7 @@ Derision: Fixed a buffer overflow problem. ==01/19/2009== cavedude00: Increased itemid limit to 120,000. -cavedude00: Meditate will now skill up at a more Live Like speed. +cavedude00: Meditate will now skill up at a more Live Like speed. ==01/18/2009== Derision: Fixed a cause of zone crashes. @@ -6172,7 +6210,7 @@ Derision: Mapped field in LogServer_Struct to allow voice macro window to be ope Derision: Removed code that stole all your money if you logged on with more than 1 million of any denomination of coin on your person. ==01/11/2009== -Angelox: Bots: Added rule 'EQOffline:BotCount' defaults to 5, for desired amount of bots in the group - values are 0-5 (0 means bots are disabled, max limit is 5). +Angelox: Bots: Added rule 'EQOffline:BotCount' defaults to 5, for desired amount of bots in the group - values are 0-5 (0 means bots are disabled, max limit is 5). Derision: Mail/Chatchannels: Added sanity check on packet size in EQPacket::ChatDecode. Derision: Mail/Chatchannels: Increased stream timeout from 45 to 135 seconds. Derision: Mail: Use correct opcodes for sending Headers. **utils/mail_opcodes.conf updated** @@ -6192,7 +6230,7 @@ Angelox: Bots: '#bot sow wolf' should not affect pets anymore. Derision: Chatserver: Added some extra syntax error checking to prevent crashes. Angelox: Bots: Added Wizard class and level check to the '#bot evac' command Angelox: Bots: Added '#bot invis see' for see invisible -Required SQL: +Required SQL: DELETE FROM npc_spells_entries where (npc_spells_id >=701 and npc_spells_id <=712) AND spellid=80; Congdar: Fix NPC::RemoveItem(uint16 item_id) to uint32 to work with items that have id's larger than 65535. '#npcloot remove [itemid]' will now successfully remove items with larger id's from npc loot. Congdar: Bots: Visible gear will now show correctly when trading/equipping bots. @@ -6212,7 +6250,7 @@ Congdar: Bots: update command #bot inventory list Now shows gear as item links Optional SQL: update npc_types set lastname='' where isbot=1; ==01/01/2009== -Angelox: Bots: Added command '#bot sow' (Druid has options) +Angelox: Bots: Added command '#bot sow' (Druid has options) Angelox: Bots: Added command '#bot levitate'. Angelox: Bots: Added command '#bot invis' - (has options). @@ -6525,8 +6563,8 @@ Congdar: Clone NoDrop removal code to NoRent, Lore, NoTrade. Optionally enabled Congdar: Bot code cleanup, method call reduction Congdar: Bot DoubleAttack method is more like clients Congdar: Remove AFK leveling with bots -Angelox: Added command '#Bot evac' for Druid bots -Angelox: Added command '#Bot target calm' for Enchanter or Cleric bots +Angelox: Added command '#Bot evac' for Druid bots +Angelox: Added command '#Bot target calm' for Enchanter or Cleric bots Trevius: Removed the * 10 multiplier from the SE_ProcChance since it isn't needed KLS: Zone appearance should update for players zoning. KLS: Added command #modifynpcstat @@ -6576,7 +6614,7 @@ AndMetal: (seveianrex) Hate w[h]iped on CoH AndMetal: (via Yeahlight) New command: #aggrozone. Requires 100 status by default ==10/19/2008 -Angelox: Added a start to Bot tracking - Thanks Derision for all the help and know-how. +Angelox: Added a start to Bot tracking - Thanks Derision for all the help and know-how. Derision: Altered damage shield message processing to be more like (the same?) as live. Derision: If it exists, damage shield types are read from a new table (damageshieldtypes) Derision: If no entry exists in the table, a default based on resist type is used. @@ -6594,10 +6632,10 @@ KLS: Disarm trap will no longer fail to disarm traps on success. KLS: Increased range on disarm trap slightly. KLS: Change to MakeRandomInt() and MakeRandomFloat() KLS: Int generation be nearly 100% or more faster in most cases and float generation should be slightly faster in most cases -cavedude00: (seveianrex) Slay Undead Fix -cavedude00: (seveianrex) Levitate effect will no longer be removed in cases where you have two stacked lev spells and the first wears off. +cavedude00: (seveianrex) Slay Undead Fix +cavedude00: (seveianrex) Levitate effect will no longer be removed in cases where you have two stacked lev spells and the first wears off. cavedude00: (seveianrex) Hate list will now be cleared following CoH -cavedude00: (seveianrex) Group members will now see tradeskill emotes. +cavedude00: (seveianrex) Group members will now see tradeskill emotes. Required SQL: ALTER TABLE `traps` DROP `spawnchance`; @@ -6704,7 +6742,7 @@ Derision: (Spoon/Andmetal) Rez spells with effectdescnum != 82 or 39067 (6.2 spe Derision: Corrected message when interrupting a spell with Shift-S. Derision: Fix to display Frogloks correctly in /who all and /who all froglok (half of the change came from a post by Theeper on the forums). -Required SQL: +Required SQL: ALTER TABLE `tasks` ADD `minlevel` TINYINT UNSIGNED NOT NULL DEFAULT '0', ADD `maxlevel` TINYINT UNSIGNED NOT NULL DEFAULT '0'; @@ -6760,7 +6798,7 @@ cavedude00: (AndMetal) Slippery Attacks AA cavedude00: (trevius/Derision) Additional IP limiting rules cavedude00: (Congdar) Further PC main and second hand attack fixes cavedude00: (Derision) Fix for percent heals -cavedude00: (Theeper) Fix for quest:itemlink() +cavedude00: (Theeper) Fix for quest:itemlink() cavedude00: (Rocker8956) More work on zone instancing Optional SQL: @@ -6917,7 +6955,7 @@ KLS: (irv0) Fix for out of order ack not being sent in some situations. KLS: (Derision) Pet bar OOC update fix. KLS: Should have made client pets unable to give experience, untested but should work. KLS: Healing aggro should function better for people not on the hate list. -KLS: Some work on public tradeskill objects saving their content between uses. +KLS: Some work on public tradeskill objects saving their content between uses. ==06/22/2008 KLS: Changed world/clientlist.cpp's line endings back to unix style line endings @@ -6992,7 +7030,7 @@ CREATE TABLE `hackers` ( `zone` text, `date` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, PRIMARY KEY (`id`) -) AUTO_INCREMENT=8; +) AUTO_INCREMENT=8; ==04/26/2008 KLS: Should have fixed discipline and combat ability timer overlap. @@ -7018,7 +7056,7 @@ Rogean: Fixed #hideme, it won't show you zone in and then disappear anymore. (Re Rogean: Fixed /summon. Rogean: Changes to /who all and GM's: * GM * Tags will no longer show up unless your #gm on - You will not show up to players of lower status if your /anon and #gm on, if your #gm off and /anon you will show up as + You will not show up to players of lower status if your /anon and #gm on, if your #gm off and /anon you will show up as a normal player, regardless of statuses. ==04/09/2008 @@ -7125,7 +7163,7 @@ KLS: Ironed out some quirks between min hit and the AC code that was keeping it KLS: (Knightly)#time should no longer be off by an hour, $zonehour, $zonemin and $zonetime are now exported to perl KLS: Added player quests, player quests are quests designed to be attached to player processes such as zoning and clicking objects. KLS: Player quests are loaded as either 'quests/zonename/player.pl' or as a template in 'quests/template/player.pl' -KLS: Player quests will export qglobals the client should be able to see and the following events are available for player quests: +KLS: Player quests will export qglobals the client should be able to see and the following events are available for player quests: EVENT_TIMER with var $timer EVENT_CLICKDOOR with var $doorid EVENT_LOOT with vars $looted_id and $looted_charges @@ -7253,7 +7291,7 @@ NPC:OOCRegen (Default: 0) KLS: Fixed logic on duration 7 formula.. again. ==12/02/2007 -KLS: Reworked buff duration formula 7 calculations. +KLS: Reworked buff duration formula 7 calculations. KLS: (Cripp)Added (Missing?) pathing z rules. ==11/30/2007 @@ -7367,7 +7405,7 @@ KLS: NPCs should now accept signals while engaged in combat. FatherNitwit: Quick fix for Divine Aura on pets exploit (KingMort) ==10/09/2007 -KLS: Tweaked my AC system a bit, should get less default defense and the system should be much more lienent overall, +KLS: Tweaked my AC system a bit, should get less default defense and the system should be much more lienent overall, goal is to allow people to see more of a benefit from having AC. ==09/24/2007 @@ -7379,7 +7417,7 @@ KLS: Small changes to the order in the getweapondamage code to make it a little KLS: Added a field that allows you to override a factions innate desire to assist their own faction in battle to the npc_faction table Required SQL: -alter table npc_faction add column ignore_primary_assist tinyint(3) not null default 0; +alter table npc_faction add column ignore_primary_assist tinyint(3) not null default 0; ==09/02/2007 KLS: Fixed an issue with items adding stats to NPCs exponentially. @@ -7398,7 +7436,7 @@ KLS: The error in acmod() should no longer complain as much. Required: Opcode files have changed, be sure to update to the latest .conf files. ==08/30/2007 -KLS: Reworked Mob::GetWeaponDamage(), it will now return damage done by any item and NULL and will return a +KLS: Reworked Mob::GetWeaponDamage(), it will now return damage done by any item and NULL and will return a value zero or less if we can't hit a mob with said item KLS: Applied new GetWeaponDamage() to Attack and Special Attack code KLS: Seperated Finishing Blow code to it's own function @@ -7568,8 +7606,8 @@ WildcardX: Area of effect beneficial spells will no longer affect non-player cha ==2/16/2007 KLS: Updating Changelog AA work to be more clear as well as the required SQL. -Required SQL: -alter table altadv_vars add column cost_inc tinyint(4) not null default 0; +Required SQL: +alter table altadv_vars add column cost_inc tinyint(4) not null default 0; Optionally: Source in AA_data.sql @@ -7784,7 +7822,7 @@ CREATE TABLE skill_caps ( ); ==11/07/2006 -KLS: Changed how critical hits work, as a result things that could not crit before like special attacks, now can. +KLS: Changed how critical hits work, as a result things that could not crit before like special attacks, now can. KLS: Cleave like effects should be fixed to do an increase of your already standing chance to critical instead of a flat increase. KLS: Reworked much of the special attack code. KLS: Applied haste to combat skill timers, they should give trouble much less often. @@ -7830,7 +7868,7 @@ FatherNitwit: Tweak for 64 bit pointer support in the item code of the struct st FatherNitwit: More const cleanup in npc.h ==10/22/2006 -FatherNitwit: added EVENT_COMBAT triggered when an NPC engages any form of +FatherNitwit: added EVENT_COMBAT triggered when an NPC engages any form of FatherNitwit: combat and when combat is over. $combat_state signals which. FatherNitwit: Hopefully fixed d_meele_texture FatherNitwit: Cleaned up a lot of const related issues (attrs, AAs, and more) @@ -8139,7 +8177,7 @@ FatherNitwit: Fixed countless rediculous things about guilds. FatherNitwit: Any change in guild state should update in real time now. FatherNitwit: Guild leadership may need re-establishing since it changed from account to char based. FatherNitwit: Guild structure in database has changed significantly. -Note: custom rank stuff is too much work for me to preserve in the DB, since I doubt +Note: custom rank stuff is too much work for me to preserve in the DB, since I doubt anybody uses it, so if you have them, you can figure it out yourself or remake them. Required SQL: ALTER TABLE character_ ADD class TINYINT NOT NULL DEFAULT 0; @@ -8482,7 +8520,7 @@ ALTER TABLE npc_types ADD AGI MEDIUMINT UNSIGNED NOT NULL DEFAULT '75'; ALTER TABLE npc_types ADD _INT MEDIUMINT UNSIGNED NOT NULL DEFAULT '75'; ALTER TABLE npc_types ADD WIS MEDIUMINT UNSIGNED NOT NULL DEFAULT '75'; ALTER TABLE npc_types ADD CHA MEDIUMINT UNSIGNED NOT NULL DEFAULT '75'; - + ==09/23/2005 FatherNitwit: Inverted XY coordinates on in zone objects and ground spawns. FatherNitwit: Maybe fixed quests setting a waypoint in EVENT_WAYPOINT @@ -8646,7 +8684,7 @@ Doodman: New item structure is in: Item fields now match the dump from (utils/load_13thfloor_items.pl) eqitems.13th-floor.org Item table convert script: utils/items-0.6.0-DR2-0.6.1-DR1-convert.sql - + ==01/10/2005 FatherNitwit: Disable sleep() in perl, it is bad news. FatherNitwit: Fixed guild MOTD at login (hopefully). @@ -8916,7 +8954,7 @@ WR Merges: - pets should actually taunt now. - rouge pets should backstab now. - optional: group buffs hit group pets now. -- fixed memory blur chances +- fixed memory blur chances - several minor group tweaks, should make groups more stable - improved duel messages - optional random luclin attributes for NPCs with boring faces @@ -9308,7 +9346,7 @@ kathgar: Fixed, SE_Fear, SE_SpinStun, SE_Charm for fixed duration spells and no Added member bool client to Buff_Struct to aid in above fix Fixed solar's changelog entry where he spelled my name wrong No EQLive fixes in this change -solar: fixed a bug that was causing people to be set as gm when they shouldn't +solar: fixed a bug that was causing people to be set as gm when they shouldn't ==2/15/2004 solar: characters being created are checked for validity now. thanks to @@ -9424,7 +9462,7 @@ solar: Buff fading should work for slots besides the first one now. This will solar: sense heading skill will now improve as you move around ==1/19/2004= -Scorpious2k: fixed faction command for quests +Scorpious2k: fixed faction command for quests LethalEncounter: Fixed a problem with queued cursor items. solar: fixes to #gassign @@ -9479,24 +9517,24 @@ LethalEncounter: Fixed loot messages. LethalEncounter: Fixed loot so right click autoequips correctly now. ==1/4/04== -MYRA: changed to new opcode for follow -MYRA: changed to new opcode for taunt -MYRA: use new (5.x) Status labels in who for telnet connection -MYRA: Added code to depop at end of grid for wander type 4 -MYRA: Added wander type 4 (single run) -MYRA: fixed eval in ExportVar per Eglin -MYRA: corrected spelling for var $uguildrank for event_timer (was $uguildrang) -MYRA: added vars $status & $cumflag per Eglin -MYRA: added vars $mobid & $mlevel per Eglin -MYRA: added missing commands + itemlink to perl -MYRA: added EVAL & KEEPERR to eval per Eglin's recommendation -MYRA: restore missing commands for qst type files & add itemlink -MYRA: fixed comma bug for me command -MYRA: fixed comma bug for echo command -MYRA: fixed comma bug for say command -MYRA: fixed comma bug for emote command -MYRA: fixed comma bug for shout command -MYRA: added itemlink(ItemNumber) command +MYRA: changed to new opcode for follow +MYRA: changed to new opcode for taunt +MYRA: use new (5.x) Status labels in who for telnet connection +MYRA: Added code to depop at end of grid for wander type 4 +MYRA: Added wander type 4 (single run) +MYRA: fixed eval in ExportVar per Eglin +MYRA: corrected spelling for var $uguildrank for event_timer (was $uguildrang) +MYRA: added vars $status & $cumflag per Eglin +MYRA: added vars $mobid & $mlevel per Eglin +MYRA: added missing commands + itemlink to perl +MYRA: added EVAL & KEEPERR to eval per Eglin's recommendation +MYRA: restore missing commands for qst type files & add itemlink +MYRA: fixed comma bug for me command +MYRA: fixed comma bug for echo command +MYRA: fixed comma bug for say command +MYRA: fixed comma bug for emote command +MYRA: fixed comma bug for shout command +MYRA: added itemlink(ItemNumber) command ==1/2/04== @@ -9645,7 +9683,7 @@ solar: HP wasn't being updated to client properly and would fall out of sync LethalEncounter: Fixed a bug in doors that would cause triggered doors to go into an endless loop and crash. ==11/13/03== -kathgar: Fixed a crash when calling ZSList::FindByZoneID() when sending an invalid zone number. +kathgar: Fixed a crash when calling ZSList::FindByZoneID() when sending an invalid zone number. Stack was corrupt in the backtrace, so I am not sure what called it in this way. LethalEncounter: Fixed animation bug with attack. LethalEncounter: Fixed Tradeskills (again) @@ -9679,7 +9717,7 @@ IsEngaged(), so it always returned false, now it uses AutoAttackEnabled() in its Image: Reversed the AICheckCloseSpells if statements, now sanity checks before distance check, uses less CPU usage. ==11/05/03== -LethalEncounter: Updated all of the opcodes that were changed in the patch today. +LethalEncounter: Updated all of the opcodes that were changed in the patch today. LethalEncounter: Refined AA's some, added table to hold the timers for AA's so users can exploit them. Look in db.sql for the table. diff --git a/client_files/export/main.cpp b/client_files/export/main.cpp index 865fbd6d8..708d48456 100644 --- a/client_files/export/main.cpp +++ b/client_files/export/main.cpp @@ -32,6 +32,7 @@ EQEmuLogSys Log; void ExportSpells(SharedDatabase *db); void ExportSkillCaps(SharedDatabase *db); void ExportBaseData(SharedDatabase *db); +void ExportDBStrings(SharedDatabase *db); int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformClientExport); @@ -62,6 +63,7 @@ int main(int argc, char **argv) { ExportSpells(&database); ExportSkillCaps(&database); ExportBaseData(&database); + ExportDBStrings(&database); Log.CloseFileLogs(); @@ -194,7 +196,38 @@ void ExportBaseData(SharedDatabase *db) { fprintf(f, "%s\n", line.c_str()); } - } else { + } + + fclose(f); +} + +void ExportDBStrings(SharedDatabase *db) { + Log.Out(Logs::General, Logs::Status, "Exporting DB Strings..."); + + FILE *f = fopen("export/dbstr_us.txt", "w"); + if(!f) { + Log.Out(Logs::General, Logs::Error, "Unable to open export/dbstr_us.txt to write, skipping."); + return; + } + + fprintf(f, "Major^Minor^String(New)\n"); + const std::string query = "SELECT * FROM db_str ORDER BY id, type"; + auto results = db->QueryDatabase(query); + if(results.Success()) { + for(auto row = results.begin(); row != results.end(); ++row) { + std::string line; + unsigned int fields = results.ColumnCount(); + for(unsigned int rowIndex = 0; rowIndex < fields; ++rowIndex) { + if(rowIndex != 0) + line.push_back('^'); + + if(row[rowIndex] != nullptr) { + line += row[rowIndex]; + } + } + + fprintf(f, "%s\n", line.c_str()); + } } fclose(f); diff --git a/client_files/import/main.cpp b/client_files/import/main.cpp index 3a2f4154f..72ae6fd5d 100644 --- a/client_files/import/main.cpp +++ b/client_files/import/main.cpp @@ -30,6 +30,7 @@ EQEmuLogSys Log; void ImportSpells(SharedDatabase *db); void ImportSkillCaps(SharedDatabase *db); void ImportBaseData(SharedDatabase *db); +void ImportDBStrings(SharedDatabase *db); int main(int argc, char **argv) { RegisterExecutablePlatform(ExePlatformClientImport); @@ -59,6 +60,7 @@ int main(int argc, char **argv) { ImportSpells(&database); ImportSkillCaps(&database); ImportBaseData(&database); + ImportDBStrings(&database); Log.CloseFileLogs(); @@ -202,7 +204,6 @@ void ImportSkillCaps(SharedDatabase *db) { continue; } - int class_id, skill_id, level, cap; class_id = atoi(split[0].c_str()); skill_id = atoi(split[1].c_str()); @@ -262,3 +263,56 @@ void ImportBaseData(SharedDatabase *db) { fclose(f); } + +void ImportDBStrings(SharedDatabase *db) { + Log.Out(Logs::General, Logs::Status, "Importing DB Strings..."); + + FILE *f = fopen("import/dbstr_us.txt", "r"); + if(!f) { + Log.Out(Logs::General, Logs::Error, "Unable to open import/dbstr_us.txt to read, skipping."); + return; + } + + std::string delete_sql = "DELETE FROM db_str"; + db->QueryDatabase(delete_sql); + + char buffer[2048]; + bool first = true; + while(fgets(buffer, 2048, f)) { + if(first) { + first = false; + continue; + } + + for(int i = 0; i < 2048; ++i) { + if(buffer[i] == '\n') { + buffer[i] = 0; + break; + } + } + + auto split = SplitString(buffer, '^'); + + if(split.size() < 2) { + continue; + } + + std::string sql; + int id, type; + std::string value; + + id = atoi(split[0].c_str()); + type = atoi(split[1].c_str()); + + if(split.size() >= 3) { + value = ::EscapeString(split[2]); + } + + sql = StringFormat("INSERT INTO db_str(id, type, value) VALUES(%u, %u, '%s')", + id, type, value.c_str()); + + db->QueryDatabase(sql); + } + + fclose(f); +} diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 62978655a..e4793826e 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -348,6 +348,7 @@ N(OP_OpenTributeMaster), N(OP_PDeletePetition), N(OP_PetBuffWindow), N(OP_PetCommands), +N(OP_PetHoTT), N(OP_Petition), N(OP_PetitionBug), N(OP_PetitionCheckIn), @@ -364,6 +365,8 @@ N(OP_PetitionUnCheckout), N(OP_PetitionUpdate), N(OP_PickPocket), N(OP_PlayerProfile), +N(OP_PlayerStateAdd), +N(OP_PlayerStateRemove), N(OP_PlayEverquestRequest), N(OP_PlayEverquestResponse), N(OP_PlayMP3), @@ -519,8 +522,6 @@ N(OP_VetRewardsAvaliable), N(OP_VoiceMacroIn), N(OP_VoiceMacroOut), N(OP_WeaponEquip1), -N(OP_WeaponEquip2), -N(OP_WeaponUnequip2), N(OP_WearChange), N(OP_Weather), N(OP_Weblink), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index 39acb6cc3..162f9b016 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -281,7 +281,8 @@ struct Spawn_Struct { /*0146*/ uint8 beard; // Beard style (not totally, sure but maybe!) /*0147*/ uint8 unknown0147[4]; /*0151*/ uint8 level; // Spawn Level -/*0152*/ uint8 unknown0259[4]; // ***Placeholder +// None = 0, Open = 1, WeaponSheathed = 2, Aggressive = 4, ForcedAggressive = 8, InstrumentEquipped = 16, Stunned = 32, PrimaryWeaponEquipped = 64, SecondaryWeaponEquipped = 128 +/*0152*/ uint32 PlayerState; // Controls animation stuff /*0156*/ uint8 beardcolor; // Beard color /*0157*/ char suffix[32]; // Player's suffix (of Veeshan, etc.) /*0189*/ uint32 petOwnerId; // If this is a pet, the spawn id of owner @@ -374,6 +375,11 @@ union }; +struct PlayerState_Struct { +/*00*/ uint32 spawn_id; +/*04*/ uint32 state; +}; + /* ** New Spawn ** Length: 176 Bytes @@ -555,7 +561,7 @@ struct SpellBuff_Struct /*002*/ uint8 bard_modifier; /*003*/ uint8 effect; //not real /*004*/ uint32 spellid; -/*008*/ uint32 duration; +/*008*/ int32 duration; /*012*/ uint32 counters; /*016*/ uint32 player_id; //'global' ID of the caster, for wearoff messages /*020*/ @@ -568,7 +574,7 @@ struct SpellBuffFade_Struct { /*006*/ uint8 effect; /*007*/ uint8 unknown7; /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 num_hits; /*020*/ uint32 unknown020; //prolly global player ID /*024*/ uint32 slotid; @@ -586,14 +592,8 @@ struct BuffRemoveRequest_Struct struct PetBuff_Struct { /*000*/ uint32 petid; -/*004*/ uint32 spellid[BUFF_COUNT]; -/*104*/ uint32 unknown700; -/*108*/ uint32 unknown701; -/*112*/ uint32 unknown702; -/*116*/ uint32 unknown703; -/*120*/ uint32 unknown704; -/*124*/ uint32 ticsremaining[BUFF_COUNT]; -/*224*/ uchar unknown705[20]; +/*004*/ uint32 spellid[BUFF_COUNT+5]; +/*124*/ int32 ticsremaining[BUFF_COUNT+5]; /*244*/ uint32 buffcount; }; @@ -734,6 +734,7 @@ struct AA_Array { uint32 AA; uint32 value; + uint32 charges; }; @@ -1168,7 +1169,7 @@ struct TargetReject_Struct { struct PetCommand_Struct { /*000*/ uint32 command; -/*004*/ uint32 unknown; +/*004*/ uint32 target; }; /* @@ -1326,9 +1327,9 @@ struct CombatDamage_Struct /* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells /* 05 */ uint16 spellid; /* 07 */ uint32 damage; -/* 11 */ uint32 unknown11; -/* 15 */ uint32 sequence; // see above notes in Action_Struct -/* 19 */ uint32 unknown19; +/* 11 */ float force; +/* 15 */ float meleepush_xy; // see above notes in Action_Struct +/* 19 */ float meleepush_z; /* 23 */ }; @@ -2167,24 +2168,24 @@ struct Illusion_Struct_Old { // OP_Sound - Size: 68 struct QuestReward_Struct { -/*000*/ uint32 from_mob; // ID of mob awarding the client -/*004*/ uint32 unknown004; -/*008*/ uint32 unknown008; -/*012*/ uint32 unknown012; -/*016*/ uint32 unknown016; -/*020*/ uint32 unknown020; -/*024*/ uint32 silver; // Gives silver to the client -/*028*/ uint32 gold; // Gives gold to the client -/*032*/ uint32 platinum; // Gives platinum to the client -/*036*/ uint32 unknown036; -/*040*/ uint32 unknown040; -/*044*/ uint32 unknown044; -/*048*/ uint32 unknown048; -/*052*/ uint32 unknown052; -/*056*/ uint32 unknown056; -/*060*/ uint32 unknown060; -/*064*/ uint32 unknown064; -/*068*/ + /*000*/ uint32 mob_id; // ID of mob awarding the client + /*004*/ uint32 target_id; + /*008*/ uint32 exp_reward; + /*012*/ uint32 faction; + /*016*/ int32 faction_mod; + /*020*/ uint32 copper; // Gives copper to the client + /*024*/ uint32 silver; // Gives silver to the client + /*028*/ uint32 gold; // Gives gold to the client + /*032*/ uint32 platinum; // Gives platinum to the client + /*036*/ uint32 item_id; + /*040*/ uint32 unknown040; + /*044*/ uint32 unknown044; + /*048*/ uint32 unknown048; + /*052*/ uint32 unknown052; + /*056*/ uint32 unknown056; + /*060*/ uint32 unknown060; + /*064*/ uint32 unknown064; + /*068*/ }; // Size: 8 @@ -2554,8 +2555,8 @@ struct BookRequest_Struct { */ struct Object_Struct { /*00*/ uint32 linked_list_addr[2];// They are, get this, prev and next, ala linked list -/*08*/ uint16 unknown008; // -/*10*/ uint16 unknown010; // +/*08*/ uint16 size; // +/*10*/ uint16 solidtype; // /*12*/ uint32 drop_id; // Unique object id for zone /*16*/ uint16 zone_id; // Redudant, but: Zone the object appears in /*18*/ uint16 zone_instance; // @@ -4049,7 +4050,7 @@ struct MarkNPC_Struct struct InspectBuffs_Struct { /*000*/ uint32 spell_id[BUFF_COUNT]; -/*100*/ uint32 tics_remaining[BUFF_COUNT]; +/*100*/ int32 tics_remaining[BUFF_COUNT]; }; struct RaidGeneral_Struct { @@ -4295,14 +4296,6 @@ struct AA_Action { /*12*/ uint32 exp_value; }; - -struct AA_Skills { //this should be removed and changed to AA_Array -/*00*/ uint32 aa_skill; // Total AAs Spent -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -/*12*/ -}; - struct AAExpUpdate_Struct { /*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability /*04*/ uint32 aapoints_unspent; @@ -4320,12 +4313,12 @@ struct AltAdvStats_Struct { }; struct PlayerAA_Struct { // Is this still used? - AA_Skills aa_list[MAX_PP_AA_ARRAY]; + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct AATable_Struct { /*00*/ int32 aa_spent; // Total AAs Spent -/*04*/ AA_Skills aa_list[MAX_PP_AA_ARRAY]; +/*04*/ AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct Weather_Struct { @@ -4750,7 +4743,7 @@ struct BuffIconEntry_Struct { uint32 buff_slot; uint32 spell_id; - uint32 tics_remaining; + int32 tics_remaining; uint32 num_hits; }; @@ -4759,6 +4752,7 @@ struct BuffIcon_Struct uint32 entity_id; uint8 all_buffs; uint16 count; + uint8 type; // 0 = self buff window, 1 = self target window, 4 = group, 5 = PC, 7 = NPC BuffIconEntry_Struct entries[0]; }; diff --git a/common/eq_stream.cpp b/common/eq_stream.cpp index be1ae50d4..3438f533c 100644 --- a/common/eq_stream.cpp +++ b/common/eq_stream.cpp @@ -72,6 +72,8 @@ void EQStream::init(bool resetSession) { RateThreshold=RATEBASE/250; DecayRate=DECAYBASE/250; BytesWritten=0; + sent_packet_count = 0; + received_packet_count = 0; SequencedBase = 0; NextSequencedSend = 0; @@ -464,37 +466,45 @@ void EQStream::ProcessPacket(EQProtocolPacket *p) } break; case OP_SessionStatRequest: { - if(p->Size() < sizeof(SessionStats)) + if(p->Size() < sizeof(ClientSessionStats)) { Log.Out(Logs::Detail, Logs::Netcode, _L "Received OP_SessionStatRequest that was of malformed size" __L); break; } #ifndef COLLECTOR - SessionStats *Stats=(SessionStats *)p->pBuffer; + ClientSessionStats *ClientStats=(ClientSessionStats *)p->pBuffer; Log.Out(Logs::Detail, Logs::Netcode, _L "Received Stats: %lu packets received, %lu packets sent, Deltas: local %lu, (%lu <- %lu -> %lu) remote %lu" __L, - (unsigned long)ntohl(Stats->packets_received), (unsigned long)ntohl(Stats->packets_sent), (unsigned long)ntohl(Stats->last_local_delta), - (unsigned long)ntohl(Stats->low_delta), (unsigned long)ntohl(Stats->average_delta), - (unsigned long)ntohl(Stats->high_delta), (unsigned long)ntohl(Stats->last_remote_delta)); - uint64 x=Stats->packets_received; - Stats->packets_received=Stats->packets_sent; - Stats->packets_sent=x; - NonSequencedPush(new EQProtocolPacket(OP_SessionStatResponse,p->pBuffer,p->size)); - AdjustRates(ntohl(Stats->average_delta)); + (unsigned long)ntohl(ClientStats->packets_received), (unsigned long)ntohl(ClientStats->packets_sent), (unsigned long)ntohl(ClientStats->last_local_delta), + (unsigned long)ntohl(ClientStats->low_delta), (unsigned long)ntohl(ClientStats->average_delta), + (unsigned long)ntohl(ClientStats->high_delta), (unsigned long)ntohl(ClientStats->last_remote_delta)); + + AdjustRates(ntohl(ClientStats->average_delta)); if(GetExecutablePlatform() == ExePlatformWorld || GetExecutablePlatform() == ExePlatformZone) { - if(RETRANSMIT_TIMEOUT_MULT && ntohl(Stats->average_delta)) { + if (RETRANSMIT_TIMEOUT_MULT && ntohl(ClientStats->average_delta)) { //recalculate retransmittimeout using the larger of the last rtt or average rtt, which is multiplied by the rule value - if((ntohl(Stats->last_local_delta) + ntohl(Stats->last_remote_delta)) > (ntohl(Stats->average_delta) * 2)) { - retransmittimeout = (ntohl(Stats->last_local_delta) + ntohl(Stats->last_remote_delta)) + if ((ntohl(ClientStats->last_local_delta) + ntohl(ClientStats->last_remote_delta)) > (ntohl(ClientStats->average_delta) * 2)) { + retransmittimeout = (ntohl(ClientStats->last_local_delta) + ntohl(ClientStats->last_remote_delta)) * RETRANSMIT_TIMEOUT_MULT; } else { - retransmittimeout = ntohl(Stats->average_delta) * 2 * RETRANSMIT_TIMEOUT_MULT; + retransmittimeout = ntohl(ClientStats->average_delta) * 2 * RETRANSMIT_TIMEOUT_MULT; } if(retransmittimeout > RETRANSMIT_TIMEOUT_MAX) retransmittimeout = RETRANSMIT_TIMEOUT_MAX; Log.Out(Logs::Detail, Logs::Netcode, _L "Retransmit timeout recalculated to %dms" __L, retransmittimeout); } } + + ServerSessionStats *ServerStats = (ServerSessionStats *)p->pBuffer; + + //ServerStats->RequestID = ClientStats->RequestID; // no change + ServerStats->ServerTime = htonl(Timer::GetCurrentTime()); + ServerStats->packets_sent_echo = ClientStats->packets_sent; // still in htonll format + ServerStats->packets_received_echo = ClientStats->packets_received; // still in htonll format + ServerStats->packets_sent = htonll(GetPacketsSent()); + ServerStats->packets_received = htonll(GetPacketsReceived()); + + NonSequencedPush(new EQProtocolPacket(OP_SessionStatResponse, p->pBuffer, p->size)); #endif } break; @@ -573,16 +583,18 @@ void EQStream::SendPacket(uint16 opcode, EQApplicationPacket *p) // Convert the EQApplicationPacket to 1 or more EQProtocolPackets if (p->size>(MaxLen-8)) { // proto-op(2), seq(2), app-op(2) ... data ... crc(2) - Log.Out(Logs::Detail, Logs::Netcode, _L "Making oversized packet, len %d" __L, p->size); + Log.Out(Logs::Detail, Logs::Netcode, _L "Making oversized packet, len %d" __L, p->Size()); unsigned char *tmpbuff=new unsigned char[p->size+3]; length=p->serialize(opcode, tmpbuff); + if (length != p->Size()) + Log.Out(Logs::Detail, Logs::Netcode, _L "Packet adjustment, len %d to %d" __L, p->Size(), length); EQProtocolPacket *out=new EQProtocolPacket(OP_Fragment,nullptr,MaxLen-4); - *(uint32 *)(out->pBuffer+2)=htonl(p->Size()); + *(uint32 *)(out->pBuffer+2)=htonl(length); used=MaxLen-10; memcpy(out->pBuffer+6,tmpbuff,used); - Log.Out(Logs::Detail, Logs::Netcode, _L "First fragment: used %d/%d. Put size %d in the packet" __L, used, p->size, p->Size()); + Log.Out(Logs::Detail, Logs::Netcode, _L "First fragment: used %d/%d. Payload size %d in the packet" __L, used, length, p->size); SequencedPush(out); @@ -593,7 +605,7 @@ void EQStream::SendPacket(uint16 opcode, EQApplicationPacket *p) out->size=chunksize+2; SequencedPush(out); used+=chunksize; - Log.Out(Logs::Detail, Logs::Netcode, _L "Subsequent fragment: len %d, used %d/%d." __L, chunksize, used, p->size); + Log.Out(Logs::Detail, Logs::Netcode, _L "Subsequent fragment: len %d, used %d/%d." __L, chunksize, used, length); } delete p; delete[] tmpbuff; @@ -1101,8 +1113,8 @@ EQProtocolPacket *p=nullptr; void EQStream::Process(const unsigned char *buffer, const uint32 length) { -static unsigned char newbuffer[2048]; -uint32 newlength=0; + static unsigned char newbuffer[2048]; + uint32 newlength=0; if (EQProtocolPacket::ValidateCRC(buffer,length,Key)) { if (compressed) { newlength=EQProtocolPacket::Decompress(buffer,length,newbuffer,2048); diff --git a/common/eq_stream.h b/common/eq_stream.h index 72eb53cdd..0bdfeab53 100644 --- a/common/eq_stream.h +++ b/common/eq_stream.h @@ -71,7 +71,7 @@ struct SessionResponse { }; //Deltas are in ms, representing round trip times -struct SessionStats { +struct ClientSessionStats { /*000*/ uint16 RequestID; /*002*/ uint32 last_local_delta; /*006*/ uint32 average_delta; @@ -83,6 +83,16 @@ struct SessionStats { /*038*/ }; +struct ServerSessionStats { +/*000*/ uint16 RequestID; +/*002*/ uint32 ServerTime; +/*006*/ uint64 packets_sent_echo; +/*014*/ uint64 packets_received_echo; +/*022*/ uint64 packets_sent; +/*030*/ uint64 packets_received; +/*038*/ +}; + #pragma pack() class OpcodeManager; @@ -158,6 +168,9 @@ class EQStream : public EQStreamInterface { int32 BytesWritten; + uint64 sent_packet_count; + uint64 received_packet_count; + Mutex MRate; int32 RateThreshold; int32 DecayRate; @@ -265,11 +278,13 @@ class EQStream : public EQStreamInterface { void AddBytesSent(uint32 bytes) { bytes_sent += bytes; + ++sent_packet_count; } void AddBytesRecv(uint32 bytes) { bytes_recv += bytes; + ++received_packet_count; } virtual const uint32 GetBytesSent() const { return bytes_sent; } @@ -288,6 +303,9 @@ class EQStream : public EQStreamInterface { return bytes_recv / (Timer::GetTimeSeconds() - create_time); } + const uint64 GetPacketsSent() { return sent_packet_count; } + const uint64 GetPacketsReceived() { return received_packet_count; } + //used for dynamic stream identification class Signature { public: diff --git a/common/eqtime.cpp b/common/eqtime.cpp index 79301a28f..e504964b8 100644 --- a/common/eqtime.cpp +++ b/common/eqtime.cpp @@ -43,19 +43,19 @@ EQTime::EQTime(TimeOfDay_Struct start_eq, time_t start_real) EQTime::EQTime() { - timezone=0; + timezone = 0; memset(&eqTime, 0, sizeof(eqTime)); //Defaults for time TimeOfDay_Struct start; - start.day=1; - start.hour=9; - start.minute=0; - start.month=1; - start.year=3100; + start.day = 1; + start.hour = 9; + start.minute = 0; + start.month = 1; + start.year = 3100; //Set default time zone - timezone=0; + timezone = 0; //Start EQTimer - setEQTimeOfDay(start, time(0)); + SetCurrentEQTimeOfDay(start, time(0)); } EQTime::~EQTime() @@ -67,10 +67,10 @@ EQTime::~EQTime() //Input: Current Time (as a time_t), a pointer to the TimeOfDay_Struct that will be written to. //Output: 0=Error, 1=Sucess -int EQTime::getEQTimeOfDay( time_t timeConvert, struct TimeOfDay_Struct *eqTimeOfDay ) +int EQTime::GetCurrentEQTimeOfDay(time_t timeConvert, struct TimeOfDay_Struct *eqTimeOfDay) { /* check to see if we have a reference time to go by. */ - if( eqTime.start_realtime == 0 ) + if (eqTime.start_realtime == 0) return 0; unsigned long diff = timeConvert - eqTime.start_realtime; @@ -83,7 +83,7 @@ int EQTime::getEQTimeOfDay( time_t timeConvert, struct TimeOfDay_Struct *eqTimeO int32 ntz = timezone; /* The minutes range from 0 - 59 */ - diff += eqTime.start_eqtime.minute + (ntz%60); + diff += eqTime.start_eqtime.minute + (ntz % 60); eqTimeOfDay->minute = diff % 60; diff /= 60; ntz /= 60; @@ -97,24 +97,24 @@ int EQTime::getEQTimeOfDay( time_t timeConvert, struct TimeOfDay_Struct *eqTimeO // // Modify it so that it works from // 0-23 for our calculations - diff += ( eqTime.start_eqtime.hour - 1) + (ntz%24); - eqTimeOfDay->hour = (diff%24) + 1; + diff += (eqTime.start_eqtime.hour - 1) + (ntz % 24); + eqTimeOfDay->hour = (diff % 24) + 1; diff /= 24; ntz /= 24; // The days range from 1-28 // Modify it so that it works from // 0-27 for our calculations - diff += ( eqTime.start_eqtime.day - 1 ) + (ntz%28); - eqTimeOfDay->day = (diff%28) + 1; + diff += (eqTime.start_eqtime.day - 1) + (ntz % 28); + eqTimeOfDay->day = (diff % 28) + 1; diff /= 28; ntz /= 28; // The months range from 1-12 // Modify it so that it works from // 0-11 for our calculations - diff += ( eqTime.start_eqtime.month - 1 ) + (ntz%12); - eqTimeOfDay->month = (diff%12) + 1; + diff += (eqTime.start_eqtime.month - 1) + (ntz % 12); + eqTimeOfDay->month = (diff % 12) + 1; diff /= 12; ntz /= 12; @@ -124,12 +124,12 @@ int EQTime::getEQTimeOfDay( time_t timeConvert, struct TimeOfDay_Struct *eqTimeO } //setEQTimeOfDay -int EQTime::setEQTimeOfDay(TimeOfDay_Struct start_eq, time_t start_real) +int EQTime::SetCurrentEQTimeOfDay(TimeOfDay_Struct start_eq, time_t start_real) { - if(start_real==0) + if (start_real == 0) return 0; - eqTime.start_eqtime=start_eq; - eqTime.start_realtime=start_real; + eqTime.start_eqtime = start_eq; + eqTime.start_realtime = start_real; return 1; } @@ -139,7 +139,7 @@ bool EQTime::saveFile(const char *filename) { std::ofstream of; of.open(filename); - if(!of) + if (!of) { Log.Out(Logs::General, Logs::Error, "EQTime::saveFile failed: Unable to open file '%s'", filename); return false; @@ -200,24 +200,24 @@ bool EQTime::loadFile(const char *filename) bool EQTime::IsTimeBefore(TimeOfDay_Struct *base, TimeOfDay_Struct *test) { - if(base->year > test->year) + if (base->year > test->year) return(true); - if(base->year < test->year) + if (base->year < test->year) return(false); //same years - if(base->month > test->month) + if (base->month > test->month) return(true); - if(base->month < test->month) + if (base->month < test->month) return(false); //same month - if(base->day > test->day) + if (base->day > test->day) return(true); - if(base->day < test->day) + if (base->day < test->day) return(false); //same day - if(base->hour > test->hour) + if (base->hour > test->hour) return(true); - if(base->hour < test->hour) + if (base->hour < test->hour) return(false); //same hour... return(base->minute > test->minute); @@ -230,7 +230,7 @@ void EQTime::AddMinutes(uint32 minutes, TimeOfDay_Struct *to) { //minutes start at 0, everything else starts at 1 cur = to->minute; cur += minutes; - if(cur < 60) { + if (cur < 60) { to->minute = cur; return; } @@ -238,29 +238,29 @@ void EQTime::AddMinutes(uint32 minutes, TimeOfDay_Struct *to) { //carry hours cur /= 60; cur += to->hour; - if(cur <= 24) { + if (cur <= 24) { to->hour = cur; return; } - to->hour = ((cur-1) % 24) + 1; + to->hour = ((cur - 1) % 24) + 1; //carry days - cur = (cur-1) / 24; + cur = (cur - 1) / 24; cur += to->day; - if(cur <= 28) { + if (cur <= 28) { to->day = cur; return; } - to->day = ((cur-1) % 28) + 1; + to->day = ((cur - 1) % 28) + 1; //carry months - cur = (cur-1) / 28; + cur = (cur - 1) / 28; cur += to->month; - if(cur <= 12) { + if (cur <= 12) { to->month = cur; return; } - to->month = ((cur-1) % 12) + 1; + to->month = ((cur - 1) % 12) + 1; //carry years - to->year += (cur-1) / 12; + to->year += (cur - 1) / 12; } void EQTime::ToString(TimeOfDay_Struct *t, std::string &str) { @@ -269,5 +269,4 @@ void EQTime::ToString(TimeOfDay_Struct *t, std::string &str) { t->month, t->day, t->year, t->hour, t->minute); buf[127] = '\0'; str = buf; -} - +} \ No newline at end of file diff --git a/common/eqtime.h b/common/eqtime.h index aa608f307..aeda9f0f6 100644 --- a/common/eqtime.h +++ b/common/eqtime.h @@ -21,8 +21,8 @@ public: ~EQTime(); //Get functions - int getEQTimeOfDay( TimeOfDay_Struct *eqTimeOfDay ) { return(getEQTimeOfDay(time(nullptr), eqTimeOfDay)); } - int getEQTimeOfDay( time_t timeConvert, TimeOfDay_Struct *eqTimeOfDay ); + int GetCurrentEQTimeOfDay( TimeOfDay_Struct *eqTimeOfDay ) { return(GetCurrentEQTimeOfDay(time(nullptr), eqTimeOfDay)); } + int GetCurrentEQTimeOfDay( time_t timeConvert, TimeOfDay_Struct *eqTimeOfDay ); TimeOfDay_Struct getStartEQTime() { return eqTime.start_eqtime; } time_t getStartRealTime() { return eqTime.start_realtime; } uint32 getEQTimeZone() { return timezone; } @@ -30,7 +30,7 @@ public: uint32 getEQTimeZoneMin() { return timezone%60; } //Set functions - int setEQTimeOfDay(TimeOfDay_Struct start_eq, time_t start_real); + int SetCurrentEQTimeOfDay(TimeOfDay_Struct start_eq, time_t start_real); void setEQTimeZone(int32 in_timezone) { timezone=in_timezone; } //Time math/logic functions diff --git a/common/item_data.h b/common/item_data.h index ccb6b6411..fbfdba93b 100644 --- a/common/item_data.h +++ b/common/item_data.h @@ -183,7 +183,7 @@ struct ItemData { uint32 AugType; uint8 AugSlotType[EmuConstants::ITEM_COMMON_SIZE]; // RoF: Augment Slot 1-6 Type uint8 AugSlotVisible[EmuConstants::ITEM_COMMON_SIZE]; // RoF: Augment Slot 1-6 Visible - uint8 AugSlotUnk2[EmuConstants::ITEM_COMMON_SIZE]; // RoF: Augment Slot 1-6 Unknown + uint8 AugSlotUnk2[EmuConstants::ITEM_COMMON_SIZE]; // RoF: Augment Slot 1-6 Unknown Most likely Powersource related uint32 LDoNTheme; uint32 LDoNPrice; uint32 LDoNSold; diff --git a/common/patches/rof.cpp b/common/patches/rof.cpp index ef1626e84..2f515bcf0 100644 --- a/common/patches/rof.cpp +++ b/common/patches/rof.cpp @@ -417,7 +417,7 @@ namespace RoF outapp->WriteUInt32(0); // Duration outapp->WriteUInt32(0); // ? outapp->WriteUInt8(0); // Caster name - outapp->WriteUInt8(0); // Terminating byte + outapp->WriteUInt8(0); // Type } FINISH_ENCODE(); @@ -454,7 +454,7 @@ namespace RoF __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown __packet->WriteString(""); } - __packet->WriteUInt8(!emu->all_buffs); // Unknown + __packet->WriteUInt8(emu->type); // Unknown FINISH_ENCODE(); } @@ -659,7 +659,9 @@ namespace RoF OUT(type); OUT(spellid); OUT(damage); - eq->sequence = emu->sequence; + OUT(force) + OUT(meleepush_xy); + OUT(meleepush_z) FINISH_ENCODE(); } @@ -974,8 +976,8 @@ namespace RoF VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Same for all objects in the zone VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->heading); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0); // Normally 0, but seen (float)255.0 as well - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0); // Unknown - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 1); // Need to add emu->size to struct + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->solidtype); // Unknown + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->size != 0 && (float)emu->size < 5000.f ? (float)((float)emu->size / 100.0f) : 1.f ); // This appears to be the size field. Hackish logic because some PEQ DB items were corrupt. VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->y); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->x); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->z); @@ -2119,7 +2121,7 @@ namespace RoF { outapp->WriteUInt32(emu->aa_array[r].AA); outapp->WriteUInt32(emu->aa_array[r].value); - outapp->WriteUInt32(0); + outapp->WriteUInt32(emu->aa_array[r].charges); } // Fill the other 60 AAs with zeroes @@ -2817,9 +2819,9 @@ namespace RoF for (uint32 i = 0; i < MAX_PP_AA_ARRAY; ++i) { - eq->aa_list[i].aa_skill = emu->aa_list[i].aa_skill; - eq->aa_list[i].aa_value = emu->aa_list[i].aa_value; - eq->aa_list[i].unknown08 = emu->aa_list[i].unknown08; + eq->aa_list[i].AA = emu->aa_list[i].AA; + eq->aa_list[i].value = emu->aa_list[i].value; + eq->aa_list[i].charges = emu->aa_list[i].charges; } FINISH_ENCODE(); @@ -2851,7 +2853,7 @@ namespace RoF // Check clientver field to verify this AA should be sent for SoF // clientver 1 is for all clients and 5 is for Live - if (emu->clientver <= 5) + if (emu->clientver <= 7) { OUT(id); eq->unknown004 = 1; @@ -2867,9 +2869,9 @@ namespace RoF OUT(cost); OUT(seq); OUT(current_level); - eq->unknown037 = 1; // Introduced during HoT + eq->prereq_skill_count = 1; // min 1 OUT(prereq_skill); - eq->unknown045 = 1; // New Mar 21 2012 - Seen 1 + eq->prereq_minpoints_count = 1; // min 1 OUT(prereq_minpoints); eq->type = emu->sof_type; OUT(spellid); @@ -2885,6 +2887,7 @@ namespace RoF OUT(cost2); eq->aa_expansion = emu->aa_expansion; eq->special_category = emu->special_category; + eq->expendable_charges = emu->special_category == 7 ? 1 : 0; // temp hack, this can actually be any number OUT(total_abilities); unsigned int r; for (r = 0; r < emu->total_abilities; r++) { @@ -2945,11 +2948,12 @@ namespace RoF for (int counter = 0; counter < character_count; ++counter) { emu_cse = (CharacterSelectEntry_Struct *)emu_ptr; - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // base address strcpy(eq_cse->Name, emu_cse->Name); - eq_ptr += strlen(eq_cse->Name); - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_ptr += strlen(emu_cse->Name); + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // offset address (base + name length offset) + eq_cse->Name[0] = '\0'; // (offset)eq_cse->Name[0] = (base)eq_cse->Name[strlen(emu_cse->Name)] eq_cse->Class = emu_cse->Class; eq_cse->Race = emu_cse->Race; @@ -3989,7 +3993,7 @@ namespace RoF VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown13 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown14 - Stance 64 = normal 4 = aggressive 40 = stun/mezzed + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown15 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown16 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown17 @@ -4390,7 +4394,7 @@ namespace RoF IN(type); IN(spellid); IN(damage); - emu->sequence = eq->sequence; + IN(meleepush_xy); FINISH_DIRECT_DECODE(); } @@ -4718,7 +4722,7 @@ namespace RoF SETUP_DIRECT_DECODE(PetCommand_Struct, structs::PetCommand_Struct); IN(command); - emu->unknown = eq->unknown04; + IN(target); FINISH_DIRECT_DECODE(); } diff --git a/common/patches/rof2.cpp b/common/patches/rof2.cpp index b60016128..9dd4bdee5 100644 --- a/common/patches/rof2.cpp +++ b/common/patches/rof2.cpp @@ -485,7 +485,7 @@ namespace RoF2 outapp->WriteUInt32(0); // Duration outapp->WriteUInt32(0); // ? outapp->WriteUInt8(0); // Caster name - outapp->WriteUInt8(0); // Terminating byte + outapp->WriteUInt8(0); // Type } FINISH_ENCODE(); @@ -527,7 +527,7 @@ namespace RoF2 __packet->WriteUInt32(emu->entries[i].num_hits); // Unknown __packet->WriteString(""); } - __packet->WriteUInt8(!emu->all_buffs); // Unknown + __packet->WriteUInt8(emu->type); // Unknown FINISH_ENCODE(); } @@ -700,7 +700,9 @@ namespace RoF2 OUT(type); OUT(spellid); OUT(damage); - eq->sequence = emu->sequence; + OUT(force) + OUT(meleepush_xy); + OUT(meleepush_z) FINISH_ENCODE(); } @@ -1015,8 +1017,8 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Same for all objects in the zone VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->heading); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0); // Normally 0, but seen (float)255.0 as well - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 0); // Unknown - VARSTRUCT_ENCODE_TYPE(float, OutBuffer, 1); // Need to add emu->size to struct + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->solidtype); // Unknown + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->size != 0 && (float)emu->size < 5000.f ? (float)((float)emu->size / 100.0f) : 1.f ); // This appears to be the size field. Hackish logic because some PEQ DB items were corrupt. VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->y); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->x); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->z); @@ -2185,7 +2187,7 @@ namespace RoF2 { outapp->WriteUInt32(emu->aa_array[r].AA); outapp->WriteUInt32(emu->aa_array[r].value); - outapp->WriteUInt32(0); + outapp->WriteUInt32(emu->aa_array[r].charges); } // Fill the other 60 AAs with zeroes @@ -2892,9 +2894,9 @@ namespace RoF2 for (uint32 i = 0; i < MAX_PP_AA_ARRAY; ++i) { - eq->aa_list[i].aa_skill = emu->aa_list[i].aa_skill; - eq->aa_list[i].aa_value = emu->aa_list[i].aa_value; - eq->aa_list[i].unknown08 = emu->aa_list[i].unknown08; + eq->aa_list[i].AA = emu->aa_list[i].AA; + eq->aa_list[i].value = emu->aa_list[i].value; + eq->aa_list[i].charges = emu->aa_list[i].charges; } FINISH_ENCODE(); @@ -2926,7 +2928,7 @@ namespace RoF2 // Check clientver field to verify this AA should be sent for SoF // clientver 1 is for all clients and 5 is for Live - if (emu->clientver <= 5) + if (emu->clientver <= 8) { OUT(id); eq->unknown004 = 1; @@ -2942,9 +2944,9 @@ namespace RoF2 OUT(cost); OUT(seq); OUT(current_level); - eq->unknown037 = 1; // Introduced during HoT + eq->prereq_skill_count = 1; // min 1 OUT(prereq_skill); - eq->unknown045 = 1; // New Mar 21 2012 - Seen 1 + eq->prereq_minpoints_count = 1; // min 1 OUT(prereq_minpoints); eq->type = emu->sof_type; OUT(spellid); @@ -2961,6 +2963,7 @@ namespace RoF2 eq->aa_expansion = emu->aa_expansion; eq->special_category = emu->special_category; OUT(total_abilities); + eq->expendable_charges = emu->special_category == 7 ? 1 : 0; // temp hack, this can actually be any number unsigned int r; for (r = 0; r < emu->total_abilities; r++) { OUT(abilities[r].skill_id); @@ -3020,11 +3023,12 @@ namespace RoF2 for (int counter = 0; counter < character_count; ++counter) { emu_cse = (CharacterSelectEntry_Struct *)emu_ptr; - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // base address strcpy(eq_cse->Name, emu_cse->Name); - eq_ptr += strlen(eq_cse->Name); - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_ptr += strlen(emu_cse->Name); + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // offset address (base + name length offset) + eq_cse->Name[0] = '\0'; // (offset)eq_cse->Name[0] = (base)eq_cse->Name[strlen(emu_cse->Name)] eq_cse->Class = emu_cse->Class; eq_cse->Race = emu_cse->Race; @@ -4125,7 +4129,7 @@ namespace RoF2 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown13 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown14 - Stance 64 = normal 4 = aggressive 40 = stun/mezzed + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown15 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown16 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown17 @@ -4525,7 +4529,7 @@ namespace RoF2 IN(type); IN(spellid); IN(damage); - emu->sequence = eq->sequence; + IN(meleepush_xy); FINISH_DIRECT_DECODE(); } @@ -4856,7 +4860,7 @@ namespace RoF2 SETUP_DIRECT_DECODE(PetCommand_Struct, structs::PetCommand_Struct); IN(command); - emu->unknown = eq->unknown04; + IN(target); FINISH_DIRECT_DECODE(); } diff --git a/common/patches/rof2_structs.h b/common/patches/rof2_structs.h index 205d3d603..e472616b7 100644 --- a/common/patches/rof2_structs.h +++ b/common/patches/rof2_structs.h @@ -416,7 +416,7 @@ struct Spawn_Struct /*0000*/ uint8 unknown12; /*0000*/ uint32 petOwnerId; /*0000*/ uint8 unknown13; -/*0000*/ uint32 unknown14; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed +/*0000*/ uint32 PlayerState; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed /*0000*/ uint32 unknown15; /*0000*/ uint32 unknown16; /*0000*/ uint32 unknown17; @@ -687,7 +687,7 @@ struct SpellBuff_Struct /*005*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages /*009*/ uint32 unknown016; /*013*/ uint8 bard_modifier; -/*014*/ uint32 duration; +/*014*/ int32 duration; /*018*/ uint8 level; /*019*/ uint32 spellid; /*023*/ uint32 counters; @@ -703,7 +703,7 @@ struct SpellBuff_Struct_Old /*003*/ uint8 effect; // not real /*004*/ float unknown004; // Seen 1 for no buff /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 unknown016; /*020*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages /*024*/ uint32 counters; @@ -720,7 +720,7 @@ struct SpellBuffFade_Struct_Live { /*007*/ uint8 unknown007; /*008*/ float unknown008; /*012*/ uint32 spellid; -/*016*/ uint32 duration; +/*016*/ int32 duration; /*020*/ uint32 playerId; // Global player ID? /*024*/ uint32 num_hits; /*028*/ uint8 unknown0028[64]; @@ -736,7 +736,7 @@ struct SpellBuffFade_Struct { /*006*/ uint8 effect; /*007*/ uint8 unknown7; /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 num_hits; /*020*/ uint32 unknown020; // Global player ID? /*024*/ uint32 playerId; // Player id who cast the buff @@ -877,7 +877,7 @@ struct AA_Array { uint32 AA; uint32 value; - uint32 unknown08; // Looks like AA_Array is now 12 bytes in Live + uint32 charges; // expendable charges }; struct Disciplines_Struct { @@ -1293,7 +1293,7 @@ struct TargetReject_Struct { struct PetCommand_Struct { /*00*/ uint32 command; -/*04*/ uint32 unknown04; +/*04*/ uint32 target; /*08*/ uint32 unknown08; }; @@ -1484,9 +1484,10 @@ struct CombatDamage_Struct /* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells /* 05 */ uint32 spellid; /* 09 */ int32 damage; -/* 13 */ float unknown11; // cd cc cc 3d -/* 17 */ float sequence; // see above notes in Action_Struct -/* 21 */ uint8 unknown19[9]; // was [9] +/* 13 */ float force; // cd cc cc 3d +/* 17 */ float meleepush_xy; // see above notes in Action_Struct +/* 21 */ float meleepush_z; +/* 25 */ uint8 unknown25[5]; // was [9] /* 30 */ }; @@ -2472,7 +2473,7 @@ struct GroupFollow_Struct { // Live Follow Struct struct InspectBuffs_Struct { /*000*/ uint32 spell_id[BUFF_COUNT]; -/*168*/ uint32 tics_remaining[BUFF_COUNT]; +/*168*/ int32 tics_remaining[BUFF_COUNT]; }; struct LFG_Struct { @@ -4252,9 +4253,9 @@ struct SendAA_Struct { /*0025*/ uint32 cost; /*0029*/ uint32 seq; /*0033*/ uint32 current_level; //1s, MQ2 calls this AARankRequired -/*0037*/ uint32 unknown037; // Introduced during HoT +/*0037*/ uint32 prereq_skill_count; // mutliple prereqs at least 1, even no prereqs /*0041*/ uint32 prereq_skill; //is < 0, abs() is category # -/*0045*/ uint32 unknown045; // New Mar 21 2012 - Seen 1 +/*0045*/ uint32 prereq_minpoints_count; // mutliple prereqs at least 1, even no prereqs /*0049*/ uint32 prereq_minpoints; //min points in the prereq /*0053*/ uint32 type; /*0057*/ uint32 spellid; @@ -4267,10 +4268,16 @@ struct SendAA_Struct { /*0081*/ uint32 last_id; /*0085*/ uint32 next_id; /*0089*/ uint32 cost2; -/*0093*/ uint8 unknown80[7]; +/*0093*/ uint8 unknown93; +/*0094*/ uint8 grant_only; // VetAAs, progression, etc +/*0095*/ uint8 unknown95; // 1 for skill cap increase AAs, Mystical Attuning, and RNG attack inc, doesn't seem to matter though +/*0096*/ uint32 expendable_charges; // max charges of the AA /*0100*/ uint32 aa_expansion; /*0104*/ uint32 special_category; -/*0108*/ uint32 unknown0096; +/*0108*/ uint8 shroud; +/*0109*/ uint8 unknown109; +/*0110*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter? +/*0111*/ uint8 unknown111; /*0112*/ uint32 total_abilities; /*0116*/ AA_Ability abilities[0]; }; @@ -4287,12 +4294,6 @@ struct AA_Action { /*16*/ }; -struct AA_Skills { //this should be removed and changed to AA_Array -/*00*/ uint32 aa_skill; // Total AAs Spent -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -/*12*/ -}; struct AAExpUpdate_Struct { /*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability @@ -4312,14 +4313,7 @@ struct AltAdvStats_Struct { }; struct PlayerAA_Struct { // Is this still used? - AA_Skills aa_list[MAX_PP_AA_ARRAY]; -}; - -struct AA_Values { -/*00*/ uint32 aa_skill; -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -/*12*/ + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct AATable_Struct { @@ -4329,7 +4323,7 @@ struct AATable_Struct { /*12*/ uint32 aa_spent_archetype; // Seen 40 /*16*/ uint32 aa_spent_class; // Seen 103 /*20*/ uint32 aa_spent_special; // Seen 0 -/*24*/ AA_Values aa_list[MAX_PP_AA_ARRAY]; +/*24*/ AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct Weather_Struct { diff --git a/common/patches/rof_structs.h b/common/patches/rof_structs.h index 539560723..08c63ddf3 100644 --- a/common/patches/rof_structs.h +++ b/common/patches/rof_structs.h @@ -410,7 +410,7 @@ struct Spawn_Struct /*0000*/ uint8 unknown12; /*0000*/ uint32 petOwnerId; /*0000*/ uint8 unknown13; -/*0000*/ uint32 unknown14; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed +/*0000*/ uint32 PlayerState; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed /*0000*/ uint32 unknown15; /*0000*/ uint32 unknown16; /*0000*/ uint32 unknown17; @@ -676,7 +676,7 @@ struct SpellBuff_Struct /*005*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages /*009*/ uint32 unknown016; /*013*/ uint8 bard_modifier; -/*014*/ uint32 duration; +/*014*/ int32 duration; /*018*/ uint8 level; /*019*/ uint32 spellid; /*023*/ uint32 counters; @@ -692,7 +692,7 @@ struct SpellBuff_Struct_Old /*003*/ uint8 effect; // not real /*004*/ float unknown004; // Seen 1 for no buff /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 unknown016; /*020*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages /*024*/ uint32 counters; @@ -709,7 +709,7 @@ struct SpellBuffFade_Struct_Live { /*007*/ uint8 unknown007; /*008*/ float unknown008; /*012*/ uint32 spellid; -/*016*/ uint32 duration; +/*016*/ int32 duration; /*020*/ uint32 playerId; // Global player ID? /*024*/ uint32 num_hits; /*028*/ uint8 unknown0028[64]; @@ -725,7 +725,7 @@ struct SpellBuffFade_Struct { /*006*/ uint8 effect; /*007*/ uint8 unknown7; /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 num_hits; /*020*/ uint32 unknown020; // Global player ID? /*024*/ uint32 playerId; // Player id who cast the buff @@ -866,7 +866,7 @@ struct AA_Array { uint32 AA; uint32 value; - uint32 unknown08; // Looks like AA_Array is now 12 bytes in Live + uint32 charges; // expendable charges }; struct Disciplines_Struct { @@ -1323,7 +1323,7 @@ struct TargetReject_Struct { struct PetCommand_Struct { /*00*/ uint32 command; -/*04*/ uint32 unknown04; +/*04*/ uint32 target; /*08*/ uint32 unknown08; }; @@ -1514,9 +1514,10 @@ struct CombatDamage_Struct /* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells /* 05 */ uint32 spellid; /* 09 */ int32 damage; -/* 13 */ float unknown11; // cd cc cc 3d -/* 17 */ float sequence; // see above notes in Action_Struct -/* 21 */ uint8 unknown19[9]; // was [9] +/* 13 */ float force; // cd cc cc 3d +/* 17 */ float meleepush_xy; // see above notes in Action_Struct +/* 21 */ float meleepush_z; +/* 25 */ uint8 unknown25[5]; // was [9] /* 30 */ }; @@ -2500,7 +2501,7 @@ struct GroupFollow_Struct { // Live Follow Struct struct InspectBuffs_Struct { /*000*/ uint32 spell_id[BUFF_COUNT]; -/*168*/ uint32 tics_remaining[BUFF_COUNT]; +/*168*/ int32 tics_remaining[BUFF_COUNT]; }; struct LFG_Struct { @@ -4251,9 +4252,9 @@ struct SendAA_Struct { /*0025*/ uint32 cost; /*0029*/ uint32 seq; /*0033*/ uint32 current_level; //1s, MQ2 calls this AARankRequired -/*0037*/ uint32 unknown037; // Introduced during HoT +/*0037*/ uint32 prereq_skill_count; // mutliple prereqs at least 1, even no prereqs /*0041*/ uint32 prereq_skill; //is < 0, abs() is category # -/*0045*/ uint32 unknown045; // New Mar 21 2012 - Seen 1 +/*0045*/ uint32 prereq_minpoints_count; // mutliple prereqs at least 1, even no prereqs /*0049*/ uint32 prereq_minpoints; //min points in the prereq /*0053*/ uint32 type; /*0057*/ uint32 spellid; @@ -4266,10 +4267,16 @@ struct SendAA_Struct { /*0081*/ uint32 last_id; /*0085*/ uint32 next_id; /*0089*/ uint32 cost2; -/*0093*/ uint8 unknown80[7]; +/*0093*/ uint8 unknown93; +/*0094*/ uint8 grant_only; // VetAAs, progression, etc +/*0095*/ uint8 unknown95; // 1 for skill cap increase AAs, Mystical Attuning, and RNG attack inc, doesn't seem to matter though +/*0096*/ uint32 expendable_charges; // max charges of the AA /*0100*/ uint32 aa_expansion; /*0104*/ uint32 special_category; -/*0108*/ uint32 unknown0096; +/*0108*/ uint8 shroud; +/*0109*/ uint8 unknown109; +/*0110*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter? +/*0111*/ uint8 unknown111; /*0112*/ uint32 total_abilities; /*0116*/ AA_Ability abilities[0]; }; @@ -4286,13 +4293,6 @@ struct AA_Action { /*16*/ }; -struct AA_Skills { //this should be removed and changed to AA_Array -/*00*/ uint32 aa_skill; // Total AAs Spent -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -/*12*/ -}; - struct AAExpUpdate_Struct { /*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability /*04*/ uint32 aapoints_unspent; @@ -4311,14 +4311,7 @@ struct AltAdvStats_Struct { }; struct PlayerAA_Struct { // Is this still used? - AA_Skills aa_list[MAX_PP_AA_ARRAY]; -}; - -struct AA_Values { -/*00*/ uint32 aa_skill; -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -/*12*/ + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct AATable_Struct { @@ -4328,7 +4321,7 @@ struct AATable_Struct { /*12*/ uint32 aa_spent_archetype; // Seen 40 /*16*/ uint32 aa_spent_class; // Seen 103 /*20*/ uint32 aa_spent_special; // Seen 0 -/*24*/ AA_Values aa_list[MAX_PP_AA_ARRAY]; +/*24*/ AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct Weather_Struct { diff --git a/common/patches/sod.cpp b/common/patches/sod.cpp index 1c0abf90a..1ee7d4b6c 100644 --- a/common/patches/sod.cpp +++ b/common/patches/sod.cpp @@ -447,7 +447,9 @@ namespace SoD OUT(type); OUT(spellid); OUT(damage); - eq->sequence = emu->sequence; + OUT(force) + OUT(meleepush_xy); + OUT(meleepush_z) FINISH_ENCODE(); } @@ -1557,6 +1559,7 @@ namespace SoD for (r = 0; r < MAX_PP_AA_ARRAY; r++) { OUT(aa_array[r].AA); OUT(aa_array[r].value); + OUT(aa_array[r].charges); } // OUT(unknown02220[4]); OUT(mana); @@ -1897,6 +1900,7 @@ namespace SoD OUT(cost2); eq->aa_expansion = emu->aa_expansion; eq->special_category = emu->special_category; + eq->expendable_charges = emu->special_category == 7 ? 1 : 0; // temp hack, this can actually be any number OUT(total_abilities); unsigned int r; for (r = 0; r < emu->total_abilities; r++) { @@ -1961,15 +1965,16 @@ namespace SoD for (int counter = 0; counter < character_count; ++counter) { emu_cse = (CharacterSelectEntry_Struct *)emu_ptr; - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // base address eq_cse->Level = emu_cse->Level; eq_cse->HairStyle = emu_cse->HairStyle; eq_cse->Gender = emu_cse->Gender; strcpy(eq_cse->Name, emu_cse->Name); - eq_ptr += strlen(eq_cse->Name); - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_ptr += strlen(emu_cse->Name); + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // offset address (base + name length offset) + eq_cse->Name[0] = '\0'; // (offset)eq_cse->Name[0] = (base)eq_cse->Name[strlen(emu_cse->Name)] eq_cse->Beard = emu_cse->Beard; eq_cse->HairColor = emu_cse->HairColor; @@ -2736,7 +2741,7 @@ namespace SoD VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown12 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown13 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown14 - Stance 64 = normal 4 = aggressive 40 = stun/mezzed + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown15 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown16 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown17 @@ -3350,7 +3355,7 @@ namespace SoD default: emu->command = eq->command; } - OUT(unknown); + IN(target); FINISH_DIRECT_DECODE(); } diff --git a/common/patches/sod_structs.h b/common/patches/sod_structs.h index 43a179ae9..ab70301da 100644 --- a/common/patches/sod_structs.h +++ b/common/patches/sod_structs.h @@ -286,7 +286,7 @@ struct Spawn_Struct /*0000*/ uint8 unknown12; /*0000*/ uint32 petOwnerId; /*0000*/ uint8 unknown13; -/*0000*/ uint32 unknown14; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed +/*0000*/ uint32 PlayerState; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed /*0000*/ uint32 unknown15; /*0000*/ uint32 unknown16; /*0000*/ uint32 unknown17; @@ -547,7 +547,7 @@ struct SpellBuff_Struct /*002*/ uint8 bard_modifier; /*003*/ uint8 effect; //not real /*004*/ uint32 spellid; -/*008*/ uint32 duration; +/*008*/ int32 duration; /*012*/ uint32 counters; /*016*/ uint32 unknown004; //Might need to be swapped with player_id /*020*/ uint32 player_id; //'global' ID of the caster, for wearoff messages @@ -564,7 +564,7 @@ struct SpellBuffFade_Struct { /*006*/ uint8 effect; /*007*/ uint8 unknown7; /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 unknown016; /*020*/ uint32 unknown020; //prolly global player ID /*024*/ uint32 playerId; // Player id who cast the buff @@ -666,7 +666,7 @@ struct AA_Array { uint32 AA; uint32 value; - uint32 unknown08; // Looks like AA_Array is now 12 bytes in Live + uint32 charges; // expendable }; @@ -1091,7 +1091,7 @@ struct TargetReject_Struct { struct PetCommand_Struct { /*000*/ uint32 command; -/*004*/ uint32 unknown; +/*004*/ uint32 target; }; /* @@ -1272,9 +1272,10 @@ struct CombatDamage_Struct /* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells /* 05 */ uint16 spellid; /* 07 */ int32 damage; -/* 11 */ float unknown11; // cd cc cc 3d -/* 15 */ float sequence; // see above notes in Action_Struct -/* 19 */ uint8 unknown19[9]; // was [9] +/* 11 */ float force; // cd cc cc 3d +/* 15 */ float meleepush_xy; // see above notes in Action_Struct +/* 19 */ float meleepush_z; +/* 23 */ uint8 unknown23[5]; // was [9] /* 28 */ }; @@ -3818,10 +3819,16 @@ struct SendAA_Struct { /*0069*/ uint32 last_id; /*0073*/ uint32 next_id; /*0077*/ uint32 cost2; -/*0081*/ uint8 unknown80[7]; +/*0081*/ uint8 unknown81; +/*0082*/ uint8 grant_only; // VetAAs, progression, etc +/*0083*/ uint8 unknown83; // 1 for skill cap increase AAs, Mystical Attuning, and RNG attack inc, doesn't seem to matter though +/*0084*/ uint32 expendable_charges; // max charges of the AA /*0088*/ uint32 aa_expansion; /*0092*/ uint32 special_category; -/*0096*/ uint32 unknown0096; +/*0096*/ uint8 shroud; +/*0097*/ uint8 unknown97; +/*0098*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter? +/*0099*/ uint8 unknown99; /*0100*/ uint32 total_abilities; /*0104*/ AA_Ability abilities[0]; }; @@ -3837,12 +3844,6 @@ struct AA_Action { /*12*/ uint32 exp_value; }; -struct AA_Skills { //this should be removed and changed to AA_Array -/*00*/ uint32 aa_skill; // Total AAs Spent -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -}; - struct AAExpUpdate_Struct { /*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability /*04*/ uint32 aapoints_unspent; @@ -3860,12 +3861,12 @@ struct AltAdvStats_Struct { }; struct PlayerAA_Struct { // Is this still used? - AA_Skills aa_list[MAX_PP_AA_ARRAY]; + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct AATable_Struct { /*00*/ int32 aa_spent; // Total AAs Spent -/*04*/ AA_Skills aa_list[MAX_PP_AA_ARRAY]; +/*04*/ AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct Weather_Struct { diff --git a/common/patches/sof.cpp b/common/patches/sof.cpp index 47a45809f..5db53b64f 100644 --- a/common/patches/sof.cpp +++ b/common/patches/sof.cpp @@ -427,7 +427,9 @@ namespace SoF OUT(type); OUT(spellid); OUT(damage); - eq->sequence = emu->sequence; + OUT(force) + OUT(meleepush_xy); + OUT(meleepush_z) FINISH_ENCODE(); } @@ -1215,6 +1217,7 @@ namespace SoF for (r = 0; r < MAX_PP_AA_ARRAY; r++) { OUT(aa_array[r].AA); OUT(aa_array[r].value); + OUT(aa_array[r].charges); } // OUT(unknown02220[4]); OUT(mana); @@ -1556,6 +1559,7 @@ namespace SoF OUT(cost2); eq->aa_expansion = emu->aa_expansion; eq->special_category = emu->special_category; + eq->expendable_charges = emu->special_category == 7 ? 1 : 0; // temp hack, this can actually be any number OUT(total_abilities); unsigned int r; for (r = 0; r < emu->total_abilities; r++) { @@ -1620,15 +1624,16 @@ namespace SoF for (int counter = 0; counter < character_count; ++counter) { emu_cse = (CharacterSelectEntry_Struct *)emu_ptr; - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // base address eq_cse->Level = emu_cse->Level; eq_cse->HairStyle = emu_cse->HairStyle; eq_cse->Gender = emu_cse->Gender; strcpy(eq_cse->Name, emu_cse->Name); - eq_ptr += strlen(eq_cse->Name); - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_ptr += strlen(emu_cse->Name); + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // offset address (base + name length offset) + eq_cse->Name[0] = '\0'; // (offset)eq_cse->Name[0] = (base)eq_cse->Name[strlen(emu_cse->Name)] eq_cse->Beard = emu_cse->Beard; eq_cse->HairColor = emu_cse->HairColor; @@ -2087,6 +2092,7 @@ namespace SoF eq->runspeed = emu->runspeed; eq->light = emu->light; eq->level = emu->level; + eq->PlayerState = emu->PlayerState; eq->lfg = emu->lfg; eq->hairstyle = emu->hairstyle; eq->haircolor = emu->haircolor; @@ -2688,7 +2694,7 @@ namespace SoF default: emu->command = eq->command; } - OUT(unknown); + IN(target); FINISH_DIRECT_DECODE(); } diff --git a/common/patches/sof_structs.h b/common/patches/sof_structs.h index 504c82277..55db16f4e 100644 --- a/common/patches/sof_structs.h +++ b/common/patches/sof_structs.h @@ -241,7 +241,8 @@ struct Spawn_Struct { /*0506*/ uint8 light; // Spawn's lightsource /*0507*/ uint8 unknown0507[4]; /*0511*/ uint8 level; // Spawn Level -/*0512*/ uint8 unknown0512[16]; +/*0512*/ uint32 PlayerState; +/*0516*/ uint8 unknown0516[12]; /*0528*/ uint8 lfg; /*0529*/ uint8 unknown0529[4]; /*0533*/ uint8 hairstyle; // Sets the style of hair @@ -525,7 +526,7 @@ struct SpellBuff_Struct /*002*/ uint8 bard_modifier; /*003*/ uint8 effect; //not real /*004*/ uint32 spellid; -/*008*/ uint32 duration; +/*008*/ int32 duration; /*012*/ uint32 counters; /*016*/ uint32 unknown004; //Might need to be swapped with player_id /*020*/ uint32 player_id; //'global' ID of the caster, for wearoff messages @@ -542,7 +543,7 @@ struct SpellBuffFade_Struct { /*006*/ uint8 effect; /*007*/ uint8 unknown7; /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 unknown016; /*020*/ uint32 unknown020; //prolly global player ID /*024*/ uint32 playerId; // Player id who cast the buff @@ -644,7 +645,7 @@ struct AA_Array { uint32 AA; uint32 value; - uint32 unknown08; // Looks like AA_Array is now 12 bytes in Live + uint32 charges; // expendable charges }; @@ -1068,7 +1069,7 @@ struct TargetReject_Struct { struct PetCommand_Struct { /*000*/ uint32 command; -/*004*/ uint32 unknown; +/*004*/ uint32 target; }; /* @@ -1249,9 +1250,10 @@ struct CombatDamage_Struct /* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells /* 05 */ uint16 spellid; /* 07 */ int32 damage; -/* 11 */ float unknown11; // cd cc cc 3d -/* 15 */ float sequence; // see above notes in Action_Struct -/* 19 */ uint8 unknown19[9]; // was [9] +/* 11 */ float force; // cd cc cc 3d +/* 15 */ float meleepush_xy; // see above notes in Action_Struct +/* 19 */ float meleepush_z; +/* 23 */ uint8 unknown23[5]; // was [9] /* 28 */ }; @@ -3681,10 +3683,14 @@ struct SendAA_Struct { /*0069*/ uint32 last_id; /*0073*/ uint32 next_id; /*0077*/ uint32 cost2; -/*0081*/ uint8 unknown80[7]; +/*0081*/ uint8 unknown81; +/*0082*/ uint8 grant_only; // VetAAs, progression, etc +/*0083*/ uint8 unknown83; // 1 for skill cap increase AAs, Mystical Attuning, and RNG attack inc, doesn't seem to matter though +/*0084*/ uint32 expendable_charges; // max charges of the AA /*0088*/ uint32 aa_expansion; /*0092*/ uint32 special_category; -/*0096*/ uint16 unknown0096; +/*0096*/ uint8 shroud; +/*0097*/ uint8 unknown97; /*0098*/ uint32 total_abilities; /*0102*/ AA_Ability abilities[0]; }; @@ -3700,12 +3706,6 @@ struct AA_Action { /*12*/ uint32 exp_value; }; -struct AA_Skills { //this should be removed and changed to AA_Array -/*00*/ uint32 aa_skill; // Total AAs Spent -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -}; - struct AAExpUpdate_Struct { /*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability /*04*/ uint32 aapoints_unspent; @@ -3723,12 +3723,12 @@ struct AltAdvStats_Struct { }; struct PlayerAA_Struct { - AA_Skills aa_list[MAX_PP_AA_ARRAY]; + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct AATable_Struct { /*00*/ int32 aa_spent; // Total AAs Spent -/*04*/ AA_Skills aa_list[MAX_PP_AA_ARRAY]; +/*04*/ AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct Weather_Struct { diff --git a/common/patches/titanium.cpp b/common/patches/titanium.cpp index 8a6241521..c0bf50978 100644 --- a/common/patches/titanium.cpp +++ b/common/patches/titanium.cpp @@ -122,7 +122,7 @@ namespace Titanium EAT_ENCODE(OP_GuildMemberLevelUpdate); // added ; EAT_ENCODE(OP_ZoneServerReady); // added ; - + ENCODE(OP_Action) { ENCODE_LENGTH_EXACT(Action_Struct); @@ -328,7 +328,7 @@ namespace Titanium { SETUP_VAR_ENCODE(ExpeditionCompass_Struct); ALLOC_VAR_ENCODE(structs::ExpeditionCompass_Struct, sizeof(structs::ExpeditionInfo_Struct) + sizeof(structs::ExpeditionCompassEntry_Struct) * emu->count); - + OUT(count); for (uint32 i = 0; i < emu->count; ++i) @@ -1100,8 +1100,8 @@ namespace Titanium unsigned int r; for (r = 0; r < structs::MAX_PP_AA_ARRAY; r++) { - OUT(aa_list[r].aa_skill); - OUT(aa_list[r].aa_value); + OUT(aa_list[r].AA); + OUT(aa_list[r].value); } FINISH_ENCODE(); @@ -1310,7 +1310,7 @@ namespace Titanium VARSTRUCT_ENCODE_TYPE(uint8, OutBuffer, emu->unknown12[11]); VARSTRUCT_ENCODE_STRING(OutBuffer, new_message.c_str()); - + delete[] __emu_buffer; dest->FastQueuePacket(&in, ack_req); } @@ -1353,7 +1353,7 @@ namespace Titanium InBuffer += strlen(InBuffer) + 1; memcpy(OutBuffer, InBuffer, sizeof(TaskDescriptionTrailer_Struct)); - + delete[] __emu_buffer; dest->FastQueuePacket(&in, ack_req); } @@ -1551,7 +1551,7 @@ namespace Titanium eq->beardcolor = emu->beardcolor; // eq->unknown0147[4] = emu->unknown0147[4]; eq->level = emu->level; - // eq->unknown0259[4] = emu->unknown0259[4]; + eq->PlayerState = emu->PlayerState; eq->beard = emu->beard; strcpy(eq->suffix, emu->suffix); eq->petOwnerId = emu->petOwnerId; @@ -1623,7 +1623,7 @@ namespace Titanium FINISH_DIRECT_DECODE(); } - + DECODE(OP_ApplyPoison) { DECODE_LENGTH_EXACT(structs::ApplyPoison_Struct); @@ -1944,7 +1944,7 @@ namespace Titanium default: emu->command = eq->command; } - OUT(unknown); + IN(target); FINISH_DIRECT_DECODE(); } @@ -2153,7 +2153,7 @@ namespace Titanium return serverSlot; // deprecated } - + static inline int16 ServerToTitaniumCorpseSlot(uint32 serverCorpseSlot) { //int16 TitaniumCorpse; @@ -2168,7 +2168,7 @@ namespace Titanium return titaniumSlot; // deprecated } - + static inline uint32 TitaniumToServerCorpseSlot(int16 titaniumCorpseSlot) { //uint32 ServerCorpse; diff --git a/common/patches/titanium_structs.h b/common/patches/titanium_structs.h index f00b943eb..2fac68ed6 100644 --- a/common/patches/titanium_structs.h +++ b/common/patches/titanium_structs.h @@ -212,7 +212,7 @@ struct Spawn_Struct { /*0146*/ uint8 beardcolor; // Beard color /*0147*/ uint8 unknown0147[4]; /*0151*/ uint8 level; // Spawn Level -/*0152*/ uint8 unknown0259[4]; // ***Placeholder +/*0152*/ uint32 PlayerState; // PlayerState controls some animation stuff /*0156*/ uint8 beard; // Beard style /*0157*/ char suffix[32]; // Player's suffix (of Veeshan, etc.) /*0189*/ uint32 petOwnerId; // If this is a pet, the spawn id of owner @@ -445,7 +445,7 @@ struct SpellBuff_Struct /*002*/ uint8 bard_modifier; /*003*/ uint8 effect; //not real /*004*/ uint32 spellid; -/*008*/ uint32 duration; +/*008*/ int32 duration; /*012*/ uint32 counters; /*016*/ uint32 player_id; //'global' ID of the caster, for wearoff messages }; @@ -457,7 +457,7 @@ struct SpellBuffFade_Struct { /*006*/ uint8 effect; /*007*/ uint8 unknown7; /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 unknown016; /*020*/ uint32 unknown020; //prolly global player ID /*024*/ uint32 slotid; @@ -950,7 +950,7 @@ struct TargetReject_Struct { struct PetCommand_Struct { /*000*/ uint32 command; -/*004*/ uint32 unknown; +/*004*/ uint32 target; }; /* @@ -1101,9 +1101,9 @@ struct CombatDamage_Struct /* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells /* 05 */ uint16 spellid; /* 07 */ uint32 damage; -/* 11 */ uint32 unknown11; -/* 15 */ uint32 sequence; // see above notes in Action_Struct -/* 19 */ uint32 unknown19; +/* 11 */ float force; +/* 15 */ float meleepush_xy; // see above notes in Action_Struct +/* 19 */ float meleepush_z; /* 23 */ }; @@ -3179,11 +3179,6 @@ struct AA_Action { /*12*/ uint32 exp_value; }; -struct AA_Skills { //this should be removed and changed to AA_Array -/*00*/ uint32 aa_skill; -/*04*/ uint32 aa_value; -}; - struct AAExpUpdate_Struct { /*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability /*04*/ uint32 aapoints_unspent; @@ -3201,11 +3196,11 @@ struct AltAdvStats_Struct { }; struct PlayerAA_Struct { - AA_Skills aa_list[MAX_PP_AA_ARRAY]; + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct AATable_Struct { - AA_Skills aa_list[MAX_PP_AA_ARRAY]; + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct Weather_Struct { diff --git a/common/patches/uf.cpp b/common/patches/uf.cpp index 421a48d6a..b14d1b8b0 100644 --- a/common/patches/uf.cpp +++ b/common/patches/uf.cpp @@ -387,7 +387,7 @@ namespace UF __packet->WriteUInt32(emu->entries[i].num_hits); __packet->WriteString(""); } - __packet->WriteUInt8(!emu->all_buffs); + __packet->WriteUInt8(emu->type); FINISH_ENCODE(); /* @@ -582,7 +582,9 @@ namespace UF OUT(type); OUT(spellid); OUT(damage); - eq->sequence = emu->sequence; + OUT(force) + OUT(meleepush_xy); + OUT(meleepush_z) FINISH_ENCODE(); } @@ -856,8 +858,8 @@ namespace UF // field to be set to (float)255.0 to appear at all, and also the size field below to be 5, to be the correct size. I think SoD has the same // issue. VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // Unknown, observed 0 - VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, 0); // This appears to be the size field. + VARSTRUCT_ENCODE_TYPE(uint32, OutBuffer, emu->solidtype); // Unknown, observed 0 + VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->size != 0 && (float)emu->size < 5000.f ? (float)((float)emu->size / 100.0f) : 1.f ); // This appears to be the size field. Hackish logic because some PEQ DB items were corrupt. VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->y); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->x); VARSTRUCT_ENCODE_TYPE(float, OutBuffer, emu->z); @@ -1806,6 +1808,7 @@ namespace UF for (r = 0; r < MAX_PP_AA_ARRAY; r++) { OUT(aa_array[r].AA); OUT(aa_array[r].value); + OUT(aa_array[r].charges); } // OUT(unknown02220[4]); OUT(mana); @@ -2133,9 +2136,9 @@ namespace UF for (uint32 i = 0; i < MAX_PP_AA_ARRAY; ++i) { - eq->aa_list[i].aa_skill = emu->aa_list[i].aa_skill; - eq->aa_list[i].aa_value = emu->aa_list[i].aa_value; - eq->aa_list[i].unknown08 = emu->aa_list[i].unknown08; + eq->aa_list[i].AA = emu->aa_list[i].AA; + eq->aa_list[i].value = emu->aa_list[i].value; + eq->aa_list[i].charges = emu->aa_list[i].charges; } FINISH_ENCODE(); @@ -2180,6 +2183,7 @@ namespace UF OUT(cost2); eq->aa_expansion = emu->aa_expansion; eq->special_category = emu->special_category; + eq->expendable_charges = emu->special_category == 7 ? 1 : 0; // temp hack, this can actually be any number OUT(total_abilities); unsigned int r; for (r = 0; r < emu->total_abilities; r++) { @@ -2214,7 +2218,7 @@ namespace UF FINISH_ENCODE(); return; } - + unsigned char *emu_ptr = __emu_buffer; emu_ptr += sizeof(CharacterSelect_Struct); CharacterSelectEntry_Struct *emu_cse = (CharacterSelectEntry_Struct *)nullptr; @@ -2252,15 +2256,16 @@ namespace UF for (int counter = 0; counter < character_count; ++counter) { emu_cse = (CharacterSelectEntry_Struct *)emu_ptr; - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // base address eq_cse->Level = emu_cse->Level; eq_cse->HairStyle = emu_cse->HairStyle; eq_cse->Gender = emu_cse->Gender; strcpy(eq_cse->Name, emu_cse->Name); - eq_ptr += strlen(eq_cse->Name); - eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; + eq_ptr += strlen(emu_cse->Name); + eq_cse = (structs::CharacterSelectEntry_Struct *)eq_ptr; // offset address (base + name length offset) + eq_cse->Name[0] = '\0'; // (offset)eq_cse->Name[0] = (base)eq_cse->Name[strlen(emu_cse->Name)] eq_cse->Beard = emu_cse->Beard; eq_cse->HairColor = emu_cse->HairColor; @@ -3004,7 +3009,7 @@ namespace UF VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown12 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // unknown13 - VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown14 - Stance 64 = normal 4 = aggressive 40 = stun/mezzed + VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown15 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown16 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // unknown17 @@ -3357,7 +3362,7 @@ namespace UF IN(type); IN(spellid); IN(damage); - emu->sequence = eq->sequence; + IN(meleepush_xy); FINISH_DIRECT_DECODE(); } @@ -3600,7 +3605,7 @@ namespace UF SETUP_DIRECT_DECODE(PetCommand_Struct, structs::PetCommand_Struct); IN(command); - IN(unknown); + IN(target); FINISH_DIRECT_DECODE(); } @@ -3862,7 +3867,7 @@ namespace UF UF::structs::ItemSerializationHeaderFinish hdrf; hdrf.ornamentIcon = ornaIcon; - hdrf.unknown060 = 0; //This is Always 0.. or it breaks shit.. + hdrf.unknown060 = 0; //This is Always 0.. or it breaks shit.. hdrf.unknown061 = 0; //possibly ornament / special ornament hdrf.isCopied = 0; //Flag for item to be 'Copied' hdrf.ItemClass = item->ItemClass; diff --git a/common/patches/uf_structs.h b/common/patches/uf_structs.h index 42f6ccef6..6508bce55 100644 --- a/common/patches/uf_structs.h +++ b/common/patches/uf_structs.h @@ -286,7 +286,7 @@ struct Spawn_Struct /*0000*/ uint8 unknown12; /*0000*/ uint32 petOwnerId; /*0000*/ uint8 unknown13; -/*0000*/ uint32 unknown14; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed +/*0000*/ uint32 PlayerState; // Stance 64 = normal 4 = aggressive 40 = stun/mezzed /*0000*/ uint32 unknown15; /*0000*/ uint32 unknown16; /*0000*/ uint32 unknown17; @@ -551,7 +551,7 @@ struct SpellBuff_Struct /*003*/ uint8 effect; // not real /*004*/ uint32 unknown004; // Seen 1 for no buff /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 unknown016; /*020*/ uint32 player_id; // 'global' ID of the caster, for wearoff messages /*024*/ uint32 counters; @@ -568,7 +568,7 @@ struct SpellBuffFade_Struct_Underfoot { /*007*/ uint8 unknown7; /*008*/ float unknown008; /*012*/ uint32 spellid; -/*016*/ uint32 duration; +/*016*/ int32 duration; /*020*/ uint32 num_hits; /*024*/ uint32 playerId; // Global player ID? /*028*/ uint32 unknown020; @@ -585,7 +585,7 @@ struct SpellBuffFade_Struct { /*006*/ uint8 effect; /*007*/ uint8 unknown7; /*008*/ uint32 spellid; -/*012*/ uint32 duration; +/*012*/ int32 duration; /*016*/ uint32 unknown016; /*020*/ uint32 unknown020; // Global player ID? /*024*/ uint32 playerId; // Player id who cast the buff @@ -713,7 +713,7 @@ struct AA_Array { uint32 AA; uint32 value; - uint32 unknown08; // Looks like AA_Array is now 12 bytes in Underfoot + uint32 charges; // expendable }; @@ -1146,7 +1146,7 @@ struct TargetReject_Struct { struct PetCommand_Struct { /*000*/ uint32 command; -/*004*/ uint32 unknown; +/*004*/ uint32 target; }; /* @@ -1330,9 +1330,10 @@ struct CombatDamage_Struct /* 04 */ uint8 type; //slashing, etc. 231 (0xE7) for spells /* 05 */ uint16 spellid; /* 07 */ int32 damage; -/* 11 */ float unknown11; // cd cc cc 3d -/* 15 */ float sequence; // see above notes in Action_Struct -/* 19 */ uint8 unknown19[9]; // was [9] +/* 11 */ float force; // cd cc cc 3d +/* 15 */ float meleepush_xy; // see above notes in Action_Struct +/* 19 */ float meleepush_z; +/* 23 */ uint8 unknown23[5]; // was [9] /* 28 */ }; @@ -2187,7 +2188,7 @@ struct GroupFollow_Struct { // Underfoot Follow Struct struct InspectBuffs_Struct { /*000*/ uint32 spell_id[BUFF_COUNT]; /*100*/ uint32 filler100[5]; // BUFF_COUNT is really 30... -/*120*/ uint32 tics_remaining[BUFF_COUNT]; +/*120*/ int32 tics_remaining[BUFF_COUNT]; /*220*/ uint32 filler220[5]; // BUFF_COUNT is really 30... }; @@ -3891,10 +3892,16 @@ struct SendAA_Struct { /*0069*/ uint32 last_id; /*0073*/ uint32 next_id; /*0077*/ uint32 cost2; -/*0081*/ uint8 unknown80[7]; +/*0081*/ uint8 unknown81; +/*0082*/ uint8 grant_only; // VetAAs, progression, etc +/*0083*/ uint8 unknown83; // 1 for skill cap increase AAs, Mystical Attuning, and RNG attack inc, doesn't seem to matter though +/*0084*/ uint32 expendable_charges; // max charges of the AA /*0088*/ uint32 aa_expansion; /*0092*/ uint32 special_category; -/*0096*/ uint32 unknown0096; +/*0096*/ uint8 shroud; +/*0097*/ uint8 unknown97; +/*0098*/ uint8 layonhands; // 1 for lay on hands -- doesn't seem to matter? +/*0099*/ uint8 unknown99; /*0100*/ uint32 total_abilities; /*0104*/ AA_Ability abilities[0]; }; @@ -3910,11 +3917,6 @@ struct AA_Action { /*12*/ uint32 exp_value; }; -struct AA_Skills { //this should be removed and changed to AA_Array -/*00*/ uint32 aa_skill; // Total AAs Spent -/*04*/ uint32 aa_value; -/*08*/ uint32 unknown08; -}; struct AAExpUpdate_Struct { /*00*/ uint32 unknown00; //seems to be a value from AA_Action.ability @@ -3933,7 +3935,7 @@ struct AltAdvStats_Struct { }; struct PlayerAA_Struct { // Is this still used? - AA_Skills aa_list[MAX_PP_AA_ARRAY]; + AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct AATable_Struct { @@ -3943,7 +3945,7 @@ struct AATable_Struct { /*12*/ int32 unknown012; /*16*/ int32 unknown016; /*20*/ int32 unknown020; -/*24*/ AA_Skills aa_list[MAX_PP_AA_ARRAY]; +/*24*/ AA_Array aa_list[MAX_PP_AA_ARRAY]; }; struct Weather_Struct { diff --git a/common/ptimer.h b/common/ptimer.h index d12b6779d..8a559a148 100644 --- a/common/ptimer.h +++ b/common/ptimer.h @@ -39,7 +39,8 @@ enum { //values for pTimerType pTimerDisciplineReuseStart = 14, pTimerDisciplineReuseEnd = 24, pTimerCombatAbility = 25, - pTimerBeggingPickPocket = 26, + pTimerCombatAbility2 = 26, // RoF2+ Tiger Claw is unlinked from other monk skills, generic in case other classes ever need it + pTimerBeggingPickPocket = 27, pTimerLayHands = 87, //these IDs are used by client too pTimerHarmTouch = 89, //so dont change them diff --git a/common/races.h b/common/races.h index 2c360e908..68add5cca 100644 --- a/common/races.h +++ b/common/races.h @@ -47,6 +47,8 @@ #define IKSAR 128 #define VAHSHIR 130 #define CONTROLLED_BOAT 141 +#define MINOR_ILL_OBJ 142 +#define TREE 143 #define IKSAR_SKELETON 161 #define FROGLOK 330 #define FROGLOK2 74 // Not sure why /who all reports race as 74 for frogloks diff --git a/common/ruletypes.h b/common/ruletypes.h index c8f9557f5..1919c7168 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -20,597 +20,604 @@ -RULE_CATEGORY( Character ) -RULE_INT ( Character, MaxLevel, 65 ) -RULE_BOOL ( Character, PerCharacterQglobalMaxLevel, false) // This will check for qglobal 'CharMaxLevel' character qglobal (Type 5), if player tries to level beyond that point, it will not go beyond that level -RULE_INT ( Character, MaxExpLevel, 0 ) //Sets the Max Level attainable via Experience -RULE_INT ( Character, DeathExpLossLevel, 10 ) // Any level greater than this will lose exp on death -RULE_INT ( Character, DeathExpLossMaxLevel, 255 ) // Any level greater than this will no longer lose exp on death -RULE_INT ( Character, DeathItemLossLevel, 10 ) -RULE_INT ( Character, DeathExpLossMultiplier, 3) //Adjust how much exp is lost -RULE_BOOL( Character, UseDeathExpLossMult, false ) //Adjust to use the above multiplier or to use code default. -RULE_INT ( Character, CorpseDecayTimeMS, 10800000 ) -RULE_INT ( Character, CorpseResTimeMS, 10800000 ) // time before cant res corpse(3 hours) -RULE_BOOL( Character, LeaveCorpses, true ) -RULE_BOOL( Character, LeaveNakedCorpses, false ) -RULE_INT ( Character, MaxDraggedCorpses, 2 ) -RULE_REAL( Character, DragCorpseDistance, 400) // If the corpse is <= this distance from the player, it won't move -RULE_REAL( Character, ExpMultiplier, 0.5 ) -RULE_REAL( Character, AAExpMultiplier, 0.5 ) -RULE_REAL( Character, GroupExpMultiplier, 0.5 ) -RULE_REAL( Character, RaidExpMultiplier, 0.2 ) -RULE_BOOL( Character, UseXPConScaling, true ) -RULE_INT ( Character, LightBlueModifier, 40 ) -RULE_INT ( Character, BlueModifier, 90 ) -RULE_INT ( Character, WhiteModifier, 100 ) -RULE_INT ( Character, YellowModifier, 125 ) -RULE_INT ( Character, RedModifier, 150 ) -RULE_INT ( Character, AutosaveIntervalS, 300 ) //0=disabled -RULE_INT ( Character, HPRegenMultiplier, 100) -RULE_INT ( Character, ManaRegenMultiplier, 100) -RULE_INT ( Character, EnduranceRegenMultiplier, 100) -RULE_INT ( Character, ConsumptionMultiplier, 100) //item's hunger restored = this value * item's food level, 100 = normal, 50 = people eat 2x as fast, 200 = people eat 2x as slow -RULE_BOOL( Character, HealOnLevel, false) -RULE_BOOL( Character, FeignKillsPet, false) -RULE_INT ( Character, ItemManaRegenCap, 15) -RULE_INT ( Character, ItemHealthRegenCap, 35) -RULE_INT ( Character, ItemDamageShieldCap, 30) -RULE_INT ( Character, ItemAccuracyCap, 150) -RULE_INT ( Character, ItemAvoidanceCap, 100) -RULE_INT ( Character, ItemCombatEffectsCap, 100) -RULE_INT ( Character, ItemShieldingCap, 35) -RULE_INT ( Character, ItemSpellShieldingCap, 35) -RULE_INT ( Character, ItemDoTShieldingCap, 35) -RULE_INT ( Character, ItemStunResistCap, 35) -RULE_INT ( Character, ItemStrikethroughCap, 35) -RULE_INT ( Character, ItemATKCap, 250) -RULE_INT ( Character, ItemHealAmtCap, 250) -RULE_INT ( Character, ItemSpellDmgCap, 250) -RULE_INT ( Character, ItemClairvoyanceCap, 250) -RULE_INT ( Character, ItemDSMitigationCap, 50) -RULE_INT ( Character, ItemEnduranceRegenCap, 15) -RULE_INT ( Character, ItemExtraDmgCap, 150) // Cap for bonuses to melee skills like Bash, Frenzy, etc -RULE_INT ( Character, HasteCap, 100) // Haste cap for non-v3(overhaste) haste. -RULE_INT ( Character, SkillUpModifier, 100) //skill ups are at 100% -RULE_BOOL ( Character, SharedBankPlat, false) //off by default to prevent duping for now -RULE_BOOL ( Character, BindAnywhere, false) -RULE_INT ( Character, RestRegenPercent, 0) // Set to >0 to enable rest state bonus HP and mana regen. -RULE_INT ( Character, RestRegenTimeToActivate, 30) // Time in seconds for rest state regen to kick in. -RULE_INT ( Character, RestRegenRaidTimeToActivate, 300) // Time in seconds for rest state regen to kick in with a raid target. -RULE_BOOL ( Character, RestRegenEndurance, false) // Whether rest regen will work for endurance or not. -RULE_INT ( Character, KillsPerGroupLeadershipAA, 250) // Number of dark blues or above per Group Leadership AA -RULE_INT ( Character, KillsPerRaidLeadershipAA, 250) // Number of dark blues or above per Raid Leadership AA -RULE_INT ( Character, MaxFearDurationForPlayerCharacter, 4) //4 tics, each tic calculates every 6 seconds. -RULE_INT ( Character, MaxCharmDurationForPlayerCharacter, 15) -RULE_INT ( Character, BaseHPRegenBonusRaces, 4352) //a bitmask of race(s) that receive the regen bonus. Iksar (4096) & Troll (256) = 4352. see common/races.h for the bitmask values -RULE_BOOL ( Character, SoDClientUseSoDHPManaEnd, false) // Setting this to true will allow SoD clients to use the SoD HP/Mana/End formulas and previous clients will use the old formulas -RULE_BOOL ( Character, UseRaceClassExpBonuses, true) // Setting this to true will enable Class and Racial experience rate bonuses -RULE_BOOL ( Character, RespawnFromHover, false) // Use Respawn window, or not. -RULE_INT ( Character, RespawnFromHoverTimer, 300) // Respawn Window countdown timer, in SECONDS -RULE_BOOL ( Character, UseNewStatsWindow, true) // New stats window shows everything -RULE_BOOL ( Character, ItemCastsUseFocus, false) // If true, this allows item clickies to use focuses that have limited max levels on them -RULE_INT ( Character, MinStatusForNoDropExemptions, 80) // This allows status x and higher to trade no drop items. -RULE_INT ( Character, SkillCapMaxLevel, 75 ) // Sets the Max Level used for Skill Caps (from skill_caps table). -1 makes it use MaxLevel rule value. It is set to 75 because PEQ only has skillcaps up to that level, and grabbing the players' skill past 75 will return 0, breaking all skills past that level. This helps servers with obsurd level caps (75+ level cap) function without any modifications. -RULE_INT ( Character, StatCap, 0 ) -RULE_BOOL ( Character, CheckCursorEmptyWhenLooting, true ) // If true, a player cannot loot a corpse (player or NPC) with an item on their cursor -RULE_BOOL ( Character, MaintainIntoxicationAcrossZones, true ) // If true, alcohol effects are maintained across zoning and logging out/in. -RULE_BOOL ( Character, EnableDiscoveredItems, true ) // If enabled, it enables EVENT_DISCOVER_ITEM and also saves character names and timestamps for the first time an item is discovered. -RULE_BOOL ( Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients. -RULE_BOOL ( Character, KeepLevelOverMax, false) // Don't delevel a character that has somehow gone over the level cap -RULE_INT ( Character, FoodLossPerUpdate, 35) // How much food/water you lose per stamina update -RULE_INT ( Character, BaseInstrumentSoftCap, 36) // Softcap for instrument mods, 36 commonly referred to as "3.6" as well. -RULE_INT ( Character, BaseRunSpeedCap, 158) // Base Run Speed Cap, on live it's 158% which will give you a runspeed of 1.580 hard capped to 225. -RULE_INT ( Character, OrnamentationAugmentType, 20) //Ornamentation Augment Type +RULE_CATEGORY(Character) +RULE_INT(Character, MaxLevel, 65) +RULE_BOOL(Character, PerCharacterQglobalMaxLevel, false) // This will check for qglobal 'CharMaxLevel' character qglobal (Type 5), if player tries to level beyond that point, it will not go beyond that level +RULE_INT(Character, MaxExpLevel, 0) //Sets the Max Level attainable via Experience +RULE_INT(Character, DeathExpLossLevel, 10) // Any level greater than this will lose exp on death +RULE_INT(Character, DeathExpLossMaxLevel, 255) // Any level greater than this will no longer lose exp on death +RULE_INT(Character, DeathItemLossLevel, 10) +RULE_INT(Character, DeathExpLossMultiplier, 3) //Adjust how much exp is lost +RULE_BOOL(Character, UseDeathExpLossMult, false) //Adjust to use the above multiplier or to use code default. +RULE_INT(Character, CorpseDecayTimeMS, 10800000) +RULE_INT(Character, CorpseResTimeMS, 10800000) // time before cant res corpse(3 hours) +RULE_BOOL(Character, LeaveCorpses, true) +RULE_BOOL(Character, LeaveNakedCorpses, false) +RULE_INT(Character, MaxDraggedCorpses, 2) +RULE_REAL(Character, DragCorpseDistance, 400) // If the corpse is <= this distance from the player, it won't move +RULE_REAL(Character, ExpMultiplier, 0.5) +RULE_REAL(Character, AAExpMultiplier, 0.5) +RULE_REAL(Character, GroupExpMultiplier, 0.5) +RULE_REAL(Character, RaidExpMultiplier, 0.2) +RULE_BOOL(Character, UseXPConScaling, true) +RULE_INT(Character, LightBlueModifier, 40) +RULE_INT(Character, BlueModifier, 90) +RULE_INT(Character, WhiteModifier, 100) +RULE_INT(Character, YellowModifier, 125) +RULE_INT(Character, RedModifier, 150) +RULE_INT(Character, AutosaveIntervalS, 300) //0=disabled +RULE_INT(Character, HPRegenMultiplier, 100) +RULE_INT(Character, ManaRegenMultiplier, 100) +RULE_INT(Character, EnduranceRegenMultiplier, 100) +RULE_INT(Character, ConsumptionMultiplier, 100) //item's hunger restored = this value * item's food level, 100 = normal, 50 = people eat 2x as fast, 200 = people eat 2x as slow +RULE_BOOL(Character, HealOnLevel, false) +RULE_BOOL(Character, FeignKillsPet, false) +RULE_INT(Character, ItemManaRegenCap, 15) +RULE_INT(Character, ItemHealthRegenCap, 35) +RULE_INT(Character, ItemDamageShieldCap, 30) +RULE_INT(Character, ItemAccuracyCap, 150) +RULE_INT(Character, ItemAvoidanceCap, 100) +RULE_INT(Character, ItemCombatEffectsCap, 100) +RULE_INT(Character, ItemShieldingCap, 35) +RULE_INT(Character, ItemSpellShieldingCap, 35) +RULE_INT(Character, ItemDoTShieldingCap, 35) +RULE_INT(Character, ItemStunResistCap, 35) +RULE_INT(Character, ItemStrikethroughCap, 35) +RULE_INT(Character, ItemATKCap, 250) +RULE_INT(Character, ItemHealAmtCap, 250) +RULE_INT(Character, ItemSpellDmgCap, 250) +RULE_INT(Character, ItemClairvoyanceCap, 250) +RULE_INT(Character, ItemDSMitigationCap, 50) +RULE_INT(Character, ItemEnduranceRegenCap, 15) +RULE_INT(Character, ItemExtraDmgCap, 150) // Cap for bonuses to melee skills like Bash, Frenzy, etc +RULE_INT(Character, HasteCap, 100) // Haste cap for non-v3(overhaste) haste. +RULE_INT(Character, SkillUpModifier, 100) //skill ups are at 100% +RULE_BOOL(Character, SharedBankPlat, false) //off by default to prevent duping for now +RULE_BOOL(Character, BindAnywhere, false) +RULE_INT(Character, RestRegenPercent, 0) // Set to >0 to enable rest state bonus HP and mana regen. +RULE_INT(Character, RestRegenTimeToActivate, 30) // Time in seconds for rest state regen to kick in. +RULE_INT(Character, RestRegenRaidTimeToActivate, 300) // Time in seconds for rest state regen to kick in with a raid target. +RULE_BOOL(Character, RestRegenEndurance, false) // Whether rest regen will work for endurance or not. +RULE_INT(Character, KillsPerGroupLeadershipAA, 250) // Number of dark blues or above per Group Leadership AA +RULE_INT(Character, KillsPerRaidLeadershipAA, 250) // Number of dark blues or above per Raid Leadership AA +RULE_INT(Character, MaxFearDurationForPlayerCharacter, 4) //4 tics, each tic calculates every 6 seconds. +RULE_INT(Character, MaxCharmDurationForPlayerCharacter, 15) +RULE_INT(Character, BaseHPRegenBonusRaces, 4352) //a bitmask of race(s) that receive the regen bonus. Iksar (4096) & Troll (256) = 4352. see common/races.h for the bitmask values +RULE_BOOL(Character, SoDClientUseSoDHPManaEnd, false) // Setting this to true will allow SoD clients to use the SoD HP/Mana/End formulas and previous clients will use the old formulas +RULE_BOOL(Character, UseRaceClassExpBonuses, true) // Setting this to true will enable Class and Racial experience rate bonuses +RULE_BOOL(Character, RespawnFromHover, false) // Use Respawn window, or not. +RULE_INT(Character, RespawnFromHoverTimer, 300) // Respawn Window countdown timer, in SECONDS +RULE_BOOL(Character, UseNewStatsWindow, true) // New stats window shows everything +RULE_BOOL(Character, ItemCastsUseFocus, false) // If true, this allows item clickies to use focuses that have limited max levels on them +RULE_INT(Character, MinStatusForNoDropExemptions, 80) // This allows status x and higher to trade no drop items. +RULE_INT(Character, SkillCapMaxLevel, 75) // Sets the Max Level used for Skill Caps (from skill_caps table). -1 makes it use MaxLevel rule value. It is set to 75 because PEQ only has skillcaps up to that level, and grabbing the players' skill past 75 will return 0, breaking all skills past that level. This helps servers with obsurd level caps (75+ level cap) function without any modifications. +RULE_INT(Character, StatCap, 0) +RULE_BOOL(Character, CheckCursorEmptyWhenLooting, true) // If true, a player cannot loot a corpse (player or NPC) with an item on their cursor +RULE_BOOL(Character, MaintainIntoxicationAcrossZones, true) // If true, alcohol effects are maintained across zoning and logging out/in. +RULE_BOOL(Character, EnableDiscoveredItems, true) // If enabled, it enables EVENT_DISCOVER_ITEM and also saves character names and timestamps for the first time an item is discovered. +RULE_BOOL(Character, EnableXTargetting, true) // Enable Extended Targetting Window, for users with UF and later clients. +RULE_BOOL(Character, KeepLevelOverMax, false) // Don't delevel a character that has somehow gone over the level cap +RULE_INT(Character, FoodLossPerUpdate, 35) // How much food/water you lose per stamina update +RULE_INT(Character, BaseInstrumentSoftCap, 36) // Softcap for instrument mods, 36 commonly referred to as "3.6" as well. +RULE_INT(Character, BaseRunSpeedCap, 158) // Base Run Speed Cap, on live it's 158% which will give you a runspeed of 1.580 hard capped to 225. +RULE_INT(Character, OrnamentationAugmentType, 20) //Ornamentation Augment Type RULE_REAL(Character, EnvironmentDamageMulipliter, 1) RULE_BOOL(Character, UnmemSpellsOnDeath, true) -RULE_INT ( Character, TradeskillUpAlchemy, 2 ) // Alchemy skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpBaking, 2 ) // Baking skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpBlacksmithing, 2 ) // Blacksmithing skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpBrewing, 3 ) // Brewing skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpFletching, 2 ) // Fletching skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpJewelcrafting, 2 ) // Jewelcrafting skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpMakePoison, 2 ) // Make Poison skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpPottery, 4 ) // Pottery skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpResearch, 1 ) // Research skillup rate adjust. Lower is faster. -RULE_INT ( Character, TradeskillUpTinkering, 2 ) // Tinkering skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpAlchemy, 2) // Alchemy skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpBaking, 2) // Baking skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpBlacksmithing, 2) // Blacksmithing skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpBrewing, 3) // Brewing skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpFletching, 2) // Fletching skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpJewelcrafting, 2) // Jewelcrafting skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpMakePoison, 2) // Make Poison skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpPottery, 4) // Pottery skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpResearch, 1) // Research skillup rate adjust. Lower is faster. +RULE_INT(Character, TradeskillUpTinkering, 2) // Tinkering skillup rate adjust. Lower is faster. RULE_CATEGORY_END() -RULE_CATEGORY( Mercs ) -RULE_INT (Mercs, SuspendIntervalMS, 10000) -RULE_INT (Mercs, UpkeepIntervalMS, 180000) -RULE_INT (Mercs, SuspendIntervalS, 10) -RULE_INT (Mercs, UpkeepIntervalS, 180) -RULE_BOOL (Mercs, AllowMercs, false) -RULE_BOOL (Mercs, ChargeMercPurchaseCost, false) -RULE_BOOL (Mercs, ChargeMercUpkeepCost, false) -RULE_INT (Mercs, AggroRadius, 100) // Determines the distance from which a merc will aggro group member's target(also used to determine the distance at which a healer merc will begin healing a group member) -RULE_INT (Mercs, AggroRadiusPuller, 25) // Determines the distance from which a merc will aggro group member's target, if they have the group role of puller (also used to determine the distance at which a healer merc will begin healing a group member, if they have the group role of puller) -RULE_INT (Mercs, ResurrectRadius, 50) // Determines the distance from which a healer merc will attempt to resurrect a group member's corpse -RULE_INT (Mercs, ScaleRate, 100) +RULE_CATEGORY(Mercs) +RULE_INT(Mercs, SuspendIntervalMS, 10000) +RULE_INT(Mercs, UpkeepIntervalMS, 180000) +RULE_INT(Mercs, SuspendIntervalS, 10) +RULE_INT(Mercs, UpkeepIntervalS, 180) +RULE_BOOL(Mercs, AllowMercs, false) +RULE_BOOL(Mercs, ChargeMercPurchaseCost, false) +RULE_BOOL(Mercs, ChargeMercUpkeepCost, false) +RULE_INT(Mercs, AggroRadius, 100) // Determines the distance from which a merc will aggro group member's target(also used to determine the distance at which a healer merc will begin healing a group member) +RULE_INT(Mercs, AggroRadiusPuller, 25) // Determines the distance from which a merc will aggro group member's target, if they have the group role of puller (also used to determine the distance at which a healer merc will begin healing a group member, if they have the group role of puller) +RULE_INT(Mercs, ResurrectRadius, 50) // Determines the distance from which a healer merc will attempt to resurrect a group member's corpse +RULE_INT(Mercs, ScaleRate, 100) RULE_CATEGORY_END() -RULE_CATEGORY( Guild ) -RULE_INT ( Guild, MaxMembers, 2048 ) -RULE_BOOL ( Guild, PlayerCreationAllowed, false) // Allow players to create a guild using the window in Underfoot+ -RULE_INT ( Guild, PlayerCreationLimit, 1) // Only allow use of the UF+ window if the account has < than this number of guild leaders on it -RULE_INT ( Guild, PlayerCreationRequiredStatus, 0) // Required admin status. -RULE_INT ( Guild, PlayerCreationRequiredLevel, 0) // Required Level of the player attempting to create the guild. -RULE_INT ( Guild, PlayerCreationRequiredTime, 0) // Required Time Entitled On Account (in Minutes) to create the guild. +RULE_CATEGORY(Guild) +RULE_INT(Guild, MaxMembers, 2048) +RULE_BOOL(Guild, PlayerCreationAllowed, false) // Allow players to create a guild using the window in Underfoot+ +RULE_INT(Guild, PlayerCreationLimit, 1) // Only allow use of the UF+ window if the account has < than this number of guild leaders on it +RULE_INT(Guild, PlayerCreationRequiredStatus, 0) // Required admin status. +RULE_INT(Guild, PlayerCreationRequiredLevel, 0) // Required Level of the player attempting to create the guild. +RULE_INT(Guild, PlayerCreationRequiredTime, 0) // Required Time Entitled On Account (in Minutes) to create the guild. RULE_CATEGORY_END() -RULE_CATEGORY( Skills ) -RULE_INT ( Skills, MaxTrainTradeskills, 21 ) -RULE_BOOL ( Skills, UseLimitTradeskillSearchSkillDiff, true ) -RULE_INT ( Skills, MaxTradeskillSearchSkillDiff, 50 ) -RULE_INT ( Skills, MaxTrainSpecializations, 50 ) // Max level a GM trainer will train casting specializations -RULE_INT ( Skills, SwimmingStartValue, 100 ) -RULE_BOOL ( Skills, TrainSenseHeading, false ) -RULE_INT ( Skills, SenseHeadingStartValue, 200 ) +RULE_CATEGORY(Skills) +RULE_INT(Skills, MaxTrainTradeskills, 21) +RULE_BOOL(Skills, UseLimitTradeskillSearchSkillDiff, true) +RULE_INT(Skills, MaxTradeskillSearchSkillDiff, 50) +RULE_INT(Skills, MaxTrainSpecializations, 50) // Max level a GM trainer will train casting specializations +RULE_INT(Skills, SwimmingStartValue, 100) +RULE_BOOL(Skills, TrainSenseHeading, false) +RULE_INT(Skills, SenseHeadingStartValue, 200) RULE_CATEGORY_END() -RULE_CATEGORY( Pets ) -RULE_REAL( Pets, AttackCommandRange, 150 ) -RULE_BOOL( Pets, UnTargetableSwarmPet, false ) +RULE_CATEGORY(Pets) +RULE_REAL(Pets, AttackCommandRange, 150) +RULE_BOOL(Pets, UnTargetableSwarmPet, false) +RULE_REAL(Pets, PetPowerLevelCap, 10) // Max number of levels your pet can go up with pet power RULE_CATEGORY_END() -RULE_CATEGORY( GM ) -RULE_INT ( GM, MinStatusToSummonItem, 250) -RULE_INT ( GM, MinStatusToZoneAnywhere, 250 ) +RULE_CATEGORY(GM) +RULE_INT(GM, MinStatusToSummonItem, 250) +RULE_INT(GM, MinStatusToZoneAnywhere, 250) RULE_CATEGORY_END() -RULE_CATEGORY( World ) -RULE_INT ( World, ZoneAutobootTimeoutMS, 60000 ) -RULE_INT ( World, ClientKeepaliveTimeoutMS, 65000 ) -RULE_BOOL ( World, UseBannedIPsTable, false ) // Toggle whether or not to check incoming client connections against the Banned_IPs table. Set this value to false to disable this feature. -RULE_BOOL ( World, EnableTutorialButton, true) -RULE_BOOL ( World, EnableReturnHomeButton, true) -RULE_INT ( World, MaxLevelForTutorial, 10) -RULE_INT ( World, TutorialZoneID, 189) -RULE_INT ( World, GuildBankZoneID, 345) -RULE_INT ( World, MinOfflineTimeToReturnHome, 21600) // 21600 seconds is 6 Hours -RULE_INT ( World, MaxClientsPerIP, -1 ) // Maximum number of clients allowed to connect per IP address if account status is < AddMaxClientsStatus. Default value: -1 (feature disabled) -RULE_INT ( World, ExemptMaxClientsStatus, -1 ) // Exempt accounts from the MaxClientsPerIP and AddMaxClientsStatus rules, if their status is >= this value. Default value: -1 (feature disabled) -RULE_INT ( World, AddMaxClientsPerIP, -1 ) // Maximum number of clients allowed to connect per IP address if account status is < ExemptMaxClientsStatus. Default value: -1 (feature disabled) -RULE_INT ( World, AddMaxClientsStatus, -1 ) // Accounts with status >= this rule will be allowed to use the amount of accounts defined in the AddMaxClientsPerIP. Default value: -1 (feature disabled) -RULE_BOOL ( World, MaxClientsSetByStatus, false) // If True, IP Limiting will be set to the status on the account as long as the status is > MaxClientsPerIP -RULE_BOOL ( World, ClearTempMerchantlist, true) // Clears temp merchant items when world boots. -RULE_BOOL ( World, DeleteStaleCorpeBackups, true) // Deletes stale corpse backups older than 2 weeks. -RULE_INT ( World, AccountSessionLimit, -1 ) //Max number of characters allowed on at once from a single account (-1 is disabled) -RULE_INT ( World, ExemptAccountLimitStatus, -1 ) //Min status required to be exempt from multi-session per account limiting (-1 is disabled) -RULE_BOOL ( World, GMAccountIPList, false) // Check ip list against GM Accounts, AntiHack GM Accounts. -RULE_INT ( World, MinGMAntiHackStatus, 1 ) //Minimum GM status to check against AntiHack list -RULE_INT ( World, SoFStartZoneID, -1 ) //Sets the Starting Zone for SoF Clients separate from Titanium Clients (-1 is disabled) -RULE_INT ( World, TitaniumStartZoneID, -1) //Sets the Starting Zone for Titanium Clients (-1 is disabled). Replaces the old method. -RULE_INT ( World, ExpansionSettings, 16383) // Sets the expansion settings for the server, This is sent on login to world and affects client expansion settings. Defaults to all expansions enabled up to TSS. -RULE_INT ( World, PVPSettings, 0) // Sets the PVP settings for the server, 1 = Rallos Zek RuleSet, 2 = Tallon/Vallon Zek Ruleset, 4 = Sullon Zek Ruleset, 6 = Discord Ruleset, anything above 6 is the Discord Ruleset without the no-drop restrictions removed. TODO: Edit IsAttackAllowed in Zone to accomodate for these rules. +RULE_CATEGORY(World) +RULE_INT(World, ZoneAutobootTimeoutMS, 60000) +RULE_INT(World, ClientKeepaliveTimeoutMS, 65000) +RULE_BOOL(World, UseBannedIPsTable, false) // Toggle whether or not to check incoming client connections against the Banned_IPs table. Set this value to false to disable this feature. +RULE_BOOL(World, EnableTutorialButton, true) +RULE_BOOL(World, EnableReturnHomeButton, true) +RULE_INT(World, MaxLevelForTutorial, 10) +RULE_INT(World, TutorialZoneID, 189) +RULE_INT(World, GuildBankZoneID, 345) +RULE_INT(World, MinOfflineTimeToReturnHome, 21600) // 21600 seconds is 6 Hours +RULE_INT(World, MaxClientsPerIP, -1) // Maximum number of clients allowed to connect per IP address if account status is < AddMaxClientsStatus. Default value: -1 (feature disabled) +RULE_INT(World, ExemptMaxClientsStatus, -1) // Exempt accounts from the MaxClientsPerIP and AddMaxClientsStatus rules, if their status is >= this value. Default value: -1 (feature disabled) +RULE_INT(World, AddMaxClientsPerIP, -1) // Maximum number of clients allowed to connect per IP address if account status is < ExemptMaxClientsStatus. Default value: -1 (feature disabled) +RULE_INT(World, AddMaxClientsStatus, -1) // Accounts with status >= this rule will be allowed to use the amount of accounts defined in the AddMaxClientsPerIP. Default value: -1 (feature disabled) +RULE_BOOL(World, MaxClientsSetByStatus, false) // If True, IP Limiting will be set to the status on the account as long as the status is > MaxClientsPerIP +RULE_BOOL(World, ClearTempMerchantlist, true) // Clears temp merchant items when world boots. +RULE_BOOL(World, DeleteStaleCorpeBackups, true) // Deletes stale corpse backups older than 2 weeks. +RULE_INT(World, AccountSessionLimit, -1) //Max number of characters allowed on at once from a single account (-1 is disabled) +RULE_INT(World, ExemptAccountLimitStatus, -1) //Min status required to be exempt from multi-session per account limiting (-1 is disabled) +RULE_BOOL(World, GMAccountIPList, false) // Check ip list against GM Accounts, AntiHack GM Accounts. +RULE_INT(World, MinGMAntiHackStatus, 1) //Minimum GM status to check against AntiHack list +RULE_INT(World, SoFStartZoneID, -1) //Sets the Starting Zone for SoF Clients separate from Titanium Clients (-1 is disabled) +RULE_INT(World, TitaniumStartZoneID, -1) //Sets the Starting Zone for Titanium Clients (-1 is disabled). Replaces the old method. +RULE_INT(World, ExpansionSettings, 16383) // Sets the expansion settings for the server, This is sent on login to world and affects client expansion settings. Defaults to all expansions enabled up to TSS. +RULE_INT(World, PVPSettings, 0) // Sets the PVP settings for the server, 1 = Rallos Zek RuleSet, 2 = Tallon/Vallon Zek Ruleset, 4 = Sullon Zek Ruleset, 6 = Discord Ruleset, anything above 6 is the Discord Ruleset without the no-drop restrictions removed. TODO: Edit IsAttackAllowed in Zone to accomodate for these rules. RULE_BOOL (World, IsGMPetitionWindowEnabled, false) RULE_INT (World, FVNoDropFlag, 0) // Sets the Firiona Vie settings on the client. If set to 2, the flag will be set for GMs only, allowing trading of no-drop items. RULE_BOOL (World, IPLimitDisconnectAll, false) RULE_INT (World, TellQueueSize, 20) RULE_CATEGORY_END() -RULE_CATEGORY( Zone ) -RULE_INT ( Zone, NPCPositonUpdateTicCount, 32 ) //ms between intervals of sending a position update to the entire zone. -RULE_INT ( Zone, ClientLinkdeadMS, 180000) //the time a client remains link dead on the server after a sudden disconnection -RULE_INT ( Zone, GraveyardTimeMS, 1200000) //ms time until a player corpse is moved to a zone's graveyard, if one is specified for the zone -RULE_BOOL ( Zone, EnableShadowrest, 1 ) // enables or disables the shadowrest zone feature for player corpses. Default is turned on. -RULE_BOOL ( Zone, UsePlayerCorpseBackups, true) // Keeps backups of player corpses. -RULE_INT ( Zone, MQWarpExemptStatus, -1 ) // Required status level to exempt the MQWarpDetector. Set to -1 to disable this feature. -RULE_INT ( Zone, MQZoneExemptStatus, -1 ) // Required status level to exempt the MQZoneDetector. Set to -1 to disable this feature. -RULE_INT ( Zone, MQGateExemptStatus, -1 ) // Required status level to exempt the MQGateDetector. Set to -1 to disable this feature. -RULE_INT ( Zone, MQGhostExemptStatus, -1 ) // Required status level to exempt the MGhostDetector. Set to -1 to disable this feature. -RULE_BOOL ( Zone, EnableMQWarpDetector, true ) // Enable the MQWarp Detector. Set to False to disable this feature. -RULE_BOOL ( Zone, EnableMQZoneDetector, true ) // Enable the MQZone Detector. Set to False to disable this feature. -RULE_BOOL ( Zone, EnableMQGateDetector, true ) // Enable the MQGate Detector. Set to False to disable this feature. -RULE_BOOL ( Zone, EnableMQGhostDetector, true ) // Enable the MQGhost Detector. Set to False to disable this feature. -RULE_REAL ( Zone, MQWarpDetectionDistanceFactor, 9.0) //clients move at 4.4 about if in a straight line but with movement and to acct for lag we raise it a bit -RULE_BOOL ( Zone, MarkMQWarpLT, false ) -RULE_INT ( Zone, AutoShutdownDelay, 5000 ) //How long a dynamic zone stays loaded while empty -RULE_INT ( Zone, PEQZoneReuseTime, 900 ) //How long, in seconds, until you can reuse the #peqzone command. -RULE_INT ( Zone, PEQZoneDebuff1, 4454 ) //First debuff casted by #peqzone Default is Cursed Keeper's Blight. -RULE_INT ( Zone, PEQZoneDebuff2, 2209 ) //Second debuff casted by #peqzone Default is Tendrils of Apathy. -RULE_BOOL ( Zone, UsePEQZoneDebuffs, true ) //Will determine if #peqzone will debuff players or not when used. -RULE_REAL ( Zone, HotZoneBonus, 0.75 ) -RULE_INT ( Zone, ReservedInstances, 30 ) //Will reserve this many instance ids for globals... probably not a good idea to change this while a server is running. -RULE_INT ( Zone, EbonCrystalItemID, 40902) -RULE_INT ( Zone, RadiantCrystalItemID, 40903) -RULE_BOOL ( Zone, LevelBasedEXPMods, false) // Allows you to use the level_exp_mods table in consideration to your players EXP hits -RULE_INT ( Zone, WeatherTimer, 600) // Weather timer when no duration is available -RULE_BOOL ( Zone, EnableLoggedOffReplenishments, true) -RULE_INT ( Zone, MinOfflineTimeToReplenishments, 21600) // 21600 seconds is 6 Hours +RULE_CATEGORY(Zone) +RULE_INT(Zone, NPCPositonUpdateTicCount, 32) //ms between intervals of sending a position update to the entire zone. +RULE_INT(Zone, ClientLinkdeadMS, 180000) //the time a client remains link dead on the server after a sudden disconnection +RULE_INT(Zone, GraveyardTimeMS, 1200000) //ms time until a player corpse is moved to a zone's graveyard, if one is specified for the zone +RULE_BOOL(Zone, EnableShadowrest, 1) // enables or disables the shadowrest zone feature for player corpses. Default is turned on. +RULE_BOOL(Zone, UsePlayerCorpseBackups, true) // Keeps backups of player corpses. +RULE_INT(Zone, MQWarpExemptStatus, -1) // Required status level to exempt the MQWarpDetector. Set to -1 to disable this feature. +RULE_INT(Zone, MQZoneExemptStatus, -1) // Required status level to exempt the MQZoneDetector. Set to -1 to disable this feature. +RULE_INT(Zone, MQGateExemptStatus, -1) // Required status level to exempt the MQGateDetector. Set to -1 to disable this feature. +RULE_INT(Zone, MQGhostExemptStatus, -1) // Required status level to exempt the MGhostDetector. Set to -1 to disable this feature. +RULE_BOOL(Zone, EnableMQWarpDetector, true) // Enable the MQWarp Detector. Set to False to disable this feature. +RULE_BOOL(Zone, EnableMQZoneDetector, true) // Enable the MQZone Detector. Set to False to disable this feature. +RULE_BOOL(Zone, EnableMQGateDetector, true) // Enable the MQGate Detector. Set to False to disable this feature. +RULE_BOOL(Zone, EnableMQGhostDetector, true) // Enable the MQGhost Detector. Set to False to disable this feature. +RULE_REAL(Zone, MQWarpDetectionDistanceFactor, 9.0) //clients move at 4.4 about if in a straight line but with movement and to acct for lag we raise it a bit +RULE_BOOL(Zone, MarkMQWarpLT, false) +RULE_INT(Zone, AutoShutdownDelay, 5000) //How long a dynamic zone stays loaded while empty +RULE_INT(Zone, PEQZoneReuseTime, 900) //How long, in seconds, until you can reuse the #peqzone command. +RULE_INT(Zone, PEQZoneDebuff1, 4454) //First debuff casted by #peqzone Default is Cursed Keeper's Blight. +RULE_INT(Zone, PEQZoneDebuff2, 2209) //Second debuff casted by #peqzone Default is Tendrils of Apathy. +RULE_BOOL(Zone, UsePEQZoneDebuffs, true) //Will determine if #peqzone will debuff players or not when used. +RULE_REAL(Zone, HotZoneBonus, 0.75) +RULE_INT(Zone, ReservedInstances, 30) //Will reserve this many instance ids for globals... probably not a good idea to change this while a server is running. +RULE_INT(Zone, EbonCrystalItemID, 40902) +RULE_INT(Zone, RadiantCrystalItemID, 40903) +RULE_BOOL(Zone, LevelBasedEXPMods, false) // Allows you to use the level_exp_mods table in consideration to your players EXP hits +RULE_INT(Zone, WeatherTimer, 600) // Weather timer when no duration is available +RULE_BOOL(Zone, EnableLoggedOffReplenishments, true) +RULE_INT(Zone, MinOfflineTimeToReplenishments, 21600) // 21600 seconds is 6 Hours RULE_CATEGORY_END() -RULE_CATEGORY( Map ) +RULE_CATEGORY(Map) //enable these to help prevent mob hopping when they are pathing -RULE_BOOL ( Map, FixPathingZWhenLoading, true ) //increases zone boot times a bit to reduce hopping. -RULE_BOOL ( Map, FixPathingZAtWaypoints, false ) //alternative to `WhenLoading`, accomplishes the same thing but does it at each waypoint instead of once at boot time. -RULE_BOOL ( Map, FixPathingZWhenMoving, false ) //very CPU intensive, but helps hopping with widely spaced waypoints. -RULE_BOOL ( Map, FixPathingZOnSendTo, false ) //try to repair Z coords in the SendTo routine as well. -RULE_REAL ( Map, FixPathingZMaxDeltaMoving, 20 ) //at runtime while pathing: max change in Z to allow the BestZ code to apply. -RULE_REAL ( Map, FixPathingZMaxDeltaWaypoint, 20 ) //at runtime at each waypoint: max change in Z to allow the BestZ code to apply. -RULE_REAL ( Map, FixPathingZMaxDeltaSendTo, 20 ) //at runtime in SendTo: max change in Z to allow the BestZ code to apply. -RULE_REAL ( Map, FixPathingZMaxDeltaLoading, 45 ) //while loading each waypoint: max change in Z to allow the BestZ code to apply. -RULE_INT ( Map, FindBestZHeightAdjust, 1) // Adds this to the current Z before seeking the best Z position +RULE_BOOL(Map, FixPathingZWhenLoading, true) //increases zone boot times a bit to reduce hopping. +RULE_BOOL(Map, FixPathingZAtWaypoints, false) //alternative to `WhenLoading`, accomplishes the same thing but does it at each waypoint instead of once at boot time. +RULE_BOOL(Map, FixPathingZWhenMoving, false) //very CPU intensive, but helps hopping with widely spaced waypoints. +RULE_BOOL(Map, FixPathingZOnSendTo, false) //try to repair Z coords in the SendTo routine as well. +RULE_REAL(Map, FixPathingZMaxDeltaMoving, 20) //at runtime while pathing: max change in Z to allow the BestZ code to apply. +RULE_REAL(Map, FixPathingZMaxDeltaWaypoint, 20) //at runtime at each waypoint: max change in Z to allow the BestZ code to apply. +RULE_REAL(Map, FixPathingZMaxDeltaSendTo, 20) //at runtime in SendTo: max change in Z to allow the BestZ code to apply. +RULE_REAL(Map, FixPathingZMaxDeltaLoading, 45) //while loading each waypoint: max change in Z to allow the BestZ code to apply. +RULE_INT(Map, FindBestZHeightAdjust, 1) // Adds this to the current Z before seeking the best Z position RULE_CATEGORY_END() -RULE_CATEGORY( Pathing ) +RULE_CATEGORY(Pathing) // Some of these rules may benefit by being made into columns in the zone table, // for instance, in dungeons, the min LOS distances could be substantially lowered. -RULE_BOOL ( Pathing, Aggro, true ) // Enable pathing for aggroed mobs. -RULE_BOOL ( Pathing, AggroReturnToGrid, true ) // Enable pathing for aggroed roaming mobs returning to their previous waypoint. -RULE_BOOL ( Pathing, Guard, true ) // Enable pathing for mobs moving to their guard point. -RULE_BOOL ( Pathing, Find, true ) // Enable pathing for FindPerson requests from the client. -RULE_BOOL ( Pathing, Fear, true ) // Enable pathing for fear -RULE_REAL ( Pathing, ZDiffThreshold, 10) // If a mob las LOS to it's target, it will run to it if the Z difference is < this. -RULE_INT ( Pathing, LOSCheckFrequency, 1000) // A mob will check for LOS to it's target this often (milliseconds). -RULE_INT ( Pathing, RouteUpdateFrequencyShort, 1000) // How often a new route will be calculated if the target has moved. -RULE_INT ( Pathing, RouteUpdateFrequencyLong, 5000) // How often a new route will be calculated if the target has moved. +RULE_BOOL(Pathing, Aggro, true) // Enable pathing for aggroed mobs. +RULE_BOOL(Pathing, AggroReturnToGrid, true) // Enable pathing for aggroed roaming mobs returning to their previous waypoint. +RULE_BOOL(Pathing, Guard, true) // Enable pathing for mobs moving to their guard point. +RULE_BOOL(Pathing, Find, true) // Enable pathing for FindPerson requests from the client. +RULE_BOOL(Pathing, Fear, true) // Enable pathing for fear +RULE_REAL(Pathing, ZDiffThreshold, 10) // If a mob las LOS to it's target, it will run to it if the Z difference is < this. +RULE_INT(Pathing, LOSCheckFrequency, 1000) // A mob will check for LOS to it's target this often (milliseconds). +RULE_INT(Pathing, RouteUpdateFrequencyShort, 1000) // How often a new route will be calculated if the target has moved. +RULE_INT(Pathing, RouteUpdateFrequencyLong, 5000) // How often a new route will be calculated if the target has moved. // When a path has a path node route and it's target changes position, if it has RouteUpdateFrequencyNodeCount or less nodes to go on it's // current path, it will recalculate it's path based on the RouteUpdateFrequencyShort timer, otherwise it will use the // RouteUpdateFrequencyLong timer. -RULE_INT ( Pathing, RouteUpdateFrequencyNodeCount, 5) -RULE_REAL ( Pathing, MinDistanceForLOSCheckShort, 40000) // (NoRoot). While following a path, only check for LOS to target within this distance. -RULE_REAL ( Pathing, MinDistanceForLOSCheckLong, 1000000) // (NoRoot). Min distance when initially attempting to acquire the target. -RULE_INT ( Pathing, MinNodesLeftForLOSCheck, 4) // Only check for LOS when we are down to this many path nodes left to run. +RULE_INT(Pathing, RouteUpdateFrequencyNodeCount, 5) +RULE_REAL(Pathing, MinDistanceForLOSCheckShort, 40000) // (NoRoot). While following a path, only check for LOS to target within this distance. +RULE_REAL(Pathing, MinDistanceForLOSCheckLong, 1000000) // (NoRoot). Min distance when initially attempting to acquire the target. +RULE_INT(Pathing, MinNodesLeftForLOSCheck, 4) // Only check for LOS when we are down to this many path nodes left to run. // This next rule was put in for situations where the mob and it's target may be on different sides of a 'hazard', e.g. a pit // If the mob has LOS to it's target, even though there is a hazard in it's way, it may break off from the node path and run at // the target, only to later detect the hazard and re-acquire a node path. Depending upon the placement of the path nodes, this // can lead to the mob looping. The rule is intended to allow the mob to at least get closer to it's target each time before // checking LOS and trying to head straight for it. -RULE_INT ( Pathing, MinNodesTraversedForLOSCheck, 3) // Only check for LOS after we have traversed this many path nodes. -RULE_INT ( Pathing, CullNodesFromStart, 1) // Checks LOS from Start point to second node for this many nodes and removes first node if there is LOS -RULE_INT ( Pathing, CullNodesFromEnd, 1) // Checks LOS from End point to second to last node for this many nodes and removes last node if there is LOS -RULE_REAL ( Pathing, CandidateNodeRangeXY, 400) // When searching for path start/end nodes, only nodes within this range will be considered. -RULE_REAL ( Pathing, CandidateNodeRangeZ, 10) // When searching for path start/end nodes, only nodes within this range will be considered. +RULE_INT(Pathing, MinNodesTraversedForLOSCheck, 3) // Only check for LOS after we have traversed this many path nodes. +RULE_INT(Pathing, CullNodesFromStart, 1) // Checks LOS from Start point to second node for this many nodes and removes first node if there is LOS +RULE_INT(Pathing, CullNodesFromEnd, 1) // Checks LOS from End point to second to last node for this many nodes and removes last node if there is LOS +RULE_REAL(Pathing, CandidateNodeRangeXY, 400) // When searching for path start/end nodes, only nodes within this range will be considered. +RULE_REAL(Pathing, CandidateNodeRangeZ, 10) // When searching for path start/end nodes, only nodes within this range will be considered. RULE_CATEGORY_END() -RULE_CATEGORY( Watermap ) +RULE_CATEGORY(Watermap) // enable these to use the water detection code. Requires Water Maps generated by awater utility -RULE_BOOL ( Watermap, CheckWaypointsInWaterWhenLoading, false ) // Does not apply BestZ as waypoints are loaded if they are in water -RULE_BOOL ( Watermap, CheckForWaterAtWaypoints, false) // Check if a mob has moved into/out of water when at waypoints and sets flymode -RULE_BOOL ( Watermap, CheckForWaterWhenMoving, false) // Checks if a mob has moved into/out of water each time it's loc is recalculated -RULE_BOOL ( Watermap, CheckForWaterOnSendTo, false) // Checks if a mob has moved into/out of water on SendTo -RULE_BOOL ( Watermap, CheckForWaterWhenFishing, false) // Only lets a player fish near water (if a water map exists for the zone) -RULE_REAL ( Watermap, FishingRodLength, 30) // How far in front of player water must be for fishing to work -RULE_REAL ( Watermap, FishingLineLength, 40) // If water is more than this far below the player, it is considered too far to fish +RULE_BOOL(Watermap, CheckWaypointsInWaterWhenLoading, false) // Does not apply BestZ as waypoints are loaded if they are in water +RULE_BOOL(Watermap, CheckForWaterAtWaypoints, false) // Check if a mob has moved into/out of water when at waypoints and sets flymode +RULE_BOOL(Watermap, CheckForWaterWhenMoving, false) // Checks if a mob has moved into/out of water each time it's loc is recalculated +RULE_BOOL(Watermap, CheckForWaterOnSendTo, false) // Checks if a mob has moved into/out of water on SendTo +RULE_BOOL(Watermap, CheckForWaterWhenFishing, false) // Only lets a player fish near water (if a water map exists for the zone) +RULE_REAL(Watermap, FishingRodLength, 30) // How far in front of player water must be for fishing to work +RULE_REAL(Watermap, FishingLineLength, 100) // If water is more than this far below the player, it is considered too far to fish +RULE_REAL(Watermap, FishingLineStepSize, 1) // Basic step size for fishing calc, too small and it will eat cpu, too large and it will miss potential water RULE_CATEGORY_END() -RULE_CATEGORY( Spells ) -RULE_INT ( Spells, AutoResistDiff, 15) -RULE_REAL ( Spells, ResistChance, 2.0) //chance to resist given no resists and same level -RULE_REAL ( Spells, ResistMod, 0.40) //multiplier, chance to resist = this * ResistAmount -RULE_REAL ( Spells, PartialHitChance, 0.7) //The chance when a spell is resisted that it will partial hit. -RULE_REAL ( Spells, PartialHitChanceFear, 0.25) //The chance when a fear spell is resisted that it will partial hit. -RULE_INT ( Spells, BaseCritChance, 0) //base % chance that everyone has to crit a spell -RULE_INT ( Spells, BaseCritRatio, 100) //base % bonus to damage on a successful spell crit. 100 = 2x damage -RULE_INT ( Spells, WizCritLevel, 12) //level wizards first get spell crits -RULE_INT ( Spells, WizCritChance, 7) //wiz's crit chance, on top of BaseCritChance -RULE_INT ( Spells, WizCritRatio, 0) //wiz's crit bonus, on top of BaseCritRatio (should be 0 for Live-like) -RULE_INT ( Spells, ResistPerLevelDiff, 85) //8.5 resist per level difference. -RULE_INT ( Spells, TranslocateTimeLimit, 0) // If not zero, time in seconds to accept a Translocate. -RULE_INT ( Spells, SacrificeMinLevel, 46) //first level Sacrifice will work on -RULE_INT ( Spells, SacrificeMaxLevel, 69) //last level Sacrifice will work on -RULE_INT ( Spells, SacrificeItemID, 9963) //Item ID of the item Sacrifice will return (defaults to an EE) -RULE_BOOL ( Spells, EnableSpellGlobals, false) // If Enabled, spells check the spell_globals table and compare character data from the quest globals before allowing that spell to scribe with scribespells -RULE_INT ( Spells, MaxBuffSlotsNPC, 25) -RULE_INT ( Spells, MaxSongSlotsNPC, 10) -RULE_INT ( Spells, MaxDiscSlotsNPC, 1) -RULE_INT ( Spells, MaxTotalSlotsNPC, 36) -RULE_INT ( Spells, MaxTotalSlotsPET, 25) // do not set this higher than 25 until the player profile is removed from the blob +RULE_CATEGORY(Spells) +RULE_INT(Spells, AutoResistDiff, 15) +RULE_REAL(Spells, ResistChance, 2.0) //chance to resist given no resists and same level +RULE_REAL(Spells, ResistMod, 0.40) //multiplier, chance to resist = this * ResistAmount +RULE_REAL(Spells, PartialHitChance, 0.7) //The chance when a spell is resisted that it will partial hit. +RULE_REAL(Spells, PartialHitChanceFear, 0.25) //The chance when a fear spell is resisted that it will partial hit. +RULE_INT(Spells, BaseCritChance, 0) //base % chance that everyone has to crit a spell +RULE_INT(Spells, BaseCritRatio, 100) //base % bonus to damage on a successful spell crit. 100 = 2x damage +RULE_INT(Spells, WizCritLevel, 12) //level wizards first get spell crits +RULE_INT(Spells, WizCritChance, 7) //wiz's crit chance, on top of BaseCritChance +RULE_INT(Spells, WizCritRatio, 0) //wiz's crit bonus, on top of BaseCritRatio (should be 0 for Live-like) +RULE_INT(Spells, ResistPerLevelDiff, 85) //8.5 resist per level difference. +RULE_INT(Spells, TranslocateTimeLimit, 0) // If not zero, time in seconds to accept a Translocate. +RULE_INT(Spells, SacrificeMinLevel, 46) //first level Sacrifice will work on +RULE_INT(Spells, SacrificeMaxLevel, 69) //last level Sacrifice will work on +RULE_INT(Spells, SacrificeItemID, 9963) //Item ID of the item Sacrifice will return (defaults to an EE) +RULE_BOOL(Spells, EnableSpellGlobals, false) // If Enabled, spells check the spell_globals table and compare character data from the quest globals before allowing that spell to scribe with scribespells +RULE_INT(Spells, MaxBuffSlotsNPC, 25) +RULE_INT(Spells, MaxSongSlotsNPC, 10) +RULE_INT(Spells, MaxDiscSlotsNPC, 1) +RULE_INT(Spells, MaxTotalSlotsNPC, 36) +RULE_INT(Spells, MaxTotalSlotsPET, 25) // do not set this higher than 25 until the player profile is removed from the blob RULE_BOOL (Spells, EnableBlockedBuffs, true) -RULE_INT ( Spells, ReflectType, 1) //0 = disabled, 1 = single target player spells only, 2 = all player spells, 3 = all single target spells, 4 = all spells -RULE_INT ( Spells, VirusSpreadDistance, 30) // The distance a viral spell will jump to its next victim -RULE_BOOL( Spells, LiveLikeFocusEffects, true) // Determines whether specific healing, dmg and mana reduction focuses are randomized -RULE_INT ( Spells, BaseImmunityLevel, 55) // The level that targets start to be immune to stun, fear and mez spells with a max level of 0. -RULE_BOOL ( Spells, NPCIgnoreBaseImmunity, true) // Whether or not NPCs get to ignore the BaseImmunityLevel for their spells. -RULE_REAL ( Spells, AvgSpellProcsPerMinute, 6.0) //Adjust rate for sympathetic spell procs -RULE_INT ( Spells, ResistFalloff, 67) //Max that level that will adjust our resist chance based on level modifiers -RULE_INT ( Spells, CharismaEffectiveness, 10) // Deterimes how much resist modification charisma applies to charm/pacify checks. Default 10 CHA = -1 resist mod. -RULE_INT ( Spells, CharismaEffectivenessCap, 255) // Deterimes how much resist modification charisma applies to charm/pacify checks. Default 10 CHA = -1 resist mod. -RULE_BOOL ( Spells, CharismaCharmDuration, false) // Allow CHA resist mod to extend charm duration. -RULE_INT ( Spells, CharmBreakCheckChance, 25) //Determines chance for a charm break check to occur each buff tick. -RULE_INT ( Spells, MaxCastTimeReduction, 50) //Max percent your spell cast time can be reduced by spell haste -RULE_INT ( Spells, RootBreakFromSpells, 55) //Chance for root to break when cast on. -RULE_INT ( Spells, DeathSaveCharismaMod, 3) //Determines how much charisma effects chance of death save firing. -RULE_INT ( Spells, DivineInterventionHeal, 8000) //Divine intervention heal amount. -RULE_INT ( Spells, AdditiveBonusWornType, 0) //Calc worn bonuses to add together (instead of taking highest) if set to THIS worn type. (2=Will covert live items automatically) -RULE_BOOL ( Spells, UseCHAScribeHack, false) //ScribeSpells and TrainDiscs quest functions will ignore entries where field 12 is CHA. What's the best way to do this? -RULE_BOOL ( Spells, BuffLevelRestrictions, true) //Buffs will not land on low level toons like live -RULE_INT ( Spells, RootBreakCheckChance, 70) //Determines chance for a root break check to occur each buff tick. -RULE_INT ( Spells, FearBreakCheckChance, 70) //Determines chance for a fear break check to occur each buff tick. -RULE_INT ( Spells, SuccorFailChance, 2) //Determines chance for a succor spell not to teleport an invidual player -RULE_INT ( Spells, FRProjectileItem_Titanium, 1113) // Item id for Titanium clients for Fire 'spell projectile'. -RULE_INT ( Spells, FRProjectileItem_SOF, 80684) // Item id for SOF clients for Fire 'spell projectile'. -RULE_INT ( Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell projectile'. -RULE_BOOL ( Spells, UseLiveSpellProjectileGFX, false) // Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file. -RULE_BOOL ( Spells, FocusCombatProcs, false) //Allow all combat procs to receive focus effects. -RULE_BOOL ( Spells, PreNerfBardAEDoT, false) //Allow bard AOE dots to damage targets when moving. -RULE_INT ( Spells, AI_SpellCastFinishedFailRecast, 800) // AI spell recast time(MS) when an spell is cast but fails (ie stunned). -RULE_INT ( Spells, AI_EngagedNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while engaged. (min time in random) -RULE_INT ( Spells, AI_EngagedNoSpellMaxRecast, 1000) // AI spell recast time(MS) check when no spell is cast engaged.(max time in random) -RULE_INT ( Spells, AI_EngagedBeneficialSelfChance, 100) // Chance during first AI Cast check to do a beneficial spell on self. -RULE_INT ( Spells, AI_EngagedBeneficialOtherChance, 25) // Chance during second AI Cast check to do a beneficial spell on others. -RULE_INT ( Spells, AI_EngagedDetrimentalChance, 20) // Chance during third AI Cast check to do a determental spell on others. -RULE_INT ( Spells, AI_PursueNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while chasing target. (min time in random) -RULE_INT ( Spells, AI_PursueNoSpellMaxRecast, 2000) // AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random) -RULE_INT ( Spells, AI_PursueDetrimentalChance, 90) // Chance while chasing target to cast a detrimental spell. -RULE_INT ( Spells, AI_IdleNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while idle. (min time in random) -RULE_INT ( Spells, AI_IdleNoSpellMaxRecast, 2000) // AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random) -RULE_INT ( Spells, AI_IdleBeneficialChance, 100) // Chance while idle to do a beneficial spell on self or others. -RULE_BOOL ( Spells, SHDProcIDOffByOne, true) // pre June 2009 SHD spell procs were off by 1, they stopped doing this in June 2009 (so UF+ spell files need this false) -RULE_BOOL ( Spells, Jun182014HundredHandsRevamp, false) // this should be true for if you import a spell file newer than June 18, 2014 -RULE_BOOL ( Spells, SwarmPetTargetLock, false) // Use old method of swarm pets target locking till target dies then despawning. -RULE_BOOL ( Spells, NPC_UseFocusFromSpells, true) // Allow npcs to use most spell derived focus effects. -RULE_BOOL ( Spells, NPC_UseFocusFromItems, false) // Allow npcs to use most item derived focus effects. -RULE_BOOL ( Spells, UseAdditiveFocusFromWornSlot, false) // Allows an additive focus effect to be calculated from worn slot. +RULE_INT(Spells, ReflectType, 1) //0 = disabled, 1 = single target player spells only, 2 = all player spells, 3 = all single target spells, 4 = all spells +RULE_INT(Spells, VirusSpreadDistance, 30) // The distance a viral spell will jump to its next victim +RULE_BOOL(Spells, LiveLikeFocusEffects, true) // Determines whether specific healing, dmg and mana reduction focuses are randomized +RULE_INT(Spells, BaseImmunityLevel, 55) // The level that targets start to be immune to stun, fear and mez spells with a max level of 0. +RULE_BOOL(Spells, NPCIgnoreBaseImmunity, true) // Whether or not NPCs get to ignore the BaseImmunityLevel for their spells. +RULE_REAL(Spells, AvgSpellProcsPerMinute, 6.0) //Adjust rate for sympathetic spell procs +RULE_INT(Spells, ResistFalloff, 67) //Max that level that will adjust our resist chance based on level modifiers +RULE_INT(Spells, CharismaEffectiveness, 10) // Deterimes how much resist modification charisma applies to charm/pacify checks. Default 10 CHA = -1 resist mod. +RULE_INT(Spells, CharismaEffectivenessCap, 255) // Deterimes how much resist modification charisma applies to charm/pacify checks. Default 10 CHA = -1 resist mod. +RULE_BOOL(Spells, CharismaCharmDuration, false) // Allow CHA resist mod to extend charm duration. +RULE_INT(Spells, CharmBreakCheckChance, 25) //Determines chance for a charm break check to occur each buff tick. +RULE_INT(Spells, MaxCastTimeReduction, 50) //Max percent your spell cast time can be reduced by spell haste +RULE_INT(Spells, RootBreakFromSpells, 55) //Chance for root to break when cast on. +RULE_INT(Spells, DeathSaveCharismaMod, 3) //Determines how much charisma effects chance of death save firing. +RULE_INT(Spells, DivineInterventionHeal, 8000) //Divine intervention heal amount. +RULE_INT(Spells, AdditiveBonusWornType, 0) //Calc worn bonuses to add together (instead of taking highest) if set to THIS worn type. (2=Will covert live items automatically) +RULE_BOOL(Spells, UseCHAScribeHack, false) //ScribeSpells and TrainDiscs quest functions will ignore entries where field 12 is CHA. What's the best way to do this? +RULE_BOOL(Spells, BuffLevelRestrictions, true) //Buffs will not land on low level toons like live +RULE_INT(Spells, RootBreakCheckChance, 70) //Determines chance for a root break check to occur each buff tick. +RULE_INT(Spells, FearBreakCheckChance, 70) //Determines chance for a fear break check to occur each buff tick. +RULE_INT(Spells, SuccorFailChance, 2) //Determines chance for a succor spell not to teleport an invidual player +RULE_INT(Spells, FRProjectileItem_Titanium, 1113) // Item id for Titanium clients for Fire 'spell projectile'. +RULE_INT(Spells, FRProjectileItem_SOF, 80684) // Item id for SOF clients for Fire 'spell projectile'. +RULE_INT(Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell projectile'. +RULE_BOOL(Spells, UseLiveSpellProjectileGFX, false) // Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file. +RULE_BOOL(Spells, FocusCombatProcs, false) //Allow all combat procs to receive focus effects. +RULE_BOOL(Spells, PreNerfBardAEDoT, false) //Allow bard AOE dots to damage targets when moving. +RULE_INT(Spells, AI_SpellCastFinishedFailRecast, 800) // AI spell recast time(MS) when an spell is cast but fails (ie stunned). +RULE_INT(Spells, AI_EngagedNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while engaged. (min time in random) +RULE_INT(Spells, AI_EngagedNoSpellMaxRecast, 1000) // AI spell recast time(MS) check when no spell is cast engaged.(max time in random) +RULE_INT(Spells, AI_EngagedBeneficialSelfChance, 100) // Chance during first AI Cast check to do a beneficial spell on self. +RULE_INT(Spells, AI_EngagedBeneficialOtherChance, 25) // Chance during second AI Cast check to do a beneficial spell on others. +RULE_INT(Spells, AI_EngagedDetrimentalChance, 20) // Chance during third AI Cast check to do a determental spell on others. +RULE_INT(Spells, AI_PursueNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while chasing target. (min time in random) +RULE_INT(Spells, AI_PursueNoSpellMaxRecast, 2000) // AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random) +RULE_INT(Spells, AI_PursueDetrimentalChance, 90) // Chance while chasing target to cast a detrimental spell. +RULE_INT(Spells, AI_IdleNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while idle. (min time in random) +RULE_INT(Spells, AI_IdleNoSpellMaxRecast, 2000) // AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random) +RULE_INT(Spells, AI_IdleBeneficialChance, 100) // Chance while idle to do a beneficial spell on self or others. +RULE_BOOL(Spells, SHDProcIDOffByOne, true) // pre June 2009 SHD spell procs were off by 1, they stopped doing this in June 2009 (so UF+ spell files need this false) +RULE_BOOL(Spells, Jun182014HundredHandsRevamp, false) // this should be true for if you import a spell file newer than June 18, 2014 +RULE_BOOL(Spells, SwarmPetTargetLock, false) // Use old method of swarm pets target locking till target dies then despawning. +RULE_BOOL(Spells, NPC_UseFocusFromSpells, true) // Allow npcs to use most spell derived focus effects. +RULE_BOOL(Spells, NPC_UseFocusFromItems, false) // Allow npcs to use most item derived focus effects. +RULE_BOOL(Spells, UseAdditiveFocusFromWornSlot, false) // Allows an additive focus effect to be calculated from worn slot. +RULE_BOOL(Spells, AlwaysSendTargetsBuffs, false) // ignore LAA level if true RULE_CATEGORY_END() -RULE_CATEGORY( Combat ) -RULE_INT ( Combat, MeleeBaseCritChance, 0 ) //The base crit chance for non warriors, NOTE: This will apply to NPCs as well -RULE_INT ( Combat, WarBerBaseCritChance, 3 ) //The base crit chance for warriors and berserkers, only applies to clients -RULE_INT ( Combat, BerserkBaseCritChance, 6 ) //The bonus base crit chance you get when you're berserk -RULE_INT ( Combat, NPCBashKickLevel, 6 ) //The level that npcs can KICK/BASH -RULE_INT ( Combat, NPCBashKickStunChance, 15 ) //Percent chance that a bash/kick will stun -RULE_INT ( Combat, RogueCritThrowingChance, 25) //Rogue throwing crit bonus -RULE_INT ( Combat, RogueDeadlyStrikeChance, 80) //Rogue chance throwing from behind crit becomes a deadly strike -RULE_INT ( Combat, RogueDeadlyStrikeMod, 2) //Deadly strike modifier to crit damage -RULE_INT ( Combat, ClientBaseCritChance, 0 ) //The base crit chance for all clients, this will stack with warrior's/zerker's crit chance. -RULE_BOOL ( Combat, UseIntervalAC, true) -RULE_INT ( Combat, PetAttackMagicLevel, 30) -RULE_BOOL ( Combat, EnableFearPathing, true) -RULE_REAL ( Combat, FleeMultiplier, 2.0) // Determines how quickly a NPC will slow down while fleeing. Decrease multiplier to slow NPC down quicker. -RULE_INT ( Combat, FleeHPRatio, 25) //HP % when a NPC begins to flee. -RULE_BOOL ( Combat, FleeIfNotAlone, false) // If false, mobs won't flee if other mobs are in combat with it. -RULE_BOOL ( Combat, AdjustProcPerMinute, true) -RULE_REAL ( Combat, AvgProcsPerMinute, 2.0) -RULE_REAL ( Combat, ProcPerMinDexContrib, 0.075) -RULE_REAL ( Combat, BaseProcChance, 0.035) -RULE_REAL ( Combat, ProcDexDivideBy, 11000) -RULE_BOOL ( Combat, AdjustSpecialProcPerMinute, true) //Set PPM for special abilities like HeadShot (Live does this as of 4-14) -RULE_REAL ( Combat, AvgSpecialProcsPerMinute, 2.0) //Unclear what best value is atm. -RULE_REAL ( Combat, BaseHitChance, 69.0) -RULE_REAL ( Combat, NPCBonusHitChance, 26.0) -RULE_REAL ( Combat, HitFalloffMinor, 5.0) //hit will fall off up to 5% over the initial level range -RULE_REAL ( Combat, HitFalloffModerate, 7.0) //hit will fall off up to 7% over the three levels after the initial level range -RULE_REAL ( Combat, HitFalloffMajor, 50.0) //hit will fall off sharply if we're outside the minor and moderate range -RULE_REAL ( Combat, HitBonusPerLevel, 1.2) //You gain this % of hit for every level you are above your target -RULE_REAL ( Combat, WeaponSkillFalloff, 0.33) //For every weapon skill point that's not maxed you lose this % of hit -RULE_REAL ( Combat, ArcheryHitPenalty, 0.25) //Archery has a hit penalty to try to help balance it with the plethora of long term +hit modifiers for it -RULE_REAL ( Combat, AgiHitFactor, 0.01) -RULE_REAL ( Combat, MinChancetoHit, 5.0) //Minimum % chance to hit with regular melee/ranged -RULE_REAL ( Combat, MaxChancetoHit, 95.0) //Maximum % chance to hit with regular melee/ranged -RULE_INT ( Combat, MinRangedAttackDist, 25) //Minimum Distance to use Ranged Attacks -RULE_BOOL ( Combat, ArcheryBonusRequiresStationary, true) //does the 2x archery bonus chance require a stationary npc -RULE_REAL ( Combat, ArcheryBaseDamageBonus, 1) // % Modifier to Base Archery Damage (.5 = 50% base damage, 1 = 100%, 2 = 200%) -RULE_REAL ( Combat, ArcheryNPCMultiplier, 1.0) // this is multiplied by the regular dmg to get the archery dmg -RULE_BOOL ( Combat, AssistNoTargetSelf, true) //when assisting a target that does not have a target: true = target self, false = leave target as was before assist (false = live like) -RULE_INT ( Combat, MaxRampageTargets, 3) //max number of people hit with rampage -RULE_INT ( Combat, DefaultRampageTargets, 1) // default number of people to hit with rampage -RULE_BOOL ( Combat, RampageHitsTarget, false) // rampage will hit the target if it still has targets left -RULE_INT ( Combat, MaxFlurryHits, 2) //max number of extra hits from flurry -RULE_INT ( Combat, MonkDamageTableBonus, 5) //% bonus monks get to their damage table calcs -RULE_INT ( Combat, FlyingKickBonus, 25) //% Modifier that this skill gets to str and skill bonuses -RULE_INT ( Combat, DragonPunchBonus, 20) //% Modifier that this skill gets to str and skill bonuses -RULE_INT ( Combat, EagleStrikeBonus, 15) //% Modifier that this skill gets to str and skill bonuses -RULE_INT ( Combat, TigerClawBonus, 10) //% Modifier that this skill gets to str and skill bonuses -RULE_INT ( Combat, RoundKickBonus, 5) //% Modifier that this skill gets to str and skill bonuses -RULE_INT ( Combat, FrenzyBonus, 0) //% Modifier to damage -RULE_BOOL ( Combat, ProcTargetOnly, true) //true = procs will only affect our target, false = procs will affect all of our targets -RULE_REAL ( Combat, NPCACFactor, 2.25) -RULE_INT ( Combat, ClothACSoftcap, 75) -RULE_INT ( Combat, LeatherACSoftcap, 100) -RULE_INT ( Combat, MonkACSoftcap, 120) -RULE_INT ( Combat, ChainACSoftcap, 200) -RULE_INT ( Combat, PlateACSoftcap, 300) -RULE_REAL ( Combat, AAMitigationACFactor, 3.0) -RULE_REAL ( Combat, WarriorACSoftcapReturn, 0.45) -RULE_REAL ( Combat, KnightACSoftcapReturn, 0.33) -RULE_REAL ( Combat, LowPlateChainACSoftcapReturn, 0.23) -RULE_REAL ( Combat, LowChainLeatherACSoftcapReturn, 0.17) -RULE_REAL ( Combat, CasterACSoftcapReturn, 0.06) -RULE_REAL ( Combat, MiscACSoftcapReturn, 0.3) -RULE_BOOL ( Combat, OldACSoftcapRules, false) // use old softcaps -RULE_BOOL ( Combat, UseOldDamageIntervalRules, false) // use old damage formulas for everything -RULE_REAL ( Combat, WarACSoftcapReturn, 0.3448) // new AC returns -RULE_REAL ( Combat, ClrRngMnkBrdACSoftcapReturn, 0.3030) -RULE_REAL ( Combat, PalShdACSoftcapReturn, 0.3226) -RULE_REAL ( Combat, DruNecWizEncMagACSoftcapReturn, 0.2000) -RULE_REAL ( Combat, RogShmBstBerACSoftcapReturn, 0.2500) -RULE_REAL ( Combat, SoftcapFactor, 1.88) -RULE_REAL ( Combat, ACthac0Factor, 0.55) -RULE_REAL ( Combat, ACthac20Factor, 0.55) -RULE_INT ( Combat, HitCapPre20, 40) // live has it capped at 40 for whatever dumb reason... this is mainly for custom servers -RULE_INT ( Combat, HitCapPre10, 20) // live has it capped at 20, see above :p -RULE_INT ( Combat, MinHastedDelay, 400) // how fast we can get with haste. -RULE_REAL ( Combat, AvgDefProcsPerMinute, 2.0) -RULE_REAL ( Combat, DefProcPerMinAgiContrib, 0.075) //How much agility contributes to defensive proc rate -RULE_INT ( Combat, SpecialAttackACBonus, 15) //Percent amount of damage per AC gained for certain special attacks (damage = AC*SpecialAttackACBonus/100). -RULE_INT ( Combat, NPCFlurryChance, 20) // Chance for NPC to flurry. +RULE_CATEGORY(Combat) +RULE_INT(Combat, MeleeBaseCritChance, 0) //The base crit chance for non warriors, NOTE: This will apply to NPCs as well +RULE_INT(Combat, WarBerBaseCritChance, 3) //The base crit chance for warriors and berserkers, only applies to clients +RULE_INT(Combat, BerserkBaseCritChance, 6) //The bonus base crit chance you get when you're berserk +RULE_INT(Combat, NPCBashKickLevel, 6) //The level that npcs can KICK/BASH +RULE_INT(Combat, NPCBashKickStunChance, 15) //Percent chance that a bash/kick will stun +RULE_INT(Combat, RogueCritThrowingChance, 25) //Rogue throwing crit bonus +RULE_INT(Combat, RogueDeadlyStrikeChance, 80) //Rogue chance throwing from behind crit becomes a deadly strike +RULE_INT(Combat, RogueDeadlyStrikeMod, 2) //Deadly strike modifier to crit damage +RULE_INT(Combat, ClientBaseCritChance, 0) //The base crit chance for all clients, this will stack with warrior's/zerker's crit chance. +RULE_BOOL(Combat, UseIntervalAC, true) +RULE_INT(Combat, PetAttackMagicLevel, 30) +RULE_BOOL(Combat, EnableFearPathing, true) +RULE_REAL(Combat, FleeMultiplier, 2.0) // Determines how quickly a NPC will slow down while fleeing. Decrease multiplier to slow NPC down quicker. +RULE_INT(Combat, FleeHPRatio, 25) //HP % when a NPC begins to flee. +RULE_BOOL(Combat, FleeIfNotAlone, false) // If false, mobs won't flee if other mobs are in combat with it. +RULE_BOOL(Combat, AdjustProcPerMinute, true) +RULE_REAL(Combat, AvgProcsPerMinute, 2.0) +RULE_REAL(Combat, ProcPerMinDexContrib, 0.075) +RULE_REAL(Combat, BaseProcChance, 0.035) +RULE_REAL(Combat, ProcDexDivideBy, 11000) +RULE_BOOL(Combat, AdjustSpecialProcPerMinute, true) //Set PPM for special abilities like HeadShot (Live does this as of 4-14) +RULE_REAL(Combat, AvgSpecialProcsPerMinute, 2.0) //Unclear what best value is atm. +RULE_REAL(Combat, BaseHitChance, 69.0) +RULE_REAL(Combat, NPCBonusHitChance, 26.0) +RULE_REAL(Combat, HitFalloffMinor, 5.0) //hit will fall off up to 5% over the initial level range +RULE_REAL(Combat, HitFalloffModerate, 7.0) //hit will fall off up to 7% over the three levels after the initial level range +RULE_REAL(Combat, HitFalloffMajor, 50.0) //hit will fall off sharply if we're outside the minor and moderate range +RULE_REAL(Combat, HitBonusPerLevel, 1.2) //You gain this % of hit for every level you are above your target +RULE_REAL(Combat, WeaponSkillFalloff, 0.33) //For every weapon skill point that's not maxed you lose this % of hit +RULE_REAL(Combat, ArcheryHitPenalty, 0.25) //Archery has a hit penalty to try to help balance it with the plethora of long term +hit modifiers for it +RULE_REAL(Combat, AgiHitFactor, 0.01) +RULE_REAL(Combat, MinChancetoHit, 5.0) //Minimum % chance to hit with regular melee/ranged +RULE_REAL(Combat, MaxChancetoHit, 95.0) //Maximum % chance to hit with regular melee/ranged +RULE_INT(Combat, MinRangedAttackDist, 25) //Minimum Distance to use Ranged Attacks +RULE_BOOL(Combat, ArcheryBonusRequiresStationary, true) //does the 2x archery bonus chance require a stationary npc +RULE_REAL(Combat, ArcheryBaseDamageBonus, 1) // % Modifier to Base Archery Damage (.5 = 50% base damage, 1 = 100%, 2 = 200%) +RULE_REAL(Combat, ArcheryNPCMultiplier, 1.0) // this is multiplied by the regular dmg to get the archery dmg +RULE_BOOL(Combat, AssistNoTargetSelf, true) //when assisting a target that does not have a target: true = target self, false = leave target as was before assist (false = live like) +RULE_INT(Combat, MaxRampageTargets, 3) //max number of people hit with rampage +RULE_INT(Combat, DefaultRampageTargets, 1) // default number of people to hit with rampage +RULE_BOOL(Combat, RampageHitsTarget, false) // rampage will hit the target if it still has targets left +RULE_INT(Combat, MaxFlurryHits, 2) //max number of extra hits from flurry +RULE_INT(Combat, MonkDamageTableBonus, 5) //% bonus monks get to their damage table calcs +RULE_INT(Combat, FlyingKickBonus, 25) //% Modifier that this skill gets to str and skill bonuses +RULE_INT(Combat, DragonPunchBonus, 20) //% Modifier that this skill gets to str and skill bonuses +RULE_INT(Combat, EagleStrikeBonus, 15) //% Modifier that this skill gets to str and skill bonuses +RULE_INT(Combat, TigerClawBonus, 10) //% Modifier that this skill gets to str and skill bonuses +RULE_INT(Combat, RoundKickBonus, 5) //% Modifier that this skill gets to str and skill bonuses +RULE_INT(Combat, FrenzyBonus, 0) //% Modifier to damage +RULE_INT(Combat, BackstabBonus, 0) //% Modifier to damage +RULE_BOOL(Combat, ProcTargetOnly, true) //true = procs will only affect our target, false = procs will affect all of our targets +RULE_REAL(Combat, NPCACFactor, 2.25) +RULE_INT(Combat, ClothACSoftcap, 75) +RULE_INT(Combat, LeatherACSoftcap, 100) +RULE_INT(Combat, MonkACSoftcap, 120) +RULE_INT(Combat, ChainACSoftcap, 200) +RULE_INT(Combat, PlateACSoftcap, 300) +RULE_REAL(Combat, AAMitigationACFactor, 3.0) +RULE_REAL(Combat, WarriorACSoftcapReturn, 0.45) +RULE_REAL(Combat, KnightACSoftcapReturn, 0.33) +RULE_REAL(Combat, LowPlateChainACSoftcapReturn, 0.23) +RULE_REAL(Combat, LowChainLeatherACSoftcapReturn, 0.17) +RULE_REAL(Combat, CasterACSoftcapReturn, 0.06) +RULE_REAL(Combat, MiscACSoftcapReturn, 0.3) +RULE_BOOL(Combat, OldACSoftcapRules, false) // use old softcaps +RULE_BOOL(Combat, UseOldDamageIntervalRules, false) // use old damage formulas for everything +RULE_REAL(Combat, WarACSoftcapReturn, 0.3448) // new AC returns +RULE_REAL(Combat, ClrRngMnkBrdACSoftcapReturn, 0.3030) +RULE_REAL(Combat, PalShdACSoftcapReturn, 0.3226) +RULE_REAL(Combat, DruNecWizEncMagACSoftcapReturn, 0.2000) +RULE_REAL(Combat, RogShmBstBerACSoftcapReturn, 0.2500) +RULE_REAL(Combat, SoftcapFactor, 1.88) +RULE_REAL(Combat, ACthac0Factor, 0.55) +RULE_REAL(Combat, ACthac20Factor, 0.55) +RULE_INT(Combat, HitCapPre20, 40) // live has it capped at 40 for whatever dumb reason... this is mainly for custom servers +RULE_INT(Combat, HitCapPre10, 20) // live has it capped at 20, see above :p +RULE_INT(Combat, MinHastedDelay, 400) // how fast we can get with haste. +RULE_REAL(Combat, AvgDefProcsPerMinute, 2.0) +RULE_REAL(Combat, DefProcPerMinAgiContrib, 0.075) //How much agility contributes to defensive proc rate +RULE_INT(Combat, SpecialAttackACBonus, 15) //Percent amount of damage per AC gained for certain special attacks (damage = AC*SpecialAttackACBonus/100). +RULE_INT(Combat, NPCFlurryChance, 20) // Chance for NPC to flurry. RULE_BOOL (Combat,TauntOverLevel, 1) //Allows you to taunt NPC's over warriors level. RULE_REAL (Combat,TauntSkillFalloff, 0.33)//For every taunt skill point that's not maxed you lose this % chance to taunt. RULE_BOOL (Combat,EXPFromDmgShield, false) //Determine if damage from a damage shield counts for EXP gain. -RULE_INT ( Combat, MonkACBonusWeight, 15) -RULE_INT ( Combat, ClientStunLevel, 55) //This is the level where client kicks and bashes can stun the target -RULE_INT ( Combat, QuiverWRHasteDiv, 3) //Weight Reduction is divided by this to get haste contribution for quivers -RULE_BOOL ( Combat, UseArcheryBonusRoll, false) //Make the 51+ archery bonus require an actual roll -RULE_INT ( Combat, ArcheryBonusChance, 50) -RULE_INT ( Combat, BerserkerFrenzyStart, 35) -RULE_INT ( Combat, BerserkerFrenzyEnd, 45) -RULE_BOOL ( Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round -RULE_BOOL ( Combat, ProjectileDmgOnImpact, true) //If enabled, projectiles (ie arrows) will hit on impact, instead of instantly. +RULE_INT(Combat, MonkACBonusWeight, 15) +RULE_INT(Combat, ClientStunLevel, 55) //This is the level where client kicks and bashes can stun the target +RULE_INT(Combat, QuiverWRHasteDiv, 3) //Weight Reduction is divided by this to get haste contribution for quivers +RULE_BOOL(Combat, UseArcheryBonusRoll, false) //Make the 51+ archery bonus require an actual roll +RULE_INT(Combat, ArcheryBonusChance, 50) +RULE_INT(Combat, BerserkerFrenzyStart, 35) +RULE_INT(Combat, BerserkerFrenzyEnd, 45) +RULE_BOOL(Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round +RULE_BOOL(Combat, ProjectileDmgOnImpact, true) //If enabled, projectiles (ie arrows) will hit on impact, instead of instantly. +RULE_BOOL(Combat, MeleePush, true) // enable melee push +RULE_INT(Combat, MeleePushChance, 50) // (NPCs) chance the target will be pushed. Made up, 100 actually isn't that bad RULE_CATEGORY_END() -RULE_CATEGORY( NPC ) -RULE_INT ( NPC, MinorNPCCorpseDecayTimeMS, 450000 ) //level<55 -RULE_INT ( NPC, MajorNPCCorpseDecayTimeMS, 1500000 ) //level>=55 -RULE_INT ( NPC, CorpseUnlockTimer, 150000 ) -RULE_INT ( NPC, EmptyNPCCorpseDecayTimeMS, 0 ) +RULE_CATEGORY(NPC) +RULE_INT(NPC, MinorNPCCorpseDecayTimeMS, 450000) //level<55 +RULE_INT(NPC, MajorNPCCorpseDecayTimeMS, 1500000) //level>=55 +RULE_INT(NPC, CorpseUnlockTimer, 150000) +RULE_INT(NPC, EmptyNPCCorpseDecayTimeMS, 0) RULE_BOOL (NPC, UseItemBonusesForNonPets, true) -RULE_INT ( NPC, SayPauseTimeInSec, 5) -RULE_INT ( NPC, OOCRegen, 0) -RULE_BOOL ( NPC, BuffFriends, false ) -RULE_BOOL ( NPC, EnableNPCQuestJournal, false) -RULE_INT ( NPC, LastFightingDelayMovingMin, 10000) -RULE_INT ( NPC, LastFightingDelayMovingMax, 20000) -RULE_BOOL ( NPC, SmartLastFightingDelayMoving, true) -RULE_BOOL ( NPC, ReturnNonQuestNoDropItems, false) // Returns NO DROP items on NPCs that don't have an EVENT_TRADE sub in their script -RULE_INT ( NPC, StartEnrageValue, 9) // % HP that an NPC will begin to enrage -RULE_BOOL ( NPC, LiveLikeEnrage, false) // If set to true then only player controlled pets will enrage -RULE_BOOL ( NPC, EnableMeritBasedFaction, false) // If set to true, faction will given in the same way as experience (solo/group/raid) +RULE_INT(NPC, SayPauseTimeInSec, 5) +RULE_INT(NPC, OOCRegen, 0) +RULE_BOOL(NPC, BuffFriends, false) +RULE_BOOL(NPC, EnableNPCQuestJournal, false) +RULE_INT(NPC, LastFightingDelayMovingMin, 10000) +RULE_INT(NPC, LastFightingDelayMovingMax, 20000) +RULE_BOOL(NPC, SmartLastFightingDelayMoving, true) +RULE_BOOL(NPC, ReturnNonQuestNoDropItems, false) // Returns NO DROP items on NPCs that don't have an EVENT_TRADE sub in their script +RULE_INT(NPC, StartEnrageValue, 9) // % HP that an NPC will begin to enrage +RULE_BOOL(NPC, LiveLikeEnrage, false) // If set to true then only player controlled pets will enrage +RULE_BOOL(NPC, EnableMeritBasedFaction, false) // If set to true, faction will given in the same way as experience (solo/group/raid) RULE_CATEGORY_END() -RULE_CATEGORY ( Aggro ) -RULE_BOOL ( Aggro, SmartAggroList, true ) -RULE_INT ( Aggro, SittingAggroMod, 35 ) //35% -RULE_INT ( Aggro, MeleeRangeAggroMod, 10 ) //10% -RULE_INT ( Aggro, CurrentTargetAggroMod, 0 ) //0% -- will prefer our current target to any other; makes it harder for our npcs to switch targets. -RULE_INT ( Aggro, CriticallyWoundedAggroMod, 100 ) //100% -RULE_INT ( Aggro, SpellAggroMod, 100 ) -RULE_INT ( Aggro, SongAggroMod, 33 ) -RULE_INT ( Aggro, PetSpellAggroMod, 10 ) -RULE_REAL ( Aggro, TunnelVisionAggroMod, 0.75 ) //people not currently the top hate generate this much hate on a Tunnel Vision mob -RULE_INT ( Aggro, MaxStunProcAggro, 400 ) // Set to -1 for no limit. Maxmimum amount of aggro that a stun based proc will add. -RULE_INT ( Aggro, IntAggroThreshold, 75 ) // Int <= this will aggro regardless of level difference. +RULE_CATEGORY(Aggro) +RULE_BOOL(Aggro, SmartAggroList, true) +RULE_INT(Aggro, SittingAggroMod, 35) //35% +RULE_INT(Aggro, MeleeRangeAggroMod, 10) //10% +RULE_INT(Aggro, CurrentTargetAggroMod, 0) //0% -- will prefer our current target to any other; makes it harder for our npcs to switch targets. +RULE_INT(Aggro, CriticallyWoundedAggroMod, 100) //100% +RULE_INT(Aggro, SpellAggroMod, 100) +RULE_INT(Aggro, SongAggroMod, 33) +RULE_INT(Aggro, PetSpellAggroMod, 10) +RULE_REAL(Aggro, TunnelVisionAggroMod, 0.75) //people not currently the top hate generate this much hate on a Tunnel Vision mob +RULE_INT(Aggro, MaxStunProcAggro, 400) // Set to -1 for no limit. Maxmimum amount of aggro that a stun based proc will add. +RULE_INT(Aggro, IntAggroThreshold, 75) // Int <= this will aggro regardless of level difference. RULE_CATEGORY_END() -RULE_CATEGORY ( TaskSystem) -RULE_BOOL ( TaskSystem, EnableTaskSystem, true) // Globally enable or disable the Task system -RULE_INT ( TaskSystem, PeriodicCheckTimer, 5) // Seconds between checks for failed tasks. Also used by the 'Touch' activity -RULE_BOOL ( TaskSystem, RecordCompletedTasks, true) -RULE_BOOL ( TaskSystem, RecordCompletedOptionalActivities, false) -RULE_BOOL ( TaskSystem, KeepOneRecordPerCompletedTask, true) -RULE_BOOL ( TaskSystem, EnableTaskProximity, true) +RULE_CATEGORY(TaskSystem) +RULE_BOOL(TaskSystem, EnableTaskSystem, true) // Globally enable or disable the Task system +RULE_INT(TaskSystem, PeriodicCheckTimer, 5) // Seconds between checks for failed tasks. Also used by the 'Touch' activity +RULE_BOOL(TaskSystem, RecordCompletedTasks, true) +RULE_BOOL(TaskSystem, RecordCompletedOptionalActivities, false) +RULE_BOOL(TaskSystem, KeepOneRecordPerCompletedTask, true) +RULE_BOOL(TaskSystem, EnableTaskProximity, true) RULE_CATEGORY_END() #ifdef BOTS -RULE_CATEGORY ( Bots ) -RULE_REAL ( Bots, BotManaRegen, 2.0 ) // Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players. -RULE_BOOL ( Bots, BotFinishBuffing, false ) // Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat. -RULE_INT ( Bots, CreateBotCount, 150 ) // Number of bots that each account can create -RULE_INT ( Bots, SpawnBotCount, 71 ) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid -RULE_BOOL ( Bots, BotQuest, false ) // Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl -RULE_BOOL ( Bots, BotGroupBuffing, false ) // Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB. -RULE_BOOL ( Bots, BotSpellQuest, false ) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests. -RULE_INT ( Bots, BotAAExpansion, 8 ) // Bots get AAs through this expansion -RULE_BOOL ( Bots, BotGroupXP, false ) // Determines whether client gets xp for bots outside their group. -RULE_BOOL ( Bots, BotBardUseOutOfCombatSongs, true) // Determines whether bard bots use additional out of combat songs. -RULE_BOOL ( Bots, BotLevelsWithOwner, false) // Auto-updates spawned bots as owner levels/de-levels (false is original behavior) +RULE_CATEGORY(Bots) +RULE_REAL(Bots, BotManaRegen, 2.0) // Adjust mana regen for bots, 1 is fast and higher numbers slow it down 3 is about the same as players. +RULE_BOOL(Bots, BotFinishBuffing, false) // Allow for buffs to complete even if the bot caster is out of mana. Only affects buffing out of combat. +RULE_INT(Bots, CreateBotCount, 150) // Number of bots that each account can create +RULE_INT(Bots, SpawnBotCount, 71) // Number of bots a character can have spawned at one time, You + 71 bots is a 12 group raid +RULE_BOOL(Bots, BotQuest, false) // Optional quest method to manage bot spawn limits using the quest_globals name bot_spawn_limit, see: /bazaar/Aediles_Thrall.pl +RULE_BOOL(Bots, BotGroupBuffing, false) // Bots will cast single target buffs as group buffs, default is false for single. Does not make single target buffs work for MGB. +RULE_BOOL(Bots, BotSpellQuest, false) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests. +RULE_INT(Bots, BotAAExpansion, 8) // Bots get AAs through this expansion +RULE_BOOL(Bots, BotGroupXP, false) // Determines whether client gets xp for bots outside their group. +RULE_BOOL(Bots, BotBardUseOutOfCombatSongs, true) // Determines whether bard bots use additional out of combat songs. +RULE_BOOL(Bots, BotLevelsWithOwner, false) // Auto-updates spawned bots as owner levels/de-levels (false is original behavior) RULE_CATEGORY_END() #endif -RULE_CATEGORY ( Chat ) -RULE_BOOL ( Chat, ServerWideOOC, true) -RULE_BOOL ( Chat, ServerWideAuction, true) -RULE_BOOL ( Chat, EnableVoiceMacros, true) -RULE_BOOL ( Chat, EnableMailKeyIPVerification, true) -RULE_BOOL ( Chat, EnableAntiSpam, true) -RULE_BOOL ( Chat, SuppressCommandErrors, false) // Do not suppress by default -RULE_INT ( Chat, MinStatusToBypassAntiSpam, 100) -RULE_INT ( Chat, MinimumMessagesPerInterval, 4) -RULE_INT ( Chat, MaximumMessagesPerInterval, 12) -RULE_INT ( Chat, MaxMessagesBeforeKick, 20) -RULE_INT ( Chat, IntervalDurationMS, 60000) -RULE_INT ( Chat, KarmaUpdateIntervalMS, 1200000) -RULE_INT ( Chat, KarmaGlobalChatLimit, 72) //amount of karma you need to be able to talk in ooc/auction/chat below the level limit -RULE_INT ( Chat, GlobalChatLevelLimit, 8) //level limit you need to of reached to talk in ooc/auction/chat if your karma is too low. +RULE_CATEGORY(Chat) +RULE_BOOL(Chat, ServerWideOOC, true) +RULE_BOOL(Chat, ServerWideAuction, true) +RULE_BOOL(Chat, EnableVoiceMacros, true) +RULE_BOOL(Chat, EnableMailKeyIPVerification, true) +RULE_BOOL(Chat, EnableAntiSpam, true) +RULE_BOOL(Chat, SuppressCommandErrors, false) // Do not suppress by default +RULE_INT(Chat, MinStatusToBypassAntiSpam, 100) +RULE_INT(Chat, MinimumMessagesPerInterval, 4) +RULE_INT(Chat, MaximumMessagesPerInterval, 12) +RULE_INT(Chat, MaxMessagesBeforeKick, 20) +RULE_INT(Chat, IntervalDurationMS, 60000) +RULE_INT(Chat, KarmaUpdateIntervalMS, 1200000) +RULE_INT(Chat, KarmaGlobalChatLimit, 72) //amount of karma you need to be able to talk in ooc/auction/chat below the level limit +RULE_INT(Chat, GlobalChatLevelLimit, 8) //level limit you need to of reached to talk in ooc/auction/chat if your karma is too low. RULE_CATEGORY_END() -RULE_CATEGORY ( Merchant ) -RULE_BOOL ( Merchant, UsePriceMod, true) // Use faction/charisma price modifiers. -RULE_REAL ( Merchant, SellCostMod, 1.05) // Modifier for NPC sell price. -RULE_REAL ( Merchant, BuyCostMod, 0.95) // Modifier for NPC buy price. -RULE_INT ( Merchant, PriceBonusPct, 4) // Determines maximum price bonus from having good faction/CHA. Value is a percent. -RULE_INT ( Merchant, PricePenaltyPct, 4) // Determines maximum price penalty from having bad faction/CHA. Value is a percent. -RULE_REAL( Merchant, ChaBonusMod, 3.45) // Determines CHA cap, from 104 CHA. 3.45 is 132 CHA at apprehensive. 0.34 is 400 CHA at apprehensive. -RULE_REAL ( Merchant, ChaPenaltyMod, 1.52) // Determines CHA bottom, up to 102 CHA. 1.52 is 37 CHA at apprehensive. 0.98 is 0 CHA at apprehensive. -RULE_BOOL ( Merchant, EnableAltCurrencySell, true) // Enables the ability to resell items to alternate currency merchants +RULE_CATEGORY(Merchant) +RULE_BOOL(Merchant, UsePriceMod, true) // Use faction/charisma price modifiers. +RULE_REAL(Merchant, SellCostMod, 1.05) // Modifier for NPC sell price. +RULE_REAL(Merchant, BuyCostMod, 0.95) // Modifier for NPC buy price. +RULE_INT(Merchant, PriceBonusPct, 4) // Determines maximum price bonus from having good faction/CHA. Value is a percent. +RULE_INT(Merchant, PricePenaltyPct, 4) // Determines maximum price penalty from having bad faction/CHA. Value is a percent. +RULE_REAL(Merchant, ChaBonusMod, 3.45) // Determines CHA cap, from 104 CHA. 3.45 is 132 CHA at apprehensive. 0.34 is 400 CHA at apprehensive. +RULE_REAL(Merchant, ChaPenaltyMod, 1.52) // Determines CHA bottom, up to 102 CHA. 1.52 is 37 CHA at apprehensive. 0.98 is 0 CHA at apprehensive. +RULE_BOOL(Merchant, EnableAltCurrencySell, true) // Enables the ability to resell items to alternate currency merchants RULE_CATEGORY_END() -RULE_CATEGORY ( Bazaar ) -RULE_BOOL ( Bazaar, AuditTrail, false) -RULE_INT ( Bazaar, MaxSearchResults, 50) -RULE_BOOL ( Bazaar, EnableWarpToTrader, true) -RULE_INT ( Bazaar, MaxBarterSearchResults, 200) // The max results returned in the /barter search +RULE_CATEGORY(Bazaar) +RULE_BOOL(Bazaar, AuditTrail, false) +RULE_INT(Bazaar, MaxSearchResults, 50) +RULE_BOOL(Bazaar, EnableWarpToTrader, true) +RULE_INT(Bazaar, MaxBarterSearchResults, 200) // The max results returned in the /barter search RULE_CATEGORY_END() -RULE_CATEGORY ( Mail ) -RULE_BOOL ( Mail, EnableMailSystem, true) // If false, client won't bring up the Mail window. -RULE_INT ( Mail, ExpireTrash, 0) // Time in seconds. 0 will delete all messages in the trash when the mailserver starts -RULE_INT ( Mail, ExpireRead, 31536000 ) // 1 Year. Set to -1 for never -RULE_INT ( Mail, ExpireUnread, 31536000 ) // 1 Year. Set to -1 for never +RULE_CATEGORY(Mail) +RULE_BOOL(Mail, EnableMailSystem, true) // If false, client won't bring up the Mail window. +RULE_INT(Mail, ExpireTrash, 0) // Time in seconds. 0 will delete all messages in the trash when the mailserver starts +RULE_INT(Mail, ExpireRead, 31536000) // 1 Year. Set to -1 for never +RULE_INT(Mail, ExpireUnread, 31536000) // 1 Year. Set to -1 for never RULE_CATEGORY_END() -RULE_CATEGORY ( Channels ) -RULE_INT ( Channels, RequiredStatusAdmin, 251) // Required status to administer chat channels -RULE_INT ( Channels, RequiredStatusListAll, 251) // Required status to list all chat channels -RULE_INT ( Channels, DeleteTimer, 1440) // Empty password protected channels will be deleted after this many minutes +RULE_CATEGORY(Channels) +RULE_INT(Channels, RequiredStatusAdmin, 251) // Required status to administer chat channels +RULE_INT(Channels, RequiredStatusListAll, 251) // Required status to list all chat channels +RULE_INT(Channels, DeleteTimer, 1440) // Empty password protected channels will be deleted after this many minutes RULE_CATEGORY_END() -RULE_CATEGORY ( EventLog ) -RULE_BOOL ( EventLog, RecordSellToMerchant, false ) // Record sales from a player to an NPC merchant in eventlog table -RULE_BOOL ( EventLog, RecordBuyFromMerchant, false ) // Record purchases by a player from an NPC merchant in eventlog table +RULE_CATEGORY(EventLog) +RULE_BOOL(EventLog, RecordSellToMerchant, false) // Record sales from a player to an NPC merchant in eventlog table +RULE_BOOL(EventLog, RecordBuyFromMerchant, false) // Record purchases by a player from an NPC merchant in eventlog table RULE_CATEGORY_END() -RULE_CATEGORY ( Adventure ) -RULE_INT ( Adventure, MinNumberForGroup, 2 ) -RULE_INT ( Adventure, MaxNumberForGroup, 6 ) -RULE_INT ( Adventure, MinNumberForRaid, 18 ) -RULE_INT ( Adventure, MaxNumberForRaid, 36 ) -RULE_INT ( Adventure, MaxLevelRange, 9 ) -RULE_INT ( Adventure, NumberKillsForBossSpawn, 45) -RULE_REAL ( Adventure, DistanceForRescueAccept, 10000.0) -RULE_REAL ( Adventure, DistanceForRescueComplete, 2500.0) -RULE_INT ( Adventure, ItemIDToEnablePorts, 41000 ) //0 to disable, otherwise using a LDoN portal will require the user to have this item. -RULE_INT ( Adventure, LDoNTrapDistanceUse, 625 ) -RULE_REAL ( Adventure, LDoNBaseTrapDifficulty, 15.0 ) -RULE_REAL ( Adventure, LDoNCriticalFailTrapThreshold, 10.0 ) -RULE_INT ( Adventure, LDoNAdventureExpireTime, 1800) //30 minutes to expire +RULE_CATEGORY(Adventure) +RULE_INT(Adventure, MinNumberForGroup, 2) +RULE_INT(Adventure, MaxNumberForGroup, 6) +RULE_INT(Adventure, MinNumberForRaid, 18) +RULE_INT(Adventure, MaxNumberForRaid, 36) +RULE_INT(Adventure, MaxLevelRange, 9) +RULE_INT(Adventure, NumberKillsForBossSpawn, 45) +RULE_REAL(Adventure, DistanceForRescueAccept, 10000.0) +RULE_REAL(Adventure, DistanceForRescueComplete, 2500.0) +RULE_INT(Adventure, ItemIDToEnablePorts, 41000) //0 to disable, otherwise using a LDoN portal will require the user to have this item. +RULE_INT(Adventure, LDoNTrapDistanceUse, 625) +RULE_REAL(Adventure, LDoNBaseTrapDifficulty, 15.0) +RULE_REAL(Adventure, LDoNCriticalFailTrapThreshold, 10.0) +RULE_INT(Adventure, LDoNAdventureExpireTime, 1800) //30 minutes to expire RULE_CATEGORY_END() -RULE_CATEGORY ( AA ) -RULE_INT ( AA, ExpPerPoint, 23976503) //Amount of exp per AA. Is the same as the amount of exp to go from level 51 to level 52. -RULE_BOOL ( AA, Stacking, true) //Allow AA that belong to the same group to stack on SOF+ clients. +RULE_CATEGORY(AA) +RULE_INT(AA, ExpPerPoint, 23976503) //Amount of exp per AA. Is the same as the amount of exp to go from level 51 to level 52. +RULE_BOOL(AA, Stacking, true) //Allow AA that belong to the same group to stack on SOF+ clients. RULE_CATEGORY_END() -RULE_CATEGORY( Console ) -RULE_INT ( Console, SessionTimeOut, 600000 ) // Amount of time in ms for the console session to time out +RULE_CATEGORY(Console) +RULE_INT(Console, SessionTimeOut, 600000) // Amount of time in ms for the console session to time out RULE_CATEGORY_END() -RULE_CATEGORY( QueryServ ) -RULE_BOOL( QueryServ, PlayerLogChat, false) // Logs Player Chat -RULE_BOOL( QueryServ, PlayerLogTrades, false) // Logs Player Trades -RULE_BOOL( QueryServ, PlayerLogHandins, false) // Logs Player Handins -RULE_BOOL( QueryServ, PlayerLogNPCKills, false) // Logs Player NPC Kills -RULE_BOOL( QueryServ, PlayerLogDeletes, false) // Logs Player Deletes -RULE_BOOL( QueryServ, PlayerLogMoves, false) // Logs Player Moves -RULE_BOOL( QueryServ, PlayerLogMerchantTransactions, false) // Logs Merchant Transactions -RULE_BOOL( QueryServ, PlayerLogPCCoordinates, false) // Logs Player Coordinates with certain events -RULE_BOOL( QueryServ, PlayerLogDropItem, false) // Logs Player Drop Item -RULE_BOOL( QueryServ, PlayerLogZone, false) // Logs Player Zone Events -RULE_BOOL( QueryServ, PlayerLogDeaths, false) // Logs Player Deaths -RULE_BOOL( QueryServ, PlayerLogConnectDisconnect, false) // Logs Player Connect Disconnect State -RULE_BOOL( QueryServ, PlayerLogLevels, false) // Logs Player Leveling/Deleveling -RULE_BOOL( QueryServ, PlayerLogAARate, false) // Logs Player AA Experience Rates -RULE_BOOL( QueryServ, PlayerLogQGlobalUpdate, false) // Logs Player QGlobal Updates -RULE_BOOL( QueryServ, PlayerLogTaskUpdates, false) // Logs Player Task Updates -RULE_BOOL( QueryServ, PlayerLogKeyringAddition, false) // Log PLayer Keyring additions -RULE_BOOL( QueryServ, PlayerLogAAPurchases, false) // Log Player AA Purchases -RULE_BOOL( QueryServ, PlayerLogTradeSkillEvents, false) // Log Player Tradeskill Transactions -RULE_BOOL( QueryServ, PlayerLogIssuedCommandes, false ) // Log Player Issued Commands -RULE_BOOL( QueryServ, PlayerLogMoneyTransactions, false) // Log Player Money Transaction/Splits -RULE_BOOL( QueryServ, PlayerLogAlternateCurrencyTransactions, false) // Log Ploayer Alternate Currency Transactions +RULE_CATEGORY(QueryServ) +RULE_BOOL(QueryServ, PlayerLogChat, false) // Logs Player Chat +RULE_BOOL(QueryServ, PlayerLogTrades, false) // Logs Player Trades +RULE_BOOL(QueryServ, PlayerLogHandins, false) // Logs Player Handins +RULE_BOOL(QueryServ, PlayerLogNPCKills, false) // Logs Player NPC Kills +RULE_BOOL(QueryServ, PlayerLogDeletes, false) // Logs Player Deletes +RULE_BOOL(QueryServ, PlayerLogMoves, false) // Logs Player Moves +RULE_BOOL(QueryServ, PlayerLogMerchantTransactions, false) // Logs Merchant Transactions +RULE_BOOL(QueryServ, PlayerLogPCCoordinates, false) // Logs Player Coordinates with certain events +RULE_BOOL(QueryServ, PlayerLogDropItem, false) // Logs Player Drop Item +RULE_BOOL(QueryServ, PlayerLogZone, false) // Logs Player Zone Events +RULE_BOOL(QueryServ, PlayerLogDeaths, false) // Logs Player Deaths +RULE_BOOL(QueryServ, PlayerLogConnectDisconnect, false) // Logs Player Connect Disconnect State +RULE_BOOL(QueryServ, PlayerLogLevels, false) // Logs Player Leveling/Deleveling +RULE_BOOL(QueryServ, PlayerLogAARate, false) // Logs Player AA Experience Rates +RULE_BOOL(QueryServ, PlayerLogQGlobalUpdate, false) // Logs Player QGlobal Updates +RULE_BOOL(QueryServ, PlayerLogTaskUpdates, false) // Logs Player Task Updates +RULE_BOOL(QueryServ, PlayerLogKeyringAddition, false) // Log PLayer Keyring additions +RULE_BOOL(QueryServ, PlayerLogAAPurchases, false) // Log Player AA Purchases +RULE_BOOL(QueryServ, PlayerLogTradeSkillEvents, false) // Log Player Tradeskill Transactions +RULE_BOOL(QueryServ, PlayerLogIssuedCommandes, false) // Log Player Issued Commands +RULE_BOOL(QueryServ, PlayerLogMoneyTransactions, false) // Log Player Money Transaction/Splits +RULE_BOOL(QueryServ, PlayerLogAlternateCurrencyTransactions, false) // Log Ploayer Alternate Currency Transactions RULE_CATEGORY_END() -RULE_CATEGORY( Inventory ) -RULE_BOOL ( Inventory, EnforceAugmentRestriction, true) // Forces augment slot restrictions -RULE_BOOL ( Inventory, EnforceAugmentUsability, true) // Forces augmented item usability -RULE_BOOL ( Inventory, EnforceAugmentWear, true) // Forces augment wear slot validation -RULE_BOOL ( Inventory, DeleteTransformationMold, true) //False if you want mold to last forever -RULE_BOOL ( Inventory, AllowAnyWeaponTransformation, false) //Weapons can use any weapon transformation +RULE_CATEGORY(Inventory) +RULE_BOOL(Inventory, EnforceAugmentRestriction, true) // Forces augment slot restrictions +RULE_BOOL(Inventory, EnforceAugmentUsability, true) // Forces augmented item usability +RULE_BOOL(Inventory, EnforceAugmentWear, true) // Forces augment wear slot validation +RULE_BOOL(Inventory, DeleteTransformationMold, true) //False if you want mold to last forever +RULE_BOOL(Inventory, AllowAnyWeaponTransformation, false) //Weapons can use any weapon transformation +RULE_BOOL(Inventory, TransformSummonedBags, false) //Transforms summoned bags into disenchanted ones instead of deleting RULE_CATEGORY_END() -RULE_CATEGORY( Client ) -RULE_BOOL( Client, UseLiveFactionMessage, false) // Allows players to see faction adjustments like Live +RULE_CATEGORY(Client) +RULE_BOOL(Client, UseLiveFactionMessage, false) // Allows players to see faction adjustments like Live RULE_CATEGORY_END() #undef RULE_CATEGORY diff --git a/common/skills.cpp b/common/skills.cpp index ceb726d8c..5edca778b 100644 --- a/common/skills.cpp +++ b/common/skills.cpp @@ -55,3 +55,54 @@ bool EQEmu::IsSpecializedSkill(SkillUseTypes skill) return false; } } + +float EQEmu::GetSkillMeleePushForce(SkillUseTypes skill) +{ + // This is the force/magnitude of the push from an attack of this skill type + // You can find these numbers in the clients skill struct + switch (skill) { + case Skill1HBlunt: + case Skill1HSlashing: + case SkillHandtoHand: + case SkillThrowing: + return 0.1f; + case Skill2HBlunt: + case Skill2HSlashing: + case SkillEagleStrike: + case SkillKick: + case SkillTigerClaw: + //case Skill2HPiercing: + return 0.2f; + case SkillArchery: + return 0.15f; + case SkillBackstab: + case SkillBash: + return 0.3f; + case SkillDragonPunch: + case SkillRoundKick: + return 0.25f; + case SkillFlyingKick: + return 0.4f; + case Skill1HPiercing: + case SkillFrenzy: + return 0.05f; + case SkillIntimidation: + return 2.5f; + default: + return 0.0f; + } +} + +bool EQEmu::IsBardInstrumentSkill(SkillUseTypes skill) +{ + switch (skill) { + case SkillBrassInstruments: + case SkillSinging: + case SkillStringedInstruments: + case SkillWindInstruments: + case SkillPercussionInstruments: + return true; + default: + return false; + } +} diff --git a/common/skills.h b/common/skills.h index f7f66b49c..1fad4de3e 100644 --- a/common/skills.h +++ b/common/skills.h @@ -270,6 +270,8 @@ typedef enum { namespace EQEmu { bool IsTradeskill(SkillUseTypes skill); bool IsSpecializedSkill(SkillUseTypes skill); + float GetSkillMeleePushForce(SkillUseTypes skill); + bool IsBardInstrumentSkill(SkillUseTypes skill); } #endif diff --git a/common/spdat.cpp b/common/spdat.cpp index e52a06e2e..7dd21479c 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -72,7 +72,7 @@ -#include "../common/eqemu_logsys.h" +#include "../common/eqemu_logsys.h" #include "classes.h" #include "spdat.h" @@ -162,7 +162,7 @@ bool IsCureSpell(uint16 spell_id) bool CureEffect = false; for(int i = 0; i < EFFECT_COUNT; i++){ - if (sp.effectid[i] == SE_DiseaseCounter || sp.effectid[i] == SE_PoisonCounter + if (sp.effectid[i] == SE_DiseaseCounter || sp.effectid[i] == SE_PoisonCounter || sp.effectid[i] == SE_CurseCounter || sp.effectid[i] == SE_CorruptionCounter) CureEffect = true; } @@ -405,7 +405,7 @@ bool IsPartialCapableSpell(uint16 spell_id) { if (spells[spell_id].no_partial_resist) return false; - + if (IsPureNukeSpell(spell_id)) return true; @@ -447,7 +447,7 @@ bool IsTGBCompatibleSpell(uint16 spell_id) bool IsBardSong(uint16 spell_id) { - if (IsValidSpell(spell_id) && spells[spell_id].classes[BARD - 1] < 255) + if (IsValidSpell(spell_id) && spells[spell_id].classes[BARD - 1] < 127 && !spells[spell_id].IsDisciplineBuff) return true; return false; @@ -693,9 +693,9 @@ bool IsCombatSkill(uint16 spell_id) { if (!IsValidSpell(spell_id)) return false; - + //Check if Discipline - if ((spells[spell_id].mana == 0 && (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep))) + if ((spells[spell_id].mana == 0 && (spells[spell_id].EndurCost || spells[spell_id].EndurUpkeep))) return true; return false; @@ -1040,7 +1040,7 @@ bool IsCastonFadeDurationSpell(uint16 spell_id) bool IsPowerDistModSpell(uint16 spell_id) { - if (IsValidSpell(spell_id) && + if (IsValidSpell(spell_id) && (spells[spell_id].max_dist_mod || spells[spell_id].min_dist_mod) && spells[spell_id].max_dist > spells[spell_id].min_dist) return true; diff --git a/common/spdat.h b/common/spdat.h index 487b5d00a..bf2d23f30 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -133,7 +133,7 @@ typedef enum { /* 42 */ ST_Directional = 0x2a, //ae around this target between two angles /* 43 */ ST_GroupClientAndPet = 0x2b, /* 44 */ ST_Beam = 0x2c, -/* 45 */ ST_Ring = 0x2d, +/* 45 */ ST_Ring = 0x2d, /* 46 */ ST_TargetsTarget = 0x2e, // uses the target of your target /* 47 */ ST_PetMaster = 0x2f, // uses the master as target /* 48 */ // UNKNOWN @@ -151,10 +151,10 @@ typedef enum { } DmgShieldType; //Spell Effect IDs -// full listing: https://forums.station.sony.com/eq/index.php?threads/enumerated-spa-list.206288/ +// https://forums.daybreakgames.com/eq/index.php?threads/enumerated-spa-list.206288/ // mirror: http://pastebin.com/MYeQqGwe #define SE_CurrentHP 0 // implemented - Heals and nukes, repeates every tic if in a buff -#define SE_ArmorClass 1 // implemented +#define SE_ArmorClass 1 // implemented #define SE_ATK 2 // implemented #define SE_MovementSpeed 3 // implemented - SoW, SoC, etc #define SE_STR 4 // implemented @@ -197,7 +197,7 @@ typedef enum { #define SE_Destroy 41 // implemented - Disintegrate, Banishment of Shadows #define SE_ShadowStep 42 // implemented #define SE_Berserk 43 // implemented (*not used in any known live spell) Makes client 'Berserk' giving crip blow chance. -#define SE_Lycanthropy 44 // implemented +#define SE_Lycanthropy 44 // implemented #define SE_Vampirism 45 // implemented (*not used in any known live spell) Stackable lifetap from melee. #define SE_ResistFire 46 // implemented #define SE_ResistCold 47 // implemented @@ -247,7 +247,7 @@ typedef enum { #define SE_SummonCorpse 91 // implemented #define SE_InstantHate 92 // implemented - add hate #define SE_StopRain 93 // implemented - Wake of Karana -#define SE_NegateIfCombat 94 // implemented +#define SE_NegateIfCombat 94 // implemented #define SE_Sacrifice 95 // implemented #define SE_Silence 96 // implemented #define SE_ManaPool 97 // implemented @@ -299,7 +299,7 @@ typedef enum { #define SE_LimitCastTimeMin 143 // implemented #define SE_LimitCastTimeMax 144 // implemented (*not used in any known live spell) #define SE_Teleport2 145 // implemented - Banishment of the Pantheon -//#define SE_ElectricityResist 146 // *not implemented (Lightning Rod: 23233) +//#define SE_ElectricityResist 146 // *not implemented (Lightning Rod: 23233) #define SE_PercentalHeal 147 // implemented #define SE_StackingCommand_Block 148 // implemented? #define SE_StackingCommand_Overwrite 149 // implemented? @@ -529,7 +529,7 @@ typedef enum { #define SE_CastOnFadeEffectAlways 373 // implemented - Triggers if fades after natural duration OR from rune/numhits fades. #define SE_ApplyEffect 374 // implemented #define SE_DotCritDmgIncrease 375 // implemented - Increase damage of DoT critical amount -//#define SE_Fling 376 // *not implemented - used in 2 test spells (12945 | Movement Test Spell 1) +//#define SE_Fling 376 // *not implemented - used in 2 test spells (12945 | Movement Test Spell 1) #define SE_CastOnFadeEffectNPC 377 // implemented - Triggers only if fades after natural duration (On live these are usually players spells that effect an NPC). #define SE_SpellEffectResistChance 378 // implemented - Increase chance to resist specific spell effect (base1=value, base2=spell effect id) #define SE_ShadowStepDirectional 379 // implemented - handled by client @@ -560,7 +560,7 @@ typedef enum { #define SE_LimitSpellSubclass 404 // *not implemented - Limits to specific types of spells (see CheckSpellCategory) [Categories NOT defined yet] #define SE_TwoHandBluntBlock 405 // implemented - chance to block attacks when using two hand blunt weapons (similiar to shield block) #define SE_CastonNumHitFade 406 // implemented - casts a spell when a buff fades due to its numhits being depleted -#define SE_CastonFocusEffect 407 // implemented - casts a spell if focus limits are met (ie triggers when a focus effects is applied) +#define SE_CastonFocusEffect 407 // implemented - casts a spell if focus limits are met (ie triggers when a focus effects is applied) #define SE_LimitHPPercent 408 // implemented - limited to a certain percent of your hp(ie heals up to 50%) #define SE_LimitManaPercent 409 // implemented - limited to a certain percent of your mana #define SE_LimitEndPercent 410 // implemented - limited to a certain percent of your end @@ -576,7 +576,7 @@ typedef enum { #define SE_FcLimitUse 420 // implemented - increases numhits count by percent (Note: not used in any known live spells) #define SE_FcIncreaseNumHits 421 // implemented[AA] - increases number of hits a buff has till fade. (focus) #define SE_LimitUseMin 422 // implemented - limit a focus to require a min amount of numhits value (used with above) -#define SE_LimitUseType 423 // implemented - limit a focus to require a certain numhits type +#define SE_LimitUseType 423 // implemented - limit a focus to require a certain numhits type #define SE_GravityEffect 424 // implemented - Pulls/pushes you toward/away the mob at a set pace //#define SE_Display 425 // *not implemented - Illusion: Flying Dragon(21626) //#define SE_IncreaseExtTargetWindow 426 // *not implmented[AA] - increases the capacity of your extended target window @@ -600,9 +600,9 @@ typedef enum { #define SE_ImprovedTaunt 444 // implemented - Locks Aggro On Caster and Decrease other Players Aggro by X% on NPC targets below level Y //#define SE_AddMercSlot 445 // *not implemented[AA] - [Hero's Barracks] Allows you to conscript additional mercs. #define SE_AStacker 446 // implementet - bufff stacking blocker (26219 | Qirik's Watch) -#define SE_BStacker 447 // implemented +#define SE_BStacker 447 // implemented #define SE_CStacker 448 // implemented -#define SE_DStacker 449 // implemented +#define SE_DStacker 449 // implemented #define SE_MitigateDotDamage 450 // implemented DOT spell mitigation rune with max value #define SE_MeleeThresholdGuard 451 // implemented Partial Melee Rune that only is lowered if melee hits are over X amount of damage #define SE_SpellThresholdGuard 452 // implemented Partial Spell Rune that only is lowered if spell hits are over X amount of damage @@ -618,6 +618,7 @@ typedef enum { #define DF_Permanent 50 +#define DF_Aura 51 // note this struct is historical, we don't actually need it to be // aligned to anything, but for maintaining it it is kept in the order that @@ -733,31 +734,31 @@ struct SPDat_Spell_Struct /* 197 */ bool not_extendable; /* 198- 199 */ /* 200 */ bool suspendable; // buff is suspended in suspended buff zones -/* 201 */ int viral_range; +/* 201 */ int viral_range; /* 202 */ /* 203 */ //int songcap; // individual song cap (how live currently does it, not implemented) /* 204 */ /* 205 */ bool no_block; -/* 206 */ +/* 206 */ /* 207 */ int spellgroup; /* 208 */ int rank; //increments AA effects with same name /* 209 */ int powerful_flag; // Need more investigation to figure out what to call this, for now we know -1 makes charm spells not break before their duration is complete, it does alot more though -/* 210 */ // bool DurationFrozen; ??? +/* 210 */ // bool DurationFrozen; ??? /* 211 */ int CastRestriction; //Various restriction categories for spells most seem targetable race related but have also seen others for instance only castable if target hp 20% or lower or only if target out of combat /* 212 */ bool AllowRest; /* 213 */ bool InCombat; //Allow spell if target is in combat /* 214 */ bool OutofCombat; //Allow spell if target is out of combat /* 215 - 217 */ -/* 218 */ int aemaxtargets; //Is used for various AE effects +/* 218 */ int aemaxtargets; //Is used for various AE effects /* 219 */ int maxtargets; //Is used for beam and ring spells for target # limits (not implemented) -/* 220 - 223 */ +/* 220 - 223 */ /* 224 */ bool persistdeath; // buff doesn't get stripped on death /* 225 - 226 */ /* 227 */ float min_dist; //spell power modified by distance from caster (Min Distance) /* 228 */ float min_dist_mod; //spell power modified by distance from caster (Modifier at Min Distance) /* 229 */ float max_dist; //spell power modified by distance from caster (Max Distance) /* 230 */ float max_dist_mod; //spell power modified by distance from caster (Modifier at Max Distance) -/* 231 */ float min_range; //Min casting range +/* 231 */ float min_range; //Min casting range /* 232 - 236 */ uint8 DamageShieldType; // This field does not exist in spells_us.txt }; diff --git a/common/types.h b/common/types.h index 9064b67e0..2e1b5d3f3 100644 --- a/common/types.h +++ b/common/types.h @@ -83,4 +83,20 @@ typedef const char Const_char; //for perl XS #define DLLFUNC extern "C" #endif +// htonll and ntohll already defined on windows +#ifndef WIN32 +# if defined(__linux__) +# include +# elif defined(__FreeBSD__) || defined(__NetBSD__) +# include +# elif defined (__OpenBSD__) +# include +# define be16toh(x) betoh16(x) +# define be32toh(x) betoh32(x) +# define be64toh(x) betoh64(x) +# endif +# define htonll(x) htobe64(x) +# define ntohll(x) be64toh(x) +#endif + #endif diff --git a/common/version.h b/common/version.h index 7b8cfd78c..1813f77c3 100644 --- a/common/version.h +++ b/common/version.h @@ -24,13 +24,13 @@ #define CURRENT_VERSION "1.1.3" -/* - Everytime a Database SQL is added to Github, +/* + Everytime a Database SQL is added to Github, increment CURRENT_BINARY_DATABASE_VERSION number and make sure you update the manifest - Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt + Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9077 +#define CURRENT_BINARY_DATABASE_VERSION 9083 #define COMPILE_DATE __DATE__ #define COMPILE_TIME __TIME__ #ifndef WIN32 diff --git a/loginserver/database_mysql.cpp b/loginserver/database_mysql.cpp index 9cf08563b..cffec5548 100644 --- a/loginserver/database_mysql.cpp +++ b/loginserver/database_mysql.cpp @@ -113,8 +113,8 @@ bool DatabaseMySQL::GetWorldRegistration(string long_name, string short_name, un length = mysql_real_escape_string(db, escaped_short_name, short_name.substr(0, 100).c_str(), short_name.substr(0, 100).length()); escaped_short_name[length+1] = 0; stringstream query(stringstream::in | stringstream::out); - query << "SELECT WSR.ServerID, WSR.ServerTagDescription, WSR.ServerTrusted, SLT.ServerListTypeID, "; - query << "SLT.ServerListTypeDescription, WSR.ServerAdminID FROM " << server.options.GetWorldRegistrationTable(); + query << "SELECT ifnull(WSR.ServerID,999999) AS ServerID, WSR.ServerTagDescription, ifnull(WSR.ServerTrusted,0) AS ServerTrusted, ifnull(SLT.ServerListTypeID,3) AS ServerListTypeID, "; + query << "SLT.ServerListTypeDescription, ifnull(WSR.ServerAdminID,0) AS ServerAdminID FROM " << server.options.GetWorldRegistrationTable(); query << " AS WSR JOIN " << server.options.GetWorldServerTypeTable() << " AS SLT ON WSR.ServerListTypeID = SLT.ServerListTypeID"; query << " WHERE WSR.ServerShortName = '"; query << escaped_short_name; @@ -254,7 +254,7 @@ bool DatabaseMySQL::CreateWorldRegistration(string long_name, string short_name, length = mysql_real_escape_string(db, escaped_short_name, short_name.substr(0, 100).c_str(), short_name.substr(0, 100).length()); escaped_short_name[length+1] = 0; stringstream query(stringstream::in | stringstream::out); - query << "SELECT max(ServerID) FROM " << server.options.GetWorldRegistrationTable(); + query << "SELECT ifnull(max(ServerID),0) FROM " << server.options.GetWorldRegistrationTable(); if(mysql_query(db, query.str().c_str()) != 0) { diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index d144b040e..6e3f0ab1a 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -194,6 +194,7 @@ OP_Consent=0x400e OP_ConsentDeny=0x34c1 OP_AutoFire=0x314e OP_PetCommands=0x0093 +OP_PetHoTT=0x0df4 OP_DeleteSpell=0x305c OP_Surname=0x1a87 OP_ClearSurname=0x17b6 @@ -269,8 +270,8 @@ OP_RequestDuel=0x1ea9 OP_MobRename=0x5040 OP_AugmentItem=0x1627 # Was 0x37cb OP_WeaponEquip1=0x35c3 -OP_WeaponEquip2=0x012f # Was 0x6022 -OP_WeaponUnequip2=0x1076 # Was 0x0110 +OP_PlayerStateAdd=0x012f # Was 0x6022 +OP_PlayerStateRemove=0x1076 # Was 0x0110 OP_ApplyPoison=0x1499 OP_Save=0x2e6f OP_TestBuff=0x046e # Was 0x3772 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 0cc0c69d0..49f5f28b3 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -193,6 +193,7 @@ OP_Consent=0x1fd1 OP_ConsentDeny=0x7a45 OP_AutoFire=0x241e OP_PetCommands=0x0159 +OP_PetHoTT=0x794a OP_DeleteSpell=0x3358 OP_Surname=0x0423 OP_ClearSurname=0x3fb0 @@ -268,8 +269,8 @@ OP_RequestDuel=0x3af1 OP_MobRename=0x2c57 OP_AugmentItem=0x661b OP_WeaponEquip1=0x34a7 -OP_WeaponEquip2=0x559a -OP_WeaponUnequip2=0x2d25 +OP_PlayerStateAdd=0x559a +OP_PlayerStateRemove=0x2d25 OP_ApplyPoison=0x31e6 OP_Save=0x4a39 OP_TestBuff=0x7cb8 @@ -294,8 +295,8 @@ OP_MarkNPC=0x1fb5 OP_MarkRaidNPC=0x5a58 #unimplemented OP_ClearNPCMarks=0x2003 OP_ClearRaidNPCMarks=0x20d3 #unimplemented -OP_DelegateAbility=0x4c9d -OP_SetGroupTarget=0x026 +OP_DelegateAbility=0x76b8 +OP_SetGroupTarget=0x2814 OP_Charm=0x5d92 OP_Stun=0x36a4 OP_SendFindableNPCs=0x4613 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index a5dee668c..2f01ef89b 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -266,8 +266,8 @@ OP_RequestDuel=0x79e0 # C OP_MobRename=0x0a1d # C OP_AugmentItem=0x0370 # C OP_WeaponEquip1=0x719e # C -OP_WeaponEquip2=0x7b6e # C -OP_WeaponUnequip2=0x19a8 # C +OP_PlayerStateAdd=0x7b6e # C +OP_PlayerStateRemove=0x19a8 # C OP_ApplyPoison=0x405b # C OP_Save=0x5c85 # C OP_TestBuff=0x5fc7 # C diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 80aee8bdc..9c58b5684 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -262,8 +262,8 @@ OP_RequestDuel=0x3A2B #Xinu 02/22/09 OP_MobRename=0x6be5 #Trevius 01/16/09 OP_AugmentItem=0x172A #Trevius 03/14/09 OP_WeaponEquip1=0x7260 #Trevius 02/27/09 -OP_WeaponEquip2=0x5C2F #Trevius 02/27/09 -OP_WeaponUnequip2=0x6213 #Trevius 02/27/09 +OP_PlayerStateAdd=0x5C2F #Trevius 02/27/09 +OP_PlayerStateRemove=0x6213 #Trevius 02/27/09 OP_ApplyPoison=0x4543 #WildcardX 03/6/09 OP_Save=0x72F2 #Trevius 03/15/09 OP_TestBuff=0x07BF #/testbuff diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index 2660738de..99ef322a0 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -534,8 +534,8 @@ OP_PVPLeaderBoardDetailsRequest=0x06a2 OP_PVPLeaderBoardDetailsReply=0x246a OP_PickLockSuccess=0x40E7 OP_WeaponEquip1=0x6c5e -OP_WeaponEquip2=0x63da -OP_WeaponUnequip2=0x381d +OP_PlayerStateAdd=0x63da +OP_PlayerStateRemove=0x381d OP_VoiceMacroIn=0x2866 # Client to Server OP_VoiceMacroOut=0x2ec6 # Server to Client OP_CameraEffect=0x0937 # Correct diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 09c6a8252..6f94c92db 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -197,6 +197,7 @@ OP_Consent=0x6bb9 # C OP_ConsentDeny=0x4cd1 # C OP_AutoFire=0x5db5 # C OP_PetCommands=0x7706 # C +OP_PetHoTT=0x2528 OP_DeleteSpell=0x0698 # C OP_Surname=0x44ae # C OP_ClearSurname=0x6705 # C @@ -272,8 +273,8 @@ OP_RequestDuel=0x6cfe # C OP_MobRename=0x0507 # C OP_AugmentItem=0x7c87 # C OP_WeaponEquip1=0x4572 # C -OP_WeaponEquip2=0x399b # C -OP_WeaponUnequip2=0x416b # C +OP_PlayerStateAdd=0x399b # C +OP_PlayerStateRemove=0x416b # C OP_ApplyPoison=0x5cd3 # C OP_Save=0x6618 # C OP_TestBuff=0x3415 # C diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 2ad0b647c..e7aa46221 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -331,9 +331,15 @@ 9075|2015_02_02_logsys_packet_logs_with_dump.sql|SELECT * FROM `logsys_categories` WHERE `log_category_description` LIKE 'Packet: Server -> Client With Dump'|empty| 9076|2015_02_04_average_coin.sql|SHOW COLUMNS FROM `loottable` WHERE Field = 'avgcoin'|contains|smallint 9077|2015_02_12_zone_gravity.sql|SHOW COLUMNS FROM `zone` LIKE 'gravity'|empty| +9078|2015_05_20_BuffInstrumentMod.sql|SHOW COLUMNS FROM `character_buffs` LIKE 'instrument_mod'|empty| +9079|2015_05_23_BuffDurations.sql|SHOW COLUMNS FROM `character_buffs` LIKE 'ticsremaining'|contains|unsigned| +9080|2015_05_23_PetBuffInstrumentMod.sql|SHOW COLUMNS FROM `character_pet_buffs` LIKE 'instrument_mod'|empty| +9081|2015_05_23_dbstr_us.sql|SHOW TABLES LIKE 'db_str'|empty| +9082|2015_05_25_npc_types_texture_fields.sql|SHOW COLUMNS FROM `npc_types` LIKE 'armtexture'|empty| +9083|2015_06_07_aa_update.sql|SHOW COLUMNS FROM `character_alternate_abilities` LIKE 'charges'|empty| # Upgrade conditions: -# This won't be needed after this system is implemented, but it is used database that are not +# This won't be needed after this system is implemented, but it is used database that are not # yet using the versioning system to figure out where the database is schema wise to determine # which updates are necessary to run # diff --git a/utils/sql/git/optional/2014_03_17_EnforceAugmentRules.sql b/utils/sql/git/optional/2014_03_17_EnforceAugmentRules.sql index e89d4a437..25ec3734d 100644 --- a/utils/sql/git/optional/2014_03_17_EnforceAugmentRules.sql +++ b/utils/sql/git/optional/2014_03_17_EnforceAugmentRules.sql @@ -1,3 +1,3 @@ INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Inventory:EnforceAugmentRestriction', 'false', 'Forces augment slot restrictions.'); INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Inventory:EnforceAugmentUsability', 'false', 'Forces augmented item usability.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Inventory:EnforceAugmentWear', 'false', 'Forces augment wear slot validation.'); \ No newline at end of file +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Inventory:EnforceAugmentWear', 'false', 'Forces augment wear slot validation.'); diff --git a/utils/sql/git/optional/2015_2_5_UseAdditiveFocusFromWornSlot.sql b/utils/sql/git/optional/2015_02_05_UseAdditiveFocusFromWornSlot.sql similarity index 100% rename from utils/sql/git/optional/2015_2_5_UseAdditiveFocusFromWornSlot.sql rename to utils/sql/git/optional/2015_02_05_UseAdditiveFocusFromWornSlot.sql diff --git a/utils/sql/git/optional/2015_2_6_AdditiveBonusWornType.sql b/utils/sql/git/optional/2015_02_06_AdditiveBonusWornType.sql similarity index 100% rename from utils/sql/git/optional/2015_2_6_AdditiveBonusWornType.sql rename to utils/sql/git/optional/2015_02_06_AdditiveBonusWornType.sql diff --git a/utils/sql/git/optional/2015_04_30_MeleePush.sql b/utils/sql/git/optional/2015_04_30_MeleePush.sql new file mode 100644 index 000000000..e640d4237 --- /dev/null +++ b/utils/sql/git/optional/2015_04_30_MeleePush.sql @@ -0,0 +1,2 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:MeleePush', 'true', 'Turns on Melee Push.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:MeleePushChance', '50', 'Chance that an NPC can be pushed from melee.'); diff --git a/utils/sql/git/optional/2015_05_18_FishingLineLength.sql b/utils/sql/git/optional/2015_05_18_FishingLineLength.sql new file mode 100644 index 000000000..f57c4d02e --- /dev/null +++ b/utils/sql/git/optional/2015_05_18_FishingLineLength.sql @@ -0,0 +1 @@ +UPDATE rule_values SET rule_value=100 WHERE rule_name='Watermap:FishingLineLength'; diff --git a/utils/sql/git/optional/2015_06_07_SpellsTargetBuffsRule.sql b/utils/sql/git/optional/2015_06_07_SpellsTargetBuffsRule.sql new file mode 100644 index 000000000..1dcc9d922 --- /dev/null +++ b/utils/sql/git/optional/2015_06_07_SpellsTargetBuffsRule.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AlwaysSendTargetBuffs', 'false', 'Allows the server to send the targets buffs ignoring the LAA.'); diff --git a/utils/sql/git/optional/2015_06_07_TransformSummonedBagsRule.sql b/utils/sql/git/optional/2015_06_07_TransformSummonedBagsRule.sql new file mode 100644 index 000000000..d0af46a24 --- /dev/null +++ b/utils/sql/git/optional/2015_06_07_TransformSummonedBagsRule.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Inventory:TransformSummonedBags', 'true', 'Transforms summoned bags into disenchanted ones instead of deleting.'); diff --git a/utils/sql/git/required/2015_05_20_BuffInstrumentMod.sql b/utils/sql/git/required/2015_05_20_BuffInstrumentMod.sql new file mode 100644 index 000000000..140fe5b66 --- /dev/null +++ b/utils/sql/git/required/2015_05_20_BuffInstrumentMod.sql @@ -0,0 +1 @@ +ALTER TABLE `character_buffs` ADD COLUMN `instrument_mod` int(10) DEFAULT 10 NOT NULL; diff --git a/utils/sql/git/required/2015_05_23_BuffDurations.sql b/utils/sql/git/required/2015_05_23_BuffDurations.sql new file mode 100644 index 000000000..5f03039a6 --- /dev/null +++ b/utils/sql/git/required/2015_05_23_BuffDurations.sql @@ -0,0 +1,2 @@ +ALTER TABLE `character_buffs` CHANGE COLUMN `ticsremaining` `ticsremaining` INT(11) SIGNED NOT NULL; +ALTER TABLE `merc_buffs` CHANGE COLUMN `TicsRemaining` `TicsRemaining` INT(11) SIGNED NOT NULL DEFAULT 0; diff --git a/utils/sql/git/required/2015_05_23_PetBuffInstrumentMod.sql b/utils/sql/git/required/2015_05_23_PetBuffInstrumentMod.sql new file mode 100644 index 000000000..40cc453dc --- /dev/null +++ b/utils/sql/git/required/2015_05_23_PetBuffInstrumentMod.sql @@ -0,0 +1 @@ +ALTER TABLE `character_pet_buffs` ADD COLUMN `instrument_mod` tinyint UNSIGNED DEFAULT 10 NOT NULL; diff --git a/utils/sql/git/required/2015_05_23_dbstr_us.sql b/utils/sql/git/required/2015_05_23_dbstr_us.sql new file mode 100644 index 000000000..1eba0afa6 --- /dev/null +++ b/utils/sql/git/required/2015_05_23_dbstr_us.sql @@ -0,0 +1,6 @@ +CREATE TABLE `db_str` ( + `id` INT(10) NOT NULL, + `type` INT(10) NOT NULL, + `value` TEXT NOT NULL, + PRIMARY KEY (`id`, `type`) +); diff --git a/utils/sql/git/required/2015_05_25_npc_types_texture_fields.sql b/utils/sql/git/required/2015_05_25_npc_types_texture_fields.sql new file mode 100644 index 000000000..acd8d26b6 --- /dev/null +++ b/utils/sql/git/required/2015_05_25_npc_types_texture_fields.sql @@ -0,0 +1,6 @@ +ALTER TABLE npc_types +ADD COLUMN `armtexture` tinyint(2) NOT NULL DEFAULT '0' AFTER `raid_target`, +ADD COLUMN `bracertexture` tinyint(2) NOT NULL DEFAULT '0' AFTER `armtexture`, +ADD COLUMN `handtexture` tinyint(2) NOT NULL DEFAULT '0' AFTER `bracertexture`, +ADD COLUMN `legtexture` tinyint(2) NOT NULL DEFAULT '0' AFTER `handtexture`, +ADD COLUMN `feettexture` tinyint(2) NOT NULL DEFAULT '0' AFTER `legtexture`; \ No newline at end of file diff --git a/utils/sql/git/required/2015_06_07_aa_update.sql b/utils/sql/git/required/2015_06_07_aa_update.sql new file mode 100644 index 000000000..a888a75ea --- /dev/null +++ b/utils/sql/git/required/2015_06_07_aa_update.sql @@ -0,0 +1 @@ +ALTER TABLE character_alternate_abilities ADD COLUMN charges SMALLINT(11) UNSIGNED NOT NULL DEFAULT 0; diff --git a/world/worlddb.cpp b/world/worlddb.cpp index d1b10f64d..86d115233 100644 --- a/world/worlddb.cpp +++ b/world/worlddb.cpp @@ -105,6 +105,7 @@ void WorldDatabase::GetCharSelectInfo(uint32 accountID, EQApplicationPacket **ou memset(&pp, 0, sizeof(PlayerProfile_Struct)); /* Fill CharacterSelectEntry_Struct */ + memset(cse->Name, 0, sizeof(cse->Name)); strcpy(cse->Name, row[1]); cse->Class = (uint8)atoi(row[4]); cse->Race = (uint32)atoi(row[3]); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index ffc22352a..17cac7f62 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -190,7 +190,7 @@ bool ZoneServer::Process() { Log.Out(Logs::Detail, Logs::World_Server,"Zone authorization failed."); auto pack = new ServerPacket(ServerOP_ZAAuthFailed); SendPacket(pack); - delete pack; + safe_delete(pack); Disconnect(); return false; } @@ -201,7 +201,7 @@ bool ZoneServer::Process() { Log.Out(Logs::Detail, Logs::World_Server,"Zone authorization failed."); auto pack = new ServerPacket(ServerOP_ZAAuthFailed); SendPacket(pack); - delete pack; + safe_delete(pack); Disconnect(); return false; } @@ -779,7 +779,7 @@ bool ZoneServer::Process() { whom->wrace = whoall->wrace; strcpy(whom->whom,whoall->whom); client_list.SendWhoAll(whoall->fromid,whoall->from, whoall->admin, whom, this); - delete whom; + safe_delete(whom); break; } case ServerOP_RequestOnlineGuildMembers: { @@ -958,13 +958,13 @@ bool ZoneServer::Process() { tod->start_eqtime=zoneserver_list.worldclock.getStartEQTime(); tod->start_realtime=zoneserver_list.worldclock.getStartRealTime(); SendPacket(pack); - delete pack; + safe_delete(pack); break; } case ServerOP_SetWorldTime: { Log.Out(Logs::Detail, Logs::World_Server,"Received SetWorldTime"); eqTimeOfDay* newtime = (eqTimeOfDay*) pack->pBuffer; - zoneserver_list.worldclock.setEQTimeOfDay(newtime->start_eqtime, newtime->start_realtime); + zoneserver_list.worldclock.SetCurrentEQTimeOfDay(newtime->start_eqtime, newtime->start_realtime); Log.Out(Logs::Detail, Logs::World_Server,"New time = %d-%d-%d %d:%d (%d)\n", newtime->start_eqtime.year, newtime->start_eqtime.month, (int)newtime->start_eqtime.day, (int)newtime->start_eqtime.hour, (int)newtime->start_eqtime.minute, (int)newtime->start_realtime); zoneserver_list.worldclock.saveFile(WorldConfig::get()->EQTimeFile.c_str()); zoneserver_list.SendTimeSync(); @@ -1059,8 +1059,7 @@ bool ZoneServer::Process() { } else { - delete pack; - pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); + auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; strcpy(scs->grantname, s->grantname); strcpy(scs->ownername, s->ownername); @@ -1076,6 +1075,7 @@ bool ZoneServer::Process() { else { Log.Out(Logs::Detail, Logs::World_Server, "Unable to locate zone record for instance id %u in zoneserver list for ServerOP_Consent_Response operation.", s->instance_id); } + safe_delete(pack); } } else @@ -1091,8 +1091,7 @@ bool ZoneServer::Process() { } else { // send target not found back to requester - delete pack; - pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); + auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; strcpy(scs->grantname, s->grantname); strcpy(scs->ownername, s->ownername); @@ -1107,13 +1106,13 @@ bool ZoneServer::Process() { else { Log.Out(Logs::Detail, Logs::World_Server, "Unable to locate zone record for zone id %u in zoneserver list for ServerOP_Consent_Response operation.", s->zone_id); } + safe_delete(pack); } } } else { // send target not found back to requester - delete pack; - pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); + auto pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; strcpy(scs->grantname, s->grantname); strcpy(scs->ownername, s->ownername); @@ -1128,6 +1127,7 @@ bool ZoneServer::Process() { else { Log.Out(Logs::Detail, Logs::World_Server, "Unable to locate zone record for zone id %u in zoneserver list for ServerOP_Consent_Response operation.", s->zone_id); } + safe_delete(pack); } break; } @@ -1313,15 +1313,17 @@ bool ZoneServer::Process() { } default: { - Log.Out(Logs::Detail, Logs::World_Server,"Unknown ServerOPcode from zone 0x%04x, size %d",pack->opcode,pack->size); + Log.Out(Logs::Detail, Logs::World_Server, "Unknown ServerOPcode from zone 0x%04x, size %d", pack->opcode, pack->size); DumpPacket(pack->pBuffer, pack->size); break; } } - if (pack) - delete pack; - else - Log.Out(Logs::Detail, Logs::World_Server, "Zoneserver process tried to delete pack when pack does not exist."); + if (pack) { + safe_delete(pack); + } + else { + Log.Out(Logs::Detail, Logs::World_Server, "Zoneserver process attempted to delete pack when pack does not exist."); + } } return true; } diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 44a6d02c8..5573e6f4f 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -20,6 +20,7 @@ SET(zone_sources embparser_api.cpp embperl.cpp embxs.cpp + encounter.cpp entity.cpp exp.cpp fearpath.cpp @@ -35,6 +36,7 @@ SET(zone_sources lua_corpse.cpp lua_client.cpp lua_door.cpp + lua_encounter.cpp lua_entity.cpp lua_entity_list.cpp lua_general.cpp @@ -137,6 +139,7 @@ SET(zone_headers embparser.h embperl.h embxs.h + encounter.h entity.h errmsg.h event_codes.h @@ -148,6 +151,7 @@ SET(zone_headers lua_bit.h lua_client.h lua_corpse.h + lua_encounter.h lua_entity.h lua_entity_list.h lua_general.h diff --git a/zone/aa.cpp b/zone/aa.cpp index 7c7b2eb11..51872cce2 100644 --- a/zone/aa.cpp +++ b/zone/aa.cpp @@ -1104,9 +1104,9 @@ void Client::SendAATable() { uint32 i; for(i=0;i < MAX_PP_AA_ARRAY;i++){ - aa2->aa_list[i].aa_skill = aa[i]->AA; - aa2->aa_list[i].aa_value = aa[i]->value; - aa2->aa_list[i].unknown08 = 0; + aa2->aa_list[i].AA = aa[i]->value ? aa[i]->AA : 0; // bit of a hack to prevent expendables punching a hole + aa2->aa_list[i].value = aa[i]->value; + aa2->aa_list[i].charges = aa[i]->charges; } QueuePacket(outapp); safe_delete(outapp); @@ -1309,11 +1309,9 @@ void Client::SendAA(uint32 id, int seq) { SendAA_Struct* saa_next = nullptr; saa_next = zone->FindAA(saa->sof_next_id); - // hard-coding values like this is dangerous and makes adding/updating clients a nightmare... - if (saa_next && - (((GetClientVersionBit() == 4) && (saa_next->clientver > 4)) - || ((GetClientVersionBit() == 8) && (saa_next->clientver > 5)) - || ((GetClientVersionBit() == 16) && (saa_next->clientver > 6)))){ + // this check should work as long as we continue to just add the clients and just increase + // each number .... + if (saa_next && static_cast(GetClientVersion()) < saa_next->clientver - 1) { saa->next_id=0xFFFFFFFF; } } @@ -1397,26 +1395,33 @@ uint32 Client::GetAA(uint32 aa_id) const { bool Client::SetAA(uint32 aa_id, uint32 new_value) { aa_points[aa_id] = new_value; uint32 cur; + auto sendaa = zone->FindAA(aa_id); // this is a bit hacky + uint32 charges = sendaa->special_category == 7 && new_value ? 1 : 0; for(cur=0;cur < MAX_PP_AA_ARRAY;cur++){ if((aa[cur]->value > 1) && ((aa[cur]->AA - aa[cur]->value + 1)== aa_id)){ aa[cur]->value = new_value; if(new_value > 0) aa[cur]->AA++; - else - aa[cur]->AA = 0; + aa[cur]->charges = charges; return true; } else if((aa[cur]->value == 1) && (aa[cur]->AA == aa_id)){ aa[cur]->value = new_value; if(new_value > 0) aa[cur]->AA++; - else - aa[cur]->AA = 0; + aa[cur]->charges = charges; + return true; + } + // hack to prevent expendable exploit, we should probably be reshuffling the array to fix the hole + else if(aa[cur]->value == 0 && new_value == 1 && aa[cur]->AA == aa_id) { + aa[cur]->value = new_value; + aa[cur]->charges = charges; return true; } else if(aa[cur]->AA==0){ //end of list aa[cur]->AA = aa_id; aa[cur]->value = new_value; + aa[cur]->charges = charges; return true; } } @@ -1487,8 +1492,10 @@ void Client::ResetAA(){ for (i=0; i < MAX_PP_AA_ARRAY; i++) { aa[i]->AA = 0; aa[i]->value = 0; - m_pp.aa_array[MAX_PP_AA_ARRAY].AA = 0; - m_pp.aa_array[MAX_PP_AA_ARRAY].value = 0; + aa[i]->charges = 0; + m_pp.aa_array[i].AA = 0; + m_pp.aa_array[i].value = 0; + m_pp.aa_array[i].charges= 0; } std::map::iterator itr; diff --git a/zone/attack.cpp b/zone/attack.cpp index c88a9af19..95cf1a376 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -982,14 +982,24 @@ int Mob::GetWeaponDamage(Mob *against, const ItemInst *weapon_item, uint32 *hate return 0; } else{ - if((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30){ - dmg = GetMonkHandToHandDamage(); - if (hate) *hate += dmg; + bool MagicGloves=false; + if (IsClient()) { + ItemInst *gloves=CastToClient()->GetInv().GetItem(MainHands); + if (gloves != nullptr) { + MagicGloves = gloves->GetItem()->Magic; + } + } + + if((GetClass() == MONK || GetClass() == BEASTLORD)) { + if(MagicGloves || GetLevel() >= 30){ + dmg = GetMonkHandToHandDamage(); + if (hate) *hate += dmg; + } } else if(GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)){ //pets wouldn't actually use this but... dmg = 1; //it gives us an idea if we can hit } - else if(GetSpecialAbility(SPECATK_MAGICAL)){ + else if(MagicGloves || GetSpecialAbility(SPECATK_MAGICAL)){ dmg = 1; } else @@ -2134,6 +2144,10 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack if(give_exp && give_exp->IsClient()) give_exp_client = give_exp->CastToClient(); + //do faction hits even if we are a merchant, so long as a player killed us + if (give_exp_client && !RuleB(NPC, EnableMeritBasedFaction)) + hate_list.DoFactionHits(GetNPCFactionID()); + bool IsLdonTreasure = (this->GetClass() == LDON_TREASURE); if (give_exp_client && !IsCorpse()) { @@ -2277,10 +2291,6 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack } } - //do faction hits even if we are a merchant, so long as a player killed us - if(give_exp_client && !RuleB(NPC, EnableMeritBasedFaction)) - hate_list.DoFactionHits(GetNPCFactionID()); - if (!HasOwner() && !IsMerc() && class_ != MERCHANT && class_ != ADVENTUREMERCHANT && !GetSwarmInfo() && MerchantType == 0 && killer && (killer->IsClient() || (killer->HasOwner() && killer->GetUltimateOwner()->IsClient()) || (killer->IsNPC() && killer->CastToNPC()->GetSwarmInfo() && killer->CastToNPC()->GetSwarmInfo()->GetOwner() && killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient()))) @@ -3683,7 +3693,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons //send an HP update if we are hurt if(GetHP() < GetMaxHP()) - SendHPUpdate(); + SendHPUpdate(!iBuffTic); // the OP_Damage actually updates the client in these cases, so we skill them } //end `if damage was done` //send damage packet... @@ -3700,6 +3710,23 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons a->type = SkillDamageTypes[skill_used]; // was 0x1c a->damage = damage; a->spellid = spell_id; + a->meleepush_xy = attacker->GetHeading() * 2.0f; + if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && + (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { + a->force = EQEmu::GetSkillMeleePushForce(skill_used); + // update NPC stuff + auto new_pos = glm::vec3(m_Position.x + (a->force * std::sin(a->meleepush_xy) + m_Delta.x), + m_Position.y + (a->force * std::cos(a->meleepush_xy) + m_Delta.y), m_Position.z); + if (zone->zonemap && zone->zonemap->CheckLoS(glm::vec3(m_Position), new_pos)) { // If we have LoS on the new loc it should be reachable. + if (IsNPC()) { + // Is this adequate? + Teleport(new_pos); + SendPosUpdate(); + } + } else { + a->force = 0.0f; // we couldn't move there, so lets not + } + } //Note: if players can become pets, they will not receive damage messages of their own //this was done to simplify the code here (since we can only effectively skip one mob on queue) @@ -3968,7 +3995,7 @@ void Mob::TryWeaponProc(const ItemInst* weapon_g, Mob *on, uint16 hand) { } // Innate + aug procs from weapons - // TODO: powersource procs + // TODO: powersource procs -- powersource procs are on invis augs, so shouldn't need anything extra TryWeaponProc(weapon_g, weapon_g->GetItem(), on, hand); // Procs from Buffs and AA both melee and range TrySpellProc(weapon_g, weapon_g->GetItem(), on, hand); @@ -4054,7 +4081,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const ItemData *weapon, Mob *on, u } } } - // TODO: Powersource procs + // TODO: Powersource procs -- powersource procs are from augs so shouldn't need anything extra return; } @@ -4116,7 +4143,7 @@ void Mob::TrySpellProc(const ItemInst *inst, const ItemData *weapon, Mob *on, ui outapp->priority = 3; entity_list.QueueCloseClients(this, outapp, false, 200, 0, true); safe_delete(outapp); - ExecWeaponProc(nullptr, SpellProcs[i].spellID, on); + ExecWeaponProc(nullptr, SpellProcs[i].spellID, on, SpellProcs[i].level_override); CheckNumHitsRemaining(NumHit::OffensiveSpellProcs, 0, SpellProcs[i].base_spellID); } else { @@ -4222,7 +4249,7 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack } #ifdef BOTS - if (this->IsPet() && this->GetOwner()->IsBot()) { + if (this->IsPet() && this->GetOwner() && this->GetOwner()->IsBot()) { this->TryPetCriticalHit(defender,skill,damage); return; } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index f650e11ce..819c83edb 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -428,7 +428,7 @@ void Client::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAu newbon->DSMitigation += item->DSMitigation; } if (item->Worn.Effect > 0 && item->Worn.Type == ET_WornEffect) {// latent effects - ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, newbon, 0, item->Worn.Type); + ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, newbon, 0, item->Worn.Type); } if (item->Focus.Effect>0 && (item->Focus.Type == ET_Focus)) { // focus effects @@ -559,7 +559,7 @@ void Client::AdditiveWornBonuses(const ItemInst *inst, StatBonuses* newbon, bool /* Powerful Non-live like option allows developers to add worn effects on items that can stack with other worn effects of the same spell effect type, instead of only taking the highest value. - Ie Cleave I = 40 pct cleave - So if you equip 3 cleave I items you will have a 120 pct cleave bonus. + Ie Cleave I = 40 pct cleave - So if you equip 3 cleave I items you will have a 120 pct cleave bonus. To enable use RuleI(Spells, AdditiveBonusWornType) Setting value = 2 Will force all live items to automatically be calculated additivily Setting value to anything else will indicate the item 'worntype' that if set to the same, will cause the bonuses to use this calculation @@ -579,7 +579,7 @@ void Client::AdditiveWornBonuses(const ItemInst *inst, StatBonuses* newbon, bool if(GetLevel() < item->ReqLevel) return; - + if (item->Worn.Effect > 0 && item->Worn.Type == RuleI(Spells, AdditiveBonusWornType)) ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, newbon, 0, item->Worn.Type);// Non-live like - Addititive latent effects @@ -691,7 +691,7 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) continue; Log.Out(Logs::Detail, Logs::AA, "Applying Effect %d from AA %u in slot %d (base1: %d, base2: %d) on %s", effect, aaid, slot, base1, base2, this->GetCleanName()); - + uint8 focus = IsFocusEffect(0, 0, true,effect); if (focus) { @@ -1007,7 +1007,7 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) case SE_BlockBehind: newbon->BlockBehind += base1; break; - + case SE_StrikeThrough: case SE_StrikeThrough2: newbon->StrikeThrough += base1; @@ -1313,7 +1313,7 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) case SE_Vampirism: newbon->Vampirism += base1; - break; + break; case SE_FrenziedDevastation: newbon->FrenziedDevastation += base2; @@ -1416,7 +1416,7 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) } case SE_SkillProcSuccess:{ - + for(int e = 0; e < MAX_SKILL_PROCS; e++) { if(newbon->SkillProcSuccess[e] && newbon->SkillProcSuccess[e] == aaid) @@ -1449,7 +1449,7 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) int buff_count = GetMaxTotalSlots(); for(i = 0; i < buff_count; i++) { if(buffs[i].spellid != SPELL_UNKNOWN){ - ApplySpellsBonuses(buffs[i].spellid, buffs[i].casterlevel, newbon, buffs[i].casterid, 0, buffs[i].ticsremaining,i); + ApplySpellsBonuses(buffs[i].spellid, buffs[i].casterlevel, newbon, buffs[i].casterid, 0, buffs[i].ticsremaining, i, buffs[i].instrument_mod); if (buffs[i].numhits > 0) Numhits(true); @@ -1472,8 +1472,9 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells. } -void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* new_bonus, uint16 casterId, uint8 WornType, uint32 ticsremaining, int buffslot, - bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max) +void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *new_bonus, uint16 casterId, + uint8 WornType, int32 ticsremaining, int buffslot, int instrument_mod, + bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max) { int i, effect_value, base2, max, effectid; bool AdditiveWornBonus = false; @@ -1509,9 +1510,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if (WornType && (RuleI(Spells, AdditiveBonusWornType) == WornType)) AdditiveWornBonus = true; - + effectid = spells[spell_id].effectid[i]; - effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining); + effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, instrument_mod, caster, ticsremaining); base2 = spells[spell_id].base2[i]; max = spells[spell_id].max[i]; } @@ -1620,10 +1621,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if (effect_value > 0 && effect_value > new_bonus->inhibitmelee) { effect_value -= ((effect_value * GetSlowMitigation()/100)); - if (effect_value > new_bonus->inhibitmelee) + if (effect_value > new_bonus->inhibitmelee) new_bonus->inhibitmelee = effect_value; } - + break; } @@ -1839,7 +1840,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne new_bonus->DamageShieldType = GetDamageShieldType(spell_id, max); else new_bonus->DamageShieldType = GetDamageShieldType(spell_id); - + break; } @@ -2020,7 +2021,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_Vampirism: new_bonus->Vampirism += effect_value; - break; + break; case SE_AllInstrumentMod: { @@ -2263,7 +2264,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_CriticalSpellChance: { new_bonus->CriticalSpellChance += effect_value; - + if (base2 > new_bonus->SpellCritDmgIncNoStack) new_bonus->SpellCritDmgIncNoStack = base2; break; @@ -2473,7 +2474,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_NegateAttacks: { - if (!new_bonus->NegateAttacks[0] || + if (!new_bonus->NegateAttacks[0] || ((new_bonus->NegateAttacks[0] && new_bonus->NegateAttacks[2]) && (new_bonus->NegateAttacks[2] < max))){ new_bonus->NegateAttacks[0] = 1; new_bonus->NegateAttacks[1] = buffslot; @@ -2493,7 +2494,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne break; } - + case SE_MeleeThresholdGuard: { if (new_bonus->MeleeThresholdGuard[0] < effect_value){ @@ -2860,17 +2861,17 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne new_bonus->NegateIfCombat = true; break; - case SE_Screech: + case SE_Screech: new_bonus->Screech = effect_value; break; case SE_AlterNPCLevel: if (IsNPC()){ - if (!new_bonus->AlterNPCLevel - || ((effect_value < 0) && (new_bonus->AlterNPCLevel > effect_value)) + if (!new_bonus->AlterNPCLevel + || ((effect_value < 0) && (new_bonus->AlterNPCLevel > effect_value)) || ((effect_value > 0) && (new_bonus->AlterNPCLevel < effect_value))) { - + int tmp_lv = GetOrigLevel() + effect_value; if (tmp_lv < 1) tmp_lv = 1; @@ -2908,7 +2909,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne new_bonus->BerserkSPA = true; break; - + case SE_Metabolism: new_bonus->Metabolism += effect_value; break; @@ -3009,7 +3010,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne } case SE_SkillProc:{ - + for(int e = 0; e < MAX_SKILL_PROCS; e++) { if(new_bonus->SkillProc[e] && new_bonus->SkillProc[e] == spell_id) @@ -3024,7 +3025,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne } case SE_SkillProcSuccess:{ - + for(int e = 0; e < MAX_SKILL_PROCS; e++) { if(new_bonus->SkillProcSuccess[e] && new_bonus->SkillProcSuccess[e] == spell_id) @@ -3040,9 +3041,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table if (IsAISpellEffect) { - + //Non-Focused Effect to modify incoming spell damage by resist type. - case SE_FcSpellVulnerability: + case SE_FcSpellVulnerability: ModVulnerability(base2, effect_value); break; } @@ -4394,7 +4395,7 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) aabonuses.SlayUndead[0] = effect_value; aabonuses.SlayUndead[1] = effect_value; break; - + case SE_DoubleRangedAttack: spellbonuses.DoubleRangedAttack = effect_value; aabonuses.DoubleRangedAttack = effect_value; @@ -4414,7 +4415,7 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) aabonuses.ShieldEquipDmgMod[1] = effect_value; itembonuses.ShieldEquipDmgMod[0] = effect_value; itembonuses.ShieldEquipDmgMod[1] = effect_value; - break; + break; case SE_TriggerMeleeThreshold: spellbonuses.TriggerMeleeThreshold = false; diff --git a/zone/bot.cpp b/zone/bot.cpp index 6335ad072..2f02abe12 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -13,19 +13,16 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm if(botOwner) { this->SetBotOwner(botOwner); this->_botOwnerCharacterID = botOwner->CharacterID(); - } - else { + } else { this->SetBotOwner(0); this->_botOwnerCharacterID = 0; } _guildRank = 0; _guildId = 0; - _lastTotalPlayTime = 0; _startTotalPlayTime = time(&_startTotalPlayTime); _lastZoneId = 0; - _baseMR = npcTypeData.MR; _baseCR = npcTypeData.CR; _baseDR = npcTypeData.DR; @@ -46,7 +43,6 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; - SetBotID(0); SetBotSpellID(0); SetSpawnStatus(false); @@ -67,55 +63,43 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm SetHealRotationTimer(0); SetNumHealRotationMembers(0); SetBardUseOutOfCombatSongs(GetClass() == BARD); + SetShowHelm(true); CalcChanceToCast(); rest_timer.Disable(); - SetFollowDistance(184); - // Do this once and only in this constructor GenerateAppearance(); - GenerateBaseStats(); GenerateArmorClass(); - // Calculate HitPoints Last As It Uses Base Stats cur_hp = GenerateBaseHitPoints(); cur_mana = GenerateBaseManaPoints(); cur_end = CalcBaseEndurance(); - hp_regen = CalcHPRegen(); mana_regen = CalcManaRegen(); end_regen = CalcEnduranceRegen(); - - for (int i = 0; i < MaxTimer; i++) { + for (int i = 0; i < MaxTimer; i++) timers[i] = 0; - } - for(int i = 0; i < MaxHealRotationTargets; i++) { + for(int i = 0; i < MaxHealRotationTargets; i++) _healRotationTargets[i] = 0; - } strcpy(this->name, this->GetCleanName()); - memset(&m_Light, 0, sizeof(LightProfile_Struct)); } // This constructor is used when the bot is loaded out of the database Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double totalPlayTime, uint32 lastZoneId, NPCType npcTypeData) : NPC(&npcTypeData, nullptr, glm::vec4(), 0, false), rest_timer(1) { this->_botOwnerCharacterID = botOwnerCharacterID; - - if(this->_botOwnerCharacterID > 0) { + if(this->_botOwnerCharacterID > 0) this->SetBotOwner(entity_list.GetClientByCharID(this->_botOwnerCharacterID)); - } _guildRank = 0; _guildId = 0; - _lastTotalPlayTime = totalPlayTime; _startTotalPlayTime = time(&_startTotalPlayTime); _lastZoneId = lastZoneId; berserk = false; - _baseMR = npcTypeData.MR; _baseCR = npcTypeData.CR; _baseDR = npcTypeData.DR; @@ -135,11 +119,9 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to _baseGender = npcTypeData.gender; cur_hp = npcTypeData.cur_hp; cur_mana = npcTypeData.Mana; - RestRegenHP = 0; RestRegenMana = 0; RestRegenEndurance = 0; - SetBotID(botID); SetBotSpellID(botSpellsID); SetSpawnStatus(false); @@ -163,61 +145,49 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to SetNumHealRotationMembers(0); CalcChanceToCast(); rest_timer.Disable(); - SetFollowDistance(184); - strcpy(this->name, this->GetCleanName()); - database.GetBotInspectMessage(this->GetBotID(), &_botInspectMessage); - LoadGuildMembership(&_guildId, &_guildRank, &_guildName); - std::string TempErrorMessage; - EquipBot(&TempErrorMessage); - if(!TempErrorMessage.empty()) { - // TODO: log error message to zone error log if(GetBotOwner()) GetBotOwner()->Message(13, TempErrorMessage.c_str()); } - for (int i = 0; i < MaxTimer; i++) { + for (int i = 0; i < MaxTimer; i++) timers[i] = 0; - } - for(int i = 0; i < MaxHealRotationTargets; i++) { + for(int i = 0; i < MaxHealRotationTargets; i++) _healRotationTargets[i] = 0; - } GenerateBaseStats(); - LoadTimers(); LoadAAs(); LoadBuffs(); - CalcBotStats(false); - hp_regen = CalcHPRegen(); mana_regen = CalcManaRegen(); end_regen = CalcEnduranceRegen(); - if(cur_hp > max_hp) cur_hp = max_hp; + if(cur_hp <= 0) { SetHP(max_hp/5); SetMana(0); BuffFadeAll(); SpellOnTarget(756, this); // Rezz effects } + if(cur_mana > max_mana) cur_mana = max_mana; + cur_end = max_end; } Bot::~Bot() { AI_Stop(); - if(HasGroup()) Bot::RemoveBotFromGroup(this, GetGroup()); @@ -236,65 +206,45 @@ void Bot::SetBotSpellID(uint32 newSpellID) { this->npc_spells_id = newSpellID; } -uint32 Bot::GetBotArcheryRange() -{ +uint32 Bot::GetBotArcheryRange() { const ItemInst *range_inst = GetBotItem(MainRange); const ItemInst *ammo_inst = GetBotItem(MainAmmo); - - // empty slots if (!range_inst || !ammo_inst) return 0; const ItemData *range_item = range_inst->GetItem(); const ItemData *ammo_item = ammo_inst->GetItem(); - - // no item struct for whatever reason - if (!range_item || !ammo_item) - return 0; - - // bad item types - if (range_item->ItemType != ItemTypeBow || ammo_item->ItemType != ItemTypeArrow) + if (!range_item || !ammo_item || range_item->ItemType != ItemTypeBow || ammo_item->ItemType != ItemTypeArrow) return 0; // everything is good! - return range_item->Range + ammo_item->Range; + return (range_item->Range + ammo_item->Range); } void Bot::ChangeBotArcherWeapons(bool isArcher) { - if((GetClass()==WARRIOR) || (GetClass()==PALADIN) || (GetClass()==RANGER) - || (GetClass()==SHADOWKNIGHT) || (GetClass()==ROGUE)) - { + if((GetClass()==WARRIOR) || (GetClass()==PALADIN) || (GetClass()==RANGER) || (GetClass()==SHADOWKNIGHT) || (GetClass()==ROGUE)) { if(!isArcher) { BotAddEquipItem(MainPrimary, GetBotItemBySlot(MainPrimary)); BotAddEquipItem(MainSecondary, GetBotItemBySlot(MainSecondary)); - //archerbot->SendWearChange(MATERIAL_PRIMARY); - //archerbot->SendWearChange(MATERIAL_SECONDARY); SetAttackTimer(); - Say("My blade is ready."); - } - else { - //archerbot->SendWearChange(MATERIAL_PRIMARY); - //archerbot->SendWearChange(MATERIAL_SECONDARY); + BotGroupSay(this, "My blade is ready."); + } else { BotRemoveEquipItem(MainPrimary); BotRemoveEquipItem(MainSecondary); - //archerbot->SendBotArcheryWearChange(MATERIAL_PRIMARY, archeryMaterial, archeryColor); BotAddEquipItem(MainAmmo, GetBotItemBySlot(MainAmmo)); BotAddEquipItem(MainSecondary, GetBotItemBySlot(MainRange)); SetAttackTimer(); - Say("My bow is true and ready."); + BotGroupSay(this, "My bow is true and ready."); } } - else { - Say("I don't know how to use a bow."); - } + else + BotGroupSay(this, "I don't know how to use a bow."); } void Bot::Sit() { if(IsMoving()) { moved = false; - // SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); tar_ndx = 0; } @@ -307,7 +257,6 @@ void Bot::Stand() { bool Bot::IsSitting() { bool result = false; - if(GetAppearance() == eaSitting && !IsMoving()) result = true; @@ -316,7 +265,6 @@ bool Bot::IsSitting() { bool Bot::IsStanding() { bool result = false; - if(GetAppearance() == eaStanding) result = true; @@ -326,15 +274,12 @@ bool Bot::IsStanding() { NPCType Bot::FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender, float size, uint32 face, uint32 hairStyle, uint32 hairColor, uint32 eyeColor, uint32 eyeColor2, uint32 beardColor, uint32 beard, uint32 drakkinHeritage, uint32 drakkinTattoo, uint32 drakkinDetails, int32 hp, int32 mana, int32 mr, int32 cr, int32 dr, int32 fr, int32 pr, int32 corrup, int32 ac, uint32 str, uint32 sta, uint32 dex, uint32 agi, uint32 _int, uint32 wis, uint32 cha, uint32 attack) { NPCType BotNPCType; int CopyLength = 0; - CopyLength = botName.copy(BotNPCType.name, 63); BotNPCType.name[CopyLength] = '\0'; CopyLength = 0; - CopyLength = botLastName.copy(BotNPCType.lastname, 69); BotNPCType.lastname[CopyLength] = '\0'; CopyLength = 0; - BotNPCType.npc_spells_id = botSpellsID; BotNPCType.level = botLevel; BotNPCType.race = botRace; @@ -368,42 +313,35 @@ NPCType Bot::FillNPCTypeStruct(uint32 botSpellsID, std::string botName, std::str BotNPCType.WIS = wis; BotNPCType.CHA = cha; BotNPCType.ATK = attack; - BotNPCType.npc_id = 0; BotNPCType.texture = 0; BotNPCType.d_melee_texture1 = 0; BotNPCType.d_melee_texture2 = 0; BotNPCType.qglobal = false; BotNPCType.attack_speed = 0; - BotNPCType.runspeed = 1.25; + BotNPCType.runspeed = 0.7f; BotNPCType.bodytype = 1; BotNPCType.findable = 0; BotNPCType.hp_regen = 1; BotNPCType.mana_regen = 1; BotNPCType.maxlevel = botLevel; - BotNPCType.light = NOT_USED; // due to the way that bots are coded..this is sent post-spawn - return BotNPCType; } NPCType Bot::CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender) { NPCType Result; int CopyLength = 0; - CopyLength = botName.copy(Result.name, 63); Result.name[CopyLength] = '\0'; CopyLength = 0; - CopyLength = botLastName.copy(Result.lastname, 69); Result.lastname[CopyLength] = '\0'; CopyLength = 0; - Result.level = botLevel; Result.race = botRace; Result.class_ = botClass; Result.gender = gender; - // default values Result.maxlevel = botLevel; Result.size = 6.0; @@ -412,7 +350,7 @@ NPCType Bot::CreateDefaultNPCTypeStructForBot(std::string botName, std::string b Result.drakkin_details = 0; Result.drakkin_heritage = 0; Result.drakkin_tattoo = 0; - Result.runspeed = 1.25; + Result.runspeed = 0.7f; Result.bodytype = 1; Result.findable = 0; Result.hp_regen = 1; @@ -438,13 +376,11 @@ NPCType Bot::CreateDefaultNPCTypeStructForBot(std::string botName, std::string b Result.CR = 25; Result.Corrup = 15; Result.AC = 12; - return Result; } void Bot::GenerateBaseStats() { int BotSpellID = 0; - // base stats uint32 Strength = _baseSTR; uint32 Stamina = _baseSTA; @@ -460,286 +396,284 @@ void Bot::GenerateBaseStats() { int32 PoisonResist = _basePR; int32 ColdResist = _baseCR; int32 CorruptionResist = _baseCorrup; - switch(this->GetClass()) { - case 1: // Warrior (why not just use 'case WARRIOR:'?) - Strength += 10; - Stamina += 20; - Agility += 10; - Dexterity += 10; - Attack += 12; - break; - case 2: // Cleric - BotSpellID = 701; - Strength += 5; - Stamina += 5; - Agility += 10; - Wisdom += 30; - Attack += 8; - break; - case 3: // Paladin - BotSpellID = 708; - Strength += 15; - Stamina += 5; - Wisdom += 15; - Charisma += 10; - Dexterity += 5; - Attack += 17; - break; - case 4: // Ranger - BotSpellID = 710; - Strength += 15; - Stamina += 10; - Agility += 10; - Wisdom += 15; - Attack += 17; - break; - case 5: // Shadowknight - BotSpellID = 709; - Strength += 10; - Stamina += 15; - Intelligence += 20; - Charisma += 5; - Attack += 17; - break; - case 6: // Druid - BotSpellID = 707; - Stamina += 15; - Wisdom += 35; - Attack += 5; - break; - case 7: // Monk - Strength += 5; - Stamina += 15; - Agility += 15; - Dexterity += 15; - Attack += 17; - break; - case 8: // Bard - BotSpellID = 711; - Strength += 15; - Dexterity += 10; - Charisma += 15; - Intelligence += 10; - Attack += 17; - break; - case 9: // Rogue - Strength += 10; - Stamina += 20; - Agility += 10; - Dexterity += 10; - Attack += 12; - break; - case 10: // Shaman - BotSpellID = 706; - Stamina += 10; - Wisdom += 30; - Charisma += 10; - Attack += 28; - break; - case 11: // Necromancer - BotSpellID = 703; - Dexterity += 10; - Agility += 10; - Intelligence += 30; - Attack += 5; - break; - case 12: // Wizard - BotSpellID = 702; - Stamina += 20; - Intelligence += 30; - Attack += 5; - break; - case 13: // Magician - BotSpellID = 704; - Stamina += 20; - Intelligence += 30; - Attack += 5; - break; - case 14: // Enchanter - BotSpellID = 705; - Intelligence += 25; - Charisma += 25; - Attack += 5; - break; - case 15: // Beastlord - BotSpellID = 712; - Stamina += 10; - Agility += 10; - Dexterity += 5; - Wisdom += 20; - Charisma += 5; - Attack += 31; - break; - case 16: // Berserker - Strength += 10; - Stamina += 15; - Dexterity += 15; - Agility += 10; - Attack += 25; - break; + case 1: // Warrior (why not just use 'case WARRIOR:'?) + Strength += 10; + Stamina += 20; + Agility += 10; + Dexterity += 10; + Attack += 12; + break; + case 2: // Cleric + BotSpellID = 701; + Strength += 5; + Stamina += 5; + Agility += 10; + Wisdom += 30; + Attack += 8; + break; + case 3: // Paladin + BotSpellID = 708; + Strength += 15; + Stamina += 5; + Wisdom += 15; + Charisma += 10; + Dexterity += 5; + Attack += 17; + break; + case 4: // Ranger + BotSpellID = 710; + Strength += 15; + Stamina += 10; + Agility += 10; + Wisdom += 15; + Attack += 17; + break; + case 5: // Shadowknight + BotSpellID = 709; + Strength += 10; + Stamina += 15; + Intelligence += 20; + Charisma += 5; + Attack += 17; + break; + case 6: // Druid + BotSpellID = 707; + Stamina += 15; + Wisdom += 35; + Attack += 5; + break; + case 7: // Monk + Strength += 5; + Stamina += 15; + Agility += 15; + Dexterity += 15; + Attack += 17; + break; + case 8: // Bard + BotSpellID = 711; + Strength += 15; + Dexterity += 10; + Charisma += 15; + Intelligence += 10; + Attack += 17; + break; + case 9: // Rogue + Strength += 10; + Stamina += 20; + Agility += 10; + Dexterity += 10; + Attack += 12; + break; + case 10: // Shaman + BotSpellID = 706; + Stamina += 10; + Wisdom += 30; + Charisma += 10; + Attack += 28; + break; + case 11: // Necromancer + BotSpellID = 703; + Dexterity += 10; + Agility += 10; + Intelligence += 30; + Attack += 5; + break; + case 12: // Wizard + BotSpellID = 702; + Stamina += 20; + Intelligence += 30; + Attack += 5; + break; + case 13: // Magician + BotSpellID = 704; + Stamina += 20; + Intelligence += 30; + Attack += 5; + break; + case 14: // Enchanter + BotSpellID = 705; + Intelligence += 25; + Charisma += 25; + Attack += 5; + break; + case 15: // Beastlord + BotSpellID = 712; + Stamina += 10; + Agility += 10; + Dexterity += 5; + Wisdom += 20; + Charisma += 5; + Attack += 31; + break; + case 16: // Berserker + Strength += 10; + Stamina += 15; + Dexterity += 15; + Agility += 10; + Attack += 25; + break; } float BotSize = GetSize(); switch(this->GetRace()) { - case 1: // Humans have no race bonus - break; - case 2: // Barbarian - Strength += 28; - Stamina += 20; - Agility += 7; - Dexterity -= 5; - Wisdom -= 5; - Intelligence -= 10; - Charisma -= 20; - BotSize = 7.0; - ColdResist += 10; - break; - case 3: // Erudite - Strength -= 15; - Stamina -= 5; - Agility -= 5; - Dexterity -= 5; - Wisdom += 8; - Intelligence += 32; - Charisma -= 5; - MagicResist += 5; - DiseaseResist -= 5; - break; - case 4: // Wood Elf - Strength -= 10; - Stamina -= 10; - Agility += 20; - Dexterity += 5; - Wisdom += 5; - BotSize = 5.0; - break; - case 5: // High Elf - Strength -= 20; - Stamina -= 10; - Agility += 10; - Dexterity -= 5; - Wisdom += 20; - Intelligence += 12; - Charisma += 5; - break; - case 6: // Dark Elf - Strength -= 15; - Stamina -= 10; - Agility += 15; - Wisdom += 8; - Intelligence += 24; - Charisma -= 15; - BotSize = 5.0; - break; - case 7: // Half Elf - Strength -= 5; - Stamina -= 5; - Agility += 15; - Dexterity += 10; - Wisdom -= 15; - BotSize = 5.5; - break; - case 8: // Dwarf - Strength += 15; - Stamina += 15; - Agility -= 5; - Dexterity += 15; - Wisdom += 8; - Intelligence -= 15; - Charisma -= 30; - BotSize = 4.0; - MagicResist -= 5; - PoisonResist += 5; - break; - case 9: // Troll - Strength += 33; - Stamina += 34; - Agility += 8; - Wisdom -= 15; - Intelligence -= 23; - Charisma -= 35; - BotSize = 8.0; - FireResist -= 20; - break; - case 10: // Ogre - Strength += 55; - Stamina += 77; - Agility -= 5; - Dexterity -= 5; - Wisdom -= 8; - Intelligence -= 15; - Charisma -= 38; - BotSize = 9.0; - break; - case 11: // Halfling - Strength -= 5; - Agility += 20; - Dexterity += 15; - Wisdom += 5; - Intelligence -= 8; - Charisma -= 25; - BotSize = 3.5; - PoisonResist += 5; - DiseaseResist += 5; - break; - case 12: // Gnome - Strength -= 15; - Stamina -= 5; - Agility += 10; - Dexterity += 10; - Wisdom -= 8; - Intelligence += 23; - Charisma -= 15; - BotSize = 3.0; - break; - case 128: // Iksar - Strength -= 5; - Stamina -= 5; - Agility += 15; - Dexterity += 10; - Wisdom += 5; - Charisma -= 20; - MagicResist -= 5; - FireResist -= 5; - break; - case 130: // Vah Shir - Strength += 15; - Agility += 15; - Dexterity -= 5; - Wisdom -= 5; - Intelligence -= 10; - Charisma -= 10; - BotSize = 7.0; - MagicResist -= 5; - FireResist -= 5; - break; - case 330: // Froglok - Strength -= 5; - Stamina += 5; - Agility += 25; - Dexterity += 25; - Charisma -= 25; - BotSize = 5.0; - MagicResist -= 5; - FireResist -= 5; - break; - case 522: // Drakkin - Strength -= 5; - Stamina += 5; - Agility += 10; - Intelligence += 10; - Wisdom += 5; - BotSize = 5.0; - PoisonResist += 2; - DiseaseResist += 2; - MagicResist += 2; - FireResist += 2; - ColdResist += 2; - break; + case 1: // Humans have no race bonus + break; + case 2: // Barbarian + Strength += 28; + Stamina += 20; + Agility += 7; + Dexterity -= 5; + Wisdom -= 5; + Intelligence -= 10; + Charisma -= 20; + BotSize = 7.0; + ColdResist += 10; + break; + case 3: // Erudite + Strength -= 15; + Stamina -= 5; + Agility -= 5; + Dexterity -= 5; + Wisdom += 8; + Intelligence += 32; + Charisma -= 5; + MagicResist += 5; + DiseaseResist -= 5; + break; + case 4: // Wood Elf + Strength -= 10; + Stamina -= 10; + Agility += 20; + Dexterity += 5; + Wisdom += 5; + BotSize = 5.0; + break; + case 5: // High Elf + Strength -= 20; + Stamina -= 10; + Agility += 10; + Dexterity -= 5; + Wisdom += 20; + Intelligence += 12; + Charisma += 5; + break; + case 6: // Dark Elf + Strength -= 15; + Stamina -= 10; + Agility += 15; + Wisdom += 8; + Intelligence += 24; + Charisma -= 15; + BotSize = 5.0; + break; + case 7: // Half Elf + Strength -= 5; + Stamina -= 5; + Agility += 15; + Dexterity += 10; + Wisdom -= 15; + BotSize = 5.5; + break; + case 8: // Dwarf + Strength += 15; + Stamina += 15; + Agility -= 5; + Dexterity += 15; + Wisdom += 8; + Intelligence -= 15; + Charisma -= 30; + BotSize = 4.0; + MagicResist -= 5; + PoisonResist += 5; + break; + case 9: // Troll + Strength += 33; + Stamina += 34; + Agility += 8; + Wisdom -= 15; + Intelligence -= 23; + Charisma -= 35; + BotSize = 8.0; + FireResist -= 20; + break; + case 10: // Ogre + Strength += 55; + Stamina += 77; + Agility -= 5; + Dexterity -= 5; + Wisdom -= 8; + Intelligence -= 15; + Charisma -= 38; + BotSize = 9.0; + break; + case 11: // Halfling + Strength -= 5; + Agility += 20; + Dexterity += 15; + Wisdom += 5; + Intelligence -= 8; + Charisma -= 25; + BotSize = 3.5; + PoisonResist += 5; + DiseaseResist += 5; + break; + case 12: // Gnome + Strength -= 15; + Stamina -= 5; + Agility += 10; + Dexterity += 10; + Wisdom -= 8; + Intelligence += 23; + Charisma -= 15; + BotSize = 3.0; + break; + case 128: // Iksar + Strength -= 5; + Stamina -= 5; + Agility += 15; + Dexterity += 10; + Wisdom += 5; + Charisma -= 20; + MagicResist -= 5; + FireResist -= 5; + break; + case 130: // Vah Shir + Strength += 15; + Agility += 15; + Dexterity -= 5; + Wisdom -= 5; + Intelligence -= 10; + Charisma -= 10; + BotSize = 7.0; + MagicResist -= 5; + FireResist -= 5; + break; + case 330: // Froglok + Strength -= 5; + Stamina += 5; + Agility += 25; + Dexterity += 25; + Charisma -= 25; + BotSize = 5.0; + MagicResist -= 5; + FireResist -= 5; + break; + case 522: // Drakkin + Strength -= 5; + Stamina += 5; + Agility += 10; + Intelligence += 10; + Wisdom += 5; + BotSize = 5.0; + PoisonResist += 2; + DiseaseResist += 2; + MagicResist += 2; + FireResist += 2; + ColdResist += 2; + break; } - this->STR = Strength; this->STA = Stamina; this->DEX = Dexterity; @@ -753,20 +687,22 @@ void Bot::GenerateBaseStats() { this->DR = DiseaseResist; this->PR = PoisonResist; this->CR = ColdResist; + this->PhR = 0; this->Corrup = CorruptionResist; SetBotSpellID(BotSpellID); this->size = BotSize; + this->pAggroRange = 0; + this->pAssistRange = 0; + this->raid_target = false; } void Bot::GenerateAppearance() { // Randomize facial appearance int iFace = 0; - if(this->GetRace() == 2) { // Barbarian w/Tatoo + if(this->GetRace() == 2) // Barbarian w/Tatoo iFace = zone->random.Int(0, 79); - } - else { + else iFace = zone->random.Int(0, 7); - } int iHair = 0; int iBeard = 0; @@ -775,40 +711,32 @@ void Bot::GenerateAppearance() { iHair = zone->random.Int(0, 8); iBeard = zone->random.Int(0, 11); iBeardColor = zone->random.Int(0, 3); - } - else if(this->GetGender()) { + } else if(this->GetGender()) { iHair = zone->random.Int(0, 2); if(this->GetRace() == 8) { // Dwarven Females can have a beard - if(zone->random.Int(1, 100) < 50) { + if(zone->random.Int(1, 100) < 50) iFace += 10; - } } - } - else { + } else { iHair = zone->random.Int(0, 3); iBeard = zone->random.Int(0, 5); iBeardColor = zone->random.Int(0, 19); } int iHairColor = 0; - if(this->GetRace() == 522) { + if(this->GetRace() == 522) iHairColor = zone->random.Int(0, 3); - } - else { + else iHairColor = zone->random.Int(0, 19); - } uint8 iEyeColor1 = (uint8)zone->random.Int(0, 9); uint8 iEyeColor2 = 0; - if(this->GetRace() == 522) { + if(this->GetRace() == 522) iEyeColor1 = iEyeColor2 = (uint8)zone->random.Int(0, 11); - } - else if(zone->random.Int(1, 100) > 96) { + else if(zone->random.Int(1, 100) > 96) iEyeColor2 = zone->random.Int(0, 9); - } - else { + else iEyeColor2 = iEyeColor1; - } int iHeritage = 0; int iTattoo = 0; @@ -818,7 +746,6 @@ void Bot::GenerateAppearance() { iTattoo = zone->random.Int(0, 7); iDetails = zone->random.Int(0, 7); } - this->luclinface = iFace; this->hairstyle = iHair; this->beard = iBeard; @@ -829,77 +756,71 @@ void Bot::GenerateAppearance() { this->drakkin_heritage = iHeritage; this->drakkin_tattoo = iTattoo; this->drakkin_details = iDetails; - } -int32 Bot::acmod() -{ +int32 Bot::acmod() { int agility = GetAGI(); int level = GetLevel(); if(agility < 1 || level < 1) return 0; - if(agility <= 74) - { + if(agility <= 74) { if(agility == 1) return -24; - else if(agility <=3) + else if(agility <= 3) return -23; else if(agility == 4) return -22; - else if(agility <=6) + else if(agility <= 6) return -21; - else if(agility <=8) + else if(agility <= 8) return -20; else if(agility == 9) return -19; - else if(agility <=11) + else if(agility <= 11) return -18; else if(agility == 12) return -17; - else if(agility <=14) + else if(agility <= 14) return -16; - else if(agility <=16) + else if(agility <= 16) return -15; else if(agility == 17) return -14; - else if(agility <=19) + else if(agility <= 19) return -13; else if(agility == 20) return -12; - else if(agility <=22) + else if(agility <= 22) return -11; - else if(agility <=24) + else if(agility <= 24) return -10; else if(agility == 25) return -9; - else if(agility <=27) + else if(agility <= 27) return -8; else if(agility == 28) return -7; - else if(agility <=30) + else if(agility <= 30) return -6; - else if(agility <=32) + else if(agility <= 32) return -5; else if(agility == 33) return -4; - else if(agility <=35) + else if(agility <= 35) return -3; else if(agility == 36) return -2; - else if(agility <=38) + else if(agility <= 38) return -1; - else if(agility <=65) + else if(agility <= 65) return 0; - else if(agility <=70) + else if(agility <= 70) return 1; - else if(agility <=74) + else if(agility <= 74) return 5; - } - else if(agility <= 137) - { - if(agility == 75) - { + } else if(agility <= 137) { + if(agility == 75) { if(level <= 6) return 9; else if(level <= 19) @@ -908,9 +829,7 @@ int32 Bot::acmod() return 33; else return 39; - } - else if(agility >= 76 && agility <= 79) - { + } else if(agility >= 76 && agility <= 79) { if(level <= 6) return 10; else if(level <= 19) @@ -919,9 +838,7 @@ int32 Bot::acmod() return 33; else return 40; - } - else if(agility == 80) - { + } else if(agility == 80) { if(level <= 6) return 11; else if(level <= 19) @@ -930,9 +847,7 @@ int32 Bot::acmod() return 34; else return 41; - } - else if(agility >= 81 && agility <= 85) - { + } else if(agility >= 81 && agility <= 85) { if(level <= 6) return 12; else if(level <= 19) @@ -941,9 +856,7 @@ int32 Bot::acmod() return 35; else return 42; - } - else if(agility >= 86 && agility <= 90) - { + } else if(agility >= 86 && agility <= 90) { if(level <= 6) return 12; else if(level <= 19) @@ -952,9 +865,7 @@ int32 Bot::acmod() return 36; else return 42; - } - else if(agility >= 91 && agility <= 95) - { + } else if(agility >= 91 && agility <= 95) { if(level <= 6) return 13; else if(level <= 19) @@ -963,8 +874,7 @@ int32 Bot::acmod() return 36; else return 43; - } - else if(agility >= 96 && agility <= 99){ + } else if(agility >= 96 && agility <= 99) { if(level <= 6) return 14; else if(level <= 19) @@ -973,9 +883,7 @@ int32 Bot::acmod() return 37; else return 44; - } - else if(agility == 100 && level >= 7) - { + } else if(agility == 100 && level >= 7) { if(level <= 19) return 28; else if (level <= 39) @@ -984,39 +892,30 @@ int32 Bot::acmod() return 45; } else if(level <= 6) - { return 15; - } //level is >6 - else if(agility >= 101 && agility <= 105) - { + else if(agility >= 101 && agility <= 105) { if(level <= 19) return 29; else if(level <= 39) return 39;// not verified else return 45; - } - else if(agility >= 106 && agility <= 110) - { + } else if(agility >= 106 && agility <= 110) { if(level <= 19) return 29; else if(level <= 39) return 39;// not verified else return 46; - } - else if(agility >= 111 && agility <= 115) - { + } else if(agility >= 111 && agility <= 115) { if(level <= 19) return 30; else if(level <= 39) return 40;// not verified else return 47; - } - else if(agility >= 116 && agility <= 119) - { + } else if(agility >= 116 && agility <= 119) { if(level <= 19) return 31; else if(level <= 39) @@ -1025,307 +924,269 @@ int32 Bot::acmod() return 47; } else if(level <= 19) - { return 32; - } //level is > 19 - else if(agility == 120) - { + else if(agility == 120) { if(level <= 39) return 42; else return 48; - } - else if(agility <= 125) - { + } else if(agility <= 125) { if(level <= 39) return 42; else return 49; - } - else if(agility <= 135) - { + } else if(agility <= 135) { if(level <= 39) return 42; else return 50; - } - else { + } else { if(level <= 39) return 42; else return 51; } - } - else if(agility <= 300) - { + } else if(agility <= 300) { if(level <= 6) { if(agility <= 139) - return(21); + return 21; else if(agility == 140) - return(22); + return 22; else if(agility <= 145) - return(23); + return 23; else if(agility <= 150) - return(23); + return 23; else if(agility <= 155) - return(24); + return 24; else if(agility <= 159) - return(25); + return 25; else if(agility == 160) - return(26); + return 26; else if(agility <= 165) - return(26); + return 26; else if(agility <= 170) - return(27); + return 27; else if(agility <= 175) - return(28); + return 28; else if(agility <= 179) - return(28); + return 28; else if(agility == 180) - return(29); + return 29; else if(agility <= 185) - return(30); + return 30; else if(agility <= 190) - return(31); + return 31; else if(agility <= 195) - return(31); + return 31; else if(agility <= 199) - return(32); + return 32; else if(agility <= 219) - return(33); + return 33; else if(agility <= 239) - return(34); + return 34; else - return(35); - } - else if(level <= 19) - { + return 35; + } else if(level <= 19) { if(agility <= 139) - return(34); + return 34; else if(agility == 140) - return(35); + return 35; else if(agility <= 145) - return(36); + return 36; else if(agility <= 150) - return(37); + return 37; else if(agility <= 155) - return(37); + return 37; else if(agility <= 159) - return(38); + return 38; else if(agility == 160) - return(39); + return 39; else if(agility <= 165) - return(40); + return 40; else if(agility <= 170) - return(40); + return 40; else if(agility <= 175) - return(41); + return 41; else if(agility <= 179) - return(42); + return 42; else if(agility == 180) - return(43); + return 43; else if(agility <= 185) - return(43); + return 43; else if(agility <= 190) - return(44); + return 44; else if(agility <= 195) - return(45); + return 45; else if(agility <= 199) - return(45); + return 45; else if(agility <= 219) - return(46); + return 46; else if(agility <= 239) - return(47); + return 47; else - return(48); - } - else if(level <= 39) - { + return 48; + } else if(level <= 39) { if(agility <= 139) - return(44); + return 44; else if(agility == 140) - return(45); + return 45; else if(agility <= 145) - return(46); + return 46; else if(agility <= 150) - return(47); + return 47; else if(agility <= 155) - return(47); + return 47; else if(agility <= 159) - return(48); + return 48; else if(agility == 160) - return(49); + return 49; else if(agility <= 165) - return(50); + return 50; else if(agility <= 170) - return(50); + return 50; else if(agility <= 175) - return(51); + return 51; else if(agility <= 179) - return(52); + return 52; else if(agility == 180) - return(53); + return 53; else if(agility <= 185) - return(53); + return 53; else if(agility <= 190) - return(54); + return 54; else if(agility <= 195) - return(55); + return 55; else if(agility <= 199) - return(55); + return 55; else if(agility <= 219) - return(56); + return 56; else if(agility <= 239) - return(57); + return 57; else - return(58); - } - else - { //lvl >= 40 + return 58; + } else { //lvl >= 40 if(agility <= 139) - return(51); + return 51; else if(agility == 140) - return(52); + return 52; else if(agility <= 145) - return(53); + return 53; else if(agility <= 150) - return(53); + return 53; else if(agility <= 155) - return(54); + return 54; else if(agility <= 159) - return(55); + return 55; else if(agility == 160) - return(56); + return 56; else if(agility <= 165) - return(56); + return 56; else if(agility <= 170) - return(57); + return 57; else if(agility <= 175) - return(58); + return 58; else if(agility <= 179) - return(58); + return 58; else if(agility == 180) - return(59); + return 59; else if(agility <= 185) - return(60); + return 60; else if(agility <= 190) - return(61); + return 61; else if(agility <= 195) - return(61); + return 61; else if(agility <= 199) - return(62); + return 62; else if(agility <= 219) - return(63); + return 63; else if(agility <= 239) - return(64); + return 64; else - return(65); + return 65; } } else - { - //seems about 21 agil per extra AC pt over 300... - return (65 + ((agility-300) / 21)); - } + return (65 + ((agility - 300) / 21)); #if EQDEBUG >= 11 Log.Out(Logs::General, Logs::Error, "Error in Bot::acmod(): Agility: %i, Level: %i",agility,level); #endif return 0; } -void Bot::GenerateArmorClass() -{ +void Bot::GenerateArmorClass() { /// new formula int avoidance = 0; - avoidance = (acmod() + ((GetSkill(SkillDefense)*16)/9)); + avoidance = (acmod() + ((GetSkill(SkillDefense) * 16) / 9)); if(avoidance < 0) avoidance = 0; int mitigation = 0; - if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER) - { - mitigation = GetSkill(SkillDefense)/4 + (itembonuses.AC+1); + if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER) { + mitigation = (GetSkill(SkillDefense) / 4 + (itembonuses.AC + 1)); mitigation -= 4; - } - else - { - mitigation = GetSkill(SkillDefense)/3 + ((itembonuses.AC*4)/3); + } else { + mitigation = (GetSkill(SkillDefense) / 3 + ((itembonuses.AC * 4) / 3)); if(GetClass() == MONK) - mitigation += GetLevel() * 13/10; //the 13/10 might be wrong, but it is close... + mitigation += (GetLevel() * 13 / 10); //the 13/10 might be wrong, but it is close... } int displayed = 0; - displayed += ((avoidance+mitigation)*1000)/847; //natural AC + displayed += (((avoidance + mitigation) * 1000) / 847); //natural AC //Iksar AC, untested - if(GetRace() == IKSAR) - { + if(GetRace() == IKSAR) { displayed += 12; int iksarlevel = GetLevel(); iksarlevel -= 10; if(iksarlevel > 25) iksarlevel = 25; + if(iksarlevel > 0) - displayed += iksarlevel * 12 / 10; + displayed += (iksarlevel * 12 / 10); } //spell AC bonuses are added directly to natural total displayed += spellbonuses.AC; - this->AC = displayed; } -uint16 Bot::GetPrimarySkillValue() -{ +uint16 Bot::GetPrimarySkillValue() { SkillUseTypes skill = HIGHEST_SKILL; //because nullptr == 0, which is 1H Slashing, & we want it to return 0 from GetSkill bool equiped = m_inv.GetItem(MainPrimary); - if(!equiped) - { skill = SkillHandtoHand; - } - else - { + else { uint8 type = m_inv.GetItem(MainPrimary)->GetItem()->ItemType; //is this the best way to do this? - switch(type) - { - case ItemType1HSlash: // 1H Slashing - { + switch(type) { + case ItemType1HSlash: { skill = Skill1HSlashing; break; } - case ItemType2HSlash: // 2H Slashing - { + case ItemType2HSlash: { skill = Skill2HSlashing; break; } - case ItemType1HPiercing: // Piercing - { + case ItemType1HPiercing: { skill = Skill1HPiercing; break; } - case ItemType1HBlunt: // 1H Blunt - { + case ItemType1HBlunt: { skill = Skill1HBlunt; break; } - case ItemType2HBlunt: // 2H Blunt - { + case ItemType2HBlunt: { skill = Skill2HBlunt; break; } - case ItemType2HPiercing: // 2H Piercing - { + case ItemType2HPiercing: { skill = Skill1HPiercing; // change to Skill2HPiercing once activated break; } - case ItemTypeMartial: // Hand to Hand - { + case ItemTypeMartial: { skill = SkillHandtoHand; break; } - default: // All other types default to Hand to Hand - { + default: { skill = SkillHandtoHand; break; } @@ -1339,15 +1200,12 @@ uint16 Bot::MaxSkill(SkillUseTypes skillid, uint16 class_, uint16 level) const { return(database.GetSkillCap(class_, skillid, level)); } -uint32 Bot::GetTotalATK() -{ +uint32 Bot::GetTotalATK() { uint32 AttackRating = 0; uint32 WornCap = itembonuses.ATK; - if(IsBot()) { AttackRating = ((WornCap * 1.342) + (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69)); AttackRating += aabonuses.ATK + GroupLeadershipAAOffenseEnhancement(); - if (AttackRating < 10) AttackRating = 10; } @@ -1355,34 +1213,27 @@ uint32 Bot::GetTotalATK() AttackRating = GetATK(); AttackRating += spellbonuses.ATK; - return AttackRating; } -uint32 Bot::GetATKRating() -{ +uint32 Bot::GetATKRating() { uint32 AttackRating = 0; if(IsBot()) { AttackRating = (GetSkill(SkillOffense) * 1.345) + ((GetSTR() - 66) * 0.9) + (GetPrimarySkillValue() * 2.69); - if (AttackRating < 10) AttackRating = 10; } return AttackRating; } -int32 Bot::GenerateBaseHitPoints() -{ +int32 Bot::GenerateBaseHitPoints() { // Calc Base Hit Points int new_base_hp = 0; uint32 lm = GetClassLevelFactor(); - uint32 Post255; - uint32 NormalSTA = GetSTA(); - - if(GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->GetClientVersion() >= ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) - { + int32 Post255; + int32 NormalSTA = GetSTA(); + if(GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->GetClientVersion() >= ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { float SoDPost255; - if(((NormalSTA - 255) / 2) > 0) SoDPost255 = ((NormalSTA - 255) / 2); else @@ -1391,33 +1242,20 @@ int32 Bot::GenerateBaseHitPoints() int hp_factor = GetClassHPFactor(); if(level < 41) - { new_base_hp = (5 + (GetLevel() * hp_factor / 12) + ((NormalSTA - SoDPost255) * GetLevel() * hp_factor / 3600)); - } else if(level < 81) - { - new_base_hp = (5 + (40 * hp_factor / 12) + ((GetLevel() - 40) * hp_factor / 6) + - ((NormalSTA - SoDPost255) * hp_factor / 90) + - ((NormalSTA - SoDPost255) * (GetLevel() - 40) * hp_factor / 1800)); - } + new_base_hp = (5 + (40 * hp_factor / 12) + ((GetLevel() - 40) * hp_factor / 6) + ((NormalSTA - SoDPost255) * hp_factor / 90) + ((NormalSTA - SoDPost255) * (GetLevel() - 40) * hp_factor / 1800)); else - { - new_base_hp = (5 + (80 * hp_factor / 8) + ((GetLevel() - 80) * hp_factor / 10) + - ((NormalSTA - SoDPost255) * hp_factor / 90) + - ((NormalSTA - SoDPost255) * hp_factor / 45)); - } - } - else - { - if((NormalSTA-255)/2 > 0) - Post255 = (NormalSTA-255)/2; + new_base_hp = (5 + (80 * hp_factor / 8) + ((GetLevel() - 80) * hp_factor / 10) + ((NormalSTA - SoDPost255) * hp_factor / 90) + ((NormalSTA - SoDPost255) * hp_factor / 45)); + } else { + if(((NormalSTA - 255) / 2) > 0) + Post255 = ((NormalSTA - 255) / 2); else Post255 = 0; - new_base_hp = (5)+(GetLevel()*lm/10) + (((NormalSTA-Post255)*GetLevel()*lm/3000)) + ((Post255*1)*lm/6000); + new_base_hp = (5) + (GetLevel() * lm / 10) + (((NormalSTA - Post255) * GetLevel() * lm / 3000)) + ((Post255 * 1) * lm / 6000); } this->base_hp = new_base_hp; - return new_base_hp; } @@ -1425,12 +1263,9 @@ void Bot::GenerateAABonuses(StatBonuses* newbon) { // General AA bonus uint8 botClass = GetClass(); uint8 botLevel = GetLevel(); - memset(newbon, 0, sizeof(StatBonuses)); //start fresh - if(botLevel >= 51) { //level 51 = 1 AA level - int i; int totalAAs = database.CountAAs(); uint32 slots = 0; @@ -1445,7 +1280,7 @@ void Bot::GenerateAABonuses(StatBonuses* newbon) { //slots = database.GetTotalAALevels(aa_AA); //find out how many effects from aa_effects table slots = zone->GetTotalAALevels(aa_AA); //find out how many effects from aa_effects, which is loaded into memory if (slots > 0) //and does it have any effects? may be able to put this above, not sure if it runs on each iteration - ApplyAABonuses(aa_AA + aa_value -1, slots, newbon); //add the bonuses + ApplyAABonuses((aa_AA + aa_value - 1), slots, newbon); //add the bonuses } } } @@ -1457,25 +1292,21 @@ void Bot::LoadAAs() { botAAs.clear(); //start fresh std::string query; - if(GetClass() == BERSERKER) query = StringFormat("SELECT skill_id FROM altadv_vars WHERE berserker = 1 AND class_type > 1 AND class_type <= %i AND aa_expansion <= %i ORDER BY skill_id;", GetLevel(), maxAAExpansion); else query = StringFormat("SELECT skill_id FROM altadv_vars WHERE ((classes & ( 1 << %i )) >> %i) = 1 AND class_type > 1 AND class_type <= %i AND aa_expansion <= %i ORDER BY skill_id;", GetClass(), GetClass(), GetLevel(), maxAAExpansion); auto results = database.QueryDatabase(query); - if(!results.Success()) { Log.Out(Logs::General, Logs::Error, "Error in Bot::LoadAAs()"); return; } int totalAAs = database.CountAAs(); - for (auto row = results.begin(); row != results.end(); ++row) { uint32 skill_id = 0; skill_id = atoi(row[0]); - if(skill_id <= 0 || skill_id >= totalAAs) continue; @@ -1486,11 +1317,10 @@ void Bot::LoadAAs() { for(int i=0; imax_level; i++) { //Get AA info & add to list - uint32 aaid = sendAA->id + i; + uint32 aaid = (sendAA->id + i); uint8 total_levels = 0; uint8 req_level; std::map::iterator RequiredLevel = AARequiredLevelAndCost.find(aaid); - //Get level required for AA if(RequiredLevel != AARequiredLevelAndCost.end()) req_level = RequiredLevel->second.Level; @@ -1502,43 +1332,36 @@ void Bot::LoadAAs() { //Bot is high enough level for AA std::map::iterator foundAA = botAAs.find(aaid); - // AA is already in list if(foundAA != botAAs.end()) continue; if(sendAA->id == aaid) { BotAA newAA; - newAA.total_levels = 0; newAA.aa_id = aaid; newAA.req_level = req_level; newAA.total_levels += 1; - botAAs[aaid] = newAA; //add to list } else //update master AA record with number of levels a bot has in AA, based on level. - botAAs[sendAA->id].total_levels+=1; + botAAs[sendAA->id].total_levels += 1; } } } uint32 Bot::GetAA(uint32 aa_id) { - std::map::const_iterator find_iter = botAAs.find(aa_id); int aaLevel = 0; - - if(find_iter != botAAs.end()) { + if(find_iter != botAAs.end()) aaLevel = find_iter->second.total_levels; - } return aaLevel; } //current with Client::ApplyAABonuses 9/26/12 -void Bot::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) -{ +void Bot::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) { if(slots == 0) //sanity check. why bother if no slots to fill? return; @@ -1547,38 +1370,27 @@ void Bot::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) int32 base1 = 0; int32 base2 = 0; //only really used for SE_RaiseStatCap & SE_ReduceSkillTimer in aa_effects table uint32 slot = 0; - std::map >::const_iterator find_iter = aa_effects.find(aaid); if(find_iter == aa_effects.end()) - { return; - } for (std::map::const_iterator iter = aa_effects[aaid].begin(); iter != aa_effects[aaid].end(); ++iter) { effect = iter->second.skill_id; base1 = iter->second.base1; base2 = iter->second.base2; slot = iter->second.slot; - //we default to 0 (SE_CurrentHP) for the effect, so if there aren't any base1/2 values, we'll just skip it - if (effect == 0 && base1 == 0 && base2 == 0) - continue; - - //IsBlankSpellEffect() - if (effect == SE_Blank || (effect == SE_CHA && base1 == 0) || effect == SE_StackingCommand_Block || effect == SE_StackingCommand_Overwrite) + if ((effect == 0 && base1 == 0 && base2 == 0) || effect == SE_Blank || (effect == SE_CHA && base1 == 0) || effect == SE_StackingCommand_Block || effect == SE_StackingCommand_Overwrite) continue; Log.Out(Logs::Detail, Logs::AA, "Applying Effect %d from AA %u in slot %d (base1: %d, base2: %d) on %s", effect, aaid, slot, base1, base2, this->GetCleanName()); - - uint8 focus = IsFocusEffect(0, 0, true,effect); - if (focus) - { + uint8 focus = IsFocusEffect(0, 0, true, effect); + if (focus) { newbon->FocusEffects[focus] = effect; continue; } - switch (effect) - { + switch (effect) { //Note: AA effects that use accuracy are skill limited, while spell effect is not. case SE_Accuracy: if ((base2 == -1) && (newbon->Accuracy[HIGHEST_SKILL+1] < base1)) @@ -1593,9 +1405,7 @@ void Bot::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) newbon->EnduranceRegen += base1; break; case SE_MovementSpeed: - newbon->movementspeed += base1; //should we let these stack? - /*if (base1 > newbon->movementspeed) //or should we use a total value? - newbon->movementspeed = base1;*/ + newbon->movementspeed += base1; break; case SE_STR: newbon->STR += base1; @@ -1660,8 +1470,7 @@ void Bot::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) case SE_SetBreathLevel: break; case SE_RaiseStatCap: - switch(base2) - { + switch(base2) { //are these #define'd somewhere? case 0: //str newbon->STRCapMod += base1; @@ -1876,216 +1685,169 @@ void Bot::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) case SE_UnfailingDivinity: newbon->UnfailingDivinity += base1; break; - - case SE_ProcOnKillShot: - for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) - { - if(!newbon->SpellOnKill[i] || ((newbon->SpellOnKill[i] == base2) && (newbon->SpellOnKill[i+1] < base1))) - { + case SE_ProcOnKillShot: { + for(int i = 0; i < (MAX_SPELL_TRIGGER * 3); i += 3) { + if(!newbon->SpellOnKill[i] || ((newbon->SpellOnKill[i] == base2) && (newbon->SpellOnKill[i + 1] < base1))) { //base1 = chance, base2 = SpellID to be triggered, base3 = min npc level newbon->SpellOnKill[i] = base2; - newbon->SpellOnKill[i+1] = base1; + newbon->SpellOnKill[i + 1] = base1; if (GetLevel() > 15) - newbon->SpellOnKill[i+2] = GetLevel() - 15; //AA specifiy "non-trivial" + newbon->SpellOnKill[i + 2] = (GetLevel() - 15); //AA specifiy "non-trivial" else - newbon->SpellOnKill[i+2] = 0; + newbon->SpellOnKill[i + 2] = 0; break; } } - break; - - case SE_SpellOnDeath: - for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) - { - if(!newbon->SpellOnDeath[i]) - { + break; + } + case SE_SpellOnDeath: { + for(int i = 0; i < (MAX_SPELL_TRIGGER * 2); i += 2) { + if(!newbon->SpellOnDeath[i]) { // base1 = SpellID to be triggered, base2 = chance to fire newbon->SpellOnDeath[i] = base1; - newbon->SpellOnDeath[i+1] = base2; + newbon->SpellOnDeath[i + 1] = base2; break; } } - break; - - case SE_TriggerOnCast: - - for(int i = 0; i < MAX_SPELL_TRIGGER; i++) - { + break; + } + case SE_TriggerOnCast: { + for(int i = 0; i < MAX_SPELL_TRIGGER; i++) { if (newbon->SpellTriggers[i] == aaid) break; - if(!newbon->SpellTriggers[i]) - { + if(!newbon->SpellTriggers[i]) { //Save the 'aaid' of each triggerable effect to an array newbon->SpellTriggers[i] = aaid; break; } } - break; - - case SE_CriticalHitChance: - { + break; + } + case SE_CriticalHitChance: { if(base2 == -1) newbon->CriticalHitChance[HIGHEST_SKILL+1] += base1; else newbon->CriticalHitChance[base2] += base1; + break; } - break; - - case SE_CriticalDamageMob: - { + case SE_CriticalDamageMob: { // base1 = effect value, base2 = skill restrictions(-1 for all) if(base2 == -1) - newbon->CritDmgMob[HIGHEST_SKILL+1] += base1; + newbon->CritDmgMob[HIGHEST_SKILL + 1] += base1; else newbon->CritDmgMob[base2] += base1; break; } - - case SE_CriticalSpellChance: - { + case SE_CriticalSpellChance: { newbon->CriticalSpellChance += base1; - if (base2 > newbon->SpellCritDmgIncrease) newbon->SpellCritDmgIncrease = base2; break; } - - case SE_ResistFearChance: - { + case SE_ResistFearChance: { if(base1 == 100) // If we reach 100% in a single spell/item then we should be immune to negative fear resist effects until our immunity is over newbon->Fearless = true; newbon->ResistFearChance += base1; // these should stack break; } - - case SE_SkillDamageAmount: - { + case SE_SkillDamageAmount: { if(base2 == -1) - newbon->SkillDamageAmount[HIGHEST_SKILL+1] += base1; + newbon->SkillDamageAmount[HIGHEST_SKILL + 1] += base1; else newbon->SkillDamageAmount[base2] += base1; break; } - - case SE_SpecialAttackKBProc: - { + case SE_SpecialAttackKBProc: { //You can only have one of these per client. [AA Dragon Punch] newbon->SpecialAttackKBProc[0] = base1; //Chance base 100 = 25% proc rate newbon->SpecialAttackKBProc[1] = base2; //Skill to KB Proc Off break; } - - case SE_DamageModifier: - { + case SE_DamageModifier: { if(base2 == -1) - newbon->DamageModifier[HIGHEST_SKILL+1] += base1; + newbon->DamageModifier[HIGHEST_SKILL + 1] += base1; else newbon->DamageModifier[base2] += base1; break; } - - case SE_SlayUndead: - { + case SE_SlayUndead: { if(newbon->SlayUndead[1] < base1) newbon->SlayUndead[0] = base1; // Rate newbon->SlayUndead[1] = base2; // Damage Modifier break; } - - case SE_GiveDoubleRiposte: - { + case SE_GiveDoubleRiposte: { //0=Regular Riposte 1=Skill Attack Riposte 2=Skill - if(base2 == 0){ + if(base2 == 0) { if(newbon->GiveDoubleRiposte[0] < base1) newbon->GiveDoubleRiposte[0] = base1; } //Only for special attacks. - else if(base2 > 0 && (newbon->GiveDoubleRiposte[1] < base1)){ + else if(base2 > 0 && (newbon->GiveDoubleRiposte[1] < base1)) { newbon->GiveDoubleRiposte[1] = base1; newbon->GiveDoubleRiposte[2] = base2; } - break; } - //Kayen: Not sure best way to implement this yet. //Physically raises skill cap ie if 55/55 it will raise to 55/60 - case SE_RaiseSkillCap: - { - if(newbon->RaiseSkillCap[0] < base1){ + case SE_RaiseSkillCap: { + if(newbon->RaiseSkillCap[0] < base1) { newbon->RaiseSkillCap[0] = base1; //value newbon->RaiseSkillCap[1] = base2; //skill } break; } - - case SE_MasteryofPast: - { + case SE_MasteryofPast: { if(newbon->MasteryofPast < base1) newbon->MasteryofPast = base1; break; } - case SE_CastingLevel2: - case SE_CastingLevel: - { + case SE_CastingLevel: { newbon->effective_casting_level += base1; break; } - - - case SE_DivineSave: - { - if(newbon->DivineSaveChance[0] < base1) - { + case SE_DivineSave: { + if(newbon->DivineSaveChance[0] < base1) { newbon->DivineSaveChance[0] = base1; newbon->DivineSaveChance[1] = base2; } break; } - - case SE_SpellEffectResistChance: - { - for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) - { - if(!newbon->SEResist[e] || ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < base1)) ){ + case SE_SpellEffectResistChance: { + for(int e = 0; e < (MAX_RESISTABLE_EFFECTS * 2); e += 2) { + if(!newbon->SEResist[e] || ((newbon->SEResist[e] = base2) && (newbon->SEResist[e + 1] < base1)) ){ newbon->SEResist[e] = base2; - newbon->SEResist[e+1] = base1; - break; + newbon->SEResist[e + 1] = base1; + break; } } break; } - - case SE_MitigateDamageShield: - { + case SE_MitigateDamageShield: { if (base1 < 0) - base1 = base1*(-1); + base1 = (base1 * -1); newbon->DSMitigationOffHand += base1; break; } - - case SE_FinishingBlow: - { - + case SE_FinishingBlow: { //base1 = chance, base2 = damage - if (newbon->FinishingBlow[1] < base2){ + if (newbon->FinishingBlow[1] < base2) { newbon->FinishingBlow[0] = base1; newbon->FinishingBlow[1] = base2; } break; } - - case SE_FinishingBlowLvl: - { + case SE_FinishingBlowLvl: { //base1 = level, base2 = ??? (Set to 200 in AA data, possible proc rate mod?) - if (newbon->FinishingBlowLvl[0] < base1){ + if (newbon->FinishingBlowLvl[0] < base1) { newbon->FinishingBlowLvl[0] = base1; newbon->FinishingBlowLvl[1] = base2; } @@ -2317,42 +2079,31 @@ bool Bot::IsValidName() { std::string TempBotName = std::string(this->GetCleanName()); for(int iCounter = 0; iCounter < TempBotName.length(); iCounter++) { - if(isalpha(TempBotName[iCounter]) || TempBotName[iCounter] == '_') { + if(isalpha(TempBotName[iCounter]) || TempBotName[iCounter] == '_') Result = true; - } } return Result; } -bool Bot::IsBotNameAvailable(std::string* errorMessage) { - - if(!this->GetCleanName()) - return false; - - std::string query = StringFormat("SELECT COUNT(id) FROM vwBotCharacterMobs " - "WHERE name LIKE '%s'", this->GetCleanName()); - auto results = database.QueryDatabase(query); - if(!results.Success()) { - *errorMessage = std::string(results.ErrorMessage()); +bool Bot::IsBotNameAvailable(char *botName, std::string* errorMessage) { + if (botName == "" || strlen(botName) > 15 || !database.CheckNameFilter(botName) || !database.CheckUsedName(botName)) return false; - } - uint32 existingNameCount = 0; - - for (auto row = results.begin(); row != results.end(); ++row) { - existingNameCount = atoi(row[0]); - break; - } - - if(existingNameCount != 0) + std::string query = StringFormat("SELECT id FROM vwBotCharacterMobs WHERE name LIKE '%s'", botName); + auto results = database.QueryDatabase(query); + if(!results.Success()) { + *errorMessage = std::string(results.ErrorMessage()); + return false; + } + + if (results.RowCount()) return false; - return true; + return true; //We made it with a valid name! } bool Bot::Save() { - if(this->GetBotID() == 0) { // New bot record std::string query = StringFormat("INSERT INTO bots (BotOwnerCharacterID, BotSpellsID, Name, LastName, " @@ -2378,6 +2129,7 @@ bool Bot::Save() { auto botOwner = GetBotOwner(); if (botOwner) botOwner->Message(13, results.ErrorMessage().c_str()); + return false; } @@ -2416,36 +2168,28 @@ bool Bot::Save() { auto botOwner = GetBotOwner(); if (botOwner) botOwner->Message(13, results.ErrorMessage().c_str()); + return false; } - SaveBuffs(); SavePet(); SaveStance(); SaveTimers(); - return true; } // Returns the current total play time for the bot uint32 Bot::GetTotalPlayTime() { uint32 Result = 0; - double TempTotalPlayTime = 0; - time_t currentTime = time(¤tTime); - TempTotalPlayTime = difftime(currentTime, _startTotalPlayTime); - TempTotalPlayTime += _lastTotalPlayTime; - Result = (uint32)TempTotalPlayTime; - return Result; } void Bot::SaveBuffs() { - // Remove any existing buff saves std::string query = StringFormat("DELETE FROM botbuffs WHERE BotId = %u", GetBotID()); auto results = database.QueryDatabase(query); @@ -2462,7 +2206,7 @@ void Bot::SaveBuffs() { "CorruptionCounters, HitCount, MeleeRune, MagicRune, dot_rune, " "caston_x, Persistent, caston_y, caston_z, ExtraDIChance) " "VALUES (%u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %i, %u, " - "%i, %i, %i);", + "%i, %i, %i)", GetBotID(), buffs[buffIndex].spellid, buffs[buffIndex].casterlevel, spells[buffs[buffIndex].spellid].buffdurationformula, buffs[buffIndex].ticsremaining, @@ -2477,24 +2221,16 @@ void Bot::SaveBuffs() { auto results = database.QueryDatabase(query); if(!results.Success()) return; - } - } void Bot::LoadBuffs() { - - std::string query = StringFormat("SELECT SpellId, CasterLevel, DurationFormula, TicsRemaining, " - "PoisonCounters, DiseaseCounters, CurseCounters, CorruptionCounters, " - "HitCount, MeleeRune, MagicRune, dot_rune, caston_x, Persistent, " - "caston_y, caston_z, ExtraDIChance FROM botbuffs WHERE BotId = %u", - GetBotID()); + std::string query = StringFormat("SELECT SpellId, CasterLevel, DurationFormula, TicsRemaining, PoisonCounters, DiseaseCounters, CurseCounters, CorruptionCounters, HitCount, MeleeRune, MagicRune, dot_rune, caston_x, Persistent, caston_y, caston_z, ExtraDIChance FROM botbuffs WHERE BotId = %u", GetBotID()); auto results = database.QueryDatabase(query); if(!results.Success()) return; int buffCount = 0; - for (auto row = results.begin(); row != results.end(); ++row) { if(buffCount == BUFF_COUNT) break; @@ -2502,7 +2238,6 @@ void Bot::LoadBuffs() { buffs[buffCount].spellid = atoi(row[0]); buffs[buffCount].casterlevel = atoi(row[1]); buffs[buffCount].ticsremaining = atoi(row[3]); - if(CalculatePoisonCounters(buffs[buffCount].spellid) > 0) buffs[buffCount].counters = atoi(row[4]); else if(CalculateDiseaseCounters(buffs[buffCount].spellid) > 0) @@ -2518,49 +2253,35 @@ void Bot::LoadBuffs() { buffs[buffCount].dot_rune = atoi(row[11]); buffs[buffCount].caston_x = atoi(row[12]); buffs[buffCount].casterid = 0; - buffs[buffCount].persistant_buff = atoi(row[13])? true: false; - buffs[buffCount].caston_y = atoi(row[14]); buffs[buffCount].caston_z = atoi(row[15]); buffs[buffCount].ExtraDIChance = atoi(row[16]); - buffCount++; } - query = StringFormat("DELETE FROM botbuffs WHERE BotId = %u", GetBotID()); results = database.QueryDatabase(query); - } uint32 Bot::GetPetSaveId() { - - std::string query = StringFormat("SELECT BotPetsId FROM botpets WHERE BotId = %u;", GetBotID()); + std::string query = StringFormat("SELECT BotPetsId FROM botpets WHERE BotId = %u", GetBotID()); auto results = database.QueryDatabase(query); - if(!results.Success()) + if(!results.Success() || results.RowCount() == 0) return 0; - if (results.RowCount() == 0) - return 0; - auto row = results.begin(); - return atoi(row[0]); } void Bot::LoadPet() { uint32 PetSaveId = GetPetSaveId(); - if(PetSaveId > 0 && !GetPet() && PetSaveId <= SPDAT_RECORDS) { std::string petName; uint32 petMana = 0; uint32 petHitPoints = 0; uint32 botPetId = 0; - LoadPetStats(&petName, &petMana, &petHitPoints, &botPetId, PetSaveId); - MakePet(botPetId, spells[botPetId].teleport_zone, petName.c_str()); - if(GetPet() && GetPet()->IsNPC()) { NPC *pet = GetPet()->CastToNPC(); SpellBuff_Struct petBuffs[BUFF_COUNT]; @@ -2575,7 +2296,6 @@ void Bot::LoadPet() { pet->SetHP(petHitPoints); pet->SetMana(petMana); } - DeletePetStats(PetSaveId); } } @@ -2584,18 +2304,12 @@ void Bot::LoadPetStats(std::string* petName, uint32* petMana, uint32* petHitPoin if(botPetSaveId == 0) return; - std::string query = StringFormat("SELECT PetId, Name, Mana, HitPoints " - "FROM botpets WHERE BotPetsId = %u;", - botPetSaveId); + std::string query = StringFormat("SELECT PetId, Name, Mana, HitPoints FROM botpets WHERE BotPetsId = %u", botPetSaveId); auto results = database.QueryDatabase(query); - if(!results.Success()) - return; - - if (results.RowCount() == 0) + if(!results.Success() || results.RowCount() == 0) return; auto row = results.begin(); - *botPetId = atoi(row[0]); *petName = std::string(row[1]); *petMana = atoi(row[2]); @@ -2606,15 +2320,12 @@ void Bot::LoadPetBuffs(SpellBuff_Struct* petBuffs, uint32 botPetSaveId) { if(!petBuffs || botPetSaveId == 0) return; - std::string query = StringFormat("SELECT SpellId, CasterLevel, Duration " - "FROM botpetbuffs WHERE BotPetsId = %u;", botPetSaveId); + std::string query = StringFormat("SELECT SpellId, CasterLevel, Duration FROM botpetbuffs WHERE BotPetsId = %u;", botPetSaveId); auto results = database.QueryDatabase(query); if(!results.Success()) return; - int buffIndex = 0; - for (auto row = results.begin();row != results.end(); ++row) { if(buffIndex == BUFF_COUNT) break; @@ -2622,39 +2333,31 @@ void Bot::LoadPetBuffs(SpellBuff_Struct* petBuffs, uint32 botPetSaveId) { petBuffs[buffIndex].spellid = atoi(row[0]); petBuffs[buffIndex].level = atoi(row[1]); petBuffs[buffIndex].duration = atoi(row[2]); - buffIndex++; } - query = StringFormat("DELETE FROM botpetbuffs WHERE BotPetsId = %u;", botPetSaveId); results = database.QueryDatabase(query); - } void Bot::LoadPetItems(uint32* petItems, uint32 botPetSaveId) { if(!petItems || botPetSaveId == 0) return; - std::string query = StringFormat("SELECT ItemId FROM botpetinventory " - "WHERE BotPetsId = %u;", botPetSaveId); + std::string query = StringFormat("SELECT ItemId FROM botpetinventory WHERE BotPetsId = %u;", botPetSaveId); auto results = database.QueryDatabase(query); if(!results.Success()) return; int itemIndex = 0; - for(auto row = results.begin(); row != results.end(); ++row) { if(itemIndex == EmuConstants::EQUIPMENT_SIZE) break; petItems[itemIndex] = atoi(row[0]); - itemIndex++; } - - query = StringFormat("DELETE FROM botpetinventory WHERE BotPetsId = %u;", botPetSaveId); + query = StringFormat("DELETE FROM botpetinventory WHERE BotPetsId = %u", botPetSaveId); results = database.QueryDatabase(query); - } void Bot::SavePet() { @@ -2666,40 +2369,28 @@ void Bot::SavePet() { char* tempPetName = new char[64]; SpellBuff_Struct petBuffs[BUFF_COUNT]; uint32 petItems[EmuConstants::EQUIPMENT_SIZE]; - pet->GetPetState(petBuffs, petItems, tempPetName); - uint32 existingBotPetSaveId = GetPetSaveId(); - if(existingBotPetSaveId > 0) { // Remove any existing pet buffs DeletePetBuffs(existingBotPetSaveId); - // Remove any existing pet items DeletePetItems(existingBotPetSaveId); } - // Save pet stats and get a new bot pet save id uint32 botPetSaveId = SavePetStats(std::string(tempPetName), petMana, petHitPoints, botPetId); - // Save pet buffs SavePetBuffs(petBuffs, botPetSaveId); - // Save pet items SavePetItems(petItems, botPetSaveId); - if(tempPetName) safe_delete_array(tempPetName); } } uint32 Bot::SavePetStats(std::string petName, uint32 petMana, uint32 petHitPoints, uint32 botPetId) { - - std::string query = StringFormat("REPLACE INTO botpets SET PetId = %u, BotId = %u, Name = '%s', " - "Mana = %u, HitPoints = %u;", botPetId, GetBotID(), petName.c_str(), - petMana, petHitPoints); + std::string query = StringFormat("REPLACE INTO botpets SET PetId = %u, BotId = %u, Name = '%s', Mana = %u, HitPoints = %u", botPetId, GetBotID(), petName.c_str(), petMana, petHitPoints); auto results = database.QueryDatabase(query); - return 0; } @@ -2708,21 +2399,14 @@ void Bot::SavePetBuffs(SpellBuff_Struct* petBuffs, uint32 botPetSaveId) { return; int buffIndex = 0; - while(buffIndex < BUFF_COUNT) { if(petBuffs[buffIndex].spellid > 0 && petBuffs[buffIndex].spellid != SPELL_UNKNOWN) { - std::string query = StringFormat("INSERT INTO botpetbuffs " - "(BotPetsId, SpellId, CasterLevel, Duration) " - "VALUES(%u, %u, %u, %u);", - botPetSaveId, petBuffs[buffIndex].spellid, - petBuffs[buffIndex].level, petBuffs[buffIndex].duration); + std::string query = StringFormat("INSERT INTO botpetbuffs (BotPetsId, SpellId, CasterLevel, Duration) VALUES(%u, %u, %u, %u)", botPetSaveId, petBuffs[buffIndex].spellid, petBuffs[buffIndex].level, petBuffs[buffIndex].duration); auto results = database.QueryDatabase(query); if(!results.Success()) break; - } - buffIndex++; } @@ -2736,55 +2420,46 @@ void Bot::SavePetItems(uint32* petItems, uint32 botPetSaveId) { if(petItems[itemIndex] == 0) continue; - std::string query = StringFormat("INSERT INTO botpetinventory " - "(BotPetsId, ItemId) VALUES(%u, %u);", - botPetSaveId, petItems[itemIndex]); + std::string query = StringFormat("INSERT INTO botpetinventory (BotPetsId, ItemId) VALUES(%u, %u)", botPetSaveId, petItems[itemIndex]); auto results = database.QueryDatabase(query); if(!results.Success()) break; } - } void Bot::DeletePetBuffs(uint32 botPetSaveId) { if(botPetSaveId == 0) return; - std::string query = StringFormat("DELETE FROM botpetbuffs WHERE BotPetsId = %u;", botPetSaveId); + std::string query = StringFormat("DELETE FROM botpetbuffs WHERE BotPetsId = %u", botPetSaveId); auto results = database.QueryDatabase(query); - } void Bot::DeletePetItems(uint32 botPetSaveId) { if(botPetSaveId == 0) return; - std::string query = StringFormat("DELETE FROM botpetinventory WHERE BotPetsId = %u;", botPetSaveId); + std::string query = StringFormat("DELETE FROM botpetinventory WHERE BotPetsId = %u", botPetSaveId); auto results = database.QueryDatabase(query); - } void Bot::DeletePetStats(uint32 botPetSaveId) { if(botPetSaveId == 0) return; - std::string query = StringFormat("DELETE from botpets where BotPetsId = %u;", botPetSaveId); + std::string query = StringFormat("DELETE from botpets where BotPetsId = %u", botPetSaveId); auto results = database.QueryDatabase(query); - } void Bot::LoadStance() { - - std::string query = StringFormat("SELECT StanceID FROM botstances WHERE BotID = %u;", GetBotID()); + std::string query = StringFormat("SELECT StanceID FROM botstances WHERE BotID = %u", GetBotID()); auto results = database.QueryDatabase(query); if(!results.Success() || results.RowCount() == 0) { Log.Out(Logs::General, Logs::Error, "Error in Bot::LoadStance()"); SetDefaultBotStance(); return; } - auto row = results.begin(); - SetBotStance((BotStanceType)atoi(row[0])); } @@ -2792,22 +2467,19 @@ void Bot::SaveStance() { if(_baseBotStance == _botStance) return; - std::string query = StringFormat("REPLACE INTO botstances (BotID, StanceId) " - "VALUES(%u, %u);", GetBotID(), GetBotStance()); + std::string query = StringFormat("REPLACE INTO botstances (BotID, StanceId) VALUES(%u, %u)", GetBotID(), GetBotStance()); auto results = database.QueryDatabase(query); if(!results.Success()) Log.Out(Logs::General, Logs::Error, "Error in Bot::SaveStance()"); - } void Bot::LoadTimers() { - std::string query = StringFormat("SELECT IfNull(bt.TimerID, 0) As TimerID, IfNull(bt.Value, 0) As Value, " "IfNull(MAX(sn.recast_time), 0) AS MaxTimer FROM bottimers bt, spells_new sn " "WHERE bt.BotID = %u AND sn.EndurTimerIndex = " "(SELECT case WHEN TimerID > %i THEN TimerID - %i ELSE TimerID END AS TimerID " "FROM bottimers WHERE TimerID = bt.TimerID AND BotID = bt.BotID ) " - "AND sn.classes%i <= %i;", + "AND sn.classes%i <= %i", GetBotID(), DisciplineReuseStart-1, DisciplineReuseStart-1, GetClass(), GetLevel()); auto results = database.QueryDatabase(query); if(!results.Success()) { @@ -2818,12 +2490,10 @@ void Bot::LoadTimers() { int timerID = 0; uint32 value = 0; uint32 maxValue = 0; - for (auto row = results.begin(); row != results.end(); ++row) { timerID = atoi(row[0]) - 1; value = atoi(row[1]); maxValue = atoi(row[2]); - if(timerID >= 0 && timerID < MaxTimer && value < (Timer::GetCurrentTime() + maxValue)) timers[timerID] = value; } @@ -2832,8 +2502,7 @@ void Bot::LoadTimers() { void Bot::SaveTimers() { bool hadError = false; - - std::string query = StringFormat("DELETE FROM bottimers WHERE BotID = %u;", GetBotID()); + std::string query = StringFormat("DELETE FROM bottimers WHERE BotID = %u", GetBotID()); auto results = database.QueryDatabase(query); if(!results.Success()) hadError = true; @@ -2842,10 +2511,8 @@ void Bot::SaveTimers() { if(timers[timerIndex] <= Timer::GetCurrentTime()) continue; - query = StringFormat("REPLACE INTO bottimers (BotID, TimerID, Value) VALUES(%u, %u, %u);", - GetBotID(), timerIndex+1, timers[timerIndex]); + query = StringFormat("REPLACE INTO bottimers (BotID, TimerID, Value) VALUES(%u, %u, %u)", GetBotID(), timerIndex + 1, timers[timerIndex]); results = database.QueryDatabase(query); - if(!results.Success()) hadError = true; } @@ -2855,19 +2522,14 @@ void Bot::SaveTimers() { } -bool Bot::Process() -{ +bool Bot::Process() { if(IsStunned() && stunned_timer.Check()) - { - this->stunned = false; - this->stunned_timer.Disable(); - } + Mob::UnStun(); if(!GetBotOwner()) return false; - if (GetDepop()) - { + if (GetDepop()) { _botOwner = 0; _botOwnerCharacterID = 0; _previousTarget = 0; @@ -2876,25 +2538,18 @@ bool Bot::Process() SpellProcess(); - if(tic_timer.Check()) - { + if(tic_timer.Check()) { //6 seconds, or whatever the rule is set to has passed, send this position to everyone to avoid ghosting - if(!IsMoving() && !IsEngaged()) - { + if(!IsMoving() && !IsEngaged()) { SendPosition(); - if(IsSitting()) - { + if(IsSitting()) { if(!rest_timer.Enabled()) - { rest_timer.Start(RuleI(Character, RestRegenTimeToActivate) * 1000); - } } } BuffProcess(); - CalcRestState(); - if(curfp) ProcessFlee(); @@ -2909,9 +2564,8 @@ bool Bot::Process() SetEndurance(GetEndurance() + CalcEnduranceRegen() + RestRegenEndurance); } - if (sendhpupdate_timer.Check()) { + if (sendhpupdate_timer.Check(false)) { SendHPUpdate(); - if(HasPet()) GetPet()->SendHPUpdate(); } @@ -2922,24 +2576,14 @@ bool Bot::Process() if (IsStunned() || IsMezzed()) return true; - //Handle assists... - /*if(assist_timer.Check() && !Charmed() && GetTarget() != nullptr) { - entity_list.AIYellForHelp(this, GetTarget()); - }*/ - // Bot AI AI_Process(); - return true; } -void Bot::SpellProcess() -{ - // check the rapid recast prevention timer - if(spellend_timer.Check(false)) - { +void Bot::SpellProcess() { + if(spellend_timer.Check(false)) { NPC::SpellProcess(); - if(GetClass() == BARD) { if (casting_spell_id != 0) casting_spell_id = 0; @@ -2949,34 +2593,24 @@ void Bot::SpellProcess() void Bot::BotMeditate(bool isSitting) { if(isSitting) { - // If the bot is a caster has less than 99% mana while its not engaged, he needs to sit to meditate - if(GetManaRatio() < 99.0f || GetHPRatio() < 99.0f) - { + if(GetManaRatio() < 99.0f || GetHPRatio() < 99.0f) { if (!IsEngaged() && !IsSitting()) Sit(); - } - else - { + } else { if(IsSitting()) Stand(); } - } - else - { + } else { if(IsSitting()) Stand(); } - if(IsSitting()) - { + + if(IsSitting()) { if(!rest_timer.Enabled()) - { rest_timer.Start(RuleI(Character, RestRegenTimeToActivate) * 1000); - } } else - { rest_timer.Disable(); - } } void Bot::BotRangedAttack(Mob* other) { @@ -3002,21 +2636,11 @@ void Bot::BotRangedAttack(Mob* other) { return; Log.Out(Logs::Detail, Logs::Combat, "Shooting %s with bow %s (%d) and arrow %s (%d)", other->GetCleanName(), RangeWeapon->Name, RangeWeapon->ID, Ammo->Name, Ammo->ID); - - if(!IsAttackAllowed(other) || - IsCasting() || - DivineAura() || - IsStunned() || - IsMezzed() || - (GetAppearance() == eaDead)) - { + if(!IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) return; - } SendItemAnimation(other, Ammo, SkillArchery); - DoArcheryAttackDmg(GetTarget(), rangedItem, ammoItem); - //break invis when you attack if(invisible) { Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility due to melee attack."); @@ -3024,13 +2648,15 @@ void Bot::BotRangedAttack(Mob* other) { BuffFadeByEffect(SE_Invisibility2); invisible = false; } + if(invisible_undead) { Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. undead due to melee attack."); BuffFadeByEffect(SE_InvisVsUndead); BuffFadeByEffect(SE_InvisVsUndead2); invisible_undead = false; } - if(invisible_animals){ + + if(invisible_animals) { Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. animals due to melee attack."); BuffFadeByEffect(SE_InvisVsAnimals); invisible_animals = false; @@ -3053,26 +2679,21 @@ void Bot::BotRangedAttack(Mob* other) { } bool Bot::CheckBotDoubleAttack(bool tripleAttack) { - //Check for bonuses that give you a double attack chance regardless of skill (ie Bestial Frenzy/Harmonious Attack AA) - uint32 bonusGiveDA = aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack; - + uint32 bonusGiveDA = (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack); // If you don't have the double attack skill, return if(!GetSkill(SkillDoubleAttack) && !(GetClass() == BARD || GetClass() == BEASTLORD)) return false; // You start with no chance of double attacking float chance = 0.0f; - uint16 skill = GetSkill(SkillDoubleAttack); - - int32 bonusDA = aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance; - + int32 bonusDA = (aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance); //Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base. if (skill) - chance = (float(skill+GetLevel()) * (float(100.0f+bonusDA+bonusGiveDA) /100.0f)) /500.0f; + chance = ((float(skill + GetLevel()) * (float(100.0f + bonusDA + bonusGiveDA) / 100.0f)) / 500.0f); else - chance = (float(bonusGiveDA) * (float(100.0f+bonusDA)/100.0f) ) /100.0f; + chance = ((float(bonusGiveDA) * (float(100.0f + bonusDA) / 100.0f)) / 100.0f); //Live now uses a static Triple Attack skill (lv 46 = 2% lv 60 = 20%) - We do not have this skill on EMU ATM. //A reasonable forumla would then be TA = 20% * chance @@ -3080,9 +2701,9 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) { //Kayen: Need to decide if we can implement triple attack skill before working in over the cap effect. if(tripleAttack) { // Only some Double Attack classes get Triple Attack [This is already checked in client_processes.cpp] - int32 triple_bonus = spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; + int32 triple_bonus = (spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance); chance *= 0.2f; //Baseline chance is 20% of your double attack chance. - chance *= float(100.0f+triple_bonus)/100.0f; //Apply modifiers. + chance *= (float(100.0f + triple_bonus) / 100.0f); //Apply modifiers. } if((zone->random.Real(0, 1) < chance)) @@ -3091,44 +2712,37 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) { return false; } -void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse, int16 chance_mod, int16 focus, bool CanRiposte, int ReuseTime) -{ +void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse, int16 chance_mod, int16 focus, bool CanRiposte, int ReuseTime) { if (!CanDoSpecialAttack(other)) return; //For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically. - //Kayen: This is unlikely to be completely accurate but use OFFENSE skill value for these effects. if (skillinuse == SkillBegging) skillinuse = SkillOffense; int damage = 0; uint32 hate = 0; int Hand = MainPrimary; - if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + if (hate == 0 && weapon_damage > 1) + hate = weapon_damage; - if(weapon_damage > 0){ - - if(GetClass() == BERSERKER){ - int bonus = 3 + GetLevel()/10; - weapon_damage = weapon_damage * (100+bonus) / 100; + if(weapon_damage > 0) { + if(GetClass() == BERSERKER) { + int bonus = (3 + GetLevel( )/ 10); + weapon_damage = (weapon_damage * (100 + bonus) / 100); } int32 min_hit = 1; - int32 max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; - - if(GetLevel() >= 28 && IsWarriorClass() ) - { - int ucDamageBonus = GetWeaponDamageBonus((const ItemData*) nullptr ); - + int32 max_hit = ((2 * weapon_damage * GetDamageTable(skillinuse)) / 100); + if(GetLevel() >= 28 && IsWarriorClass()) { + int ucDamageBonus = GetWeaponDamageBonus((const ItemData*) nullptr); min_hit += (int) ucDamageBonus; max_hit += (int) ucDamageBonus; hate += ucDamageBonus; } ApplySpecialAttackMod(skillinuse, max_hit, min_hit); - - min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; - + min_hit += (min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100); if(max_hit < min_hit) max_hit = min_hit; @@ -3137,16 +2751,16 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes else damage = zone->random.Int(min_hit, max_hit); - if(!other->CheckHitChance(this, skillinuse, Hand, chance_mod)) { + if(!other->CheckHitChance(this, skillinuse, Hand, chance_mod)) damage = 0; - } else { + else { other->AvoidDamage(this, damage, CanRiposte); other->MeleeMitigation(this, damage, min_hit); if(damage > 0) { damage += damage*focus/100; ApplyMeleeDamageBonus(skillinuse, damage); damage += other->GetFcDamageAmtIncoming(this, 0, true, skillinuse); - damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); + damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); TryCriticalHit(other, skillinuse, damage, nullptr); } } @@ -3157,7 +2771,6 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes return; } } - else damage = -5; @@ -3166,11 +2779,12 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes const ItemData* botweapon = 0; if(inst) botweapon = inst->GetItem(); + if(botweapon) { - if(botweapon->ItemType == ItemTypeShield) { + if(botweapon->ItemType == ItemTypeShield) hate += botweapon->AC; - } - hate = hate * (100 + GetFuriousBash(botweapon->Focus.Effect)) / 100; + + hate = (hate * (100 + GetFuriousBash(botweapon->Focus.Effect)) / 100); } } @@ -3183,7 +2797,6 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes } other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); - if (HasDied()) return; @@ -3203,27 +2816,22 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes } void Bot::ApplySpecialAttackMod(SkillUseTypes skill, int32 &dmg, int32 &mindmg) { - int item_slot = -1; //1: Apply bonus from AC (BOOT/SHIELD/HANDS) est. 40AC=6dmg - - switch (skill){ - + switch (skill) { case SkillFlyingKick: case SkillRoundKick: case SkillKick: item_slot = MainFeet; - break; - + break; case SkillBash: item_slot = MainSecondary; - break; - + break; case SkillDragonPunch: case SkillEagleStrike: case SkillTigerClaw: item_slot = MainHands; - break; + break; } if (item_slot >= EmuConstants::EQUIPMENT_BEGIN){ @@ -3231,13 +2839,13 @@ void Bot::ApplySpecialAttackMod(SkillUseTypes skill, int32 &dmg, int32 &mindmg) const ItemData* botweapon = 0; if(inst) botweapon = inst->GetItem(); + if(botweapon) dmg += botweapon->AC * (RuleI(Combat, SpecialAttackACBonus))/100; } } -bool Bot::CanDoSpecialAttack(Mob *other) -{ +bool Bot::CanDoSpecialAttack(Mob *other) { //Make sure everything is valid before doing any attacks. if (!other) { SetTarget(nullptr); @@ -3247,12 +2855,7 @@ bool Bot::CanDoSpecialAttack(Mob *other) if(!GetTarget()) SetTarget(other); - if ((other == nullptr || ((GetAppearance() == eaDead) || (other->IsClient() && other->CastToClient()->IsDead())) - || HasDied() || (!IsAttackAllowed(other)))) { - return false; - } - - if(other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)) + if ((other == nullptr || ((GetAppearance() == eaDead) || (other->IsClient() && other->CastToClient()->IsDead())) || HasDied() || (!IsAttackAllowed(other))) || other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)) return false; return true; @@ -3269,7 +2872,6 @@ void Bot::SetTarget(Mob* mob) { float Bot::GetMaxMeleeRangeToTarget(Mob* target) { float result = 0; - if(target) { float size_mod = GetSize(); float other_size_mod = target->GetSize(); @@ -3284,22 +2886,19 @@ float Bot::GetMaxMeleeRangeToTarget(Mob* target) { else if (other_size_mod < 6.0) other_size_mod = 8.0f; - if (other_size_mod > size_mod) { + if (other_size_mod > size_mod) size_mod = other_size_mod; - } - - // this could still use some work, but for now it's an improvement.... if (size_mod > 29) size_mod *= size_mod; else if (size_mod > 19) - size_mod *= size_mod * 2; + size_mod *= (size_mod * 2); else - size_mod *= size_mod * 4; + size_mod *= (size_mod * 4); // prevention of ridiculously sized hit boxes if (size_mod > 10000) - size_mod = size_mod / 7; + size_mod = (size_mod / 7); result = size_mod; } @@ -3314,21 +2913,14 @@ void Bot::AI_Process() { uint8 botClass = GetClass(); uint8 botLevel = GetLevel(); - if(IsCasting() && (botClass != BARD)) return; // A bot wont start its AI if not grouped - if(!GetBotOwner() || !IsGrouped()) { - return; - } - - if(GetAppearance() == eaDead) + if(!GetBotOwner() || !IsGrouped() || GetAppearance() == eaDead) return; Mob* BotOwner = GetBotOwner(); - - // The bots need an owner if(!BotOwner) return; @@ -3350,9 +2942,8 @@ void Bot::AI_Process() { SetHasHealedThisCycle(true); NotifyNextHealRotationMember(); } - else { + else NotifyNextHealRotationMember(true); - } } if(GetHasBeenSummoned()) { @@ -3361,11 +2952,10 @@ void Bot::AI_Process() { if(!GetTarget() || (IsBotCaster() && !IsBotCasterCombatRange(GetTarget())) || (IsBotArcher() && IsArcheryRange(GetTarget())) || (DistanceSquaredNoZ(static_cast(m_Position), m_PreSummonLocation) < 10)) { if(GetTarget()) FaceTarget(GetTarget()); + SetHasBeenSummoned(false); - } - else if(!IsRooted()) { - if(GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) - { + } else if(!IsRooted()) { + if(GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { Log.Out(Logs::Detail, Logs::AI, "Returning to location prior to being summoned."); CalculateNewPosition2(m_PreSummonLocation.x, m_PreSummonLocation.y, m_PreSummonLocation.z, GetRunspeed()); SetHeading(CalculateHeadingToTarget(m_PreSummonLocation.x, m_PreSummonLocation.y)); @@ -3378,35 +2968,29 @@ void Bot::AI_Process() { else SendPosition(); } - } - else { + } else { if(GetTarget()) FaceTarget(GetTarget()); + SetHasBeenSummoned(false); } - return; } if(!IsEngaged()) { if(GetFollowID()) { - if(BotOwner && BotOwner->GetTarget() && BotOwner->GetTarget()->IsNPC() && (BotOwner->GetTarget()->GetHateAmount(BotOwner) - || BotOwner->CastToClient()->AutoAttackEnabled()) && IsAttackAllowed(BotOwner->GetTarget())) { + if(BotOwner && BotOwner->GetTarget() && BotOwner->GetTarget()->IsNPC() && (BotOwner->GetTarget()->GetHateAmount(BotOwner) || BotOwner->CastToClient()->AutoAttackEnabled()) && IsAttackAllowed(BotOwner->GetTarget())) { AddToHateList(BotOwner->GetTarget(), 1); - if(HasPet()) GetPet()->AddToHateList(BotOwner->GetTarget(), 1); - } - else { + } else { Group* g = GetGroup(); - if(g) { for(int counter = 0; counter < g->GroupCount(); counter++) { if(g->members[counter]) { Mob* tar = g->members[counter]->GetTarget(); if(tar && tar->IsNPC() && tar->GetHateAmount(g->members[counter]) && IsAttackAllowed(g->members[counter]->GetTarget())) { AddToHateList(tar, 1); - if(HasPet()) GetPet()->AddToHateList(tar, 1); @@ -3419,9 +3003,7 @@ void Bot::AI_Process() { } } - if(IsEngaged()) - { - + if(IsEngaged()) { if(rest_timer.Enabled()) rest_timer.Disable(); @@ -3447,31 +3029,25 @@ void Bot::AI_Process() { // Else, it was causing the bot to aggro behind wall etc... causing massive trains. if(!CheckLosFN(GetTarget()) || GetTarget()->IsMezzed() || !IsAttackAllowed(GetTarget())) { WipeHateList(); - if(IsMoving()) { SetHeading(0); SetRunAnimSpeed(0); - - if(moved) { - moved = false; - SendPosition(); - SetMoving(false); - } + SetCurrentSpeed(GetRunspeed()); + if(moved) + SetCurrentSpeed(0); } - return; } + if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) + SendAddPlayerState(PlayerState::Aggressive); + bool atCombatRange = false; - float meleeDistance = GetMaxMeleeRangeToTarget(GetTarget()); - - if(botClass == SHADOWKNIGHT || botClass == PALADIN || botClass == WARRIOR) { - meleeDistance = meleeDistance * .30; - } - else { + if(botClass == SHADOWKNIGHT || botClass == PALADIN || botClass == WARRIOR) + meleeDistance = (meleeDistance * .30); + else meleeDistance *= (float)zone->random.Real(.50, .85); - } bool atArcheryRange = IsArcheryRange(GetTarget()); @@ -3481,8 +3057,7 @@ void Bot::AI_Process() { if(atArcheryRange && !IsBotArcher()) { SetBotArcher(true); changeWeapons = true; - } - else if(!atArcheryRange && IsBotArcher()) { + } else if(!atArcheryRange && IsBotArcher()) { SetBotArcher(false); changeWeapons = true; } @@ -3495,33 +3070,27 @@ void Bot::AI_Process() { if(IsMoving()) { SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); SetRunAnimSpeed(0); - + SetCurrentSpeed(0); if(moved) { moved = false; - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); } } - atCombatRange = true; - } - else if(IsBotCaster() && GetLevel() > 12) { + } else if(IsBotCaster() && GetLevel() > 12) { if(IsBotCasterCombatRange(GetTarget())) atCombatRange = true; } - else if(DistanceSquared(m_Position, GetTarget()->GetPosition()) <= meleeDistance) { + else if(DistanceSquared(m_Position, GetTarget()->GetPosition()) <= meleeDistance) atCombatRange = true; - } if(atCombatRange) { if(IsMoving()) { SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); - SetRunAnimSpeed(0); - + SetCurrentSpeed(0); if(moved) { moved = false; - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); } } @@ -3531,7 +3100,6 @@ void Bot::AI_Process() { float newX = 0; float newY = 0; float newZ = 0; - if(PlotPositionAroundTarget(GetTarget(), newX, newY, newZ)) { CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); return; @@ -3542,7 +3110,6 @@ void Bot::AI_Process() { float newX = 0; float newY = 0; float newZ = 0; - if(PlotPositionAroundTarget(GetTarget(), newX, newY, newZ, false) && GetArchetype() != ARCHETYPE_CASTER) { CalculateNewPosition2(newX, newY, newZ, GetRunspeed()); return; @@ -3557,35 +3124,24 @@ void Bot::AI_Process() { if(IsBotArcher() && ranged_timer.Check(false)) { if(GetTarget()->GetHPRatio() <= 99.0f) - // Mob::DoArcheryAttackDmg() takes care of Bot Range and Ammo procs BotRangedAttack(GetTarget()); } else if(!IsBotArcher() && (!(IsBotCaster() && GetLevel() > 12)) && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead)) { // we can't fight if we don't have a target, are stun/mezzed or dead.. // Stop attacking if the target is enraged - if(IsEngaged() && !BehindMob(GetTarget(), GetX(), GetY()) && GetTarget()->IsEnraged()) - return; - - if(GetBotStance() == BotStancePassive) + if((IsEngaged() && !BehindMob(GetTarget(), GetX(), GetY()) && GetTarget()->IsEnraged()) || GetBotStance() == BotStancePassive) return; // First, special attack per class (kick, backstab etc..) DoClassAttacks(GetTarget()); - - //try main hand first if(attack_timer.Check()) { Attack(GetTarget(), MainPrimary); - ItemInst *wpn = GetBotItem(MainPrimary); TryWeaponProc(wpn, GetTarget(), MainPrimary); - bool tripleSuccess = false; - if(BotOwner && GetTarget() && CanThisClassDoubleAttack()) { - - if(BotOwner && CheckBotDoubleAttack()) { + if(BotOwner && CheckBotDoubleAttack()) Attack(GetTarget(), MainPrimary, true); - } if(BotOwner && GetTarget() && GetSpecialAbility(SPECATK_TRIPLE) && CheckBotDoubleAttack(true)) { tripleSuccess = true; @@ -3593,37 +3149,27 @@ void Bot::AI_Process() { } //quad attack, does this belong here?? - if(BotOwner && GetTarget() && GetSpecialAbility(SPECATK_QUAD) && CheckBotDoubleAttack(true)) { + if(BotOwner && GetTarget() && GetSpecialAbility(SPECATK_QUAD) && CheckBotDoubleAttack(true)) Attack(GetTarget(), MainPrimary, true); - } } //Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). - int32 flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; - - if (GetTarget() && flurrychance) - { - if(zone->random.Int(0, 100) < flurrychance) - { + int32 flurrychance = (aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance); + if (GetTarget() && flurrychance) { + if(zone->random.Int(0, 100) < flurrychance) { Message_StringID(MT_NPCFlurry, YOU_FLURRY); Attack(GetTarget(), MainPrimary, false); Attack(GetTarget(), MainPrimary, false); } } - int32 ExtraAttackChanceBonus = spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance; - + int32 ExtraAttackChanceBonus = (spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance); if (GetTarget() && ExtraAttackChanceBonus) { ItemInst *wpn = GetBotItem(MainPrimary); - if(wpn){ - if(wpn->GetItem()->ItemType == ItemType2HSlash || - wpn->GetItem()->ItemType == ItemType2HBlunt || - wpn->GetItem()->ItemType == ItemType2HPiercing ) - { + if(wpn) { + if(wpn->GetItem()->ItemType == ItemType2HSlash || wpn->GetItem()->ItemType == ItemType2HBlunt || wpn->GetItem()->ItemType == ItemType2HPiercing) { if(zone->random.Int(0, 100) < ExtraAttackChanceBonus) - { Attack(GetTarget(), MainPrimary, false); - } } } } @@ -3634,6 +3180,7 @@ void Bot::AI_Process() { entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_START, GetName()); this->berserk = true; } + if (berserk && this->GetHPRatio() > 30) { entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_END, GetName()); this->berserk = false; @@ -3651,7 +3198,6 @@ void Bot::AI_Process() { int weapontype = 0; // No weapon type. bool bIsFist = true; - if(weapon) { weapontype = weapon->ItemType; bIsFist = false; @@ -3659,21 +3205,15 @@ void Bot::AI_Process() { if(bIsFist || ((weapontype != ItemType2HSlash) && (weapontype != ItemType2HPiercing) && (weapontype != ItemType2HBlunt))) { float DualWieldProbability = 0.0f; - - int32 Ambidexterity = aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity; - DualWieldProbability = (GetSkill(SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f; // 78.0 max - int32 DWBonus = spellbonuses.DualWieldChance + itembonuses.DualWieldChance; - DualWieldProbability += DualWieldProbability*float(DWBonus)/ 100.0f; - + int32 Ambidexterity = (aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity); + DualWieldProbability = ((GetSkill(SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f); // 78.0 max + int32 DWBonus = (spellbonuses.DualWieldChance + itembonuses.DualWieldChance); + DualWieldProbability += (DualWieldProbability * float(DWBonus) / 100.0f); float random = zone->random.Real(0, 1); - if (random < DualWieldProbability){ // Max 78% of DW - Attack(GetTarget(), MainSecondary); // Single attack with offhand - ItemInst *wpn = GetBotItem(MainSecondary); TryWeaponProc(wpn, GetTarget(), MainSecondary); - if( CanThisClassDoubleAttack() && CheckBotDoubleAttack()) { if(GetTarget() && GetTarget()->GetHP() > -10) Attack(GetTarget(), MainSecondary); // Single attack with offhand @@ -3683,8 +3223,7 @@ void Bot::AI_Process() { } } } - } // end in combat range - else { + } else { if(GetTarget()->IsFeared() && !spellend_timer.Enabled()){ // This is a mob that is fleeing either because it has been feared or is low on hitpoints if(GetBotStance() != BotStancePassive) @@ -3709,54 +3248,43 @@ void Bot::AI_Process() { if(GetBotStance() == BotStancePassive) return; - if(AI_EngagedCastCheck()) { + if(AI_EngagedCastCheck()) BotMeditate(false); - } else if(GetArchetype() == ARCHETYPE_CASTER) BotMeditate(true); } - } // end IsEngaged() - else { - // Not engaged in combat + } else { SetTarget(0); + if (m_PlayerState & static_cast(PlayerState::Aggressive)) + SendRemovePlayerState(PlayerState::Aggressive); if(!IsMoving() && AIthink_timer->Check() && !spellend_timer.Enabled()) { if(GetBotStance() != BotStancePassive) { if(!AI_IdleCastCheck() && !IsCasting()) BotMeditate(true); } - else { + else BotMeditate(true); - } - } if(AImovement_timer->Check()) { if(GetFollowID()) { Mob* follow = entity_list.GetMob(GetFollowID()); - if(follow) { float dist = DistanceSquared(m_Position, follow->GetPosition()); - float speed = follow->GetRunspeed(); - + int speed = follow->GetRunspeed(); if(dist < GetFollowDistance() + 1000) speed = follow->GetWalkspeed(); - SetRunAnimSpeed(0); - if(dist > GetFollowDistance()) { CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); if(rest_timer.Enabled()) rest_timer.Disable(); return; - } - else - { - if(moved) - { - moved=false; - SendPosition(); - SetMoving(false); + } else { + if(moved) { + moved = false; + SetCurrentSpeed(0); } } } @@ -3767,33 +3295,20 @@ void Bot::AI_Process() { // AI Processing for a Bot object's pet void Bot::PetAIProcess() { - if( !HasPet() || !GetPet() || !GetPet()->IsNPC()) return; Mob* BotOwner = this->GetBotOwner(); NPC* botPet = this->GetPet()->CastToNPC(); - if(!botPet->GetOwner() || !botPet->GetID() || !botPet->GetOwnerID()) { Kill(); return; } - if (!botPet->IsAIControlled()) - return; - - if(botPet->GetAttackTimer().Check(false)) - return; - - if (botPet->IsCasting()) - return; - - // Return if the owner of the bot pet isnt a bot. - if (!botPet->GetOwner()->IsBot()) + if (!botPet->IsAIControlled() || botPet->GetAttackTimer().Check(false) || botPet->IsCasting() || !botPet->GetOwner()->IsBot()) return; if (IsEngaged()) { - if (botPet->IsRooted()) botPet->SetTarget(hate_list.GetClosestEntOnHateList(botPet)); else @@ -3805,43 +3320,23 @@ void Bot::PetAIProcess() { if(!botPet->CheckLosFN(botPet->GetTarget()) || botPet->GetTarget()->IsMezzed() || !botPet->IsAttackAllowed(GetTarget())) { botPet->WipeHateList(); botPet->SetTarget(botPet->GetOwner()); - return; } botPet->FaceTarget(botPet->GetTarget()); - - // Lets see if we can let the main tank build a little aggro - /*if(GetBotRaidID()) { - BotRaids *br = entity_list.GetBotRaidByMob(GetOwner()); - if(br) { - if(br->GetBotMainTank() && (br->GetBotMainTank() != this)) { - if(br->GetBotMainTarget() && (br->GetBotMainTarget()->GetHateAmount(br->GetBotMainTank()) < 5000)) { - if(GetTarget() == br->GetBotMainTarget()) { - return; - } - } - } - } - }*/ - bool is_combat_range = botPet->CombatRange(botPet->GetTarget()); - // Ok, we're engaged, each class type has a special AI // Only melee class will go to melee. Casters and healers will stay behind, following the leader by default. // I should probably make the casters staying in place so they can cast.. // Ok, we 're a melee or any other class lvl<12. Yes, because after it becomes hard to go in melee for casters.. even for bots.. - if( is_combat_range ) { + if(is_combat_range) { botPet->GetAIMovementTimer()->Check(); - if(botPet->IsMoving()) { - botPet->SetRunAnimSpeed(0); botPet->SetHeading(botPet->GetTarget()->GetHeading()); if(moved) { - moved=false; - botPet->SendPosition(); - botPet->SetMoving(false); + moved = false; + botPet->SetRunAnimSpeed(0); } } @@ -3850,10 +3345,8 @@ void Bot::PetAIProcess() { float newY = 0; float newZ = 0; bool petHasAggro = false; - - if(botPet->GetTarget() && botPet->GetTarget()->GetHateTop() && botPet->GetTarget()->GetHateTop() == botPet) { + if(botPet->GetTarget() && botPet->GetTarget()->GetHateTop() && botPet->GetTarget()->GetHateTop() == botPet) petHasAggro = true; - } if(botPet->GetClass() == ROGUE && !petHasAggro && !botPet->BehindMob(botPet->GetTarget(), botPet->GetX(), botPet->GetY())) { // Move the rogue to behind the mob @@ -3873,7 +3366,6 @@ void Bot::PetAIProcess() { // Let's try to adjust our melee range so we don't appear to be bunched up bool isBehindMob = false; bool moveBehindMob = false; - if(botPet->BehindMob(botPet->GetTarget(), botPet->GetX(), botPet->GetY())) isBehindMob = true; @@ -3896,14 +3388,11 @@ void Bot::PetAIProcess() { return; if(botPet->Attack(GetTarget(), MainPrimary)) // try the main hand - if (botPet->GetTarget()) // Do we still have a target? - { + if (botPet->GetTarget()) { // We're a pet so we re able to dual attack int32 RandRoll = zone->random.Int(0, 99); - if (botPet->CanThisClassDoubleAttack() && (RandRoll < (botPet->GetLevel() + NPCDualAttackModifier))) - { - if(botPet->Attack(botPet->GetTarget(), MainPrimary)) - {} + if (botPet->CanThisClassDoubleAttack() && (RandRoll < (botPet->GetLevel() + NPCDualAttackModifier))) { + if(botPet->Attack(botPet->GetTarget(), MainPrimary)) {} } } @@ -3916,19 +3405,18 @@ void Bot::PetAIProcess() { aa_skill += botPet->GetOwner()->GetAA(aaQuickeningofDeath); // Beastlord AA aa_skill += botPet->GetOwner()->GetAA(aaWardersAlacrity); - - if(aa_skill >= 1) { - aa_chance += (aa_skill > 5 ? 5 : aa_skill) * 4; - } - if(aa_skill >= 6) { - aa_chance += (aa_skill-5 > 3 ? 3 : aa_skill-5) * 7; - } - if(aa_skill >= 9) { - aa_chance += (aa_skill-8 > 3 ? 3 : aa_skill-8) * 3; - } - if(aa_skill >= 12) { - aa_chance += (aa_skill - 11) * 1; - } + if(aa_skill >= 1) + aa_chance += ((aa_skill > 5 ? 5 : aa_skill) * 4); + + if(aa_skill >= 6) + aa_chance += ((aa_skill - 5 > 3 ? 3 : aa_skill - 5) * 7); + + if(aa_skill >= 9) + aa_chance += ((aa_skill - 8 > 3 ? 3 : aa_skill - 8) * 3); + + if(aa_skill >= 12) + aa_chance += ((aa_skill - 11) * 1); + //aa_chance += botPet->GetOwner()->GetAA(aaCompanionsAlacrity) * 3; @@ -3937,21 +3425,16 @@ void Bot::PetAIProcess() { } // Ok now, let's check pet's offhand. - if (botPet->GetAttackDWTimer().Check() && botPet->GetOwnerID() && botPet->GetOwner() && ((botPet->GetOwner()->GetClass() == MAGICIAN) || (botPet->GetOwner()->GetClass() == NECROMANCER) || (botPet->GetOwner()->GetClass() == SHADOWKNIGHT) || (botPet->GetOwner()->GetClass() == BEASTLORD))) - { - if(botPet->GetOwner()->GetLevel() >= 24) - { - float DualWieldProbability = (botPet->GetSkill(SkillDualWield) + botPet->GetLevel()) / 400.0f; + if (botPet->GetAttackDWTimer().Check() && botPet->GetOwnerID() && botPet->GetOwner() && ((botPet->GetOwner()->GetClass() == MAGICIAN) || (botPet->GetOwner()->GetClass() == NECROMANCER) || (botPet->GetOwner()->GetClass() == SHADOWKNIGHT) || (botPet->GetOwner()->GetClass() == BEASTLORD))) { + if(botPet->GetOwner()->GetLevel() >= 24) { + float DualWieldProbability = ((botPet->GetSkill(SkillDualWield) + botPet->GetLevel()) / 400.0f); DualWieldProbability -= zone->random.Real(0, 1); - if(DualWieldProbability < 0){ + if(DualWieldProbability < 0) { botPet->Attack(botPet->GetTarget(), MainSecondary); - if (botPet->CanThisClassDoubleAttack()) - { + if (botPet->CanThisClassDoubleAttack()) { int32 RandRoll = zone->random.Int(0, 99); if (RandRoll < (botPet->GetLevel() + 20)) - { botPet->Attack(botPet->GetTarget(), MainSecondary); - } } } } @@ -3965,25 +3448,21 @@ void Bot::PetAIProcess() { // See if the pet can cast any spell botPet->AI_EngagedCastCheck(); } - }// end of the combat in range - else{ + } else { // Now, if we cannot reach our target - if (!botPet->HateSummon()) - { - if(botPet->GetTarget() && botPet->AI_PursueCastCheck()) - {} - else if (botPet->GetTarget() && botPet->GetAIMovementTimer()->Check()) - { + if (!botPet->HateSummon()) { + if(botPet->GetTarget() && botPet->AI_PursueCastCheck()) {} + else if (botPet->GetTarget() && botPet->GetAIMovementTimer()->Check()) { botPet->SetRunAnimSpeed(0); if(!botPet->IsRooted()) { Log.Out(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", botPet->GetTarget()->GetCleanName()); botPet->CalculateNewPosition2(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ(), botPet->GetOwner()->GetRunspeed()); return; - } - else { + } else { botPet->SetHeading(botPet->GetTarget()->GetHeading()); if(moved) { - moved=false; + moved = false; + SetCurrentSpeed(0); botPet->SendPosition(); botPet->SetMoving(false); } @@ -3991,38 +3470,33 @@ void Bot::PetAIProcess() { } } } - } - else{ - // Franck: EQoffline + } else { // Ok if we're not engaged, what's happening.. - if(botPet->GetTarget() != botPet->GetOwner()) { + if(botPet->GetTarget() != botPet->GetOwner()) botPet->SetTarget(botPet->GetOwner()); - } - if(!IsMoving()) { + if(!IsMoving()) botPet->AI_IdleCastCheck(); - } if(botPet->GetAIMovementTimer()->Check()) { switch(pStandingPetOrder) { - case SPO_Follow: - { - float dist = DistanceSquared(botPet->GetPosition(), botPet->GetTarget()->GetPosition()); - botPet->SetRunAnimSpeed(0); - if(dist > 184) { - botPet->CalculateNewPosition2(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ(), botPet->GetTarget()->GetRunspeed()); - return; - } - else { - botPet->SetHeading(botPet->GetTarget()->GetHeading()); - if(moved) { - moved=false; - botPet->SendPosition(); - botPet->SetMoving(false); - } + case SPO_Follow: { + float dist = DistanceSquared(botPet->GetPosition(), botPet->GetTarget()->GetPosition()); + botPet->SetRunAnimSpeed(0); + if(dist > 184) { + botPet->CalculateNewPosition2(botPet->GetTarget()->GetX(), botPet->GetTarget()->GetY(), botPet->GetTarget()->GetZ(), botPet->GetTarget()->GetRunspeed()); + return; + } else { + botPet->SetHeading(botPet->GetTarget()->GetHeading()); + if(moved) { + moved = false; + SetCurrentSpeed(0); + botPet->SendPosition(); + botPet->SetMoving(false); } } break; + } case SPO_Sit: botPet->SetAppearance(eaSitting); break; @@ -4036,26 +3510,21 @@ void Bot::PetAIProcess() { void Bot::Depop() { WipeHateList(); - entity_list.RemoveFromHateLists(this); - if(HasGroup()) Bot::RemoveBotFromGroup(this, GetGroup()); - if(HasPet()) { + if(HasPet()) GetPet()->Depop(); - } _botOwner = 0; _botOwnerCharacterID = 0; _previousTarget = 0; - NPC::Depop(false); } bool Bot::DeleteBot(std::string* errorMessage) { bool hadError = false; - if(this->GetBotID() == 0) return false; @@ -4115,20 +3584,12 @@ void Bot::Spawn(Client* botCharacterOwner, std::string* errorMessage) { // Make the bot look at the bot owner FaceTarget(botCharacterOwner); - - // Level the bot to the same level as the bot owner - //this->SetLevel(botCharacterOwner->GetLevel()); - UpdateEquipmentLight(); UpdateActiveLight(); - entity_list.AddBot(this, true, true); - // Load pet LoadPet(); - this->SendPosition(); - // there is something askew with spawn struct appearance fields... // I re-enabled this until I can sort it out uint32 itemID = 0; @@ -4137,6 +3598,7 @@ void Bot::Spawn(Client* botCharacterOwner, std::string* errorMessage) { itemID = GetBotItemBySlot(i); if(itemID != 0) { materialFromSlot = InventoryOld::CalcMaterialFromSlot(i); + if(materialFromSlot != 0xFF) this->SendWearChange(materialFromSlot); } @@ -4146,9 +3608,7 @@ void Bot::Spawn(Client* botCharacterOwner, std::string* errorMessage) { // Saves the specified item as an inventory record in the database for this bot. void Bot::SetBotItemInSlot(uint32 slotID, uint32 itemID, const ItemInst* inst, std::string *errorMessage) { - uint32 augslot[EmuConstants::ITEM_COMMON_SIZE] = { NO_ITEM, NO_ITEM, NO_ITEM, NO_ITEM, NO_ITEM }; - if (this->GetBotID() == 0 || slotID < EmuConstants::EQUIPMENT_BEGIN || itemID <= NO_ITEM) return; @@ -4173,13 +3633,10 @@ void Bot::SetBotItemInSlot(uint32 slotID, uint32 itemID, const ItemInst* inst, s // Deletes the inventory record for the specified item from the database for this bot. void Bot::RemoveBotItemBySlot(uint32 slotID, std::string *errorMessage) { - if(this->GetBotID() == 0) return; - std::string query = StringFormat("DELETE FROM botinventory " - "WHERE botid = %i AND slotid = %i", - this->GetBotID(), slotID); + std::string query = StringFormat("DELETE FROM botinventory WHERE botid = %i AND slotid = %i", this->GetBotID(), slotID); auto results = database.QueryDatabase(query); if(!results.Success()) *errorMessage = std::string(results.ErrorMessage()); @@ -4190,7 +3647,6 @@ void Bot::RemoveBotItemBySlot(uint32 slotID, std::string *errorMessage) { // Retrieves all the inventory records from the database for this bot. void Bot::GetBotItems(std::string* errorMessage, InventoryOld &inv) { - if(this->GetBotID() == 0) return; @@ -4205,10 +3661,10 @@ void Bot::GetBotItems(std::string* errorMessage, InventoryOld &inv) { } for (auto row = results.begin(); row != results.end(); ++row) { - int16 slot_id = atoi(row[0]); - uint32 item_id = atoi(row[1]); - uint16 charges = atoi(row[2]); - uint32 color = atoul(row[3]); + int16 slot_id = atoi(row[0]); + uint32 item_id = atoi(row[1]); + uint16 charges = atoi(row[2]); + uint32 color = atoul(row[3]); uint32 aug[EmuConstants::ITEM_COMMON_SIZE]; aug[0] = (uint32)atoul(row[4]); aug[1] = (uint32)atoul(row[5]); @@ -4225,13 +3681,13 @@ void Bot::GetBotItems(std::string* errorMessage, InventoryOld &inv) { int16 put_slot_id = INVALID_INDEX; - if (instnodrop || ((slot_id >= EmuConstants::EQUIPMENT_BEGIN) && (slot_id <= EmuConstants::EQUIPMENT_END) && inst->GetItem()->Attuneable)) + if (instnodrop || (((slot_id >= EmuConstants::EQUIPMENT_BEGIN) && (slot_id <= EmuConstants::EQUIPMENT_END) || slot_id == 9999) && inst->GetItem()->Attuneable)) inst->SetAttuned(true); if (color > 0) inst->SetColor(color); - if (charges==255) + if (charges == 255) inst->SetCharges(-1); else inst->SetCharges(charges); @@ -4244,7 +3700,6 @@ void Bot::GetBotItems(std::string* errorMessage, InventoryOld &inv) { // Save ptr to item in inventory if (put_slot_id == INVALID_INDEX) Log.Out(Logs::General, Logs::Error, "Warning: Invalid slot_id for item in inventory: botid=%i, item_id=%i, slot_id=%i",this->GetBotID(), item_id, slot_id); - } UpdateEquipmentLight(); @@ -4252,26 +3707,20 @@ void Bot::GetBotItems(std::string* errorMessage, InventoryOld &inv) { // Returns the inventory record for this bot from the database for the specified equipment slot. uint32 Bot::GetBotItemBySlot(uint32 slotID) { - if(this->GetBotID() == 0 || slotID < EmuConstants::EQUIPMENT_BEGIN) return 0; - std::string query = StringFormat("SELECT itemid FROM botinventory WHERE botid=%i AND slotid = %i", GetBotID(), slotID); + std::string query = StringFormat("SELECT itemid FROM botinventory WHERE botid = %i AND slotid = %i", GetBotID(), slotID); auto results = database.QueryDatabase(query); - if(!results.Success()) - return 0; - - if(results.RowCount() != 1) + if(!results.Success() || results.RowCount() != 1) return 0; auto row = results.begin(); - return atoi(row[0]); } // Returns the number of inventory records the bot has in the database. uint32 Bot::GetBotItemsCount(std::string *errorMessage) { - if(this->GetBotID() == 0) return 0; @@ -4291,50 +3740,35 @@ uint32 Bot::GetBotItemsCount(std::string *errorMessage) { bool Bot::MesmerizeTarget(Mob* target) { bool Result = false; - if(target) { int mezid = 0; int mezlevel = GetLevel(); - - if(mezlevel >= 69) { + if(mezlevel >= 69) mezid = 5520; - } - else if(mezlevel == 68) { + else if(mezlevel == 68) mezid = 8035; - } - else if(mezlevel == 67) { + else if(mezlevel == 67) mezid = 5503; - } - else if(mezlevel >= 64) { + else if(mezlevel >= 64) mezid = 3358; - } - else if(mezlevel == 63) { + else if(mezlevel == 63) mezid = 3354; - } - else if(mezlevel >= 61) { + else if(mezlevel >= 61) mezid = 3341; - } - else if(mezlevel == 60) { + else if(mezlevel == 60) mezid = 2120; - } - else if(mezlevel == 59) { + else if(mezlevel == 59) mezid = 1692; - } - else if(mezlevel >= 54) { + else if(mezlevel >= 54) mezid = 1691; - } - else if(mezlevel >= 47) { + else if(mezlevel >= 47) mezid = 190; - } - else if(mezlevel >= 30) { + else if(mezlevel >= 30) mezid = 188; - } - else if(mezlevel >= 13) { + else if(mezlevel >= 13) mezid = 187; - } - else if(mezlevel >= 2) { + else if(mezlevel >= 2) mezid = 292; - } if(mezid > 0) { uint32 DontRootMeBeforeTime = 0; CastSpell(mezid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); @@ -4347,16 +3781,13 @@ bool Bot::MesmerizeTarget(Mob* target) { } void Bot::SetLevel(uint8 in_level, bool command) { - if(in_level > 0) { + if(in_level > 0) Mob::SetLevel(in_level, command); - } } void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { - if(ns) - { + if(ns) { Mob::FillSpawnStruct(ns, ForWho); - ns->spawn.afk = 0; ns->spawn.lfg = 0; ns->spawn.anon = 0; @@ -4368,78 +3799,55 @@ void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { ns->spawn.is_npc = 0; // 0=no, 1=yes ns->spawn.is_pet = 0; ns->spawn.guildrank = 0; - ns->spawn.showhelm = 1; + ns->spawn.showhelm = GetShowHelm(); ns->spawn.flymode = 0; ns->spawn.size = 0; ns->spawn.NPC = 0; // 0=player,1=npc,2=pc corpse,3=npc corpse - UpdateActiveLight(); ns->spawn.light = m_Light.Type.Active; - - ns->spawn.helm = helmtexture; //0xFF; + ns->spawn.helm = (GetShowHelm() ? helmtexture : 0); //0xFF; ns->spawn.equip_chest2 = texture; //0xFF; - const ItemData* item = 0; const ItemInst* inst = 0; - uint32 spawnedbotid = 0; spawnedbotid = this->GetBotID(); - - for (int i = 0; i < MaterialPrimary; i++) - { + for (int i = 0; i < MaterialPrimary; i++) { inst = GetBotItem(i); - if (inst) - { + if (inst) { item = inst->GetItem(); - if (item != 0) - { + if (item != 0) { ns->spawn.equipment[i].Material = item->Material; ns->spawn.equipment[i].EliteMaterial = item->EliteMaterial; ns->spawn.equipment[i].HeroForgeModel = item->HerosForgeModel; if (armor_tint[i]) - { ns->spawn.colors[i].Color = armor_tint[i]; - - } else - { ns->spawn.colors[i].Color = item->Color; - } - } - else - { + } else { if (armor_tint[i]) - { ns->spawn.colors[i].Color = armor_tint[i]; - } } } } inst = GetBotItem(MainPrimary); - if(inst) - { + if(inst) { item = inst->GetItem(); - if(item) - { + if(item) { if(strlen(item->IDFile) > 2) - { ns->spawn.equipment[MaterialPrimary].Material = atoi(&item->IDFile[2]); - } + ns->spawn.colors[MaterialPrimary].Color = GetEquipmentColor(MaterialPrimary); } } inst = GetBotItem(MainSecondary); - if(inst) - { + if(inst) { item = inst->GetItem(); - if(item) - { + if(item) { if(strlen(item->IDFile) > 2) - { ns->spawn.equipment[MaterialSecondary].Material = atoi(&item->IDFile[2]); - } + ns->spawn.colors[MaterialSecondary].Color = GetEquipmentColor(MaterialSecondary); } } @@ -4452,10 +3860,7 @@ uint32 Bot::GetBotIDByBotName(std::string botName) { std::string query = StringFormat("SELECT BotID FROM bots WHERE Name = '%s'", botName.c_str()); auto results = database.QueryDatabase(query); - if(!results.Success()) - return 0; - - if (results.RowCount() == 0) + if(!results.Success() || results.RowCount() == 0) return 0; auto row = results.begin(); @@ -4464,7 +3869,6 @@ uint32 Bot::GetBotIDByBotName(std::string botName) { Bot* Bot::LoadBot(uint32 botID, std::string* errorMessage) { Bot* loadedBot = nullptr; - if(botID == 0) return nullptr; @@ -4485,9 +3889,7 @@ Bot* Bot::LoadBot(uint32 botID, std::string* errorMessage) { return nullptr; auto row = results.begin(); - NPCType defaultNPCTypeStruct = CreateDefaultNPCTypeStructForBot(std::string(row[2]), std::string(row[3]), - atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7])); - + NPCType defaultNPCTypeStruct = CreateDefaultNPCTypeStructForBot(std::string(row[2]), std::string(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7])); NPCType tempNPCStruct = FillNPCTypeStruct(atoi(row[1]), std::string(row[2]), std::string(row[3]), atoi(row[4]), atoi(row[5]), atoi(row[6]), atoi(row[7]), atof(row[8]), atoi(row[9]), atoi(row[10]), atoi(row[11]), atoi(row[12]), atoi(row[13]), atoi(row[14]), @@ -4498,21 +3900,16 @@ Bot* Bot::LoadBot(uint32 botID, std::string* errorMessage) { defaultNPCTypeStruct.STA, defaultNPCTypeStruct.DEX, defaultNPCTypeStruct.AGI, defaultNPCTypeStruct.INT, defaultNPCTypeStruct.WIS, defaultNPCTypeStruct.CHA, defaultNPCTypeStruct.ATK); - loadedBot = new Bot(botID, atoi(row[0]), atoi(row[1]), atof(row[38]), atoi(row[39]), tempNPCStruct); - return loadedBot; } std::list Bot::GetGroupedBotsByGroupId(uint32 groupId, std::string* errorMessage) { std::list groupedBots; - if(groupId == 0) return groupedBots; - std::string query = StringFormat("SELECT g.mobid AS BotID FROM vwGroups AS g " - "JOIN bots AS b ON g.mobid = b.BotId AND g.mobtype = 'B' " - "WHERE g.groupid = %u", groupId); + std::string query = StringFormat("SELECT g.mobid AS BotID FROM vwGroups AS g JOIN bots AS b ON g.mobid = b.BotId AND g.mobtype = 'B' WHERE g.groupid = %u", groupId); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4534,11 +3931,9 @@ void Bot::LoadAndSpawnAllZonedBots(Client* botOwner) { uint32 TempGroupId = g->GetID(); std::string errorMessage; std::list ActiveBots = Bot::GetGroupedBotsByGroupId(botOwner->GetGroup()->GetID(), &errorMessage); - if(errorMessage.empty() && !ActiveBots.empty()) { for(std::list::iterator itr = ActiveBots.begin(); itr != ActiveBots.end(); ++itr) { Bot* activeBot = Bot::LoadBot(*itr, &errorMessage); - if(!errorMessage.empty()) { safe_delete(activeBot); break; @@ -4546,9 +3941,7 @@ void Bot::LoadAndSpawnAllZonedBots(Client* botOwner) { if(activeBot) { activeBot->Spawn(botOwner, &errorMessage); - g->UpdatePlayer(activeBot); - if(g->GetLeader()) activeBot->SetFollowID(g->GetLeader()->GetID()); } @@ -4557,11 +3950,6 @@ void Bot::LoadAndSpawnAllZonedBots(Client* botOwner) { database.SetGroupID(activeBot->GetCleanName(), 0, activeBot->GetBotID()); } } - - // Catch all condition for error messages destined for the zone error log - if(!errorMessage.empty()) { - // TODO: Log this error message to zone error log - } } } } @@ -4570,12 +3958,10 @@ void Bot::LoadAndSpawnAllZonedBots(Client* botOwner) { // Returns TRUE if there is atleast 1 bot in the specified group bool Bot::GroupHasBot(Group* group) { bool Result = false; - if(group) { for(int Counter = 0; Counter < MAX_GROUP_MEMBERS; Counter++) { - if (group->members[Counter] == nullptr) { + if (group->members[Counter] == nullptr) continue; - } if(group->members[Counter]->IsBot()) { Result = true; @@ -4583,18 +3969,15 @@ bool Bot::GroupHasBot(Group* group) { } } } - return Result; } std::list Bot::GetBotList(uint32 botOwnerCharacterID, std::string* errorMessage) { std::list ownersBots; - if(botOwnerCharacterID == 0) return ownersBots; - std::string query = StringFormat("SELECT BotID, Name, Class, BotLevel, Race " - "FROM bots WHERE BotOwnerCharacterID = '%u'", botOwnerCharacterID); + std::string query = StringFormat("SELECT BotID, Name, Class, BotLevel, Race FROM bots WHERE BotOwnerCharacterID = '%u'", botOwnerCharacterID); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4608,20 +3991,17 @@ std::list Bot::GetBotList(uint32 botOwnerCharacterID, std::st availableBot.BotClass = atoi(row[2]); availableBot.BotLevel = atoi(row[3]); availableBot.BotRace = atoi(row[4]); - ownersBots.push_back(availableBot); } - return ownersBots; } std::list Bot::ListSpawnedBots(uint32 characterID, std::string* errorMessage) { std::list spawnedBots; - if(characterID == 0) return spawnedBots; - std::string query = StringFormat("SELECT bot_name, zone_name FROM botleader WHERE leaderid=%i", characterID); + std::string query = StringFormat("SELECT bot_name, zone_name FROM botleader WHERE leaderid = %i", characterID); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4633,7 +4013,6 @@ std::list Bot::ListSpawnedBots(uint32 characterID, std::string* spawnedBotsList.BotLeaderCharID = characterID; strcpy(spawnedBotsList.BotName, row[0]); strcpy(spawnedBotsList.ZoneName, row[1]); - spawnedBots.push_back(spawnedBotsList); } @@ -4645,15 +4024,12 @@ void Bot::SaveBotGroup(Group* botGroup, std::string botGroupName, std::string* e return; Mob* tempGroupLeader = botGroup->GetLeader(); - if(!tempGroupLeader->IsBot()) return; uint32 botGroupId = 0; uint32 botGroupLeaderBotId = tempGroupLeader->CastToBot()->GetBotID(); - - std::string query = StringFormat("INSERT INTO botgroup (BotGroupLeaderBotId, BotGroupName) " - "VALUES (%u, '%s')", botGroupLeaderBotId, botGroupName.c_str()); + std::string query = StringFormat("INSERT INTO botgroup (BotGroupLeaderBotId, BotGroupName) VALUES (%u, '%s')", botGroupLeaderBotId, botGroupName.c_str()); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4665,14 +4041,11 @@ void Bot::SaveBotGroup(Group* botGroup, std::string botGroupName, std::string* e for(int groupMemberIndex = 0; groupMemberIndex < botGroup->GroupCount(); groupMemberIndex++) { Mob* tempBot = botGroup->members[groupMemberIndex]; - if(!tempBot || !tempBot->IsBot()) continue; uint32 botGroupMemberBotId = tempBot->CastToBot()->GetBotID(); - - query = StringFormat("INSERT INTO botgroupmembers (BotGroupId, BotId) " - "VALUES (%u, %u)", botGroupId, botGroupMemberBotId); + query = StringFormat("INSERT INTO botgroupmembers (BotGroupId, BotId) VALUES (%u, %u)", botGroupId, botGroupMemberBotId); results = database.QueryDatabase(query); if(!results.Success()) *errorMessage = std::string(results.ErrorMessage()); @@ -4681,12 +4054,10 @@ void Bot::SaveBotGroup(Group* botGroup, std::string botGroupName, std::string* e } void Bot::DeleteBotGroup(std::string botGroupName, std::string* errorMessage) { - if(botGroupName.empty()) return; uint32 botGroupId = GetBotGroupIdByBotGroupName(botGroupName, errorMessage); - if(!errorMessage->empty() || botGroupId== 0) return; @@ -4705,12 +4076,10 @@ void Bot::DeleteBotGroup(std::string botGroupName, std::string* errorMessage) { std::list Bot::LoadBotGroup(std::string botGroupName, std::string* errorMessage) { std::list botGroup; - if(botGroupName.empty()) return botGroup; uint32 botGroupId = GetBotGroupIdByBotGroupName(botGroupName, errorMessage); - if(botGroupId == 0) return botGroup; @@ -4725,21 +4094,17 @@ std::list Bot::LoadBotGroup(std::string botGroupName, std::string* err BotGroup tempBotGroup; tempBotGroup.BotGroupID = botGroupId; tempBotGroup.BotID = atoi(row[0]); - botGroup.push_back(tempBotGroup); } - return botGroup; } std::list Bot::GetBotGroupListByBotOwnerCharacterId(uint32 botOwnerCharacterId, std::string* errorMessage) { std::list botGroups; - if(botOwnerCharacterId == 0) return botGroups; - std::string query = StringFormat("SELECT BotGroupName, BotGroupLeaderName FROM vwBotGroups " - "WHERE BotOwnerCharacterId = %u", botOwnerCharacterId); + std::string query = StringFormat("SELECT BotGroupName, BotGroupLeaderName FROM vwBotGroups WHERE BotOwnerCharacterId = %u", botOwnerCharacterId); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4750,20 +4115,16 @@ std::list Bot::GetBotGroupListByBotOwnerCharacterId(uint32 botOwne BotGroupList botGroupList; botGroupList.BotGroupName = std::string(row[0]); botGroupList.BotGroupLeaderName = std::string(row[1]); - botGroups.push_back(botGroupList); } - return botGroups; } bool Bot::DoesBotGroupNameExist(std::string botGroupName) { - if(botGroupName.empty()) return false; - std::string query = StringFormat("SELECT BotGroupId FROM vwBotGroups " - "WHERE BotGroupName = '%s'", botGroupName.c_str()); + std::string query = StringFormat("SELECT BotGroupId FROM vwBotGroups WHERE BotGroupName = '%s'", botGroupName.c_str()); auto results = database.QueryDatabase(query); if (!results.Success() || results.RowCount() == 0) return false; @@ -4771,7 +4132,6 @@ bool Bot::DoesBotGroupNameExist(std::string botGroupName) { for(auto row = results.begin(); row != results.end(); ++row) { uint32 tempBotGroupId = atoi(row[0]); std::string tempBotGroupName = std::string(row[1]); - if (botGroupName == tempBotGroupName && tempBotGroupId != 0) return true; } @@ -4780,12 +4140,10 @@ bool Bot::DoesBotGroupNameExist(std::string botGroupName) { } uint32 Bot::CanLoadBotGroup(uint32 botOwnerCharacterId, std::string botGroupName, std::string* errorMessage) { - if(botOwnerCharacterId == 0 || botGroupName.empty()) return 0; - std::string query = StringFormat("SELECT BotGroupId, BotGroupName FROM vwBotGroups " - "WHERE BotOwnerCharacterId = %u", botOwnerCharacterId); + std::string query = StringFormat("SELECT BotGroupId, BotGroupName FROM vwBotGroups WHERE BotOwnerCharacterId = %u", botOwnerCharacterId); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4796,10 +4154,8 @@ uint32 Bot::CanLoadBotGroup(uint32 botOwnerCharacterId, std::string botGroupName return 0; for(auto row = results.begin(); row != results.end(); ++row) { - uint32 tempBotGroupId = atoi(row[0]); - std::string tempBotGroupName = std::string(row[1]); - + std::string tempBotGroupName = std::string(row[1]); if(botGroupName == tempBotGroupName) return tempBotGroupId; } @@ -4808,12 +4164,10 @@ uint32 Bot::CanLoadBotGroup(uint32 botOwnerCharacterId, std::string botGroupName } uint32 Bot::GetBotGroupIdByBotGroupName(std::string botGroupName, std::string* errorMessage) { - if(botGroupName.empty()) return 0; - std::string query = StringFormat("SELECT BotGroupId FROM vwBotGroups " - "WHERE BotGroupName = '%s'", botGroupName.c_str()); + std::string query = StringFormat("SELECT BotGroupId FROM vwBotGroups WHERE BotGroupName = '%s'", botGroupName.c_str()); auto results = database.QueryDatabase(query); if(!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4828,7 +4182,6 @@ uint32 Bot::GetBotGroupIdByBotGroupName(std::string botGroupName, std::string* e } uint32 Bot::GetBotGroupLeaderIdByBotGroupName(std::string botGroupName) { - if(botGroupName.empty()) return 0; @@ -4842,13 +4195,10 @@ uint32 Bot::GetBotGroupLeaderIdByBotGroupName(std::string botGroupName) { } uint32 Bot::AllowedBotSpawns(uint32 botOwnerCharacterID, std::string* errorMessage) { - if(botOwnerCharacterID == 0) return 0; - std::string query = StringFormat("SELECT value FROM quest_globals " - "WHERE name = 'bot_spawn_limit' AND charid = %i", - botOwnerCharacterID); + std::string query = StringFormat("SELECT value FROM quest_globals WHERE name = 'bot_spawn_limit' AND charid = %i", botOwnerCharacterID); auto results = database.QueryDatabase(query); if (!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -4864,23 +4214,18 @@ uint32 Bot::AllowedBotSpawns(uint32 botOwnerCharacterID, std::string* errorMessa uint32 Bot::SpawnedBotCount(uint32 botOwnerCharacterID, std::string* errorMessage) { uint32 Result = 0; - if(botOwnerCharacterID > 0) { std::list SpawnedBots = entity_list.GetBotsByBotOwnerCharacterID(botOwnerCharacterID); - Result = SpawnedBots.size(); } - return Result; } uint32 Bot::CreatedBotCount(uint32 botOwnerCharacterID, std::string* errorMessage) { - if(botOwnerCharacterID == 0) return 0; - std::string query = StringFormat("SELECT COUNT(BotID) FROM bots " - "WHERE BotOwnerCharacterID=%i", botOwnerCharacterID); + std::string query = StringFormat("SELECT COUNT(BotID) FROM bots WHERE BotOwnerCharacterID=%i", botOwnerCharacterID); auto results = database.QueryDatabase(query); if (!results.Success()) { *errorMessage = std::string(results.ErrorMessage()); @@ -5069,9 +4414,8 @@ void Bot::SendBotArcheryWearChange(uint8 material_slot, uint32 material, uint32 // Returns the item id that is in the bot inventory collection for the specified slot. ItemInst* Bot::GetBotItem(uint32 slotID) { ItemInst* item = m_inv.GetItem(slotID); - if(item){ + if(item) return item; - } return nullptr; } @@ -5148,193 +4492,148 @@ bool Bot::Bot_Command_Resist(int resisttype, int level) { int resistid = 0; switch(resisttype) { case 1: // Poison Cleric - if(level >= 30) { + if(level >= 30) resistid = 62; - } - else if(level >= 6) { + else if(level >= 6) resistid = 227; - } break; case 2: // Disease Cleric - if(level >= 36) { + if(level >= 36) resistid = 63; - } - else if(level >= 11) { + else if(level >= 11) resistid = 226; - } break; case 3: // Fire Cleric - if(level >= 33) { + if(level >= 33) resistid = 60; - } - else if(level >= 8) { + else if(level >= 8) resistid = 224; - } break; case 4: // Cold Cleric - if(level >= 38) { + if(level >= 38) resistid = 61; - } - else if(level >= 13) { + else if(level >= 13) resistid = 225; - } break; case 5: // Magic Cleric - if(level >= 43) { + if(level >= 43) resistid = 64; - } - else if(level >= 16) { + else if(level >= 16) resistid = 228; - } break; case 6: // Magic Enchanter - if(level >= 37) { + if(level >= 37) resistid = 64; - } - else if(level >= 17) { + else if(level >= 17) resistid = 228; - } break; case 7: // Poison Druid - if(level >= 44) { + if(level >= 44) resistid = 62; - } - else if(level >= 19) { + else if(level >= 19) resistid = 227; - } break; case 8: // Disease Druid - if(level >= 44) { + if(level >= 44) resistid = 63; - } - else if(level >= 19) { + else if(level >= 19) resistid = 226; - } break; case 9: // Fire Druid - if(level >= 20) { + if(level >= 20) resistid = 60; - } - else if(level >= 1) { + else if(level >= 1) resistid = 224; - } break; case 10: // Cold Druid - if(level >= 30) { + if(level >= 30) resistid = 61; - } - else if(level >= 9) { + else if(level >= 9) resistid = 225; - } break; case 11: // Magic Druid - if(level >= 49) { + if(level >= 49) resistid = 64; - } - else if(level >= 34) { + else if(level >= 34) resistid = 228; - } break; case 12: // Poison Shaman - if(level >= 35) { + if(level >= 35) resistid = 62; - } - else if(level >= 20) { + else if(level >= 20) resistid = 227; - } break; case 13: // Disease Shaman - if(level >= 30) { + if(level >= 30) resistid = 63; - } - else if(level >= 8) { + else if(level >= 8) resistid = 226; - } break; case 14: // Fire Shaman - if(level >= 27) { + if(level >= 27) resistid = 60; - } - else if(level >= 5) { + else if(level >= 5) resistid = 224; - } break; case 15: // Cold Shaman - if(level >= 24) { + if(level >= 24) resistid = 61; - } - else if(level >= 1) { + else if(level >= 1) resistid = 225; - } break; case 16: // Magic Shaman - if(level >= 43) { + if(level >= 43) resistid = 64; - } - else if(level >= 19) { + else if(level >= 19) resistid = 228; - } break; } if(resistid > 0) { Group* g = GetGroup(); if(g) { - for(int k=0; kmembers[k]) { + for(int k = 0; k < MAX_GROUP_MEMBERS; k++) { + if(g->members[k]) SpellOnTarget(resistid, g->members[k]); - } } return true; } } - return false; } bool Bot::RemoveBotFromGroup(Bot* bot, Group* group) { bool Result = false; - if(bot && group) { if(bot->HasGroup()) { if(!group->IsLeader(bot)) { bot->SetFollowID(0); - if(group->DelMember(bot)) database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID()); - - if(group->GroupCount() <= 1 && ZoneLoaded) - group->DisbandGroup(); - } - else { + } else { for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { if(!group->members[i]) continue; group->members[i]->SetFollowID(0); } - group->DisbandGroup(); database.SetGroupID(bot->GetCleanName(), 0, bot->GetBotID()); } - Result = true; } } - return Result; } bool Bot::AddBotToGroup(Bot* bot, Group* group) { bool Result = false; - if(bot && group) { if(!bot->HasGroup()) { // Add bot to this group if(group->AddMember(bot)) { if(group->GetLeader()) { bot->SetFollowID(group->GetLeader()->GetID()); - // Need to send this only once when a group is formed with a bot so the client knows it is also the group leader if(group->GroupCount() == 2 && group->GetLeader()->IsClient()) { group->UpdateGroupAAs(); @@ -5342,45 +4641,35 @@ bool Bot::AddBotToGroup(Bot* bot, Group* group) { group->SendUpdate(groupActUpdate, TempLeader); } } - Result = true; } } } - return Result; } bool Bot::BotGroupCreate(std::string botGroupLeaderName) { bool Result = false; - if(!botGroupLeaderName.empty()) { Bot* botGroupLeader = entity_list.GetBotByBotName(botGroupLeaderName); - if(botGroupLeader) Result = BotGroupCreate(botGroupLeader); } - return Result; } bool Bot::BotGroupCreate(Bot* botGroupLeader) { bool Result = false; - if(botGroupLeader && !botGroupLeader->HasGroup()) { Group* newGroup = new Group(botGroupLeader); - if(newGroup) { entity_list.AddGroup(newGroup); database.SetGroupID(botGroupLeader->GetName(), newGroup->GetID(), botGroupLeader->GetBotID()); database.SetGroupLeaderName(newGroup->GetID(), botGroupLeader->GetName()); - botGroupLeader->SetFollowID(botGroupLeader->GetBotOwner()->GetID()); - Result = true; } } - return Result; } @@ -5390,64 +4679,46 @@ bool Bot::Bot_Command_CharmTarget(int charmtype, Mob *target) { if(target) { switch(charmtype) { case 1: // Enchanter - if((charmlevel >= 64) && (charmlevel <= 75)) { + if((charmlevel >= 64) && (charmlevel <= 75)) charmid = 3355; - } - else if((charmlevel >= 62) && (charmlevel <= 63)) { + else if((charmlevel >= 62) && (charmlevel <= 63)) charmid = 3347; - } - else if((charmlevel >= 60) && (charmlevel <= 61)) { + else if((charmlevel >= 60) && (charmlevel <= 61)) charmid = 1707; - } - else if((charmlevel >= 53) && (charmlevel <= 59)) { + else if((charmlevel >= 53) && (charmlevel <= 59)) charmid = 1705; - } - else if((charmlevel >= 37) && (charmlevel <= 52)) { + else if((charmlevel >= 37) && (charmlevel <= 52)) charmid = 183; - } - else if((charmlevel >= 23) && (charmlevel <= 36)) { + else if((charmlevel >= 23) && (charmlevel <= 36)) charmid = 182; - } - else if((charmlevel >= 11) && (charmlevel <= 22)) { + else if((charmlevel >= 11) && (charmlevel <= 22)) charmid = 300; - } break; case 2: // Necromancer - if((charmlevel >= 60) && (charmlevel <= 75)) { + if((charmlevel >= 60) && (charmlevel <= 75)) charmid = 1629; - } - else if((charmlevel >=47) && (charmlevel <= 59)) { + else if((charmlevel >=47) && (charmlevel <= 59)) charmid = 198; - } - else if((charmlevel >= 31) && (charmlevel <= 46)) { + else if((charmlevel >= 31) && (charmlevel <= 46)) charmid = 197; - } - else if((charmlevel >= 18) && (charmlevel <= 30)) { + else if((charmlevel >= 18) && (charmlevel <= 30)) charmid = 196; - } break; case 3: // Druid - if((charmlevel >= 63) && (charmlevel <= 75)) { + if((charmlevel >= 63) && (charmlevel <= 75)) charmid = 3445; - } - else if((charmlevel >= 55) && (charmlevel <= 62)) { + else if((charmlevel >= 55) && (charmlevel <= 62)) charmid = 1556; - } - else if((charmlevel >= 52) && (charmlevel <= 54)) { + else if((charmlevel >= 52) && (charmlevel <= 54)) charmid = 1553; - } - else if((charmlevel >= 43) && (charmlevel <= 51)) { + else if((charmlevel >= 43) && (charmlevel <= 51)) charmid = 142; - } - else if((charmlevel >= 33) && (charmlevel <= 42)) { + else if((charmlevel >= 33) && (charmlevel <= 42)) charmid = 141; - } - else if((charmlevel >= 23) && (charmlevel <= 32)) { + else if((charmlevel >= 23) && (charmlevel <= 32)) charmid = 260; - } - else if((charmlevel >= 13) && (charmlevel <= 22)) { + else if((charmlevel >= 13) && (charmlevel <= 22)) charmid = 242; - } break; } if(charmid > 0) { @@ -5466,28 +4737,22 @@ bool Bot::Bot_Command_DireTarget(int diretype, Mob *target) { if(target) { switch(diretype) { case 1: // Enchanter - if(direlevel >= 65) { + if(direlevel >= 65) direid = 5874; - } - else if(direlevel >= 55) { + else if(direlevel >= 55) direid = 2761; - } break; case 2: // Necromancer - if(direlevel >= 65) { + if(direlevel >= 65) direid = 5876; - } - else if(direlevel >= 55) { + else if(direlevel >= 55) direid = 2759; - } break; case 3: // Druid - if(direlevel >= 65) { + if(direlevel >= 65) direid = 5875; - } - else if(direlevel >= 55) { + else if(direlevel >= 55) direid = 2760; - } break; } if(direid > 0) { @@ -5504,24 +4769,18 @@ bool Bot::Bot_Command_CalmTarget(Mob *target) { if(target) { int calmid = 0; int calmlevel = GetLevel(); - if((calmlevel >= 67) && (calmlevel <= 75)) { + if((calmlevel >= 67) && (calmlevel <= 75)) calmid = 5274; - } - else if((calmlevel >= 62) && (calmlevel <= 66)) { + else if((calmlevel >= 62) && (calmlevel <= 66)) calmid = 3197; - } - else if((calmlevel >= 35) && (calmlevel <= 61)) { + else if((calmlevel >= 35) && (calmlevel <= 61)) calmid = 45; - } - else if((calmlevel >= 18) && (calmlevel <= 34)) { + else if((calmlevel >= 18) && (calmlevel <= 34)) calmid = 47; - } - else if((calmlevel >= 6) && (calmlevel <= 17)) { + else if((calmlevel >= 6) && (calmlevel <= 17)) calmid = 501; - } - else if((calmlevel >= 1) && (calmlevel <= 5)) { + else if((calmlevel >= 1) && (calmlevel <= 5)) calmid = 208; - } if(calmid > 0) { uint32 DontRootMeBeforeTime = 0; CastSpell(calmid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); @@ -5536,30 +4795,22 @@ bool Bot::Bot_Command_RezzTarget(Mob *target) { if(target) { int rezid = 0; int rezlevel = GetLevel(); - if(rezlevel >= 56) { + if(rezlevel >= 56) rezid = 1524; - } - else if(rezlevel >= 47) { + else if(rezlevel >= 47) rezid = 392; - } - else if(rezlevel >= 42) { + else if(rezlevel >= 42) rezid = 2172; - } - else if(rezlevel >= 37) { + else if(rezlevel >= 37) rezid = 388; - } - else if(rezlevel >= 32) { + else if(rezlevel >= 32) rezid = 2171; - } - else if(rezlevel >= 27) { + else if(rezlevel >= 27) rezid = 391; - } - else if(rezlevel >= 22) { + else if(rezlevel >= 22) rezid = 2170; - } - else if(rezlevel >= 18) { + else if(rezlevel >= 18) rezid = 2169; - } if(rezid > 0) { uint32 DontRootMeBeforeTime = 0; CastSpell(rezid, target->GetID(), 1, -1, -1, &DontRootMeBeforeTime); @@ -5574,58 +4825,45 @@ bool Bot::Bot_Command_Cure(int curetype, int level) { int cureid = 0; switch(curetype) { case 1: // Poison - if(level >= 58) { + if(level >= 58) cureid = 1525; - } - else if(level >= 48) { + else if(level >= 48) cureid = 97; - } - else if(level >= 22) { + else if(level >= 22) cureid = 95; - } - else if(level >= 1) { + else if(level >= 1) cureid = 203; - } break; case 2: // Disease - if(level >= 51) { + if(level >= 51) cureid = 3693; - } - else if(level >= 28) { + else if(level >= 28) cureid = 96; - } - else if(level >= 4) { + else if(level >= 4) cureid = 213; - } break; case 3: // Curse - if(level >= 54) { + if(level >= 54) cureid = 2880; - } - else if(level >= 38) { + else if(level >= 38) cureid = 2946; - } - else if(level >= 23) { + else if(level >= 23) cureid = 4057; - } - else if(level >= 8) { + else if(level >= 8) cureid = 4056; - } break; case 4: // Blindness - if(level >= 3) { + if(level >= 3) cureid = 212; - } break; } if(cureid > 0) { Group* g = GetGroup(); if(g) { - for(int k=0; kmembers[k]) { + for(int k = 0; k < MAX_GROUP_MEMBERS; k++) { + if(g->members[k]) SpellOnTarget(cureid, g->members[k]); - } } return true; } @@ -5662,7 +4900,7 @@ void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* cli uint8 charges[MAX_SLOT_ID] = {0}; bool botCanWear[MAX_SLOT_ID] = {0}; - for(int16 i=beginSlotID; i<=endSlotID; ++i) { + for(int16 i = beginSlotID; i <= endSlotID; ++i) { bool BotCanWear = false; bool UpdateClient = false; bool already_returned = false; @@ -5702,13 +4940,16 @@ void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* cli botCanWear[i] = BotCanWear; ItemInst* swap_item = nullptr; - const char* equipped[EmuConstants::EQUIPMENT_SIZE] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", + const char* equipped[EmuConstants::EQUIPMENT_SIZE + 1] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", "Left Wrist", "Right Wrist", "Range", "Hands", "Primary Hand", "Secondary Hand", - "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo" }; + "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo", "Powersource" }; bool success = false; int how_many_slots = 0; - for(int j = EmuConstants::EQUIPMENT_BEGIN; j <= EmuConstants::EQUIPMENT_END; ++j) { + for(int j = EmuConstants::EQUIPMENT_BEGIN; j <= (EmuConstants::EQUIPMENT_END + 1); ++j) { if((mWeaponItem->Slots & (1 << j))) { + if (j == 22) + j = 9999; + how_many_slots++; if(!GetBotItem(j)) { if(j == MainPrimary) { @@ -5738,7 +4979,7 @@ void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* cli success = true; } else { - Say("I can't Dual Wield yet."); + BotGroupSay(this, "I can't Dual Wield yet."); --how_many_slots; } } @@ -5765,8 +5006,11 @@ void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* cli } } if(!success) { - for(int j = EmuConstants::EQUIPMENT_BEGIN; j <= EmuConstants::EQUIPMENT_END; ++j) { + for(int j = EmuConstants::EQUIPMENT_BEGIN; j <= (EmuConstants::EQUIPMENT_END + 1); ++j) { if((mWeaponItem->Slots & (1 << j))) { + if (j == 22) + j = 9999; + swap_item = GetBotItem(j); failedLoreCheck = false; for (int k = AUG_BEGIN; k < EmuConstants::ITEM_COMMON_SIZE; ++k) { @@ -5808,7 +5052,7 @@ void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* cli } else { botCanWear[i] = false; - Say("I can't Dual Wield yet."); + BotGroupSay(this, "I can't Dual Wield yet."); } } else { @@ -5846,22 +5090,22 @@ void Bot::PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* cli } } if(inst) { + client->DeleteItemInInventory(i, 0, UpdateClient); if(!botCanWear[i]) { client->PushItemOnCursor(*inst, true); } - client->DeleteItemInInventory(i, 0, UpdateClient); } } const ItemData* item2 = 0; - for(int y=beginSlotID; y<=endSlotID; ++y) { + for(int y = beginSlotID; y <= endSlotID; ++y) { item2 = database.GetItem(items[y]); if(item2) { if(botCanWear[y]) { - Say("Thank you for the %s, %s.", item2->Name, client->GetName()); + BotGroupSay(this, "Thank you for the %s, %s!", item2->Name, client->GetName()); } else { - Say("I can't use this %s!", item2->Name); + BotGroupSay(this, "I can't use this %s!", item2->Name); } } } @@ -5873,44 +5117,44 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, SkillUseTypes att return false; Save(); - Mob *give_exp = hate_list.GetDamageTopOnHateList(this); Client *give_exp_client = nullptr; - if(give_exp && give_exp->IsClient()) give_exp_client = give_exp->CastToClient(); bool IsLdonTreasure = (this->GetClass() == LDON_TREASURE); - if(entity_list.GetCorpseByID(GetID())) entity_list.GetCorpseByID(GetID())->Depop(); Group *g = GetGroup(); if(g) { - for(int i=0; imembers[i]) { if(g->members[i] == this) { // If the leader dies, make the next bot the leader // and reset all bots followid if(g->IsLeader(g->members[i])) { - if(g->members[i+1]) { - g->SetLeader(g->members[i+1]); - g->members[i+1]->SetFollowID(g->members[i]->GetFollowID()); - for(int j=0; jmembers[j] && (g->members[j] != g->members[i+1])) { - g->members[j]->SetFollowID(g->members[i+1]->GetID()); - } + if(g->members[i + 1]) { + g->SetLeader(g->members[i + 1]); + g->members[i + 1]->SetFollowID(g->members[i]->GetFollowID()); + for(int j = 0; j < MAX_GROUP_MEMBERS; j++) { + if(g->members[j] && (g->members[j] != g->members[i + 1])) + g->members[j]->SetFollowID(g->members[i + 1]->GetID()); } } } // delete from group data RemoveBotFromGroup(this, g); + //Make sure group still exists if it doesnt they were already updated in RemoveBotFromGroup + g = GetGroup(); + if (!g) + break; // if group members exist below this one, move // them all up one slot in the group list - int j = i+1; - for(; jmembers[j]) { g->members[j-1] = g->members[j]; strcpy(g->membername[j-1], g->members[j]->GetCleanName()); @@ -5926,55 +5170,26 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, SkillUseTypes att gu->action = groupActLeave; strcpy(gu->membername, GetCleanName()); if(g) { - for(int k=0; kmembers[k] && g->members[k]->IsClient()) g->members[k]->CastToClient()->QueuePacket(outapp); } } safe_delete(outapp); - - // now that's done, lets see if all we have left is the client - // and we can clean up the clients raid group and group - /*if(GetBotRaidID()) { - BotRaids* br = entity_list.GetBotRaidByMob(this); - if(br) { - if(this == br->botmaintank) { - br->botmaintank = nullptr; - } - if(this == br->botsecondtank) { - br->botsecondtank = nullptr; - } - } - if(g->GroupCount() == 0) { - uint32 gid = g->GetID(); - if(br) { - br->RemoveEmptyBotGroup(); - } - entity_list.RemoveGroup(gid); - } - if(br && (br->RaidBotGroupsCount() == 1)) { - br->RemoveClientGroup(br->GetRaidBotLeader()); - } - if(br && (br->RaidBotGroupsCount() == 0)) { - br->DisbandBotRaid(); - } - }*/ } } } } - if(GetInHealRotation()) { + if(GetInHealRotation()) GetHealRotationLeader()->RemoveHealRotationMember(this); - } entity_list.RemoveBot(this->GetID()); - return true; } void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) { - if(spell_id==0) + if(spell_id == 0) spell_id = SPELL_UNKNOWN; //handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds @@ -5982,13 +5197,8 @@ void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, SkillUseTypes attack_ Log.Out(Logs::Detail, Logs::Combat, "Triggering EVENT_ATTACK due to attack by %s", from->GetName()); parse->EventNPC(EVENT_ATTACK, this, from, "", 0); } - + attacked_timer.Start(CombatEventTimer_expire); - - // TODO: A bot doesnt call this, right? - /*if (!IsEngaged()) - zone->AddAggroMob();*/ - // if spell is lifetap add hp to the caster if (spell_id != SPELL_UNKNOWN && IsLifetapSpell(spell_id)) { int healed = GetActSpellHealing(spell_id, damage); @@ -5998,7 +5208,6 @@ void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, SkillUseTypes attack_ } CommonDamage(from, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic); - if(GetHP() < 0) { if(IsCasting()) InterruptSpell(); @@ -6006,35 +5215,26 @@ void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, SkillUseTypes attack_ } SendHPUpdate(); - - if(this == from) { + if(this == from) return; - } // Aggro the bot's group members - if(IsGrouped()) - { + if(IsGrouped()) { Group *g = GetGroup(); - if(g) - { - for(int i=0; imembers[i] && g->members[i]->IsBot() && from && !g->members[i]->CheckAggro(from) && g->members[i]->IsAttackAllowed(from)) - { g->members[i]->AddToHateList(from, 1); - } } } } } -void Bot::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/) -{ +void Bot::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, bool iYellForHelp /*= true*/, bool bFrenzy /*= false*/, bool iBuffTic /*= false*/) { Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic); } -bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) -{ +bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); Log.Out(Logs::General, Logs::Error, "A null Mob object was passed to Bot::Attack for evaluation!"); @@ -6045,15 +5245,10 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b SetTarget(other); Log.Out(Logs::Detail, Logs::Combat, "Attacking %s with hand %d %s", other?other->GetCleanName():"(nullptr)", Hand, FromRiposte?"(this is a riposte)":""); - - if ((IsCasting() && (GetClass() != BARD) && !IsFromSpell) || - other == nullptr || - (GetHP() < 0) || - (GetAppearance() == eaDead) || - (!IsAttackAllowed(other))) - { + if ((IsCasting() && (GetClass() != BARD) && !IsFromSpell) || other == nullptr || (GetHP() < 0) || (GetAppearance() == eaDead) || (!IsAttackAllowed(other))) { if(this->GetOwnerID()) entity_list.MessageClose(this, 1, 200, 10, "%s says, '%s is not a legal target master.'", this->GetCleanName(), this->GetTarget()->GetCleanName()); + if(other) { RemoveFromHateList(other); Log.Out(Logs::Detail, Logs::Combat, "I am not allowed to attack %s", other->GetCleanName()); @@ -6065,22 +5260,14 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b Log.Out(Logs::Detail, Logs::Combat, "Attack canceled, Divine Aura is in effect."); return false; } - - // TODO: Uncomment this block after solved the bug that is assigning a null value to GetTarget() for bots while in combat. Appears to happen at random, but frequently. - /*if(HasGroup() && _previousTarget != GetTarget()) { - std::ostringstream attackMessage; - attackMessage << "Attacking " << other->GetCleanName() << "."; - - GetGroup()->GroupMessage(this, 0, 100, attackMessage.str().c_str()); - }*/ - + FaceTarget(GetTarget()); - ItemInst* weapon = nullptr; if(Hand == MainPrimary) { weapon = GetBotItem(MainPrimary); OffHandAtk(false); } + if(Hand == MainSecondary) { weapon = GetBotItem(MainSecondary); OffHandAtk(true); @@ -6089,46 +5276,46 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if(weapon != nullptr) { if (!weapon->IsWeapon()) { Log.Out(Logs::Detail, Logs::Combat, "Attack canceled, Item %s (%d) is not a weapon.", weapon->GetItem()->Name, weapon->GetID()); - return(false); + return false; } Log.Out(Logs::Detail, Logs::Combat, "Attacking with weapon: %s (%d)", weapon->GetItem()->Name, weapon->GetID()); - } else { - Log.Out(Logs::Detail, Logs::Combat, "Attacking without a weapon."); } + else + Log.Out(Logs::Detail, Logs::Combat, "Attacking without a weapon."); // calculate attack_skill and skillinuse depending on hand and weapon // also send Packet to near clients SkillUseTypes skillinuse; AttackAnimation(skillinuse, Hand, weapon); Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse); - /// Now figure out damage int damage = 0; uint8 mylevel = GetLevel() ? GetLevel() : 1; uint32 hate = 0; - if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt; + if (weapon) + hate = (weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt); + int weapon_damage = GetWeaponDamage(other, weapon, &hate); - if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + if (hate == 0 && weapon_damage > 1) + hate = weapon_damage; //if weapon damage > 0 then we know we can hit the target with this weapon //otherwise we cannot and we set the damage to -5 later on - if(weapon_damage > 0){ - + if(weapon_damage > 0) { //Berserker Berserk damage bonus if(berserk && (GetClass() == BERSERKER)){ - int bonus = 3 + GetLevel()/10; //unverified - weapon_damage = weapon_damage * (100+bonus) / 100; + int bonus = (3 + GetLevel() / 10); //unverified + weapon_damage = (weapon_damage * (100 + bonus) / 100); Log.Out(Logs::Detail, Logs::Combat, "Berserker damage bonus increases DMG to %d", weapon_damage); } //try a finishing blow.. if successful end the attack - if(TryFinishingBlow(other, skillinuse)) { - return (true); - } + if(TryFinishingBlow(other, skillinuse)) + return true; //damage formula needs some work int min_hit = 1; - int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; + int max_hit = ((2 * weapon_damage * GetDamageTable(skillinuse)) / 100); if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) max_hit = (RuleI(Combat, HitCapPre10)); @@ -6146,16 +5333,11 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b // // This is not recommended for normal usage, as the damage bonus represents a non-trivial component of the DPS output // of weapons wielded by higher-level melee characters (especially for two-handed weapons). - int ucDamageBonus = 0; - - if( Hand == MainPrimary && GetLevel() >= 28 && IsWarriorClass() ) - { + if(Hand == MainPrimary && GetLevel() >= 28 && IsWarriorClass()) { // Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) by characters level 28 and above // who belong to a melee class. If we're here, then all of these conditions apply. - ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const ItemData*) nullptr ); - min_hit += (int) ucDamageBonus; max_hit += (int) ucDamageBonus; hate += ucDamageBonus; @@ -6164,16 +5346,14 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b //Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon. if (Hand==MainSecondary) { if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){ - ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const ItemData*) nullptr ); - min_hit += (int) ucDamageBonus; max_hit += (int) ucDamageBonus; hate += ucDamageBonus; } } - min_hit = min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; + min_hit = (min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100); if(max_hit < min_hit) max_hit = min_hit; @@ -6203,7 +5383,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b other->MeleeMitigation(this, damage, min_hit, opts); if(damage > 0) { ApplyMeleeDamageBonus(skillinuse, damage); - damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); + damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); TryCriticalHit(other, skillinuse, damage, opts); Log.Out(Logs::Detail, Logs::Combat, "Generating hate %d towards %s", hate, GetCleanName()); // now add done damage to the hate list @@ -6217,12 +5397,13 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b //riposte bool slippery_attack = false; // Part of hack to allow riposte to become a miss, but still allow a Strikethrough chance (like on Live) if (damage == -3) { - if (FromRiposte) return false; + if (FromRiposte) + return false; else { if (Hand == MainSecondary) {// Do we even have it & was attack with mainhand? If not, don't bother with other calculations //Live AA - SlipperyAttacks //This spell effect most likely directly modifies the actual riposte chance when using offhand attack. - int32 OffhandRiposteFail = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; + int32 OffhandRiposteFail = (aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail); OffhandRiposteFail *= -1; //Live uses a negative value for this. if (OffhandRiposteFail && @@ -6231,17 +5412,18 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b slippery_attack = true; } else DoRiposte(other); - if (GetHP() < 0) return false; + if (GetHP() < 0) + return false; } else DoRiposte(other); - if (GetHP() < 0) return false; + if (GetHP() < 0) + return false; } } if (((damage < 0) || slippery_attack) && !FromRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA - int32 bonusStrikeThrough = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - + int32 bonusStrikeThrough = (itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough); if(bonusStrikeThrough && (zone->random.Int(0, 100) < bonusStrikeThrough)) { Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit @@ -6249,22 +5431,21 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b } } } - else{ + else damage = -5; - } // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. // If we are this far, this means we are atleast making a swing. - if (!FromRiposte) {// Ripostes never generate any aggro. + if (!FromRiposte) other->AddToHateList(this, hate); - } /////////////////////////////////////////////////////////// ////// Send Attack Damage /////////////////////////////////////////////////////////// other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); - if (GetHP() < 0) return false; + if (GetHP() < 0) + return false; MeleeLifeTap(damage); @@ -6278,12 +5459,14 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b BuffFadeByEffect(SE_Invisibility2); invisible = false; } + if(invisible_undead) { Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. undead due to melee attack."); BuffFadeByEffect(SE_InvisVsUndead); BuffFadeByEffect(SE_InvisVsUndead2); invisible_undead = false; } + if(invisible_animals){ Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. animals due to melee attack."); BuffFadeByEffect(SE_InvisVsAnimals); @@ -6310,15 +5493,12 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (damage > 0) return true; - else return false; } -int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) -{ +int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) { const SPDat_Spell_Struct &spell = spells[spell_id]; - int32 value = 0; int lvlModifier = 100; int spell_level = 0; @@ -6329,116 +5509,94 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) int32 base1 = 0; int32 base2 = 0; uint32 slot = 0; - bool LimitFound = false; int FocusCount = 0; - std::map >::const_iterator find_iter = aa_effects.find(aa_ID); if(find_iter == aa_effects.end()) - { return 0; - } - - for (std::map::const_iterator iter = aa_effects[aa_ID].begin(); iter != aa_effects[aa_ID].end(); ++iter) - { + + for (std::map::const_iterator iter = aa_effects[aa_ID].begin(); iter != aa_effects[aa_ID].end(); ++iter) { effect = iter->second.skill_id; base1 = iter->second.base1; base2 = iter->second.base2; slot = iter->second.slot; - //AA Foci's can contain multiple focus effects within the same AA. //To handle this we will not automatically return zero if a limit is found. //Instead if limit is found and multiple effects, we will reset the limit check //when the next valid focus effect is found. - if (IsFocusEffect(0, 0, true,effect) || (effect == SE_TriggerOnCast)){ + if (IsFocusEffect(0, 0, true,effect) || (effect == SE_TriggerOnCast)) { FocusCount++; //If limit found on prior check next, else end loop. - if (FocusCount > 1){ - if (LimitFound){ + if (FocusCount > 1) { + if (LimitFound) { value = 0; LimitFound = false; } - - else{ + else break; - } } } - switch (effect) - { + switch (effect) { case SE_Blank: break; - - //Handle Focus Limits case SE_LimitResist: - if(base1) - { + if(base1) { if(spell.resisttype != base1) LimitFound = true; } - break; + break; case SE_LimitInstant: if(spell.buffduration) LimitFound = true; - break; + break; case SE_LimitMaxLevel: - spell_level = spell.classes[(GetClass()%16) - 1]; + spell_level = spell.classes[(GetClass() % 16) - 1]; lvldiff = spell_level - base1; //every level over cap reduces the effect by base2 percent unless from a clicky when ItemCastsUseFocus is true - if(lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) - { - if(base2 > 0) - { - lvlModifier -= base2*lvldiff; + if(lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) { + if(base2 > 0) { + lvlModifier -= (base2 * lvldiff); if(lvlModifier < 1) LimitFound = true; } - else { + else LimitFound = true; - } } - break; + break; case SE_LimitMinLevel: - if((spell.classes[(GetClass()%16) - 1]) < base1) + if((spell.classes[(GetClass() % 16) - 1]) < base1) LimitFound = true; - break; + break; case SE_LimitCastTimeMin: if (spell.cast_time < base1) LimitFound = true; - break; + break; case SE_LimitSpell: - // Exclude spell(any but this) if(base1 < 0) { if (spell_id == (base1*-1)) LimitFound = true; - } - else { - // Include Spell(only this) + } else { if (spell_id != base1) LimitFound = true; } - break; + break; case SE_LimitMinDur: if (base1 > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) LimitFound = true; - break; + break; case SE_LimitEffect: - // Exclude effect(any but this) if(base1 < 0) { if(IsEffectInSpell(spell_id,(base1*-1))) LimitFound = true; - } - else { - // Include effect(only this) + } else { if(!IsEffectInSpell(spell_id,base1)) LimitFound = true; } - break; + break; case SE_LimitSpellType: - switch(base1) - { + switch(base1) { case 0: if (!IsDetrimentalSpell(spell_id)) LimitFound = true; @@ -6448,130 +5606,93 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) LimitFound = true; break; } - break; + break; case SE_LimitManaMin: if(spell.mana < base1) LimitFound = true; - break; - + break; case SE_LimitTarget: - // Exclude - if(base1 < 0){ - if(-base1 == spell.targettype) - LimitFound = true; - } - // Include - else { - if(base1 != spell.targettype) - LimitFound = true; - } - break; - + if(base1 < 0) { + if(-base1 == spell.targettype) + LimitFound = true; + } else { + if(base1 != spell.targettype) + LimitFound = true; + } + break; case SE_LimitCombatSkills: - // 1 is for disciplines only - if(base1 == 1 && !IsDiscipline(spell_id)) - LimitFound = true; - // 0 is spells only - else if(base1 == 0 && IsDiscipline(spell_id)) + if((base1 == 1 && !IsDiscipline(spell_id)) || (base1 == 0 && IsDiscipline(spell_id))) LimitFound = true; break; - case SE_LimitSpellGroup: - if(base1 > 0 && base1 != spell.spellgroup) + if((base1 > 0 && base1 != spell.spellgroup) || (base1 < 0 && base1 == spell.spellgroup)) LimitFound = true; - else if(base1 < 0 && base1 == spell.spellgroup) - LimitFound = true; - break; - - + break; case SE_LimitCastingSkill: LimitSpellSkill = true; if(base1 == spell.skill) SpellSkill_Found = true; - break; - + break; case SE_LimitClass: //Do not use this limit more then once per spell. If multiple class, treat value like items would. - if (!PassLimitClass(base1, GetClass())) - LimitFound = true; - break; - - + if (!PassLimitClass(base1, GetClass())) + LimitFound = true; + break; //Handle Focus Effects case SE_ImprovedDamage: if (type == focusImprovedDamage && base1 > value) value = base1; - break; - + break; case SE_ImprovedHeal: if (type == focusImprovedHeal && base1 > value) value = base1; - break; - + break; case SE_ReduceManaCost: - if (type == focusManaCost ) + if (type == focusManaCost) value = base1; - break; - + break; case SE_IncreaseSpellHaste: if (type == focusSpellHaste && base1 > value) value = base1; break; - case SE_IncreaseSpellDuration: if (type == focusSpellDuration && base1 > value) value = base1; break; - case SE_SpellDurationIncByTic: if (type == focusSpellDurByTic && base1 > value) value = base1; break; - case SE_SwarmPetDuration: if (type == focusSwarmPetDuration && base1 > value) value = base1; break; - case SE_IncreaseRange: if (type == focusRange && base1 > value) value = base1; break; - case SE_ReduceReagentCost: if (type == focusReagentCost && base1 > value) value = base1; break; - case SE_PetPowerIncrease: if (type == focusPetPower && base1 > value) value = base1; break; - case SE_SpellResistReduction: if (type == focusResistRate && base1 > value) value = base1; break; - case SE_SpellHateMod: - if (type == focusSpellHateMod) - { - if(value != 0) - { - if(value > 0) - { + if (type == focusSpellHateMod) { + if(value != 0) { + if(value > 0) { if(base1 > value) - { value = base1; - } - } - else - { + } else { if(base1 < value) - { value = base1; - } } } else @@ -6579,158 +5700,100 @@ int32 Bot::CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint16 spell_id) } break; - case SE_ReduceReuseTimer: - { + case SE_ReduceReuseTimer: { if(type == focusReduceRecastTime) - value = base1 / 1000; - + value = (base1 / 1000); break; } - - case SE_TriggerOnCast: - { - if(type == focusTriggerOnCast) - { - if(zone->random.Int(0, 100) <= base1){ + case SE_TriggerOnCast: { + if(type == focusTriggerOnCast) { + if(zone->random.Int(0, 100) <= base1) value = base2; - } - - else{ + else { value = 0; LimitFound = true; } } break; } - case SE_FcSpellVulnerability: - { + case SE_FcSpellVulnerability: { if(type == focusSpellVulnerability) - { value = base1; - } break; } - case SE_BlockNextSpellFocus: - { - if(type == focusBlockNextSpell) - { + case SE_BlockNextSpellFocus: { + if(type == focusBlockNextSpell) { if(zone->random.Int(1, 100) <= base1) value = 1; } break; } - case SE_FcTwincast: - { + case SE_FcTwincast: { if(type == focusTwincast) - { value = base1; - } break; } - - /* - case SE_SympatheticProc: - { - if(type == focusSympatheticProc) - { - float ProcChance, ProcBonus; - int16 ProcRateMod = base1; //Baseline is 100 for most Sympathetic foci - int32 cast_time = GetActSpellCasttime(spell_id, spells[spell_id].cast_time); - GetSympatheticProcChances(ProcBonus, ProcChance, cast_time, ProcRateMod); - - if(zone->random.Real(0, 1) <= ProcChance) - value = focus_id; - - else - value = 0; - } - break; - } - */ - case SE_FcDamageAmt: - { + case SE_FcDamageAmt: { if(type == focusFcDamageAmt) value = base1; - break; } - - case SE_FcDamageAmtCrit: - { + case SE_FcDamageAmtCrit: { if(type == focusFcDamageAmtCrit) value = base1; - break; } - - case SE_FcDamageAmtIncoming: - { + case SE_FcDamageAmtIncoming: { if(type == focusFcDamageAmtIncoming) value = base1; - break; } - case SE_FcHealAmtIncoming: if(type == focusFcHealAmtIncoming) value = base1; break; - case SE_FcHealPctCritIncoming: if (type == focusFcHealPctCritIncoming) value = base1; break; - case SE_FcHealAmtCrit: if(type == focusFcHealAmtCrit) value = base1; break; - case SE_FcHealAmt: if(type == focusFcHealAmt) value = base1; break; - case SE_FcHealPctIncoming: if(type == focusFcHealPctIncoming) value = base1; break; - - case SE_FcBaseEffects: - { + case SE_FcBaseEffects: { if (type == focusFcBaseEffects) value = base1; - break; } - case SE_FcDamagePctCrit: - { + case SE_FcDamagePctCrit: { if(type == focusFcDamagePctCrit) value = base1; - break; } - - case SE_FcIncreaseNumHits: - { + case SE_FcIncreaseNumHits: { if(type == focusIncreaseNumHits) value = base1; - break; } //Check for spell skill limits. - if ((LimitSpellSkill) && (!SpellSkill_Found)) - return 0; - + if ((LimitSpellSkill) && (!SpellSkill_Found)) + return 0; } } - if (LimitFound){ + if (LimitFound) return 0; - } - return(value*lvlModifier/100); + return (value * lvlModifier / 100); } int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { @@ -6741,18 +5804,13 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { int32 realTotal2 = 0; int32 realTotal3 = 0; bool rand_effectiveness = false; - //Improved Healing, Damage & Mana Reduction are handled differently in that some are random percentages //In these cases we need to find the most powerful effect, so that each piece of gear wont get its own chance - if((bottype == BotfocusManaCost || bottype == BotfocusImprovedHeal || bottype == BotfocusImprovedDamage) - && RuleB(Spells, LiveLikeFocusEffects)) - { + if((bottype == BotfocusManaCost || bottype == BotfocusImprovedHeal || bottype == BotfocusImprovedDamage) && RuleB(Spells, LiveLikeFocusEffects)) rand_effectiveness = true; - } //Check if item focus effect exists for the client. - if (itembonuses.FocusEffects[bottype]){ - + if (itembonuses.FocusEffects[bottype]) { const ItemData* TempItem = 0; const ItemData* UsedItem = 0; const ItemInst* TempInst = 0; @@ -6760,35 +5818,25 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { int32 Total = 0; int32 focus_max = 0; int32 focus_max_real = 0; - //item focus - for(int x = EmuConstants::EQUIPMENT_BEGIN; x <= EmuConstants::EQUIPMENT_END; x++) - { + for(int x = EmuConstants::EQUIPMENT_BEGIN; x <= EmuConstants::EQUIPMENT_END; x++) { TempItem = nullptr; ItemInst* ins = GetBotItem(x); if (!ins) continue; + TempItem = ins->GetItem(); if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) { if(rand_effectiveness) { focus_max = CalcBotFocusEffect(bottype, TempItem->Focus.Effect, spell_id, true); - if (focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) { - focus_max_real = focus_max; - UsedItem = TempItem; - UsedFocusID = TempItem->Focus.Effect; - } else if (focus_max < 0 && focus_max < focus_max_real) { + if ((focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) || (focus_max < 0 && focus_max < focus_max_real)) { focus_max_real = focus_max; UsedItem = TempItem; UsedFocusID = TempItem->Focus.Effect; } - } - else { + } else { Total = CalcBotFocusEffect(bottype, TempItem->Focus.Effect, spell_id); - if (Total > 0 && realTotal >= 0 && Total > realTotal) { - realTotal = Total; - UsedItem = TempItem; - UsedFocusID = TempItem->Focus.Effect; - } else if (Total < 0 && Total < realTotal) { + if ((Total > 0 && realTotal >= 0 && Total > realTotal) || (Total < 0 && Total < realTotal)) { realTotal = Total; UsedItem = TempItem; UsedFocusID = TempItem->Focus.Effect; @@ -6796,8 +5844,7 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { } } - for (int y = AUG_BEGIN; y < EmuConstants::ITEM_COMMON_SIZE; ++y) - { + for (int y = AUG_BEGIN; y < EmuConstants::ITEM_COMMON_SIZE; ++y) { ItemInst *aug = nullptr; aug = ins->GetAugment(y); if(aug) @@ -6806,23 +5853,14 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { if (TempItemAug && TempItemAug->Focus.Effect > 0 && TempItemAug->Focus.Effect != SPELL_UNKNOWN) { if(rand_effectiveness) { focus_max = CalcBotFocusEffect(bottype, TempItemAug->Focus.Effect, spell_id, true); - if (focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) { - focus_max_real = focus_max; - UsedItem = TempItem; - UsedFocusID = TempItemAug->Focus.Effect; - } else if (focus_max < 0 && focus_max < focus_max_real) { + if ((focus_max > 0 && focus_max_real >= 0 && focus_max > focus_max_real) || (focus_max < 0 && focus_max < focus_max_real)) { focus_max_real = focus_max; UsedItem = TempItem; UsedFocusID = TempItemAug->Focus.Effect; } - } - else { + } else { Total = CalcBotFocusEffect(bottype, TempItemAug->Focus.Effect, spell_id); - if (Total > 0 && realTotal >= 0 && Total > realTotal) { - realTotal = Total; - UsedItem = TempItem; - UsedFocusID = TempItemAug->Focus.Effect; - } else if (Total < 0 && Total < realTotal) { + if ((Total > 0 && realTotal >= 0 && Total > realTotal) || (Total < 0 && Total < realTotal)) { realTotal = Total; UsedItem = TempItem; UsedFocusID = TempItemAug->Focus.Effect; @@ -6838,13 +5876,11 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { } //Check if spell focus effect exists for the client. - if (spellbonuses.FocusEffects[bottype]){ - + if (spellbonuses.FocusEffects[bottype]) { //Spell Focus int32 Total2 = 0; int32 focus_max2 = 0; int32 focus_max_real2 = 0; - int buff_tracker = -1; int buff_slot = 0; uint32 focusspellid = 0; @@ -6857,23 +5893,14 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { if(rand_effectiveness) { focus_max2 = CalcBotFocusEffect(bottype, focusspellid, spell_id, true); - if (focus_max2 > 0 && focus_max_real2 >= 0 && focus_max2 > focus_max_real2) { - focus_max_real2 = focus_max2; - buff_tracker = buff_slot; - focusspell_tracker = focusspellid; - } else if (focus_max2 < 0 && focus_max2 < focus_max_real2) { + if ((focus_max2 > 0 && focus_max_real2 >= 0 && focus_max2 > focus_max_real2) || (focus_max2 < 0 && focus_max2 < focus_max_real2)) { focus_max_real2 = focus_max2; buff_tracker = buff_slot; focusspell_tracker = focusspellid; } - } - else { + } else { Total2 = CalcBotFocusEffect(bottype, focusspellid, spell_id); - if (Total2 > 0 && realTotal2 >= 0 && Total2 > realTotal2) { - realTotal2 = Total2; - buff_tracker = buff_slot; - focusspell_tracker = focusspellid; - } else if (Total2 < 0 && Total2 < realTotal2) { + if ((Total2 > 0 && realTotal2 >= 0 && Total2 > realTotal2) || (Total2 < 0 && Total2 < realTotal2)) { realTotal2 = Total2; buff_tracker = buff_slot; focusspell_tracker = focusspellid; @@ -6885,20 +5912,17 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { realTotal2 = CalcBotFocusEffect(bottype, focusspell_tracker, spell_id); // For effects like gift of mana that only fire once, save the spellid into an array that consists of all available buff slots. - if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) { + if(buff_tracker >= 0 && buffs[buff_tracker].numhits > 0) m_spellHitsLeft[buff_tracker] = focusspell_tracker; - } } // AA Focus - if (aabonuses.FocusEffects[bottype]){ - + if (aabonuses.FocusEffects[bottype]) { int totalAAs = database.CountAAs(); int32 Total3 = 0; uint32 slots = 0; uint32 aa_AA = 0; uint32 aa_value = 0; - for (int i = 0; i < totalAAs; i++) { //iterate through all of the client's AAs std::map::iterator aa = botAAs.find(i); if(aa != botAAs.end()) { // make sure aa exists or we'll crash zone @@ -6908,12 +5932,8 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { continue; Total3 = CalcBotAAFocus(bottype, aa_AA, spell_id); - if (Total3 > 0 && realTotal3 >= 0 && Total3 > realTotal3) { + if ((Total3 > 0 && realTotal3 >= 0 && Total3 > realTotal3) || (Total3 < 0 && Total3 < realTotal3)) realTotal3 = Total3; - } - else if (Total3 < 0 && Total3 < realTotal3) { - realTotal3 = Total3; - } } } } @@ -6921,13 +5941,10 @@ int32 Bot::GetBotFocusEffect(BotfocusType bottype, uint16 spell_id) { if(bottype == BotfocusReagentCost && IsSummonPetSpell(spell_id) && GetAA(aaElementalPact)) return 100; - if(bottype == BotfocusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))){ + if(bottype == BotfocusReagentCost && (IsEffectInSpell(spell_id, SE_SummonItem) || IsSacrificeSpell(spell_id))) return 0; - //Summon Spells that require reagents are typically imbue type spells, enchant metal, sacrifice and shouldn't be affected - //by reagent conservation for obvious reasons. - } - return realTotal + realTotal2; + return (realTotal + realTotal2); } int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spell_id, bool best_focus) { @@ -6936,433 +5953,317 @@ int32 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel const SPDat_Spell_Struct &focus_spell = spells[focus_id]; const SPDat_Spell_Struct &spell = spells[spell_id]; - int32 value = 0; int lvlModifier = 100; int spell_level = 0; int lvldiff = 0; bool LimitSpellSkill = false; bool SpellSkill_Found = false; - for (int i = 0; i < EFFECT_COUNT; i++) { switch (focus_spell.effectid[i]) { - case SE_Blank: - break; - //check limits - - case SE_LimitResist:{ - if(focus_spell.base[i]){ - if(spell.resisttype != focus_spell.base[i]) - return(0); - } - break; - } - case SE_LimitInstant:{ - if(spell.buffduration) - return(0); - break; - } - - case SE_LimitMaxLevel:{ - if (IsNPC()) + case SE_Blank: break; - spell_level = spell.classes[(GetClass()%16) - 1]; - lvldiff = spell_level - focus_spell.base[i]; - //every level over cap reduces the effect by focus_spell.base2[i] percent unless from a clicky when ItemCastsUseFocus is true - if(lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) - { - if(focus_spell.base2[i] > 0) - { - lvlModifier -= focus_spell.base2[i]*lvldiff; - if(lvlModifier < 1) + case SE_LimitResist:{ + if(focus_spell.base[i]) { + if(spell.resisttype != focus_spell.base[i]) return 0; } - else - { - return 0; - } - } - break; - } - - case SE_LimitMinLevel: - if (IsNPC()) break; - if (spell.classes[(GetClass()%16) - 1] < focus_spell.base[i]) - return(0); - break; - - case SE_LimitCastTimeMin: - if (spells[spell_id].cast_time < (uint32)focus_spell.base[i]) - return(0); - break; - - case SE_LimitSpell: - if(focus_spell.base[i] < 0) { //exclude spell - if (spell_id == (focus_spell.base[i]*-1)) - return(0); - } else { - //this makes the assumption that only one spell can be explicitly included... - if (spell_id != focus_spell.base[i]) - return(0); } - break; - - case SE_LimitMinDur: - if (focus_spell.base[i] > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) - return(0); - break; - - case SE_LimitEffect: - if(focus_spell.base[i] < 0){ - if(IsEffectInSpell(spell_id,focus_spell.base[i])){ //we limit this effect, can't have + case SE_LimitInstant: { + if(spell.buffduration) return 0; - } + break; } - else{ - if(focus_spell.base[i] == SE_SummonPet) //summoning haste special case - { //must have one of the three pet effects to qualify - if(!IsEffectInSpell(spell_id, SE_SummonPet) && - !IsEffectInSpell(spell_id, SE_NecPet) && - !IsEffectInSpell(spell_id, SE_SummonBSTPet)) - { - return 0; + case SE_LimitMaxLevel:{ + if (IsNPC()) + break; + spell_level = spell.classes[(GetClass() % 16) - 1]; + lvldiff = (spell_level - focus_spell.base[i]); + if(lvldiff > 0 && (spell_level <= RuleI(Character, MaxLevel) || RuleB(Character, ItemCastsUseFocus) == false)) { + if(focus_spell.base2[i] > 0) { + lvlModifier -= (focus_spell.base2[i] * lvldiff); + if(lvlModifier < 1) + return 0; } + else + return 0; } - else if(!IsEffectInSpell(spell_id,focus_spell.base[i])){ //we limit this effect, must have + break; + } + case SE_LimitMinLevel: + if (IsNPC()) + break; + if (spell.classes[(GetClass() % 16) - 1] < focus_spell.base[i]) return 0; + break; + + case SE_LimitCastTimeMin: + if (spells[spell_id].cast_time < (uint32)focus_spell.base[i]) + return 0; + break; + case SE_LimitSpell: + if(focus_spell.base[i] < 0) { + if (spell_id == (focus_spell.base[i] * -1)) + return 0; + } else { + if (spell_id != focus_spell.base[i]) + return 0; } - } - break; - - - case SE_LimitSpellType: - switch( focus_spell.base[i] ) - { - case 0: - if (!IsDetrimentalSpell(spell_id)) + break; + case SE_LimitMinDur: + if (focus_spell.base[i] > CalcBuffDuration_formula(GetLevel(), spell.buffdurationformula, spell.buffduration)) + return 0; + break; + case SE_LimitEffect: + if(focus_spell.base[i] < 0) { + if(IsEffectInSpell(spell_id,focus_spell.base[i])) return 0; - break; - case 1: - if (!IsBeneficialSpell(spell_id)) + } else { + if(focus_spell.base[i] == SE_SummonPet) { + if(!IsEffectInSpell(spell_id, SE_SummonPet) && !IsEffectInSpell(spell_id, SE_NecPet) && !IsEffectInSpell(spell_id, SE_SummonBSTPet)) { + return 0; + } + } else if(!IsEffectInSpell(spell_id,focus_spell.base[i])) return 0; - break; - default: - Log.Out(Logs::General, Logs::Normal, "CalcFocusEffect: unknown limit spelltype %d", focus_spell.base[i]); - } - break; - - case SE_LimitManaMin: + } + break; + + + case SE_LimitSpellType: + switch(focus_spell.base[i]) { + case 0: + if (!IsDetrimentalSpell(spell_id)) + return 0; + break; + case 1: + if (!IsBeneficialSpell(spell_id)) + return 0; + break; + default: + Log.Out(Logs::General, Logs::Normal, "CalcFocusEffect: unknown limit spelltype %d", focus_spell.base[i]); + } + break; + + case SE_LimitManaMin: if(spell.mana < focus_spell.base[i]) return 0; - break; - - case SE_LimitTarget: - // Exclude - if((focus_spell.base[i] < 0) && -focus_spell.base[i] == spell.targettype) - return 0; - // Include - else if (focus_spell.base[i] > 0 && focus_spell.base[i] != spell.targettype) - return 0; - - break; - - case SE_LimitCombatSkills: - // 1 is for disciplines only + break; + case SE_LimitTarget: + if((focus_spell.base[i] < 0) && -focus_spell.base[i] == spell.targettype) + return 0; + else if (focus_spell.base[i] > 0 && focus_spell.base[i] != spell.targettype) + return 0; + break; + case SE_LimitCombatSkills: if(focus_spell.base[i] == 1 && !IsDiscipline(spell_id)) return 0; - // 0 is for spells only else if(focus_spell.base[i] == 0 && IsDiscipline(spell_id)) return 0; - break; - - case SE_LimitSpellGroup: + break; + case SE_LimitSpellGroup: if(focus_spell.base[i] > 0 && focus_spell.base[i] != spell.spellgroup) return 0; else if(focus_spell.base[i] < 0 && focus_spell.base[i] == spell.spellgroup) return 0; - break; - - case SE_LimitCastingSkill: + break; + case SE_LimitCastingSkill: LimitSpellSkill = true; if(focus_spell.base[i] == spell.skill) SpellSkill_Found = true; - break; - - case SE_LimitClass: - //Do not use this limit more then once per spell. If multiple class, treat value like items would. - if (!PassLimitClass(focus_spell.base[i], GetClass())) - return 0; - break; - - //handle effects - case SE_ImprovedDamage: - // No Spell used this, its handled by different spell effect IDs. - if (bottype == BotfocusImprovedDamage) { - // This is used to determine which focus should be used for the random calculation - if(best_focus) { - // If the spell contains a value in the base2 field then that is the max value - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; - } - // If the spell does not contain a base2 value, then its a straight non random value - else { - value = focus_spell.base[i]; - } - } - // Actual focus calculation starts here - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } - else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); - } - } - break; - case SE_ImprovedHeal: - if (bottype == BotfocusImprovedHeal) { - if(best_focus) { - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; - } - else { - value = focus_spell.base[i]; - } - } - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } - else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); - } - } - break; - case SE_ReduceManaCost: - if (bottype == BotfocusManaCost) { - if(best_focus) { - if (focus_spell.base2[i] != 0) { - value = focus_spell.base2[i]; - } - else { - value = focus_spell.base[i]; - } - } - else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) { - value = focus_spell.base[i]; - } - else { - value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); - } - } - break; - - case SE_IncreaseSpellHaste: - if (bottype == BotfocusSpellHaste && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_IncreaseSpellDuration: - if (bottype == BotfocusSpellDuration && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_SpellDurationIncByTic: - if (bottype == BotfocusSpellDurByTic && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_SwarmPetDuration: - if (bottype == BotfocusSwarmPetDuration && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_IncreaseRange: - if (bottype == BotfocusRange && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_ReduceReagentCost: - if (bottype == BotfocusReagentCost && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_PetPowerIncrease: - if (bottype == BotfocusPetPower && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_SpellResistReduction: - if (bottype == BotfocusResistRate && focus_spell.base[i] > value) - { - value = focus_spell.base[i]; - } - break; - case SE_SpellHateMod: - if (bottype == BotfocusSpellHateMod) - { - if(value != 0) - { - if(value > 0) - { - if(focus_spell.base[i] > value) - { + break; + case SE_LimitClass: + if (!PassLimitClass(focus_spell.base[i], GetClass())) + return 0; + break; + case SE_ImprovedDamage: + if (bottype == BotfocusImprovedDamage) { + if(best_focus) { + if (focus_spell.base2[i] != 0) + value = focus_spell.base2[i]; + else value = focus_spell.base[i]; - } } + else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) + value = focus_spell.base[i]; else - { - if(focus_spell.base[i] < value) - { - value = focus_spell.base[i]; - } - } + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); } - else + break; + case SE_ImprovedHeal: + if (bottype == BotfocusImprovedHeal) { + if(best_focus) { + if (focus_spell.base2[i] != 0) + value = focus_spell.base2[i]; + else + value = focus_spell.base[i]; + } + else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) + value = focus_spell.base[i]; + else + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + } + break; + case SE_ReduceManaCost: + if (bottype == BotfocusManaCost) { + if(best_focus) { + if (focus_spell.base2[i] != 0) + value = focus_spell.base2[i]; + else + value = focus_spell.base[i]; + } + else if (focus_spell.base2[i] == 0 || focus_spell.base[i] == focus_spell.base2[i]) + value = focus_spell.base[i]; + else + value = zone->random.Int(focus_spell.base[i], focus_spell.base2[i]); + } + break; + case SE_IncreaseSpellHaste: + if (bottype == BotfocusSpellHaste && focus_spell.base[i] > value) value = focus_spell.base[i]; + break; + case SE_IncreaseSpellDuration: + if (bottype == BotfocusSpellDuration && focus_spell.base[i] > value) + value = focus_spell.base[i]; + break; + case SE_SpellDurationIncByTic: + if (bottype == BotfocusSpellDurByTic && focus_spell.base[i] > value) + value = focus_spell.base[i]; + break; + case SE_SwarmPetDuration: + if (bottype == BotfocusSwarmPetDuration && focus_spell.base[i] > value) + value = focus_spell.base[i]; + break; + case SE_IncreaseRange: + if (bottype == BotfocusRange && focus_spell.base[i] > value) + value = focus_spell.base[i]; + break; + case SE_ReduceReagentCost: + if (bottype == BotfocusReagentCost && focus_spell.base[i] > value) + value = focus_spell.base[i]; + break; + case SE_PetPowerIncrease: + if (bottype == BotfocusPetPower && focus_spell.base[i] > value) + value = focus_spell.base[i]; + break; + case SE_SpellResistReduction: + if (bottype == BotfocusResistRate && focus_spell.base[i] > value) + value = focus_spell.base[i]; + break; + case SE_SpellHateMod: + if (bottype == BotfocusSpellHateMod) { + if(value != 0) { + if(value > 0) { + if(focus_spell.base[i] > value) + value = focus_spell.base[i]; + } + else { + if(focus_spell.base[i] < value) + value = focus_spell.base[i]; + } + } else + value = focus_spell.base[i]; + } + break; + case SE_ReduceReuseTimer: { + if(bottype == BotfocusReduceRecastTime) + value = (focus_spell.base[i] / 1000); + break; } - break; - - case SE_ReduceReuseTimer: - { - if(bottype == BotfocusReduceRecastTime) - value = focus_spell.base[i] / 1000; - - break; - } - - case SE_TriggerOnCast: - { - if(bottype == BotfocusTriggerOnCast) - - if(zone->random.Int(0, 100) <= focus_spell.base[i]) - value = focus_spell.base2[i]; - - else - value = 0; - - break; - } - case SE_FcSpellVulnerability: - { - if(bottype == BotfocusSpellVulnerability) - { - value = focus_spell.base[i]; + case SE_TriggerOnCast: { + if(bottype == BotfocusTriggerOnCast) { + if(zone->random.Int(0, 100) <= focus_spell.base[i]) + value = focus_spell.base2[i]; + else + value = 0; + } + break; } - break; - } - case SE_BlockNextSpellFocus: - { - if(bottype == BotfocusBlockNextSpell) - { - if(zone->random.Int(1, 100) <= focus_spell.base[i]) - value = 1; + case SE_FcSpellVulnerability: { + if(bottype == BotfocusSpellVulnerability) + value = focus_spell.base[i]; + break; } - break; - } - case SE_FcTwincast: - { - if(bottype == BotfocusTwincast) - { - value = focus_spell.base[i]; + case SE_BlockNextSpellFocus: { + if(bottype == BotfocusBlockNextSpell) { + if(zone->random.Int(1, 100) <= focus_spell.base[i]) + value = 1; + } + break; } - break; - } - case SE_SympatheticProc: - { - if(bottype == BotfocusSympatheticProc) - { - - float ProcChance = GetSympatheticProcChances(spell_id, focus_spell.base[i]); - - if(zone->random.Real(0, 1) <= ProcChance) - value = focus_id; - - else - value = 0; + case SE_FcTwincast: { + if(bottype == BotfocusTwincast) + value = focus_spell.base[i]; + break; } - break; - } - case SE_FcDamageAmt: - { - if(bottype == BotfocusFcDamageAmt) - value = focus_spell.base[i]; - - break; - } - - case SE_FcDamageAmtCrit: - { - if(bottype == BotfocusFcDamageAmtCrit) - value = focus_spell.base[i]; - - break; - } - - case SE_FcHealAmtIncoming: - if(bottype == BotfocusFcHealAmtIncoming) - value = focus_spell.base[i]; - break; - - case SE_FcHealPctCritIncoming: - if (bottype == BotfocusFcHealPctCritIncoming) - value = focus_spell.base[i]; - break; - - case SE_FcHealAmtCrit: - if(bottype == BotfocusFcHealAmtCrit) - value = focus_spell.base[i]; - break; - - case SE_FcHealAmt: - if(bottype == BotfocusFcHealAmt) - value = focus_spell.base[i]; - break; - - case SE_FcHealPctIncoming: - if(bottype == BotfocusFcHealPctIncoming) - value = focus_spell.base[i]; - break; - - case SE_FcBaseEffects: - { - if (bottype == BotfocusFcBaseEffects) - value = focus_spell.base[i]; - - break; - } - case SE_FcDamagePctCrit: - { - if(bottype == BotfocusFcDamagePctCrit) - value = focus_spell.base[i]; - - break; - } - - case SE_FcIncreaseNumHits: - { - if(bottype == BotfocusIncreaseNumHits) - value = focus_spell.base[i]; - - break; - } - //this spits up a lot of garbage when calculating spell focuses - //since they have all kinds of extra effects on them. - default: - Log.Out(Logs::General, Logs::Spells, "CalcFocusEffect: unknown effectid %d", focus_spell.effectid[i]); + case SE_SympatheticProc: { + if(bottype == BotfocusSympatheticProc) { + float ProcChance = GetSympatheticProcChances(spell_id, focus_spell.base[i]); + if(zone->random.Real(0, 1) <= ProcChance) + value = focus_id; + else + value = 0; + } + break; + } + case SE_FcDamageAmt: { + if(bottype == BotfocusFcDamageAmt) + value = focus_spell.base[i]; + break; + } + case SE_FcDamageAmtCrit: { + if(bottype == BotfocusFcDamageAmtCrit) + value = focus_spell.base[i]; + break; + } + case SE_FcHealAmtIncoming: + if(bottype == BotfocusFcHealAmtIncoming) + value = focus_spell.base[i]; + break; + case SE_FcHealPctCritIncoming: + if (bottype == BotfocusFcHealPctCritIncoming) + value = focus_spell.base[i]; + break; + case SE_FcHealAmtCrit: + if(bottype == BotfocusFcHealAmtCrit) + value = focus_spell.base[i]; + break; + case SE_FcHealAmt: + if(bottype == BotfocusFcHealAmt) + value = focus_spell.base[i]; + break; + case SE_FcHealPctIncoming: + if(bottype == BotfocusFcHealPctIncoming) + value = focus_spell.base[i]; + break; + case SE_FcBaseEffects: { + if (bottype == BotfocusFcBaseEffects) + value = focus_spell.base[i]; + + break; + } + case SE_FcDamagePctCrit: { + if(bottype == BotfocusFcDamagePctCrit) + value = focus_spell.base[i]; + + break; + } + case SE_FcIncreaseNumHits: { + if(bottype == BotfocusIncreaseNumHits) + value = focus_spell.base[i]; + + break; + } + default: + Log.Out(Logs::General, Logs::Spells, "CalcFocusEffect: unknown effectid %d", focus_spell.effectid[i]); + break; } } //Check for spell skill limits. if ((LimitSpellSkill) && (!SpellSkill_Found)) return 0; - return(value*lvlModifier/100); + return(value * lvlModifier / 100); } //proc chance includes proc bonus @@ -7382,38 +6283,23 @@ float Bot::GetProcChances(float ProcBonus, uint16 hand) { break; } - //calculate the weapon speed in ms, so we can use the rule to compare against. - // fast as a client can swing, so should be the floor of the proc chance if (weapon_speed < RuleI(Combat, MinHastedDelay)) weapon_speed = RuleI(Combat, MinHastedDelay); if (RuleB(Combat, AdjustProcPerMinute)) { - ProcChance = (static_cast(weapon_speed) * - RuleR(Combat, AvgProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms + ProcChance = (static_cast(weapon_speed) * RuleR(Combat, AvgProcsPerMinute) / 60000.0f); ProcBonus += static_cast(mydex) * RuleR(Combat, ProcPerMinDexContrib); - ProcChance += ProcChance * ProcBonus / 100.0f; + ProcChance += (ProcChance * ProcBonus / 100.0f); } else { - ProcChance = RuleR(Combat, BaseProcChance) + - static_cast(mydex) / RuleR(Combat, ProcDexDivideBy); - ProcChance += ProcChance*ProcBonus / 100.0f; + ProcChance = (RuleR(Combat, BaseProcChance) + static_cast(mydex) / RuleR(Combat, ProcDexDivideBy)); + ProcChance += (ProcChance * ProcBonus / 100.0f); } Log.Out(Logs::Detail, Logs::Combat, "Proc chance %.2f (%.2f from bonuses)", ProcChance, ProcBonus); return ProcChance; } -bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) -{ - /* called when a mob is attacked, does the checks to see if it's a hit - * and does other mitigation checks. 'this' is the mob being attacked. - * - * special return values: - * -1 - block - * -2 - parry - * -3 - riposte - * -4 - dodge - * - */ +bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) { if(GetAppearance() == eaDead) return false; @@ -7421,76 +6307,52 @@ bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) float bonus = 0; float RollTable[4] = {0,0,0,0}; float roll = 0; - Mob *attacker=other; - Mob *defender=this; + Mob *attacker = other; + Mob *defender = this; - //garunteed hit bool ghit = false; if((attacker->GetSpellBonuses().MeleeSkillCheck + attacker->GetItemBonuses().MeleeSkillCheck) > 500) ghit = true; - ////////////////////////////////////////////////////////// - // make enrage same as riposte - ///////////////////////////////////////////////////////// if (IsEnraged() && !other->BehindMob(this, other->GetX(), other->GetY())) { damage = -3; Log.Out(Logs::Detail, Logs::Combat, "I am enraged, riposting frontal attack."); } - - ///////////////////////////////////////////////////////// - // riposte - ///////////////////////////////////////////////////////// + float riposte_chance = 0.0f; - if (CanRiposte && damage > 0 && CanThisClassRiposte() && !other->BehindMob(this, other->GetX(), other->GetY())) - { - riposte_chance = (100.0f + (float)defender->GetAABonuses().RiposteChance + (float)defender->GetSpellBonuses().RiposteChance + (float)defender->GetItemBonuses().RiposteChance) / 100.0f; + if (CanRiposte && damage > 0 && CanThisClassRiposte() && !other->BehindMob(this, other->GetX(), other->GetY())) { + riposte_chance = ((100.0f + (float)defender->GetAABonuses().RiposteChance + (float)defender->GetSpellBonuses().RiposteChance + (float)defender->GetItemBonuses().RiposteChance) / 100.0f); skill = GetSkill(SkillRiposte); - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/60.0 + (GetDEX()/200); + if (!ghit) { + bonus = (2.0 + skill / 60.0 + (GetDEX() / 200)); bonus *= riposte_chance; - RollTable[0] = bonus + (itembonuses.HeroicDEX / 25); // 25 heroic = 1%, applies to ripo, parry, block + RollTable[0] = (bonus + (itembonuses.HeroicDEX / 25)); } } - - /////////////////////////////////////////////////////// - // block - /////////////////////////////////////////////////////// - + bool bBlockFromRear = false; bool bShieldBlockFromRear = false; - if (this->IsBot()) { int aaChance = 0; - - // a successful roll on this does not mean a successful block is forthcoming. only that a chance to block - // from a direction other than the rear is granted. - - //Live AA - HightenedAwareness - int BlockBehindChance = aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind; - + int BlockBehindChance = (aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind); if (BlockBehindChance && (BlockBehindChance > zone->random.Int(1, 100))){ bBlockFromRear = true; - if (spellbonuses.BlockBehind || itembonuses.BlockBehind) - bShieldBlockFromRear = true; //This bonus should allow a chance to Shield Block from behind. + bShieldBlockFromRear = true; } } float block_chance = 0.0f; if (damage > 0 && CanThisClassBlock() && (!other->BehindMob(this, other->GetX(), other->GetY()) || bBlockFromRear)) { - block_chance = (100.0f + (float)spellbonuses.IncreaseBlockChance + (float)itembonuses.IncreaseBlockChance) / 100.0f; + block_chance = ((100.0f + (float)spellbonuses.IncreaseBlockChance + (float)itembonuses.IncreaseBlockChance) / 100.0f); skill = GetSkill(SkillBlock); - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/35.0 + (GetDEX()/200); - RollTable[1] = RollTable[0] + (bonus * block_chance) - riposte_chance; - block_chance *= bonus; // set this so we can remove it from the parry calcs + if (!ghit) { + bonus = (2.0 + skill / 35.0 + (GetDEX() / 200)); + RollTable[1] = (RollTable[0] + (bonus * block_chance) - riposte_chance); + block_chance *= bonus; } - } - else{ + } else RollTable[1] = RollTable[0]; - } if(damage > 0 && (aabonuses.ShieldBlock || spellbonuses.ShieldBlock || itembonuses.ShieldBlock) && (!other->BehindMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) { @@ -7499,10 +6361,8 @@ bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) uint8 shield = GetBotItem(MainSecondary)->GetItem()->ItemType; float bonusShieldBlock = 0.0f; if(shield == ItemTypeShield) { - - //Live AA - Shield Block - bonusShieldBlock = aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock; - RollTable[1] = RollTable[0] + bonusShieldBlock; + bonusShieldBlock = (aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock); + RollTable[1] = (RollTable[0] + bonusShieldBlock); } } } @@ -7514,79 +6374,58 @@ bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) uint8 TwoHandBlunt = GetBotItem(MainPrimary)->GetItem()->ItemType; float bonusStaffBlock = 0.0f; if(TwoHandBlunt == ItemType2HBlunt) { - - bonusStaffBlock = aabonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock; - RollTable[1] = RollTable[0] + bonusStaffBlock; + bonusStaffBlock = (aabonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock); + RollTable[1] = (RollTable[0] + bonusStaffBlock); } } } - ////////////////////////////////////////////////////// - // parry - ////////////////////////////////////////////////////// float parry_chance = 0.0f; - if (damage > 0 && CanThisClassParry() && !other->BehindMob(this, other->GetX(), other->GetY())) - { - parry_chance = (100.0f + (float)defender->GetSpellBonuses().ParryChance + (float)defender->GetItemBonuses().ParryChance) / 100.0f; + if (damage > 0 && CanThisClassParry() && !other->BehindMob(this, other->GetX(), other->GetY())) { + parry_chance = ((100.0f + (float)defender->GetSpellBonuses().ParryChance + (float)defender->GetItemBonuses().ParryChance) / 100.0f); skill = GetSkill(SkillParry); - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/60.0 + (GetDEX()/200); + if (!ghit) { + bonus = (2.0 + skill / 60.0 + (GetDEX() / 200)); bonus *= parry_chance; - RollTable[2] = RollTable[1] + bonus - block_chance; + RollTable[2] = (RollTable[1] + bonus - block_chance); } - } - else{ - RollTable[2] = RollTable[1] - block_chance; - } + } else + RollTable[2] = (RollTable[1] - block_chance); - //////////////////////////////////////////////////////// - // dodge - //////////////////////////////////////////////////////// float dodge_chance = 0.0f; - if (damage > 0 && CanThisClassDodge() && !other->BehindMob(this, other->GetX(), other->GetY())) - { - dodge_chance = (100.0f + (float)defender->GetSpellBonuses().DodgeChance + (float)defender->GetItemBonuses().DodgeChance) / 100.0f; + if (damage > 0 && CanThisClassDodge() && !other->BehindMob(this, other->GetX(), other->GetY())) { + dodge_chance = ((100.0f + (float)defender->GetSpellBonuses().DodgeChance + (float)defender->GetItemBonuses().DodgeChance) / 100.0f); skill = GetSkill(SkillDodge); - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/60.0 + (GetAGI()/200); + if (!ghit) { + bonus = (2.0 + skill / 60.0 + (GetAGI() / 200)); bonus *= dodge_chance; - RollTable[3] = RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25) - parry_chance; // Remove the dex as it doesnt count for dodge + RollTable[3] = (RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25) - parry_chance); } - } - else{ - RollTable[3] = RollTable[2] - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25) - parry_chance; + } else { + RollTable[3] = (RollTable[2] - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25) - parry_chance); } - if(damage > 0) - { + if(damage > 0) { roll = zone->random.Real(0,100); - if(roll <= RollTable[0]){ + if(roll <= RollTable[0]) damage = -3; - } - else if(roll <= RollTable[1]){ + else if(roll <= RollTable[1]) damage = -1; - } - else if(roll <= RollTable[2]){ + else if(roll <= RollTable[2]) damage = -2; - } - else if(roll <= RollTable[3]){ + else if(roll <= RollTable[3]) damage = -4; - } } Log.Out(Logs::Detail, Logs::Combat, "Final damage after all avoidances: %d", damage); if (damage < 0) return true; + return false; } -int Bot::GetMonkHandToHandDamage(void) -{ - // Kaiyodo - Determine a monk's fist damage. Table data from www.monkly-business.com - // saved as static array - this should speed this function up considerably +int Bot::GetMonkHandToHandDamage(void) { static int damage[66] = { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 99, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, @@ -7594,15 +6433,11 @@ int Bot::GetMonkHandToHandDamage(void) 12,12,12,12,12,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14, 14,14,15,15,15,15 }; - // Have a look to see if we have epic fists on - uint32 botWeaponId = INVALID_ID; botWeaponId = CastToNPC()->GetEquipment(MaterialHands); - if(botWeaponId == 10652) { //Monk Epic ID + if(botWeaponId == 10652) return 9; - } - else - { + else { int Level = GetLevel(); if(Level > 65) return 19; @@ -7612,30 +6447,25 @@ int Bot::GetMonkHandToHandDamage(void) int Level = GetLevel(); if (Level > 65) - return(19); + return 19; else return damage[Level]; } -bool Bot::TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse) -{ +bool Bot::TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse) { if (!defender) return false; - if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10){ - - uint32 chance = aabonuses.FinishingBlow[0]/10; //500 = 5% chance. + if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10) { + uint32 chance = (aabonuses.FinishingBlow[0] / 10); uint32 damage = aabonuses.FinishingBlow[1]; uint16 levelreq = aabonuses.FinishingBlowLvl[0]; - if(defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))){ Log.Out(Logs::Detail, Logs::Combat, "Landed a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); defender->Damage(this, damage, SPELL_UNKNOWN, skillinuse); return true; - } - else - { + } else { Log.Out(Logs::Detail, Logs::Combat, "FAILED a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); return false; } @@ -7645,27 +6475,17 @@ bool Bot::TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse) void Bot::DoRiposte(Mob* defender) { Log.Out(Logs::Detail, Logs::Combat, "Preforming a riposte"); - if (!defender) return; defender->Attack(this, MainPrimary, true); - - //double riposte - int32 DoubleRipChance = defender->GetAABonuses().GiveDoubleRiposte[0] + - defender->GetSpellBonuses().GiveDoubleRiposte[0] + - defender->GetItemBonuses().GiveDoubleRiposte[0]; - + int32 DoubleRipChance = (defender->GetAABonuses().GiveDoubleRiposte[0] + defender->GetSpellBonuses().GiveDoubleRiposte[0] + defender->GetItemBonuses().GiveDoubleRiposte[0]); if(DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { Log.Out(Logs::Detail, Logs::Combat, "Preforming a double riposte (%d percent chance)", DoubleRipChance); - defender->Attack(this, MainPrimary, true); } - //Double Riposte effect, allows for a chance to do RIPOSTE with a skill specfic special attack (ie Return Kick). - //Coded narrowly: Limit to one per client. Limit AA only. [1 = Skill Attack Chance, 2 = Skill] DoubleRipChance = defender->GetAABonuses().GiveDoubleRiposte[1]; - if(DoubleRipChance && (DoubleRipChance >= zone->random.Int(0, 100))) { if (defender->GetClass() == MONK) defender->MonkSpecialAttack(this, defender->GetAABonuses().GiveDoubleRiposte[2]); @@ -7675,9 +6495,6 @@ void Bot::DoRiposte(Mob* defender) { } void Bot::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int32 min_damage, int32 hate_override,int ReuseTime, bool HitChance) { - //this really should go through the same code as normal melee damage to - //pick up all the special behavior there - int32 hate = max_damage; if(hate_override > -1) hate = hate_override; @@ -7687,22 +6504,21 @@ void Bot::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, const ItemData* botweapon = 0; if(inst) botweapon = inst->GetItem(); + if(botweapon) { - if(botweapon->ItemType == ItemTypeShield) { + if(botweapon->ItemType == ItemTypeShield) hate += botweapon->AC; - } - hate = hate * (100 + GetFuriousBash(botweapon->Focus.Effect)) / 100; + + hate = (hate * (100 + GetFuriousBash(botweapon->Focus.Effect)) / 100); } } - min_damage += min_damage * GetMeleeMinDamageMod_SE(skill) / 100; - + min_damage += (min_damage * GetMeleeMinDamageMod_SE(skill) / 100); if(HitChance && !who->CheckHitChance(this, skill, MainPrimary)) max_damage = 0; - - else{ + else { bool CanRiposte = true; - if(skill == SkillThrowing || skill == SkillArchery) // changed from '&&' + if(skill == SkillThrowing || skill == SkillArchery) CanRiposte = false; who->AvoidDamage(this, max_damage, CanRiposte); @@ -7711,37 +6527,35 @@ void Bot::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, if(max_damage > 0) { ApplyMeleeDamageBonus(skill, max_damage); max_damage += who->GetFcDamageAmtIncoming(this, 0, true, skill); - max_damage += (itembonuses.HeroicSTR / 10) + (max_damage * who->GetSkillDmgTaken(skill) / 100) + GetSkillDmgAmt(skill); + max_damage += ((itembonuses.HeroicSTR / 10) + (max_damage * who->GetSkillDmgTaken(skill) / 100) + GetSkillDmgAmt(skill)); TryCriticalHit(who, skill, max_damage); } } - if(max_damage >= 0) //You should probably get aggro no matter what, but unclear why it was set like this. + if(max_damage >= 0) who->AddToHateList(this, hate); who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); - if(!GetTarget())return; - if (HasDied()) return; + if(!GetTarget() || HasDied()) + return; if (max_damage > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); - //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skill){ int kb_chance = 25; - kb_chance += kb_chance*(100-aabonuses.SpecialAttackKBProc[0])/100; + kb_chance += (kb_chance * (100 - aabonuses.SpecialAttackKBProc[0]) / 100); if (zone->random.Int(0, 99) < kb_chance) SpellFinished(904, who, 10, 0, -1, spells[904].ResistDiff); - //who->Stun(100); Kayen: This effect does not stun on live, it only moves the NPC. } if (HasSkillProcs()) - TrySkillProc(who, skill, ReuseTime*1000); + TrySkillProc(who, skill, (ReuseTime * 1000)); if (max_damage > 0 && HasSkillProcSuccess()) - TrySkillProc(who, skill, ReuseTime*1000, true); + TrySkillProc(who, skill, (ReuseTime * 1000), true); if(max_damage == -3 && !(who->GetHP() <= 0)) DoRiposte(who); @@ -7753,53 +6567,35 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { bool bIsBehind = false; bool bCanFrontalBS = false; - const ItemInst* inst = GetBotItem(MainPrimary); const ItemData* botpiercer = nullptr; if(inst) botpiercer = inst->GetItem(); + if(!botpiercer || (botpiercer->ItemType != ItemType1HPiercing)) { - Say("I can't backstab with this weapon!"); + BotGroupSay(this, "I can't backstab with this weapon!"); return; } - //Live AA - Triple Backstab - int tripleChance = itembonuses.TripleBackstab + spellbonuses.TripleBackstab + aabonuses.TripleBackstab; - - if (BehindMob(other, GetX(), GetY())) { + int tripleChance = (itembonuses.TripleBackstab + spellbonuses.TripleBackstab + aabonuses.TripleBackstab); + if (BehindMob(other, GetX(), GetY())) bIsBehind = true; - } else { - //Live AA - Seized Opportunity - int FrontalBSChance = itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance; - + int FrontalBSChance = (itembonuses.FrontalBackstabChance + spellbonuses.FrontalBackstabChance + aabonuses.FrontalBackstabChance); if (FrontalBSChance && (FrontalBSChance > zone->random.Int(0, 100))) bCanFrontalBS = true; } - if (bIsBehind || bCanFrontalBS){ // Bot is behind other OR can do Frontal Backstab - - // chance to assassinate - int chance = 10 + (GetDEX()/10) + (itembonuses.HeroicDEX/10); //18.5% chance at 85 dex 40% chance at 300 dex - if( - level >= 60 && // bot is 60 or higher - other->GetLevel() <= 45 && // mob 45 or under - !other->CastToNPC()->IsEngaged() && // not aggro - other->GetHP()<=32000 - && other->IsNPC() - && zone->random.Real(0, 99) < chance // chance - ) { + if (bIsBehind || bCanFrontalBS) { + int chance = (10 + (GetDEX() / 10) + (itembonuses.HeroicDEX / 10)); + if(level >= 60 && other->GetLevel() <= 45 && !other->CastToNPC()->IsEngaged() && other->GetHP()<= 32000 && other->IsNPC() && zone->random.Real(0, 99) < chance) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); RogueAssassinate(other); - } - else { + } else { RogueBackstab(other); if (level > 54) { - float DoubleAttackProbability = (GetSkill(SkillDoubleAttack) + GetLevel()) / 500.0f; // 62.4 max - // Check for double attack with main hand assuming maxed DA Skill (MS) - - if(zone->random.Real(0, 1) < DoubleAttackProbability) // Max 62.4 % chance of DA - { + float DoubleAttackProbability = ((GetSkill(SkillDoubleAttack) + GetLevel()) / 500.0f); + if(zone->random.Real(0, 1) < DoubleAttackProbability) { if(other->GetHP() > 0) RogueBackstab(other,false,ReuseTime); @@ -7808,169 +6604,125 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { } } } - } - //Live AA - Chaotic Backstab - else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { - - //we can stab from any angle, we do min damage though. + } else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { RogueBackstab(other, true); if (level > 54) { - float DoubleAttackProbability = (GetSkill(SkillDoubleAttack) + GetLevel()) / 500.0f; // 62.4 max - // Check for double attack with main hand assuming maxed DA Skill (MS) - if(zone->random.Real(0, 1) < DoubleAttackProbability) // Max 62.4 % chance of DA + float DoubleAttackProbability = ((GetSkill(SkillDoubleAttack) + GetLevel()) / 500.0f); + if(zone->random.Real(0, 1) < DoubleAttackProbability) if(other->GetHP() > 0) RogueBackstab(other,true, ReuseTime); if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) - RogueBackstab(other,false,ReuseTime); + RogueBackstab(other,false,ReuseTime); } } - else { //We do a single regular attack if we attack from the front without chaotic stab + else Attack(other, MainPrimary); - } } -//heko: backstab -void Bot::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) -{ +void Bot::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) { int32 ndamage = 0; int32 max_hit = 0; int32 min_hit = 0; int32 hate = 0; int32 primaryweapondamage = 0; int32 backstab_dmg = 0; - ItemInst* botweaponInst = GetBotItem(MainPrimary); if(botweaponInst) { primaryweapondamage = GetWeaponDamage(other, botweaponInst); backstab_dmg = botweaponInst->GetItem()->BackstabDmg; - for (int i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; ++i) - { + for (int i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; ++i) { ItemInst *aug = botweaponInst->GetAugment(i); if(aug) - { backstab_dmg += aug->GetItem()->BackstabDmg; - } } - } - else - { - primaryweapondamage = (GetLevel()/7)+1; // fallback incase it's a npc without a weapon, 2 dmg at 10, 10 dmg at 65 + } else { + primaryweapondamage = ((GetLevel() / 7) + 1); backstab_dmg = primaryweapondamage; } - if(primaryweapondamage > 0){ - if(level > 25){ - max_hit = (((2*backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + ((level-25)/3) + 1; - hate = 20 * backstab_dmg * GetSkill(SkillBackstab) / 355; - } - else{ - max_hit = (((2*backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + 1; - hate = 20 * backstab_dmg * GetSkill(SkillBackstab) / 355; + if(primaryweapondamage > 0) { + if(level > 25) { + max_hit = (((((2 * backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + ((level - 25) / 3) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); + hate = (20 * backstab_dmg * GetSkill(SkillBackstab) / 355); + } else { + max_hit = (((((2 * backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); + hate = (20 * backstab_dmg * GetSkill(SkillBackstab) / 355); } - // determine minimum hits if (level < 51) - { - min_hit = (level*15/10); - } + min_hit = (level * 15 / 10); else - { - // Trumpcard: Replaced switch statement with formula calc. This will give minhit increases all the way to 65. - min_hit = (level * ( level*5 - 105)) / 100; - } + min_hit = ((level * ( level * 5 - 105)) / 100); - if(!other->CheckHitChance(this, SkillBackstab, 0)) { + if(!other->CheckHitChance(this, SkillBackstab, 0)) ndamage = 0; - } - else{ - if(min_damage){ + else { + if (min_damage) { ndamage = min_hit; - } - else - { + } else { if (max_hit < min_hit) max_hit = min_hit; - if(RuleB(Combat, UseIntervalAC)) - ndamage = max_hit; - else - ndamage = zone->random.Int(min_hit, max_hit); - + ndamage = (RuleB(Combat, UseIntervalAC) ? max_hit : zone->random.Int(min_hit, max_hit)); } } - } - else{ + } else ndamage = -5; - } DoSpecialAttackDamage(other, SkillBackstab, ndamage, min_hit, hate, ReuseTime); DoAnim(animPiercing); } -void Bot::RogueAssassinate(Mob* other) -{ +void Bot::RogueAssassinate(Mob* other) { ItemInst* botweaponInst = GetBotItem(MainPrimary); if(botweaponInst) { - if(GetWeaponDamage(other, botweaponInst)) { + if(GetWeaponDamage(other, botweaponInst)) other->Damage(this, 32000, SPELL_UNKNOWN, SkillBackstab); - } - else { + else other->Damage(this, -5, SPELL_UNKNOWN, SkillBackstab); - } } - DoAnim(animPiercing); //piercing animation + DoAnim(animPiercing); } void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { - if(!target) - return; - - if(spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0) - return; - - if(!IsAttackAllowed(target)) + if(!target || spellend_timer.Enabled() || IsFeared() || IsStunned() || IsMezzed() || DivineAura() || GetHP() < 0 || !IsAttackAllowed(target)) return; bool taunt_time = taunt_timer.Check(); bool ca_time = classattack_timer.Check(false); bool ka_time = knightattack_timer.Check(false); - - //only check attack allowed if we are going to do something if((taunt_time || ca_time || ka_time) && !IsAttackAllowed(target)) return; if(ka_time){ - int knightreuse = 1000; //lets give it a small cooldown actually. + int knightreuse = 1000; switch(GetClass()){ - case SHADOWKNIGHT: case SHADOWKNIGHTGM:{ + case SHADOWKNIGHT: + case SHADOWKNIGHTGM: { CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID()); - knightreuse = HarmTouchReuseTime * 1000; + knightreuse = (HarmTouchReuseTime * 1000); break; } - case PALADIN: case PALADINGM:{ + case PALADIN: + case PALADINGM: { if(GetHPRatio() < 20) { CastSpell(SPELL_LAY_ON_HANDS, GetID()); - knightreuse = LayOnHandsReuseTime * 1000; - } else { - knightreuse = 2000; //Check again in two seconds. + knightreuse = (LayOnHandsReuseTime * 1000); } + else + knightreuse = 2000; + break; } } knightattack_timer.Start(knightreuse); } - //general stuff, for all classes.... - //only gets used when their primary ability get used too - - //franck-add: EQoffline. Warrior bots must taunt the target. - if(taunting && target && target->IsNPC() && taunt_time ) { - //Only taunt if we are not top on target's hate list - //This ensures we have taunt available to regain aggro if needed + if(taunting && target && target->IsNPC() && taunt_time) { if(GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { - Say("Taunting %s", target->GetCleanName()); + BotGroupSay(this, "Taunting %s", target->GetCleanName()); Taunt(target->CastToNPC(), false); taunt_timer.Start(TauntReuseTime * 1000); } @@ -7979,287 +6731,193 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(!ca_time) return; - float HasteModifier = GetHaste() * 0.01f; + float HasteModifier = (GetHaste() * 0.01f); int32 dmg = 0; - uint16 skill_to_use = -1; - int level = GetLevel(); - int reuse = TauntReuseTime * 1000; //make this very long since if they dont use it once, they prolly never will + int reuse = (TauntReuseTime * 1000); bool did_attack = false; - - switch(GetClass()) - { - case WARRIOR: - if(level >= RuleI(Combat, NPCBashKickLevel)){ - bool canBash = false; - if((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) // Racial Slam - || (m_inv.GetItem(MainSecondary) && m_inv.GetItem(MainSecondary)->GetItem()->ItemType == ItemTypeShield) //Using Shield - || (m_inv.GetItem(MainPrimary) && (m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HSlash - || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HBlunt - || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HPiercing) - && GetAA(aa2HandBash) >= 1)) { //Using 2 hand weapon, but has AA 2 Hand Bash - canBash = true; + switch(GetClass()) { + case WARRIOR: + if(level >= RuleI(Combat, NPCBashKickLevel)){ + bool canBash = false; + if((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(MainSecondary) && m_inv.GetItem(MainSecondary)->GetItem()->ItemType == ItemTypeShield) || (m_inv.GetItem(MainPrimary) && (m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HSlash || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HBlunt || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HPiercing) && GetAA(aa2HandBash) >= 1)) + canBash = true; + + if(!canBash || zone->random.Int(0, 100) > 25) + skill_to_use = SkillKick; + else + skill_to_use = SkillBash; } - - if(!canBash || zone->random.Int(0, 100) > 25) { //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. - skill_to_use = SkillKick; - } - else { - skill_to_use = SkillBash; - } - } - case RANGER: - case BEASTLORD: - skill_to_use = SkillKick; - break; - case BERSERKER: - skill_to_use = SkillFrenzy; - break; - case CLERIC: - case SHADOWKNIGHT: - case PALADIN: - if(level >= RuleI(Combat, NPCBashKickLevel)){ - if((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) // Racial Slam - || (m_inv.GetItem(MainSecondary) && m_inv.GetItem(MainSecondary)->GetItem()->ItemType == ItemTypeShield) //Using Shield - || (m_inv.GetItem(MainPrimary) && (m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HSlash - || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HBlunt - || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HPiercing) - && GetAA(aa2HandBash) >= 1)) { //Using 2 hand weapon, but has AA 2 Hand Bash - skill_to_use = SkillBash; - } - } - break; - case MONK: - if(GetLevel() >= 30) - { - skill_to_use = SkillFlyingKick; - } - else if(GetLevel() >= 25) - { - skill_to_use = SkillDragonPunch; - } - else if(GetLevel() >= 20) - { - skill_to_use = SkillEagleStrike; - } - else if(GetLevel() >= 10) - { - skill_to_use = SkillTigerClaw; - } - else if(GetLevel() >= 5) - { - skill_to_use = SkillRoundKick; - } - else - { + case RANGER: + case BEASTLORD: skill_to_use = SkillKick; - } - break; - case ROGUE: - skill_to_use = SkillBackstab; - break; + break; + case BERSERKER: + skill_to_use = SkillFrenzy; + break; + case CLERIC: + case SHADOWKNIGHT: + case PALADIN: + if(level >= RuleI(Combat, NPCBashKickLevel)){ + if((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(MainSecondary) && m_inv.GetItem(MainSecondary)->GetItem()->ItemType == ItemTypeShield) || (m_inv.GetItem(MainPrimary) && (m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HSlash || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HBlunt || m_inv.GetItem(MainPrimary)->GetItem()->ItemType == ItemType2HPiercing) && GetAA(aa2HandBash) >= 1)) + skill_to_use = SkillBash; + } + break; + case MONK: + if(GetLevel() >= 30) + skill_to_use = SkillFlyingKick; + else if(GetLevel() >= 25) + skill_to_use = SkillDragonPunch; + else if(GetLevel() >= 20) + skill_to_use = SkillEagleStrike; + else if(GetLevel() >= 10) + skill_to_use = SkillTigerClaw; + else if(GetLevel() >= 5) + skill_to_use = SkillRoundKick; + else + skill_to_use = SkillKick; + break; + case ROGUE: + skill_to_use = SkillBackstab; + break; } if(skill_to_use == -1) return; - - if(skill_to_use == SkillBash) - { - if (target!=this) - { + if(skill_to_use == SkillBash) { + if (target != this) { DoAnim(animTailRake); - - if(GetWeaponDamage(target, GetBotItem(MainSecondary)) <= 0 && - GetWeaponDamage(target, GetBotItem(MainShoulders)) <= 0){ + if(GetWeaponDamage(target, GetBotItem(MainSecondary)) <= 0 && GetWeaponDamage(target, GetBotItem(MainShoulders)) <= 0) dmg = -5; - } - else{ - if(!target->CheckHitChance(this, SkillBash, 0)) { + else { + if(!target->CheckHitChance(this, SkillBash, 0)) dmg = 0; - } - else{ + else { if(RuleB(Combat, UseIntervalAC)) dmg = GetBashDamage(); else dmg = zone->random.Int(1, GetBashDamage()); - } } - - reuse = BashReuseTime * 1000; - //reuse = (reuse*HasteModifier)/100; - - DoSpecialAttackDamage(target, SkillBash, dmg, 1,-1,reuse); - + reuse = (BashReuseTime * 1000); + DoSpecialAttackDamage(target, SkillBash, dmg, 1, -1,reuse); did_attack = true; - - if(reuse > 0 && !IsRiposte) - { - //p_timers.Start(pTimerCombatAbility, reuse); - } } } - if(skill_to_use == SkillFrenzy) - { + if(skill_to_use == SkillFrenzy) { int AtkRounds = 3; int skillmod = 0; - if(MaxSkill(SkillFrenzy) > 0) - skillmod = 100*GetSkill(SkillFrenzy)/MaxSkill(SkillFrenzy); + skillmod = (100 * GetSkill(SkillFrenzy)/MaxSkill(SkillFrenzy)); - int32 max_dmg = (26 + ((((GetLevel()-6) * 2)*skillmod)/100)) * ((100+RuleI(Combat, FrenzyBonus))/100); + int32 max_dmg = (26 + ((((GetLevel() - 6) * 2) * skillmod) / 100)) * ((100 + RuleI(Combat, FrenzyBonus)) / 100); int32 min_dmg = 0; DoAnim(anim2HSlashing); if (GetLevel() < 51) min_dmg = 1; else - min_dmg = GetLevel()*8/10; + min_dmg = (GetLevel() * 8 / 10); if (min_dmg > max_dmg) max_dmg = min_dmg; - reuse = FrenzyReuseTime * 1000; - //reuse = (reuse * HasteModifier)/100; - + reuse = (FrenzyReuseTime * 1000); did_attack = true; - - //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { - - if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0,100) < 75)){ - DoSpecialAttackDamage(GetTarget(), SkillFrenzy, max_dmg, min_dmg, max_dmg , reuse, true); + if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0, 100) < 75)) { + DoSpecialAttackDamage(GetTarget(), SkillFrenzy, max_dmg, min_dmg, max_dmg, reuse, true); } - AtkRounds--; - } - if(reuse > 0 && !IsRiposte) { - //p_timers.Start(pTimerCombatAbility, reuse); + AtkRounds--; } } - if(skill_to_use == SkillKick) - { - if(target!=this) - { + if(skill_to_use == SkillKick) { + if(target != this) { DoAnim(animKick); - - if(GetWeaponDamage(target, GetBotItem(MainFeet)) <= 0){ + if(GetWeaponDamage(target, GetBotItem(MainFeet)) <= 0) dmg = -5; - } - else{ - if(!target->CheckHitChance(this, SkillKick, 0)) { + else { + if(!target->CheckHitChance(this, SkillKick, 0)) dmg = 0; - } - else{ + else { if(RuleB(Combat, UseIntervalAC)) dmg = GetKickDamage(); else dmg = zone->random.Int(1, GetKickDamage()); } } - - reuse = KickReuseTime * 1000; - - DoSpecialAttackDamage(target, SkillKick, dmg, 1,-1, reuse); - + reuse = (KickReuseTime * 1000); + DoSpecialAttackDamage(target, SkillKick, dmg, 1, -1, reuse); did_attack = true; } } - if(skill_to_use == SkillFlyingKick || - skill_to_use == SkillDragonPunch || - skill_to_use == SkillEagleStrike || - skill_to_use == SkillTigerClaw || - skill_to_use == SkillRoundKick) - { - reuse = MonkSpecialAttack(target, skill_to_use) - 1; + if(skill_to_use == SkillFlyingKick || skill_to_use == SkillDragonPunch || skill_to_use == SkillEagleStrike || skill_to_use == SkillTigerClaw || skill_to_use == SkillRoundKick) { + reuse = (MonkSpecialAttack(target, skill_to_use) - 1); MonkSpecialAttack(target, skill_to_use); - - //Live AA - Technique of Master Wu - uint32 bDoubleSpecialAttack = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; - if( bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > zone->random.Int(0,100))) { - + uint32 bDoubleSpecialAttack = (itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack); + if(bDoubleSpecialAttack && (bDoubleSpecialAttack >= 100 || bDoubleSpecialAttack > zone->random.Int(0, 100))) { int MonkSPA [5] = { SkillFlyingKick, SkillDragonPunch, SkillEagleStrike, SkillTigerClaw, SkillRoundKick }; - MonkSpecialAttack(target, MonkSPA[zone->random.Int(0,4)]); - + MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]); int TripleChance = 25; - if (bDoubleSpecialAttack > 100) - TripleChance += TripleChance*(100-bDoubleSpecialAttack)/100; + TripleChance += (TripleChance * (100 - bDoubleSpecialAttack) / 100); - if(TripleChance > zone->random.Int(0,100)) { - MonkSpecialAttack(target, MonkSPA[zone->random.Int(0,4)]); - } + if(TripleChance > zone->random.Int(0,100)) + MonkSpecialAttack(target, MonkSPA[zone->random.Int(0, 4)]); } reuse *= 1000; did_attack = true; } - if(skill_to_use == SkillBackstab) - { - reuse = BackstabReuseTime * 1000; + if(skill_to_use == SkillBackstab) { + reuse = (BackstabReuseTime * 1000); did_attack = true; - if (IsRiposte) - reuse=0; + reuse = 0; TryBackstab(target,reuse); } - classattack_timer.Start(reuse / HasteModifier); } bool Bot::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { bool Result = false; - if(defender && (defender->GetBodyType() == BT_Humanoid) && (skillInUse == SkillArchery) && (GetClass() == RANGER) && (GetLevel() >= 62)) { int defenderLevel = defender->GetLevel(); int rangerLevel = GetLevel(); - // Bot Ranger Headshot AA through level 85(Underfoot) - if( GetAA(aaHeadshot) && ((defenderLevel - 46) <= GetAA(aaHeadshot) * 2) ) { - // WildcardX: These chance formula's below are arbitrary. If someone has a better formula that is more - // consistent with live, feel free to update these. + if(GetAA(aaHeadshot) && ((defenderLevel - 46) <= GetAA(aaHeadshot) * 2)) { float AttackerChance = 0.20f + ((float)(rangerLevel - 51) * 0.005f); float DefenderChance = (float)zone->random.Real(0.00f, 1.00f); if(AttackerChance > DefenderChance) { Log.Out(Logs::Detail, Logs::Combat, "Landed a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - // WildcardX: At the time I wrote this, there wasnt a string id for something like HEADSHOT_BLOW - //entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); entity_list.MessageClose(this, false, 200, MT_CritMelee, "%s has scored a leathal HEADSHOT!", GetName()); defender->Damage(this, (defender->GetMaxHP()+50), SPELL_UNKNOWN, skillInUse); Result = true; - } - else { + } else Log.Out(Logs::Detail, Logs::Combat, "FAILED a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - } } } - return Result; } -//offensive spell aggro int32 Bot::CheckAggroAmount(uint16 spellid) { int32 AggroAmount = Mob::CheckAggroAmount(spellid); - int32 focusAggro = GetBotFocusEffect(BotfocusSpellHateMod, spellid); - AggroAmount = (AggroAmount * (100+focusAggro) / 100); - + AggroAmount = (AggroAmount * (100 + focusAggro) / 100); return AggroAmount; } int32 Bot::CheckHealAggroAmount(uint16 spellid, uint32 heal_possible) { int32 AggroAmount = Mob::CheckHealAggroAmount(spellid, heal_possible); - int32 focusAggro = GetBotFocusEffect(BotfocusSpellHateMod, spellid); - AggroAmount = (AggroAmount * (100 + focusAggro) / 100); - return AggroAmount; } @@ -8272,20 +6930,15 @@ void Bot::AI_Stop() { Mob::AI_Stop(); } -//this is called with 'this' as the mob being looked at, and -//iOther the mob who is doing the looking. It should figure out -//what iOther thinks about 'this' FACTION_VALUE Bot::GetReverseFactionCon(Mob* iOther) { - if(iOther->IsBot()) { + if(iOther->IsBot()) return FACTION_ALLY; - } return NPC::GetReverseFactionCon(iOther); } Mob* Bot::GetOwnerOrSelf() { Mob* Result = 0; - if(this->GetBotOwner()) Result = GetBotOwner(); else @@ -8296,103 +6949,61 @@ Mob* Bot::GetOwnerOrSelf() { Mob* Bot::GetOwner() { Mob* Result = 0; - Result = GetBotOwner(); - - if(!Result) { + if(!Result) this->SetBotOwner(0); - } return Result; } -bool Bot::IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined) -{ +bool Bot::IsBotAttackAllowed(Mob* attacker, Mob* target, bool& hasRuleDefined) { bool Result = false; - - if(attacker && target) - { - if(attacker == target) - { + if(attacker && target) { + if(attacker == target) { hasRuleDefined = true; Result = false; - } - else if(attacker->IsClient() && target->IsBot() && attacker->CastToClient()->GetPVP() && target->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) - { + } else if(attacker->IsClient() && target->IsBot() && attacker->CastToClient()->GetPVP() && target->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) { hasRuleDefined = true; Result = true; - } - else if(attacker->IsClient() && target->IsBot()) - { + } else if(attacker->IsClient() && target->IsBot()) { hasRuleDefined = true; Result = false; - } - else if(attacker->IsBot() && target->IsNPC()) - { + } else if(attacker->IsBot() && target->IsNPC()) { hasRuleDefined = true; Result = true; - } - else if(attacker->IsBot() && !target->IsNPC()) - { + } else if(attacker->IsBot() && !target->IsNPC()) { hasRuleDefined = true; Result = false; - } - else if(attacker->IsPet() && attacker->IsFamiliar()) - { + } else if(attacker->IsPet() && attacker->IsFamiliar()) { hasRuleDefined = true; Result = false; - } - else if(attacker->IsBot() && attacker->CastToBot()->GetBotOwner() && attacker->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) - { - if(target->IsBot() && target->GetOwner() && target->GetOwner()->CastToClient()->GetPVP()) - { - // my target is a bot and it's owner is pvp + } else if(attacker->IsBot() && attacker->CastToBot()->GetBotOwner() && attacker->CastToBot()->GetBotOwner()->CastToClient()->GetPVP()) { + if(target->IsBot() && target->GetOwner() && target->GetOwner()->CastToClient()->GetPVP()) { hasRuleDefined = true; - if(target->GetOwner() == attacker->GetOwner()) - { - // no attacking if my target's owner is my owner Result = false; - } else - { Result = true; - } - } - else if(target->IsClient() && target->CastToClient()->GetPVP()) - { - // my target is a player and it's pvp + } else if(target->IsClient() && target->CastToClient()->GetPVP()) { hasRuleDefined = true; - if(target == attacker->GetOwner()) - { - // my target cannot be my owner Result = false; - } else - { Result = true; - } - } - else if(target->IsNPC()) - { + } else if(target->IsNPC()) { hasRuleDefined = true; Result = true; - } - else if(!target->IsNPC()) - { + } else if(!target->IsNPC()) { hasRuleDefined = true; Result = false; } } } - return Result; } void Bot::EquipBot(std::string* errorMessage) { GetBotItems(errorMessage, m_inv); - const ItemInst* inst = 0; const ItemData* item = 0; for(int i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; ++i) { @@ -8404,33 +7015,14 @@ void Bot::EquipBot(std::string* errorMessage) { return; } } - UpdateEquipmentLight(); } -//// This method is meant to be called by zone or client methods to clean up objects when a client camps, goes LD, zones out or something like that. -//void Bot::DestroyBotRaidObjects(Client* client) { -// if(client) { -// if(client->GetBotRaidID() > 0) { -// BotRaids* br = entity_list.GetBotRaidByMob(client); -// if(br) { -// br->RemoveRaidBots(); -// br = nullptr; -// } -// } -// -// //BotOrderCampAll(client); -// } -//} - -// Orders all the bots owned by the specified client bot owner to camp out of the game void Bot::BotOrderCampAll(Client* c) { if(c) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { + for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) (*botListItr)->Camp(); - } } } @@ -8438,11 +7030,9 @@ void Bot::ProcessBotOwnerRefDelete(Mob* botOwner) { if(botOwner) { if(botOwner->IsClient()) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(botOwner->CastToClient()->CharacterID()); - if(!BotList.empty()) { for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot) { tempBot->SetTarget(0); tempBot->SetBotOwner(0); @@ -8455,20 +7045,12 @@ void Bot::ProcessBotOwnerRefDelete(Mob* botOwner) { void Bot::ProcessGuildInvite(Client* guildOfficer, Bot* botToGuild) { if(guildOfficer && botToGuild) { - // Bots can be only guild member rank if(!botToGuild->IsInAGuild()) { - //they are not in this or any other guild, this is an invite if (!guild_mgr.CheckPermission(guildOfficer->GuildID(), guildOfficer->GuildRank(), GUILD_INVITE)) { guildOfficer->Message(13, "You dont have permission to invite."); return; } - - // Log.Out(Logs::Detail, Logs::Guilds, "Inviting %s (%d) into guild %s (%d)", botToGuild->GetName(), botToGuild->GetBotID(), guild_mgr.GetGuildName(client->GuildID()), client->GuildID()); - SetBotGuildMembership(botToGuild->GetBotID(), guildOfficer->GuildID(), GUILD_MEMBER); - - //Log.LogDebugType(Logs::Detail, Logs::Guilds, "Sending char refresh for BOT %s from guild %d to world", botToGuild->GetName(), guildOfficer->GuildID(); - ServerPacket* pack = new ServerPacket(ServerOP_GuildCharRefresh, sizeof(ServerGuildCharRefresh_Struct)); ServerGuildCharRefresh_Struct *s = (ServerGuildCharRefresh_Struct *) pack->pBuffer; s->guild_id = guildOfficer->GuildID(); @@ -8476,9 +7058,7 @@ void Bot::ProcessGuildInvite(Client* guildOfficer, Bot* botToGuild) { s->char_id = botToGuild->GetBotID(); worldserver.SendPacket(pack); safe_delete(pack); - } else { - //they are in some other guild guildOfficer->Message(13, "Player is in a guild."); return; } @@ -8487,18 +7067,14 @@ void Bot::ProcessGuildInvite(Client* guildOfficer, Bot* botToGuild) { bool Bot::ProcessGuildRemoval(Client* guildOfficer, std::string botName) { bool Result = false; - if(guildOfficer && !botName.empty()) { Bot* botToUnGuild = entity_list.GetBotByBotName(botName); if(botToUnGuild) { SetBotGuildMembership(botToUnGuild->GetBotID(), 0, 0); Result = true; - } - else { + } else { uint32 botId = GetBotIDByBotName(botName); - if(botId > 0) { - // Bot is camped or in another zone SetBotGuildMembership(botId, 0, 0); Result = true; } @@ -8514,7 +7090,6 @@ bool Bot::ProcessGuildRemoval(Client* guildOfficer, std::string botName) { safe_delete(outapp); } } - return Result; } @@ -8523,27 +7098,20 @@ void Bot::SetBotGuildMembership(uint32 botId, uint32 guildid, uint8 rank) { return; if(guildid > 0) { - std::string query = StringFormat("REPLACE INTO botguildmembers " - "SET char_id = %u, guild_id = %u, rank = %u;", - botId, guildid, rank); + std::string query = StringFormat("REPLACE INTO botguildmembers SET char_id = %u, guild_id = %u, rank = %u", botId, guildid, rank); auto results = database.QueryDatabase(query); return; } - std::string query = StringFormat("DELETE FROM botguildmembers WHERE char_id = %u;", botId); + std::string query = StringFormat("DELETE FROM botguildmembers WHERE char_id = %u", botId); auto results = database.QueryDatabase(query); } void Bot::LoadGuildMembership(uint32* guildId, uint8* guildRank, std::string* guildName) { - if(guildId == nullptr || guildRank == nullptr || guildName == nullptr) return; - std::string query = StringFormat("SELECT gm.guild_id, gm.rank, g.name " - "FROM vwGuildMembers AS gm JOIN guilds AS g " - "ON gm.guild_id = g.id " - "WHERE gm.char_id = %u AND gm.mobtype = 'B';", - GetBotID()); + std::string query = StringFormat("SELECT gm.guild_id, gm.rank, g.name FROM vwGuildMembers AS gm JOIN guilds AS g ON gm.guild_id = g.id WHERE gm.char_id = %u AND gm.mobtype = 'B'", GetBotID()); auto results = database.QueryDatabase(query); if(!results.Success() || results.RowCount() == 0) return; @@ -8555,56 +7123,45 @@ void Bot::LoadGuildMembership(uint32* guildId, uint8* guildRank, std::string* gu } int32 Bot::CalcMaxMana() { - switch(GetCasterClass()) - { + switch(GetCasterClass()) { case 'I': - case 'W': - { + case 'W': { max_mana = (GenerateBaseManaPoints() + itembonuses.Mana + spellbonuses.Mana + GroupLeadershipAAManaEnhancement()); break; } - case 'N': - { + case 'N': { max_mana = 0; break; } - default: - { + default: { Log.Out(Logs::General, Logs::None, "Invalid Class '%c' in CalcMaxMana", GetCasterClass()); max_mana = 0; break; } } - if(cur_mana > max_mana) { + if(cur_mana > max_mana) cur_mana = max_mana; - } - else if(max_mana < 0) { + else if(max_mana < 0) max_mana = 0; - } return max_mana; } void Bot::SetAttackTimer() { - float haste_mod = GetHaste() * 0.01f; - - //default value for attack timer in case they have - //an invalid weapon equipped: + float haste_mod = (GetHaste() * 0.01f); attack_timer.SetAtTrigger(4000, true); - Timer* TimerToUse = nullptr; const ItemData* PrimaryWeapon = nullptr; for (int i = MainRange; i <= MainSecondary; i++) { - //pick a timer if (i == MainPrimary) TimerToUse = &attack_timer; else if (i == MainRange) TimerToUse = &ranged_timer; else if (i == MainSecondary) TimerToUse = &attack_dw_timer; - else //invalid slot (hands will always hit this) + else continue; const ItemData* ItemToUse = nullptr; @@ -8612,60 +7169,36 @@ void Bot::SetAttackTimer() { if (ci) ItemToUse = ci->GetItem(); - //special offhand stuff if (i == MainSecondary) { - //if we have a 2H weapon in our main hand, no dual if (PrimaryWeapon != nullptr) { - if (PrimaryWeapon->ItemClass == ItemClassCommon - && (PrimaryWeapon->ItemType == ItemType2HSlash - || PrimaryWeapon->ItemType == ItemType2HBlunt - || PrimaryWeapon->ItemType == ItemType2HPiercing)) { + if (PrimaryWeapon->ItemClass == ItemClassCommon && (PrimaryWeapon->ItemType == ItemType2HSlash || PrimaryWeapon->ItemType == ItemType2HBlunt || PrimaryWeapon->ItemType == ItemType2HPiercing)) { attack_dw_timer.Disable(); continue; } } - //clients must have the skill to use it... if (!GetSkill(SkillDualWield)) { attack_dw_timer.Disable(); continue; } } - //see if we have a valid weapon if (ItemToUse != nullptr) { - //check type and damage/delay - if (ItemToUse->ItemClass != ItemClassCommon - || ItemToUse->Damage == 0 - || ItemToUse->Delay == 0) { - //no weapon + if (ItemToUse->ItemClass != ItemClassCommon || ItemToUse->Damage == 0 || ItemToUse->Delay == 0 || ((ItemToUse->ItemType > ItemTypeLargeThrowing) && (ItemToUse->ItemType != ItemTypeMartial) && (ItemToUse->ItemType != ItemType2HPiercing))) ItemToUse = nullptr; - } - // Check to see if skill is valid - else if ((ItemToUse->ItemType > ItemTypeLargeThrowing) && (ItemToUse->ItemType != ItemTypeMartial) && (ItemToUse->ItemType != ItemType2HPiercing)) { - //no weapon - ItemToUse = nullptr; - } } - int hhe = itembonuses.HundredHands + spellbonuses.HundredHands; + int hhe = (itembonuses.HundredHands + spellbonuses.HundredHands); int speed = 0; int delay = 36; - - //if we have no weapon.. if (ItemToUse == nullptr) { - //above checks ensure ranged weapons do not fall into here - // Work out if we're a monk if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) delay = GetMonkHandToHandDelay(); } else { - //we have a weapon, use its delay delay = ItemToUse->Delay; } - if (RuleB(Spells, Jun182014HundredHandsRevamp)) - speed = static_cast(((delay / haste_mod) + ((hhe / 1000.0f) * (delay / haste_mod))) * 100); - else - speed = static_cast(((delay / haste_mod) + ((hhe / 100.0f) * delay)) * 100); + + speed = (RuleB(Spells, Jun182014HundredHandsRevamp) ? static_cast(((delay / haste_mod) + ((hhe / 1000.0f) * (delay / haste_mod))) * 100) : static_cast(((delay / haste_mod) + ((hhe / 100.0f) * delay)) * 100)); TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); if(i == MainPrimary) @@ -8674,66 +7207,52 @@ void Bot::SetAttackTimer() { } int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { - if (spells[spell_id].targettype == ST_Self) return value; bool Critical = false; int32 value_BaseEffect = 0; - - value_BaseEffect = value + (value*GetBotFocusEffect(BotfocusFcBaseEffects, spell_id)/100); - + value_BaseEffect = (value + (value*GetBotFocusEffect(BotfocusFcBaseEffects, spell_id) / 100)); // Need to scale HT damage differently after level 40! It no longer scales by the constant value in the spell file. It scales differently, instead of 10 more damage per level, it does 30 more damage per level. So we multiply the level minus 40 times 20 if they are over level 40. if ( (spell_id == SPELL_HARM_TOUCH || spell_id == SPELL_HARM_TOUCH2 || spell_id == SPELL_IMP_HARM_TOUCH ) && GetLevel() > 40) - value -= (GetLevel() - 40) * 20; + value -= ((GetLevel() - 40) * 20); //This adds the extra damage from the AA Unholy Touch, 450 per level to the AA Improved Harm TOuch. if (spell_id == SPELL_IMP_HARM_TOUCH) //Improved Harm Touch - value -= GetAA(aaUnholyTouch) * 450; //Unholy Touch + value -= (GetAA(aaUnholyTouch) * 450); //Unholy Touch int chance = RuleI(Spells, BaseCritChance); - chance += itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance; + chance += (itembonuses.CriticalSpellChance + spellbonuses.CriticalSpellChance + aabonuses.CriticalSpellChance); - if (chance > 0){ + if (chance > 0) { + int32 ratio = RuleI(Spells, BaseCritRatio); + if (spell_id == SPELL_IMP_HARM_TOUCH && (GetAA(aaSpellCastingFury) > 0) && (GetAA(aaUnholyTouch) > 0)) + chance = 100; - int32 ratio = RuleI(Spells, BaseCritRatio); //Critical modifier is applied from spell effects only. Keep at 100 for live like criticals. - - //Improved Harm Touch is a guaranteed crit if you have at least one level of SCF. - if (spell_id == SPELL_IMP_HARM_TOUCH && (GetAA(aaSpellCastingFury) > 0) && (GetAA(aaUnholyTouch) > 0)) - chance = 100; - - if (zone->random.Int(1,100) <= chance){ + if (zone->random.Int(1, 100) <= chance){ Critical = true; - ratio += itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease; - ratio += itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack; - } - - else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (zone->random.Int(1,100) <= RuleI(Spells, WizCritChance))) { - ratio = zone->random.Int(1,100); //Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. + ratio += (itembonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncrease + aabonuses.SpellCritDmgIncrease); + ratio += (itembonuses.SpellCritDmgIncNoStack + spellbonuses.SpellCritDmgIncNoStack + aabonuses.SpellCritDmgIncNoStack); + } else if (GetClass() == WIZARD && (GetLevel() >= RuleI(Spells, WizCritLevel)) && (zone->random.Int(1, 100) <= RuleI(Spells, WizCritChance))) { + ratio = zone->random.Int(1, 100); Critical = true; } - - ratio += RuleI(Spells, WizCritRatio); //Default is zero - - if (Critical){ - - value = value_BaseEffect*ratio/100; - - value += value_BaseEffect*GetBotFocusEffect(BotfocusImprovedDamage, spell_id)/100; - - value += int(value_BaseEffect*GetBotFocusEffect(BotfocusFcDamagePctCrit, spell_id)/100)*ratio/100; - + ratio += RuleI(Spells, WizCritRatio); + if (Critical) { + value = (value_BaseEffect * ratio / 100); + value += (value_BaseEffect * GetBotFocusEffect(BotfocusImprovedDamage, spell_id) / 100); + value += (int(value_BaseEffect * GetBotFocusEffect(BotfocusFcDamagePctCrit, spell_id) / 100) * ratio / 100); if (target) { - value += int(value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; + value += (int(value_BaseEffect * target->GetVulnerability(this, spell_id, 0) / 100) * ratio / 100); value -= target->GetFcDamageAmtIncoming(this, spell_id); } - value -= GetBotFocusEffect(BotfocusFcDamageAmtCrit, spell_id)*ratio/100; + value -= (GetBotFocusEffect(BotfocusFcDamageAmtCrit, spell_id) * ratio / 100); value -= GetBotFocusEffect(BotfocusFcDamageAmt, spell_id); - if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) - value += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value)*ratio/100; + if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 16) - 1] >= GetLevel() - 5) + value += (GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value) * ratio / 100); entity_list.MessageClose(this, false, 100, MT_SpellCrits, "%s delivers a critical blast! (%d)", GetName(), -value); @@ -8741,29 +7260,23 @@ int32 Bot::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { } } - value = value_BaseEffect; - - value += value_BaseEffect*GetBotFocusEffect(BotfocusImprovedDamage, spell_id)/100; - - value += value_BaseEffect*GetBotFocusEffect(BotfocusFcDamagePctCrit, spell_id)/100; - - if (target) { - value += value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100; + value = value_BaseEffect; + value += (value_BaseEffect * GetBotFocusEffect(BotfocusImprovedDamage, spell_id) / 100); + value += (value_BaseEffect * GetBotFocusEffect(BotfocusFcDamagePctCrit, spell_id) / 100); + if (target) { + value += (value_BaseEffect * target->GetVulnerability(this, spell_id, 0) / 100); value -= target->GetFcDamageAmtIncoming(this, spell_id); - } + } - value -= GetBotFocusEffect(BotfocusFcDamageAmtCrit, spell_id); - - value -= GetBotFocusEffect(BotfocusFcDamageAmt, spell_id); - - if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) - value += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value); + value -= GetBotFocusEffect(BotfocusFcDamageAmtCrit, spell_id); + value -= GetBotFocusEffect(BotfocusFcDamageAmt, spell_id); + if(itembonuses.SpellDmg && spells[spell_id].classes[(GetClass() % 16) - 1] >= GetLevel() - 5) + value += GetExtraSpellAmt(spell_id, itembonuses.SpellDmg, value); return value; } int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { - if (target == nullptr) target = this; @@ -8771,72 +7284,52 @@ int32 Bot::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { int32 chance = 0; int8 modifier = 1; bool Critical = false; - - value_BaseEffect = value + (value*GetBotFocusEffect(BotfocusFcBaseEffects, spell_id)/100); - + value_BaseEffect = (value + (value*GetBotFocusEffect(BotfocusFcBaseEffects, spell_id) / 100)); value = value_BaseEffect; - - value += int(value_BaseEffect*GetBotFocusEffect(BotfocusImprovedHeal, spell_id)/100); - - // Instant Heals + value += int(value_BaseEffect*GetBotFocusEffect(BotfocusImprovedHeal, spell_id) / 100); if(spells[spell_id].buffduration < 1) { - - chance += itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance; - + chance += (itembonuses.CriticalHealChance + spellbonuses.CriticalHealChance + aabonuses.CriticalHealChance); chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); - if (spellbonuses.CriticalHealDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalHealDecay); - if(chance && (zone->random.Int(0,99) < chance)) { + if(chance && (zone->random.Int(0, 99) < chance)) { Critical = true; - modifier = 2; //At present time no critical heal amount modifier SPA exists. + modifier = 2; } value *= modifier; - value += GetBotFocusEffect(BotfocusFcHealAmtCrit, spell_id) * modifier; + value += (GetBotFocusEffect(BotfocusFcHealAmtCrit, spell_id) * modifier); value += GetBotFocusEffect(BotfocusFcHealAmt, spell_id); value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); - if(itembonuses.HealAmt && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) - value += GetExtraSpellAmt(spell_id, itembonuses.HealAmt, value) * modifier; - - value += value*target->GetHealRate(spell_id, this)/100; + if(itembonuses.HealAmt && spells[spell_id].classes[(GetClass() % 16) - 1] >= GetLevel() - 5) + value += (GetExtraSpellAmt(spell_id, itembonuses.HealAmt, value) * modifier); + value += (value * target->GetHealRate(spell_id, this) / 100); if (Critical) entity_list.MessageClose(this, false, 100, MT_SpellCrits, "%s performs an exceptional heal! (%d)", GetName(), value); return value; - } - - //Heal over time spells. [Heal Rate and Additional Healing effects do not increase this value] - else { - - chance = itembonuses.CriticalHealOverTime + spellbonuses.CriticalHealOverTime + aabonuses.CriticalHealOverTime; - + } else { + chance = (itembonuses.CriticalHealOverTime + spellbonuses.CriticalHealOverTime + aabonuses.CriticalHealOverTime); chance += target->GetFocusIncoming(focusFcHealPctCritIncoming, SE_FcHealPctCritIncoming, this, spell_id); - if (spellbonuses.CriticalRegenDecay) chance += GetDecayEffectValue(spell_id, SE_CriticalRegenDecay); if(chance && (zone->random.Int(0,99) < chance)) return (value * 2); } - return value; } int32 Bot::GetActSpellCasttime(uint16 spell_id, int32 casttime) { int32 cast_reducer = 0; cast_reducer += GetBotFocusEffect(BotfocusSpellHaste, spell_id); - uint8 botlevel = GetLevel(); uint8 botclass = GetClass(); - - if (botlevel >= 51 && casttime >= 3000 && !BeneficialSpell(spell_id) - && (botclass == SHADOWKNIGHT || botclass == RANGER - || botclass == PALADIN || botclass == BEASTLORD )) - cast_reducer += (GetLevel()-50)*3; + if (botlevel >= 51 && casttime >= 3000 && !BeneficialSpell(spell_id) && (botclass == SHADOWKNIGHT || botclass == RANGER || botclass == PALADIN || botclass == BEASTLORD )) + cast_reducer += ((GetLevel() - 50) * 3); if((casttime >= 4000) && BeneficialSpell(spell_id) && IsBuffSpell(spell_id)) { switch (GetAA(aaSpellCastingDeftness)) { @@ -8909,86 +7402,70 @@ int32 Bot::GetActSpellCasttime(uint16 spell_id, int32 casttime) { if (cast_reducer > RuleI(Spells, MaxCastTimeReduction)) cast_reducer = RuleI(Spells, MaxCastTimeReduction); - casttime = (casttime*(100 - cast_reducer)/100); - + casttime = (casttime * (100 - cast_reducer) / 100); return casttime; } int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { - // Formula = Unknown exact, based off a random percent chance up to mana cost(after focuses) of the cast spell - if(this->itembonuses.Clairvoyance && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) - { - int32 mana_back = this->itembonuses.Clairvoyance * zone->random.Int(1, 100) / 100; - // Doesnt generate mana, so best case is a free spell + if(this->itembonuses.Clairvoyance && spells[spell_id].classes[(GetClass()%16) - 1] >= GetLevel() - 5) { + int32 mana_back = (this->itembonuses.Clairvoyance * zone->random.Int(1, 100) / 100); if(mana_back > cost) mana_back = cost; cost -= mana_back; } - // This formula was derived from the following resource: - // http://www.eqsummoners.com/eq1/specialization-library.html - // WildcardX float PercentManaReduction = 0; float SpecializeSkill = GetSpecializeSkillValue(spell_id); int SuccessChance = zone->random.Int(0, 100); - float bonus = 1.0; - switch(GetAA(aaSpellCastingMastery)) - { - case 1: - bonus += 0.05; - break; - case 2: - bonus += 0.15; - break; - case 3: - bonus += 0.30; - break; + switch(GetAA(aaSpellCastingMastery)) { + case 1: + bonus += 0.05; + break; + case 2: + bonus += 0.15; + break; + case 3: + bonus += 0.30; + break; } - bonus += 0.05 * GetAA(aaAdvancedSpellCastingMastery); + bonus += (0.05 * GetAA(aaAdvancedSpellCastingMastery)); - if(SuccessChance <= (SpecializeSkill * 0.3 * bonus)) - { - PercentManaReduction = 1 + 0.05 * SpecializeSkill; - switch(GetAA(aaSpellCastingMastery)) - { - case 1: - PercentManaReduction += 2.5; - break; - case 2: - PercentManaReduction += 5.0; - break; - case 3: - PercentManaReduction += 10.0; - break; + if(SuccessChance <= (SpecializeSkill * 0.3 * bonus)) { + PercentManaReduction = (1 + 0.05 * SpecializeSkill); + switch(GetAA(aaSpellCastingMastery)) { + case 1: + PercentManaReduction += 2.5; + break; + case 2: + PercentManaReduction += 5.0; + break; + case 3: + PercentManaReduction += 10.0; + break; } - switch(GetAA(aaAdvancedSpellCastingMastery)) - { - case 1: - PercentManaReduction += 2.5; - break; - case 2: - PercentManaReduction += 5.0; - break; - case 3: - PercentManaReduction += 10.0; - break; + switch(GetAA(aaAdvancedSpellCastingMastery)) { + case 1: + PercentManaReduction += 2.5; + break; + case 2: + PercentManaReduction += 5.0; + break; + case 3: + PercentManaReduction += 10.0; + break; } } int32 focus_redux = GetBotFocusEffect(BotfocusManaCost, spell_id); if(focus_redux > 0) - { PercentManaReduction += zone->random.Real(1, (double)focus_redux); - } cost -= (cost * (PercentManaReduction / 100)); - - // Gift of Mana - reduces spell cost to 1 mana if(focus_redux >= 100) { uint32 buff_max = GetMaxTotalSlots(); for (int buffSlot = 0; buffSlot < buff_max; buffSlot++) { @@ -9011,17 +7488,15 @@ int32 Bot::GetActSpellCost(uint16 spell_id, int32 cost) { float Bot::GetActSpellRange(uint16 spell_id, float range) { float extrange = 100; extrange += GetBotFocusEffect(BotfocusRange, spell_id); - return (range * extrange) / 100; + return ((range * extrange) / 100); } int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { int increase = 100; increase += GetBotFocusEffect(BotfocusSpellDuration, spell_id); - int tic_inc = 0; - tic_inc = GetBotFocusEffect(BotfocusSpellDurByTic, spell_id); + int tic_inc = 0; tic_inc = GetBotFocusEffect(BotfocusSpellDurByTic, spell_id); - if(IsBeneficialSpell(spell_id)) - { + if(IsBeneficialSpell(spell_id)) { switch (GetAA(aaSpellCastingReinforcement)) { case 1: increase += 5; @@ -9033,6 +7508,7 @@ int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { increase += 30; if (GetAA(aaSpellCastingReinforcementMastery) == 1) increase += 20; + break; } @@ -9040,81 +7516,72 @@ int32 Bot::GetActSpellDuration(uint16 spell_id, int32 duration) { increase += 20; } - if(IsMezSpell(spell_id)) { + if(IsMezSpell(spell_id)) tic_inc += GetAA(aaMesmerizationMastery); - } return (((duration * increase) / 100) + tic_inc); } float Bot::GetAOERange(uint16 spell_id) { float range; - range = spells[spell_id].aoerange; - if(range == 0) //for TGB spells, they prolly do not have an aoe range - range = spells[spell_id].range; if(range == 0) - range = 10; //something.... + range = spells[spell_id].range; + + if(range == 0) + range = 10; if(IsBardSong(spell_id) && IsBeneficialSpell(spell_id)) { - //Live AA - Extended Notes, SionachiesCrescendo - float song_bonus = aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange; - range += range*song_bonus /100.0f; + float song_bonus = (aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange); + range += (range * song_bonus / 100.0f); } - range = GetActSpellRange(spell_id, range); - return range; } bool Bot::SpellEffect(Mob* caster, uint16 spell_id, float partial) { bool Result = false; - Result = Mob::SpellEffect(caster, spell_id, partial); - - // Franck-add: If healed/doted, a bot must show its new HP to its leader if(IsGrouped()) { Group *g = GetGroup(); if(g) { EQApplicationPacket hp_app; CreateHPPacket(&hp_app); for(int i=0; imembers[i] && g->members[i]->IsClient()) { + if(g->members[i] && g->members[i]->IsClient()) g->members[i]->CastToClient()->QueuePacket(&hp_app); - } } } } - return Result; } -void Bot::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster) { - Mob::DoBuffTic(spell_id, slot, ticsremaining, caster_level, caster); +void Bot::DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster) { + Mob::DoBuffTic(buff, slot, caster); } bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, int16 *resist_adjust) { bool Result = false; - if(zone && !zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))) { - - Log.Out(Logs::Detail, Logs::Spells, "CastSpell called for spell %s (%d) on entity %d, slot %d, time %d, mana %d, from item slot %d", - spells[spell_id].name, spell_id, target_id, slot, cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); + Log.Out(Logs::Detail, Logs::Spells, "CastSpell called for spell %s (%d) on entity %d, slot %d, time %d, mana %d, from item slot %d", spells[spell_id].name, spell_id, target_id, slot, cast_time, mana_cost, (item_slot==0xFFFFFFFF)?999:item_slot); if(casting_spell_id == spell_id) ZeroCastingVars(); if(GetClass() != BARD) { if(!IsValidSpell(spell_id) || casting_spell_id || delaytimer || spellend_timer.Enabled() || IsStunned() || IsFeared() || IsMezzed() || (IsSilenced() && !IsDiscipline(spell_id)) || (IsAmnesiad() && IsDiscipline(spell_id))) { - Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: not able to cast now. Valid? %d, casting %d, waiting? %d, spellend? %d, stunned? %d, feared? %d, mezed? %d, silenced? %d", - IsValidSpell(spell_id), casting_spell_id, delaytimer, spellend_timer.Enabled(), IsStunned(), IsFeared(), IsMezzed(), IsSilenced() ); + Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: not able to cast now. Valid? %d, casting %d, waiting? %d, spellend? %d, stunned? %d, feared? %d, mezed? %d, silenced? %d", IsValidSpell(spell_id), casting_spell_id, delaytimer, spellend_timer.Enabled(), IsStunned(), IsFeared(), IsMezzed(), IsSilenced() ); if(IsSilenced() && !IsDiscipline(spell_id)) Message_StringID(13, SILENCED_STRING); + if(IsAmnesiad() && IsDiscipline(spell_id)) + Message_StringID(13, MELEE_SILENCE); + if(casting_spell_id) AI_Event_SpellCastFinished(false, casting_spell_slot); - return(false); + + return false; } } @@ -9122,60 +7589,45 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_t Message_StringID(13, SPELL_WOULDNT_HOLD); if(casting_spell_id) AI_Event_SpellCastFinished(false, casting_spell_slot); - return(false); + + return false; } - //cannot cast under deivne aura if(DivineAura()) { Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: cannot cast while Divine Aura is in effect."); InterruptSpell(173, 0x121, false); - return(false); + return false; } - - // check for fizzle - // note that CheckFizzle itself doesn't let NPCs fizzle, - // but this code allows for it. - if(slot < MAX_PP_MEMSPELL && !CheckFizzle(spell_id)) - { + + if(slot < MAX_PP_MEMSPELL && !CheckFizzle(spell_id)) { int fizzle_msg = IsBardSong(spell_id) ? MISS_NOTE : SPELL_FIZZLE; InterruptSpell(fizzle_msg, 0x121, spell_id); uint32 use_mana = ((spells[spell_id].mana) / 4); Log.Out(Logs::Detail, Logs::Spells, "Spell casting canceled: fizzled. %d mana has been consumed", use_mana); - - // fizzle 1/4 the mana away SetMana(GetMana() - use_mana); - return(false); + return false; } if (HasActiveSong()) { Log.Out(Logs::Detail, Logs::Spells, "Casting a new spell/song while singing a song. Killing old song %d.", bardsong); - //Note: this does NOT tell the client - //_StopSong(); bardsong = 0; bardsong_target_id = 0; bardsong_slot = 0; bardsong_timer.Disable(); } - Result = DoCastSpell(spell_id, target_id, slot, cast_time, mana_cost, oSpellWillFinish, item_slot); } - return Result; } bool Bot::SpellOnTarget(uint16 spell_id, Mob* spelltar) { bool Result = false; - if(!IsValidSpell(spell_id)) return false; if(spelltar) { if(spelltar->IsBot() && (spells[spell_id].targettype == ST_GroupTeleport)) { - // So I made this check because teleporting a group of bots tended to crash the zone - // It seems several group spells also show up as ST_GroupTeleport for some - // reason so I now have to check by spell id. These appear to be Group v1 spells and - // Heal over Time spells. switch(spell_id) { // Paladin case 3577: // Wave of Life @@ -9246,50 +7698,39 @@ bool Bot::SpellOnTarget(uint16 spell_id, Mob* spelltar) { } } - //Franck-add: can't detrimental spell on bots and bots can't detriment on you or the others bots if(((IsDetrimentalSpell(spell_id) && spelltar->IsBot()) || (IsDetrimentalSpell(spell_id) && spelltar->IsClient())) && !IsResurrectionEffects(spell_id)) return false; if(spelltar->IsPet()) { - for(int i=0; iGetZoneID() == 202) && !(this == caster)) { Result = Mob::IsImmuneToSpell(spell_id, caster); - if(!Result) { if(caster->IsBot()) { if(spells[spell_id].targettype == ST_Undead) { if((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Undead) && (GetBodyType() != BT_Vampire)) { - Log.Out(Logs::Detail, Logs::Spells, "Bot's target is not an undead."); - return true; + Log.Out(Logs::Detail, Logs::Spells, "Bot's target is not an undead."); + return true; } } if(spells[spell_id].targettype == ST_Summoned) { - if((GetBodyType() != BT_SummonedUndead) - && (GetBodyType() != BT_Summoned) - && (GetBodyType() != BT_Summoned2) - && (GetBodyType() != BT_Summoned3) - ) { - Log.Out(Logs::Detail, Logs::Spells, "Bot's target is not a summoned creature."); - return true; + if((GetBodyType() != BT_SummonedUndead) && (GetBodyType() != BT_Summoned) && (GetBodyType() != BT_Summoned2) && (GetBodyType() != BT_Summoned3)) { + Log.Out(Logs::Detail, Logs::Spells, "Bot's target is not a summoned creature."); + return true; } } } @@ -9303,175 +7744,135 @@ bool Bot::IsImmuneToSpell(uint16 spell_id, Mob *caster) { bool Bot::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction) { bool Result = false; - SpellTargetType targetType = spells[spell_id].targettype; - - - // This is so PoK NPC Necro/Shd can create essence emeralds for pc's from perl scripts if(targetType == ST_GroupClientAndPet) { - if(((spell_id == 1768) && (zone->GetZoneID() == 202)) || (!IsDetrimentalSpell(spell_id))) { + if((spell_id == 1768 && zone->GetZoneID() == 202) || (!IsDetrimentalSpell(spell_id))) { CastAction = SingleTarget; return true; } } - Result = Mob::DetermineSpellTargets(spell_id, spell_target, ae_center, CastAction); - return Result; } bool Bot::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot) { bool Result = false; - - if(GetClass() == BARD) { - // Bard bots casting time is interrupting thier melee + if(GetClass() == BARD) cast_time = 0; - } Result = Mob::DoCastSpell(spell_id, target_id, slot, cast_time, mana_cost, oSpellWillFinish, item_slot); - if(oSpellWillFinish) { const SPDat_Spell_Struct &spell = spells[spell_id]; *oSpellWillFinish = Timer::GetCurrentTime() + ((spell.recast_time > 20000) ? 10000 : spell.recast_time); } - return Result; } -int32 Bot::GenerateBaseManaPoints() -{ - // Now, we need to calc the base mana. +int32 Bot::GenerateBaseManaPoints() { int32 bot_mana = 0; int32 WisInt = 0; int32 MindLesserFactor, MindFactor; int wisint_mana = 0; int base_mana = 0; int ConvertedWisInt = 0; - - switch(GetCasterClass()) - { + switch(GetCasterClass()) { case 'I': WisInt = INT; if(GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->GetClientVersion() >= ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { if(WisInt > 100) { ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100); - if(WisInt > 201) { + if(WisInt > 201) ConvertedWisInt -= ((WisInt - 201) * 5 / 4); - } } - else { + else ConvertedWisInt = WisInt; - } + if(GetLevel() < 41) { wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000); base_mana = (GetLevel() * 15); - } - else if(GetLevel() < 81) { + } else if(GetLevel() < 81) { wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100)); base_mana = (600 + ((GetLevel() - 40) * 30)); - } - else { + } else { wisint_mana = (9 * ConvertedWisInt); base_mana = (1800 + ((GetLevel() - 80) * 18)); } - bot_mana = base_mana + wisint_mana; - } - else { - if((( WisInt - 199 ) / 2) > 0) { - MindLesserFactor = ( WisInt - 199 ) / 2; - } - else { + bot_mana = (base_mana + wisint_mana); + } else { + if(((WisInt - 199) / 2) > 0) + MindLesserFactor = ((WisInt - 199) / 2); + else MindLesserFactor = 0; - } + MindFactor = WisInt - MindLesserFactor; - if(WisInt > 100) { + if(WisInt > 100) bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40); - } - else { + else bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100); - } } break; - case 'W': WisInt = WIS; if(GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->GetClientVersion() >= ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { if(WisInt > 100) { ConvertedWisInt = (((WisInt - 100) * 5 / 2) + 100); - if(WisInt > 201) { + if(WisInt > 201) ConvertedWisInt -= ((WisInt - 201) * 5 / 4); - } - } - else { + } else ConvertedWisInt = WisInt; - } + if(GetLevel() < 41) { wisint_mana = (GetLevel() * 75 * ConvertedWisInt / 1000); base_mana = (GetLevel() * 15); - } - else if(GetLevel() < 81) { + } else if(GetLevel() < 81) { wisint_mana = ((3 * ConvertedWisInt) + ((GetLevel() - 40) * 15 * ConvertedWisInt / 100)); base_mana = (600 + ((GetLevel() - 40) * 30)); - } - else { + } else { wisint_mana = (9 * ConvertedWisInt); base_mana = (1800 + ((GetLevel() - 80) * 18)); } - bot_mana = base_mana + wisint_mana; - } - else { - if((( WisInt - 199 ) / 2) > 0) { - MindLesserFactor = ( WisInt - 199 ) / 2; - } - else { + bot_mana = (base_mana + wisint_mana); + } else { + if(((WisInt - 199) / 2) > 0) + MindLesserFactor = ((WisInt - 199) / 2); + else MindLesserFactor = 0; - } - MindFactor = WisInt - MindLesserFactor; - if(WisInt > 100) { + + MindFactor = (WisInt - MindLesserFactor); + if(WisInt > 100) bot_mana = (((5 * (MindFactor + 20)) / 2) * 3 * GetLevel() / 40); - } - else { + else bot_mana = (((5 * (MindFactor + 200)) / 2) * 3 * GetLevel() / 100); - } } break; - default: bot_mana = 0; break; } - max_mana = bot_mana; - return bot_mana; } -void Bot::GenerateSpecialAttacks() -{ - // Special Attacks - if(((GetClass() == MONK) || (GetClass() == WARRIOR) || (GetClass() == RANGER) || (GetClass() == BERSERKER)) && (GetLevel() >= 60)) { +void Bot::GenerateSpecialAttacks() { + if(((GetClass() == MONK) || (GetClass() == WARRIOR) || (GetClass() == RANGER) || (GetClass() == BERSERKER)) && (GetLevel() >= 60)) SetSpecialAbility(SPECATK_TRIPLE, 1); - } } bool Bot::DoFinishedSpellAETarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool& stopLogic) { if(GetClass() == BARD) { - if(!ApplyNextBardPulse(bardsong, this, bardsong_slot)) { + if(!ApplyNextBardPulse(bardsong, this, bardsong_slot)) InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); - } + stopLogic = true; } - return true; } bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool& stopLogic) { if(spellTarget) { if(IsGrouped() && (spellTarget->IsBot() || spellTarget->IsClient()) && RuleB(Bots, BotGroupBuffing)) { - //NPC *bot = this->CastToNPC(); bool noGroupSpell = false; uint16 thespell = spell_id; - for(int i=0; i < AIspells.size(); i++) { int j = BotGetSpells(i); int spelltype = BotGetSpellType(i); @@ -9480,11 +7881,8 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, uint16 bool spelltypetargetequal = ((spelltype == 8) && (spells[thespell].targettype == ST_Self)); bool spelltypeclassequal = ((spelltype == 1024) && (GetClass() == SHAMAN)); bool slotequal = (slot == USE_ITEM_SPELL_SLOT); - - // if it's a targeted heal or escape spell or pet spell or it's self only buff or self buff weapon proc, we only want to cast it once if(spellequal || slotequal) { if((spelltypeequal || spelltypetargetequal) || spelltypeclassequal || slotequal) { - // Don't let the Shaman canni themselves to death if(((spells[thespell].effectid[0] == 0) && (spells[thespell].base[0] < 0)) && (spellTarget->GetHP() < ((spells[thespell].base[0] * (-1)) + 100))) { return false; @@ -9500,81 +7898,55 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, uint16 if(!noGroupSpell) { Group *g = GetGroup(); if(g) { - for(int i=0; imembers[i]) { - if((g->members[i]->GetClass() == NECROMANCER) && - (IsEffectInSpell(thespell, SE_AbsorbMagicAtt) || IsEffectInSpell(thespell, SE_Rune))) { - // don't cast this on necro's, their health to mana - // spell eats up the rune spell and it just keeps - // getting recast over and over + if((g->members[i]->GetClass() == NECROMANCER) && (IsEffectInSpell(thespell, SE_AbsorbMagicAtt) || IsEffectInSpell(thespell, SE_Rune))) { } else - { SpellOnTarget(thespell, g->members[i]); - } - if(g->members[i] && g->members[i]->GetPetID()) { + + if(g->members[i] && g->members[i]->GetPetID()) SpellOnTarget(thespell, g->members[i]->GetPet()); - } } } SetMana(GetMana() - (GetActSpellCost(thespell, spells[thespell].mana) * (g->GroupCount() - 1))); } } - stopLogic = true; } } - return true; } bool Bot::DoFinishedSpellGroupTarget(uint16 spell_id, Mob* spellTarget, uint16 slot, bool& stopLogic) { bool isMainGroupMGB = false; - - //if(GetBotRaidID() > 0) { - // BotRaids *br = entity_list.GetBotRaidByMob(this); - // if(br) { - // for(int n=0; nBotRaidGroups[0] && (br->BotRaidGroups[0]->members[n] == this)) { - // if(GetLevel() >= 59) // MGB AA - // isMainGroupMGB = true; - // break; - // } - // } - // } - //} - if(isMainGroupMGB && (GetClass() != BARD)) { - Say("MGB %s", spells[spell_id].name); + BotGroupSay(this, "MGB %s", spells[spell_id].name); SpellOnTarget(spell_id, this); entity_list.AESpell(this, this, spell_id, true); - } - else { + } else { Group *g = GetGroup(); if(g) { - for(int i=0; imembers[i]) { SpellOnTarget(spell_id, g->members[i]); - if(g->members[i] && g->members[i]->GetPetID()) { + if(g->members[i] && g->members[i]->GetPetID()) SpellOnTarget(spell_id, g->members[i]->GetPet()); - } } } } } - stopLogic = true; - return true; } void Bot::CalcBonuses() { + memset(&itembonuses, 0, sizeof(StatBonuses)); GenerateBaseStats(); - CalcItemBonuses(); + CalcItemBonuses(&itembonuses); CalcSpellBonuses(&spellbonuses); GenerateAABonuses(&aabonuses); SetAttackTimer(); - CalcATK(); CalcSTR(); CalcSTA(); @@ -9583,16 +7955,13 @@ void Bot::CalcBonuses() { CalcINT(); CalcWIS(); CalcCHA(); - CalcMR(); CalcFR(); CalcDR(); CalcPR(); CalcCR(); CalcCorrup(); - GenerateArmorClass(); - CalcMaxHP(); CalcMaxMana(); CalcMaxEndurance(); @@ -9604,17 +7973,14 @@ void Bot::CalcBonuses() { int32 Bot::CalcHPRegenCap(){ int level = GetLevel(); int32 hpregen_cap = 0; - hpregen_cap = RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA/25; - - hpregen_cap += aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap; - + hpregen_cap = (RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25); + hpregen_cap += (aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap); return (hpregen_cap * RuleI(Character, HPRegenMultiplier) / 100); } int32 Bot::CalcManaRegenCap(){ int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap; - switch(GetCasterClass()) - { + switch(GetCasterClass()) { case 'I': cap += (itembonuses.HeroicINT / 25); break; @@ -9622,36 +7988,27 @@ int32 Bot::CalcManaRegenCap(){ cap += (itembonuses.HeroicWIS / 25); break; } - return (cap * RuleI(Character, ManaRegenMultiplier) / 100); } -// Return max stat value for level int32 Bot::GetMaxStat() { int level = GetLevel(); int32 base = 0; - - if (level < 61) { + if (level < 61) base = 255; - } - else if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->GetClientVersion() >= ClientVersion::SoF) { - base = 255 + 5 * (level - 60); - } - else if (level < 71) { - base = 255 + 5 * (level - 60); - } - else { + else if (GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->GetClientVersion() >= ClientVersion::SoF) + base = (255 + 5 * (level - 60)); + else if (level < 71) + base = (255 + 5 * (level - 60)); + else base = 330; - } - return(base); + return base; } int32 Bot::GetMaxResist() { int level = GetLevel(); - int32 base = 500; - if(level > 60) base += ((level - 60) * 5); @@ -9659,93 +8016,63 @@ int32 Bot::GetMaxResist() { } int32 Bot::GetMaxSTR() { - return GetMaxStat() - + itembonuses.STRCapMod - + spellbonuses.STRCapMod - + aabonuses.STRCapMod; + return (GetMaxStat() + itembonuses.STRCapMod + spellbonuses.STRCapMod + aabonuses.STRCapMod); } + int32 Bot::GetMaxSTA() { - return GetMaxStat() - + itembonuses.STACapMod - + spellbonuses.STACapMod - + aabonuses.STACapMod; + return (GetMaxStat() + itembonuses.STACapMod + spellbonuses.STACapMod + aabonuses.STACapMod); } + int32 Bot::GetMaxDEX() { - return GetMaxStat() - + itembonuses.DEXCapMod - + spellbonuses.DEXCapMod - + aabonuses.DEXCapMod; + return (GetMaxStat() + itembonuses.DEXCapMod + spellbonuses.DEXCapMod + aabonuses.DEXCapMod); } + int32 Bot::GetMaxAGI() { - return GetMaxStat() - + itembonuses.AGICapMod - + spellbonuses.AGICapMod - + aabonuses.AGICapMod; + return (GetMaxStat() + itembonuses.AGICapMod + spellbonuses.AGICapMod + aabonuses.AGICapMod); } + int32 Bot::GetMaxINT() { - return GetMaxStat() - + itembonuses.INTCapMod - + spellbonuses.INTCapMod - + aabonuses.INTCapMod; + return (GetMaxStat() + itembonuses.INTCapMod + spellbonuses.INTCapMod + aabonuses.INTCapMod); } + int32 Bot::GetMaxWIS() { - return GetMaxStat() - + itembonuses.WISCapMod - + spellbonuses.WISCapMod - + aabonuses.WISCapMod; + return (GetMaxStat() + itembonuses.WISCapMod + spellbonuses.WISCapMod + aabonuses.WISCapMod); } int32 Bot::GetMaxCHA() { - return GetMaxStat() - + itembonuses.CHACapMod - + spellbonuses.CHACapMod - + aabonuses.CHACapMod; + return (GetMaxStat() + itembonuses.CHACapMod + spellbonuses.CHACapMod + aabonuses.CHACapMod); } + int32 Bot::GetMaxMR() { - return GetMaxResist() - + itembonuses.MRCapMod - + spellbonuses.MRCapMod - + aabonuses.MRCapMod; + return (GetMaxResist() + itembonuses.MRCapMod + spellbonuses.MRCapMod + aabonuses.MRCapMod); } + int32 Bot::GetMaxPR() { - return GetMaxResist() - + itembonuses.PRCapMod - + spellbonuses.PRCapMod - + aabonuses.PRCapMod; + return (GetMaxResist() + itembonuses.PRCapMod + spellbonuses.PRCapMod + aabonuses.PRCapMod); } + int32 Bot::GetMaxDR() { - return GetMaxResist() - + itembonuses.DRCapMod - + spellbonuses.DRCapMod - + aabonuses.DRCapMod; + return (GetMaxResist() + itembonuses.DRCapMod + spellbonuses.DRCapMod + aabonuses.DRCapMod); } + int32 Bot::GetMaxCR() { - return GetMaxResist() - + itembonuses.CRCapMod - + spellbonuses.CRCapMod - + aabonuses.CRCapMod; + return (GetMaxResist() + itembonuses.CRCapMod + spellbonuses.CRCapMod + aabonuses.CRCapMod); } + int32 Bot::GetMaxFR() { - return GetMaxResist() - + itembonuses.FRCapMod - + spellbonuses.FRCapMod - + aabonuses.FRCapMod; + return (GetMaxResist() + itembonuses.FRCapMod + spellbonuses.FRCapMod + aabonuses.FRCapMod); } + int32 Bot::GetMaxCorrup() { - return GetMaxResist() - + itembonuses.CorrupCapMod - + spellbonuses.CorrupCapMod - + aabonuses.CorrupCapMod; + return (GetMaxResist() + itembonuses.CorrupCapMod + spellbonuses.CorrupCapMod + aabonuses.CorrupCapMod); } int32 Bot::CalcSTR() { - int32 val = STR + itembonuses.STR + spellbonuses.STR; - + int32 val = (STR + itembonuses.STR + spellbonuses.STR); int32 mod = aabonuses.STR; - - if(val>255 && GetLevel() <= 60) + if(val > 255 && GetLevel() <= 60) val = 255; - STR = val + mod; - + + STR = (val + mod); if(STR < 1) STR = 1; @@ -9753,18 +8080,16 @@ int32 Bot::CalcSTR() { if(STR > m) STR = m; - return(STR); + return STR; } int32 Bot::CalcSTA() { - int32 val = STA + itembonuses.STA + spellbonuses.STA; - + int32 val = (STA + itembonuses.STA + spellbonuses.STA); int32 mod = aabonuses.STA; - - if(val>255 && GetLevel() <= 60) + if(val > 255 && GetLevel() <= 60) val = 255; - STA = val + mod; - + + STA = (val + mod); if(STA < 1) STA = 1; @@ -9772,18 +8097,16 @@ int32 Bot::CalcSTA() { if(STA > m) STA = m; - return(STA); + return STA; } int32 Bot::CalcAGI() { - int32 val = AGI + itembonuses.AGI + spellbonuses.AGI; + int32 val = (AGI + itembonuses.AGI + spellbonuses.AGI); int32 mod = aabonuses.AGI; - - if(val>255 && GetLevel() <= 60) + if(val > 255 && GetLevel() <= 60) val = 255; - AGI = val + mod; - + AGI = (val + mod); if(AGI < 1) AGI = 1; @@ -9791,18 +8114,16 @@ int32 Bot::CalcAGI() { if(AGI > m) AGI = m; - return(AGI); + return AGI; } int32 Bot::CalcDEX() { - int32 val = DEX + itembonuses.DEX + spellbonuses.DEX; - + int32 val = (DEX + itembonuses.DEX + spellbonuses.DEX); int32 mod = aabonuses.DEX; - - if(val>255 && GetLevel() <= 60) + if(val > 255 && GetLevel() <= 60) val = 255; - DEX = val + mod; - + + DEX = (val + mod); if(DEX < 1) DEX = 1; @@ -9810,35 +8131,34 @@ int32 Bot::CalcDEX() { if(DEX > m) DEX = m; - return(DEX); + return DEX; } int32 Bot::CalcINT() { - int32 val = INT + itembonuses.INT + spellbonuses.INT; - + int32 val = (INT + itembonuses.INT + spellbonuses.INT); int32 mod = aabonuses.INT; - - if(val>255 && GetLevel() <= 60) + if(val > 255 && GetLevel() <= 60) val = 255; - INT = val + mod; + + INT = (val + mod); if(INT < 1) INT = 1; + int m = GetMaxINT(); if(INT > m) INT = m; - return(INT); + return INT; } int32 Bot::CalcWIS() { - int32 val = WIS + itembonuses.WIS + spellbonuses.WIS; - + int32 val = (WIS + itembonuses.WIS + spellbonuses.WIS); int32 mod = aabonuses.WIS; - - if(val>255 && GetLevel() <= 60) + if(val > 255 && GetLevel() <= 60) val = 255; - WIS = val + mod; + + WIS = (val + mod); if(WIS < 1) WIS = 1; @@ -9847,17 +8167,16 @@ int32 Bot::CalcWIS() { if(WIS > m) WIS = m; - return(WIS); + return WIS; } int32 Bot::CalcCHA() { - int32 val = CHA + itembonuses.CHA + spellbonuses.CHA; - + int32 val = (CHA + itembonuses.CHA + spellbonuses.CHA); int32 mod = aabonuses.CHA; - - if(val>255 && GetLevel() <= 60) + if(val > 255 && GetLevel() <= 60) val = 255; - CHA = val + mod; + + CHA = (val + mod); if(CHA < 1) CHA = 1; @@ -9866,18 +8185,13 @@ int32 Bot::CalcCHA() { if(CHA > m) CHA = m; - return(CHA); + return CHA; } -//The AA multipliers are set to be 5, but were 2 on WR -//The resistant discipline which I think should be here is implemented -//in Mob::ResistSpell -int32 Bot::CalcMR() -{ - MR += itembonuses.MR + spellbonuses.MR + aabonuses.MR; - +int32 Bot::CalcMR() { + MR += (itembonuses.MR + spellbonuses.MR + aabonuses.MR); if(GetClass() == WARRIOR) - MR += GetLevel() / 2; + MR += (GetLevel() / 2); if(MR < 1) MR = 1; @@ -9885,21 +8199,19 @@ int32 Bot::CalcMR() if(MR > GetMaxMR()) MR = GetMaxMR(); - return(MR); + return MR; } -int32 Bot::CalcFR() -{ +int32 Bot::CalcFR() { int c = GetClass(); if(c == RANGER) { FR += 4; - int l = GetLevel(); if(l > 49) - FR += l - 49; + FR += (l - 49); } - FR += itembonuses.FR + spellbonuses.FR + aabonuses.FR; + FR += (itembonuses.FR + spellbonuses.FR + aabonuses.FR); if(FR < 1) FR = 1; @@ -9907,57 +8219,48 @@ int32 Bot::CalcFR() if(FR > GetMaxFR()) FR = GetMaxFR(); - return(FR); + return FR; } -int32 Bot::CalcDR() -{ +int32 Bot::CalcDR() { int c = GetClass(); if(c == PALADIN) { DR += 8; - int l = GetLevel(); if(l > 49) - DR += l - 49; - + DR += (l - 49); } else if(c == SHADOWKNIGHT) { DR += 4; - int l = GetLevel(); if(l > 49) - DR += l - 49; + DR += (l - 49); } - DR += itembonuses.DR + spellbonuses.DR + aabonuses.DR; - + DR += (itembonuses.DR + spellbonuses.DR + aabonuses.DR); if(DR < 1) DR = 1; if(DR > GetMaxDR()) DR = GetMaxDR(); - return(DR); + return DR; } -int32 Bot::CalcPR() -{ +int32 Bot::CalcPR() { int c = GetClass(); if(c == ROGUE) { PR += 8; - int l = GetLevel(); if(l > 49) - PR += l - 49; - + PR += (l - 49); } else if(c == SHADOWKNIGHT) { PR += 4; - int l = GetLevel(); if(l > 49) - PR += l - 49; + PR += (l - 49); } - PR += itembonuses.PR + spellbonuses.PR + aabonuses.PR; + PR += (itembonuses.PR + spellbonuses.PR + aabonuses.PR); if(PR < 1) PR = 1; @@ -9965,21 +8268,19 @@ int32 Bot::CalcPR() if(PR > GetMaxPR()) PR = GetMaxPR(); - return(PR); + return PR; } -int32 Bot::CalcCR() -{ +int32 Bot::CalcCR() { int c = GetClass(); if(c == RANGER) { CR += 4; - int l = GetLevel(); if(l > 49) - CR += l - 49; + CR += (l - 49); } - CR += itembonuses.CR + spellbonuses.CR + aabonuses.CR; + CR += (itembonuses.CR + spellbonuses.CR + aabonuses.CR); if(CR < 1) CR = 1; @@ -9987,39 +8288,28 @@ int32 Bot::CalcCR() if(CR > GetMaxCR()) CR = GetMaxCR(); - return(CR); + return CR; } -int32 Bot::CalcCorrup() -{ - Corrup = Corrup + itembonuses.Corrup + spellbonuses.Corrup + aabonuses.Corrup; - +int32 Bot::CalcCorrup() { + Corrup = (Corrup + itembonuses.Corrup + spellbonuses.Corrup + aabonuses.Corrup); if(Corrup > GetMaxCorrup()) Corrup = GetMaxCorrup(); - return(Corrup); + return Corrup; } int32 Bot::CalcATK() { - ATK = itembonuses.ATK + spellbonuses.ATK + aabonuses.ATK + GroupLeadershipAAOffenseEnhancement(); - return(ATK); + ATK = (itembonuses.ATK + spellbonuses.ATK + aabonuses.ATK + GroupLeadershipAAOffenseEnhancement()); + return ATK; } void Bot::CalcRestState() { - - // This method calculates rest state HP and mana regeneration. - // The bot must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds, - // must be sitting down, and must not have any detrimental spells affecting them. - // if(!RuleI(Character, RestRegenPercent)) return; RestRegenHP = RestRegenMana = RestRegenEndurance = 0; - - if(IsEngaged() || !IsSitting()) - return; - - if(!rest_timer.Check(false)) + if(IsEngaged() || !IsSitting() || !rest_timer.Check(false)) return; uint32 buff_count = GetMaxTotalSlots(); @@ -10032,44 +8322,35 @@ void Bot::CalcRestState() { } RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100); - RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100); - if(RuleB(Character, RestRegenEndurance)) RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100); } -int32 Bot::LevelRegen() -{ +int32 Bot::LevelRegen() { int level = GetLevel(); bool bonus = GetRaceBitmask(_baseRace) & RuleI(Character, BaseHPRegenBonusRaces); uint8 multiplier1 = bonus ? 2 : 1; int32 hp = 0; - - //these calculations should match up with the info from Monkly Business, which was last updated ~05/2008: http://www.monkly-business.net/index.php?pageid=abilities if (level < 51) { if (IsSitting()) { if (level < 20) - hp += 2 * multiplier1; + hp += (2 * multiplier1); else if (level < 50) - hp += 3 * multiplier1; - else //level == 50 - hp += 4 * multiplier1; - } - else //feigned or standing - hp += 1 * multiplier1; - } - //there may be an easier way to calculate this next part, but I don't know what it is - else { //level >= 51 + hp += (3 * multiplier1); + else + hp += (4 * multiplier1); + } else + hp += (1 * multiplier1); + } else { int32 tmp = 0; float multiplier2 = 1; if (level < 56) { tmp = 2; if (bonus) multiplier2 = 3; - } - else if (level < 60) { - tmp = 3; + } else if (level < 60) { + tmp = 3; if (bonus) multiplier2 = 3.34; } @@ -10077,56 +8358,44 @@ int32 Bot::LevelRegen() tmp = 4; if (bonus) multiplier2 = 3; - } - else if (level < 63) { + } else if (level < 63) { tmp = 5; if (bonus) multiplier2 = 2.8; - } - else if (level < 65) { + } else if (level < 65) { tmp = 6; if (bonus) multiplier2 = 2.67; - } - else { //level >= 65 + } else { tmp = 7; if (bonus) multiplier2 = 2.58; } - - hp += int32(float(tmp) * multiplier2); + hp += (int32(float(tmp) * multiplier2)); } - return hp; } int32 Bot::CalcHPRegen() { - int32 regen = LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen; - regen += aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration(); - - regen = (regen * RuleI(Character, HPRegenMultiplier)) / 100; + int32 regen = (LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen); + regen += (aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration()); + regen = ((regen * RuleI(Character, HPRegenMultiplier)) / 100); return regen; } -int32 Bot::CalcManaRegen() -{ +int32 Bot::CalcManaRegen() { uint8 level = GetLevel(); uint8 botclass = GetClass(); int32 regen = 0; - //this should be changed so we dont med while camping, etc... - if (IsSitting()) - { + if (IsSitting()) { BuffFadeBySitModifier(); if(botclass != WARRIOR && botclass != MONK && botclass != ROGUE && botclass != BERSERKER) { - regen = (((GetSkill(SkillMeditate) / 10) + (level - (level / 4))) / 4) + 4; - regen += spellbonuses.ManaRegen + itembonuses.ManaRegen; - } - else - regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; - } - else { - regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen; - } + regen = ((((GetSkill(SkillMeditate) / 10) + (level - (level / 4))) / 4) + 4); + regen += (spellbonuses.ManaRegen + itembonuses.ManaRegen); + } else + regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); + } else + regen = (2 + spellbonuses.ManaRegen + itembonuses.ManaRegen); if(GetCasterClass() == 'I') regen += (itembonuses.HeroicINT / 25); @@ -10136,37 +8405,18 @@ int32 Bot::CalcManaRegen() regen = 0; regen += aabonuses.ManaRegen; - - regen = (regen * RuleI(Character, ManaRegenMultiplier)) / 100; - + regen = ((regen * RuleI(Character, ManaRegenMultiplier)) / 100); float mana_regen_rate = RuleR(Bots, BotManaRegen); if(mana_regen_rate < 0.0f) mana_regen_rate = 0.0f; - regen = regen * mana_regen_rate; // 90% of people wouldnt guess that manaregen would decrease the larger the number they input, this makes more sense - + regen = (regen * mana_regen_rate); return regen; } -// This is for calculating Base HPs + STA bonus for SoD or later clients. uint32 Bot::GetClassHPFactor() { - - int factor; - - // Note: Base HP factor under level 41 is equal to factor / 12, and from level 41 to 80 is factor / 6. - // Base HP over level 80 is factor / 10 - // HP per STA point per level is factor / 30 for level 80+ - // HP per STA under level 40 is the level 80 HP Per STA / 120, and for over 40 it is / 60. - - switch(GetClass()) - { - case DRUID: - case ENCHANTER: - case NECROMANCER: - case MAGICIAN: - case WIZARD: - factor = 240; - break; + uint32 factor; + switch(GetClass()) { case BEASTLORD: case BERSERKER: case MONK: @@ -10198,47 +8448,36 @@ uint32 Bot::GetClassHPFactor() { int32 Bot::CalcMaxHP() { int32 bot_hp = 0; uint32 nd = 10000; - - bot_hp += GenerateBaseHitPoints() + itembonuses.HP; - - nd += aabonuses.MaxHP; //Natural Durability, Physical Enhancement, Planar Durability - - bot_hp = (float)bot_hp * (float)nd / (float)10000; //this is to fix the HP-above-495k issue - bot_hp += spellbonuses.HP + aabonuses.HP; - + bot_hp += (GenerateBaseHitPoints() + itembonuses.HP); + nd += aabonuses.MaxHP; + bot_hp = ((float)bot_hp * (float)nd / (float)10000); + bot_hp += (spellbonuses.HP + aabonuses.HP); bot_hp += GroupLeadershipAAHealthEnhancement(); - - bot_hp += bot_hp * ((spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f); + bot_hp += (bot_hp * ((spellbonuses.MaxHPChange + itembonuses.MaxHPChange) / 10000.0f)); max_hp = bot_hp; - if (cur_hp > max_hp) cur_hp = max_hp; int hp_perc_cap = spellbonuses.HPPercCap[0]; if(hp_perc_cap) { - int curHP_cap = (max_hp * hp_perc_cap) / 100; + int curHP_cap = ((max_hp * hp_perc_cap) / 100); if (cur_hp > curHP_cap || (spellbonuses.HPPercCap[1] && cur_hp > spellbonuses.HPPercCap[1])) cur_hp = curHP_cap; } - return max_hp; } -int32 Bot::CalcMaxEndurance() -{ - max_end = CalcBaseEndurance() + spellbonuses.Endurance + itembonuses.Endurance; - - if (max_end < 0) { +int32 Bot::CalcMaxEndurance() { + max_end = (CalcBaseEndurance() + spellbonuses.Endurance + itembonuses.Endurance); + if (max_end < 0) max_end = 0; - } - if (cur_end > max_end) { + if (cur_end > max_end) cur_end = max_end; - } int end_perc_cap = spellbonuses.EndPercCap[0]; if(end_perc_cap) { - int curEnd_cap = (max_end * end_perc_cap) / 100; + int curEnd_cap = ((max_end * end_perc_cap) / 100); if (cur_end > curEnd_cap || (spellbonuses.EndPercCap[1] && cur_end > spellbonuses.EndPercCap[1])) cur_end = curEnd_cap; } @@ -10246,49 +8485,37 @@ int32 Bot::CalcMaxEndurance() return max_end; } -int32 Bot::CalcBaseEndurance() -{ +int32 Bot::CalcBaseEndurance() { int32 base_end = 0; int32 base_endurance = 0; int32 ConvertedStats = 0; int32 sta_end = 0; int Stats = 0; - if(GetOwner() && GetOwner()->CastToClient() && GetOwner()->CastToClient()->GetClientVersion() >= ClientVersion::SoD && RuleB(Character, SoDClientUseSoDHPManaEnd)) { int HeroicStats = 0; - Stats = ((GetSTR() + GetSTA() + GetDEX() + GetAGI()) / 4); HeroicStats = ((GetHeroicSTR() + GetHeroicSTA() + GetHeroicDEX() + GetHeroicAGI()) / 4); - if (Stats > 100) { ConvertedStats = (((Stats - 100) * 5 / 2) + 100); - if (Stats > 201) { + if (Stats > 201) ConvertedStats -= ((Stats - 201) * 5 / 4); - } - } - else { + } else ConvertedStats = Stats; - } if (GetLevel() < 41) { sta_end = (GetLevel() * 75 * ConvertedStats / 1000); base_endurance = (GetLevel() * 15); - } - else if (GetLevel() < 81) { + } else if (GetLevel() < 81) { sta_end = ((3 * ConvertedStats) + ((GetLevel() - 40) * 15 * ConvertedStats / 100)); base_endurance = (600 + ((GetLevel() - 40) * 30)); - } - else { + } else { sta_end = (9 * ConvertedStats); base_endurance = (1800 + ((GetLevel() - 80) * 18)); } base_end = (base_endurance + sta_end + (HeroicStats * 10)); - } - else - { - Stats = GetSTR()+GetSTA()+GetDEX()+GetAGI(); - int LevelBase = GetLevel() * 15; - + } else { + Stats = (GetSTR()+GetSTA()+GetDEX()+GetAGI()); + int LevelBase = (GetLevel() * 15); int at_most_800 = Stats; if(at_most_800 > 800) at_most_800 = 800; @@ -10297,57 +8524,45 @@ int32 Bot::CalcBaseEndurance() int HalfBonus400to800 = 0; int Bonus800plus = 0; int HalfBonus800plus = 0; - - int BonusUpto800 = int( at_most_800 / 4 ) ; + int BonusUpto800 = int(at_most_800 / 4) ; if(Stats > 400) { - Bonus400to800 = int( (at_most_800 - 400) / 4 ); - HalfBonus400to800 = int( std::max( ( at_most_800 - 400 ), 0 ) / 8 ); - + Bonus400to800 = int((at_most_800 - 400) / 4); + HalfBonus400to800 = int(std::max((at_most_800 - 400), 0) / 8); if(Stats > 800) { - Bonus800plus = int( (Stats - 800) / 8 ) * 2; - HalfBonus800plus = int( (Stats - 800) / 16 ); + Bonus800plus = (int((Stats - 800) / 8) * 2); + HalfBonus800plus = int((Stats - 800) / 16); } } - int bonus_sum = BonusUpto800 + Bonus400to800 + HalfBonus400to800 + Bonus800plus + HalfBonus800plus; - + int bonus_sum = (BonusUpto800 + Bonus400to800 + HalfBonus400to800 + Bonus800plus + HalfBonus800plus); base_end = LevelBase; - - //take all of the sums from above, then multiply by level*0.075 - base_end += ( bonus_sum * 3 * GetLevel() ) / 40; + base_end += ((bonus_sum * 3 * GetLevel()) / 40); } return base_end; } int32 Bot::CalcEnduranceRegen() { - int32 regen = int32(GetLevel() * 4 / 10) + 2; - regen += spellbonuses.EnduranceRegen + itembonuses.EnduranceRegen; - + int32 regen = (int32(GetLevel() * 4 / 10) + 2); + regen += (spellbonuses.EnduranceRegen + itembonuses.EnduranceRegen); return (regen * RuleI(Character, EnduranceRegenMultiplier) / 100); } int32 Bot::CalcEnduranceRegenCap() { - int cap = (RuleI(Character, ItemEnduranceRegenCap) + itembonuses.HeroicSTR/25 + itembonuses.HeroicDEX/25 + itembonuses.HeroicAGI/25 + itembonuses.HeroicSTA/25); - + int cap = (RuleI(Character, ItemEnduranceRegenCap) + itembonuses.HeroicSTR / 25 + itembonuses.HeroicDEX / 25 + itembonuses.HeroicAGI / 25 + itembonuses.HeroicSTA / 25); return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100); } -void Bot::SetEndurance(int32 newEnd) -{ - /*Endurance can't be less than 0 or greater than max*/ +void Bot::SetEndurance(int32 newEnd) { if(newEnd < 0) newEnd = 0; - else if(newEnd > GetMaxEndurance()){ + else if(newEnd > GetMaxEndurance()) newEnd = GetMaxEndurance(); - } cur_end = newEnd; } void Bot::DoEnduranceUpkeep() { int upkeep_sum = 0; - - int cost_redux = spellbonuses.EnduranceReduction + itembonuses.EnduranceReduction; - + int cost_redux = (spellbonuses.EnduranceReduction + itembonuses.EnduranceReduction); uint32 buffs_i; uint32 buff_count = GetMaxTotalSlots(); for (buffs_i = 0; buffs_i < buff_count; buffs_i++) { @@ -10356,15 +8571,15 @@ void Bot::DoEnduranceUpkeep() { if(upkeep > 0) { if(cost_redux > 0) { if(upkeep <= cost_redux) - continue; //reduced to 0 + continue; + upkeep -= cost_redux; } - if((upkeep+upkeep_sum) > GetEndurance()) { - //they do not have enough to keep this one going. + + if((upkeep+upkeep_sum) > GetEndurance()) BuffFadeBySlot(buffs_i); - } else { + else upkeep_sum += upkeep; - } } } } @@ -10376,13 +8591,11 @@ void Bot::DoEnduranceUpkeep() { void Bot::Camp(bool databaseSave) { Sit(); - if(IsGrouped()) { + if(IsGrouped()) RemoveBotFromGroup(this, GetGroup()); - } - if(GetInHealRotation()) { + if(GetInHealRotation()) GetHealRotationLeader()->RemoveHealRotationMember(this); - } if(databaseSave) Save(); @@ -10391,9 +8604,8 @@ void Bot::Camp(bool databaseSave) { } void Bot::Zone() { - if(HasGroup()) { + if(HasGroup()) GetGroup()->MemberZoned(this); - } Save(); Depop(); @@ -10401,38 +8613,26 @@ void Bot::Zone() { bool Bot::IsArcheryRange(Mob *target) { bool result = false; - if(target) { - float range = GetBotArcheryRange() + 5.0; //Fudge it a little, client will let you hit something at 0 0 0 when you are at 205 0 0 - + float range = (GetBotArcheryRange() + 5.0); range *= range; - float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition()); - - float minRuleDistance = RuleI(Combat, MinRangedAttackDist) * RuleI(Combat, MinRangedAttackDist); - + float minRuleDistance = (RuleI(Combat, MinRangedAttackDist) * RuleI(Combat, MinRangedAttackDist)); if((targetDistance > range) || (targetDistance < minRuleDistance)) result = false; else result = true; } - return result; } bool Bot::IsBotCasterCombatRange(Mob *target) { bool result = false; - if(target) { float range = BotAISpellRange; - range *= range; - - // half the max so the bot doesn't always stop at max range to allow combat movement range *= .5; - float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition()); - if(targetDistance > range) result = false; else @@ -10445,36 +8645,28 @@ bool Bot::IsBotCasterCombatRange(Mob *target) { bool Bot::IsGroupPrimaryHealer() { bool result = false; uint8 botclass = GetClass(); - if(HasGroup()) { Group *g = GetGroup(); - - switch(botclass) - { - case CLERIC: - { + switch(botclass) { + case CLERIC: { result = true; break; } - case DRUID: - { + case DRUID: { result = GroupHasClericClass(g) ? false : true; break; } - case SHAMAN: - { + case SHAMAN: { result = (GroupHasClericClass(g) || GroupHasDruidClass(g)) ? false : true; break; } case PALADIN: case RANGER: - case BEASTLORD: - { + case BEASTLORD: { result = GroupHasPriestClass(g) ? false : true; break; } - default: - { + default: { result = false; break; } @@ -10487,29 +8679,22 @@ bool Bot::IsGroupPrimaryHealer() { bool Bot::IsGroupPrimarySlower() { bool result = false; uint8 botclass = GetClass(); - if(HasGroup()) { Group *g = GetGroup(); - - switch(botclass) - { - case SHAMAN: - { + switch(botclass) { + case SHAMAN: { result = true; break; } - case ENCHANTER: - { + case ENCHANTER: { result = GroupHasShamanClass(g) ? false : true; break; } - case BEASTLORD: - { + case BEASTLORD: { result = (GroupHasShamanClass(g) || GroupHasEnchanterClass(g)) ? false : true; break; } - default: - { + default: { result = false; break; } @@ -10532,93 +8717,36 @@ bool Bot::CanHeal() { botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); - if(botSpell.SpellId != 0){ + if(botSpell.SpellId != 0) result = true; - } - - /*if(GetFirstBotSpellBySpellType(this, SpellType_Heal)){ - result = true; - }*/ - + return result; } bool Bot::CalculateNewPosition2(float x, float y, float z, float speed, bool checkZ) { - // 2.5625 is the inverse of 0.3902439. The only difference is in implementation. - // NOTE: You can not change just one of the constants below. They are the same number, just expressed inversly of each other. - // const float clientOverServerRatio = 2.5625f; - const float serverOverClientRatio = 0.3902439f; - - // Use this block if using 2.5625 as the ratio. - // const int clientAnimationMovementRateTypeMultiple = 8; - - // WildcardX: These are valid rates and observations based on painstaking testing of the client response to these values - // - // - // 0 * 8 = 0 : No Movement - // 1 * 8 = 8 : Death Walk - // 2 * 8 = 16 : Slow Walk - // 3 * 8 = 24 : Normal Walk - // 4 * 8 = 32 : Jog - // 5 * 8 = 40 : Normal Run - // 6 * 8 = 48 : Faster Run - // 7 * 8 = 56 : Even Faster Run - // 8 * 8 = 64 : Fastest Yet Run (Bard Song Speed?) - // 9 * 8 = 72 : Faster Fastest Yet Run - // 10 * 8 = 80 : .... you get the idea, this is pretty fast - // 11 * 8 = 88 : .... warp speed anyone? - // 12 * 8 = 96 : .... transwarp drive was invented by gnomes in Norrath - // 13 * 8 = 104 : ... who needs warp drives when you can just displace through time and space? - // - // - // You get the idea here with these... These seem to be "benchmark values" of animation movement and how fast - // the client thinks the Mob is moving so it can make it all look seemless between updates from the server. - // This chart is scalable by the client so you can pass an animation rate of 50 and get a "faster run" but not quite a "even faster run" - - // Convert the Bot movement rate to a value the client understands based on the chart above - // Use this block if using 2.5625 as the ratio. - // speed *= clientMovementRateTypeMultiple; - - - // This sets the movement animation rate with the client - // Use this block if using 2.5625 as the ratio. - // pRunAnimSpeed = speed; - pRunAnimSpeed = ((serverOverClientRatio * 10.0f) * speed) * 10.0f; - - // Now convert our "speed" from the value necessary for the client to animate the correct movement type rate to the server side speed - // Use this block if using 2.5625 as the ratio. - // speed *= serverOverClientRatio; - speed = pRunAnimSpeed / serverOverClientRatio; - return MakeNewPositionAndSendUpdate(x, y, z, speed, checkZ); } -// Orders all bots in the specified group to follow their group leader. void Bot::BotGroupOrderFollow(Group* group, Client* client) { if(group && client) { Mob* groupLeader = group->GetLeader(); - if(groupLeader) { for(int i = 0; i< MAX_GROUP_MEMBERS; i++) { if(group->members[i] && group->members[i]->IsBot()) { Bot* botGroupMember = group->members[i]->CastToBot(); - if(botGroupMember && botGroupMember->GetBotOwnerCharacterID() == client->CharacterID()) { if(group->IsLeader(botGroupMember) && botGroupMember->GetBotOwner()) { botGroupMember->SetFollowID(botGroupMember->GetBotOwner()->GetID()); if(botGroupMember->GetBotOwner()) - botGroupMember->Say("Following %s.", botGroupMember->GetBotOwner()->GetName()); - } - else { + botGroupMember->BotGroupSay(botGroupMember, "Following %s.", botGroupMember->GetBotOwner()->GetName()); + } else { botGroupMember->SetFollowID(groupLeader->GetID()); - botGroupMember->Say("Following %s.", groupLeader->GetCleanName()); + botGroupMember->BotGroupSay(botGroupMember, "Following %s.", groupLeader->GetCleanName()); } botGroupMember->WipeHateList(); - - if(botGroupMember->HasPet() && botGroupMember->GetPet()) { + if(botGroupMember->HasPet() && botGroupMember->GetPet()) botGroupMember->GetPet()->WipeHateList(); - } } } } @@ -10626,42 +8754,33 @@ void Bot::BotGroupOrderFollow(Group* group, Client* client) { } } -// Orders all bots in the specified group to guard their current location. void Bot::BotGroupOrderGuard(Group* group, Client* client) { if(group && client) { for(int i = 0; i< MAX_GROUP_MEMBERS; i++) { if(group->members[i] && group->members[i]->IsBot()) { Bot* botGroupMember = group->members[i]->CastToBot(); - if(botGroupMember && botGroupMember->GetBotOwnerCharacterID() == client->CharacterID()) { botGroupMember->SetFollowID(0); - botGroupMember->Say("Guarding here."); - + botGroupMember->BotGroupSay(botGroupMember, "Guarding here."); botGroupMember->WipeHateList(); - - if(botGroupMember->HasPet() && botGroupMember->GetPet()) { + if(botGroupMember->HasPet() && botGroupMember->GetPet()) botGroupMember->GetPet()->WipeHateList(); - } } } } } } -// Orders all bots in the specified group to attack their group leader's target. void Bot::BotGroupOrderAttack(Group* group, Mob* target, Client* client) { if(group && target) { Mob* groupLeader = group->GetLeader(); - if(groupLeader) { for(int i=0; i < MAX_GROUP_MEMBERS; i++) { if(group->members[i] && group->members[i]->IsBot()) { Bot* botGroupMember = group->members[i]->CastToBot(); - if(botGroupMember->GetBotOwnerCharacterID() == client->CharacterID()) { botGroupMember->WipeHateList(); botGroupMember->AddToHateList(target, 1); - if(botGroupMember->HasPet() && botGroupMember->GetPet()) { botGroupMember->GetPet()->WipeHateList(); botGroupMember->GetPet()->AddToHateList(target, 1); @@ -10673,18 +8792,15 @@ void Bot::BotGroupOrderAttack(Group* group, Mob* target, Client* client) { } } -// Summons all bot group members to ther owners location. void Bot::BotGroupSummon(Group* group, Client* client) { if(group) { for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { if(group->members[i] && group->members[i]->IsBot()) { Bot* botMember = group->members[i]->CastToBot(); - if(botMember->GetBotOwnerCharacterID() == client->CharacterID()) { botMember->SetTarget(botMember->GetBotOwner()); botMember->WipeHateList(); botMember->Warp(glm::vec3(botMember->GetBotOwner()->GetPosition())); - if(botMember->HasPet() && botMember->GetPet()) { botMember->GetPet()->SetTarget(botMember); botMember->GetPet()->WipeHateList(); @@ -10696,13 +8812,10 @@ void Bot::BotGroupSummon(Group* group, Client* client) { } } -// Finds a bot in the entitity list by bot owner character id and the bot first name Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, std::string botName) { Bot* Result = 0; - if(c) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - if(!BotList.empty()) { for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { if(std::string((*botListItr)->GetCleanName()) == botName) { @@ -10712,11 +8825,9 @@ Bot* Bot::GetBotByBotClientOwnerAndBotName(Client* c, std::string botName) { } } } - return Result; } -// Processes a group invite from a Client for a Bot character. void Bot::ProcessBotGroupInvite(Client* c, std::string botName) { if(c) { Bot* invitedBot = GetBotByBotClientOwnerAndBotName(c, botName); @@ -10731,16 +8842,11 @@ void Bot::ProcessBotGroupInvite(Client* c, std::string botName) { database.SetGroupID(c->GetName(), g->GetID(), c->CharacterID()); database.SetGroupID(invitedBot->GetCleanName(), g->GetID(), invitedBot->GetBotID()); } - } - else { + } else { AddBotToGroup(invitedBot, c->GetGroup()); database.SetGroupID(invitedBot->GetCleanName(), c->GetGroup()->GetID(), invitedBot->GetBotID()); } - - /*if(c->GetBotRaidID() > 0) - invitedBot->SetBotRaidID(c->GetBotRaidID());*/ } - // TODO: if there is a bot but the bot is already in a group, do we send an group invitation cancel message back to the client? } } @@ -10866,191 +8972,351 @@ void Bot::ProcessBotInspectionRequest(Bot* inspectedBot, Client* client) { } } -void Bot::CalcItemBonuses() +void Bot::CalcItemBonuses(StatBonuses* newbon) { memset(&itembonuses, 0, sizeof(StatBonuses)); const ItemData* itemtmp = 0; - for (int i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; ++i) { - const ItemInst* item = GetBotItem(i); + for (int i = EmuConstants::EQUIPMENT_BEGIN; i <= (EmuConstants::EQUIPMENT_END + 1); ++i) { + const ItemInst* item = GetBotItem((i == 22 ? 9999 : i)); if(item) { - for(int j = AUG_BEGIN; j < EmuConstants::ITEM_COMMON_SIZE; ++j) { - const ItemInst* aug = item->GetAugment(j); - if(aug) { - itemtmp = aug->GetItem(); - if(itemtmp->AC != 0) - itembonuses.AC += itemtmp->AC; - if(itemtmp->HP != 0) - itembonuses.HP += itemtmp->HP; - if(itemtmp->Mana != 0) - itembonuses.Mana += itemtmp->Mana; - if(itemtmp->Endur != 0) - itembonuses.Endurance += itemtmp->Endur; - if(itemtmp->AStr != 0) - itembonuses.STR += itemtmp->AStr; - if(itemtmp->ASta != 0) - itembonuses.STA += itemtmp->ASta; - if(itemtmp->ADex != 0) - itembonuses.DEX += itemtmp->ADex; - if(itemtmp->AAgi != 0) - itembonuses.AGI += itemtmp->AAgi; - if(itemtmp->AInt != 0) - itembonuses.INT += itemtmp->AInt; - if(itemtmp->AWis != 0) - itembonuses.WIS += itemtmp->AWis; - if(itemtmp->ACha != 0) - itembonuses.CHA += itemtmp->ACha; - if(itemtmp->MR != 0) - itembonuses.MR += itemtmp->MR; - if(itemtmp->FR != 0) - itembonuses.FR += itemtmp->FR; - if(itemtmp->CR != 0) - itembonuses.CR += itemtmp->CR; - if(itemtmp->PR != 0) - itembonuses.PR += itemtmp->PR; - if(itemtmp->DR != 0) - itembonuses.DR += itemtmp->DR; - if(itemtmp->SVCorruption != 0) - itembonuses.Corrup += itemtmp->SVCorruption; - if(itemtmp->Regen != 0) - itembonuses.HPRegen += itemtmp->Regen; - if(itemtmp->ManaRegen != 0) - itembonuses.ManaRegen += itemtmp->ManaRegen; - if(itemtmp->Attack != 0) - itembonuses.ATK += itemtmp->Attack; - if(itemtmp->DamageShield != 0) - itembonuses.DamageShield += itemtmp->DamageShield; - if(itemtmp->SpellShield != 0) - itembonuses.SpellDamageShield += itemtmp->SpellShield; - if(itemtmp->Shielding != 0) - itembonuses.MeleeMitigation += itemtmp->Shielding; - if(itemtmp->StunResist != 0) - itembonuses.StunResist += itemtmp->StunResist; - if(itemtmp->StrikeThrough != 0) - itembonuses.StrikeThrough += itemtmp->StrikeThrough; - if(itemtmp->Avoidance != 0) - itembonuses.AvoidMeleeChance += itemtmp->Avoidance; - if(itemtmp->Accuracy != 0) - itembonuses.HitChance += itemtmp->Accuracy; - if(itemtmp->CombatEffects != 0) - itembonuses.ProcChance += itemtmp->CombatEffects; - if(itemtmp->Haste != 0) - if(itembonuses.haste < itemtmp->Haste) - itembonuses.haste = itemtmp->Haste; - if(GetClass() == BARD && itemtmp->BardValue != 0) { - if(itemtmp->BardType == ItemTypeBrassInstrument) - itembonuses.brassMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypePercussionInstrument) - itembonuses.percussionMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeSinging) - itembonuses.singingMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeStringedInstrument) - itembonuses.stringedMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeWindInstrument) - itembonuses.windMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeAllInstrumentTypes) { - itembonuses.brassMod += itemtmp->BardValue; - itembonuses.percussionMod += itemtmp->BardValue; - itembonuses.singingMod += itemtmp->BardValue; - itembonuses.stringedMod += itemtmp->BardValue; - itembonuses.windMod += itemtmp->BardValue; - } - } - if ((itemtmp->Worn.Effect != 0) && (itemtmp->Worn.Type == ET_WornEffect)) { // latent effects - ApplySpellsBonuses(itemtmp->Worn.Effect, itemtmp->Worn.Level, &itembonuses,0,itemtmp->Worn.Type); - } - } - } - itemtmp = item->GetItem(); - if(itemtmp->AC != 0) - itembonuses.AC += itemtmp->AC; - if(itemtmp->HP != 0) - itembonuses.HP += itemtmp->HP; - if(itemtmp->Mana != 0) - itembonuses.Mana += itemtmp->Mana; - if(itemtmp->Endur != 0) - itembonuses.Endurance += itemtmp->Endur; - if(itemtmp->AStr != 0) - itembonuses.STR += itemtmp->AStr; - if(itemtmp->ASta != 0) - itembonuses.STA += itemtmp->ASta; - if(itemtmp->ADex != 0) - itembonuses.DEX += itemtmp->ADex; - if(itemtmp->AAgi != 0) - itembonuses.AGI += itemtmp->AAgi; - if(itemtmp->AInt != 0) - itembonuses.INT += itemtmp->AInt; - if(itemtmp->AWis != 0) - itembonuses.WIS += itemtmp->AWis; - if(itemtmp->ACha != 0) - itembonuses.CHA += itemtmp->ACha; - if(itemtmp->MR != 0) - itembonuses.MR += itemtmp->MR; - if(itemtmp->FR != 0) - itembonuses.FR += itemtmp->FR; - if(itemtmp->CR != 0) - itembonuses.CR += itemtmp->CR; - if(itemtmp->PR != 0) - itembonuses.PR += itemtmp->PR; - if(itemtmp->DR != 0) - itembonuses.DR += itemtmp->DR; - if(itemtmp->SVCorruption != 0) - itembonuses.Corrup += itemtmp->SVCorruption; - if(itemtmp->Regen != 0) - itembonuses.HPRegen += itemtmp->Regen; - if(itemtmp->ManaRegen != 0) - itembonuses.ManaRegen += itemtmp->ManaRegen; - if(itemtmp->Attack != 0) - itembonuses.ATK += itemtmp->Attack; - if(itemtmp->DamageShield != 0) - itembonuses.DamageShield += itemtmp->DamageShield; - if(itemtmp->SpellShield != 0) - itembonuses.SpellDamageShield += itemtmp->SpellShield; - if(itemtmp->Shielding != 0) - itembonuses.MeleeMitigation += itemtmp->Shielding; - if(itemtmp->StunResist != 0) - itembonuses.StunResist += itemtmp->StunResist; - if(itemtmp->StrikeThrough != 0) - itembonuses.StrikeThrough += itemtmp->StrikeThrough; - if(itemtmp->Avoidance != 0) - itembonuses.AvoidMeleeChance += itemtmp->Avoidance; - if(itemtmp->Accuracy != 0) - itembonuses.HitChance += itemtmp->Accuracy; - if(itemtmp->CombatEffects != 0) - itembonuses.ProcChance += itemtmp->CombatEffects; - if(itemtmp->Haste != 0) - if(itembonuses.haste < itemtmp->Haste) - itembonuses.haste = itemtmp->Haste; - if(GetClass() == BARD && itemtmp->BardValue != 0) { - if(itemtmp->BardType == ItemTypeBrassInstrument) - itembonuses.brassMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypePercussionInstrument) - itembonuses.percussionMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeSinging) - itembonuses.singingMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeStringedInstrument) - itembonuses.stringedMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeWindInstrument) - itembonuses.windMod += itemtmp->BardValue; - else if(itemtmp->BardType == ItemTypeAllInstrumentTypes) { - itembonuses.brassMod += itemtmp->BardValue; - itembonuses.percussionMod += itemtmp->BardValue; - itembonuses.singingMod += itemtmp->BardValue; - itembonuses.stringedMod += itemtmp->BardValue; - itembonuses.windMod += itemtmp->BardValue; - } - } - if ((itemtmp->Worn.Effect != 0) && (itemtmp->Worn.Type == ET_WornEffect)) { // latent effects - ApplySpellsBonuses(itemtmp->Worn.Effect, itemtmp->Worn.Level, &itembonuses,0,itemtmp->Worn.Type); - } + AddItemBonuses(item, newbon); } } - if(itembonuses.HPRegen > CalcHPRegenCap()) - itembonuses.HPRegen = CalcHPRegenCap(); + // Caps + if(newbon->HPRegen > CalcHPRegenCap()) + newbon->HPRegen = CalcHPRegenCap(); - if(itembonuses.ManaRegen > CalcManaRegenCap()) - itembonuses.ManaRegen = CalcManaRegenCap(); + if(newbon->ManaRegen > CalcManaRegenCap()) + newbon->ManaRegen = CalcManaRegenCap(); + + if(newbon->EnduranceRegen > CalcEnduranceRegenCap()) + newbon->EnduranceRegen = CalcEnduranceRegenCap(); +} + +void Bot::AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAug, bool isTribute) { + if(!inst || !inst->IsType(ItemClassCommon)) + { + return; + } + + if(inst->GetAugmentType()==0 && isAug == true) + { + return; + } + + const Item_Struct *item = inst->GetItem(); + + if(!isTribute && !inst->IsEquipable(GetBaseRace(),GetClass())) + { + if(item->ItemType != ItemTypeFood && item->ItemType != ItemTypeDrink) + return; + } + + if(GetLevel() < item->ReqLevel) + { + return; + } + + if(GetLevel() >= item->RecLevel) + { + newbon->AC += item->AC; + newbon->HP += item->HP; + newbon->Mana += item->Mana; + newbon->Endurance += item->Endur; + newbon->ATK += item->Attack; + newbon->STR += (item->AStr + item->HeroicStr); + newbon->STA += (item->ASta + item->HeroicSta); + newbon->DEX += (item->ADex + item->HeroicDex); + newbon->AGI += (item->AAgi + item->HeroicAgi); + newbon->INT += (item->AInt + item->HeroicInt); + newbon->WIS += (item->AWis + item->HeroicWis); + newbon->CHA += (item->ACha + item->HeroicCha); + + newbon->MR += (item->MR + item->HeroicMR); + newbon->FR += (item->FR + item->HeroicFR); + newbon->CR += (item->CR + item->HeroicCR); + newbon->PR += (item->PR + item->HeroicPR); + newbon->DR += (item->DR + item->HeroicDR); + newbon->Corrup += (item->SVCorruption + item->HeroicSVCorrup); + + newbon->STRCapMod += item->HeroicStr; + newbon->STACapMod += item->HeroicSta; + newbon->DEXCapMod += item->HeroicDex; + newbon->AGICapMod += item->HeroicAgi; + newbon->INTCapMod += item->HeroicInt; + newbon->WISCapMod += item->HeroicWis; + newbon->CHACapMod += item->HeroicCha; + newbon->MRCapMod += item->HeroicMR; + newbon->CRCapMod += item->HeroicFR; + newbon->FRCapMod += item->HeroicCR; + newbon->PRCapMod += item->HeroicPR; + newbon->DRCapMod += item->HeroicDR; + newbon->CorrupCapMod += item->HeroicSVCorrup; + + newbon->HeroicSTR += item->HeroicStr; + newbon->HeroicSTA += item->HeroicSta; + newbon->HeroicDEX += item->HeroicDex; + newbon->HeroicAGI += item->HeroicAgi; + newbon->HeroicINT += item->HeroicInt; + newbon->HeroicWIS += item->HeroicWis; + newbon->HeroicCHA += item->HeroicCha; + newbon->HeroicMR += item->HeroicMR; + newbon->HeroicFR += item->HeroicFR; + newbon->HeroicCR += item->HeroicCR; + newbon->HeroicPR += item->HeroicPR; + newbon->HeroicDR += item->HeroicDR; + newbon->HeroicCorrup += item->HeroicSVCorrup; + + } + else + { + int lvl = GetLevel(); + int reclvl = item->RecLevel; + + newbon->AC += CalcRecommendedLevelBonus( lvl, reclvl, item->AC ); + newbon->HP += CalcRecommendedLevelBonus( lvl, reclvl, item->HP ); + newbon->Mana += CalcRecommendedLevelBonus( lvl, reclvl, item->Mana ); + newbon->Endurance += CalcRecommendedLevelBonus( lvl, reclvl, item->Endur ); + newbon->ATK += CalcRecommendedLevelBonus( lvl, reclvl, item->Attack ); + newbon->STR += CalcRecommendedLevelBonus( lvl, reclvl, (item->AStr + item->HeroicStr) ); + newbon->STA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ASta + item->HeroicSta) ); + newbon->DEX += CalcRecommendedLevelBonus( lvl, reclvl, (item->ADex + item->HeroicDex) ); + newbon->AGI += CalcRecommendedLevelBonus( lvl, reclvl, (item->AAgi + item->HeroicAgi) ); + newbon->INT += CalcRecommendedLevelBonus( lvl, reclvl, (item->AInt + item->HeroicInt) ); + newbon->WIS += CalcRecommendedLevelBonus( lvl, reclvl, (item->AWis + item->HeroicWis) ); + newbon->CHA += CalcRecommendedLevelBonus( lvl, reclvl, (item->ACha + item->HeroicCha) ); + + newbon->MR += CalcRecommendedLevelBonus( lvl, reclvl, (item->MR + item->HeroicMR) ); + newbon->FR += CalcRecommendedLevelBonus( lvl, reclvl, (item->FR + item->HeroicFR) ); + newbon->CR += CalcRecommendedLevelBonus( lvl, reclvl, (item->CR + item->HeroicCR) ); + newbon->PR += CalcRecommendedLevelBonus( lvl, reclvl, (item->PR + item->HeroicPR) ); + newbon->DR += CalcRecommendedLevelBonus( lvl, reclvl, (item->DR + item->HeroicDR) ); + newbon->Corrup += CalcRecommendedLevelBonus( lvl, reclvl, (item->SVCorruption + item->HeroicSVCorrup) ); + + newbon->STRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicStr ); + newbon->STACapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSta ); + newbon->DEXCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDex ); + newbon->AGICapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicAgi ); + newbon->INTCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicInt ); + newbon->WISCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicWis ); + newbon->CHACapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCha ); + newbon->MRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicMR ); + newbon->CRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicFR ); + newbon->FRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCR ); + newbon->PRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicPR ); + newbon->DRCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDR ); + newbon->CorrupCapMod += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSVCorrup ); + + newbon->HeroicSTR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicStr ); + newbon->HeroicSTA += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSta ); + newbon->HeroicDEX += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDex ); + newbon->HeroicAGI += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicAgi ); + newbon->HeroicINT += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicInt ); + newbon->HeroicWIS += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicWis ); + newbon->HeroicCHA += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCha ); + newbon->HeroicMR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicMR ); + newbon->HeroicFR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicFR ); + newbon->HeroicCR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicCR ); + newbon->HeroicPR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicPR ); + newbon->HeroicDR += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicDR ); + newbon->HeroicCorrup += CalcRecommendedLevelBonus( lvl, reclvl, item->HeroicSVCorrup ); + } + + //FatherNitwit: New style haste, shields, and regens + if(newbon->haste < (int32)item->Haste) { + newbon->haste = item->Haste; + } + if(item->Regen > 0) + newbon->HPRegen += item->Regen; + + if(item->ManaRegen > 0) + newbon->ManaRegen += item->ManaRegen; + + if(item->EnduranceRegen > 0) + newbon->EnduranceRegen += item->EnduranceRegen; + + if(item->DamageShield > 0) { + if((newbon->DamageShield + item->DamageShield) > RuleI(Character, ItemDamageShieldCap)) + newbon->DamageShield = RuleI(Character, ItemDamageShieldCap); + else + newbon->DamageShield += item->DamageShield; + } + if(item->SpellShield > 0) { + if((newbon->SpellShield + item->SpellShield) > RuleI(Character, ItemSpellShieldingCap)) + newbon->SpellShield = RuleI(Character, ItemSpellShieldingCap); + else + newbon->SpellShield += item->SpellShield; + } + if(item->Shielding > 0) { + if((newbon->MeleeMitigation + item->Shielding) > RuleI(Character, ItemShieldingCap)) + newbon->MeleeMitigation = RuleI(Character, ItemShieldingCap); + else + newbon->MeleeMitigation += item->Shielding; + } + if(item->StunResist > 0) { + if((newbon->StunResist + item->StunResist) > RuleI(Character, ItemStunResistCap)) + newbon->StunResist = RuleI(Character, ItemStunResistCap); + else + newbon->StunResist += item->StunResist; + } + if(item->StrikeThrough > 0) { + if((newbon->StrikeThrough + item->StrikeThrough) > RuleI(Character, ItemStrikethroughCap)) + newbon->StrikeThrough = RuleI(Character, ItemStrikethroughCap); + else + newbon->StrikeThrough += item->StrikeThrough; + } + if(item->Avoidance > 0) { + if((newbon->AvoidMeleeChance + item->Avoidance) > RuleI(Character, ItemAvoidanceCap)) + newbon->AvoidMeleeChance = RuleI(Character, ItemAvoidanceCap); + else + newbon->AvoidMeleeChance += item->Avoidance; + } + if(item->Accuracy > 0) { + if((newbon->HitChance + item->Accuracy) > RuleI(Character, ItemAccuracyCap)) + newbon->HitChance = RuleI(Character, ItemAccuracyCap); + else + newbon->HitChance += item->Accuracy; + } + if(item->CombatEffects > 0) { + if((newbon->ProcChance + item->CombatEffects) > RuleI(Character, ItemCombatEffectsCap)) + newbon->ProcChance = RuleI(Character, ItemCombatEffectsCap); + else + newbon->ProcChance += item->CombatEffects; + } + if(item->DotShielding > 0) { + if((newbon->DoTShielding + item->DotShielding) > RuleI(Character, ItemDoTShieldingCap)) + newbon->DoTShielding = RuleI(Character, ItemDoTShieldingCap); + else + newbon->DoTShielding += item->DotShielding; + } + + if(item->HealAmt > 0) { + if((newbon->HealAmt + item->HealAmt) > RuleI(Character, ItemHealAmtCap)) + newbon->HealAmt = RuleI(Character, ItemHealAmtCap); + else + newbon->HealAmt += item->HealAmt; + } + if(item->SpellDmg > 0) { + if((newbon->SpellDmg + item->SpellDmg) > RuleI(Character, ItemSpellDmgCap)) + newbon->SpellDmg = RuleI(Character, ItemSpellDmgCap); + else + newbon->SpellDmg += item->SpellDmg; + } + if(item->Clairvoyance > 0) { + if((newbon->Clairvoyance + item->Clairvoyance) > RuleI(Character, ItemClairvoyanceCap)) + newbon->Clairvoyance = RuleI(Character, ItemClairvoyanceCap); + else + newbon->Clairvoyance += item->Clairvoyance; + } + + if(item->DSMitigation > 0) { + if((newbon->DSMitigation + item->DSMitigation) > RuleI(Character, ItemDSMitigationCap)) + newbon->DSMitigation = RuleI(Character, ItemDSMitigationCap); + else + newbon->DSMitigation += item->DSMitigation; + } + if (item->Worn.Effect > 0 && item->Worn.Type == ET_WornEffect) {// latent effects + ApplySpellsBonuses(item->Worn.Effect, item->Worn.Level, newbon, 0, item->Worn.Type); + } + + if (item->Focus.Effect>0 && (item->Focus.Type == ET_Focus)) { // focus effects + ApplySpellsBonuses(item->Focus.Effect, item->Focus.Level, newbon, 0); + } + + switch(item->BardType) + { + case 51: /* All (e.g. Singing Short Sword) */ + { + if(item->BardValue > newbon->singingMod) + newbon->singingMod = item->BardValue; + if(item->BardValue > newbon->brassMod) + newbon->brassMod = item->BardValue; + if(item->BardValue > newbon->stringedMod) + newbon->stringedMod = item->BardValue; + if(item->BardValue > newbon->percussionMod) + newbon->percussionMod = item->BardValue; + if(item->BardValue > newbon->windMod) + newbon->windMod = item->BardValue; + break; + } + case 50: /* Singing */ + { + if(item->BardValue > newbon->singingMod) + newbon->singingMod = item->BardValue; + break; + } + case 23: /* Wind */ + { + if(item->BardValue > newbon->windMod) + newbon->windMod = item->BardValue; + break; + } + case 24: /* stringed */ + { + if(item->BardValue > newbon->stringedMod) + newbon->stringedMod = item->BardValue; + break; + } + case 25: /* brass */ + { + if(item->BardValue > newbon->brassMod) + newbon->brassMod = item->BardValue; + break; + } + case 26: /* Percussion */ + { + if(item->BardValue > newbon->percussionMod) + newbon->percussionMod = item->BardValue; + break; + } + } + + if (item->SkillModValue != 0 && item->SkillModType <= HIGHEST_SKILL){ + if ((item->SkillModValue > 0 && newbon->skillmod[item->SkillModType] < item->SkillModValue) || + (item->SkillModValue < 0 && newbon->skillmod[item->SkillModType] > item->SkillModValue)) + { + newbon->skillmod[item->SkillModType] = item->SkillModValue; + } + } + + if (item->ExtraDmgSkill != 0 && item->ExtraDmgSkill <= HIGHEST_SKILL) { + if((newbon->SkillDamageAmount[item->ExtraDmgSkill] + item->ExtraDmgAmt) > RuleI(Character, ItemExtraDmgCap)) + newbon->SkillDamageAmount[item->ExtraDmgSkill] = RuleI(Character, ItemExtraDmgCap); + else + newbon->SkillDamageAmount[item->ExtraDmgSkill] += item->ExtraDmgAmt; + } + + if (!isAug) + { + int i; + for (i = 0; i < EmuConstants::ITEM_COMMON_SIZE; i++) { + AddItemBonuses(inst->GetAugment(i),newbon,true); + } + } + +} + +int Bot::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat) +{ + if( (reclevel > 0) && (level < reclevel) ) + { + int32 statmod = (level * 10000 / reclevel) * basestat; + + if( statmod < 0 ) + { + statmod -= 5000; + return (statmod/10000); + } + else + { + statmod += 5000; + return (statmod/10000); + } + } + + return 0; } // This method is intended to call all necessary methods to do all bot stat calculations, including spell buffs, equipment, AA bonsues, etc. @@ -11110,9 +9376,7 @@ void Bot::CalcBotStats(bool showtext) { } bool Bot::CheckLoreConflict(const ItemData* item) { - if (!item) - return false; - if (!(item->LoreFlag)) + if (!item || !(item->LoreFlag)) return false; if (item->LoreGroup == -1) // Standard lore items; look everywhere except the shared bank, return the result @@ -11148,19 +9412,19 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } if(!strcasecmp( sep->arg[1], "help") && !strcasecmp( sep->arg[2], "\0")){ - c->Message(0, "List of commands availables for bots :"); + c->Message(0, "List of commands availables for bots:"); c->Message(0, "#bot help - show this"); c->Message(0, "#bot create [name] [class (id)] [race (id)] [model (male/female)] - create a permanent bot. See #bot help create."); c->Message(0, "#bot help create - show all the race/class id. (make it easier to create bots)"); c->Message(0, "#bot delete - completely destroy forever the targeted bot and all its items."); - c->Message(0, "#bot list [all/class(1-16)] - list your bots all or by class. Classes: 1(Warrior), 2(Cleric), 3(Paladin), 4(Ranger), 5(Sk), 6(Druid), 7(Monk), 8(Bard), 9(Rogue), 10(Shaman), 11(Necro), 12(Wiz), 13(Mag), 14(Ench), 15(Beast), 16(Bersek)"); + c->Message(0, "#bot list [all/class(1-16)] - list your bots all or by class. Classes: 1(WAR), 2(CLR), 3(PAL), 4(RNG), 5(SHD), 6(DRU), 7(MNK), 8(BRD), 9(ROG), 10(SHM), 11(NEC), 12(WIZ), 13(MAG), 14(ENC), 15(BST), 16(BER)"); c->Message(0, "#bot spawn [bot name] - spawn a bot from it's name (use list to see all the bots). "); - c->Message(0, "#bot inventory list - show the inventory (and the slots IDs) of the targetted bot."); - c->Message(0, "#bot inventory remove [slotid] - remove the item at the given slot in the inventory of the targetted bot."); + c->Message(0, "#bot inventory list - show the inventory (and the slots IDs) of the targeted bot."); + c->Message(0, "#bot inventory remove [slotid] - remove the item at the given slot in the inventory of the targeted bot."); c->Message(0, "#bot update - you must type that command once you gain a level."); c->Message(0, "#bot summon - It will summon your targeted bot to you."); - c->Message(0, "#bot ai mez - If you're grouped with an enchanter, he will mez your target."); - c->Message(0, "#bot picklock - You must have a targeted rogue bot in your group and be right on the door."); + c->Message(0, "#bot ai mez - If you're grouped with an Enchanter, he will mez your target."); + c->Message(0, "#bot picklock - You must have a targeted Rogue bot in your group and be right on the door."); c->Message(0, "#bot cure [poison|disease|curse|blindness] Cleric has most options"); c->Message(0, "#bot bindme - You must have a Cleric in your group to get Bind Affinity cast on you."); c->Message(0, "#bot track - look at mobs in the zone (ranger has options)"); @@ -11182,7 +9446,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(0, "#bot gate - you need a Druid or Wizard in your group)"); c->Message(0, "#bot archery - Toggle Archery Skilled bots between using a Bow or using Melee weapons."); c->Message(0, "#bot magepet [earth|water|air|fire|monster] - Select the pet type you want your Mage bot to use."); - c->Message(0, "#bot giveitem - Gives your targetted bot the item you have on your cursor."); + c->Message(0, "#bot giveitem - Gives your targeted bot the item you have on your cursor."); c->Message(0, "#bot augmentitem - Allows you to augment items for other classes. You must have the Augmentation Sealer window filled."); c->Message(0, "#bot camp - Tells your bot to camp out of the game."); c->Message(0, "#bot group help - Displays the commands available to manage any BOTs in your group."); @@ -11196,8 +9460,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(0, "#bot groupmessages [on|off] [bot name|all] - Turns group messages on/off for named bot/all bots."); c->Message(0, "#bot defensive [bot name] - Causes warrior or knight bot to use defensive discipline / buff."); c->Message(0, "#bot healrotation help - Displays the commands available to manage BOT heal rotations."); - // TODO: - // c->Message(0, "#bot illusion - Enchanter Bot cast an illusion buff spell on you or your target."); c->Message(0, "#bot pull [] [target] - Bot Pulling Target NPC's"); c->Message(0, "#bot setinspectmessage - Copies your inspect message to a targeted bot that you own"); c->Message(0, "#bot bardoutofcombat [on|off] - Determines wheter bard bots use out of combat songs."); @@ -11207,29 +9469,23 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { // pull if(!strcasecmp(sep->arg[1], "pull")) { Mob *target = c->GetTarget(); - if(target == nullptr || target == c || target->IsBot() || (target->IsPet() && target->GetOwner()->IsBot())) - { + if(target == nullptr || target == c || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { c->Message(15, "You must select a monster"); return; } - if(c->IsGrouped()) - { + if(c->IsGrouped()) { bool haspuller = false; Group *g = c->GetGroup(); - for(int i=0; imembers[i] && g->members[i]->IsBot() && !strcasecmp(g->members[i]->GetName() , sep->arg[2])) - { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g && g->members[i] && g->members[i]->IsBot() && !strcasecmp(g->members[i]->GetName() , sep->arg[2])) { haspuller = true; Mob *puller = g->members[i]; - if (puller->CastToBot()->IsArcheryRange(target)) - { - puller->Say("Trying to Pull %s \n", target->GetCleanName()); + if (puller->CastToBot()->IsArcheryRange(target)) { + puller->CastToBot()->BotGroupSay(puller->CastToBot(), "Trying to Pull %s \n", target->GetCleanName()); puller->CastToBot()->BotRangedAttack(target); - } - else { - puller->Say("Out of Range %s \n", target->GetCleanName()); + } else { + puller->CastToBot()->BotGroupSay(puller->CastToBot(), "Out of Range %s \n", target->GetCleanName()); } } } @@ -11248,7 +9504,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { else { uint32 BotFollowDistance = atoi(sep->arg[2]); c->GetTarget()->SetFollowDistance(BotFollowDistance); - } return; @@ -11266,33 +9521,42 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 botid = c->GetTarget()->CastToBot()->GetBotID(); std::string errorMessage; - int setslot = atoi(sep->arg[2]); uint8 red = atoi(sep->arg[3]); uint8 green = atoi(sep->arg[4]); uint8 blue = atoi(sep->arg[5]); uint32 setcolor = (red << 16) | (green << 8) | blue; - std::string query = StringFormat("UPDATE botinventory SET color = %u " - "WHERE slotID = %i AND botID = %u", - setcolor, setslot, botid); - auto results = database.QueryDatabase(query); - if(!results.Success()) - return; + std::string query; + if (setslot == -1) { + int slots[] = { 2, 7, 9, 12, 17, 18, 19 }; + query = StringFormat("UPDATE botinventory SET color = %u WHERE slotID IN (2, 7, 9, 12, 17, 18, 19) AND botID = %u", setcolor, botid); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; - uint8 slotmaterial = InventoryOld::CalcMaterialFromSlot(setslot); - c->GetTarget()->CastToBot()->SendWearChange(slotmaterial); + for (int i = 0; i < 7; i++) { + uint8 slotmaterial = InventoryOld::CalcMaterialFromSlot((uint8)slots[i]); + c->GetTarget()->CastToBot()->SendWearChange(slotmaterial); + } + } else { + query = StringFormat("UPDATE botinventory SET color = %u WHERE slotID = %i AND botID = %u", setcolor, setslot, botid); + auto results = database.QueryDatabase(query); + if (!results.Success()) + return; + + uint8 slotmaterial = InventoryOld::CalcMaterialFromSlot(setslot); + c->GetTarget()->CastToBot()->SendWearChange(slotmaterial); + } } else { c->Message(15, "You must target a bot you own to do this."); } return; } - // Help for coloring bot armor - if(!strcasecmp(sep->arg[1], "help") && !strcasecmp(sep->arg[2], "armorcolor") ){ - //read from db - + + if(!strcasecmp(sep->arg[1], "help") && !strcasecmp(sep->arg[2], "armorcolor")){ c->Message(0, "-----------------#bot armorcolor help-----------------------------"); - c->Message(0, "Armor: 17(Chest/Robe), 7(Arms), 9(Bracer), 12(Hands), 18(Legs), 19(Boots), 2(Helm)"); + c->Message(0, "Armor: -1(All), 2(Helm), 7(Arms), 9(Bracer), 12(Hands), 17(Chest/Robe), 18(Legs), 19(Boots)"); c->Message(0, "------------------------------------------------------------------"); c->Message(0, "Color: [red] [green] [blue] (enter a number from 0-255 for each"); c->Message(0, "------------------------------------------------------------------"); @@ -11310,7 +9574,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "giveitem")) { if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner() == c)) { - // Its a bot targetted and this client is the bots owner Bot* targetedBot = c->GetTarget()->CastToBot(); if(targetedBot) targetedBot->FinishTrade(c, BotTradeClientNoDropNoTrade); @@ -11324,17 +9587,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "camp")) { if(!strcasecmp(sep->arg[2], "all")) { - // Camp out all bots owned by this bot owner BotOrderCampAll(c); - } - else { - // Camp only the targetted bot + } else { if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner()->CastToClient() == c)) { Bot* targetedBot = c->GetTarget()->CastToBot(); if(targetedBot) targetedBot->Camp(); - } - else + } else c->Message(15, "You must target a bot you own to do this."); } @@ -11345,16 +9604,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->arg[2][0] == '\0' || sep->arg[3][0] == '\0' || sep->arg[4][0] == '\0' || sep->arg[5][0] == '\0' || sep->arg[6][0] != '\0') { c->Message(0, "Usage: #bot create [name] [class(id)] [race(id)] [gender (male/female)]"); return; - } - else if(strcasecmp(sep->arg[3],"1") && strcasecmp(sep->arg[3],"2") && strcasecmp(sep->arg[3],"3") && strcasecmp(sep->arg[3],"4") && strcasecmp(sep->arg[3],"5") && strcasecmp(sep->arg[3],"6") && strcasecmp(sep->arg[3],"7") && strcasecmp(sep->arg[3],"8") && strcasecmp(sep->arg[3],"9") && strcasecmp(sep->arg[3],"10") && strcasecmp(sep->arg[3],"11") && strcasecmp(sep->arg[3],"12") && strcasecmp(sep->arg[3],"13") && strcasecmp(sep->arg[3],"14") && strcasecmp(sep->arg[3],"15") && strcasecmp(sep->arg[3],"16")) { + } else if(strcasecmp(sep->arg[3],"1") && strcasecmp(sep->arg[3],"2") && strcasecmp(sep->arg[3],"3") && strcasecmp(sep->arg[3],"4") && strcasecmp(sep->arg[3],"5") && strcasecmp(sep->arg[3],"6") && strcasecmp(sep->arg[3],"7") && strcasecmp(sep->arg[3],"8") && strcasecmp(sep->arg[3],"9") && strcasecmp(sep->arg[3],"10") && strcasecmp(sep->arg[3],"11") && strcasecmp(sep->arg[3],"12") && strcasecmp(sep->arg[3],"13") && strcasecmp(sep->arg[3],"14") && strcasecmp(sep->arg[3],"15") && strcasecmp(sep->arg[3],"16")) { c->Message(0, "Usage: #bot create [name] [class(id)] [race(id)] [gender (male/female)]"); return; - } - else if(strcasecmp(sep->arg[4],"1") && strcasecmp(sep->arg[4],"2") && strcasecmp(sep->arg[4],"3") && strcasecmp(sep->arg[4],"4") && strcasecmp(sep->arg[4],"5") && strcasecmp(sep->arg[4],"6") && strcasecmp(sep->arg[4],"7") && strcasecmp(sep->arg[4],"8") && strcasecmp(sep->arg[4],"9") && strcasecmp(sep->arg[4],"10") && strcasecmp(sep->arg[4],"11") && strcasecmp(sep->arg[4],"12") && strcasecmp(sep->arg[4],"330") && strcasecmp(sep->arg[4],"128") && strcasecmp(sep->arg[4],"130") && strcasecmp(sep->arg[4],"522")) { + } else if(strcasecmp(sep->arg[4],"1") && strcasecmp(sep->arg[4],"2") && strcasecmp(sep->arg[4],"3") && strcasecmp(sep->arg[4],"4") && strcasecmp(sep->arg[4],"5") && strcasecmp(sep->arg[4],"6") && strcasecmp(sep->arg[4],"7") && strcasecmp(sep->arg[4],"8") && strcasecmp(sep->arg[4],"9") && strcasecmp(sep->arg[4],"10") && strcasecmp(sep->arg[4],"11") && strcasecmp(sep->arg[4],"12") && strcasecmp(sep->arg[4],"330") && strcasecmp(sep->arg[4],"128") && strcasecmp(sep->arg[4],"130") && strcasecmp(sep->arg[4],"522")) { c->Message(0, "Usage: #bot create [name] [class(1-16)] [race(1-12,128,130,330,522)] [gender (male/female)]"); return; - } - else if(strcasecmp(sep->arg[5],"male") && strcasecmp(sep->arg[5],"female")) { + } else if(strcasecmp(sep->arg[5],"male") && strcasecmp(sep->arg[5],"female")) { c->Message(0, "Usage: #bot create [name] [class(1-16)] [race(1-12,128,130,330,522)] [gender (male/female)]"); return; } @@ -11374,6 +9630,11 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[5], "female")) gender = 1; + if(!IsBotNameAvailable(sep->arg[2],&TempErrorMessage)) { + c->Message(0, "The name %s is already being used or is invalid. Please choose a different name.", sep->arg[2]); + return; + } + NPCType DefaultNPCTypeStruct = CreateDefaultNPCTypeStructForBot(std::string(sep->arg[2]), std::string(), c->GetLevel(), atoi(sep->arg[4]), atoi(sep->arg[3]), gender); Bot* NewBot = new Bot(DefaultNPCTypeStruct, c); @@ -11388,17 +9649,11 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - if(!NewBot->IsBotNameAvailable(&TempErrorMessage)) { - c->Message(0, "The name %s is already being used. Please choose a different name.", NewBot->GetCleanName()); - return; - } - if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return; } - // Now that all validation is complete, we can save our newly created bot if(!NewBot->Save()) c->Message(0, "Unable to save %s as a bot.", NewBot->GetCleanName()); else @@ -11407,15 +9662,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { else { // TODO: Log error message here } - - // Bot creation is complete return; } if(!strcasecmp(sep->arg[1], "help") && !strcasecmp(sep->arg[2], "create") ){ - c->Message(0, "Classes: 1(Warrior), 2(Cleric), 3(Paladin), 4(Ranger), 5(Sk), 6(Druid), 7(Monk), 8(Bard), 9(Rogue), 10(Shaman), 11(Necro), 12(Wiz), 13(Mag), 14(Ench), 15(Beast), 16(Bersek)"); + c->Message(0, "Classes: 1(WAR), 2(CLR), 3(PAL), 4(RNG), 5(SHD), 6(DRU), 7(MNK), 8(BRD), 9(ROG), 10(SHM), 11(NEC), 12(WIZ), 13(MAG), 14(ENC), 15(BST), 16(BER)"); c->Message(0, "------------------------------------------------------------------"); - c->Message(0, "Races: 1(Human), 2(Barb), 3(Erudit), 4(Wood elf), 5(High elf), 6(Dark elf), 7(Half elf), 8(Dwarf), 9(Troll), 10(Ogre), 11(Halfling), 12(Gnome), 128(Iksar), 130(Vah shir), 330(Froglok), 522(Drakkin)"); + c->Message(0, "Races: 1(Human), 2(Barbarian), 3(Erudite), 4(Wood Elf), 5(High Elf), 6(Dark Elf), 7(Half Elf), 8(Dwarf), 9(Troll), 10(Ogre), 11(Halfling), 12(Gnome), 128(Iksar), 130(Vah Shir), 330(Froglok), 522(Drakkin)"); c->Message(0, "------------------------------------------------------------------"); c->Message(0, "Usage: #bot create [name] [class(1-16)] [race(1-12,128,130,330,522)] [gender(male/female)]"); c->Message(0, "Example: #bot create Sneaky 9 6 male"); @@ -11455,7 +9708,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "list")) { bool listAll = true; int iClass = atoi(sep->arg[2]); - if(iClass > 0 && iClass < 17) listAll = false; @@ -11473,8 +9725,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(0, "Name: %s -- Class: %s -- Level: %u -- Race: %s", TempAvailableBotsList->BotName, ClassIdToString(TempAvailableBotsList->BotClass).c_str(), TempAvailableBotsList->BotLevel, RaceIdToString(TempAvailableBotsList->BotRace).c_str()); } - } - else { + } else { c->Message(0, "You have no bots created. Use the #bot create command to create a bot."); } } @@ -11482,34 +9733,25 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "mana")) { bool listAll = false; Bot* bot = 0; - if(sep->argnum == 2) { if(std::string(sep->arg[2]).compare("all") == 0) listAll = true; else { std::string botName = std::string(sep->arg[2]); - Bot* tempBot = entity_list.GetBotByBotName(botName); - - if(tempBot && tempBot->GetBotOwner() == c) { + if(tempBot && tempBot->GetBotOwner() == c) bot = tempBot; - } } - } - else { + } else { if(c->GetTarget() && c->GetTarget()->IsBot()) bot = c->GetTarget()->CastToBot(); } if(bot && !listAll) { - // Specific bot only if(bot->GetClass() != WARRIOR && bot->GetClass() != MONK && bot->GetClass() != BARD && bot->GetClass() != BERSERKER && bot->GetClass() != ROGUE) c->Message(0, "Name: %s -- Class: %s -- Mana: %3.1f%%", bot->GetCleanName(), ClassIdToString(bot->GetClass()).c_str(), bot->GetManaRatio()); - } - else { - // List all + } else { std::list spawnedBots = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - if(!spawnedBots.empty()) { for(std::list::iterator botsListItr = spawnedBots.begin(); botsListItr != spawnedBots.end(); ++botsListItr) { Bot* tempBot = *botsListItr; @@ -11518,12 +9760,10 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(0, "Name: %s -- Class: %s -- Mana: %3.1f%%", tempBot->GetCleanName(), ClassIdToString(tempBot->GetClass()).c_str(), tempBot->GetManaRatio()); } } - } - else { + } else { c->Message(0, "You have no spawned bots in this zone."); } } - return; } @@ -11545,16 +9785,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - /*if(c->GetBotRaidID() > 0) { - BotRaids *br = entity_list.GetBotRaidByMob(c->CastToMob()); - if(br) { - if(br->GetBotRaidAggro()) { - c->Message(15, "You can't summon bots while you are engaged."); - return; - } - } - }*/ - if(c->IsGrouped()) { Group *g = entity_list.GetGroupByClient(c); for (int i=0; iSpawn(c, &TempErrorMessage); if(!TempErrorMessage.empty()) { @@ -11632,10 +9861,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - TempBot->CastToMob()->Say("I am ready for battle."); - } - else { - // We did not find a bot for the specified bot id from the database + TempBot->BotGroupSay(TempBot, "I am ready for battle."); + } else { c->Message(0, "BotID: %i not found", atoi(sep->arg[2])); } @@ -11684,18 +9911,12 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { else if(c->GetTarget()->IsMob() && !c->GetTarget()->IsPet()) { Mob *b = c->GetTarget(); - if(b) - { - // Is our target "botable" ? + if(b) { if(!b->IsBot()){ c->Message(15, "You must target a bot!"); - } - else if((b->CastToBot()->GetBotOwnerCharacterID() != c->CharacterID())) - { - b->Say("You can only summon your own bots."); - } - else - { + } else if((b->CastToBot()->GetBotOwnerCharacterID() != c->CharacterID())) { + b->CastToBot()->BotGroupSay(b->CastToBot(), "You can only summon your own bots."); + } else { b->SetTarget(c->CastToMob()); b->Warp(glm::vec3(c->GetPosition())); } @@ -11716,24 +9937,24 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - const char* equipped[EmuConstants::EQUIPMENT_SIZE] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", + const char* equipped[EmuConstants::EQUIPMENT_SIZE + 1] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", "Left Wrist", "Right Wrist", "Range", "Hands", "Primary Hand", "Secondary Hand", - "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo" }; - + "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo", "Powersource" }; + const ItemInst* inst = nullptr; const ItemData* item = nullptr; bool is2Hweapon = false; - + std::string item_link; Client::TextLink linker; linker.SetLinkType(linker.linkItemInst); - for(int i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; ++i) { + for(int i = EmuConstants::EQUIPMENT_BEGIN; i <= (EmuConstants::EQUIPMENT_END + 1); ++i) { if((i == MainSecondary) && is2Hweapon) { continue; } - inst = b->CastToBot()->GetBotItem(i); + inst = b->CastToBot()->GetBotItem(i == 22 ? 9999 : i); if (inst) item = inst->GetItem(); else @@ -11744,28 +9965,23 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } if(item == nullptr) { - c->Message(15, "I need something for my %s (Item %i)", equipped[i], i); + c->Message(15, "I need something for my %s (Item %i)", equipped[i], (i == 22 ? 9999 : i)); continue; } if((i == MainPrimary) && ((item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HPiercing))) { is2Hweapon = true; } - // I could not find a difference between the criteria positive code and the criteria negative code.. - // ..so, I deleted the check (old criteria: i = { MainCharm, MainRange, MainPrimary, MainSecondary, MainAmmo }) - linker.SetItemInst(inst); item_link = linker.GenerateLink(); - c->Message(15, "Using %s in my %s (Item %i)", item_link.c_str(), equipped[i], i); + c->Message(15, "Using %s in my %s (Item %i)", item_link.c_str(), equipped[i], (i == 22 ? 9999 : i)); } - } - else { + } else { c->Message(15, "You must group your bot first."); } - } - else { + } else { c->Message(15, "You must target a bot first."); } return; @@ -11774,7 +9990,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "inventory") && !strcasecmp(sep->arg[2], "remove")) { if((c->GetTarget() == nullptr) || (sep->arg[3][0] == '\0') || !c->GetTarget()->IsBot()) { - c->Message(15, "Usage: #bot inventory remove [slotid] (You must have a bot targetted) "); + c->Message(15, "Usage: #bot inventory remove [slotid] (You must have a bot targeted) "); return; } else if(c->GetTarget()->IsBot() && c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) @@ -11783,13 +9999,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; int slotId = atoi(sep->arg[3]); - if(slotId > EmuConstants::EQUIPMENT_END || slotId < EmuConstants::EQUIPMENT_BEGIN) { - c->Message(15, "A bot has 21 slots in its inventory, please choose a slot between 0 and 21."); + if((slotId > EmuConstants::EQUIPMENT_END || slotId < EmuConstants::EQUIPMENT_BEGIN) && slotId != 9999) { + c->Message(15, "A bot has 22 slots in its inventory, please choose a slot between 0 and 21 or 9999."); return; } - const char* equipped[EmuConstants::EQUIPMENT_SIZE] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", + const char* equipped[EmuConstants::EQUIPMENT_SIZE + 1] = {"Charm", "Left Ear", "Head", "Face", "Right Ear", "Neck", "Shoulders", "Arms", "Back", "Left Wrist", "Right Wrist", "Range", "Hands", "Primary Hand", "Secondary Hand", - "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo" }; + "Left Finger", "Right Finger", "Chest", "Legs", "Feet", "Waist", "Ammo", "Powersource" }; const ItemData* itm = nullptr; const ItemInst* itminst = c->GetTarget()->CastToBot()->GetBotItem(slotId); @@ -11806,16 +10022,14 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(itminst) { for (int m = AUG_BEGIN; m < EmuConstants::ITEM_COMMON_SIZE; ++m) { ItemInst *itma = itminst->GetAugment(m); - if(itma) - { - if(c->CheckLoreConflict(itma->GetItem())) { + if(itma) { + if(c->CheckLoreConflict(itma->GetItem())) failedLoreCheck = true; - } } } - if(c->CheckLoreConflict(itm)) { + + if(c->CheckLoreConflict(itm)) failedLoreCheck = true; - } } if(!failedLoreCheck) { if(itm) { @@ -11850,16 +10064,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { case MainFinger2: case MainChest: case MainWaist: - //case MainPowerSource: + case MainPowerSource: case MainAmmo: - gearbot->Say("My %s is now unequipped.", equipped[slotId]); + gearbot->BotGroupSay(gearbot, "My %s is now unequipped.", equipped[slotId]); break; case MainShoulders: case MainArms: case MainHands: case MainLegs: case MainFeet: - gearbot->Say("My %s are now unequipped.", equipped[slotId]); + gearbot->BotGroupSay(gearbot, "My %s are now unequipped.", equipped[slotId]); break; default: break; @@ -11883,16 +10097,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { case MainFinger2: case MainChest: case MainWaist: - //case MainPowerSource: + case MainPowerSource: case MainAmmo: - c->GetTarget()->Say("My %s is already unequipped.", equipped[slotId]); + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "My %s is already unequipped.", equipped[slotId]); break; case MainShoulders: case MainArms: case MainHands: case MainLegs: case MainFeet: - c->GetTarget()->Say("My %s are already unequipped.", equipped[slotId]); + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "My %s are already unequipped.", equipped[slotId]); break; default: break; @@ -11907,20 +10121,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } if(!strcasecmp(sep->arg[1], "update")) { - // Congdar: add IsEngaged check for exploit to keep bots alive by repeatedly using #bot update. if((c->GetTarget() != nullptr) && c->GetTarget()->IsBot()) { if(c->GetLevel() <= c->GetTarget()->GetLevel()) { c->Message(15, "This bot has already been updated."); return; } - if(c->IsGrouped()) - { + if(c->IsGrouped()) { Group *g = entity_list.GetGroupByClient(c); - for (int i=0; imembers[i] && g->members[i]->IsEngaged()) - { + for (int i=0; imembers[i] && g->members[i]->IsEngaged()) { c->Message(15, "You can't update bots while you are engaged."); return; } @@ -11929,20 +10139,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if((c->GetTarget()->CastToBot()->GetBotOwner() == c->CastToMob()) && !c->GetFeigned()) { Bot* bot = c->GetTarget()->CastToBot(); - //bot->SetLevel(c->GetLevel()); bot->SetPetChooser(false); bot->CalcBotStats(); - } - else { + } else { if(c->GetFeigned()) { c->Message(15, "You cannot update bots while feigned."); - } - else { + } else { c->Message(15, "You must target your bot first"); } } - } - else { + } else { c->Message(15, "You must target a bot first"); } @@ -11953,25 +10159,23 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "bindme")) { Mob *binder = nullptr; bool hasbinder = false; - if(c->IsGrouped()) - { + if(c->IsGrouped()) { Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC)) - { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC)) { hasbinder = true; binder = g->members[i]; } } - if(!hasbinder) { + + if(!hasbinder) c->Message(15, "You must have a Cleric in your group."); - } } } + if(hasbinder) { - binder->Say("Attempting to bind you %s.", c->GetName()); + binder->CastToBot()->BotGroupSay(binder->CastToBot(), "Attempting to bind you %s.", c->GetName()); binder->CastToNPC()->CastSpell(35, c->GetID(), 1, -1, -1); } return; @@ -11981,49 +10185,41 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "runeme")) { Mob *runeer = nullptr; bool hasruneer = false; - if(c->IsGrouped()) - { + if(c->IsGrouped()) { Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) - { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) { hasruneer = true; runeer = g->members[i]; } } - if(!hasruneer) { + + if(!hasruneer) c->Message(15, "You must have an Enchanter in your group."); - } } } + if(hasruneer) { if (c->GetLevel() <= 12) { - runeer->Say("I need to be level 13 or higher for this..."); - } - else if ((c->GetLevel() >= 13) && (c->GetLevel() <= 21)) { - runeer->Say("Casting Rune I..."); + runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "I need to be level 13 or higher for this..."); + } else if ((c->GetLevel() >= 13) && (c->GetLevel() <= 21)) { + runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune I..."); runeer->CastSpell(481, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() >= 22) && (c->GetLevel() <= 32)) { - runeer->Say("Casting Rune II..."); + } else if ((c->GetLevel() >= 22) && (c->GetLevel() <= 32)) { + runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune II..."); runeer->CastSpell(482, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() >= 33) && (c->GetLevel() <= 39)) { - runeer->Say("Casting Rune III..."); + } else if ((c->GetLevel() >= 33) && (c->GetLevel() <= 39)) { + runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune III..."); runeer->CastSpell(483, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() >= 40) && (c->GetLevel() <= 51)) { - runeer->Say("Casting Rune IV..."); + } else if ((c->GetLevel() >= 40) && (c->GetLevel() <= 51)) { + runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune IV..."); runeer->CastSpell(484, c->GetID(), 1, -1, -1); - } - else if ((c->GetLevel() >= 52) && (c->GetLevel() <= 60)) { - runeer->Say("Casting Rune V..."); + } else if ((c->GetLevel() >= 52) && (c->GetLevel() <= 60)) { + runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune V..."); runeer->CastSpell(1689, c->GetID(), 1, -1, -1); - } - else if (c->GetLevel() >= 61){ - runeer->Say("Casting Rune of Zebuxoruk..."); + } else if (c->GetLevel() >= 61){ + runeer->CastToBot()->BotGroupSay(runeer->CastToBot(), "Casting Rune of Zebuxoruk..."); runeer->CastSpell(3343, c->GetID(), 1, -1, -1); } } @@ -12034,10 +10230,9 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "track") && c->IsGrouped()) { Mob *Tracker; uint32 TrackerClass = 0; - Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case RANGER: @@ -12045,14 +10240,12 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { TrackerClass = RANGER; break; case DRUID: - // Unless we have a ranger, druid is next best. if(TrackerClass != RANGER) { Tracker = g->members[i]; TrackerClass = DRUID; } break; case BARD: - // If we haven't found a tracker yet, use bard. if(TrackerClass == 0) { Tracker = g->members[i]; TrackerClass = BARD; @@ -12065,42 +10258,34 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } int Level = (c->GetLevel()); - int RangeR = (Level*80); //Ranger - int RangeD = (Level*30); //Druid - int RangeB = (Level*20); //Bard + int RangeR = (Level * 80); //Ranger + int RangeD = (Level * 30); //Druid + int RangeB = (Level * 20); //Bard switch(TrackerClass) { case RANGER: if(!strcasecmp(sep->arg[2], "all")) { - Tracker->Say("Tracking everything", c->GetName()); + Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking everything", c->GetName()); entity_list.ShowSpawnWindow(c, RangeR, false); - } - else if(!strcasecmp(sep->arg[2], "rare")) { - Tracker->Say("Selective tracking", c->GetName()); + } else if(!strcasecmp(sep->arg[2], "rare")) { + Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Selective tracking", c->GetName()); entity_list.ShowSpawnWindow(c, RangeR, true); - } - else if(!strcasecmp(sep->arg[2], "near")) { - Tracker->Say("Tracking mobs nearby", c->GetName()); + } else if(!strcasecmp(sep->arg[2], "near")) { + Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking mobs nearby", c->GetName()); entity_list.ShowSpawnWindow(c, RangeD, false); - } - else - Tracker->Say("You want to [track all], [track near], or [track rare]?", c->GetName()); + } else + Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "You want to [track all], [track near], or [track rare]?", c->GetName()); break; - case BARD: - if(TrackerClass != RANGER) - Tracker->Say("Tracking up", c->GetName()); + Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking up", c->GetName()); entity_list.ShowSpawnWindow(c, RangeB, false); break; - case DRUID: - if(TrackerClass = BARD) - Tracker->Say("Tracking up", c->GetName()); + Tracker->CastToBot()->BotGroupSay(Tracker->CastToBot(), "Tracking up", c->GetName()); entity_list.ShowSpawnWindow(c, RangeD, false); break; - default: c->Message(15, "You must have a Ranger, Druid, or Bard in your group."); break; @@ -12114,7 +10299,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 CurerClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case CLERIC: @@ -12126,13 +10311,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Curer = g->members[i]; CurerClass = SHAMAN; } + break; case DRUID: if (CurerClass == 0){ Curer = g->members[i]; CurerClass = DRUID; } break; - break; default: break; } @@ -12141,83 +10326,63 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { switch(CurerClass) { case CLERIC: if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 1)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(1, Curer->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 4)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 4)) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(2, Curer->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "curse") && (c->GetLevel() >= 8)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "curse") && (c->GetLevel() >= 8)) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(3, Curer->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 3)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 3)) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(4, Curer->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "curse") && (c->GetLevel() <= 8) + } else if (!strcasecmp(sep->arg[2], "curse") && (c->GetLevel() <= 8) || !strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() <= 3) || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 4) || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 1)) { - Curer->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Curer->Say("Do you want [cure poison], [cure disease], [cure curse], or [cure blindness]?", c->GetName()); + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have the needed level yet", sep->arg[2]); + } else + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Do you want [cure poison], [cure disease], [cure curse], or [cure blindness]?", c->GetName()); break; - case SHAMAN: if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 2)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(1, Curer->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 1)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 1)) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(2, Curer->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "curse")) { - Curer->Say("I don't have that spell", sep->arg[2]); - } - else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 7)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "curse")) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have that spell", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 7)) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(4, Curer->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() <= 7) + } else if (!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() <= 7) || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 1) || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 2)) { - Curer->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Curer->Say("Do you want [cure poison], [cure disease], or [cure blindness]?", c->GetName()); + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have the needed level yet", sep->arg[2]); + } else + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Do you want [cure poison], [cure disease], or [cure blindness]?", c->GetName()); break; - case DRUID: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 5)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(1, Curer->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 4)) { - Curer->Say("Trying to cure us of %s.", sep->arg[2]); + } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 4)) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Trying to cure us of %s.", sep->arg[2]); Curer->CastToBot()->Bot_Command_Cure(2, Curer->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "curse")) { // Fire level 1 - Curer->Say("I don't have that spell", sep->arg[2]); - } - else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 13)) { - Curer->Say("I don't have that spell", sep->arg[2]); - } - else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 4) + } else if(!strcasecmp(sep->arg[2], "curse")) { // Fire level 1 + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have that spell", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "blindness") && (c->GetLevel() >= 13)) { + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have that spell", sep->arg[2]); + } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 4) || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 5)) { - Curer->Say("I don't have the needed level yet", sep->arg[2]) ; - } - else - Curer->Say("Do you want [cure poison], or [cure disease]?", c->GetName()); - + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "I don't have the needed level yet", sep->arg[2]) ; + } else + Curer->CastToBot()->BotGroupSay(Curer->CastToBot(), "Do you want [cure poison], or [cure disease]?", c->GetName()); break; - default: c->Message(15, "You must have a Cleric, Shaman, or Druid in your group."); break; @@ -12226,300 +10391,240 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } //Mez - if(!strcasecmp(sep->arg[1], "ai") && !strcasecmp(sep->arg[2], "mez")) - { + if(!strcasecmp(sep->arg[1], "ai") && !strcasecmp(sep->arg[2], "mez")) { Mob *target = c->GetTarget(); - if(target == nullptr || target == c || target->IsBot() || (target->IsPet() && target->GetOwner()->IsBot())) - { + if(target == nullptr || target == c || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { c->Message(15, "You must select a monster"); return; } - if(c->IsGrouped()) - { + if(c->IsGrouped()) { bool hasmezzer = false; Group *g = c->GetGroup(); - for(int i=0; imembers[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) - { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g && g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) { hasmezzer = true; Mob *mezzer = g->members[i]; - mezzer->Say("Trying to mez %s \n", target->GetCleanName()); + mezzer->CastToBot()->BotGroupSay(mezzer->CastToBot(), "Trying to mesmerize %s.", target->GetCleanName()); mezzer->CastToBot()->MesmerizeTarget(target); } } - if(!hasmezzer) { + + if(!hasmezzer) c->Message(15, "You must have an Enchanter in your group."); - } } return; } //Lore (Identify item) if(!strcasecmp(sep->arg[1], "lore")) { - if(c->IsGrouped()) - { + if(c->IsGrouped()) { bool hascaster = false; Group *g = c->GetGroup(); - for(int i=0; imembers[i] && g->members[i]->IsBot()) { uint8 casterlevel = g->members[i]->GetLevel(); switch(g->members[i]->GetClass()) { case ENCHANTER: - if(casterlevel >= 15) { + if(casterlevel >= 15) hascaster = true; - } + break; case WIZARD: - if(casterlevel >= 14) { + if(casterlevel >= 14) hascaster = true; - } + break; case NECROMANCER: - if(casterlevel >= 17) { + if(casterlevel >= 17) hascaster = true; - } + break; case MAGICIAN: - if(casterlevel >= 13) { + if(casterlevel >= 13) hascaster = true; - } + break; default: break; } if(hascaster) { - g->members[i]->Say("Trying to Identify your item..."); + g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Trying to Identify your item..."); g->members[i]->CastSpell(305, c->GetID(), 1, -1, -1); break; } } } - if(!hascaster) { + + if(!hascaster) c->Message(15, "You don't see anyone in your group that can cast Identify."); - } - } - else { + } else c->Message(15, "You don't see anyone in your group that can cast Identify."); - } + return; } //Resurrect - if(!strcasecmp(sep->arg[1], "resurrectme")) - { + if(!strcasecmp(sep->arg[1], "resurrectme")) { Mob *target = c->GetTarget(); - if(target == nullptr || !target->IsCorpse()) - { - c->Message(15, "You must select a corpse"); + if(target == nullptr || !target->IsCorpse()) { + c->Message(15, "You must select a corpse!"); return; } - if(c->IsGrouped()) - { + if(c->IsGrouped()) { bool hasrezzer = false; Group *g = c->GetGroup(); - for(int i=0; imembers[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC)) - { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g && g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC)) { hasrezzer = true; Mob *rezzer = g->members[i]; - rezzer->Say("Trying to rez %s", target->GetCleanName()); + rezzer->CastToBot()->BotGroupSay(rezzer->CastToBot(), "Trying to resurrect %s.", target->GetCleanName()); rezzer->CastToBot()->Bot_Command_RezzTarget(target); break; } } - if(!hasrezzer) { - c->Message(15, "You must have a Cleric in your group."); - } - } - else { - c->Message(15, "You must have a Cleric in your group."); - } + + if(!hasrezzer) + c->Message(15, "You must have a Cleric in your group!"); + } else + c->Message(15, "You must have a Cleric in your group!"); + return; } - if(!strcasecmp(sep->arg[1], "magepet")) - { - if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->GetClass() == MAGICIAN)) - { - if(c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) - { + if(!strcasecmp(sep->arg[1], "magepet")) { + if(c->GetTarget() && c->GetTarget()->IsBot() && (c->GetTarget()->GetClass() == MAGICIAN)) { + if(c->GetTarget()->CastToBot()->GetBotOwnerCharacterID() == c->CharacterID()) { int botlevel = c->GetTarget()->GetLevel(); c->GetTarget()->CastToBot()->SetPetChooser(true); - if(botlevel == 1) - { - c->GetTarget()->Say("I don't have any pets yet."); + if(botlevel == 1) { + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have any pets yet."); return; } - if(!strcasecmp(sep->arg[2], "water")) - { + + if(!strcasecmp(sep->arg[2], "water")) { c->GetTarget()->CastToBot()->SetPetChooserID(0); - } - else if(!strcasecmp(sep->arg[2], "fire")) - { - if(botlevel < 3) - { - c->GetTarget()->Say("I don't have that pet yet."); + } else if(!strcasecmp(sep->arg[2], "fire")) { + if(botlevel < 3) { + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); return; - } - else - { + } else c->GetTarget()->CastToBot()->SetPetChooserID(1); - } - } - else if(!strcasecmp(sep->arg[2], "air")) - { - if(botlevel < 4) - { - c->GetTarget()->Say("I don't have that pet yet."); + } else if(!strcasecmp(sep->arg[2], "air")) { + if(botlevel < 4) { + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); return; - } - else - { + } else c->GetTarget()->CastToBot()->SetPetChooserID(2); - } - } - else if(!strcasecmp(sep->arg[2], "earth")) - { - if(botlevel < 5) - { - c->GetTarget()->Say("I don't have that pet yet."); + } else if(!strcasecmp(sep->arg[2], "earth")) { + if(botlevel < 5) { + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); return; - } - else - { + } else c->GetTarget()->CastToBot()->SetPetChooserID(3); - } - } - else if(!strcasecmp(sep->arg[2], "monster")) - { - if(botlevel < 30) - { - c->GetTarget()->Say("I don't have that pet yet."); + } else if(!strcasecmp(sep->arg[2], "monster")) { + if(botlevel < 30) { + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "I don't have that pet yet."); return; - } - else - { + } else c->GetTarget()->CastToBot()->SetPetChooserID(4); - } } - if(c->GetTarget()->GetPet()) - { - // cast reclaim energy + + if(c->GetTarget()->GetPet()) { uint16 id = c->GetTarget()->GetPetID(); c->GetTarget()->SetPetID(0); c->GetTarget()->CastSpell(331, id); } } - } - else - { - c->Message(15, "You must target your Magician bot."); - } + } else + c->Message(15, "You must target your Magician bot!"); + return; } //Summon Corpse if(!strcasecmp(sep->arg[1], "corpse") && !strcasecmp(sep->arg[2], "summon")) { if(c->GetTarget() == nullptr) { - c->Message(15, "You must select player with his corpse in the zone."); + c->Message(15, "You must select player with his corpse in the zone!"); return; } + if(c->IsGrouped()) { bool hassummoner = false; Mob *t = c->GetTarget(); Group *g = c->GetGroup(); int summonerlevel = 0; - for(int i=0; imembers[i] && g->members[i]->IsBot() && ((g->members[i]->GetClass() == NECROMANCER)||(g->members[i]->GetClass() == SHADOWKNIGHT))) { hassummoner = true; summonerlevel = g->members[i]->GetLevel(); g->members[i]->InterruptSpell(); if(!t->IsClient()) { - g->members[i]->Say("You have to target a player with a corpse in the zone"); + g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "You have to target a player with a corpse in the zone!"); return; - } - else { + } else { g->members[i]->SetTarget(t); - if(summonerlevel < 12) { - g->members[i]->Say("I don't have that spell yet."); - } - else if((summonerlevel > 11) && (summonerlevel < 35)) { - g->members[i]->Say("Attempting to summon %s\'s corpse.", t->GetCleanName()); + g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "I don't have that spell yet."); + } else if((summonerlevel > 11) && (summonerlevel < 35)) { + g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Attempting to summon %s\'s corpse.", t->GetCleanName()); g->members[i]->CastSpell(2213, t->GetID(), 1, -1, -1); return; - } - else if((summonerlevel > 34) && (summonerlevel < 71)) { - g->members[i]->Say("Attempting to summon %s\'s corpse.", t->GetCleanName()); + } else if((summonerlevel > 34) && (summonerlevel < 71)) { + g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Attempting to summon %s\'s corpse.", t->GetCleanName()); g->members[i]->CastSpell(3, t->GetID(), 1, -1, -1); return; - } - else if(summonerlevel > 70) { - g->members[i]->Say("Attempting to summon %s\'s corpse.", t->GetCleanName()); + } else if(summonerlevel > 70) { + g->members[i]->CastToBot()->BotGroupSay(g->members[i]->CastToBot(), "Attempting to summon %s\'s corpse.", t->GetCleanName()); g->members[i]->CastSpell(10042, t->GetID(), 1, -1, -1); return; } } } } - if (!hassummoner) { - c->Message(15, "You must have a Necromancer or Shadowknight in your group."); - } + + if (!hassummoner) + c->Message(15, "You must have a Necromancer or Shadow Knight in your group."); + return; } } //Pacify - if(!strcasecmp(sep->arg[1], "target") && !strcasecmp(sep->arg[2], "calm")) - { + if(!strcasecmp(sep->arg[1], "target") && !strcasecmp(sep->arg[2], "calm")) { Mob *target = c->GetTarget(); - - if(target == nullptr || target->IsClient() || target->IsBot() || target->IsPet() && target->GetOwner()->IsBot()) - c->Message(15, "You must select a monster"); + if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) + c->Message(15, "You must select a monster!"); else { if(c->IsGrouped()) { Group *g = c->GetGroup(); - - for(int i=0; imembers[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == ENCHANTER)) { Bot *pacer = g->members[i]->CastToBot(); - pacer->Say("Trying to pacify %s \n", target->GetCleanName()); - + pacer->BotGroupSay(pacer, "Trying to pacify %s.", target->GetCleanName()); if(pacer->Bot_Command_CalmTarget(target)) { if(target->FindType(SE_Lull) || target->FindType(SE_Harmony) || target->FindType(SE_InstantHate)) - //if(pacer->IsPacified(target)) c->Message(0, "I have successfully pacified %s.", target->GetCleanName()); - return; - /*else - c->Message(0, "I failed to pacify %s.", target->GetCleanName());*/ - } - else + + return; + } else c->Message(0, "I failed to pacify %s.", target->GetCleanName()); } // seperated cleric and chanter so chanter is primary if(g && g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == CLERIC) && (GroupHasEnchanterClass(g) == false)) { Bot *pacer = g->members[i]->CastToBot(); - pacer->Say("Trying to pacify %s \n", target->GetCleanName()); + pacer->BotGroupSay(pacer, "Trying to pacify %s.", target->GetCleanName()); if(pacer->Bot_Command_CalmTarget(target)) { if(target->FindType(SE_Lull) || target->FindType(SE_Harmony) || target->FindType(SE_InstantHate)) - //if(pacer->IsPacified(target)) c->Message(0, "I have successfully pacified %s.", target->GetCleanName()); - return; - /*else - c->Message(0, "I failed to pacify %s.", target->GetCleanName());*/ - } - else + + return; + } else c->Message(0, "I failed to pacify %s.", target->GetCleanName()); } - /*else - c->Message(15, "You must have an Enchanter or Cleric in your group.");*/ } } } @@ -12528,20 +10633,19 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } //Charm - if(!strcasecmp(sep->arg[1], "charm")) - { + if(!strcasecmp(sep->arg[1], "charm")) { Mob *target = c->GetTarget(); - if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner()->IsBot())) - { - c->Message(15, "You must select a monster"); + if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { + c->Message(15, "You must select a monster!"); return; } + uint32 DBtype = c->GetTarget()->GetBodyType(); Mob *Charmer; uint32 CharmerClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case ENCHANTER: @@ -12553,13 +10657,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Charmer = g->members[i]; CharmerClass = NECROMANCER; } + break; case DRUID: if (CharmerClass == 0){ Charmer = g->members[i]; CharmerClass = DRUID; } break; - break; default: break; } @@ -12568,42 +10672,33 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { switch(CharmerClass) { case ENCHANTER: if (c->GetLevel() >= 11) { - Charmer->Say("Trying to charm %s \n", target->GetCleanName(), sep->arg[2]); + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Trying to charm %s.", target->GetCleanName(), sep->arg[2]); Charmer->CastToBot()->Bot_Command_CharmTarget (1,target); - } - else if (c->GetLevel() <= 10){ - Charmer->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Charmer->Say("Mob level is too high or can't be charmed", c->GetName()); + } else if (c->GetLevel() <= 10){ + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "I don't have the required level yet.", sep->arg[2]); + } else + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Mob level is too high or can't be charmed.", c->GetName()); break; - case NECROMANCER: if ((c->GetLevel() >= 18) && (DBtype == 3)) { - Charmer->Say("Trying to Charm %s \n", target->GetCleanName(), sep->arg[2]); + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Trying to charm %s.", target->GetCleanName(), sep->arg[2]); Charmer->CastToBot()->Bot_Command_CharmTarget (2,target); - } - else if (c->GetLevel() <= 17){ - Charmer->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Charmer->Say("Mob Is not undead...", c->GetName()); + } else if (c->GetLevel() <= 17){ + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "I don't have the required level yet.", sep->arg[2]); + } else + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Mob is not undead.", c->GetName()); break; - case DRUID: if ((c->GetLevel() >= 13) && (DBtype == 21)) { - Charmer->Say("Trying to charm %s \n", target->GetCleanName(), sep->arg[2]); + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Trying to charm %s.", target->GetCleanName(), sep->arg[2]); Charmer->CastToBot()->Bot_Command_CharmTarget (3,target); - } - else if (c->GetLevel() <= 12){ - Charmer->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Charmer->Say("Mob is not an animal...", c->GetName()); + } else if (c->GetLevel() <= 12){ + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "I don't have the required level yet.", sep->arg[2]); + } else + Charmer->CastToBot()->BotGroupSay(Charmer->CastToBot(), "Mob is not an animal.", c->GetName()); break; - default: - c->Message(15, "You must have an Enchanter, Necromancer or Druid in your group."); + c->Message(15, "You must have an Enchanter, Necromancer, or Druid in your group."); break; } } @@ -12616,36 +10711,28 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { ((c->GetTarget()->GetClass() == NECROMANCER) || (c->GetTarget()->GetClass() == ENCHANTER) || (c->GetTarget()->GetClass() == DRUID))) { if(c->GetTarget()->CastToBot()->IsBotCharmer()) { c->GetTarget()->CastToBot()->SetBotCharmer(false); - c->GetTarget()->Say("Using a summoned pet."); - } - else { - if(c->GetTarget()->GetPet()) - { + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "Using a summoned pet."); + } else { + if(c->GetTarget()->GetPet()) { c->GetTarget()->GetPet()->Say_StringID(PET_GETLOST_STRING); - // c->GetTarget()->GetPet()->Kill(); c->GetTarget()->GetPet()->Depop(false); c->GetTarget()->SetPetID(0); } c->GetTarget()->CastToBot()->SetBotCharmer(true); - c->GetTarget()->Say("Available for Dire Charm command."); + c->GetTarget()->CastToBot()->BotGroupSay(c->GetTarget()->CastToBot(), "Available for Dire Charm command."); } - } - else { + } else c->Message(15, "You must target your Enchanter, Necromancer, or Druid bot."); - } - } - else { + } else c->Message(15, "You must target an Enchanter, Necromancer, or Druid bot."); - } + return; } //Dire Charm - if(!strcasecmp(sep->arg[1], "Dire") && !strcasecmp(sep->arg[2], "Charm")) - { + if(!strcasecmp(sep->arg[1], "Dire") && !strcasecmp(sep->arg[2], "Charm")) { Mob *target = c->GetTarget(); - if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner()->IsBot())) - { + if(target == nullptr || target->IsClient() || target->IsBot() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsBot())) { c->Message(15, "You must select a monster"); return; } @@ -12654,7 +10741,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 DirerClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case ENCHANTER: @@ -12666,13 +10753,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Direr = g->members[i]; DirerClass = NECROMANCER; } + break; case DRUID: if (DirerClass == 0){ Direr = g->members[i]; DirerClass = DRUID; } break; - break; default: break; } @@ -12681,42 +10768,33 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { switch(DirerClass) { case ENCHANTER: if (c->GetLevel() >= 55) { - Direr->Say("Trying to dire charm %s \n", target->GetCleanName(), sep->arg[2]); + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Trying to dire charm %s.", target->GetCleanName(), sep->arg[2]); Direr->CastToBot()->Bot_Command_DireTarget (1,target); - } - else if (c->GetLevel() <= 55){ - Direr->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Direr->Say("Mob level is too high or can't be charmed", c->GetName()); + } else if (c->GetLevel() <= 55){ + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "I don't have the required level yet.", sep->arg[2]); + } else + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Mob level is too high or can't be charmed.", c->GetName()); break; - case NECROMANCER: if ((c->GetLevel() >= 55) && (DBtype == 3)) { - Direr->Say("Trying to dire charm %s \n", target->GetCleanName(), sep->arg[2]); + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Trying to dire charm %s.", target->GetCleanName(), sep->arg[2]); Direr->CastToBot()->Bot_Command_DireTarget (2,target); - } - else if (c->GetLevel() <= 55){ - Direr->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Direr->Say("Mob Is not undead...", c->GetName()); + } else if (c->GetLevel() <= 55){ + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "I don't have the required level yet.", sep->arg[2]); + } else + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Mob is not undead.", c->GetName()); break; - case DRUID: if ((c->GetLevel() >= 55) && (DBtype == 21)) { - Direr->Say("Trying to dire charm %s \n", target->GetCleanName(), sep->arg[2]); + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Trying to dire charm %s.", target->GetCleanName(), sep->arg[2]); Direr->CastToBot()->Bot_Command_DireTarget (3,target); - } - else if (c->GetLevel() <= 55){ - Direr->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Direr->Say("Mob is not an animal...", c->GetName()); + } else if (c->GetLevel() <= 55){ + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "I don't have the required level yet.", sep->arg[2]); + } else + Direr->CastToBot()->BotGroupSay(Direr->CastToBot(), "Mob is not an animal.", c->GetName()); break; - default: - c->Message(15, "You must have an Enchanter, Necromancer or Druid in your group."); + c->Message(15, "You must have an Enchanter, Necromancer, or Druid in your group."); break; } } @@ -12726,31 +10804,27 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "evac")) { Mob *evac = nullptr; bool hasevac = false; - if(c->IsGrouped()) - { + if(c->IsGrouped()) { Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == DRUID)) - || (g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == WIZARD))) - { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if((g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == DRUID)) || (g->members[i] && g->members[i]->IsBot() && (g->members[i]->GetClass() == WIZARD))) { hasevac = true; evac = g->members[i]; } } - if(!hasevac) { + + if(!hasevac) c->Message(15, "You must have a Druid in your group."); - } } } + if((hasevac) && (c->GetLevel() >= 18)) { - evac->Say("Attempting to Evac you %s.", c->GetName()); + evac->CastToBot()->BotGroupSay(evac->CastToBot(), "Attempting to evacuate you, %s.", c->GetName()); evac->CastToClient()->CastSpell(2183, c->GetID(), 1, -1, -1); - } - else if((hasevac) && (c->GetLevel() <= 17)) { - evac->Say("I'm not level 18 yet.", c->GetName()); - } + } else if((hasevac) && (c->GetLevel() <= 17)) + evac->CastToBot()->BotGroupSay(evac->CastToBot(), "I'm not level 18 yet.", c->GetName()); + return; } @@ -12760,7 +10834,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 SowerClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case DRUID: @@ -12792,92 +10866,69 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } switch(SowerClass) { case DRUID: - if ((!strcasecmp(sep->arg[2], "regular")) && (zone->CanCastOutdoor()) && (c->GetLevel() >= 10) ) { - Sower->Say("Casting sow..."); + if ((!strcasecmp(sep->arg[2], "regular")) && (zone->CanCastOutdoor()) && (c->GetLevel() >= 10)) { + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting sow..."); Sower->CastSpell(278, c->GetID(), 1, -1, -1); } - else if ((!strcasecmp(sep->arg[2], "regular")) && (zone->CanCastOutdoor()) && (c->GetLevel() <= 10) ) { - Sower->Say("I'm not level 10 yet."); - } + else if ((!strcasecmp(sep->arg[2], "regular")) && (zone->CanCastOutdoor()) && (c->GetLevel() <= 10)) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 10 yet."); else if ((!strcasecmp(sep->arg[2], "wolf")) && zone->CanCastOutdoor() && (c->GetLevel() >= 20)) { - Sower->Say("Casting group wolf..."); + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting group wolf..."); Sower->CastSpell(428, c->GetID(), 1, -1, -1); } - else if ((!strcasecmp(sep->arg[2], "wolf")) && (c->GetLevel() <= 20)) { - Sower->Say("I'm not level 20 yet."); - } + else if ((!strcasecmp(sep->arg[2], "wolf")) && (c->GetLevel() <= 20)) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 20 yet."); else if ((!strcasecmp(sep->arg[2], "feral")) && (c->GetLevel() >= 50)) { - Sower->Say("Casting Feral Pack..."); + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Feral Pack..."); Sower->CastSpell(4058, c->GetID(), 1, -1, -1); } - else if ((!strcasecmp(sep->arg[2], "feral")) && (c->GetLevel() <= 50)) { - Sower->Say("I'm not level 50 yet."); - } + else if ((!strcasecmp(sep->arg[2], "feral")) && (c->GetLevel() <= 50)) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 50 yet."); else if ((!strcasecmp(sep->arg[2], "shrew")) && (c->GetLevel() >= 35)) { - Sower->Say("Casting Pack Shrew..."); + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Pack Shrew..."); Sower->CastSpell(4055, c->GetID(), 1, -1, -1); } - else if ((!strcasecmp(sep->arg[2], "wolf")) && (c->GetLevel() <= 35)) { - Sower->Say("I'm not level 35 yet."); - } - else if ((!zone->CanCastOutdoor()) && (!strcasecmp(sep->arg[2], "regular")) || - (!zone->CanCastOutdoor()) && (!strcasecmp(sep->arg[2], "wolf"))) { - Sower->Say("I can't cast this spell indoors, try [sow shrew] if you're 35 or higher, or [sow feral] if you're 50 or higher,", c->GetName()); - } - else if (!zone->CanCastOutdoor()) { - Sower->Say("I can't cast this spell indoors, try [sow shrew] if you're 35 or higher, or [sow feral] if you're 50 or higher,", c->GetName()); - } - else if (zone->CanCastOutdoor()) { - Sower->Say("Do you want [sow regular] or [sow wolf]?", c->GetName()); - } - else if (!zone->CanCastOutdoor()) { - Sower->Say("I can't cast this spell indoors, try [sow shrew] if you're 35 or higher, or [sow feral] if you're 50 or higher,", c->GetName()); - } + else if ((!strcasecmp(sep->arg[2], "wolf")) && (c->GetLevel() <= 35)) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 35 yet."); + else if ((!zone->CanCastOutdoor()) && (!strcasecmp(sep->arg[2], "regular")) || (!zone->CanCastOutdoor()) && (!strcasecmp(sep->arg[2], "wolf"))) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors, try [sow shrew] if you're 35 or higher, or [sow feral] if you're 50 or higher."); + else if (!zone->CanCastOutdoor()) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors, try [sow shrew] if you're 35 or higher, or [sow feral] if you're 50 or higher."); + else if (zone->CanCastOutdoor()) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Do you want [sow regular] or [sow wolf]?"); + else if (!zone->CanCastOutdoor()) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors, try [sow shrew] if you're 35 or higher, or [sow feral] if you're 50 or higher."); break; - case SHAMAN: - if ((zone->CanCastOutdoor()) && (c->GetLevel() >= 9)) { - Sower->Say("Casting SoW..."); + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Spirit of Wolf."); Sower->CastToClient()->CastSpell(278, c->GetID(), 1, -1, -1); } - else if (!zone->CanCastOutdoor()) { - Sower->Say("I can't cast this spell indoors", c->GetName()); - } - else if (c->GetLevel() <= 9) { - Sower->Say("I'm not level 9 yet."); - } + else if (!zone->CanCastOutdoor()) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors."); + else if (c->GetLevel() <= 9) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 9 yet."); break; - case RANGER: - if ((zone->CanCastOutdoor()) && (c->GetLevel() >= 28)){ - Sower->Say("Casting SoW..."); + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Spirit of Wolf."); Sower->CastToClient()->CastSpell(278, c->GetID(), 1, -1, -1); } - else if (!zone->CanCastOutdoor()) { - Sower->Say("I can't cast this spell indoors", c->GetName()); - } - else if (c->GetLevel() <= 28) { - Sower->Say("I'm not level 28 yet."); - } + else if (!zone->CanCastOutdoor()) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors."); + else if (c->GetLevel() <= 28) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 28 yet."); break; - case BEASTLORD: - if((zone->CanCastOutdoor()) && (c->GetLevel() >= 24)) { - Sower->Say("Casting SoW..."); + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "Casting Spirit of Wolf."); Sower->CastToClient()->CastSpell(278, c->GetID(), 1, -1, -1); } - else if (!zone->CanCastOutdoor()) { - Sower->Say("I can't cast this spell indoors", c->GetName()); - } - else if (c->GetLevel() <= 24) { - Sower->Say("I'm not level 24 yet."); - } + else if (!zone->CanCastOutdoor()) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I can't cast this spell indoors."); + else if (c->GetLevel() <= 24) + Sower->CastToBot()->BotGroupSay(Sower->CastToBot(), "I'm not level 24 yet."); break; - - default: c->Message(15, "You must have a Druid, Shaman, Ranger, or Beastlord in your group."); break; @@ -12891,12 +10942,11 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 ShrinkerClass = 0; Group *g = c->GetGroup(); Mob *target = c->GetTarget(); - if(target == nullptr || (!target->IsClient() && (c->GetTarget()->CastToBot()->GetBotOwner() != c))) c->Message(15, "You must select a player or bot you own"); else if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case SHAMAN: @@ -12916,27 +10966,21 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } switch(ShrinkerClass) { case SHAMAN: - if (c->GetLevel() >= 15) { - Shrinker->Say("Casting Shrink..."); + Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "Casting Shrink."); Shrinker->CastToBot()->SpellOnTarget(345, target); } - else if (c->GetLevel() <= 14) { - Shrinker->Say("I'm not level 15 yet."); - } + else if (c->GetLevel() <= 14) + Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "I'm not level 15 yet."); break; - case BEASTLORD: - if (c->GetLevel() >= 23) { - Shrinker->Say("Casting Shrink..."); + Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "Casting Shrink."); Shrinker->CastToBot()->SpellOnTarget(345, target); } - else if (c->GetLevel() <= 22) { - Shrinker->Say("I'm not level 23 yet."); - } + else if (c->GetLevel() <= 22) + Shrinker->CastToBot()->BotGroupSay(Shrinker->CastToBot(), "I'm not level 23 yet."); break; - default: c->Message(15, "You must have a Shaman or Beastlord in your group."); break; @@ -12950,7 +10994,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 GaterClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case DRUID: @@ -12971,106 +11015,81 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { switch(GaterClass) { case DRUID: if ((!strcasecmp(sep->arg[2], "karana")) && (c->GetLevel() >= 25) ) { - Gater->Say("Casting Circle of Karana..."); + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Karana."); Gater->CastSpell(550, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "commons")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting Circle of Commons..."); + } else if ((!strcasecmp(sep->arg[2], "commons")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Commons."); Gater->CastSpell(551, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "tox")) && (c->GetLevel() >= 25)) { - Gater->Say("Casting Circle of Toxxulia..."); + } else if ((!strcasecmp(sep->arg[2], "tox")) && (c->GetLevel() >= 25)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Toxxulia."); Gater->CastSpell(552, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "butcher")) && (c->GetLevel() >= 25)) { - Gater->Say("Casting Circle of Butcherblock..."); + } else if ((!strcasecmp(sep->arg[2], "butcher")) && (c->GetLevel() >= 25)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Butcherblock."); Gater->CastSpell(553, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "lava")) && (c->GetLevel() >= 30)) { - Gater->Say("Casting Circle of Lavastorm..."); + } else if ((!strcasecmp(sep->arg[2], "lava")) && (c->GetLevel() >= 30)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Lavastorm."); Gater->CastSpell(554, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "ro")) && (c->GetLevel() >= 32)) { - Gater->Say("Casting Circle of Ro..."); + } else if ((!strcasecmp(sep->arg[2], "ro")) && (c->GetLevel() >= 32)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Ro."); Gater->CastSpell(555, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "feerrott")) && (c->GetLevel() >= 32)) { - Gater->Say("Casting Circle of feerrott..."); + } else if ((!strcasecmp(sep->arg[2], "feerrott")) && (c->GetLevel() >= 32)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Feerrott."); Gater->CastSpell(556, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "steamfont")) && (c->GetLevel() >= 31)) { - Gater->Say("Casting Circle of Steamfont..."); + } else if ((!strcasecmp(sep->arg[2], "steamfont")) && (c->GetLevel() >= 31)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Steamfont."); Gater->CastSpell(557, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "misty")) && (c->GetLevel() >= 36)) { - Gater->Say("Casting Circle of Misty..."); + } else if ((!strcasecmp(sep->arg[2], "misty")) && (c->GetLevel() >= 36)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Misty."); Gater->CastSpell(558, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "wakening")) && (c->GetLevel() >= 40)) { - Gater->Say("Casting Circle of Wakening Lands..."); + } else if ((!strcasecmp(sep->arg[2], "wakening")) && (c->GetLevel() >= 40)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Wakening Lands."); Gater->CastSpell(1398, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "iceclad")) && (c->GetLevel() >= 32)) { - Gater->Say("Casting Circle of Iceclad Ocean..."); + } else if ((!strcasecmp(sep->arg[2], "iceclad")) && (c->GetLevel() >= 32)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Iceclad Ocean."); Gater->CastSpell(1434, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "divide")) && (c->GetLevel() >= 36)) { - Gater->Say("Casting Circle of The Great Divide..."); + } else if ((!strcasecmp(sep->arg[2], "divide")) && (c->GetLevel() >= 36)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of The Great Divide."); Gater->CastSpell(1438, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "cobalt")) && (c->GetLevel() >= 42)) { - Gater->Say("Casting Circle of Cobalt Scar..."); + } else if ((!strcasecmp(sep->arg[2], "cobalt")) && (c->GetLevel() >= 42)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Cobalt Scar."); Gater->CastSpell(1440, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "combines")) && (c->GetLevel() >= 33)) { - Gater->Say("Casting Circle of The Combines..."); + } else if ((!strcasecmp(sep->arg[2], "combines")) && (c->GetLevel() >= 33)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of The Combines."); Gater->CastSpell(1517, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "surefall")) && (c->GetLevel() >= 26)) { - Gater->Say("Casting Circle of Surefall Glade..."); + } else if ((!strcasecmp(sep->arg[2], "surefall")) && (c->GetLevel() >= 26)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Surefall Glade."); Gater->CastSpell(2020, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "grimling")) && (c->GetLevel() >= 29)) { - Gater->Say("Casting Circle of Grimling Forest..."); + } else if ((!strcasecmp(sep->arg[2], "grimling")) && (c->GetLevel() >= 29)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Grimling Forest."); Gater->CastSpell(2419, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "twilight")) && (c->GetLevel() >= 33)) { - Gater->Say("Casting Circle of Twilight..."); + } else if ((!strcasecmp(sep->arg[2], "twilight")) && (c->GetLevel() >= 33)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Twilight."); Gater->CastSpell(2424, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "dawnshroud")) && (c->GetLevel() >= 37)) { - Gater->Say("Casting Circle of Dawnshroud..."); + } else if ((!strcasecmp(sep->arg[2], "dawnshroud")) && (c->GetLevel() >= 37)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Dawnshroud."); Gater->CastSpell(2429, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "nexus")) && (c->GetLevel() >= 26)) { - Gater->Say("Casting Circle of The Nexus..."); + } else if ((!strcasecmp(sep->arg[2], "nexus")) && (c->GetLevel() >= 26)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of The Nexus."); Gater->CastSpell(2432, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "pok")) && (c->GetLevel() >= 38)) { - Gater->Say("Casting Circle of Knowledge..."); + } else if ((!strcasecmp(sep->arg[2], "pok")) && (c->GetLevel() >= 38)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Knowledge."); Gater->CastSpell(3184, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "stonebrunt")) && (c->GetLevel() >= 28)) { - Gater->Say("Casting Circle of Stonebrunt Mountains..."); + } else if ((!strcasecmp(sep->arg[2], "stonebrunt")) && (c->GetLevel() >= 28)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Stonebrunt Mountains."); Gater->CastSpell(3792, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "bloodfields")) && (c->GetLevel() >= 55)) { - Gater->Say("Casting Circle of Bloodfields..."); + } else if ((!strcasecmp(sep->arg[2], "bloodfields")) && (c->GetLevel() >= 55)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Bloodfields."); Gater->CastSpell(6184, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "emerald")) && (c->GetLevel() >= 39)) { - Gater->Say("Casting Wind of the South..."); + } else if ((!strcasecmp(sep->arg[2], "emerald")) && (c->GetLevel() >= 39)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Wind of the South."); Gater->CastSpell(1737, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "skyfire")) && (c->GetLevel() >= 44)) { - Gater->Say("Casting Wind of the North..."); + } else if ((!strcasecmp(sep->arg[2], "skyfire")) && (c->GetLevel() >= 44)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Wind of the North."); Gater->CastSpell(1736, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "slaughter")) && (c->GetLevel() >= 64)) { - Gater->Say("Casting Circle of Slaughter..."); + } else if ((!strcasecmp(sep->arg[2], "slaughter")) && (c->GetLevel() >= 64)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Circle of Slaughter."); Gater->CastSpell(6179, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "karana") + } else if ((!strcasecmp(sep->arg[2], "karana") || !strcasecmp(sep->arg[2], "tox") || !strcasecmp(sep->arg[2], "butcher") && (c->GetLevel() <= 25)) || !strcasecmp(sep->arg[2], "commons") && (c->GetLevel() <= 27) @@ -13095,112 +11114,84 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { || !strcasecmp(sep->arg[2], "emerald") && (c->GetLevel() <= 38) || !strcasecmp(sep->arg[2], "skyfire") && (c->GetLevel() <= 43) || !strcasecmp(sep->arg[2], "wos") && (c->GetLevel() <= 64)) { - Gater->Say("I don't have the needed level yet", sep->arg[2]); - } - else { - Gater->Say("With the proper level I can [gate] to [karana],[commons],[tox],[butcher],[lava],[ro],[feerrott],[steamfont],[misty],[wakening],[iceclad],[divide],[cobalt],[combines],[surefall],[grimling],[twilight],[dawnshroud],[nexus],[pok],[stonebrunt],[bloodfields],[emerald],[skyfire] or [wos].", c->GetName()); - } + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "I don't have the needed level yet."); + } else + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "With the proper level I can [gate] to [karana], [commons], [tox], [butcher], [lava], [ro], [feerrott], [steamfont], [misty], [wakening], [iceclad], [divide], [cobalt], [combines], [surefall], [grimling], [twilight], [dawnshroud], [nexus], [pok], [stonebrunt], [bloodfields], [emerald], [skyfire] or [wos]."); break; - case WIZARD: - if ((!strcasecmp(sep->arg[2], "commons")) && (c->GetLevel() >= 35) ) { - Gater->Say("Casting Common Portal..."); + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Common Portal."); Gater->CastSpell(566, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "fay")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting Fay Portal..."); + } else if ((!strcasecmp(sep->arg[2], "fay")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Fay Portal."); Gater->CastSpell(563, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "ro")) && (c->GetLevel() >= 37)) { - Gater->Say("Casting Ro Portal..."); + } else if ((!strcasecmp(sep->arg[2], "ro")) && (c->GetLevel() >= 37)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Ro Portal."); Gater->CastSpell(567, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "tox")) && (c->GetLevel() >= 25)) { - Gater->Say("Casting Toxxula Portal..."); + } else if ((!strcasecmp(sep->arg[2], "tox")) && (c->GetLevel() >= 25)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Toxxulia Portal."); Gater->CastSpell(561, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "nk")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting North Karana Portal..."); + } else if ((!strcasecmp(sep->arg[2], "nk")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting North Karana Portal."); Gater->CastSpell(562, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "nek")) && (c->GetLevel() >= 32)) { - Gater->Say("Casting Nektulos Portal..."); + } else if ((!strcasecmp(sep->arg[2], "nek")) && (c->GetLevel() >= 32)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Nektulos Portal."); Gater->CastSpell(564, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "wakening")) && (c->GetLevel() >= 43)) { - Gater->Say("Casting Wakening Lands Portal..."); + } else if ((!strcasecmp(sep->arg[2], "wakening")) && (c->GetLevel() >= 43)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Wakening Lands Portal."); Gater->CastSpell(1399, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "iceclad")) && (c->GetLevel() >= 33)) { - Gater->Say("Casting Iceclad Ocean Portal..."); + } else if ((!strcasecmp(sep->arg[2], "iceclad")) && (c->GetLevel() >= 33)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Iceclad Ocean Portal."); Gater->CastSpell(1418, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "divide")) && (c->GetLevel() >= 36)) { - Gater->Say("Casting Great Divide Portal..."); + } else if ((!strcasecmp(sep->arg[2], "divide")) && (c->GetLevel() >= 36)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Great Divide Portal."); Gater->CastSpell(1423, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "cobalt")) && (c->GetLevel() >= 43)) { - Gater->Say("Casting Cobalt Scar Portal..."); + } else if ((!strcasecmp(sep->arg[2], "cobalt")) && (c->GetLevel() >= 43)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Cobalt Scar Portal."); Gater->CastSpell(1425, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "combines")) && (c->GetLevel() >= 34)) { - Gater->Say("Casting Combines Portal..."); + } else if ((!strcasecmp(sep->arg[2], "combines")) && (c->GetLevel() >= 34)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Combines Portal."); Gater->CastSpell(1516, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "wk")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting West Karana Portal..."); + } else if ((!strcasecmp(sep->arg[2], "wk")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting West Karana Portal."); Gater->CastSpell(568, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "twilight")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting Twilight Portal..."); + } else if ((!strcasecmp(sep->arg[2], "twilight")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Twilight Portal."); Gater->CastSpell(2425, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "dawnshroud")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting Dawnshroud Portal..."); + } else if ((!strcasecmp(sep->arg[2], "dawnshroud")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Dawnshroud Portal."); Gater->CastSpell(2430, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "nexus")) && (c->GetLevel() >= 29)) { - Gater->Say("Casting Nexus Portal..."); + } else if ((!strcasecmp(sep->arg[2], "nexus")) && (c->GetLevel() >= 29)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Nexus Portal."); Gater->CastSpell(2944, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "pok")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting Plane of Knowledge Portal..."); + } else if ((!strcasecmp(sep->arg[2], "pok")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Plane of Knowledge Portal."); Gater->CastSpell(3180, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "wos")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting Wall of Slaughter Portal..."); + } else if ((!strcasecmp(sep->arg[2], "wos")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Wall of Slaughter Portal."); Gater->CastSpell(6178, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "grimling")) && (c->GetLevel() >= 29)) { - Gater->Say("Casting Fay Portal..."); + } else if ((!strcasecmp(sep->arg[2], "grimling")) && (c->GetLevel() >= 29)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Fay Portal."); Gater->CastSpell(2420, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "emerald")) && (c->GetLevel() >= 37)) { - Gater->Say("Porting to Emerald Jungle..."); + } else if ((!strcasecmp(sep->arg[2], "emerald")) && (c->GetLevel() >= 37)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Porting to Emerald Jungle."); Gater->CastSpell(1739, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "hateplane")) && (c->GetLevel() >= 39)) { - Gater->Say("Porting to Hate Plane..."); + } else if ((!strcasecmp(sep->arg[2], "hateplane")) && (c->GetLevel() >= 39)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Porting to Hate Plane."); Gater->CastSpell(666, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "airplane")) && (c->GetLevel() >= 39)) { - Gater->Say("Porting to airplane..."); + } else if ((!strcasecmp(sep->arg[2], "airplane")) && (c->GetLevel() >= 39)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Porting to Airplane."); Gater->CastSpell(674, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "skyfire")) && (c->GetLevel() >= 36)) { - Gater->Say("Porting to Skyfire..."); + } else if ((!strcasecmp(sep->arg[2], "skyfire")) && (c->GetLevel() >= 36)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Porting to Skyfire."); Gater->CastSpell(1738, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "bloodfields")) && (c->GetLevel() >= 55)) { - Gater->Say("Casting Bloodfields Portal..."); + } else if ((!strcasecmp(sep->arg[2], "bloodfields")) && (c->GetLevel() >= 55)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Bloodfields Portal."); Gater->CastSpell(6183, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "stonebrunt")) && (c->GetLevel() >= 27)) { - Gater->Say("Casting Stonebrunt Portal..."); + } else if ((!strcasecmp(sep->arg[2], "stonebrunt")) && (c->GetLevel() >= 27)) { + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "Casting Stonebrunt Portal."); Gater->CastSpell(3793, c->GetID(), 1, -1, -1); - } - else if ((!strcasecmp(sep->arg[2], "commons") && (c->GetLevel() <= 35)) + } else if ((!strcasecmp(sep->arg[2], "commons") && (c->GetLevel() <= 35)) || !strcasecmp(sep->arg[2], "fay") && (c->GetLevel() <= 27) || (!strcasecmp(sep->arg[2], "ro") && (c->GetLevel() <= 37)) || !strcasecmp(sep->arg[2], "tox") && (c->GetLevel() <= 25) @@ -13224,11 +11215,9 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { || !strcasecmp(sep->arg[2], "emerald") && (c->GetLevel() <= 36) || !strcasecmp(sep->arg[2], "skyfire") && (c->GetLevel() <= 36) || !strcasecmp(sep->arg[2], "wos") && (c->GetLevel() <= 64)) { - Gater->Say("I don't have the needed level yet", sep->arg[2]); - } - else { - Gater->Say("With the proper level I can [gate] to [commons],[fay],[ro],[tox],[nk],[wakening],[iceclad],[divide],[cobalt],[combines],[wk],[grimling],[twilight],[dawnshroud],[nexus],[pok],[stonebrunt],[bloodfields],[emerald],[skyfire],[hateplane],[airplane] or [wos].", c->GetName()); - } + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "I don't have the needed level yet."); + } else + Gater->CastToBot()->BotGroupSay(Gater->CastToBot(), "With the proper level I can [gate] to [commons], [fay], [ro], [tox], [nk], [wakening], [iceclad], [divide], [cobalt], [combines], [wk], [grimling], [twilight], [dawnshroud], [nexus], [pok], [stonebrunt], [bloodfields], [emerald], [skyfire], [hateplane], [airplane] or [wos].", c->GetName()); break; default: c->Message(15, "You must have a Druid or Wizard in your group."); @@ -13243,7 +11232,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 EndurerClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case DRUID: @@ -13281,48 +11270,43 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } switch(EndurerClass) { case DRUID: - if (c->GetLevel() < 6) { - Endurer->Say("I'm not level 6 yet."); - } + if (c->GetLevel() < 6) + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 6 yet."); else { - Endurer->Say("Casting Enduring Breath..."); + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); Endurer->CastSpell(86, c->GetID(), 1, -1, -1); break; } break; case SHAMAN: - if (c->GetLevel() < 12) { - Endurer->Say("I'm not level 12 yet."); - } + if (c->GetLevel() < 12) + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 12 yet."); else { - Endurer->Say("Casting Enduring Breath..."); + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); Endurer->CastSpell(86, c->GetID(), 1, -1, -1); } break; case RANGER: - if (c->GetLevel() < 20) { - Endurer->Say("I'm not level 20 yet."); - } + if (c->GetLevel() < 20) + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 20 yet."); else { - Endurer->Say("Casting Enduring Breath..."); + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); Endurer->CastSpell(86, c->GetID(), 1, -1, -1); } break; case ENCHANTER: - if (c->GetLevel() < 12) { - Endurer->Say("I'm not level 12 yet."); - } + if (c->GetLevel() < 12) + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 12 yet."); else { - Endurer->Say("Casting Enduring Breath..."); + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); Endurer->CastSpell(86, c->GetID(), 1, -1, -1); } break; case BEASTLORD: - if (c->GetLevel() < 25) { - Endurer->Say("I'm not level 25 yet."); - } + if (c->GetLevel() < 25) + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "I'm not level 25 yet."); else { - Endurer->Say("Casting Enduring Breath..."); + Endurer->CastToBot()->BotGroupSay(Endurer->CastToBot(), "Casting Enduring Breath."); Endurer->CastSpell(86, c->GetID(), 1, -1, -1); } break; @@ -13339,7 +11323,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 InviserClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case ENCHANTER: @@ -13378,137 +11362,111 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } switch(InviserClass) { case ENCHANTER: - if ((c->GetLevel() <= 14) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->Say("I'm not level 14 yet."); - } + if ((c->GetLevel() <= 14) && (!strcasecmp(sep->arg[2], "undead"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 14 yet."); else if ((!c->IsInvisible(c)) && (!c->invisible_undead) && (c->GetLevel() >= 14) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->Say("Casting invis undead..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting invis undead."); Inviser->CastSpell(235, c->GetID(), 1, -1, -1); } - else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("I'm not level 4 yet."); - } + else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "live"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 4 yet."); else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("Casting invisibilty..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting invisibilty."); Inviser->CastSpell(42, c->GetID(), 1, -1, -1); } - else if ((c->GetLevel() <= 6) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("I'm not level 6 yet."); - } + else if ((c->GetLevel() <= 6) && (!strcasecmp(sep->arg[2], "see"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 6 yet."); else if ((c->GetLevel() >= 6) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("Casting see invisible..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting see invisible."); Inviser->CastSpell(80, c->GetID(), 1, -1, -1); } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) { - Inviser->Say("I can't cast this if you're already invis-buffed..."); - } - else { - Inviser->Say("Do you want [invis undead], [invis live] or [invis see] ?", c->GetName()); - } + else if ((c->IsInvisible(c)) || (c->invisible_undead)) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); + else + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want [invis undead], [invis live] or [invis see]?"); break; case MAGICIAN: - if (!strcasecmp(sep->arg[2], "undead")) { - Inviser->Say("I don't have that spell."); - } - else if ((c->GetLevel() <= 8) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("I'm not level 8 yet."); - } + if (!strcasecmp(sep->arg[2], "undead")) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); + else if ((c->GetLevel() <= 8) && (!strcasecmp(sep->arg[2], "live"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 8 yet."); else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 8) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("Casting invisibilty..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting invisibilty."); Inviser->CastSpell(42, c->GetID(), 1, -1, -1); } - else if ((c->GetLevel() <= 16) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("I'm not level 16 yet."); - } + else if ((c->GetLevel() <= 16) && (!strcasecmp(sep->arg[2], "see"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 16 yet."); else if ((c->GetLevel() >= 16) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("Casting see invisible..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting see invisible."); Inviser->CastSpell(80, c->GetID(), 1, -1, -1); } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) { - Inviser->Say("I can't cast this if you're already invis-buffed..."); - } - else { - Inviser->Say("Do you want [invis live] or [invis see] ?", c->GetName()); - } + else if ((c->IsInvisible(c)) || (c->invisible_undead)) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); + else + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want [invis live] or [invis see]?"); break; case WIZARD: - if ((c->GetLevel() <= 39) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->Say("I'm not level 39 yet."); - } + if ((c->GetLevel() <= 39) && (!strcasecmp(sep->arg[2], "undead"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 39 yet."); else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 39) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->Say("Casting invis undead..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting invis undead."); Inviser->CastSpell(235, c->GetID(), 1, -1, -1); } - else if ((c->GetLevel() <= 16) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("I'm not level 16 yet."); - } + else if ((c->GetLevel() <= 16) && (!strcasecmp(sep->arg[2], "live"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 16 yet."); else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 16) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("Casting invisibilty..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting invisibilty."); Inviser->CastSpell(42, c->GetID(), 1, -1, -1); } - else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("I'm not level 6 yet."); - } + else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "see"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 6 yet."); else if ((c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("Casting see invisible..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting see invisible."); Inviser->CastSpell(80, c->GetID(), 1, -1, -1); } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) { - Inviser->Say("I can't cast this if you're already invis-buffed..."); - } - else { - Inviser->Say("Do you want [invis undead], [invis live] or [invis see] ?", c->GetName()); - } + else if ((c->IsInvisible(c)) || (c->invisible_undead)) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); + else + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want [invis undead], [invis live] or [invis see]?"); break; case NECROMANCER: if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (!strcasecmp(sep->arg[2], "undead"))) { - Inviser->Say("Casting invis undead..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting invis undead."); Inviser->CastSpell(235, c->GetID(), 1, -1, -1); } - else if (!strcasecmp(sep->arg[2], "see")) { - Inviser->Say("I don't have that spell..."); - } - else if (!strcasecmp(sep->arg[2], "live")) { - Inviser->Say("I don't have that spell..."); - } - else if ((c->IsInvisible(c))|| (c->invisible_undead)) { - Inviser->Say("I can't cast this if you're already invis-buffed..."); - } - else { - Inviser->Say("I only have [invis undead]", c->GetName()); - } + else if (!strcasecmp(sep->arg[2], "see")) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); + else if (!strcasecmp(sep->arg[2], "live")) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); + else if ((c->IsInvisible(c))|| (c->invisible_undead)) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); + else + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I only have [invis undead]"); break; case DRUID: - if (!strcasecmp(sep->arg[2], "undead")) { - Inviser->Say("I don't have that spell..."); - } - else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("I'm not level 4 yet."); - } + if (!strcasecmp(sep->arg[2], "undead")) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I don't have that spell."); + else if ((c->GetLevel() <= 4) && (!strcasecmp(sep->arg[2], "live"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 4 yet."); else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 18) && (!strcasecmp(sep->arg[2], "live"))) { - Inviser->Say("Casting Superior Camouflage..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Superior Camouflage."); Inviser->CastSpell(34, c->GetID(), 1, -1, -1); - } - else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live")) && (zone->CanCastOutdoor())) { - Inviser->Say("Casting Camouflage..."); + } else if ((!c->IsInvisible(c))&& (!c->invisible_undead) && (c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live")) && (zone->CanCastOutdoor())) { + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting Camouflage..."); Inviser->CastSpell(247, c->GetID(), 1, -1, -1); } - else if ((c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live")) && (!zone->CanCastOutdoor())) { - Inviser->Say("I can't cast this spell indoors..."); - } - else if ((c->GetLevel() <= 13) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("I'm not level 13 yet."); - } + else if ((c->GetLevel() >= 4) && (!strcasecmp(sep->arg[2], "live")) && (!zone->CanCastOutdoor())) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this spell indoors."); + else if ((c->GetLevel() <= 13) && (!strcasecmp(sep->arg[2], "see"))) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I'm not level 13 yet."); else if ((c->GetLevel() >= 13) && (!strcasecmp(sep->arg[2], "see"))) { - Inviser->Say("Casting see invisible..."); + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Casting see invisible."); Inviser->CastSpell(80, c->GetID(), 1, -1, -1); } - else if ((c->IsInvisible(c)) || (c->invisible_undead)) { - Inviser->Say("I can't cast this if you're already invis-buffed..."); - } - else { - Inviser->Say("Do you want [invis live] or [invis see] ?", c->GetName()); - } + else if ((c->IsInvisible(c)) || (c->invisible_undead)) + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "I can't cast this if you're already invis-buffed."); + else + Inviser->CastToBot()->BotGroupSay(Inviser->CastToBot(), "Do you want [invis live] or [invis see]?"); break; default: c->Message(15, "You must have a Enchanter, Magician, Wizard, Druid, or Necromancer in your group."); @@ -13523,7 +11481,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 LeverClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case DRUID: @@ -13555,62 +11513,46 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } switch(LeverClass) { case DRUID: - if (c->GetLevel() <= 14) { - Lever->Say("I'm not level 14 yet."); - } + if (c->GetLevel() <= 14) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 14 yet."); else if (zone->CanCastOutdoor()) { - Lever->Say("Casting Levitate..."); + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); Lever->CastSpell(261, c->GetID(), 1, -1, -1); break; } - else if (!zone->CanCastOutdoor()) { - Lever->Say("I can't cast this spell indoors", c->GetName()); - } + else if (!zone->CanCastOutdoor()) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); break; - case SHAMAN: - if ((zone->CanCastOutdoor()) && (c->GetLevel() >= 10)) { - Lever->Say("Casting Levitate..."); + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); Lever->CastToClient()->CastSpell(261, c->GetID(), 1, -1, -1); } - else if (!zone->CanCastOutdoor()) { - Lever->Say("I can't cast this spell indoors", c->GetName()); - } - else if (c->GetLevel() <= 10) { - Lever->Say("I'm not level 10 yet."); - } + else if (!zone->CanCastOutdoor()) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); + else if (c->GetLevel() <= 10) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 10 yet."); break; - case WIZARD: - - if((zone->CanCastOutdoor()) && (c->GetLevel() >= 22)){ - Lever->Say("Casting Levitate..."); + if((zone->CanCastOutdoor()) && (c->GetLevel() >= 22)) { + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); Lever->CastToClient()->CastSpell(261, c->GetID(), 1, -1, -1); } - else if (!zone->CanCastOutdoor()) { - Lever->Say("I can't cast this spell indoors", c->GetName()); - } - else if (c->GetLevel() <= 22) { - Lever->Say("I'm not level 22 yet."); - } + else if (!zone->CanCastOutdoor()) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); + else if (c->GetLevel() <= 22) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 22 yet."); break; - case ENCHANTER: - if((zone->CanCastOutdoor()) && (c->GetLevel() >= 15)) { - Lever->Say("Casting Levitate..."); + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "Casting Levitate."); Lever->CastToClient()->CastSpell(261, c->GetID(), 1, -1, -1); } - else if (!zone->CanCastOutdoor()) { - Lever->Say("I can't cast this spell indoors", c->GetName()); - } - else if (c->GetLevel() <= 15) { - Lever->Say("I'm not level 15 yet."); - } + else if (!zone->CanCastOutdoor()) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I can't cast this spell indoors."); + else if (c->GetLevel() <= 15) + Lever->CastToBot()->BotGroupSay(Lever->CastToBot(), "I'm not level 15 yet."); break; - - default: c->Message(15, "You must have a Druid, Shaman, Wizard, or Enchanter in your group."); break; @@ -13624,7 +11566,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint32 ResisterClass = 0; Group *g = c->GetGroup(); if(g) { - for(int i=0; imembers[i] && g->members[i]->IsBot()) { switch(g->members[i]->GetClass()) { case CLERIC: @@ -13636,13 +11578,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Resister = g->members[i]; ResisterClass = SHAMAN; } + break; case DRUID: if (ResisterClass == 0){ Resister = g->members[i]; ResisterClass = DRUID; } break; - break; default: break; } @@ -13650,104 +11592,81 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } switch(ResisterClass) { case CLERIC: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 6)) { - Resister->Say("Casting poison protection...", sep->arg[2]); + if(!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 6)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting poison protection."); Resister->CastToBot()->Bot_Command_Resist(1, Resister->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 11)) { - Resister->Say("Casting disease protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 11)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting disease protection."); Resister->CastToBot()->Bot_Command_Resist(2, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "fire") && (c->GetLevel() >= 8)) { - Resister->Say("Casting fire protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "fire") && (c->GetLevel() >= 8)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting fire protection."); Resister->CastToBot()->Bot_Command_Resist(3, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 13)) { - Resister->Say("Casting cold protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 13)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting cold protection."); Resister->CastToBot()->Bot_Command_Resist(4, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 16)) { - Resister->Say("Casting magic protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 16)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting magic protection."); Resister->CastToBot()->Bot_Command_Resist(5, Resister->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 16) + } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 16) || !strcasecmp(sep->arg[2], "cold") && (c->GetLevel() <= 13) || !strcasecmp(sep->arg[2], "fire") && (c->GetLevel() <= 8) || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 11) || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 6)) { - Resister->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Resister->Say("Do you want [resist poison], [resist disease], [resist fire], [resist cold], or [resist magic]?", c->GetName()); + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "I don't have the required level yet."); + } else + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Do you want [resist poison], [resist disease], [resist fire], [resist cold], or [resist magic]?"); break; - case SHAMAN: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 20)) { - Resister->Say("Casting poison protection...", sep->arg[2]); + if(!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 20)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting poison protection."); Resister->CastToBot()->Bot_Command_Resist(12, Resister->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 8)) { - Resister->Say("Casting disease protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 8)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting disease protection."); Resister->CastToBot()->Bot_Command_Resist(13, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "fire") && (c->GetLevel() >= 5)) { - Resister->Say("Casting fire protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "fire") && (c->GetLevel() >= 5)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting fire protection."); Resister->CastToBot()->Bot_Command_Resist(14, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 1)) { - Resister->Say("Casting cold protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 1)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting cold protection."); Resister->CastToBot()->Bot_Command_Resist(15, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 19)) { - Resister->Say("Casting magic protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 19)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting magic protection."); Resister->CastToBot()->Bot_Command_Resist(16, Resister->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 19) + } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 19) || !strcasecmp(sep->arg[2], "cold") && (c->GetLevel() <= 1) || !strcasecmp(sep->arg[2], "fire") && (c->GetLevel() <= 5) || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 8) || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 20)) { - Resister->Say("I don't have the needed level yet", sep->arg[2]); - } - else - Resister->Say("Do you want [resist poison], [resist disease], [resist fire], [resist cold], or [resist magic]?", c->GetName()); + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "I don't have the needed level yet."); + } else + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Do you want [resist poison], [resist disease], [resist fire], [resist cold], or [resist magic]?"); break; - case DRUID: - if (!strcasecmp(sep->arg[2], "poison") && (c->GetLevel() >= 19)) { - Resister->Say("Casting poison protection...", sep->arg[2]); + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting poison protection."); Resister->CastToBot()->Bot_Command_Resist(7, Resister->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 19)) { - Resister->Say("Casting disease protection...", sep->arg[2]); + } else if (!strcasecmp(sep->arg[2], "disease") && (c->GetLevel() >= 19)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting disease protection."); Resister->CastToBot()->Bot_Command_Resist(8, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "fire")) { // Fire level 1 - Resister->Say("Casting fire protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "fire")) { // Fire level 1 + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting fire protection."); Resister->CastToBot()->Bot_Command_Resist(9, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 13)) { - Resister->Say("Casting cold protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "cold") && (c->GetLevel() >= 13)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting cold protection."); Resister->CastToBot()->Bot_Command_Resist(10, Resister->GetLevel()); - } - else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 16)) { - Resister->Say("Casting magic protection...", sep->arg[2]); + } else if(!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() >= 16)) { + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Casting magic protection."); Resister->CastToBot()->Bot_Command_Resist(11, Resister->GetLevel()); - } - else if (!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 16) + } else if (!strcasecmp(sep->arg[2], "magic") && (c->GetLevel() <= 16) || !strcasecmp(sep->arg[2], "cold") && (c->GetLevel() <= 9) || !strcasecmp(sep->arg[2], "disease") && (c->GetLevel() <= 19) || !strcasecmp(sep->arg[2], "poison") && (c->GetLevel() <= 19)) { - Resister->Say("I don't have the needed level yet", sep->arg[2]) ; - } - else - Resister->Say("Do you want [resist poison], [resist disease], [resist fire], [resist cold], or [resist magic]?", c->GetName()); - + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "I don't have the required level yet.") ; + } else + Resister->CastToBot()->BotGroupSay(Resister->CastToBot(), "Do you want [resist poison], [resist disease], [resist fire], [resist cold], or [resist magic]?"); break; - default: c->Message(15, "You must have a Cleric, Shaman, or Druid in your group."); break; @@ -13755,52 +11674,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } } - // debug commands - if(!strcasecmp(sep->arg[1], "debug") && !strcasecmp(sep->arg[2], "inventory")) { - Mob *target = c->GetTarget(); - - if(target && target->IsBot()) { - for(int i = EmuConstants::MATERIAL_BEGIN; i <= EmuConstants::MATERIAL_END; i++) { - c->Message(15,"Equiped slot: %i , item: %i \n", i, target->CastToBot()->GetEquipment(i)); - } - if(target->CastToBot()->GetEquipment(MaterialSecondary) > 0) - c->Message(15,"This bot has an item in off-hand."); - } - return; - } - - if(!strcasecmp(sep->arg[1], "debug") && !strcasecmp(sep->arg[2], "botcaracs")) - { - Mob *target = c->GetTarget(); - if(target && target->IsBot()) - { - if(target->CanThisClassDualWield()) - c->Message(15, "This class can dual wield."); - if(target->CanThisClassDoubleAttack()) - c->Message(15, "This class can double attack."); - } - if(target->GetPetID()) - c->Message(15, "I've a pet and its name is %s", target->GetPet()->GetCleanName() ); - return; - } - - if(!strcasecmp(sep->arg[1], "debug") && !strcasecmp(sep->arg[2], "spells")) - { - Mob *target = c->GetTarget(); - if(target && target->IsBot()) - { - for(int i=0; iCastToBot()->AIspells.size(); i++) - { - if(target->CastToBot()->BotGetSpells(i) != 0) - { - SPDat_Spell_Struct botspell = spells[target->CastToBot()->BotGetSpells(i)]; - c->Message(15, "(DEBUG) %s , Slot(%i), Spell (%s) Priority (%i) \n", target->GetCleanName(), i, botspell.name, target->CastToBot()->BotGetSpellPriority(i)); - } - } - } - return; - } - // #bot group ... if(!strcasecmp(sep->arg[1], "group") && !strcasecmp(sep->arg[2], "help")) { c->Message(0, "#bot group help - will show this help."); @@ -13808,7 +11681,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(0, "#bot group follow "); c->Message(0, "#bot group guard "); c->Message(0, "#bot group attack "); - return; } @@ -13816,23 +11688,18 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[2], "follow")) { if(c->IsGrouped()) BotGroupOrderFollow(c->GetGroup(), c); - } - else if(!strcasecmp(sep->arg[2], "guard")) { + } else if(!strcasecmp(sep->arg[2], "guard")) { if(c->IsGrouped()) BotGroupOrderGuard(c->GetGroup(), c); - } - else if(!strcasecmp(sep->arg[2], "attack")) { - if(c->IsGrouped() && (c->GetTarget() != nullptr) && c->IsAttackAllowed(c->GetTarget())) { + } else if(!strcasecmp(sep->arg[2], "attack")) { + if(c->IsGrouped() && (c->GetTarget() != nullptr) && c->IsAttackAllowed(c->GetTarget())) BotGroupOrderAttack(c->GetGroup(), c->GetTarget(), c); - } else c->Message(15, "You must target a monster."); - } - else if(!strcasecmp(sep->arg[2], "summon")) { + } else if(!strcasecmp(sep->arg[2], "summon")) { if(c->IsGrouped()) BotGroupSummon(c->GetGroup(), c); } - return; } @@ -13859,10 +11726,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Mob* targetMob = c->GetTarget(); std::string targetName = std::string(sep->arg[3]); Bot* botGroupLeader = 0; - - if(!targetName.empty()) { + if(!targetName.empty()) botGroupLeader = entity_list.GetBotByBotName(targetName); - } else if(targetMob) { if(targetMob->IsBot()) botGroupLeader = targetMob->CastToBot(); @@ -13870,11 +11735,10 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupLeader) { if(Bot::BotGroupCreate(botGroupLeader)) - botGroupLeader->Say("I am prepared to lead."); + botGroupLeader->BotGroupSay(botGroupLeader, "I am prepared to lead."); else - botGroupLeader->Say("I can not lead."); - } - else + botGroupLeader->BotGroupSay(botGroupLeader, "I cannot lead."); + } else c->Message(13, "You must target a spawned bot first."); return; @@ -13882,17 +11746,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "add")) { int argCount = 0; - argCount = sep->argnum; - std::string botGroupLeaderName; std::string botGroupMemberName; - if(argCount >= 3) botGroupMemberName = std::string(sep->arg[3]); Bot* botGroupMember = entity_list.GetBotByBotName(botGroupMemberName); - if(!botGroupMember) { if(botGroupMemberName.empty()) c->Message(13, "You must target a bot in this zone. Please try again."); @@ -13903,13 +11763,10 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } Bot* botGroupLeader = 0; - if(argCount == 4) { botGroupLeaderName = std::string(sep->arg[4]); - botGroupLeader = entity_list.GetBotByBotName(botGroupLeaderName); - } - else if(c->GetTarget() && c->GetTarget()->IsBot()) + } else if(c->GetTarget() && c->GetTarget()->IsBot()) botGroupLeader = c->GetTarget()->CastToBot(); if(!botGroupLeader) { @@ -13933,41 +11790,27 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupMember) { if(!botGroupMember->HasGroup()) { - // invite if(Bot::AddBotToGroup(botGroupMember, g)) { database.SetGroupID(botGroupMember->GetName(), g->GetID(), botGroupMember->GetBotID()); - botGroupMember->Say("I have joined %s\'s group.", botGroupLeader->GetName()); - } - else { - botGroupMember->Say("I can not join %s\'s group.", botGroupLeader->GetName()); - } - } - else { - // "I am already in a group." + botGroupMember->BotGroupSay(botGroupMember, "I have joined %s\'s group.", botGroupLeader->GetName()); + } else + botGroupMember->BotGroupSay(botGroupMember, "I can not join %s\'s group.", botGroupLeader->GetName()); + } else { Group* tempGroup = botGroupMember->GetGroup(); if(tempGroup) - botGroupMember->Say("I can not join %s\'s group. I am already a member in %s\'s group.", botGroupLeader->GetName(), tempGroup->GetLeaderName()); + botGroupMember->BotGroupSay(botGroupMember, "I can not join %s\'s group. I am already a member in %s\'s group.", botGroupLeader->GetName(), tempGroup->GetLeaderName()); } - } - else { - // must target a bot message + } else c->Message(13, "You must target a spawned bot first."); - } - } - else { - // "My group is full." - botGroupLeader->Say("I have no more openings in my group, %s.", c->GetName()); - } - } - else { - // "I am not a group leader." + } else + botGroupLeader->BotGroupSay(botGroupMember, "I have no more openings in my group, %s.", c->GetName()); + } else { Group* tempGroup = botGroupLeader->GetGroup(); if(tempGroup) - botGroupLeader->Say("I can not lead anyone because I am a member in %s\'s group.", tempGroup->GetLeaderName()); + botGroupLeader->BotGroupSay(botGroupLeader, "I can not lead anyone because I am a member in %s\'s group.", tempGroup->GetLeaderName()); } } } - return; } @@ -13976,9 +11819,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { std::string targetName = std::string(sep->arg[3]); Bot* botGroupMember = 0; - if(!targetName.empty()) { + if(!targetName.empty()) botGroupMember = entity_list.GetBotByBotName(targetName); - } else if(targetMob) { if(targetMob->IsBot()) botGroupMember = targetMob->CastToBot(); @@ -13987,16 +11829,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupMember) { if(botGroupMember->HasGroup()) { Group* g = botGroupMember->GetGroup(); - if(Bot::RemoveBotFromGroup(botGroupMember, g)) - botGroupMember->Say("I am no longer in a group."); + botGroupMember->BotGroupSay(botGroupMember, "I am no longer in a group."); else - botGroupMember->Say("I can not leave %s\'s group.", g->GetLeaderName()); - } - else - botGroupMember->Say("I am not in a group."); - } - else + botGroupMember->BotGroupSay(botGroupMember, "I can not leave %s\'s group.", g->GetLeaderName()); + } else + botGroupMember->BotGroupSay(botGroupMember, "I am not in a group."); + } else c->Message(13, "You must target a spawned bot first."); return; @@ -14006,10 +11845,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Mob* targetMob = c->GetTarget(); std::string targetName = std::string(sep->arg[3]); Bot* botGroupLeader = 0; - - if(!targetName.empty()) { + if(!targetName.empty()) botGroupLeader = entity_list.GetBotByBotName(targetName); - } else if(targetMob) { if(targetMob->IsBot()) botGroupLeader = targetMob->CastToBot(); @@ -14018,21 +11855,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupLeader) { if(botGroupLeader->HasGroup()) { Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) { if(Bot::RemoveBotFromGroup(botGroupLeader, g)) - botGroupLeader->Say("I have disbanded my group, %s.", c->GetName()); + botGroupLeader->BotGroupSay(botGroupLeader, "I have disbanded my group, %s.", c->GetName()); else - botGroupLeader->Say("I was not able to disband my group, %s.", c->GetName()); - } - else { - botGroupLeader->Say("I can not disband my group, %s, because I am not the leader. %s is the leader of my group.", c->GetName(), g->GetLeaderName()); - } - } - else - botGroupLeader->Say("I am not a group leader, %s.", c->GetName()); - } - else + botGroupLeader->BotGroupSay(botGroupLeader, "I was not able to disband my group, %s.", c->GetName()); + } else + botGroupLeader->BotGroupSay(botGroupLeader, "I can not disband my group, %s, because I am not the leader. %s is the leader of my group.", c->GetName(), g->GetLeaderName()); + } else + botGroupLeader->BotGroupSay(botGroupLeader, "I am not a group leader, %s.", c->GetName()); + } else c->Message(13, "You must target a spawned bot group leader first."); return; @@ -14043,9 +11875,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { std::string targetName = std::string(sep->arg[3]); Bot* botGroupLeader = 0; - if(!targetName.empty()) { + if(!targetName.empty()) botGroupLeader = entity_list.GetBotByBotName(targetName); - } else if(targetMob) { if(targetMob->IsBot()) botGroupLeader = targetMob->CastToBot(); @@ -14054,12 +11885,10 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupLeader) { if(botGroupLeader->HasGroup()) { Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) BotGroupSummon(g, c); } - } - else if(c->HasGroup()) + } else if(c->HasGroup()) BotGroupSummon(c->GetGroup(), c); return; @@ -14069,10 +11898,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Mob* targetMob = c->GetTarget(); std::string targetName = std::string(sep->arg[3]); Bot* botGroupLeader = 0; - - if(!targetName.empty()) { + if(!targetName.empty()) botGroupLeader = entity_list.GetBotByBotName(targetName); - } else if(targetMob) { if(targetMob->IsBot()) botGroupLeader = targetMob->CastToBot(); @@ -14081,12 +11908,10 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupLeader) { if(botGroupLeader->HasGroup()) { Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) BotGroupOrderFollow(g, c); } - } - else if(c->HasGroup()) + } else if(c->HasGroup()) BotGroupOrderFollow(c->GetGroup(), c); return; @@ -14096,10 +11921,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Mob* targetMob = c->GetTarget(); std::string targetName = std::string(sep->arg[3]); Bot* botGroupLeader = 0; - - if(!targetName.empty()) { + if(!targetName.empty()) botGroupLeader = entity_list.GetBotByBotName(targetName); - } else if(targetMob) { if(targetMob->IsBot()) botGroupLeader = targetMob->CastToBot(); @@ -14108,12 +11931,10 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupLeader) { if(botGroupLeader->HasGroup()) { Group* g = botGroupLeader->GetGroup(); - if(g->IsLeader(botGroupLeader)) BotGroupOrderGuard(g, c); } - } - else if(c->HasGroup()) + } else if(c->HasGroup()) BotGroupOrderGuard(c->GetGroup(), c); return; @@ -14124,95 +11945,73 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Bot* botGroupLeader = 0; std::string botGroupLeaderName = std::string(sep->arg[3]); std::string targetName = std::string(sep->arg[4]); - if(!botGroupLeaderName.empty()) { botGroupLeader = entity_list.GetBotByBotName(botGroupLeaderName); - if(botGroupLeader) { - if(!targetName.empty()) { + if(!targetName.empty()) targetMob = entity_list.GetMob(targetName.c_str()); - } if(targetMob) { if(c->IsAttackAllowed(targetMob)) { if(botGroupLeader->HasGroup()) { Group* g = botGroupLeader->GetGroup(); - if(g) { if(g->IsLeader(botGroupLeader)) BotGroupOrderAttack(g, targetMob, c); } - } - else if(c->HasGroup()) + } else if(c->HasGroup()) BotGroupOrderAttack(c->GetGroup(), targetMob, c); - } - else + } else c->Message(13, "You must target a monster."); - } - else + } else c->Message(13, "You must target a monster."); - } - else + } else c->Message(13, "You must target a spawned bot group leader first."); } - return; } if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "list")) { std::list botGroupList = GetBotGroupListByBotOwnerCharacterId(c->CharacterID(), &TempErrorMessage); - if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return; } if(!botGroupList.empty()) { - for(std::list::iterator botGroupListItr = botGroupList.begin(); botGroupListItr != botGroupList.end(); ++botGroupListItr) { + for(std::list::iterator botGroupListItr = botGroupList.begin(); botGroupListItr != botGroupList.end(); ++botGroupListItr) c->Message(0, "Bot Group Name: %s -- Bot Group Leader: %s", botGroupListItr->BotGroupName.c_str(), botGroupListItr->BotGroupLeaderName.c_str()); - } - } - else { + } else c->Message(0, "You have no bot groups created. Use the #bot botgroup save command to save bot groups."); - } return; } if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "load")) { - - // If client is grouped, check for aggro on each group member. Group *g = c->GetGroup(); if(g) { for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { - // Skip invalid group members. - if(!g->members[i]) { continue; } - - // Fail if current group member is client and has aggro - // OR has a popuplated hate list (assume bot). - if((g->members[i]->IsClient() && g->members[i]->CastToClient()->GetAggroCount()) - || g->members[i]->IsEngaged()) { + if(!g->members[i]) + continue; + + if((g->members[i]->IsClient() && g->members[i]->CastToClient()->GetAggroCount()) || g->members[i]->IsEngaged()) { c->Message(0, "You can't spawn bots while your group is engaged."); return; } } - } - // Fail if ungrouped client has aggro. - else { + } else { if(c->GetAggroCount() > 0) { c->Message(0, "You can't spawn bots while you are engaged."); return; } } - // Parse botgroup name. std::string botGroupName = std::string(sep->arg[3]); if(botGroupName.empty()) { c->Message(13, "Invalid botgroup name supplied."); return; } - // Get botgroup id. uint32 botGroupID = CanLoadBotGroup(c->CharacterID(), botGroupName, &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); @@ -14223,59 +12022,43 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - // Get list of bots in specified group. std::list botGroup = LoadBotGroup(botGroupName, &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return; } - // Count of client's currently spawned bots. int spawnedBots = SpawnedBotCount(c->CharacterID(), &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return; } - // BotQuest rule value in database is True. if(RuleB(Bots, BotQuest)) { - // Max number of allowed spawned bots for client. const int allowedBotsBQ = AllowedBotSpawns(c->CharacterID(), &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return; } - // Fail if no bots allowed for client. if(allowedBotsBQ == 0) { c->Message(0, "You can't spawn any bots."); return; } - // Fail if maximum number of spawned bots allowed for client met or exceeded - // OR will be when bot group is spawned. - if(spawnedBots >= allowedBotsBQ - || spawnedBots + (int)botGroup.size() > allowedBotsBQ) { - c->Message(0, "You can't spawn more than %i bot(s). [Rule:BQ]", allowedBotsBQ); + if(spawnedBots >= allowedBotsBQ || spawnedBots + (int)botGroup.size() > allowedBotsBQ) { + c->Message(0, "You can't spawn more than %i bot(s).", allowedBotsBQ); return; } } - // Fail if maximum number of spawned bots allowed for client met or exceeded - // OR will be when bot group is spawned. const int allowedBotsSBC = RuleI(Bots, SpawnBotCount); - if(spawnedBots >= allowedBotsSBC - || spawnedBots + (int)botGroup.size() > allowedBotsSBC) { - c->Message(0, "You can't spawn more than %i bots. [Rule:SBC]", allowedBotsSBC); + if(spawnedBots >= allowedBotsSBC || spawnedBots + (int)botGroup.size() > allowedBotsSBC) { + c->Message(0, "You can't spawn more than %i bots.", allowedBotsSBC); return; } - // Passed all checks. Spawn requested bot group. - - // Get botgroup's leader's id. uint32 botGroupLeaderBotID = GetBotGroupLeaderIdByBotGroupName(botGroupName); - - // Load botgroup's leader. Bot *botGroupLeader = LoadBot(botGroupLeaderBotID, &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); @@ -14288,7 +12071,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - // Spawn botgroup's leader. botGroupLeader->Spawn(c, &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); @@ -14296,11 +12078,11 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - // Create botgroup. if(!BotGroupCreate(botGroupLeader)) { c->Message(13, "Unable to create botgroup."); return; } + Group *newBotGroup = botGroupLeader->GetGroup(); if(!newBotGroup) { c->Message(13, "Unable to find valid botgroup"); @@ -14308,23 +12090,21 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } for(auto botGroupItr = botGroup.begin(); botGroupItr != botGroup.end(); ++botGroupItr) { - // Don't try to re-spawn the botgroup's leader. - if(botGroupItr->BotID == botGroupLeader->GetBotID()) { continue; } + if(botGroupItr->BotID == botGroupLeader->GetBotID()) + continue; - // Load current botgroup member Bot *botGroupMember = LoadBot(botGroupItr->BotID, &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); safe_delete(botGroupMember); return; } - // Skip invalid botgroup members. + if(!botGroupMember) { safe_delete(botGroupMember); continue; } - // Spawn current botgroup member. botGroupMember->Spawn(c, &TempErrorMessage); if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); @@ -14332,19 +12112,15 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - // Add current botgroup member to botgroup. AddBotToGroup(botGroupMember, newBotGroup); } - return; } if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "delete")) { std::string botGroupName = std::string(sep->arg[3]); - if(!botGroupName.empty()) { uint32 botGroupId = CanLoadBotGroup(c->CharacterID(), botGroupName, &TempErrorMessage); - if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return; @@ -14352,24 +12128,20 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(botGroupId > 0) { DeleteBotGroup(botGroupName, &TempErrorMessage); - if(!TempErrorMessage.empty()) { c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return; } } } - return; } if(!strcasecmp(sep->arg[1], "botgroup") && !strcasecmp(sep->arg[2], "save")) { std::string botGroupName = std::string(sep->arg[3]); - if(!botGroupName.empty()) { if(!DoesBotGroupNameExist(botGroupName)) { Bot* groupLeader = 0; - if(c->GetTarget() && c->GetTarget()->IsBot()) groupLeader = c->GetTarget()->CastToBot(); else @@ -14378,23 +12150,17 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(groupLeader) { if(groupLeader->HasGroup() && groupLeader->GetGroup()->IsLeader(groupLeader)) { SaveBotGroup(groupLeader->GetGroup(), botGroupName, &TempErrorMessage); - - if(!TempErrorMessage.empty()) { + if(!TempErrorMessage.empty()) c->Message(13, "Database Error: %s", TempErrorMessage.c_str()); - } else c->Message(0, "%s's bot group has been saved as %s.", groupLeader->GetName(), botGroupName.c_str()); - } - else + } else c->Message(0, "You must target a bot group leader only."); - } - else + } else c->Message(0, "You must target a bot that is in the same zone as you."); - } - else + } else c->Message(0, "The bot group name already exists. Please choose another name to save your bot group as."); } - return; } @@ -14413,22 +12179,23 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { uint8 BeardColor = target->GetBeardColor(); uint8 EyeColor1 = target->GetEyeColor1(); uint8 EyeColor2 = target->GetEyeColor2(); - uint8 LuclinFace = target->GetLuclinFace(); uint8 Beard = target->GetBeard(); uint32 DrakkinHeritage = target->GetDrakkinHeritage(); uint32 DrakkinTattoo = target->GetDrakkinTattoo(); uint32 DrakkinDetails = target->GetDrakkinDetails(); float Size = target->GetSize(); - if (!strcasecmp(sep->arg[1], "hair")) HairStyle = atoi(sep->arg[2]); + if (!strcasecmp(sep->arg[1], "haircolor")) HairColor = atoi(sep->arg[2]); + if (!strcasecmp(sep->arg[1], "beard") || !strcasecmp(sep->arg[1], "beardcolor")) { if (!Gender || Race == 8) { if (!strcasecmp(sep->arg[1], "beard")) Beard = atoi(sep->arg[2]); + if (!strcasecmp(sep->arg[1], "beardcolor")) BeardColor = atoi(sep->arg[2]); } else { @@ -14436,6 +12203,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } } + if (!strcasecmp(sep->arg[1], "face")) LuclinFace = atoi(sep->arg[2]); @@ -14443,46 +12211,42 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { EyeColor1 = EyeColor2 = atoi(sep->arg[2]); c->Message(0, "Eye Values = 0 - 11"); } + if(!strcasecmp(sep->arg[1], "heritage") || !strcasecmp(sep->arg[1], "tattoo") || !strcasecmp(sep->arg[1], "details")) { if(Race == 522) { if(!strcasecmp(sep->arg[1], "heritage")) { DrakkinHeritage = atoi(sep->arg[2]); c->Message(0, "Heritage Values = 0 - 6"); } + if(!strcasecmp(sep->arg[1], "tattoo")) { DrakkinTattoo = atoi(sep->arg[2]); c->Message(0, "Tattoo Values = 0 - 7"); } + if(!strcasecmp(sep->arg[1], "details")) { DrakkinDetails = atoi(sep->arg[2]); c->Message(0, "Details Values = 0 - 7"); } - } - else { + } else { c->Message(0, "Drakkin only."); return; } } - target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, - EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, - DrakkinHeritage, DrakkinTattoo, DrakkinDetails, Size); - + target->SendIllusionPacket(Race, Gender, Texture, HelmTexture, HairColor, BeardColor, EyeColor1, EyeColor2, HairStyle, LuclinFace, Beard, 0xFF, DrakkinHeritage, DrakkinTattoo, DrakkinDetails, Size); if(target->CastToBot()->Save()) c->Message(0, "%s saved.", target->GetCleanName()); else c->Message(13, "%s save failed!", target->GetCleanName()); - c->Message(0,"Feature changed."); - } else { + c->Message(0, "Feature changed."); + } else c->Message(0, "You must own the bot to make changes."); - } - } else { + } else c->Message(0, "Requires a value."); - } - } else { - c->Message(0,"A bot needs to be targetted."); - } + } else + c->Message(0, "A bot needs to be targeted."); return; } @@ -14501,7 +12265,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } Bot *targetedBot = nullptr; - if(c->GetTarget() != nullptr) { if (c->GetTarget()->IsBot() && (c->GetTarget()->CastToBot()->GetBotOwner() == c)) targetedBot = c->GetTarget()->CastToBot(); @@ -14515,26 +12278,21 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(taunt) { if(!targetedBot->taunting) - targetedBot->Say("I am now taunting."); - } - else { + targetedBot->BotGroupSay(targetedBot, "I am now taunting."); + } else { if(targetedBot->taunting) - targetedBot->Say("I am no longer taunting."); + targetedBot->BotGroupSay(targetedBot, "I am no longer taunting."); } targetedBot->SetTaunting(taunt); - } - else + } else c->Message(13, "You must select a bot with the taunt skill."); - } - else { + } else { c->Message(13, "You must target a spawned bot."); } } - } - else { + } else c->Message(0, "Usage #bot taunt [on|off]"); - } return; } @@ -14543,7 +12301,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 3){ Bot* tempBot = nullptr; std::string botName = std::string(sep->arg[2]); - if(!botName.empty()) tempBot = entity_list.GetBotByBotName(botName); else @@ -14552,23 +12309,19 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(tempBot) { std::string stanceName; BotStanceType botStance; - if (tempBot->GetBotOwner() != c) { c->Message(13, "You must target a bot that you own."); return; } - if(!strcasecmp(sep->arg[3], "list")) { + if(!strcasecmp(sep->arg[3], "list")) botStance = tempBot->GetBotStance(); - } else { int stance = atoi(sep->arg[3]); - if(stance >= MaxStances || stance < 0){ c->Message(0, "Usage #bot stance [name] [stance (id)] (Passive = 0, Balanced = 1, Efficient = 2, Reactive = 3, Aggressive = 4, Burn = 5, BurnAE = 6)"); return; - } - else { + } else { botStance = (BotStanceType)stance; if(botStance != tempBot->GetBotStance()) { tempBot->SetBotStance(botStance); @@ -14613,20 +12366,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } } c->Message(0, "Stance for %s: %s.", tempBot->GetCleanName(), stanceName.c_str()); - } - else { + } else c->Message(13, "You must name a valid bot."); - } - } - else { + } else c->Message(0, "Usage #bot stance [name] [stance (id)] (Passive = 0, Balanced = 1, Efficient = 2, Reactive = 3, Aggressive = 4, Burn = 5, BurnAE = 6)"); - } + return; } if(!strcasecmp(sep->arg[1], "groupmessages")) { bool groupMessages = false; - if(sep->arg[2] && sep->arg[3]){ if(!strcasecmp(sep->arg[2], "on")) groupMessages = true; @@ -14638,10 +12387,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } Bot* tempBot; - if(!strcasecmp(sep->arg[3], "all")) { std::list spawnedBots = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - if(!spawnedBots.empty()) { for(std::list::iterator botsListItr = spawnedBots.begin(); botsListItr != spawnedBots.end(); ++botsListItr) { Bot* tempBot = *botsListItr; @@ -14649,16 +12396,12 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { tempBot->SetGroupMessagesOn(groupMessages); } } - } - else { + } else c->Message(0, "You have no spawned bots in this zone."); - } c->Message(0, "Group messages now %s for all bots.", groupMessages?"on":"off"); - } - else { + } else { std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) tempBot = entity_list.GetBotByBotName(botName); else { @@ -14671,25 +12414,21 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(13, "You must target a bot that you own."); return; } - tempBot->SetGroupMessagesOn(groupMessages); c->Message(0, "Group messages now %s.", groupMessages?"on":"off"); - } - else { + } else { c->Message(13, "You must name a valid bot."); } } - } - else { + } else c->Message(0, "Usage #bot groupmessages [on|off] [bot name|all]"); - } + return; } if(!strcasecmp(sep->arg[1], "defensive")) { Bot* tempBot; std::string botName = std::string(sep->arg[2]); - if(!botName.empty()) tempBot = entity_list.GetBotByBotName(botName); else { @@ -14700,7 +12439,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(tempBot) { uint8 botlevel = tempBot->GetLevel(); uint32 defensiveSpellID = 0; - if (tempBot->GetBotOwner() != c) { c->Message(13, "You must target a bot that you own."); return; @@ -14717,7 +12455,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { else if(botlevel >= 52) defensiveSpellID = 4503; //Evasive discipline else - c->Message(0, "Error: warrior must be level 52+"); + c->Message(0, "Warrior must be level 52 or higher."); break; case PALADIN: if(botlevel >= 73) @@ -14729,7 +12467,7 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { else if(botlevel >= 56) defensiveSpellID = 7004; //Guard of Piety else - c->Message(0, "Error: paladin must be level 56+"); + c->Message(0, "Paladin must be level 56 or higher."); break; case SHADOWKNIGHT: if(botlevel >= 73) @@ -14741,20 +12479,18 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { else if(botlevel >= 56) defensiveSpellID = 7005; //Ichor guard else - c->Message(0, "Error: shadowknight must be level 56+"); + c->Message(0, "Shadow Knight must be level 56 or higher."); break; default: - c->Message(0, "Error: you must select a warrior or knight"); + c->Message(0, "You must select a Warrior, Paladin, or Shadow Knight."); break; } - if(defensiveSpellID > 0) { + if(defensiveSpellID > 0) tempBot->UseDiscipline(defensiveSpellID, tempBot->GetID()); - } - } - else { + } else c->Message(13, "You must name a valid bot."); - } + return; } @@ -14772,16 +12508,13 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(0, "#bot healrotation start "); c->Message(0, "#bot healrotation stop "); c->Message(0, "#bot healrotation list "); - return; } if(!strcasecmp(sep->arg[2], "create")) { - if(sep->argnum == 5 || sep->argnum == 6) { //allows for target or not + if(sep->argnum == 5 || sep->argnum == 6) { Bot* leaderBot; - std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -14793,14 +12526,12 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Mob* target = nullptr; uint32 timer; bool fastHeals = false; - if (!sep->IsNumber(4)) { c->Message(0, "Usage #bot healrotation create [target]."); return; } timer = (uint32)(atof(sep->arg[4]) * 1000); - if (leaderBot->GetBotOwner() != c) { c->Message(13, "You must target a bot that you own."); return; @@ -14811,7 +12542,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - //get percentage heals if(!strcasecmp(sep->arg[5], "fasthealson")) fastHeals = true; else if(strcasecmp(sep->arg[5], "fasthealsoff")) { @@ -14820,10 +12550,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } if(!leaderBot->GetInHealRotation()) { - //check for target if(sep->argnum == 6) { std::string targetName = std::string(sep->arg[6]); - if(!targetName.empty()) target = entity_list.GetMob(targetName.c_str()); else { @@ -14836,23 +12564,18 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } } - - //create rotation leaderBot->CreateHealRotation(target, timer); leaderBot->SetHealRotationUseFastHeals(fastHeals); c->Message(0, "Bot heal rotation created successfully."); - } - else { + } else { c->Message(13, "That bot is already in a heal rotation."); return; } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(0, "Usage #bot healrotation create [target]."); return; } @@ -14862,7 +12585,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 4) { Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -14873,7 +12595,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(leaderBot) { Bot* healer; std::string healerName = std::string(sep->arg[4]); - if (leaderBot->GetBotOwner() != c) { c->Message(13, "You must target a bot that you own."); return; @@ -14897,21 +12618,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - //add to rotation - if(leaderBot->AddHealRotationMember(healer)) { + if(leaderBot->AddHealRotationMember(healer)) c->Message(0, "Bot heal rotation member added successfully."); - } - else { - c->Message(13, "Unable to add bot to rotation. "); - } + else + c->Message(13, "Unable to add bot to rotation."); } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(0, "#bot healrotation addmember "); return; } @@ -14937,7 +12653,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Bot* healer; std::string healerName = std::string(sep->arg[4]); - if(!healerName.empty()) healer = entity_list.GetBotByBotName(healerName); else { @@ -14951,25 +12666,19 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - //remove from rotation - if(leaderBot->RemoveHealRotationMember(healer)) { + if(leaderBot->RemoveHealRotationMember(healer)) c->Message(0, "Bot heal rotation member removed successfully."); - } - else { - c->Message(13, "Unable to remove bot from rotation. "); - } - } - else { + else + c->Message(13, "Unable to remove bot from rotation."); + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(0, "#bot healrotation removemember "); return; } @@ -14979,7 +12688,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 3 || sep->argnum == 4) { Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -14995,35 +12703,27 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Mob* target = nullptr; std::string targetName = std::string(sep->arg[4]); - if(!targetName.empty()) target = entity_list.GetMob(targetName.c_str()); else { - if(c->GetTarget() != nullptr) { + if(c->GetTarget() != nullptr) target = c->GetTarget(); - } } if(target) { - //add target - if(leaderBot->AddHealRotationTarget(target)) { + if(leaderBot->AddHealRotationTarget(target)) c->Message(0, "Bot heal rotation target added successfully."); - } - else { - c->Message(13, "Unable to add rotation target. "); - } - } - else { + else + c->Message(13, "Unable to add rotation target."); + } else { c->Message(13, "Invalid target."); return; } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(0, "#bot healrotation addtarget [bot healrotation target name to add] "); return; } @@ -15033,7 +12733,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 4) { Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -15049,7 +12748,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { Mob* target; std::string targetName = std::string(sep->arg[4]); - if(!targetName.empty()) target = entity_list.GetMob(targetName.c_str()); else { @@ -15058,21 +12756,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } if(target) { - //add to rotation - if(leaderBot->RemoveHealRotationTarget(target)) { + if(leaderBot->RemoveHealRotationTarget(target)) c->Message(0, "Bot heal rotation target removed successfully."); - } - else { - c->Message(13, "Unable to remove rotation target. "); - } + else + c->Message(13, "Unable to remove rotation target."); } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(0, "#bot healrotation removetarget "); return; } @@ -15082,36 +12775,27 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 3) { if(!strcasecmp(sep->arg[3], "all")) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { Bot* leaderBot = *botListItr; if(leaderBot->GetInHealRotation() && leaderBot->GetHealRotationLeader() == leaderBot) { - //start all heal rotations std::list rotationMemberList; int index = 0; - rotationMemberList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator rotationMemberItr = rotationMemberList.begin(); rotationMemberItr != rotationMemberList.end(); ++rotationMemberItr) { Bot* tempBot = *rotationMemberItr; - if(tempBot) { tempBot->SetHealRotationActive(true); tempBot->SetHealRotationNextHealTime(Timer::GetCurrentTime() + index * leaderBot->GetHealRotationTimer() * 1000); tempBot->SetHasHealedThisCycle(false); } - index++; } - c->Message(0, "Bot heal rotation started successfully."); } } - } - else { + } else { Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -15128,28 +12812,22 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot) { tempBot->SetHealRotationActive(true); tempBot->SetHealRotationNextHealTime(Timer::GetCurrentTime() + index * leaderBot->GetHealRotationTimer() * 1000); tempBot->SetHasHealedThisCycle(false); } - index++; } - c->Message(0, "Bot heal rotation started successfully."); - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } } - } - else { + } else { c->Message(0, "#bot healrotation start "); return; } @@ -15159,32 +12837,24 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 3) { if(!strcasecmp(sep->arg[3], "all")) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { Bot* leaderBot = *botListItr; if(leaderBot->GetInHealRotation() && leaderBot->GetHealRotationLeader() == leaderBot) { - //start all heal rotations std::list rotationMemberList; - rotationMemberList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator rotationMemberItr = rotationMemberList.begin(); rotationMemberItr != rotationMemberList.end(); ++rotationMemberItr) { Bot* tempBot = *rotationMemberItr; - if(tempBot) { tempBot->SetHealRotationActive(false); tempBot->SetHasHealedThisCycle(false); } } - c->Message(0, "Bot heal rotation started successfully."); } } - } - else { + } else { Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -15200,10 +12870,8 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) { tempBot->SetHealRotationActive(false); tempBot->SetHasHealedThisCycle(false); @@ -15211,14 +12879,12 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } c->Message(0, "Bot heal rotation stopped successfully."); - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } } - } - else { + } else { c->Message(0, "#bot healrotation stop "); return; } @@ -15229,21 +12895,15 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { bool showAll = false; Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!strcasecmp(sep->arg[3], "all")) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot->GetInHealRotation() && tempBot->GetHealRotationLeader() == tempBot) { - //list leaders and number of bots per rotation + if(tempBot->GetInHealRotation() && tempBot->GetHealRotationLeader() == tempBot) c->Message(0, "Bot Heal Rotation- Leader: %s, Number of Members: %i, Timer: %1.1f", tempBot->GetCleanName(), tempBot->GetNumHealRotationMembers(), (float)(tempBot->GetHealRotationTimer()/1000)); - } } - } - else { + } else { std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -15259,43 +12919,31 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } botList = GetBotsInHealRotation(leaderBot); - - //list leader and number of members c->Message(0, "Bot Heal Rotation- Leader: %s", leaderBot->GetCleanName()); c->Message(0, "Bot Heal Rotation- Timer: %1.1f", ((float)leaderBot->GetHealRotationTimer()/1000.0f)); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) { - //list rotation members + if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) c->Message(0, "Bot Heal Rotation- Member: %s", tempBot->GetCleanName()); - } } - for(int i=0; iGetHealRotationTarget(i)) { Mob* tempTarget = leaderBot->GetHealRotationTarget(i); - if(tempTarget) { std::string targetInfo = ""; - targetInfo += tempTarget->GetHPRatio() < 0 ? "(dead) " : ""; targetInfo += tempTarget->GetZoneID() != leaderBot->GetZoneID() ? "(not in zone) " : ""; - - //list targets c->Message(0, "Bot Heal Rotation- Target: %s %s", tempTarget->GetCleanName(), targetInfo.c_str()); } } } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } } - } - else { + } else { c->Message(0, "#bot healrotation list "); return; } @@ -15305,7 +12953,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 3) { Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -15321,20 +12968,16 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) tempBot->ClearHealRotationTargets(); } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(0, "#bot healrotation cleartargets "); return; } @@ -15344,7 +12987,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(sep->argnum == 3) { Bot* leaderBot; std::string botName = std::string(sep->arg[3]); - if(!botName.empty()) leaderBot = entity_list.GetBotByBotName(botName); else { @@ -15360,7 +13002,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { return; } - //get percentage heals & target if(!strcasecmp(sep->arg[4], "on")) fastHeals = true; else if(strcasecmp(sep->arg[4], "off")) { @@ -15369,36 +13010,22 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } botList = GetBotsInHealRotation(leaderBot); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == c->CharacterID()) tempBot->SetHealRotationUseFastHeals(fastHeals); } - } - else { + } else { c->Message(13, "You must name a valid bot."); return; } - } - else { + } else { c->Message(0, "#bot healrotation fastheals "); return; } } - - if(!strcasecmp(sep->arg[2], "load")) { - } - - if(!strcasecmp(sep->arg[2], "save")) { - } - - if(!strcasecmp(sep->arg[2], "delete")) { - } } - // #bot setinspectmessage if(!strcasecmp(sep->arg[1], "setinspectmessage")) { if(!strcasecmp(sep->arg[2], "help")) { c->Message(0, "[Titanium clients:]"); @@ -15412,20 +13039,15 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { c->Message(0, "- Close the self-inspect window to update the server"); c->Message(0, "- Target a bot that you own and wish to update"); c->Message(0, "- type #bot setinspectmessage to set the bot's message"); - } - else { + } else { Mob *target = c->GetTarget(); - if(target && target->IsBot() && (c == target->GetOwner()->CastToClient())) { const InspectMessage_Struct& playermessage = c->GetInspectMessage(); InspectMessage_Struct& botmessage = target->CastToBot()->GetInspectMessage(); - memcpy(&botmessage, &playermessage, sizeof(InspectMessage_Struct)); database.SetBotInspectMessage(target->CastToBot()->GetBotID(), &botmessage); - c->Message(0, "Bot %s's inspect message now reflects your inspect message.", target->GetName()); - } - else { + } else { c->Message(0, "Your target must be a bot that you own."); } } @@ -15433,7 +13055,6 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { if(!strcasecmp(sep->arg[1], "bardoutofcombat")) { bool useOutOfCombatSongs = false; - if(sep->arg[2] && sep->arg[3]){ if(!strcasecmp(sep->arg[2], "on")) useOutOfCombatSongs = true; @@ -15445,45 +13066,53 @@ void Bot::ProcessBotCommands(Client *c, const Seperator *sep) { } Mob *target = c->GetTarget(); - if(target && target->IsBot() && (c == target->GetOwner()->CastToClient())) { Bot* bardBot = target->CastToBot(); - if(bardBot) { bardBot->SetBardUseOutOfCombatSongs(useOutOfCombatSongs); c->Message(0, "Bard use of out of combat songs updated."); } - } - else { + } else c->Message(0, "Your target must be a bot that you own."); - } - } - else { + } else c->Message(0, "Usage #bot bardoutofcombat [on|off]"); - } + return; + } + + if(!strcasecmp(sep->arg[1], "showhelm")) { + bool showhelm = true; + if (sep->arg[2]) { + if (!strcasecmp(sep->arg[2], "on")) + showhelm = true; + else if (!strcasecmp(sep->arg[2], "off")) + showhelm = false; + else { + c->Message(0, "Usage #bot showhelm [on|off]"); + return; + } + + Mob *target = c->GetTarget(); + if (target && target->IsBot() && (c == target->GetOwner()->CastToClient())) { + Bot* b = target->CastToBot(); + if (b) { + b->SetShowHelm(showhelm); + c->Message(0, "Your bot will %s show their helmet.", (showhelm ? "now" : "no longer")); + } + } + } else + c->Message(0, "Usage #bot showhelm [on|off]"); + return; } } -// franck: EQoffline -// This function has been reworked for the caster bots, when engaged. -// Healers bots must heal thoses who loose HP. bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint16 iSpellTypes) { - if((iSpellTypes&SpellTypes_Detrimental) != 0) { - //according to live, you can buff and heal through walls... - //now with PCs, this only applies if you can TARGET the target, but - // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. - // - // This check was put in to address an idle-mob CPU issue Log.Out(Logs::General, Logs::Error, "Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); - return(false); + return false; } - if(!caster) - return false; - - if(!caster->AI_HasSpells()) + if(!caster || !caster->AI_HasSpells()) return false; if (iChance < 100) { @@ -15495,30 +13124,22 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl uint8 botCasterClass = caster->GetClass(); if( iSpellTypes == SpellType_Heal ) { - // Changed so heal based on health percentage is different for hybrids if( botCasterClass == CLERIC || botCasterClass == DRUID || botCasterClass == SHAMAN) { - //If AI_EngagedCastCheck() said to the healer that he had to heal - - // check in group if(caster->HasGroup()) { Group *g = caster->GetGroup(); - if(g) { for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { if(g->members[i] && !g->members[i]->qglobal) { if(g->members[i]->IsClient() && g->members[i]->GetHPRatio() < 90) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } - else if((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < 95) { + } else if((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < 95) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } - else if(g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < 80) { + } else if(g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < 80) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } - else if(g->members[i]->GetHPRatio() < 70) { + } else if(g->members[i]->GetHPRatio() < 70) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; } @@ -15534,26 +13155,13 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } } - - // TODO: raid heals } - // Changed so heal based on health percentage is different for hybrids if( botCasterClass == PALADIN || botCasterClass == BEASTLORD || botCasterClass == RANGER) { - //If AI_EngagedCastCheck() said to the healer that he had to heal - - // check in group if(caster->HasGroup()) { Group *g = caster->GetGroup(); - float hpRatioToHeal = 25.0f; - - switch(caster->GetBotStance()) - { - case BotStanceAggressive: - case BotStanceEfficient: - hpRatioToHeal = 25.0f; - break; + switch(caster->GetBotStance()) { case BotStanceReactive: case BotStanceBalanced: hpRatioToHeal = 50.0f; @@ -15562,6 +13170,8 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl case BotStanceBurnAE: hpRatioToHeal = 20.0f; break; + case BotStanceAggressive: + case BotStanceEfficient: default: hpRatioToHeal = 25.0f; break; @@ -15573,16 +13183,13 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl if(g->members[i]->IsClient() && g->members[i]->GetHPRatio() < hpRatioToHeal) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } - else if((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < hpRatioToHeal) { + } else if((g->members[i]->GetClass() == WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) && g->members[i]->GetHPRatio() < hpRatioToHeal) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } - else if(g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < hpRatioToHeal) { + } else if(g->members[i]->GetClass() == ENCHANTER && g->members[i]->GetHPRatio() < hpRatioToHeal) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; - } - else if(g->members[i]->GetHPRatio() < hpRatioToHeal/2) { + } else if(g->members[i]->GetHPRatio() < hpRatioToHeal/2) { if(caster->AICastSpell(g->members[i], 100, SpellType_Heal)) return true; } @@ -15598,15 +13205,11 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } } - - // TODO: raid heals } } - //Ok for the buffs.. if( iSpellTypes == SpellType_Buff) { - uint8 chanceToCast = caster->IsEngaged()?caster->GetChanceToCastBySpellType(SpellType_Buff):100; - // Let's try to make Bard working... + uint8 chanceToCast = caster->IsEngaged() ? caster->GetChanceToCastBySpellType(SpellType_Buff) : 100; if(botCasterClass == BARD) { if(caster->AICastSpell(caster, chanceToCast, SpellType_Buff)) return true; @@ -15616,29 +13219,22 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl if(caster->HasGroup()) { Group *g = caster->GetGroup(); - if(g) { - for( int i = 0; i < MAX_GROUP_MEMBERS; i++) { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { if(g->members[i]) { - if(caster->AICastSpell(g->members[i], chanceToCast, SpellType_Buff)) - return true; - - if(caster->AICastSpell(g->members[i]->GetPet(), chanceToCast, SpellType_Buff)) + if(caster->AICastSpell(g->members[i], chanceToCast, SpellType_Buff) || caster->AICastSpell(g->members[i]->GetPet(), chanceToCast, SpellType_Buff)) return true; } } } } - - // TODO: raid buffs } if( iSpellTypes == SpellType_Cure) { if(caster->HasGroup()) { Group *g = caster->GetGroup(); - if(g) { - for( int i = 0; i < MAX_GROUP_MEMBERS; i++) { + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { if(g->members[i] && caster->GetNeedsCured(g->members[i])) { if(caster->AICastSpell(g->members[i], caster->GetChanceToCastBySpellType(SpellType_Cure), SpellType_Cure)) return true; @@ -15653,157 +13249,128 @@ bool EntityList::Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, fl } } } - - // TODO: raid buffs } - return false; } Mob* EntityList::GetMobByBotID(uint32 botID) { Mob* Result = 0; - if(botID > 0) { auto it = mob_list.begin(); - - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - if(!it->second) continue; + for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { + if(!it->second) + continue; + if(it->second->IsBot() && it->second->CastToBot()->GetBotID() == botID) { Result = it->second; break; } } } - return Result; } Bot* EntityList::GetBotByBotID(uint32 botID) { Bot* Result = 0; - if(botID > 0) { for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotID() == botID) { Result = tempBot; break; } } } - return Result; } Bot* EntityList::GetBotByBotName(std::string botName) { Bot* Result = 0; - if(!botName.empty()) { for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && std::string(tempBot->GetName()) == botName) { Result = tempBot; break; } } } - return Result; } void EntityList::AddBot(Bot *newBot, bool SendSpawnPacket, bool dontqueue) { if(newBot) { newBot->SetID(GetFreeID()); - if(SendSpawnPacket) { if(dontqueue) { - // Send immediately EQApplicationPacket* outapp = new EQApplicationPacket(); newBot->CreateSpawnPacket(outapp); outapp->priority = 6; QueueClients(newBot, outapp, true); safe_delete(outapp); - } - else { - // Queue the packet + } else { NewSpawn_Struct* ns = new NewSpawn_Struct; memset(ns, 0, sizeof(NewSpawn_Struct)); newBot->FillSpawnStruct(ns, newBot); AddToSpawnQueue(newBot->GetID(), &ns); safe_delete(ns); } - parse->EventNPC(EVENT_SPAWN, newBot, nullptr, "", 0); } - bot_list.push_back(newBot); - mob_list.insert(std::pair(newBot->GetID(), newBot)); } } std::list EntityList::GetBotsByBotOwnerCharacterID(uint32 botOwnerCharacterID) { std::list Result; - if(botOwnerCharacterID > 0) { for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetBotOwnerCharacterID() == botOwnerCharacterID) Result.push_back(tempBot); } } - return Result; } -void EntityList::BotPickLock(Bot* rogue) -{ +void EntityList::BotPickLock(Bot* rogue) { for (auto it = door_list.begin(); it != door_list.end(); ++it) { Doors *cdoor = it->second; if(!cdoor || cdoor->IsDoorOpen()) continue; - auto diff = rogue->GetPosition() - cdoor->GetPosition(); - - float curdist = diff.x * diff.x + diff.y * diff.y; - - if((diff.z * diff.z >= 10) || (curdist > 130)) + auto diff = (rogue->GetPosition() - cdoor->GetPosition()); + float curdist = ((diff.x * diff.x) + (diff.y * diff.y)); + if(((diff.z * diff.z) >= 10) || curdist > 130) continue; - // All rogue items with lock pick bonuses are hands or primary const ItemInst* item1 = rogue->GetBotItem(MainHands); const ItemInst* item2 = rogue->GetBotItem(MainPrimary); - float bonus1 = 0.0f; float bonus2 = 0.0f; float skill = rogue->GetSkill(SkillPickLock); - - if(item1) // Hand slot item + if(item1) if(item1->GetItem()->SkillModType == SkillPickLock) - bonus1 = skill * (((float)item1->GetItem()->SkillModValue) / 100.0f); + bonus1 = (skill * (((float)item1->GetItem()->SkillModValue) / 100.0f)); - if(item2) // Primary slot item + if(item2) if(item2->GetItem()->SkillModType == SkillPickLock) - bonus2 = skill * (((float)item2->GetItem()->SkillModValue) / 100.0f); + bonus2 = (skill * (((float)item2->GetItem()->SkillModValue) / 100.0f)); - if((skill+bonus1+bonus2) >= cdoor->GetLockpick()) + if((skill + bonus1 + bonus2) >= cdoor->GetLockpick()) cdoor->ForceOpen(rogue); else - rogue->Say("I am not skilled enough for this lock."); + rogue->BotGroupSay(rogue, "I am not skilled enough for this lock."); } } bool EntityList::RemoveBot(uint16 entityID) { bool Result = false; - if(entityID > 0) { - for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) - { + for(std::list::iterator botListItr = bot_list.begin(); botListItr != bot_list.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot && tempBot->GetID() == entityID) { bot_list.erase(botListItr); Result = true; @@ -15811,23 +13378,17 @@ bool EntityList::RemoveBot(uint16 entityID) { } } } - return Result; } void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { - const char *WindowTitle = "Bot Tracking Window"; - std::string WindowText; int LastCon = -1; int CurrentCon = 0; Mob* curMob = nullptr; - uint32 array_counter = 0; - auto it = mob_list.begin(); - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { curMob = it->second; if (curMob && DistanceNoZ(curMob->GetPosition(), client->GetPosition()) <= Distance) { @@ -15866,36 +13427,34 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { "stone_","lava_","_","" }; unsigned int MyArraySize; - for ( MyArraySize = 0; true; MyArraySize++) { //Find empty string & get size - if (!(*(MyArray[MyArraySize]))) break; //Checks for null char in 1st pos + for ( MyArraySize = 0; true; MyArraySize++) { + if (!(*(MyArray[MyArraySize]))) + break; }; if (NamedOnly) { bool ContinueFlag = false; - const char *CurEntityName = cur_entity->GetName(); //Call function once + const char *CurEntityName = cur_entity->GetName(); for (int Index = 0; Index < MyArraySize; Index++) { if (!strncasecmp(CurEntityName, MyArray[Index], strlen(MyArray[Index])) || (Extras)) { ContinueFlag = true; - break; //From Index for + break; }; }; - if (ContinueFlag) continue; //Moved here or would apply to Index for + if (ContinueFlag) + continue; }; CurrentCon = client->GetLevelCon(cur_entity->GetLevel()); if(CurrentCon != LastCon) { - if(LastCon != -1) WindowText += ""; LastCon = CurrentCon; - switch(CurrentCon) { - case CON_GREEN: { WindowText += ""; break; } - case CON_LIGHTBLUE: { WindowText += ""; break; @@ -15904,7 +13463,6 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { WindowText += ""; break; } - case CON_YELLOW: { WindowText += ""; break; @@ -15919,55 +13477,43 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) { } } } - WindowText += cur_entity->GetCleanName(); WindowText += "
"; - if(strlen(WindowText.c_str()) > 4000) { - // Popup window is limited to 4096 characters. - WindowText += "


List truncated ... too many mobs to display"; + WindowText += "


List truncated... too many mobs to display"; break; } } } } WindowText += "
"; - client->SendPopupToClient(WindowTitle, WindowText.c_str()); - return; } uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets) { uint8 needHealed = 0; Group *g; - if(this->HasGroup()) { g = this->GetGroup(); - if(g) { for( int i = 0; imembers[i] && !g->members[i]->qglobal) { - if(g->members[i]->GetHPRatio() <= hpr) needHealed++; if(includePets) { - if(g->members[i]->GetPet() && g->members[i]->GetPet()->GetHPRatio() <= hpr) { + if(g->members[i]->GetPet() && g->members[i]->GetPet()->GetHPRatio() <= hpr) needHealed++; - } } } } } } - return needHealed; } -uint32 Bot::GetEquipmentColor(uint8 material_slot) const -{ - //Bot tints +uint32 Bot::GetEquipmentColor(uint8 material_slot) const { int16 slotid = 0; uint32 botid = this->GetBotID(); @@ -15976,9 +13522,7 @@ uint32 Bot::GetEquipmentColor(uint8 material_slot) const if (slotid == INVALID_INDEX) return 0; - //read from db - std::string query = StringFormat("SELECT color FROM botinventory " - "WHERE BotID = %u AND SlotID = %u", botid, slotid); + std::string query = StringFormat("SELECT color FROM botinventory WHERE BotID = %u AND SlotID = %u", botid, slotid); auto results = database.QueryDatabase(query); if (!results.Success() || results.RowCount() != 1) return 0; @@ -15987,21 +13531,16 @@ uint32 Bot::GetEquipmentColor(uint8 material_slot) const return atoul(row[0]); } -int Bot::GetRawACNoShield(int &shield_ac) -{ +int Bot::GetRawACNoShield(int &shield_ac) { int ac = itembonuses.AC + spellbonuses.AC; shield_ac = 0; ItemInst* inst = GetBotItem(MainSecondary); - if(inst) - { - if(inst->GetItem()->ItemType == ItemTypeShield) - { + if(inst) { + if(inst->GetItem()->ItemType == ItemTypeShield) { ac -= inst->GetItem()->AC; shield_ac = inst->GetItem()->AC; - for (uint8 i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; i++) - { - if(inst->GetAugment(i)) - { + for (uint8 i = AUG_BEGIN; i < EmuConstants::ITEM_COMMON_SIZE; i++) { + if(inst->GetAugment(i)) { ac -= inst->GetAugment(i)->GetItem()->AC; shield_ac += inst->GetAugment(i)->GetItem()->AC; } @@ -16012,38 +13551,32 @@ int Bot::GetRawACNoShield(int &shield_ac) } uint32 Bot::CalcCurrentWeight() { - const ItemData* TempItem = 0; ItemInst* inst; uint32 Total = 0; - for(int i = EmuConstants::EQUIPMENT_BEGIN; i <= EmuConstants::EQUIPMENT_END; ++i) { inst = GetBotItem(i); if(inst) { - TempItem = inst->GetItem(); - if (TempItem) - Total += TempItem->Weight; + TempItem = inst->GetItem(); + if (TempItem) + Total += TempItem->Weight; } } - float Packrat = (float)spellbonuses.Packrat + (float)aabonuses.Packrat; - + float Packrat = ((float)spellbonuses.Packrat + (float)aabonuses.Packrat); if (Packrat > 0) - Total = (uint32)((float)Total * (1.0f - ((Packrat * 1.0f) / 100.0f))); //AndMetal: 1% per level, up to 5% (calculated from Titanium client). verified thru client that it reduces coin weight by the same % - //without casting to float & back to uint32, this didn't work right - + Total = (uint32)((float)Total * (1.0f - ((Packrat * 1.0f) / 100.0f))); + return Total; } -int Bot::GroupLeadershipAAHealthEnhancement() -{ +int Bot::GroupLeadershipAAHealthEnhancement() { Group *g = GetGroup(); if(!g || (g->GroupCount() < 3)) return 0; - switch(g->GetLeadershipAA(groupAAHealthEnhancement)) - { + switch(g->GetLeadershipAA(groupAAHealthEnhancement)) { case 0: return 0; case 1: @@ -16053,19 +13586,15 @@ int Bot::GroupLeadershipAAHealthEnhancement() case 3: return 100; } - return 0; } -int Bot::GroupLeadershipAAManaEnhancement() -{ +int Bot::GroupLeadershipAAManaEnhancement() { Group *g = GetGroup(); - if(!g || (g->GroupCount() < 3)) return 0; - switch(g->GetLeadershipAA(groupAAManaEnhancement)) - { + switch(g->GetLeadershipAA(groupAAManaEnhancement)) { case 0: return 0; case 1: @@ -16075,19 +13604,15 @@ int Bot::GroupLeadershipAAManaEnhancement() case 3: return 100; } - return 0; } -int Bot::GroupLeadershipAAHealthRegeneration() -{ +int Bot::GroupLeadershipAAHealthRegeneration() { Group *g = GetGroup(); - if(!g || (g->GroupCount() < 3)) return 0; - switch(g->GetLeadershipAA(groupAAHealthRegeneration)) - { + switch(g->GetLeadershipAA(groupAAHealthRegeneration)) { case 0: return 0; case 1: @@ -16101,15 +13626,13 @@ int Bot::GroupLeadershipAAHealthRegeneration() return 0; } -int Bot::GroupLeadershipAAOffenseEnhancement() -{ +int Bot::GroupLeadershipAAOffenseEnhancement() { Group *g = GetGroup(); if(!g || (g->GroupCount() < 3)) return 0; - switch(g->GetLeadershipAA(groupAAOffenseEnhancement)) - { + switch(g->GetLeadershipAA(groupAAOffenseEnhancement)) { case 0: return 0; case 1: @@ -16128,23 +13651,16 @@ int Bot::GroupLeadershipAAOffenseEnhancement() bool Bot::GetNeedsCured(Mob *tar) { bool needCured = false; - if(tar) { if(tar->FindType(SE_PoisonCounter) || tar->FindType(SE_DiseaseCounter) || tar->FindType(SE_CurseCounter) || tar->FindType(SE_CorruptionCounter)) { uint32 buff_count = GetMaxTotalSlots(); int buffsWithCounters = 0; needCured = true; - for (unsigned int j = 0; j < buff_count; j++) { if(tar->GetBuffs()[j].spellid != SPELL_UNKNOWN) { if(CalculateCounters(tar->GetBuffs()[j].spellid) > 0) { buffsWithCounters++; - if(buffsWithCounters == 1 && (tar->GetBuffs()[j].ticsremaining < 2 || (int32)((tar->GetBuffs()[j].ticsremaining * 6) / tar->GetBuffs()[j].counters) < 2)) { - // Spell has ticks remaining but may have too many counters to cure in the time remaining; - // We should try to just wait it out. Could spend entire time trying to cure spell instead of healing, buffing, etc. - // Since this is the first buff with counters, don't try to cure. Cure spell will be wasted, as cure will try to - // remove counters from the first buff that has counters remaining. needCured = false; break; } @@ -16153,27 +13669,23 @@ bool Bot::GetNeedsCured(Mob *tar) { } } } - return needCured; } bool Bot::HasOrMayGetAggro() { bool mayGetAggro = false; - if(GetTarget() && GetTarget()->GetHateTop()) { Mob *topHate = GetTarget()->GetHateTop(); - if(topHate == this) - mayGetAggro = true; //I currently have aggro + mayGetAggro = true; else { uint32 myHateAmt = GetTarget()->GetHateAmount(this); uint32 topHateAmt = GetTarget()->GetHateAmount(topHate); - if(myHateAmt > 0 && topHateAmt > 0 && (uint8)((myHateAmt/topHateAmt)*100) > 90) //I have 90% as much hate as top, next action may give me aggro + if(myHateAmt > 0 && topHateAmt > 0 && (uint8)((myHateAmt / topHateAmt) * 100) > 90) mayGetAggro = true; } } - return mayGetAggro; } @@ -16181,115 +13693,56 @@ void Bot::SetHasBeenSummoned(bool wasSummoned) { _hasBeenSummoned = wasSummoned; if(!wasSummoned) m_PreSummonLocation = glm::vec3(); - } void Bot::SetDefaultBotStance() { - BotStanceType defaultStance; - - switch(GetClass()) - { - case DRUID: - case CLERIC: - case SHAMAN: - case ENCHANTER: - case NECROMANCER: - case MAGICIAN: - case WIZARD: - case BEASTLORD: - case BERSERKER: - case MONK: - case ROGUE: - case BARD: - case SHADOWKNIGHT: - case PALADIN: - case RANGER: - defaultStance = BotStanceBalanced; - break; - case WARRIOR: - defaultStance = BotStanceAggressive; - break; - default: - defaultStance = BotStanceBalanced; - break; - } + BotStanceType defaultStance = BotStanceBalanced; + if (GetClass() == WARRIOR) + defaultStance = BotStanceAggressive; + _baseBotStance = BotStancePassive; _botStance = defaultStance; } -void Bot::BotGroupSay(Mob *speaker, const char *msg, ...) -{ - +void Bot::BotGroupSay(Mob *speaker, const char *msg, ...) { char buf[1000]; va_list ap; - va_start(ap, msg); vsnprintf(buf, 1000, msg, ap); va_end(ap); - if(speaker->HasGroup()) { Group *g = speaker->GetGroup(); - if(g) g->GroupMessage(speaker->CastToMob(), 0, 100, buf); - } + } else + speaker->Say("%s", buf); } bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { - //make sure we have the spell... - int r; - /*for(r = 0; r < MAX_PP_DISCIPLINES; r++) { - if(m_pp.disciplines.values[r] == spell_id) - break; - } - if(r == MAX_PP_DISCIPLINES) - return(false); //not found. - - //Check the disc timer - pTimerType DiscTimer = pTimerDisciplineReuseStart + spells[spell_id].EndurTimerIndex; - if(!p_timers.Expired(&database, DiscTimer)) { - uint32 remain = p_timers.GetRemainingTime(DiscTimer); - //Message_StringID(0, DISCIPLINE_CANUSEIN, ConvertArray((remain)/60,val1), ConvertArray(remain%60,val2)); - Message(0, "You can use this discipline in %d minutes %d seconds.", ((remain)/60), (remain%60)); - return(false); - }*/ - - //make sure we can use it.. if(!IsValidSpell(spell_id)) { - Say("Not a valid spell"); - return(false); + BotGroupSay(this, "Not a valid spell."); + return false; } - //can we use the spell? const SPDat_Spell_Struct &spell = spells[spell_id]; uint8 level_to_use = spell.classes[GetClass() - 1]; - if(level_to_use == 255) { - return(false); + if(level_to_use == 255 || level_to_use > GetLevel()) { + return false; } - if(level_to_use > GetLevel()) { - return(false); - } - - if(GetEndurance() > spell.EndurCost) { + if(GetEndurance() > spell.EndurCost) SetEndurance(GetEndurance() - spell.EndurCost); - } else { - return(false); - } + else + return false; - if(spell.recast_time > 0) - { + if(spell.recast_time > 0) { if(CheckDisciplineRecastTimers(this, spells[spell_id].EndurTimerIndex)) { - - //CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); - if(spells[spell_id].EndurTimerIndex > 0 && spells[spell_id].EndurTimerIndex < MAX_DISCIPLINE_TIMERS) { + if(spells[spell_id].EndurTimerIndex > 0 && spells[spell_id].EndurTimerIndex < MAX_DISCIPLINE_TIMERS) SetDisciplineRecastTimer(spells[spell_id].EndurTimerIndex, spell.recast_time); - } - } - else { - uint32 remain = GetDisciplineRemainingTime(this, spells[spell_id].EndurTimerIndex) / 1000; - GetOwner()->Message(0, "%s can use this discipline in %d minutes %d seconds.", GetCleanName(), ((remain)/60), (remain%60)); - return(false); + } else { + uint32 remain = (GetDisciplineRemainingTime(this, spells[spell_id].EndurTimerIndex) / 1000); + GetOwner()->Message(0, "%s can use this discipline in %d minutes %d seconds.", GetCleanName(), (remain / 60), (remain % 60)); + return false; } } @@ -16297,8 +13750,7 @@ bool Bot::UseDiscipline(uint32 spell_id, uint32 target) { InterruptSpell(); CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); - - return(true); + return true; } void Bot::CreateHealRotation( Mob* target, uint32 timer ) { @@ -16310,7 +13762,6 @@ void Bot::CreateHealRotation( Mob* target, uint32 timer ) { SetPrevHealRotationMember(this); SetHealRotationTimer(timer); SetHasHealedThisCycle(false); - if(target) AddHealRotationTarget(target); } @@ -16319,13 +13770,11 @@ bool Bot::AddHealRotationMember( Bot* healer ) { if(healer) { if(GetNumHealRotationMembers() > 0 && GetNumHealRotationMembers() < MaxHealRotationMembers) { Bot* tempBot = GetPrevHealRotationMember(); - if(tempBot) { - //add new healer to rotation at end of list - for(int i=0; i<3; i++){ + for(int i = 0; i < 3; i++){ healer->ClearHealRotationMembers(); healer->ClearHealRotationTargets(); - healer->AddHealRotationTarget(entity_list.GetMob(_healRotationTargets[i])); // add all targets.. + healer->AddHealRotationTarget(entity_list.GetMob(_healRotationTargets[i])); } healer->SetHealRotationTimer(tempBot->GetHealRotationTimer()); healer->SetHealRotationLeader(this); @@ -16334,27 +13783,18 @@ bool Bot::AddHealRotationMember( Bot* healer ) { healer->SetInHealRotation(true); healer->SetHasHealedThisCycle(false); healer->SetHealRotationUseFastHeals(tempBot->GetHealRotationUseFastHeals()); - - //set previous rotation member's next member to new member tempBot->SetNextHealRotationMember(healer); - - //update leader's previous member (end of list) to new member and update rotation data SetPrevHealRotationMember(healer); - std::list botList = GetBotsInHealRotation(this); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot) tempBot->SetNumHealRotationMembers(GetNumHealRotationMembers()+1); } - return true; } } } - return false; } @@ -16363,15 +13803,11 @@ bool Bot::RemoveHealRotationMember( Bot* healer ) { Bot* leader = healer->GetHealRotationLeader(); Bot* prevBot = healer->GetPrevHealRotationMember(); Bot* nextBot = healer->GetNextHealRotationMember(); - if(healer == this) { - if(nextBot != this) { - //get new leader + if(nextBot != this) leader = nextBot; - } } - //remove healer from list healer->SetHealRotationTimer(0); healer->ClearHealRotationMembers(); healer->ClearHealRotationTargets(); @@ -16379,34 +13815,22 @@ bool Bot::RemoveHealRotationMember( Bot* healer ) { healer->SetHasHealedThisCycle(false); healer->SetHealRotationActive(false); healer->SetInHealRotation(false); - if(prevBot && nextBot && GetNumHealRotationMembers() > 1) { - //set previous rotation member's next member to new member prevBot->SetNextHealRotationMember(nextBot); - - //set previous rotation member's next member to new member nextBot->SetPrevHealRotationMember(prevBot); } - //update rotation data std::list botList = GetBotsInHealRotation(leader); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot) { - tempBot->SetNumHealRotationMembers(GetNumHealRotationMembers()-1); - - if(tempBot->GetHealRotationLeader() != leader) { - // change leader if leader is being removed + tempBot->SetNumHealRotationMembers(GetNumHealRotationMembers() - 1); + if(tempBot->GetHealRotationLeader() != leader) tempBot->SetHealRotationLeader(leader); - } } } - return true; } - return false; } @@ -16425,67 +13849,56 @@ void Bot::SetPrevHealRotationMember( Bot* healer ) { Bot* Bot::GetHealRotationLeader( ) { if(_healRotationLeader) return entity_list.GetBotByBotID(_healRotationLeader); + return 0; } Bot* Bot::GetNextHealRotationMember( ) { if(_healRotationMemberNext) return entity_list.GetBotByBotID(_healRotationMemberNext); + return 0; } Bot* Bot::GetPrevHealRotationMember( ) { if(_healRotationMemberNext) return entity_list.GetBotByBotID(_healRotationMemberPrev); + return 0; } bool Bot::AddHealRotationTarget( Mob* target ) { if(target) { - for (int i = 0; i < MaxHealRotationTargets; ++i) { if(_healRotationTargets[i] > 0) { Mob* tempTarget = entity_list.GetMob(_healRotationTargets[i]); - if(!tempTarget) { _healRotationTargets[i] = 0; - } - else if(!strcasecmp(tempTarget->GetCleanName(), target->GetCleanName())) { - //check to see if target's ID is incorrect (could have zoned, died, etc) - if(tempTarget->GetID() != target->GetID()) { + } else if(!strcasecmp(tempTarget->GetCleanName(), target->GetCleanName())) { + if(tempTarget->GetID() != target->GetID()) _healRotationTargets[i] = target->GetID(); - } - //target already in list + return false; } } - if (_healRotationTargets[i] == 0) - { + if (_healRotationTargets[i] == 0) { std::list botList = GetBotsInHealRotation(this); - _healRotationTargets[i] = target->GetID(); - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - - if(tempBot && tempBot != this) { - //add target to all members + if(tempBot && tempBot != this) tempBot->AddHealRotationTarget(target, i); - } } - return true; } } } - return false; } bool Bot::AddHealRotationTarget( Mob *target, int index ) { if (target && index < MaxHealRotationTargets) { - //add target to list of targets at specified index _healRotationTargets[index] = target->GetID(); return true; } @@ -16496,37 +13909,30 @@ bool Bot::RemoveHealRotationTarget( Mob* target ) { int index = 0; bool removed = false; if(target) { - //notify all heal rotation members to remove target - for(int i=0; iGetID()) { std::list botList = GetBotsInHealRotation(this); _healRotationTargets[i] = 0; index = i; removed = true; - for(std::list::iterator botListItr = botList.begin(); botListItr != botList.end(); ++botListItr) { Bot* tempBot = *botListItr; - if(tempBot) tempBot->RemoveHealRotationTarget(i); } } } } - return removed; } bool Bot::RemoveHealRotationTarget( int index ) { if(index >= 0) { - //clear rotation target at index _healRotationTargets[index] = 0; - if(index < MaxHealRotationTargets) { - for(int i=index; i 0) { - - //get first target in list target = entity_list.GetMob(_healRotationTargets[i]); - if(target) { - //check if valid target - if(target->GetZoneID() == GetZoneID() - && !(target->GetAppearance() == eaDead - && !(target->IsClient() && target->CastToClient()->GetFeigned()))) { - + if(target->GetZoneID() == GetZoneID() && !(target->GetAppearance() == eaDead && !(target->IsClient() && target->CastToClient()->GetFeigned()))) { count++; - - //get first valid target - if(!first) { + if(!first) first = target; - } - //check to see if target is group main tank - //(target first, in case top target has died and was rez'd - - //we don't want to heal them then) if(!tank) { Group* g = target->GetGroup(); - if(g && !strcasecmp(g->GetMainTankName(), target->GetCleanName())) { + if(g && !strcasecmp(g->GetMainTankName(), target->GetCleanName())) tank = target; - } } } - } - else { - //if not valid target, remove from list + } else { if(removeIndex == 0) removeIndex = i; } } } - if (removeIndex > 0) { - RemoveHealRotationTarget( removeIndex ); - } + if (removeIndex > 0) + RemoveHealRotationTarget(removeIndex); if(tank) return tank; @@ -16603,35 +13990,27 @@ Mob* Bot::GetHealRotationTarget( ) { Mob* Bot::GetHealRotationTarget( uint8 index ) { Mob* target = nullptr; - - if(_healRotationTargets[index] > 0) { - //get target at specified index + if(_healRotationTargets[index] > 0) target = entity_list.GetMob(_healRotationTargets[index]); - } return target; } std::list Bot::GetBotsInHealRotation(Bot* rotationLeader) { std::list Result; - if(rotationLeader != nullptr) { Result.push_back(rotationLeader); Bot* rotationMember = rotationLeader->GetNextHealRotationMember(); - while(rotationMember && rotationMember != rotationLeader) { Result.push_back(rotationMember); rotationMember = rotationMember->GetNextHealRotationMember(); } } - return Result; } void Bot::NotifyNextHealRotationMember(bool notifyNow) { - //check if we need to notify to start now, or after timer uint32 nextHealTime = notifyNow ? Timer::GetCurrentTime() : Timer::GetCurrentTime() + GetHealRotationTimer(); - Bot* nextMember = GetNextHealRotationMember(); if(nextMember && nextMember != this) { nextMember->SetHealRotationNextHealTime(nextHealTime); @@ -16642,11 +14021,9 @@ void Bot::NotifyNextHealRotationMember(bool notifyNow) { void Bot::BotHealRotationsClear(Client* c) { if(c) { std::list BotList = entity_list.GetBotsByBotOwnerCharacterID(c->CharacterID()); - for(std::list::iterator botListItr = BotList.begin(); botListItr != BotList.end(); ++botListItr) { Bot* tempBot = *botListItr; if(tempBot->GetInHealRotation()) { - //clear all heal rotation data for bots in a heal rotation tempBot->SetInHealRotation(false); tempBot->SetHealRotationActive(false); tempBot->SetHasHealedThisCycle(false); diff --git a/zone/bot.h b/zone/bot.h index 03681454a..c81a22a09 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -60,6 +60,7 @@ enum SpellTypeIndex { }; class Bot : public NPC { + friend class Mob; public: // Class enums enum BotfocusType { //focus types @@ -154,7 +155,7 @@ public: // Class Methods bool IsValidRaceClassCombo(); bool IsValidName(); - bool IsBotNameAvailable(std::string* errorMessage); + static bool IsBotNameAvailable(char *botName, std::string* errorMessage); bool DeleteBot(std::string* errorMessage); void Spawn(Client* botCharacterOwner, std::string* errorMessage); virtual void SetLevel(uint8 in_level, bool command = false); @@ -190,7 +191,9 @@ public: bool CanDoSpecialAttack(Mob *other); virtual int32 CheckAggroAmount(uint16 spellid); virtual void CalcBonuses(); - void CalcItemBonuses(); + void CalcItemBonuses(StatBonuses* newbon); + void AddItemBonuses(const ItemInst *inst, StatBonuses* newbon, bool isAug = false, bool isTribute = false); + int CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat); virtual void MakePet(uint16 spell_id, const char* pettype, const char *petname = nullptr); virtual FACTION_VALUE GetReverseFactionCon(Mob* iOther); inline virtual bool IsPet() { return false; } @@ -310,7 +313,7 @@ public: virtual int32 GetActSpellDuration(uint16 spell_id, int32 duration); virtual float GetAOERange(uint16 spell_id); virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100); - virtual void DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster = 0); + virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr); virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, int16 *resist_adjust = nullptr); virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); @@ -464,6 +467,7 @@ public: uint32 GetHealRotationNextHealTime() { return _healRotationNextHeal; } uint32 GetHealRotationTimer () { return _healRotationTimer; } bool GetBardUseOutOfCombatSongs() { return _bardUseOutOfCombatSongs;} + bool GetShowHelm() { return _showhelm; } inline virtual int32 GetAC() const { return AC; } inline virtual int32 GetSTR() const { return STR; } inline virtual int32 GetSTA() const { return STA; } @@ -547,6 +551,7 @@ public: void SetHealRotationTimer( uint32 timer ) { _healRotationTimer = timer; } void SetNumHealRotationMembers( uint8 numMembers ) { _numHealRotationMembers = numMembers; } void SetBardUseOutOfCombatSongs(bool useOutOfCombatSongs) { _bardUseOutOfCombatSongs = useOutOfCombatSongs;} + void SetShowHelm(bool showhelm) { _showhelm = showhelm; } // Class Destructors virtual ~Bot(); @@ -619,6 +624,7 @@ private: std::map botAAs; InspectMessage_Struct _botInspectMessage; bool _bardUseOutOfCombatSongs; + bool _showhelm; // Private "base stats" Members int32 _baseMR; diff --git a/zone/client.cpp b/zone/client.cpp index 34b16a142..0274ba6d6 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -116,7 +116,7 @@ Client::Client(EQStreamInterface* ieqs) ), //these must be listed in the order they appear in client.h position_timer(250), - hpupdate_timer(1800), + hpupdate_timer(2000), camp_timer(29000), process_timer(100), stamina_timer(40000), @@ -208,6 +208,7 @@ Client::Client(EQStreamInterface* ieqs) npclevel = 0; pQueuedSaveWorkID = 0; position_timer_counter = 0; + position_update_same_count = 0; fishing_timer.Disable(); shield_timer.Disable(); dead_timer.Disable(); @@ -549,17 +550,22 @@ bool Client::SaveAA(){ } } m_pp.aapoints_spent = spentpoints + m_epp.expended_aa; + int highest = 0; for (int a = 0; a < MAX_PP_AA_ARRAY; a++) { - if (aa[a]->AA > 0 && aa[a]->value){ + if (aa[a]->AA > 0) { // those with value 0 will be cleaned up on next load if (first_entry != 1){ - rquery = StringFormat("REPLACE INTO `character_alternate_abilities` (id, slot, aa_id, aa_value)" - " VALUES (%u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value); + rquery = StringFormat("REPLACE INTO `character_alternate_abilities` (id, slot, aa_id, aa_value, charges)" + " VALUES (%u, %u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value, aa[a]->charges); first_entry = 1; + } else { + rquery = rquery + StringFormat(", (%u, %u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value, aa[a]->charges); } - rquery = rquery + StringFormat(", (%u, %u, %u, %u)", character_id, a, aa[a]->AA, aa[a]->value); + highest = a; } } auto results = database.QueryDatabase(rquery); + /* This is another part of the hack to clean up holes left by expendable AAs */ + rquery = StringFormat("DELETE FROM `character_alternate_abilities` WHERE `id` = %u AND `slot` > %d", character_id, highest); return true; } @@ -1051,12 +1057,12 @@ void Client::ChannelMessageReceived(uint8 chan_num, uint8 language, uint8 lang_s if(quest_manager.ProximitySayInUse()) entity_list.ProcessProximitySay(message, this, language); - if (GetTarget() != 0 && GetTarget()->IsNPC()) { + if (GetTarget() != 0 && GetTarget()->IsNPC() && + !IsInvisible(GetTarget())) { if(!GetTarget()->CastToNPC()->IsEngaged()) { CheckLDoNHail(GetTarget()); CheckEmoteHail(GetTarget(), message); - if(DistanceSquaredNoZ(m_Position, GetTarget()->GetPosition()) <= 200) { NPC *tar = GetTarget()->CastToNPC(); parse->EventNPC(EVENT_SAY, tar->CastToNPC(), this, message, language); @@ -2540,12 +2546,12 @@ void Client::LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 bool Client::BindWound(Mob* bindmob, bool start, bool fail){ EQApplicationPacket* outapp = 0; - if(!fail) + if(!fail) { outapp = new EQApplicationPacket(OP_Bind_Wound, sizeof(BindWound_Struct)); BindWound_Struct* bind_out = (BindWound_Struct*) outapp->pBuffer; // Start bind - if(!bindwound_timer.Enabled()) + if(!bindwound_timer.Enabled()) { //make sure we actually have a bandage... and consume it. int16 bslot = m_inv.HasItemByUse(ItemTypeBandage, 1, invWhereWorn|invWherePersonal); @@ -2592,9 +2598,9 @@ bool Client::BindWound(Mob* bindmob, bool start, bool fail){ ; // Binding self } } - } + } else if (bindwound_timer.Check()) // Did the timer finish? - { + { // finish bind // disable complete timer bindwound_timer.Disable(); @@ -4991,7 +4997,7 @@ void Client::SetShadowStepExemption(bool v) if((cur_time - m_TimeSinceLastPositionCheck) > 1000) { float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); - float runs = GetRunspeed(); + int runs = GetRunspeed(); if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) { printf("%s %i moving too fast! moved: %.2f in %ims, speed %.2f\n", __FILE__, __LINE__, @@ -5048,7 +5054,7 @@ void Client::SetKnockBackExemption(bool v) if((cur_time - m_TimeSinceLastPositionCheck) > 1000) { float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); - float runs = GetRunspeed(); + int runs = GetRunspeed(); if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) { if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) @@ -5105,7 +5111,7 @@ void Client::SetPortExemption(bool v) if((cur_time - m_TimeSinceLastPositionCheck) > 1000) { float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); - float runs = GetRunspeed(); + int runs = GetRunspeed(); if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) { if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) @@ -5709,8 +5715,8 @@ void Client::ProcessInspectRequest(Client* requestee, Client* requester) { else if (inst && inst->GetOrnamentationIcon()) { insr->itemicons[L] = inst->GetOrnamentationIcon(); - } - else + } + else { insr->itemicons[L] = item->Icon; } @@ -7491,6 +7497,10 @@ void Client::GarbleMessage(char *message, uint8 variance) const char delimiter = 0x12; int delimiter_count = 0; + // Don't garble # commands + if (message[0] == '#') + return; + for (size_t i = 0; i < strlen(message); i++) { // Client expects hex values inside of a text link body if (message[i] == delimiter) { @@ -7500,7 +7510,7 @@ void Client::GarbleMessage(char *message, uint8 variance) } uint8 chance = (uint8)zone->random.Int(0, 115); // variation just over worst possible scrambling - if (isalpha(message[i]) && (chance <= variance)) { + if (isalpha((unsigned char)message[i]) && (chance <= variance)) { uint8 rand_char = (uint8)zone->random.Int(0,51); // choose a random character from the alpha list message[i] = alpha_list[rand_char]; } @@ -7584,7 +7594,7 @@ FACTION_VALUE Client::GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_ra } //Sets the characters faction standing with the specified NPC. -void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity) +void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity, bool quest) { int32 faction_id[MAX_NPC_FACTIONS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int32 npc_value[MAX_NPC_FACTIONS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -7608,9 +7618,18 @@ void Client::SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, ui // Find out starting faction for this faction // It needs to be used to adj max and min personal // The range is still the same, 1200-3000(4200), but adjusted for base - database.GetFactionData(&fm, GetClass(), GetRace(), GetDeity(), + database.GetFactionData(&fm, GetClass(), GetRace(), GetDeity(), faction_id[i]); + if (quest) + { + //The ole switcheroo + if (npc_value[i] > 0) + npc_value[i] = -abs(npc_value[i]); + else if (npc_value[i] < 0) + npc_value[i] = abs(npc_value[i]); + } + // Adjust the amount you can go up or down so the resulting range // is PERSONAL_MAX - PERSONAL_MIN // @@ -7649,7 +7668,7 @@ void Client::SetFactionLevel2(uint32 char_id, int32 faction_id, uint8 char_class // Find out starting faction for this faction // It needs to be used to adj max and min personal // The range is still the same, 1200-3000(4200), but adjusted for base - database.GetFactionData(&fm, GetClass(), GetRace(), GetDeity(), + database.GetFactionData(&fm, GetClass(), GetRace(), GetDeity(), faction_id); // Adjust the amount you can go up or down so the resulting range @@ -7835,14 +7854,14 @@ void Client::SendFactionMessage(int32 tmpvalue, int32 faction_id, int32 faction_ char name[50]; int32 faction_value; - // If we're dropping from MAX or raising from MIN or repairing, + // If we're dropping from MAX or raising from MIN or repairing, // we should base the message on the new updated value so we don't show // a min MAX message // // If we're changing any other place, we use the value before the // hit. For example, if we go from 1199 to 1200 which is the MAX // we still want to say faction got better this time around. - + if ( (faction_before_hit >= this_faction_max) || (faction_before_hit <= this_faction_min)) faction_value = totalvalue; @@ -8378,10 +8397,10 @@ std::string Client::TextLink::GenerateLink() m_Link.clear(); m_LinkBody.clear(); m_LinkText.clear(); - + generate_body(); generate_text(); - + if ((m_LinkBody.length() == EmuConstants::TEXT_LINK_BODY_LENGTH) && (m_LinkText.length() > 0)) { m_Link.push_back(0x12); m_Link.append(m_LinkBody); @@ -8420,7 +8439,7 @@ void Client::TextLink::generate_body() { /* Current server mask: EQClientRoF2 - + RoF2: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X" (56) RoF: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X" (55) SoF: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X" (50) @@ -8428,7 +8447,6 @@ void Client::TextLink::generate_body() */ memset(&m_LinkBodyStruct, 0, sizeof(TextLinkBody_Struct)); - const ItemData* item_data = nullptr; switch (m_LinkType) { @@ -8475,7 +8493,7 @@ void Client::TextLink::generate_body() default: break; } - + if (m_ProxyItemID != NOT_USED) { m_LinkBodyStruct.item_id = m_ProxyItemID; } @@ -8580,3 +8598,42 @@ bool Client::TextLink::GenerateLinkBody(std::string& textLinkBody, const TextLin if (textLinkBody.length() != EmuConstants::TEXT_LINK_BODY_LENGTH) { return false; } return true; } + +void Client::QuestReward(Mob* target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp, bool faction) { + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sound, sizeof(QuestReward_Struct)); + memset(outapp->pBuffer, 0, sizeof(outapp->pBuffer)); + QuestReward_Struct* qr = (QuestReward_Struct*)outapp->pBuffer; + + qr->mob_id = target->GetID(); // Entity ID for the from mob name + qr->target_id = GetID(); // The Client ID (this) + qr->copper = copper; + qr->silver = silver; + qr->gold = gold; + qr->platinum = platinum; + qr->item_id = itemid; + qr->exp_reward = exp; + + if (copper > 0 || silver > 0 || gold > 0 || platinum > 0) + AddMoneyToPP(copper, silver, gold, platinum, false); + + if (itemid > 0) + SummonItem(itemid, 0, 0, 0, 0, 0, 0, false, MainPowerSource); + + if (faction) + { + if (target->IsNPC()) + { + int32 nfl_id = target->CastToNPC()->GetNPCFactionID(); + SetFactionLevel(CharacterID(), nfl_id, GetBaseClass(), GetBaseRace(), GetDeity(), true); + qr->faction = target->CastToNPC()->GetPrimaryFaction(); + qr->faction_mod = 1; // Too lazy to get real value, not sure if this is even used by client anyhow. + } + } + + if (exp > 0) + AddEXP(exp); + + QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + safe_delete(outapp); +} diff --git a/zone/client.h b/zone/client.h index 54cf45f12..f9a43aa2d 100644 --- a/zone/client.h +++ b/zone/client.h @@ -559,6 +559,7 @@ public: void SendCrystalCounts(); void AddEXP(uint32 in_add_exp, uint8 conlevel = 0xFF, bool resexp = false); + uint32 CalcEXP(uint8 conlevel = 0xFF); void SetEXP(uint32 set_exp, uint32 set_aaxp, bool resexp=false); void AddLevelBasedExp(uint8 exp_percentage, uint8 max_level=0); void SetLeadershipEXP(uint32 group_exp, uint32 raid_exp); @@ -613,7 +614,7 @@ public: void SendFactionMessage(int32 tmpvalue, int32 faction_id, int32 faction_before_hit, int32 totalvalue, uint8 temp, int32 this_faction_min, int32 this_faction_max); void UpdatePersonalFaction(int32 char_id, int32 npc_value, int32 faction_id, int32 *current_value, int32 temp, int32 this_faction_min, int32 this_faction_max); - void SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity); + void SetFactionLevel(uint32 char_id, uint32 npc_id, uint8 char_class, uint8 char_race, uint8 char_deity, bool quest = false); void SetFactionLevel2(uint32 char_id, int32 faction_id, uint8 char_class, uint8 char_race, uint8 char_deity, int32 value, uint8 temp); int32 GetRawItemAC(); uint16 GetCombinedAC_TEST(); @@ -930,6 +931,7 @@ public: bool DecreaseByID(uint32 type, uint8 amt); uint8 SlotConvert2(uint8 slot); //Maybe not needed. void Escape(); //AA Escape + void DisenchantSummonedBags(bool client_update = true); void RemoveNoRent(bool client_update = true); void RemoveDuplicateLore(bool client_update = true); void MoveSlotNotAllowed(bool client_update = true); @@ -1154,6 +1156,7 @@ public: inline bool IsDraggingCorpse() { return (DraggedCorpses.size() > 0); } void DragCorpses(); inline void ClearDraggedCorpses() { DraggedCorpses.clear(); } + inline void ResetPositionTimer() { position_timer_counter = 0; } void SendAltCurrencies(); void SetAlternateCurrencyValue(uint32 currency_id, uint32 new_amount); void AddAlternateCurrencyValue(uint32 currency_id, int32 amount, int8 method = 0); @@ -1279,6 +1282,9 @@ public: virtual int32 Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); int32 GetMeleeDamage(Mob* other, bool GetMinDamage = false); + void QuestReward(Mob* target, uint32 copper = 0, uint32 silver = 0, uint32 gold = 0, uint32 platinum = 0, uint32 itemid = 0, uint32 exp = 0, bool faction = false); + + void ResetHPUpdateTimer() { hpupdate_timer.Start(); } protected: friend class Mob; void CalcItemBonuses(StatBonuses* newbon); @@ -1466,6 +1472,9 @@ private: Timer position_timer; uint8 position_timer_counter; + // this is used to try to cut back on position update reflections + int position_update_same_count; + PTimerList p_timers; //persistent timers Timer hpupdate_timer; Timer camp_timer; diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 42f8b474b..2f7041c34 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1974,101 +1974,87 @@ int32 Client::CalcATK() uint32 Mob::GetInstrumentMod(uint16 spell_id) const { - if (GetClass() != BARD) { + if (GetClass() != BARD || spells[spell_id].IsDisciplineBuff) // Puretone is Singing but doesn't get any mod return 10; - } + uint32 effectmod = 10; int effectmodcap = RuleI(Character, BaseInstrumentSoftCap); - //this should never use spell modifiers... - //if a spell grants better modifers, they are copied into the item mods - //because the spells are supposed to act just like having the intrument. - //item mods are in 10ths of percent increases + // this should never use spell modifiers... + // if a spell grants better modifers, they are copied into the item mods + // because the spells are supposed to act just like having the intrument. + // item mods are in 10ths of percent increases + // clickies (Symphony of Battle) that have a song skill don't get AA bonus for some reason + // but clickies that are songs (selo's on Composers Greaves) do get AA mod as well switch (spells[spell_id].skill) { - case SkillPercussionInstruments: - if (itembonuses.percussionMod == 0 && spellbonuses.percussionMod == 0) { - effectmod = 10; - } - else if (GetSkill(SkillPercussionInstruments) == 0) { - effectmod = 10; - } - else if (itembonuses.percussionMod > spellbonuses.percussionMod) { - effectmod = itembonuses.percussionMod; - } - else { - effectmod = spellbonuses.percussionMod; - } - effectmod += aabonuses.percussionMod; - break; - case SkillStringedInstruments: - if (itembonuses.stringedMod == 0 && spellbonuses.stringedMod == 0) { - effectmod = 10; - } - else if (GetSkill(SkillStringedInstruments) == 0) { - effectmod = 10; - } - else if (itembonuses.stringedMod > spellbonuses.stringedMod) { - effectmod = itembonuses.stringedMod; - } - else { - effectmod = spellbonuses.stringedMod; - } - effectmod += aabonuses.stringedMod; - break; - case SkillWindInstruments: - if (itembonuses.windMod == 0 && spellbonuses.windMod == 0) { - effectmod = 10; - } - else if (GetSkill(SkillWindInstruments) == 0) { - effectmod = 10; - } - else if (itembonuses.windMod > spellbonuses.windMod) { - effectmod = itembonuses.windMod; - } - else { - effectmod = spellbonuses.windMod; - } - effectmod += aabonuses.windMod; - break; - case SkillBrassInstruments: - if (itembonuses.brassMod == 0 && spellbonuses.brassMod == 0) { - effectmod = 10; - } - else if (GetSkill(SkillBrassInstruments) == 0) { - effectmod = 10; - } - else if (itembonuses.brassMod > spellbonuses.brassMod) { - effectmod = itembonuses.brassMod; - } - else { - effectmod = spellbonuses.brassMod; - } - effectmod += aabonuses.brassMod; - break; - case SkillSinging: - if (itembonuses.singingMod == 0 && spellbonuses.singingMod == 0) { - effectmod = 10; - } - else if (itembonuses.singingMod > spellbonuses.singingMod) { - effectmod = itembonuses.singingMod; - } - else { - effectmod = spellbonuses.singingMod; - } - effectmod += aabonuses.singingMod + spellbonuses.Amplification; - break; - default: + case SkillPercussionInstruments: + if (itembonuses.percussionMod == 0 && spellbonuses.percussionMod == 0) effectmod = 10; - break; + else if (GetSkill(SkillPercussionInstruments) == 0) + effectmod = 10; + else if (itembonuses.percussionMod > spellbonuses.percussionMod) + effectmod = itembonuses.percussionMod; + else + effectmod = spellbonuses.percussionMod; + if (IsBardSong(spell_id)) + effectmod += aabonuses.percussionMod; + break; + case SkillStringedInstruments: + if (itembonuses.stringedMod == 0 && spellbonuses.stringedMod == 0) + effectmod = 10; + else if (GetSkill(SkillStringedInstruments) == 0) + effectmod = 10; + else if (itembonuses.stringedMod > spellbonuses.stringedMod) + effectmod = itembonuses.stringedMod; + else + effectmod = spellbonuses.stringedMod; + if (IsBardSong(spell_id)) + effectmod += aabonuses.stringedMod; + break; + case SkillWindInstruments: + if (itembonuses.windMod == 0 && spellbonuses.windMod == 0) + effectmod = 10; + else if (GetSkill(SkillWindInstruments) == 0) + effectmod = 10; + else if (itembonuses.windMod > spellbonuses.windMod) + effectmod = itembonuses.windMod; + else + effectmod = spellbonuses.windMod; + if (IsBardSong(spell_id)) + effectmod += aabonuses.windMod; + break; + case SkillBrassInstruments: + if (itembonuses.brassMod == 0 && spellbonuses.brassMod == 0) + effectmod = 10; + else if (GetSkill(SkillBrassInstruments) == 0) + effectmod = 10; + else if (itembonuses.brassMod > spellbonuses.brassMod) + effectmod = itembonuses.brassMod; + else + effectmod = spellbonuses.brassMod; + if (IsBardSong(spell_id)) + effectmod += aabonuses.brassMod; + break; + case SkillSinging: + if (itembonuses.singingMod == 0 && spellbonuses.singingMod == 0) + effectmod = 10; + else if (itembonuses.singingMod > spellbonuses.singingMod) + effectmod = itembonuses.singingMod; + else + effectmod = spellbonuses.singingMod; + if (IsBardSong(spell_id)) + effectmod += aabonuses.singingMod + spellbonuses.Amplification; + break; + default: + effectmod = 10; + return effectmod; } effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; - if (effectmod < 10) { + if (effectmod < 10) effectmod = 10; - } - if (effectmod > effectmodcap) { + if (effectmod > effectmodcap) effectmod = effectmodcap; - } - Log.Out(Logs::Detail, Logs::Spells, "%s::GetInstrumentMod() spell=%d mod=%d modcap=%d\n", - GetName(), spell_id, effectmod, effectmodcap); + Log.Out(Logs::Detail, Logs::Spells, "%s::GetInstrumentMod() spell=%d mod=%d modcap=%d\n", GetName(), spell_id, + effectmod, effectmodcap); return effectmod; } diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index a24ab3979..829612a12 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -306,6 +306,8 @@ void MapOpcodes() ConnectedOpcodes[OP_PetitionRefresh] = &Client::Handle_OP_PetitionRefresh; ConnectedOpcodes[OP_PetitionResolve] = &Client::Handle_OP_PetitionResolve; ConnectedOpcodes[OP_PetitionUnCheckout] = &Client::Handle_OP_PetitionUnCheckout; + ConnectedOpcodes[OP_PlayerStateAdd] = &Client::Handle_OP_PlayerStateAdd; + ConnectedOpcodes[OP_PlayerStateRemove] = &Client::Handle_OP_PlayerStateRemove; ConnectedOpcodes[OP_PickPocket] = &Client::Handle_OP_PickPocket; ConnectedOpcodes[OP_PopupResponse] = &Client::Handle_OP_PopupResponse; ConnectedOpcodes[OP_PotionBelt] = &Client::Handle_OP_PotionBelt; @@ -703,7 +705,7 @@ void Client::CompleteConnect() case SE_AddMeleeProc: case SE_WeaponProc: { - AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid); + AddProcToWeapon(GetProcID(buffs[j1].spellid, x1), false, 100 + spells[buffs[j1].spellid].base2[x1], buffs[j1].spellid, buffs[j1].casterlevel); break; } case SE_DefensiveProc: @@ -1444,22 +1446,32 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) if (m_pp.ldon_points_available < 0 || m_pp.ldon_points_available > 2000000000){ m_pp.ldon_points_available = 0; } /* Initialize AA's : Move to function eventually */ - for (uint32 a = 0; a < MAX_PP_AA_ARRAY; a++){ aa[a] = &m_pp.aa_array[a]; } + for (uint32 a = 0; a < MAX_PP_AA_ARRAY; a++) + aa[a] = &m_pp.aa_array[a]; query = StringFormat( "SELECT " "slot, " "aa_id, " - "aa_value " + "aa_value, " + "charges " "FROM " "`character_alternate_abilities` " "WHERE `id` = %u ORDER BY `slot`", this->CharacterID()); results = database.QueryDatabase(query); i = 0; + int offset = 0; // offset to fix the hole from expendables for (auto row = results.begin(); row != results.end(); ++row) { - i = atoi(row[0]); + i = atoi(row[0]) - offset; m_pp.aa_array[i].AA = atoi(row[1]); m_pp.aa_array[i].value = atoi(row[2]); - aa[i]->AA = atoi(row[1]); - aa[i]->value = atoi(row[2]); + m_pp.aa_array[i].charges = atoi(row[3]); + /* A used expendable could cause there to be a "hole" in the array, this is very bad. Bad things like keeping your expendable after use. + We could do a few things, one of them being reshuffling when the hole is created or defer the fixing until a later point, like during load! + Or just never making a hole in the array and just have hacks every where. Fixing the hole at load really just keeps 1 hack in Client::SendAATable + and keeping this offset that will cause the next AA to be pushed back over the hole. We also need to clean up on save so we don't have multiple + entries for a single AA. + */ + if (m_pp.aa_array[i].value == 0) + offset++; } for (uint32 a = 0; a < MAX_PP_AA_ARRAY; a++){ uint32 id = aa[a]->AA; @@ -1498,7 +1510,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) for (int i = 0; i < max_slots; i++) { if (buffs[i].spellid != SPELL_UNKNOWN) { m_pp.buffs[i].spellid = buffs[i].spellid; - m_pp.buffs[i].bard_modifier = 10; + m_pp.buffs[i].bard_modifier = buffs[i].instrument_mod; m_pp.buffs[i].slotid = 2; m_pp.buffs[i].player_id = 0x2211; m_pp.buffs[i].level = buffs[i].casterlevel; @@ -1707,7 +1719,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app) /* Time of Day packet */ outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); TimeOfDay_Struct* tod = (TimeOfDay_Struct*)outapp->pBuffer; - zone->zone_time.getEQTimeOfDay(time(0), tod); + zone->zone_time.GetCurrentEQTimeOfDay(time(0), tod); outapp->priority = 6; FastQueuePacket(&outapp); @@ -4129,7 +4141,7 @@ void Client::Handle_OP_ClickObject(const EQApplicationPacket *app) char buf[10]; snprintf(buf, 9, "%u", click_object->drop_id); buf[9] = '\0'; - parse->EventPlayer(EVENT_CLICK_OBJECT, this, buf, 0, &args); + parse->EventPlayer(EVENT_CLICK_OBJECT, this, buf, GetID(), &args); } // Observed in RoF after OP_ClickObjectAction: @@ -4267,7 +4279,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) if((cur_time - m_TimeSinceLastPositionCheck) > 0) { float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); - float runs = GetRunspeed(); + int runs = GetRunspeed(); if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) { if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) @@ -4335,7 +4347,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) if((cur_time - m_TimeSinceLastPositionCheck) > 2500) { float speed = (m_DistanceSinceLastPositionCheck * 100) / (float)(cur_time - m_TimeSinceLastPositionCheck); - float runs = GetRunspeed(); + int runs = GetRunspeed(); if(speed > (runs * RuleR(Zone, MQWarpDetectionDistanceFactor))) { if(!GetGMSpeed() && (runs >= GetBaseRunspeed() || (speed > (GetBaseRunspeed() * RuleR(Zone, MQWarpDetectionDistanceFactor))))) @@ -4448,9 +4460,20 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) // Outgoing client packet float tmpheading = EQ19toFloat(ppu->heading); + /* The clients send an update at best every 1.3 seconds + * We want to avoid reflecting these updates to other clients as much as possible + * The client also sends an update every 280 ms while turning, if we prevent + * sending these by checking if the location is the same too aggressively, clients end up spinning + * so keep a count of how many packets are the same within a tolerance and stop when we get there */ - if (!FCMP(ppu->y_pos, m_Position.y) || !FCMP(ppu->x_pos, m_Position.x) || !FCMP(tmpheading, m_Position.w) || ppu->animation != animation) + bool pos_same = FCMP(ppu->y_pos, m_Position.y) && FCMP(ppu->x_pos, m_Position.x) && FCMP(tmpheading, m_Position.w) && ppu->animation == animation; + if (!pos_same || (pos_same && position_update_same_count < 6)) { + if (pos_same) + position_update_same_count++; + else + position_update_same_count = 0; + m_Position.x = ppu->x_pos; m_Position.y = ppu->y_pos; m_Position.z = ppu->z_pos; @@ -6355,15 +6378,25 @@ void Client::Handle_OP_GroupDisband(const EQApplicationPacket *app) Bot::ProcessBotGroupDisband(this, std::string()); } else { - Mob* tempMember = entity_list.GetMob(gd->name2); - if (tempMember) { - if (tempMember->IsBot()) - Bot::ProcessBotGroupDisband(this, std::string(tempMember->GetCleanName())); + Mob* tempMember = entity_list.GetMob(gd->name1); //Name1 is the target you are disbanding + if (tempMember && tempMember->IsBot()) { + tempMember->CastToBot()->RemoveBotFromGroup(tempMember->CastToBot(), group); + if (LFP) + { + // If we are looking for players, update to show we are on our own now. + UpdateLFP(); + } + return; //No need to continue from here we were removing a bot from party } } } } + + group = GetGroup(); + if (!group) //We must recheck this here.. incase the final bot disbanded the party..otherwise we crash + return; #endif + if (group->GroupCount() < 3) { group->DisbandGroup(); @@ -9705,6 +9738,7 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) char val1[20] = { 0 }; PetCommand_Struct* pet = (PetCommand_Struct*)app->pBuffer; Mob* mypet = this->GetPet(); + Mob *target = entity_list.GetMob(pet->target); if (!mypet || pet->command == PET_LEADER) { @@ -9752,22 +9786,22 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) switch (PetCommand) { case PET_ATTACK: { - if (!GetTarget()) + if (!target) break; - if (GetTarget()->IsMezzed()) { - Message_StringID(10, CANNOT_WAKE, mypet->GetCleanName(), GetTarget()->GetCleanName()); + if (target->IsMezzed()) { + Message_StringID(10, CANNOT_WAKE, mypet->GetCleanName(), target->GetCleanName()); break; } if (mypet->IsFeared()) break; //prevent pet from attacking stuff while feared - if (!mypet->IsAttackAllowed(GetTarget())) { + if (!mypet->IsAttackAllowed(target)) { mypet->Say_StringID(NOT_LEGAL_TARGET); break; } if ((mypet->GetPetType() == petAnimation && GetAA(aaAnimationEmpathy) >= 2) || mypet->GetPetType() != petAnimation) { - if (GetTarget() != this && DistanceSquaredNoZ(mypet->GetPosition(), GetTarget()->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { + if (target != this && DistanceSquaredNoZ(mypet->GetPosition(), target->GetPosition()) <= (RuleR(Pets, AttackCommandRange)*RuleR(Pets, AttackCommandRange))) { if (mypet->IsHeld()) { if (!mypet->IsFocused()) { mypet->SetHeld(false); //break the hold and guard if we explicitly tell the pet to attack. @@ -9775,12 +9809,12 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app) mypet->SetPetOrder(SPO_Follow); } else { - mypet->SetTarget(GetTarget()); + mypet->SetTarget(target); } } zone->AddAggroMob(); - mypet->AddToHateList(GetTarget(), 1); - Message_StringID(MT_PetResponse, PET_ATTACKING, mypet->GetCleanName(), GetTarget()->GetCleanName()); + mypet->AddToHateList(target, 1); + Message_StringID(MT_PetResponse, PET_ATTACKING, mypet->GetCleanName(), target->GetCleanName()); } } break; @@ -10275,6 +10309,31 @@ void Client::Handle_OP_PetitionUnCheckout(const EQApplicationPacket *app) return; } +void Client::Handle_OP_PlayerStateAdd(const EQApplicationPacket *app) +{ + if (app->size != sizeof(PlayerState_Struct)) { + std::cout << "Wrong size: OP_PlayerStateAdd, size=" << app->size << ", expected " << sizeof(PlayerState_Struct) << std::endl; + return; + } + + PlayerState_Struct *ps = (PlayerState_Struct *)app->pBuffer; + AddPlayerState(ps->state); + + entity_list.QueueClients(this, app, true); +} + +void Client::Handle_OP_PlayerStateRemove(const EQApplicationPacket *app) +{ + if (app->size != sizeof(PlayerState_Struct)) { + std::cout << "Wrong size: OP_PlayerStateRemove, size=" << app->size << ", expected " << sizeof(PlayerState_Struct) << std::endl; + return; + } + PlayerState_Struct *ps = (PlayerState_Struct *)app->pBuffer; + RemovePlayerState(ps->state); + + entity_list.QueueClients(this, app, true); +} + void Client::Handle_OP_PickPocket(const EQApplicationPacket *app) { if (app->size != sizeof(PickPocket_Struct)) @@ -12745,7 +12804,7 @@ void Client::Handle_OP_TargetCommand(const EQApplicationPacket *app) inspect_buffs = group->GetLeadershipAA(groupAAInspectBuffs); } } - if (nt == this || inspect_buffs || (nt->IsClient() && !nt->CastToClient()->GetPVP()) || + if (GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs) || nt == this || inspect_buffs || (nt->IsClient() && !nt->CastToClient()->GetPVP()) || (nt->IsPet() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP()) || (nt->IsMerc() && nt->GetOwner() && nt->GetOwner()->IsClient() && !nt->GetOwner()->CastToClient()->GetPVP())) nt->SendBuffsToClient(this); diff --git a/zone/client_packet.h b/zone/client_packet.h index 51b6713b7..1a9591f4c 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -218,6 +218,8 @@ void Handle_OP_PetitionRefresh(const EQApplicationPacket *app); void Handle_OP_PetitionResolve(const EQApplicationPacket *app); void Handle_OP_PetitionUnCheckout(const EQApplicationPacket *app); + void Handle_OP_PlayerStateAdd(const EQApplicationPacket *app); + void Handle_OP_PlayerStateRemove(const EQApplicationPacket *app); void Handle_OP_PickPocket(const EQApplicationPacket *app); void Handle_OP_PopupResponse(const EQApplicationPacket *app); void Handle_OP_PotionBelt(const EQApplicationPacket *app); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index ba30b7abe..21bcdbceb 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -129,7 +129,9 @@ bool Client::Process() { if(IsTracking() && (GetClientVersion() >= ClientVersion::SoD) && TrackingTimer.Check()) DoTracking(); - if(hpupdate_timer.Check()) + // SendHPUpdate calls hpupdate_timer.Start so it can delay this timer, so lets not reset with the check + // since the function will anyways + if(hpupdate_timer.Check(false)) SendHPUpdate(); if(mana_timer.Check()) @@ -197,10 +199,8 @@ bool Client::Process() { instalog = true; } - if (IsStunned() && stunned_timer.Check()) { - this->stunned = false; - this->stunned_timer.Disable(); - } + if (IsStunned() && stunned_timer.Check()) + Mob::UnStun(); if(!m_CheatDetectMoved) { @@ -262,7 +262,7 @@ bool Client::Process() { } if(light_update_timer.Check()) { - + UpdateEquipmentLight(); if(UpdateActiveLight()) { SendAppearancePacket(AT_Light, GetActiveLightType()); @@ -562,7 +562,7 @@ bool Client::Process() { } ProjectileAttack(); - + if(spellbonuses.GravityEffect == 1) { if(gravity_timer.Check()) DoGravityEffect(); @@ -793,7 +793,7 @@ void Client::OnDisconnect(bool hard_disconnect) { Mob *Other = trade->With(); if(Other) { - Log.Out(Logs::Detail, Logs::Trading, "Client disconnected during a trade. Returning their items."); + Log.Out(Logs::Detail, Logs::Trading, "Client disconnected during a trade. Returning their items."); FinishTrade(this); if(Other->IsClient()) diff --git a/zone/command.cpp b/zone/command.cpp index 22600ede1..7a351234a 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -1369,7 +1369,7 @@ void command_date(Client *c, const Seperator *sep) else { int h=0, m=0; TimeOfDay_Struct eqTime; - zone->zone_time.getEQTimeOfDay( time(0), &eqTime); + zone->zone_time.GetCurrentEQTimeOfDay( time(0), &eqTime); if(!sep->IsNumber(4)) h=eqTime.hour; else @@ -1402,7 +1402,7 @@ void command_timezone(Client *c, const Seperator *sep) // Update all clients with new TZ. EQApplicationPacket* outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); TimeOfDay_Struct* tod = (TimeOfDay_Struct*)outapp->pBuffer; - zone->zone_time.getEQTimeOfDay(time(0), tod); + zone->zone_time.GetCurrentEQTimeOfDay(time(0), tod); entity_list.QueueClients(c, outapp); safe_delete(outapp); } @@ -1484,7 +1484,7 @@ void command_npcstats(Client *c, const Seperator *sep) c->Message(0, "Current HP: %i Max HP: %i", c->GetTarget()->GetHP(), c->GetTarget()->GetMaxHP()); //c->Message(0, "Weapon Item Number: %s", c->GetTarget()->GetWeapNo()); c->Message(0, "Gender: %i Size: %f Bodytype: %d", c->GetTarget()->GetGender(), c->GetTarget()->GetSize(), c->GetTarget()->GetBodyType()); - c->Message(0, "Runspeed: %f Walkspeed: %f", c->GetTarget()->GetRunspeed(), c->GetTarget()->GetWalkspeed()); + c->Message(0, "Runspeed: %i Walkspeed: %i", c->GetTarget()->GetRunspeed(), c->GetTarget()->GetWalkspeed()); c->Message(0, "Spawn Group: %i Grid: %i", c->GetTarget()->CastToNPC()->GetSp2(), c->GetTarget()->CastToNPC()->GetGrid()); c->Message(0, "EmoteID: %i", c->GetTarget()->CastToNPC()->GetEmoteID()); c->GetTarget()->CastToNPC()->QueryLoot(c); @@ -4393,7 +4393,7 @@ void command_time(Client *c, const Seperator *sep) else { c->Message(13, "To set the Time: #time HH [MM]"); TimeOfDay_Struct eqTime; - zone->zone_time.getEQTimeOfDay( time(0), &eqTime); + zone->zone_time.GetCurrentEQTimeOfDay( time(0), &eqTime); sprintf(timeMessage,"%02d:%s%d %s (Timezone: %ih %im)", ((eqTime.hour - 1) % 12) == 0 ? 12 : ((eqTime.hour - 1) % 12), (eqTime.minute < 10) ? "0" : "", @@ -8565,20 +8565,20 @@ void command_object(Client *c, const Seperator *sep) od.object_type = atoi(row[7]); icon = atoi(row[8]); - od.unknown008 = atoi(row[9]); - od.unknown010 = atoi(row[10]); + od.size = atoi(row[9]); + od.solidtype = atoi(row[10]); od.unknown020 = atoi(row[11]); switch (od.object_type) { case 0: // Static Object case staticType: // Static Object unlocked for changes - if (od.unknown008 == 0) // Unknown08 field is optional Size parameter for static objects - od.unknown008 = 100; // Static object default Size is 100% + if (od.size == 0) // Unknown08 field is optional Size parameter for static objects + od.size = 100; // Static object default Size is 100% c->Message(0, "- STATIC Object (%s): id %u, x %.1f, y %.1f, z %.1f, h %.1f, model %s, " "size %u, solidtype %u, incline %u", (od.object_type == 0) ? "locked" : "unlocked", id, od.x, od.y, od.z, - od.heading, od.object_name, od.unknown008, od.unknown010, od.unknown020); + od.heading, od.object_name, od.size, od.solidtype, od.unknown020); break; case OT_DROPPEDITEM: // Ground Spawn @@ -8636,10 +8636,10 @@ void command_object(Client *c, const Seperator *sep) switch (od.object_type) { case 0: // Static Object if ((sep->argnum - col) > 3) { - od.unknown008 = atoi(sep->arg[4 + col]); // Size specified + od.size = atoi(sep->arg[4 + col]); // Size specified if ((sep->argnum - col) > 4) { - od.unknown010 = atoi(sep->arg[5 + col]); // SolidType specified + od.solidtype = atoi(sep->arg[5 + col]); // SolidType specified if ((sep->argnum - col) > 5) od.unknown020 = atoi(sep->arg[6 + col]); // Incline specified @@ -8938,16 +8938,16 @@ void command_object(Client *c, const Seperator *sep) return; } - od.unknown008 = atoi(sep->arg[4]); + od.size = atoi(sep->arg[4]); o->SetObjectData(&od); - if (od.unknown008 == 0) // 0 == unspecified == 100% - od.unknown008 = 100; + if (od.size == 0) // 0 == unspecified == 100% + od.size = 100; c->Message(0, "Static Object %u set to %u%% size. Size will take effect when you commit to the " "database with '#object Save', after which the object will be unchangeable until " "you unlock it again with '#object Edit' and zone out and back in.", - id, od.unknown008); + id, od.size); } else if (strcmp(sep->arg[3], "solidtype") == 0) { if (od.object_type != staticType) { @@ -8962,13 +8962,13 @@ void command_object(Client *c, const Seperator *sep) return; } - od.unknown010 = atoi(sep->arg[4]); + od.solidtype = atoi(sep->arg[4]); o->SetObjectData(&od); c->Message(0, "Static Object %u set to SolidType %u. Change will take effect when you commit " "to the database with '#object Save'. Support for this property is on a " "per-model basis, mostly seen in smaller objects such as chests and tables.", - id, od.unknown010); + id, od.solidtype); } else if (strcmp(sep->arg[3], "icon") == 0) { if ((od.object_type < 2) || (od.object_type == staticType)) { @@ -9255,24 +9255,24 @@ void command_object(Client *c, const Seperator *sep) "unknown08 = %u, unknown10 = %u, unknown20 = %u " "WHERE ID = %u", zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, - od.heading, od.object_name, od.object_type, icon, od.unknown008, - od.unknown010, od.unknown020, id); + od.heading, od.object_name, od.object_type, icon, od.size, + od.solidtype, od.unknown020, id); else if (id == 0) query = StringFormat("INSERT INTO object " "(zoneid, version, xpos, ypos, zpos, heading, objectname, " "type, icon, unknown08, unknown10, unknown20) " "VALUES (%u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)", zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, - od.heading, od.object_name, od.object_type, icon, od.unknown008, - od.unknown010, od.unknown020); + od.heading, od.object_name, od.object_type, icon, od.size, + od.solidtype, od.unknown020); else query = StringFormat("INSERT INTO object " "(id, zoneid, version, xpos, ypos, zpos, heading, objectname, " "type, icon, unknown08, unknown10, unknown20) " "VALUES (%u, %u, %u, %.1f, %.1f, %.1f, %.1f, '%s', %u, %u, %u, %u, %u)", id, zone->GetZoneID(), zone->GetInstanceVersion(), od.x, od.y, od.z, - od.heading, od.object_name, od.object_type, icon, od.unknown008, - od.unknown010, od.unknown020); + od.heading, od.object_name, od.object_type, icon, od.size, + od.solidtype, od.unknown020); results = database.QueryDatabase(query); if (!results.Success()) { @@ -9330,12 +9330,12 @@ void command_object(Client *c, const Seperator *sep) memcpy(door.dest_zone, "NONE", 5); - if ((door.size = od.unknown008) == 0) // unknown08 = optional size percentage + if ((door.size = od.size) == 0) // unknown08 = optional size percentage door.size = 100; switch ( door.opentype = - od.unknown010) // unknown10 = optional request_nonsolid (0 or 1 or experimental number) + od.solidtype) // unknown10 = optional request_nonsolid (0 or 1 or experimental number) { case 0: door.opentype = 31; @@ -9592,8 +9592,8 @@ void command_object(Client *c, const Seperator *sep) strn0cpy(od.object_name, row[4], sizeof(od.object_name)); od.object_type = atoi(row[5]); icon = atoi(row[6]); - od.unknown008 = atoi(row[7]); - od.unknown010 = atoi(row[8]); + od.size = atoi(row[7]); + od.solidtype = atoi(row[8]); od.unknown020 = atoi(row[9]); if (od.object_type == 0) diff --git a/zone/common.h b/zone/common.h index 56ab6f819..d9e3637a8 100644 --- a/zone/common.h +++ b/zone/common.h @@ -173,6 +173,18 @@ enum class NumHit { // Numhits type OffensiveSpellProcs = 11 // Offensive buff procs }; +enum class PlayerState : uint32 { + None = 0, + Open = 1, + WeaponSheathed = 2, + Aggressive = 4, + ForcedAggressive = 8, + InstrumentEquipped = 16, + Stunned = 32, + PrimaryWeaponEquipped = 64, + SecondaryWeaponEquipped = 128 +}; + //this is our internal representation of the BUFF struct, can put whatever we want in it struct Buffs_Struct { uint16 spellid; @@ -190,6 +202,7 @@ struct Buffs_Struct { int32 caston_z; int32 ExtraDIChance; int16 RootBreakChance; //Not saved to dbase + uint32 instrument_mod; bool persistant_buff; bool client; //True if the caster is a client bool UpdateClient; @@ -438,7 +451,7 @@ struct StatBonuses { int32 ShieldEquipHateMod; // Hate mod when shield equiped. int32 ShieldEquipDmgMod[2]; // Damage mod when shield equiped. 0 = damage modifier 1 = Unknown bool TriggerOnValueAmount; // Triggers off various different conditions, bool to check if client has effect. - int8 StunBashChance; // chance to stun with bash. + int8 StunBashChance; // chance to stun with bash. int8 IncreaseChanceMemwipe; // increases chance to memory wipe int8 CriticalMend; // chance critical monk mend int32 ImprovedReclaimEnergy; // Modifies amount of mana returned from reclaim energy @@ -455,6 +468,7 @@ typedef struct uint16 spellID; uint16 chance; uint16 base_spellID; + int level_override; } tProc; struct Shielders_Struct { @@ -507,7 +521,8 @@ typedef enum { petOther, petCharmed, petNPCFollow, - petTargetLock //remain active as long something is on the hatelist. Don't listen to any commands + petTargetLock, //remain active as long something is on the hatelist. Don't listen to any commands + petNone = 0xFF // not a pet } PetType; typedef enum { diff --git a/zone/effects.cpp b/zone/effects.cpp index 55cf148ce..8ed2af6bf 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -89,7 +89,7 @@ int32 Mob::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { if (IsClient() && GetClass() == WIZARD) ratio += RuleI(Spells, WizCritRatio); //Default is zero - + if (Critical){ value = value_BaseEffect*ratio/100; @@ -172,7 +172,7 @@ int32 Mob::GetActDoTDamage(uint16 spell_id, int32 value, Mob* target) { value += int(value_BaseEffect*GetFocusEffect(focusImprovedDamage, spell_id)/100)*ratio/100; value += int(value_BaseEffect*GetFocusEffect(focusFcDamagePctCrit, spell_id)/100)*ratio/100; value += int(value_BaseEffect*target->GetVulnerability(this, spell_id, 0)/100)*ratio/100; - extra_dmg = target->GetFcDamageAmtIncoming(this, spell_id) + + extra_dmg = target->GetFcDamageAmtIncoming(this, spell_id) + int(GetFocusEffect(focusFcDamageAmtCrit, spell_id)*ratio/100) + GetFocusEffect(focusFcDamageAmt, spell_id); @@ -219,11 +219,11 @@ int32 Mob::GetExtraSpellAmt(uint16 spell_id, int32 extra_spell_amt, int32 base_s total_cast_time = spells[spell_id].recovery_time + spells[spell_id].cast_time; if (total_cast_time > 0 && total_cast_time <= 2500) - extra_spell_amt = extra_spell_amt*25/100; - else if (total_cast_time > 2500 && total_cast_time < 7000) - extra_spell_amt = extra_spell_amt*(167*((total_cast_time - 1000)/1000)) / 1000; - else - extra_spell_amt = extra_spell_amt * total_cast_time / 7000; + extra_spell_amt = extra_spell_amt*25/100; + else if (total_cast_time > 2500 && total_cast_time < 7000) + extra_spell_amt = extra_spell_amt*(167*((total_cast_time - 1000)/1000)) / 1000; + else + extra_spell_amt = extra_spell_amt * total_cast_time / 7000; if(extra_spell_amt*2 < base_spell_dmg) return 0; @@ -281,7 +281,7 @@ int32 Mob::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { if (Critical) { entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_HEAL, GetName(), itoa(value)); - + if (IsClient()) Message_StringID(MT_SpellCrits, YOU_CRIT_HEAL, itoa(value)); } @@ -421,16 +421,21 @@ int32 Mob::GetActSpellDuration(uint16 spell_id, int32 duration) int tic_inc = 0; tic_inc = GetFocusEffect(focusSpellDurByTic, spell_id); - // Only need this for clients, since the change was for bard songs, I assume we should keep non bard songs getting +1 - // However if its bard or not and is mez, charm or fear, we need to add 1 so that client is in sync - if (IsClient() && !(IsShortDurationBuff(spell_id) && IsBardSong(spell_id)) || - IsFearSpell(spell_id) || - IsCharmSpell(spell_id) || - IsMezSpell(spell_id) || - IsBlindSpell(spell_id)) - tic_inc += 1; + // unsure on the exact details, but bard songs that don't cost mana at some point get an extra tick, 60 for now + // a level 53 bard reported getting 2 tics + // bard DOTs do get this extra tick, but beneficial long bard songs don't? (invul, crescendo) + if ((IsShortDurationBuff(spell_id) || IsDetrimentalSpell(spell_id)) && IsBardSong(spell_id) && + spells[spell_id].mana == 0 && GetClass() == BARD && GetLevel() > 60) + tic_inc++; + float focused = ((duration * increase) / 100.0f) + tic_inc; + int ifocused = static_cast(focused); - return (((duration * increase) / 100) + tic_inc); + // 7.6 is rounded to 7, 8.6 is rounded to 9 + // 6 is 6, etc + if (FCMP(focused, ifocused) || ifocused % 2) // equal or odd + return ifocused; + else // even and not equal round to odd + return ifocused + 1; } int32 Client::GetActSpellCasttime(uint16 spell_id, int32 casttime) @@ -771,7 +776,7 @@ void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_ caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); } } else { - if (spells[spell_id].aemaxtargets && iCounter < spells[spell_id].aemaxtargets) + if (spells[spell_id].aemaxtargets && iCounter < spells[spell_id].aemaxtargets) caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); if (!spells[spell_id].aemaxtargets) caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); @@ -859,7 +864,7 @@ void EntityList::AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool aff if (!center->CheckLosFN(curmob)) continue; } else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... - // See notes in AESpell() above for more info. + // See notes in AESpell() above for more info. if (caster->IsAttackAllowed(curmob, true)) continue; if (caster->CheckAggro(curmob)) diff --git a/zone/embparser.cpp b/zone/embparser.cpp index 59f268dcc..a47594c92 100644 --- a/zone/embparser.cpp +++ b/zone/embparser.cpp @@ -1072,7 +1072,7 @@ void PerlembParser::ExportZoneVariables(std::string &package_name) { ExportVar(package_name.c_str(), "instanceid", zone->GetInstanceID()); ExportVar(package_name.c_str(), "instanceversion", zone->GetInstanceVersion()); TimeOfDay_Struct eqTime; - zone->zone_time.getEQTimeOfDay( time(0), &eqTime); + zone->zone_time.GetCurrentEQTimeOfDay( time(0), &eqTime); ExportVar(package_name.c_str(), "zonehour", eqTime.hour - 1); ExportVar(package_name.c_str(), "zonemin", eqTime.minute); ExportVar(package_name.c_str(), "zonetime", (eqTime.hour - 1) * 100 + eqTime.minute); @@ -1277,6 +1277,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID case EVENT_PLAYER_PICKUP:{ ExportVar(package_name.c_str(), "picked_up_id", data); + ExportVar(package_name.c_str(), "picked_up_entity_id", extradata); break; } @@ -1320,6 +1321,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID ExportVar(package_name.c_str(), "itemid", objid); ExportVar(package_name.c_str(), "itemname", iteminst->GetItem()->Name); ExportVar(package_name.c_str(), "slotid", extradata); + ExportVar(package_name.c_str(), "spell_id", iteminst->GetItem()->Click.Effect); break; } @@ -1367,6 +1369,7 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID case EVENT_CLICK_OBJECT: { ExportVar(package_name.c_str(), "objectid", data); + ExportVar(package_name.c_str(), "clicker_id", extradata); break; } @@ -1397,6 +1400,14 @@ void PerlembParser::ExportEventVariables(std::string &package_name, QuestEventID ExportVar(package_name.c_str(), "killer_skill", sep.arg[3]); break; } + case EVENT_DROP_ITEM: { + ExportVar(package_name.c_str(), "quantity", iteminst->IsStackable() ? iteminst->GetCharges() : 1); + ExportVar(package_name.c_str(), "itemname", iteminst->GetItem()->Name); + ExportVar(package_name.c_str(), "itemid", iteminst->GetItem()->ID); + ExportVar(package_name.c_str(), "spell_id", iteminst->GetItem()->Click.Effect); + ExportVar(package_name.c_str(), "slotid", extradata); + break; + } default: { break; diff --git a/zone/embparser_api.cpp b/zone/embparser_api.cpp index 1729978b0..6db7777ac 100644 --- a/zone/embparser_api.cpp +++ b/zone/embparser_api.cpp @@ -1182,13 +1182,26 @@ XS(XS__settime); XS(XS__settime) { dXSARGS; - if (items != 2) - Perl_croak(aTHX_ "Usage: settime(new_hour, new_min)"); + if (items < 2) + Perl_croak(aTHX_ "Usage: settime(new_hour, new_min, [update_world = true])"); - int new_hour = (int)SvIV(ST(0)); - int new_min = (int)SvIV(ST(1)); + if (items == 2){ + int new_hour = (int)SvIV(ST(0)); + int new_min = (int)SvIV(ST(1)); + quest_manager.settime(new_hour, new_min, true); + } + else if (items == 3){ + int new_hour = (int)SvIV(ST(0)); + int new_min = (int)SvIV(ST(1)); - quest_manager.settime(new_hour, new_min); + int update_world = (int)SvIV(ST(2)); + if (update_world == 1){ + quest_manager.settime(new_hour, new_min, true); + } + else{ + quest_manager.settime(new_hour, new_min, false); + } + } XSRETURN_EMPTY; } @@ -1917,6 +1930,52 @@ XS(XS__repopzone) XSRETURN_EMPTY; } +XS(XS__ConnectNodeToNode); +XS(XS__ConnectNodeToNode) +{ + dXSARGS; + if (items != 4) + Perl_croak(aTHX_ "Usage: ConnectNodeToNode(node1, node2, teleport, doorid)"); + + int node1 = (int)SvIV(ST(0)); + int node2 = (int)SvIV(ST(1)); + int teleport = (int)SvIV(ST(2)); + int doorid = (int)SvIV(ST(3)); + + quest_manager.ConnectNodeToNode(node1, node2, teleport, doorid); + + XSRETURN_EMPTY; +} + +XS(XS__AddNode); +XS(XS__AddNode) +{ + dXSARGS; + //void QuestManager::AddNode(float x, float y, float z, float best_z, int32 requested_id); + if (items < 3 || items > 5) + Perl_croak(aTHX_ "Usage: AddNode(x, y, z, [best_z], [requested_id])"); + + int x = (int)SvIV(ST(0)); + int y = (int)SvIV(ST(1)); + int z = (int)SvIV(ST(2)); + int best_z = 0; + int requested_id = 0; + + if (items == 4) + { + best_z = (int)SvIV(ST(3)); + } + else if (items == 5) + { + best_z = (int)SvIV(ST(3)); + requested_id = (int)SvIV(ST(4)); + } + + quest_manager.AddNode(x, y, z, best_z, requested_id); + + XSRETURN_EMPTY; +} + XS(XS__npcrace); XS(XS__npcrace) { @@ -3699,6 +3758,8 @@ EXTERN_C XS(boot_quest) newXS(strcpy(buf, "reloadzonestaticdata"), XS__reloadzonestaticdata, file); newXS(strcpy(buf, "removetitle"), XS__removetitle, file); newXS(strcpy(buf, "repopzone"), XS__repopzone, file); + newXS(strcpy(buf, "ConnectNodeToNode"), XS__ConnectNodeToNode, file); + newXS(strcpy(buf, "AddNode"), XS__AddNode, file); newXS(strcpy(buf, "resettaskactivity"), XS__resettaskactivity, file); newXS(strcpy(buf, "respawn"), XS__respawn, file); newXS(strcpy(buf, "resume"), XS__resume, file); diff --git a/zone/encounter.cpp b/zone/encounter.cpp new file mode 100644 index 000000000..1f77de43a --- /dev/null +++ b/zone/encounter.cpp @@ -0,0 +1,53 @@ +/* EQEMu: Everquest Server Emulator +Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY except by those people which sell it, which +are required to give you total support for your newly bought product; +without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifdef _WINDOWS +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +#include "../common/races.h" +#include "encounter.h" +#include "entity.h" +#include "mob.h" + +class Zone; + +Encounter::Encounter(const char* enc_name) + :Mob + ( + nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, glm::vec4(0,0,0,0), 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ) +{ + encounter_name[0] = 0; + strn0cpy(encounter_name, enc_name, 64); + remove_me = false; +} + +Encounter::~Encounter() +{ + +} + +bool Encounter::Process() { + if (remove_me) return false; + return true; +} diff --git a/zone/encounter.h b/zone/encounter.h new file mode 100644 index 000000000..a2977d7e0 --- /dev/null +++ b/zone/encounter.h @@ -0,0 +1,62 @@ +/* EQEMu: Everquest Server Emulator +Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY except by those people which sell it, which +are required to give you total support for your newly bought product; +without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef ENCOUNTER_H +#define ENCOUNTER_H + +#include "mob.h" +#include "../common/types.h" +#include "../common/timer.h" + +class Group; +class Raid; +struct ExtraAttackOptions; + +class Encounter : public Mob +{ +public: + Encounter(const char* enc_name); + ~Encounter(); + + //abstract virtual function implementations required by base abstract class + virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillUseTypes attack_skill) { return true; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillUseTypes attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false) { return; } + virtual bool Attack(Mob* other, int Hand = MainPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, + ExtraAttackOptions *opts = nullptr) { + return false; + } + virtual bool HasRaid() { return false; } + virtual bool HasGroup() { return false; } + virtual Raid* GetRaid() { return 0; } + virtual Group* GetGroup() { return 0; } + + bool IsEncounter() const { return true; } + const char* GetEncounterName() const { return encounter_name; } + + bool Process(); + virtual void Depop(bool not_used = true) { remove_me = true; } + + +protected: + bool remove_me; + char encounter_name[64]; + +private: +}; + +#endif diff --git a/zone/entity.cpp b/zone/entity.cpp index 8fd1cb14e..1f3ec384a 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -79,7 +79,7 @@ Client *Entity::CastToClient() } #ifdef _EQDEBUG if (!IsClient()) { - Log.Out(Logs::General, Logs::Error, "CastToClient error (not client)"); + Log.Out(Logs::General, Logs::Error, "CastToClient error (not client)"); return 0; } #endif @@ -173,6 +173,11 @@ Beacon *Entity::CastToBeacon() return static_cast(this); } +Encounter *Entity::CastToEncounter() +{ + return static_cast(this); +} + const Client *Entity::CastToClient() const { if (this == 0x00) { @@ -263,6 +268,11 @@ const Beacon* Entity::CastToBeacon() const return static_cast(this); } +const Encounter* Entity::CastToEncounter() const +{ + return static_cast(this); +} + #ifdef BOTS Bot *Entity::CastToBot() { @@ -533,6 +543,21 @@ void EntityList::BeaconProcess() } } +void EntityList::EncounterProcess() +{ + auto it = encounter_list.begin(); + while (it != encounter_list.end()) { + if (!it->second->Process()) { + safe_delete(it->second); + free_ids.push(it->first); + it = encounter_list.erase(it); + } + else { + ++it; + } + } +} + void EntityList::AddGroup(Group *group) { if (group == nullptr) //this seems to be happening somehow... @@ -540,7 +565,7 @@ void EntityList::AddGroup(Group *group) uint32 gid = worldserver.NextGroupID(); if (gid == 0) { - Log.Out(Logs::General, Logs::Error, + Log.Out(Logs::General, Logs::Error, "Unable to get new group ID from world server. group is going to be broken."); return; } @@ -569,7 +594,7 @@ void EntityList::AddRaid(Raid *raid) uint32 gid = worldserver.NextGroupID(); if (gid == 0) { - Log.Out(Logs::General, Logs::Error, + Log.Out(Logs::General, Logs::Error, "Unable to get new group ID from world server. group is going to be broken."); return; } @@ -618,6 +643,8 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue) EQApplicationPacket *app = new EQApplicationPacket; npc->CreateSpawnPacket(app, npc); QueueClients(npc, app); + npc->SendArmorAppearance(); + npc->SetAppearance(npc->GetGuardPointAnim(),false); safe_delete(app); } else { NewSpawn_Struct *ns = new NewSpawn_Struct; @@ -706,6 +733,12 @@ void EntityList::AddBeacon(Beacon *beacon) beacon_list.insert(std::pair(beacon->GetID(), beacon)); } +void EntityList::AddEncounter(Encounter *encounter) +{ + encounter->SetID(GetFreeID()); + encounter_list.insert(std::pair(encounter->GetID(), encounter)); +} + void EntityList::AddToSpawnQueue(uint16 entityid, NewSpawn_Struct **ns) { uint32 count; @@ -726,10 +759,23 @@ void EntityList::CheckSpawnQueue() EQApplicationPacket *outapp = 0; iterator.Reset(); + NewSpawn_Struct *ns; + while(iterator.MoreElements()) { outapp = new EQApplicationPacket; - Mob::CreateSpawnPacket(outapp, iterator.GetData()); + ns = iterator.GetData(); + Mob::CreateSpawnPacket(outapp, ns); QueueClients(0, outapp); + auto it = npc_list.find(ns->spawn.spawnId); + if (it == npc_list.end()) { + // We must of despawned, hope that's the reason! + Log.Out(Logs::General, Logs::Error, "Error in EntityList::CheckSpawnQueue: Unable to find NPC for spawnId '%u'", ns->spawn.spawnId); + } + else { + NPC *pnpc = it->second; + pnpc->SendArmorAppearance(); + pnpc->SetAppearance(pnpc->GetGuardPointAnim(), false); + } safe_delete(outapp); iterator.RemoveCurrent(); } @@ -920,6 +966,11 @@ Entity *EntityList::GetEntityBeacon(uint16 id) return beacon_list.count(id) ? beacon_list.at(id) : nullptr; } +Entity *EntityList::GetEntityEncounter(uint16 id) +{ + return encounter_list.count(id) ? encounter_list.at(id) : nullptr; +} + Entity *EntityList::GetID(uint16 get_id) { Entity *ent = 0; @@ -935,6 +986,8 @@ Entity *EntityList::GetID(uint16 get_id) return ent; else if ((ent=entity_list.GetEntityBeacon(get_id)) != 0) return ent; + else if ((ent = entity_list.GetEntityEncounter(get_id)) != 0) + return ent; else return 0; } @@ -1149,19 +1202,39 @@ void EntityList::SendZoneSpawnsBulk(Client *client) NewSpawn_Struct ns; Mob *spawn; uint32 maxspawns = 100; + EQApplicationPacket *app; if (maxspawns > mob_list.size()) maxspawns = mob_list.size(); BulkZoneSpawnPacket *bzsp = new BulkZoneSpawnPacket(client, maxspawns); + + int32 race=-1; for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { spawn = it->second; if (spawn && spawn->InZone()) { if (spawn->IsClient() && (spawn->CastToClient()->GMHideMe(client) || spawn->CastToClient()->IsHoveringForRespawn())) continue; - memset(&ns, 0, sizeof(NewSpawn_Struct)); - spawn->FillSpawnStruct(&ns, client); - bzsp->AddSpawn(&ns); + + race = spawn->GetRace(); + + // Illusion races on PCs don't work as a mass spawn + // But they will work as an add_spawn AFTER CLIENT_CONNECTED. + if (spawn->IsClient() && (race == MINOR_ILL_OBJ || race == TREE)) { + app = new EQApplicationPacket; + spawn->CreateSpawnPacket(app); + client->QueuePacket(app, true, Client::CLIENT_CONNECTED); + safe_delete(app); + } + else { + memset(&ns, 0, sizeof(NewSpawn_Struct)); + spawn->FillSpawnStruct(&ns, client); + bzsp->AddSpawn(&ns); + } + + // Despite being sent in the OP_ZoneSpawns packet, the client + // does not display worn armor correctly so display it. + spawn->SendArmorAppearance(client); } } safe_delete(bzsp); @@ -1355,7 +1428,9 @@ void EntityList::QueueClientsByTarget(Mob *sender, const EQApplicationPacket *ap if (c != sender) { if (Target == sender) { if (inspect_buffs) { // if inspect_buffs is true we're sending a mob's buffs to those with the LAA - if (c->IsRaidGrouped()) { + if (c->GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs)) { + Send = true; + } else if (c->IsRaidGrouped()) { Raid *raid = c->GetRaid(); if (!raid) continue; @@ -3389,6 +3464,15 @@ bool EntityList::IsMobInZone(Mob *who) } ++it; } + + auto enc_it = encounter_list.begin(); + while (enc_it != encounter_list.end()) { + if (enc_it->second == who) { + return true; + } + ++enc_it; + } + return false; } diff --git a/zone/entity.h b/zone/entity.h index 8af87f649..446c5d505 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -30,6 +30,7 @@ #include "position.h" #include "zonedump.h" +class Encounter; class Beacon; class Client; class Corpse; @@ -77,6 +78,7 @@ public: virtual bool IsDoor() const { return false; } virtual bool IsTrap() const { return false; } virtual bool IsBeacon() const { return false; } + virtual bool IsEncounter() const { return false; } virtual bool Process() { return false; } virtual bool Save() { return true; } @@ -91,6 +93,7 @@ public: Doors *CastToDoors(); Trap *CastToTrap(); Beacon *CastToBeacon(); + Encounter *CastToEncounter(); const Client *CastToClient() const; const NPC *CastToNPC() const; @@ -101,6 +104,7 @@ public: const Doors *CastToDoors() const; const Trap *CastToTrap() const; const Beacon *CastToBeacon() const; + const Encounter *CastToEncounter() const; inline const uint16& GetID() const { return id; } inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; } @@ -203,6 +207,7 @@ public: void MobProcess(); void TrapProcess(); void BeaconProcess(); + void EncounterProcess(); void ProcessMove(Client *c, const glm::vec3& location); void ProcessMove(NPC *n, float x, float y, float z); void AddArea(int id, int type, float min_x, float max_x, float min_y, float max_y, float min_z, float max_z); @@ -228,6 +233,7 @@ public: void AddDoor(Doors* door); void AddTrap(Trap* trap); void AddBeacon(Beacon *beacon); + void AddEncounter(Encounter *encounter); void AddProximity(NPC *proximity_for); void Clear(); bool RemoveMob(uint16 delete_id); @@ -266,6 +272,7 @@ public: Entity *GetEntityCorpse(uint16 id); Entity *GetEntityTrap(uint16 id); Entity *GetEntityBeacon(uint16 id); + Entity *GetEntityEncounter(uint16 id); Entity *GetEntityMob(const char *name); Entity *GetEntityCorpse(const char *name); @@ -448,6 +455,7 @@ private: std::unordered_map door_list; std::unordered_map trap_list; std::unordered_map beacon_list; + std::unordered_map encounter_list; std::list proximity_list; std::list group_list; std::list raid_list; diff --git a/zone/exp.cpp b/zone/exp.cpp index 2aa60ce92..2ddf1318b 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -59,6 +59,97 @@ static uint32 MaxBankedRaidLeadershipPoints(int Level) return 10; } +uint32 Client::CalcEXP(uint8 conlevel) { + + uint32 in_add_exp = EXP_FORMULA; + + + if((XPRate != 0)) + in_add_exp = static_cast(in_add_exp * (static_cast(XPRate) / 100.0f)); + + float totalmod = 1.0; + float zemmod = 1.0; + //get modifiers + if(RuleR(Character, ExpMultiplier) >= 0){ + totalmod *= RuleR(Character, ExpMultiplier); + } + + if(zone->newzone_data.zone_exp_multiplier >= 0){ + zemmod *= zone->newzone_data.zone_exp_multiplier; + } + + if(RuleB(Character,UseRaceClassExpBonuses)) + { + if(GetBaseRace() == HALFLING){ + totalmod *= 1.05; + } + + if(GetClass() == ROGUE || GetClass() == WARRIOR){ + totalmod *= 1.05; + } + } + + if(zone->IsHotzone()) + { + totalmod += RuleR(Zone, HotZoneBonus); + } + + in_add_exp = uint32(float(in_add_exp) * totalmod * zemmod); + + if(RuleB(Character,UseXPConScaling)) + { + if (conlevel != 0xFF) { + switch (conlevel) + { + case CON_GREEN: + in_add_exp = 0; + return 0; + case CON_LIGHTBLUE: + in_add_exp = in_add_exp * RuleI(Character, LightBlueModifier)/100; + break; + case CON_BLUE: + in_add_exp = in_add_exp * RuleI(Character, BlueModifier)/100; + break; + case CON_WHITE: + in_add_exp = in_add_exp * RuleI(Character, WhiteModifier)/100; + break; + case CON_YELLOW: + in_add_exp = in_add_exp * RuleI(Character, YellowModifier)/100; + break; + case CON_RED: + in_add_exp = in_add_exp * RuleI(Character, RedModifier)/100; + break; + } + } + } + + float aatotalmod = 1.0; + if(zone->newzone_data.zone_exp_multiplier >= 0){ + aatotalmod *= zone->newzone_data.zone_exp_multiplier; + } + + + + if(RuleB(Character,UseRaceClassExpBonuses)) + { + if(GetBaseRace() == HALFLING){ + aatotalmod *= 1.05; + } + + if(GetClass() == ROGUE || GetClass() == WARRIOR){ + aatotalmod *= 1.05; + } + } + + if(RuleB(Zone, LevelBasedEXPMods)){ + if(zone->level_exp_mod[GetLevel()].ExpMod){ + in_add_exp *= zone->level_exp_mod[GetLevel()].ExpMod; + } + } + + return in_add_exp; +} + void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { ItemScriptStopReturn(); @@ -78,7 +169,7 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { //figure out how much of this goes to AAs add_aaxp = add_exp * m_epp.perAA / 100; - //take that ammount away from regular exp + //take that amount away from regular exp add_exp -= add_aaxp; float totalmod = 1.0; @@ -247,12 +338,22 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { Message(13, "Error in Client::SetEXP. EXP not set."); return; // Must be invalid class/race } + uint32 i = 0; + uint32 membercount = 0; + if(GetGroup()) + { + for (i = 0; i < MAX_GROUP_MEMBERS; i++) { + if (GetGroup()->members[i] != nullptr) { + membercount++; + } + } + } if ((set_exp + set_aaxp) > (m_pp.exp+m_pp.expAA)) { if (isrezzexp) this->Message_StringID(MT_Experience, REZ_REGAIN); else{ - if(this->IsGrouped()) + if(membercount > 1) this->Message_StringID(MT_Experience, GAIN_GROUPXP); else if(IsRaidGrouped()) Message_StringID(MT_Experience, GAIN_RAIDEXP); @@ -268,12 +369,17 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { //this ammount of exp (once these loops complete) uint16 check_level = GetLevel()+1; //see if we gained any levels + bool level_increase = true; + int8 level_count = 0; + while (set_exp >= GetEXPForLevel(check_level)) { check_level++; if (check_level > 127) { //hard level cap check_level = 127; break; } + level_count++; + if(GetMercID()) UpdateMercLevel(); } @@ -284,6 +390,7 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { check_level = 2; break; } + level_increase = false; if(GetMercID()) UpdateMercLevel(); } @@ -364,17 +471,21 @@ void Client::SetEXP(uint32 set_exp, uint32 set_aaxp, bool isrezzexp) { if ((GetLevel() != check_level) && !(check_level >= maxlevel)) { char val1[20]={0}; - if (GetLevel() == check_level-1){ - Message_StringID(MT_Experience, GAIN_LEVEL,ConvertArray(check_level,val1)); - SendLevelAppearance(); - /* Message(15, "You have gained a level! Welcome to level %i!", check_level); */ - } - if (GetLevel() == check_level){ - Message_StringID(MT_Experience, LOSE_LEVEL,ConvertArray(check_level,val1)); - /* Message(15, "You lost a level! You are now level %i!", check_level); */ + if (level_increase) + { + if (level_count == 1) + Message_StringID(MT_Experience, GAIN_LEVEL, ConvertArray(check_level, val1)); + else + Message(15, "Welcome to level %i!", check_level); + + if (check_level == RuleI(Character, DeathItemLossLevel)) + Message_StringID(15, CORPSE_ITEM_LOST); + + if (check_level == RuleI(Character, DeathExpLossLevel)) + Message_StringID(15, CORPSE_EXP_LOST); } else - Message(15, "Welcome to level %i!", check_level); + Message_StringID(MT_Experience, LOSE_LEVEL, ConvertArray(check_level, val1)); #ifdef BOTS uint8 myoldlevel = GetLevel(); @@ -594,8 +705,8 @@ void Group::SplitExp(uint32 exp, Mob* other) { groupmod = 2.16; else groupmod = 1.0; - - groupexp += (uint32)((float)exp * groupmod * (RuleR(Character, GroupExpMultiplier))); + if(membercount > 1 && membercount <= 6) + groupexp += (uint32)((float)exp * groupmod * (RuleR(Character, GroupExpMultiplier))); int conlevel = Mob::GetLevelCon(maxlevel, other->GetLevel()); if(conlevel == CON_GREEN) diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index 0b33a662e..0fd5ffa98 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -123,29 +123,6 @@ void Mob::ProcessFlee() } } -float Mob::GetFearSpeed() -{ - if (flee_mode) { - //we know ratio < FLEE_HP_RATIO - float speed = GetBaseRunspeed(); - float ratio = GetHPRatio(); - float multiplier = RuleR(Combat, FleeMultiplier); - - if (GetSnaredAmount() > 40) - multiplier = multiplier / 6.0f; - - speed = speed * ratio * multiplier / 100; - - //NPC will eventually stop. Snares speeds this up. - if (speed < 0.09) - speed = 0.0001f; - - return speed; - } - // fear and blind use their normal run speed - return GetRunspeed(); -} - void Mob::CalculateNewFearpoint() { if(RuleB(Pathing, Fear) && zone->pathing) diff --git a/zone/forage.cpp b/zone/forage.cpp index 0ffb61121..7bbca0b49 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -185,26 +185,35 @@ bool Client::CanFish() { rodPosition.x = m_Position.x + RodLength * sin(HeadingDegrees * M_PI/180.0f); rodPosition.y = m_Position.y + RodLength * cos(HeadingDegrees * M_PI/180.0f); + rodPosition.z = m_Position.z; - // Do BestZ to find where the line hanging from the rod intersects the water (if it is water). - // and go 1 unit into the water. - glm::vec3 dest; - dest.x = rodPosition.x; - dest.y = rodPosition.y; - dest.z = m_Position.z+10; - - rodPosition.z = zone->zonemap->FindBestZ(dest, nullptr) + 4; - bool in_lava = zone->watermap->InLava(rodPosition); - bool in_water = zone->watermap->InWater(rodPosition) || zone->watermap->InVWater(rodPosition); - //Message(0, "Rod is at %4.3f, %4.3f, %4.3f, InWater says %d, InLava says %d", RodX, RodY, RodZ, in_water, in_lava); - if (in_lava) { - Message_StringID(MT_Skills, FISHING_LAVA); //Trying to catch a fire elemental or something? + float bestz = zone->zonemap->FindBestZ(rodPosition, nullptr); + float len = m_Position.z - bestz; + if(len > LineLength || len < 0.0f) { + Message_StringID(MT_Skills, FISHING_LAND); return false; } - if((!in_water) || (m_Position.z-rodPosition.z)>LineLength) { //Didn't hit the water OR the water is too far below us - Message_StringID(MT_Skills, FISHING_LAND); //Trying to catch land sharks perhaps? - return false; + + float step_size = RuleR(Watermap, FishingLineStepSize); + + for(float i = 0.0f; i < len; i += step_size) { + glm::vec3 dest(rodPosition.x, rodPosition.y, m_Position.z - i); + + bool in_lava = zone->watermap->InLava(dest); + bool in_water = zone->watermap->InWater(dest) || zone->watermap->InVWater(dest); + + if (in_lava) { + Message_StringID(MT_Skills, FISHING_LAVA); //Trying to catch a fire elemental or something? + return false; + } + + if(in_water) { + return true; + } } + + Message_StringID(MT_Skills, FISHING_LAND); + return false; } return true; } diff --git a/zone/inventory.cpp b/zone/inventory.cpp index 95f0f267d..0cedb9d9a 100644 --- a/zone/inventory.cpp +++ b/zone/inventory.cpp @@ -606,7 +606,7 @@ void Client::DropItem(int16 slot_id) // Take control of item in client inventory ItemInst *inst = m_inv.PopItem(slot_id); if(inst) { - int i = parse->EventItem(EVENT_DROP_ITEM, this, inst, nullptr, "", 0); + int i = parse->EventItem(EVENT_DROP_ITEM, this, inst, nullptr, "", slot_id); if(i != 0) { safe_delete(inst); } @@ -2199,6 +2199,187 @@ bool Client::DecreaseByID(uint32 type, uint8 amt) { return true; } +static bool IsSummonedBagID(uint32 item_id) +{ + switch (item_id) { + case 17147: // "Spiritual Prismatic Pack" + case 17303: // "Spirit Pouch" + case 17304: // "Dimensional Pocket" + case 17305: // "Dimensional Hole" + case 17306: // "Glowing Backpack" + case 17307: // "Quiver of Marr" + case 17308: // "Bandoleer of Luclin" + case 17309: // "Pouch of Quellious" + case 17310: // "Phantom Satchel" + case 17510: // "Glowing Chest" + case 17900: // "Grandmaster's Satchel" + case 57260: // "Glowing Backpack" + case 57261: // "Pouch of Quellious" + case 57262: // "Phantom Satchel" + case 60224: // "Faded-Glyph Tablet" + case 95199: // "Beginner Artisan Satchel" + case 95200: // "Apprentice Artisan Satchel" + case 95201: // "Freshman Artisan Satchel" + case 95202: // "Journeyman Artisan Satchel" + case 95203: // "Expert Artisan Satchel" + case 95204: // "Master Artisan Satchel" + //case 96960: // "Artisan Satchel" - no 12-slot disenchanted bags + return true; + default: + return false; + } +} + +static uint32 GetDisenchantedBagID(uint8 bag_slots) +{ + switch (bag_slots) { + case 4: + return 77772; // "Small Disenchanted Backpack" + case 6: + return 77774; // "Disenchanted Backpack" + case 8: + return 77776; // "Large Disenchanted Backpack" + case 10: + return 77778; // "Huge Disenchanted Backpack" + default: + return 0; // no suitable conversions + } +} + +static bool CopyBagContents(ItemInst* new_bag, const ItemInst* old_bag) +{ + if (!new_bag || !old_bag) { return false; } + if (new_bag->GetItem()->BagSlots < old_bag->GetItem()->BagSlots) { return false; } + + // pre-check for size comparisons + for (auto bag_slot = 0; bag_slot < old_bag->GetItem()->BagSlots; ++bag_slot) { + if (!old_bag->GetItem(bag_slot)) { continue; } + if (old_bag->GetItem(bag_slot)->GetItem()->Size > new_bag->GetItem()->BagSize) { + Log.Out(Logs::General, Logs::Inventory, "Copy Bag Contents: Failure due to %s is larger than size capacity of %s (%i > %i)", + old_bag->GetItem(bag_slot)->GetItem()->Name, new_bag->GetItem()->Name, old_bag->GetItem(bag_slot)->GetItem()->Size, new_bag->GetItem()->BagSize); + return false; + } + } + + for (auto bag_slot = 0; bag_slot < old_bag->GetItem()->BagSlots; ++bag_slot) { + if (!old_bag->GetItem(bag_slot)) { continue; } + new_bag->PutItem(bag_slot, *(old_bag->GetItem(bag_slot))); + } + + return true; +} + +void Client::DisenchantSummonedBags(bool client_update) +{ + for (auto slot_id = EmuConstants::GENERAL_BEGIN; slot_id <= EmuConstants::GENERAL_END; ++slot_id) { + auto inst = m_inv[slot_id]; + if (!inst) { continue; } + if (!IsSummonedBagID(inst->GetItem()->ID)) { continue; } + if (inst->GetItem()->ItemClass != ItemClassContainer) { continue; } + if (inst->GetTotalItemCount() == 1) { continue; } + + auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots); + if (!new_id) { continue; } + auto new_item = database.GetItem(new_id); + if (!new_item) { continue; } + auto new_inst = database.CreateBaseItem(new_item); + if (!new_inst) { continue; } + + if (CopyBagContents(new_inst, inst)) { + Log.Out(Logs::General, Logs::Inventory, "Disenchant Summoned Bags: Replacing %s with %s in slot %i", inst->GetItem()->Name, new_inst->GetItem()->Name, slot_id); + PutItemInInventory(slot_id, *new_inst, client_update); + } + safe_delete(new_inst); + } + + for (auto slot_id = EmuConstants::BANK_BEGIN; slot_id <= EmuConstants::BANK_END; ++slot_id) { + auto inst = m_inv[slot_id]; + if (!inst) { continue; } + if (!IsSummonedBagID(inst->GetItem()->ID)) { continue; } + if (inst->GetItem()->ItemClass != ItemClassContainer) { continue; } + if (inst->GetTotalItemCount() == 1) { continue; } + + auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots); + if (!new_id) { continue; } + auto new_item = database.GetItem(new_id); + if (!new_item) { continue; } + auto new_inst = database.CreateBaseItem(new_item); + if (!new_inst) { continue; } + + if (CopyBagContents(new_inst, inst)) { + Log.Out(Logs::General, Logs::Inventory, "Disenchant Summoned Bags: Replacing %s with %s in slot %i", inst->GetItem()->Name, new_inst->GetItem()->Name, slot_id); + PutItemInInventory(slot_id, *new_inst, client_update); + } + safe_delete(new_inst); + } + + for (auto slot_id = EmuConstants::SHARED_BANK_BEGIN; slot_id <= EmuConstants::SHARED_BANK_END; ++slot_id) { + auto inst = m_inv[slot_id]; + if (!inst) { continue; } + if (!IsSummonedBagID(inst->GetItem()->ID)) { continue; } + if (inst->GetItem()->ItemClass != ItemClassContainer) { continue; } + if (inst->GetTotalItemCount() == 1) { continue; } + + auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots); + if (!new_id) { continue; } + auto new_item = database.GetItem(new_id); + if (!new_item) { continue; } + auto new_inst = database.CreateBaseItem(new_item); + if (!new_inst) { continue; } + + if (CopyBagContents(new_inst, inst)) { + Log.Out(Logs::General, Logs::Inventory, "Disenchant Summoned Bags: Replacing %s with %s in slot %i", inst->GetItem()->Name, new_inst->GetItem()->Name, slot_id); + PutItemInInventory(slot_id, *new_inst, client_update); + } + safe_delete(new_inst); + } + + while (!m_inv.CursorEmpty()) { + auto inst = m_inv[MainCursor]; + if (!inst) { break; } + if (!IsSummonedBagID(inst->GetItem()->ID)) { break; } + if (inst->GetItem()->ItemClass != ItemClassContainer) { break; } + if (inst->GetTotalItemCount() == 1) { break; } + + auto new_id = GetDisenchantedBagID(inst->GetItem()->BagSlots); + if (!new_id) { break; } + auto new_item = database.GetItem(new_id); + if (!new_item) { break; } + auto new_inst = database.CreateBaseItem(new_item); + if (!new_inst) { break; } + + if (CopyBagContents(new_inst, inst)) { + Log.Out(Logs::General, Logs::Inventory, "Disenchant Summoned Bags: Replacing %s with %s in slot %i", inst->GetItem()->Name, new_inst->GetItem()->Name, MainCursor); + std::list local; + local.push_front(new_inst); + m_inv.PopItem(MainCursor); + safe_delete(inst); + + while (!m_inv.CursorEmpty()) { + auto limbo_inst = m_inv.PopItem(MainCursor); + if (limbo_inst == nullptr) { continue; } + local.push_back(limbo_inst); + } + + for (auto iter = local.begin(); iter != local.end(); ++iter) { + auto cur_inst = *iter; + if (cur_inst == nullptr) { continue; } + m_inv.PushCursor(*cur_inst); + safe_delete(cur_inst); + } + local.clear(); + + auto s = m_inv.cursor_cbegin(), e = m_inv.cursor_cend(); + database.SaveCursor(this->CharacterID(), s, e); + } + else { + safe_delete(new_inst); // deletes disenchanted bag if not used + } + + break; + } +} + void Client::RemoveNoRent(bool client_update) { for (auto slot_id = EmuConstants::EQUIPMENT_BEGIN; slot_id <= EmuConstants::EQUIPMENT_END; ++slot_id) { diff --git a/zone/loottables.cpp b/zone/loottables.cpp index 51190c9db..5cfd95239 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -58,7 +58,7 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite if(max_cash > 0 && lts->avgcoin > 0 && EQEmu::ValueWithin(lts->avgcoin, min_cash, max_cash)) { float upper_chance = (float)(lts->avgcoin - min_cash) / (float)(max_cash - min_cash); float avg_cash_roll = (float)zone->random.Real(0.0, 1.0); - + if(avg_cash_roll < upper_chance) { cash = zone->random.Int(lts->avgcoin, max_cash); } else { @@ -332,6 +332,8 @@ void NPC::AddLootDrop(const ItemData *item2, ItemList* itemlist, int16 charges, CastToMob()->AddProcToWeapon(item2->Proc.Effect, true); eslot = MaterialPrimary; + if (item2->Damage > 0) + SendAddPlayerState(PlayerState::PrimaryWeaponEquipped); } else if (foundslot == MainSecondary && (GetOwner() != nullptr || (GetLevel() >= 13 && zone->random.Roll(NPC_DW_CHANCE)) || (item2->Damage==0)) && @@ -342,6 +344,8 @@ void NPC::AddLootDrop(const ItemData *item2, ItemList* itemlist, int16 charges, CastToMob()->AddProcToWeapon(item2->Proc.Effect, true); eslot = MaterialSecondary; + if (item2->Damage > 0) + SendAddPlayerState(PlayerState::SecondaryWeaponEquipped); } else if (foundslot == MainHead) { eslot = MaterialHead; diff --git a/zone/lua_client.cpp b/zone/lua_client.cpp index 400d2a419..d0e325fdf 100644 --- a/zone/lua_client.cpp +++ b/zone/lua_client.cpp @@ -1255,6 +1255,46 @@ void Lua_Client::PlayMP3(std::string file) self->PlayMP3(file.c_str()); } +void Lua_Client::QuestReward(Lua_Mob target) { + Lua_Safe_Call_Void(); + self->QuestReward(target); +} + +void Lua_Client::QuestReward(Lua_Mob target, uint32 copper) { + Lua_Safe_Call_Void(); + self->QuestReward(target, copper); +} + +void Lua_Client::QuestReward(Lua_Mob target, uint32 copper, uint32 silver) { + Lua_Safe_Call_Void(); + self->QuestReward(target, copper, silver); +} + +void Lua_Client::QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold) { + Lua_Safe_Call_Void(); + self->QuestReward(target, copper, silver, gold); +} + +void Lua_Client::QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum) { + Lua_Safe_Call_Void(); + self->QuestReward(target, copper, silver, gold, platinum); +} + +void Lua_Client::QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid) { + Lua_Safe_Call_Void(); + self->QuestReward(target, copper, silver, gold, platinum, itemid); +} + +void Lua_Client::QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp) { + Lua_Safe_Call_Void(); + self->QuestReward(target, copper, silver, gold, platinum, itemid, exp); +} + +void Lua_Client::QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp, bool faction) { + Lua_Safe_Call_Void(); + self->QuestReward(target, copper, silver, gold, platinum, itemid, exp, faction); +} + luabind::scope lua_register_client() { return luabind::class_("Client") .def(luabind::constructor<>()) @@ -1504,7 +1544,15 @@ luabind::scope lua_register_client() { .def("SetConsumption", (void(Lua_Client::*)(int, int))&Lua_Client::SetConsumption) .def("SendMarqueeMessage", (void(Lua_Client::*)(uint32, uint32, uint32, uint32, uint32, std::string))&Lua_Client::SendMarqueeMessage) .def("SendColoredText", (void(Lua_Client::*)(uint32, std::string))&Lua_Client::SendColoredText) - .def("PlayMP3", (void(Lua_Client::*)(std::string))&Lua_Client::PlayMP3); + .def("PlayMP3", (void(Lua_Client::*)(std::string))&Lua_Client::PlayMP3) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32, uint32))&Lua_Client::QuestReward) + .def("QuestReward", (void(Lua_Client::*)(Lua_Mob, uint32, uint32, uint32, uint32, uint32, uint32, bool))&Lua_Client::QuestReward); } luabind::scope lua_register_inventory_where() { diff --git a/zone/lua_client.h b/zone/lua_client.h index e2b0a6614..8f930fc26 100644 --- a/zone/lua_client.h +++ b/zone/lua_client.h @@ -278,6 +278,14 @@ public: void SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg); void SendColoredText(uint32 type, std::string msg); void PlayMP3(std::string file); + void QuestReward(Lua_Mob target); + void QuestReward(Lua_Mob target, uint32 copper); + void QuestReward(Lua_Mob target, uint32 copper, uint32 silver); + void QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold); + void QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum); + void QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid); + void QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp); + void QuestReward(Lua_Mob target, uint32 copper, uint32 silver, uint32 gold, uint32 platinum, uint32 itemid, uint32 exp, bool faction); }; #endif diff --git a/zone/lua_encounter.cpp b/zone/lua_encounter.cpp new file mode 100644 index 000000000..08a4f95d3 --- /dev/null +++ b/zone/lua_encounter.cpp @@ -0,0 +1,13 @@ +#ifdef LUA_EQEMU + +#include "lua.hpp" +#include +#include "lua_encounter.h" +#include "encounter.h" + + +luabind::scope lua_register_encounter() { + return luabind::class_("Encounter"); +} + +#endif \ No newline at end of file diff --git a/zone/lua_encounter.h b/zone/lua_encounter.h new file mode 100644 index 000000000..d4ce63bb9 --- /dev/null +++ b/zone/lua_encounter.h @@ -0,0 +1,30 @@ +#ifndef EQEMU_LUA_ENCOUNTER_H +#define EQEMU_LUA_ENCOUNTER_H +#ifdef LUA_EQEMU + +#include "lua_ptr.h" + +class Encounter; + +namespace luabind { + struct scope; + class object; +} + +luabind::scope lua_register_encounter(); + +class Lua_Encounter : public Lua_Ptr +{ + typedef Encounter NativeType; +public: + Lua_Encounter() { SetLuaPtrData(nullptr); } + Lua_Encounter(Encounter *d) { SetLuaPtrData(reinterpret_cast(d)); } + virtual ~Lua_Encounter() { } + + operator Encounter*() { + return reinterpret_cast(GetLuaPtrData()); + } + +}; +#endif +#endif \ No newline at end of file diff --git a/zone/lua_general.cpp b/zone/lua_general.cpp index 59a5d3740..bf65fea7c 100644 --- a/zone/lua_general.cpp +++ b/zone/lua_general.cpp @@ -18,6 +18,8 @@ #include "qglobals.h" #include "../common/timer.h" #include "../common/eqemu_logsys.h" +#include "encounter.h" +#include "lua_encounter.h" struct Events { }; struct Factions { }; @@ -34,6 +36,8 @@ struct lua_registered_event { extern std::map> lua_encounter_events_registered; extern std::map lua_encounters_loaded; +extern std::map lua_encounters; + extern void MapOpcodes(); extern void ClearMappedOpcode(EmuOpcode op); @@ -42,19 +46,23 @@ void unregister_event(std::string package_name, std::string name, int evt); void load_encounter(std::string name) { if(lua_encounters_loaded.count(name) > 0) return; - + Encounter *enc = new Encounter(name.c_str()); + entity_list.AddEncounter(enc); + lua_encounters[name] = enc; lua_encounters_loaded[name] = true; - parse->EventEncounter(EVENT_ENCOUNTER_LOAD, name, 0); + parse->EventEncounter(EVENT_ENCOUNTER_LOAD, name, "", 0); } void load_encounter_with_data(std::string name, std::string info_str) { if(lua_encounters_loaded.count(name) > 0) return; - + Encounter *enc = new Encounter(name.c_str()); + entity_list.AddEncounter(enc); + lua_encounters[name] = enc; lua_encounters_loaded[name] = true; std::vector info_ptrs; info_ptrs.push_back(&info_str); - parse->EventEncounter(EVENT_ENCOUNTER_LOAD, name, 0, &info_ptrs); + parse->EventEncounter(EVENT_ENCOUNTER_LOAD, name, "", 0, &info_ptrs); } void unload_encounter(std::string name) { @@ -80,8 +88,10 @@ void unload_encounter(std::string name) { } } + lua_encounters[name]->Depop(); + lua_encounters.erase(name); lua_encounters_loaded.erase(name); - parse->EventEncounter(EVENT_ENCOUNTER_UNLOAD, name, 0); + parse->EventEncounter(EVENT_ENCOUNTER_UNLOAD, name, "", 0); } void unload_encounter_with_data(std::string name, std::string info_str) { @@ -109,10 +119,12 @@ void unload_encounter_with_data(std::string name, std::string info_str) { } } + lua_encounters[name]->Depop(); + lua_encounters.erase(name); lua_encounters_loaded.erase(name); std::vector info_ptrs; info_ptrs.push_back(&info_str); - parse->EventEncounter(EVENT_ENCOUNTER_UNLOAD, name, 0, &info_ptrs); + parse->EventEncounter(EVENT_ENCOUNTER_UNLOAD, name, "", 0, &info_ptrs); } void register_event(std::string package_name, std::string name, int evt, luabind::adl::object func) { @@ -285,6 +297,10 @@ void lua_set_timer(const char *timer, int time_ms, Lua_Mob mob) { quest_manager.settimerMS(timer, time_ms, mob); } +void lua_set_timer(const char *timer, int time_ms, Lua_Encounter enc) { + quest_manager.settimerMS(timer, time_ms, enc); +} + void lua_stop_timer(const char *timer) { quest_manager.stoptimer(timer); } @@ -297,6 +313,10 @@ void lua_stop_timer(const char *timer, Lua_Mob mob) { quest_manager.stoptimer(timer, mob); } +void lua_stop_timer(const char *timer, Lua_Encounter enc) { + quest_manager.stoptimer(timer, enc); +} + void lua_stop_all_timers() { quest_manager.stopalltimers(); } @@ -309,6 +329,10 @@ void lua_stop_all_timers(Lua_Mob mob) { quest_manager.stopalltimers(mob); } +void lua_stop_all_timers(Lua_Encounter enc) { + quest_manager.stopalltimers(enc); +} + void lua_depop() { quest_manager.depop(0); } @@ -386,7 +410,11 @@ void lua_create_guild(const char *name, const char *leader) { } void lua_set_time(int hour, int min) { - quest_manager.settime(hour, min); + quest_manager.settime(hour, min, true); +} + +void lua_set_time(int hour, int min, bool update_world) { + quest_manager.settime(hour, min, update_world); } void lua_signal(int npc_id, int signal_id) { @@ -979,7 +1007,7 @@ int lua_get_zone_weather() { luabind::adl::object lua_get_zone_time(lua_State *L) { TimeOfDay_Struct eqTime; - zone->zone_time.getEQTimeOfDay(time(0), &eqTime); + zone->zone_time.GetCurrentEQTimeOfDay(time(0), &eqTime); luabind::adl::object ret = luabind::newtable(L); ret["zone_hour"] = eqTime.hour - 1; @@ -1442,12 +1470,15 @@ luabind::scope lua_register_general() { luabind::def("set_timer", (void(*)(const char*, int))&lua_set_timer), luabind::def("set_timer", (void(*)(const char*, int, Lua_ItemInst))&lua_set_timer), luabind::def("set_timer", (void(*)(const char*, int, Lua_Mob))&lua_set_timer), + luabind::def("set_timer", (void(*)(const char*, int, Lua_Encounter))&lua_set_timer), luabind::def("stop_timer", (void(*)(const char*))&lua_stop_timer), luabind::def("stop_timer", (void(*)(const char*, Lua_ItemInst))&lua_stop_timer), luabind::def("stop_timer", (void(*)(const char*, Lua_Mob))&lua_stop_timer), + luabind::def("stop_timer", (void(*)(const char*, Lua_Encounter))&lua_stop_timer), luabind::def("stop_all_timers", (void(*)(void))&lua_stop_all_timers), luabind::def("stop_all_timers", (void(*)(Lua_ItemInst))&lua_stop_all_timers), luabind::def("stop_all_timers", (void(*)(Lua_Mob))&lua_stop_all_timers), + luabind::def("stop_all_timers", (void(*)(Lua_Encounter))&lua_stop_all_timers), luabind::def("depop", (void(*)(void))&lua_depop), luabind::def("depop", (void(*)(int))&lua_depop), luabind::def("depop_with_timer", (void(*)(void))&lua_depop_with_timer), @@ -1467,7 +1498,8 @@ luabind::scope lua_register_general() { luabind::def("set_sky", &lua_set_sky), luabind::def("set_guild", &lua_set_guild), luabind::def("create_guild", &lua_create_guild), - luabind::def("set_time", &lua_set_time), + luabind::def("set_time", (void(*)(int, int))&lua_set_time), + luabind::def("set_time", (void(*)(int, int, bool))&lua_set_time), luabind::def("signal", (void(*)(int,int))&lua_signal), luabind::def("signal", (void(*)(int,int,int))&lua_signal), luabind::def("set_global", &lua_set_global), diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index 04d11566a..f4d7d64cb 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -1590,26 +1590,6 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) { beard, aa_title, drakkin_heritage, drakkin_tattoo, drakkin_details, size); } -void Lua_Mob::QuestReward(Lua_Client c) { - Lua_Safe_Call_Void(); - self->QuestReward(c); -} - -void Lua_Mob::QuestReward(Lua_Client c, uint32 silver) { - Lua_Safe_Call_Void(); - self->QuestReward(c, silver); -} - -void Lua_Mob::QuestReward(Lua_Client c, uint32 silver, uint32 gold) { - Lua_Safe_Call_Void(); - self->QuestReward(c, silver, gold); -} - -void Lua_Mob::QuestReward(Lua_Client c, uint32 silver, uint32 gold, uint32 platinum) { - Lua_Safe_Call_Void(); - self->QuestReward(c, silver, gold, platinum); -} - void Lua_Mob::CameraEffect(uint32 duration, uint32 intensity) { Lua_Safe_Call_Void(); self->CameraEffect(duration, intensity); @@ -1651,6 +1631,11 @@ void Lua_Mob::TempName(const char *newname) { self->TempName(newname); } +std::string Lua_Mob::GetGlobal(const char *varname) { + Lua_Safe_Call_String(); + return self->GetGlobal(varname); +} + void Lua_Mob::SetGlobal(const char *varname, const char *newvalue, int options, const char *duration) { Lua_Safe_Call_Void(); self->SetGlobal(varname, newvalue, options, duration); @@ -1866,6 +1851,10 @@ int Lua_Mob::CanBuffStack(int spell_id, int caster_level, bool fail_if_overwrite return self->CanBuffStack(spell_id, caster_level, fail_if_overwrite); } +void Lua_Mob::SetPseudoRoot(bool in) { + Lua_Safe_Call_Void(); + self->SetPseudoRoot(in); +} luabind::scope lua_register_mob() { return luabind::class_("Mob") @@ -2132,10 +2121,6 @@ luabind::scope lua_register_mob() { .def("SetRace", (void(Lua_Mob::*)(int))&Lua_Mob::SetRace) .def("SetGender", (void(Lua_Mob::*)(int))&Lua_Mob::SetGender) .def("SendIllusionPacket", (void(Lua_Mob::*)(luabind::adl::object))&Lua_Mob::SendIllusionPacket) - .def("QuestReward", (void(Lua_Mob::*)(Lua_Client))&Lua_Mob::QuestReward) - .def("QuestReward", (void(Lua_Mob::*)(Lua_Client,uint32))&Lua_Mob::QuestReward) - .def("QuestReward", (void(Lua_Mob::*)(Lua_Client,uint32,uint32))&Lua_Mob::QuestReward) - .def("QuestReward", (void(Lua_Mob::*)(Lua_Client,uint32,uint32,uint32))&Lua_Mob::QuestReward) .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32))&Lua_Mob::CameraEffect) .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client))&Lua_Mob::CameraEffect) .def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client,bool))&Lua_Mob::CameraEffect) @@ -2144,6 +2129,7 @@ luabind::scope lua_register_mob() { .def("SendSpellEffect", (void(Lua_Mob::*)(uint32,uint32,uint32,bool,uint32,bool,Lua_Client))&Lua_Mob::SendSpellEffect) .def("TempName", (void(Lua_Mob::*)(void))&Lua_Mob::TempName) .def("TempName", (void(Lua_Mob::*)(const char*))&Lua_Mob::TempName) + .def("GetGlobal", (std::string(Lua_Mob::*)(const char*))&Lua_Mob::GetGlobal) .def("SetGlobal", (void(Lua_Mob::*)(const char*,const char*,int,const char*))&Lua_Mob::SetGlobal) .def("SetGlobal", (void(Lua_Mob::*)(const char*,const char*,int,const char*,Lua_Mob))&Lua_Mob::SetGlobal) .def("TarGlobal", (void(Lua_Mob::*)(const char*,const char*,const char*,int,int,int))&Lua_Mob::TarGlobal) @@ -2186,7 +2172,8 @@ luabind::scope lua_register_mob() { .def("BuffFadeBySlot", (void(Lua_Mob::*)(int))&Lua_Mob::BuffFadeBySlot) .def("BuffFadeBySlot", (void(Lua_Mob::*)(int,bool))&Lua_Mob::BuffFadeBySlot) .def("CanBuffStack", (int(Lua_Mob::*)(int,int))&Lua_Mob::CanBuffStack) - .def("CanBuffStack", (int(Lua_Mob::*)(int,int,bool))&Lua_Mob::CanBuffStack); + .def("CanBuffStack", (int(Lua_Mob::*)(int,int,bool))&Lua_Mob::CanBuffStack) + .def("SetPseudoRoot", (void(Lua_Mob::*)(bool))&Lua_Mob::SetPseudoRoot); } luabind::scope lua_register_special_abilities() { diff --git a/zone/lua_mob.h b/zone/lua_mob.h index f272cd440..023fedd2a 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -169,7 +169,7 @@ public: bool CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost); bool CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost, int item_slot); bool CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost, int item_slot, int timer, int timer_duration); - bool CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost, int item_slot, int timer, int timer_duration, + bool CastSpell(int spell_id, int target_id, int slot, int cast_time, int mana_cost, int item_slot, int timer, int timer_duration, int resist_adjust); bool SpellFinished(int spell_id, Lua_Mob target); bool SpellFinished(int spell_id, Lua_Mob target, int slot); @@ -296,10 +296,6 @@ public: void SetRace(int in); void SetGender(int in); void SendIllusionPacket(luabind::adl::object illusion); - void QuestReward(Lua_Client c); - void QuestReward(Lua_Client c, uint32 silver); - void QuestReward(Lua_Client c, uint32 silver, uint32 gold); - void QuestReward(Lua_Client c, uint32 silver, uint32 gold, uint32 platinum); void CameraEffect(uint32 duration, uint32 intensity); void CameraEffect(uint32 duration, uint32 intensity, Lua_Client c); void CameraEffect(uint32 duration, uint32 intensity, Lua_Client c, bool global); @@ -311,6 +307,7 @@ public: uint32 unk020, bool perm_effect, Lua_Client c); void TempName(); void TempName(const char *newname); + std::string GetGlobal(const char *varname); void SetGlobal(const char *varname, const char *newvalue, int options, const char *duration); void SetGlobal(const char *varname, const char *newvalue, int options, const char *duration, Lua_Mob other); void TarGlobal(const char *varname, const char *value, const char *duration, int npc_id, int char_id, int zone_id); @@ -355,6 +352,7 @@ public: void BuffFadeBySlot(int slot, bool recalc_bonuses); int CanBuffStack(int spell_id, int caster_level); int CanBuffStack(int spell_id, int caster_level, bool fail_if_overwrite); + void SetPseudoRoot(bool in); }; #endif diff --git a/zone/lua_packet.cpp b/zone/lua_packet.cpp index e16a85c6d..1eef7220d 100644 --- a/zone/lua_packet.cpp +++ b/zone/lua_packet.cpp @@ -18,7 +18,7 @@ Lua_Packet::Lua_Packet(int opcode, int size, bool raw) { if(raw) { SetLuaPtrData(new EQApplicationPacket(OP_Unknown, size)); owned_ = true; - + EQApplicationPacket *self = reinterpret_cast(d_); self->SetOpcodeBypass(opcode); } else { @@ -692,8 +692,8 @@ luabind::scope lua_register_packet_opcodes() { luabind::value("VetClaimRequest", static_cast(OP_VetClaimRequest)), luabind::value("VetClaimReply", static_cast(OP_VetClaimReply)), luabind::value("WeaponEquip1", static_cast(OP_WeaponEquip1)), - luabind::value("WeaponEquip2", static_cast(OP_WeaponEquip2)), - luabind::value("WeaponUnequip2", static_cast(OP_WeaponUnequip2)), + luabind::value("PlayerStateAdd", static_cast(OP_PlayerStateAdd)), + luabind::value("PlayerStateRemove", static_cast(OP_PlayerStateRemove)), luabind::value("WorldLogout", static_cast(OP_WorldLogout)), luabind::value("SessionReady", static_cast(OP_SessionReady)), luabind::value("Login", static_cast(OP_Login)), diff --git a/zone/lua_parser.cpp b/zone/lua_parser.cpp index 7f6412b0f..24f25948e 100644 --- a/zone/lua_parser.cpp +++ b/zone/lua_parser.cpp @@ -34,6 +34,7 @@ #include "questmgr.h" #include "zone.h" #include "lua_parser.h" +#include "lua_encounter.h" const char *LuaEvents[_LargestEventID] = { "event_say", @@ -128,6 +129,7 @@ struct lua_registered_event { std::map> lua_encounter_events_registered; std::map lua_encounters_loaded; +std::map lua_encounters; LuaParser::LuaParser() { for(int i = 0; i < _LargestEventID; ++i) { @@ -135,6 +137,7 @@ LuaParser::LuaParser() { PlayerArgumentDispatch[i] = handle_player_null; ItemArgumentDispatch[i] = handle_item_null; SpellArgumentDispatch[i] = handle_spell_null; + EncounterArgumentDispatch[i] = handle_encounter_null; } NPCArgumentDispatch[EVENT_SAY] = handle_npc_event_say; @@ -213,6 +216,10 @@ LuaParser::LuaParser() { SpellArgumentDispatch[EVENT_SPELL_FADE] = handle_spell_fade; SpellArgumentDispatch[EVENT_SPELL_EFFECT_TRANSLOCATE_COMPLETE] = handle_translocate_finish; + EncounterArgumentDispatch[EVENT_TIMER] = handle_encounter_timer; + EncounterArgumentDispatch[EVENT_ENCOUNTER_LOAD] = handle_encounter_load; + EncounterArgumentDispatch[EVENT_ENCOUNTER_UNLOAD] = handle_encounter_unload; + L = nullptr; } @@ -575,7 +582,7 @@ int LuaParser::_EventSpell(std::string package_name, QuestEventID evt, NPC* npc, return 0; } -int LuaParser::EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, std::vector *extra_pointers) { +int LuaParser::EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers) { evt = ConvertLuaEvent(evt); if(evt >= _LargestEventID) { return 0; @@ -587,10 +594,10 @@ int LuaParser::EventEncounter(QuestEventID evt, std::string encounter_name, uint return 0; } - return _EventEncounter(package_name, evt, encounter_name, extra_data, extra_pointers); + return _EventEncounter(package_name, evt, encounter_name, data, extra_data, extra_pointers); } -int LuaParser::_EventEncounter(std::string package_name, QuestEventID evt, std::string encounter_name, uint32 extra_data, +int LuaParser::_EventEncounter(std::string package_name, QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers) { const char *sub_name = LuaEvents[evt]; @@ -604,13 +611,12 @@ int LuaParser::_EventEncounter(std::string package_name, QuestEventID evt, std:: lua_pushstring(L, encounter_name.c_str()); lua_setfield(L, -2, "name"); - if(extra_pointers) { - std::string *str = EQEmu::any_cast(extra_pointers->at(0)); - lua_pushstring(L, str->c_str()); - lua_setfield(L, -2, "data"); - } + Encounter *enc = lua_encounters[encounter_name]; - quest_manager.StartQuest(nullptr, nullptr, nullptr, encounter_name); + auto arg_function = EncounterArgumentDispatch[evt]; + arg_function(this, L, enc, data, extra_data, extra_pointers); + + quest_manager.StartQuest(enc, nullptr, nullptr, encounter_name); if(lua_pcall(L, 1, 1, 0)) { std::string error = lua_tostring(L, -1); AddError(error); @@ -786,6 +792,11 @@ void LuaParser::ReloadQuests() { lua_encounter_events_registered.clear(); lua_encounters_loaded.clear(); + for (auto encounter : lua_encounters) { + encounter.second->Depop(); + } + lua_encounters.clear(); + if(L) { lua_close(L); } @@ -968,6 +979,7 @@ void LuaParser::MapFunctions(lua_State *L) { lua_register_client_version(), lua_register_appearance(), lua_register_entity(), + lua_register_encounter(), lua_register_mob(), lua_register_special_abilities(), lua_register_npc(), diff --git a/zone/lua_parser.h b/zone/lua_parser.h index 13cebe0fc..63d9facfe 100644 --- a/zone/lua_parser.h +++ b/zone/lua_parser.h @@ -39,7 +39,7 @@ public: std::vector *extra_pointers); virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, std::vector *extra_pointers); - virtual int EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, + virtual int EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers); virtual bool HasQuestSub(uint32 npc_id, QuestEventID evt); @@ -82,7 +82,7 @@ private: uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); int _EventSpell(std::string package_name, QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, std::vector *extra_pointers, luabind::adl::object *l_func = nullptr); - int _EventEncounter(std::string package_name, QuestEventID evt, std::string encounter_name, uint32 extra_data, + int _EventEncounter(std::string package_name, QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers); void LoadScript(std::string filename, std::string package_name); @@ -99,6 +99,8 @@ private: PlayerArgumentHandler PlayerArgumentDispatch[_LargestEventID]; ItemArgumentHandler ItemArgumentDispatch[_LargestEventID]; SpellArgumentHandler SpellArgumentDispatch[_LargestEventID]; + EncounterArgumentHandler EncounterArgumentDispatch[_LargestEventID]; + }; #endif diff --git a/zone/lua_parser_events.cpp b/zone/lua_parser_events.cpp index 9b5883e7c..dbf1e0ba5 100644 --- a/zone/lua_parser_events.cpp +++ b/zone/lua_parser_events.cpp @@ -22,6 +22,7 @@ #include "lua_door.h" #include "lua_object.h" #include "lua_packet.h" +#include "lua_encounter.h" #include "zone.h" #include "lua_parser_events.h" @@ -704,4 +705,39 @@ void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* cl std::vector *extra_pointers) { } +void handle_encounter_timer(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers) { + lua_pushstring(L, data.c_str()); + lua_setfield(L, -2, "timer"); +} + +void handle_encounter_load(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers) { + if (encounter) { + Lua_Encounter l_enc(encounter); + luabind::adl::object l_enc_o = luabind::adl::object(L, l_enc); + l_enc_o.push(L); + lua_setfield(L, -2, "encounter"); + } + if (extra_pointers) { + std::string *str = EQEmu::any_cast(extra_pointers->at(0)); + lua_pushstring(L, str->c_str()); + lua_setfield(L, -2, "data"); + } +} + +void handle_encounter_unload(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers) { + if (extra_pointers) { + std::string *str = EQEmu::any_cast(extra_pointers->at(0)); + lua_pushstring(L, str->c_str()); + lua_setfield(L, -2, "data"); + } +} + +void handle_encounter_null(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers) { + +} + #endif diff --git a/zone/lua_parser_events.h b/zone/lua_parser_events.h index 1965a9189..0a2ab5ad9 100644 --- a/zone/lua_parser_events.h +++ b/zone/lua_parser_events.h @@ -6,6 +6,7 @@ typedef void(*NPCArgumentHandler)(QuestInterface*, lua_State*, NPC*, Mob*, std:: typedef void(*PlayerArgumentHandler)(QuestInterface*, lua_State*, Client*, std::string, uint32, std::vector*); typedef void(*ItemArgumentHandler)(QuestInterface*, lua_State*, Client*, ItemInst*, Mob*, std::string, uint32, std::vector*); typedef void(*SpellArgumentHandler)(QuestInterface*, lua_State*, NPC*, Client*, uint32, uint32, std::vector*); +typedef void(*EncounterArgumentHandler)(QuestInterface*, lua_State*, Encounter* encounter, std::string, uint32, std::vector*); //NPC void handle_npc_event_say(QuestInterface *parse, lua_State* L, NPC* npc, Mob *init, std::string data, uint32 extra_data, @@ -127,5 +128,16 @@ void handle_translocate_finish(QuestInterface *parse, lua_State* L, NPC* npc, Cl void handle_spell_null(QuestInterface *parse, lua_State* L, NPC* npc, Client* client, uint32 spell_id, uint32 extra_data, std::vector *extra_pointers); + +//Encounter +void handle_encounter_timer(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers); +void handle_encounter_load(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers); +void handle_encounter_unload(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers); +void handle_encounter_null(QuestInterface *parse, lua_State* L, Encounter* encounter, std::string data, uint32 extra_data, + std::vector *extra_pointers); + #endif #endif diff --git a/zone/map.cpp b/zone/map.cpp index d4bb5a268..37f394626 100644 --- a/zone/map.cpp +++ b/zone/map.cpp @@ -64,7 +64,7 @@ Map::~Map() { float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result) const { if (!imp) - return false; + return BEST_Z_INVALID; glm::vec3 tmp; if(!result) @@ -93,6 +93,41 @@ float Map::FindBestZ(glm::vec3 &start, glm::vec3 *result) const { return BEST_Z_INVALID; } +float Map::FindClosestZ(glm::vec3 &start, glm::vec3 *result) const { + // Unlike FindBestZ, this method finds the closest Z value above or below the specified point. + // + if (!imp) + return false; + + float ClosestZ = BEST_Z_INVALID; + + glm::vec3 tmp; + if (!result) + result = &tmp; + + glm::vec3 from(start.x, start.y, start.z); + glm::vec3 to(start.x, start.y, BEST_Z_INVALID); + float hit_distance; + bool hit = false; + + // first check is below us + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance); + if (hit) { + ClosestZ = result->z; + + } + + // Find nearest Z above us + to.z = -BEST_Z_INVALID; + hit = imp->rm->raycast((const RmReal*)&from, (const RmReal*)&to, (RmReal*)result, nullptr, &hit_distance); + if (hit) { + if (abs(from.z - result->z) < abs(ClosestZ - from.z)) + return result->z; + } + + return ClosestZ; +} + bool Map::LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, glm::vec3 *result) const { if(!imp) return false; diff --git a/zone/map.h b/zone/map.h index 2bce1c6db..f3e81fe20 100644 --- a/zone/map.h +++ b/zone/map.h @@ -34,6 +34,7 @@ public: ~Map(); float FindBestZ(glm::vec3 &start, glm::vec3 *result) const; + float FindClosestZ(glm::vec3 &start, glm::vec3 *result) const; bool LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, glm::vec3 *result) const; bool LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, glm::vec3 *result) const; bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc) const; diff --git a/zone/merc.cpp b/zone/merc.cpp index 0ae9a9d84..888a9c425 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -1236,10 +1236,7 @@ void Merc::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) { bool Merc::Process() { if(IsStunned() && stunned_timer.Check()) - { - this->stunned = false; - this->stunned_timer.Disable(); - } + Mob::UnStun(); if (GetDepop()) { @@ -1463,14 +1460,16 @@ void Merc::AI_Process() { if(moved) { moved = false; - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); } } return; } + if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) + SendAddPlayerState(PlayerState::Aggressive); + bool atCombatRange = false; float meleeDistance = GetMaxMeleeRangeToTarget(GetTarget()); @@ -1497,9 +1496,7 @@ void Merc::AI_Process() { SetRunAnimSpeed(0); if(moved) { - moved = false; - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); } } @@ -1684,6 +1681,9 @@ void Merc::AI_Process() { confidence_timer.Disable(); _check_confidence = false; + if (m_PlayerState & static_cast(PlayerState::Aggressive)) + SendRemovePlayerState(PlayerState::Aggressive); + if(!check_target_timer.Enabled()) check_target_timer.Start(2000, false); @@ -1707,7 +1707,7 @@ void Merc::AI_Process() { if(follow) { float dist = DistanceSquared(m_Position, follow->GetPosition()); - float speed = GetRunspeed(); + int speed = GetRunspeed(); if(dist < GetFollowDistance() + 1000) speed = GetWalkspeed(); @@ -1724,9 +1724,8 @@ void Merc::AI_Process() { { if(moved) { - moved=false; - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); + moved = false; } } } diff --git a/zone/mob.cpp b/zone/mob.cpp index 7567eb5c6..41ad94d2d 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef BOTS #include "bot.h" @@ -149,6 +150,29 @@ Mob::Mob(const char* in_name, size = in_size; base_size = size; runspeed = in_runspeed; + // neotokyo: sanity check + if (runspeed < 0 || runspeed > 20) + runspeed = 1.25f; + base_runspeed = (int)((float)runspeed * 40.0f); + // clients + if (runspeed == 0.7f) { + base_runspeed = 28; + walkspeed = 0.3f; + base_walkspeed = 12; + fearspeed = 0.625f; + base_fearspeed = 25; + // npcs + } else { + base_walkspeed = base_runspeed * 100 / 265; + walkspeed = ((float)base_walkspeed) * 0.025f; + base_fearspeed = base_runspeed * 100 / 127; + fearspeed = ((float)base_fearspeed) * 0.025f; + } + + + current_speed = base_runspeed; + + m_PlayerState = 0; // sanity check @@ -161,7 +185,7 @@ Mob::Mob(const char* in_name, m_Light.Level.Spell = m_Light.Type.Spell = 0; m_Light.Type.Active = m_Light.Type.Innate; m_Light.Level.Active = m_Light.Level.Innate; - + texture = in_texture; helmtexture = in_helmtexture; armtexture = in_armtexture; @@ -240,15 +264,19 @@ Mob::Mob(const char* in_name, PermaProcs[j].spellID = SPELL_UNKNOWN; PermaProcs[j].chance = 0; PermaProcs[j].base_spellID = SPELL_UNKNOWN; + PermaProcs[j].level_override = -1; SpellProcs[j].spellID = SPELL_UNKNOWN; SpellProcs[j].chance = 0; SpellProcs[j].base_spellID = SPELL_UNKNOWN; + SpellProcs[j].level_override = -1; DefensiveProcs[j].spellID = SPELL_UNKNOWN; DefensiveProcs[j].chance = 0; DefensiveProcs[j].base_spellID = SPELL_UNKNOWN; + DefensiveProcs[j].level_override = -1; RangedProcs[j].spellID = SPELL_UNKNOWN; RangedProcs[j].chance = 0; RangedProcs[j].base_spellID = SPELL_UNKNOWN; + RangedProcs[j].level_override = -1; } for (i = 0; i < _MaterialCount; i++) @@ -311,7 +339,7 @@ Mob::Mob(const char* in_name, pLastChange = 0; SetPetID(0); SetOwnerID(0); - typeofpet = petCharmed; //default to charmed... + typeofpet = petNone; // default to not a pet petpower = 0; held = false; nocast = false; @@ -531,48 +559,32 @@ bool Mob::IsInvisible(Mob* other) const return(false); } -float Mob::_GetMovementSpeed(int mod) const -{ - // List of movement speed modifiers, including AAs & spells: - // http://everquest.allakhazam.com/db/item.html?item=1721;page=1;howmany=50#m10822246245352 - if (IsRooted()) - return 0.0f; +int Mob::_GetWalkSpeed() const { + + if (IsRooted() || IsStunned() || IsMezzed()) + return 0; + else if (IsPseudoRooted()) - return 0.00001f; + return 0; - float speed_mod = runspeed; + int aa_mod = 0; + int speed_mod = base_walkspeed; + int base_run = base_runspeed; + bool has_horse = false; + int runspeedcap = RuleI(Character,BaseRunSpeedCap); + runspeedcap += itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap; + aa_mod += aabonuses.BaseMovementSpeed; - // These two cases ignore the cap, be wise in the DB for horses. if (IsClient()) { - if (CastToClient()->GetGMSpeed()) { - speed_mod = 3.125f; - if (mod != 0) - speed_mod += speed_mod * static_cast(mod) / 100.0f; + Mob *horse = entity_list.GetMob(CastToClient()->GetHorseId()); + if (horse) { + speed_mod = horse->GetBaseRunspeed(); return speed_mod; - } else { - Mob *horse = entity_list.GetMob(CastToClient()->GetHorseId()); - if (horse) { - speed_mod = horse->GetBaseRunspeed(); - if (mod != 0) - speed_mod += speed_mod * static_cast(mod) / 100.0f; - return speed_mod; - } } } - int aa_mod = 0; - int spell_mod = 0; - int runspeedcap = RuleI(Character,BaseRunSpeedCap); + int spell_mod = spellbonuses.movementspeed + itembonuses.movementspeed; int movemod = 0; - float frunspeedcap = 0.0f; - - runspeedcap += itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap; - aa_mod += itembonuses.BaseMovementSpeed + spellbonuses.BaseMovementSpeed + aabonuses.BaseMovementSpeed; - spell_mod += spellbonuses.movementspeed + itembonuses.movementspeed; - - // hard cap - if (runspeedcap > 225) - runspeedcap = 225; if (spell_mod < 0) movemod += spell_mod; @@ -581,27 +593,189 @@ float Mob::_GetMovementSpeed(int mod) const else movemod = aa_mod; - // cap negative movemods from snares mostly - if (movemod < -85) + // hard cap + if (runspeedcap > 225) + runspeedcap = 225; + + if(movemod < -85) //cap it at moving very very slow movemod = -85; - if (movemod != 0) - speed_mod += speed_mod * static_cast(movemod) / 100.0f; + if (!has_horse && movemod != 0) + speed_mod += (base_run * movemod / 100); - // runspeed caps - frunspeedcap = static_cast(runspeedcap) / 100.0f; - if (IsClient() && speed_mod > frunspeedcap) - speed_mod = frunspeedcap; + if(speed_mod < 1) + return(0); - // apply final mod such as the -47 for walking - // use runspeed since it should stack with snares - // and if we get here, we know runspeed was the initial - // value before we applied movemod. - if (mod != 0) - speed_mod += runspeed * static_cast(mod) / 100.0f; + //runspeed cap. + if(IsClient()) + { + if(speed_mod > runspeedcap) + speed_mod = runspeedcap; + } + return speed_mod; +} - if (speed_mod <= 0.0f) - speed_mod = IsClient() ? 0.0001f : 0.0f; +int Mob::_GetRunSpeed() const { + if (IsRooted() || IsStunned() || IsMezzed() || IsPseudoRooted()) + return 0; + + int aa_mod = 0; + int speed_mod = base_runspeed; + int base_walk = base_walkspeed; + bool has_horse = false; + if (IsClient()) + { + if(CastToClient()->GetGMSpeed()) + { + speed_mod = 325; + } + else + { + Mob* horse = entity_list.GetMob(CastToClient()->GetHorseId()); + if(horse) + { + speed_mod = horse->GetBaseRunspeed(); + base_walk = horse->GetBaseWalkspeed(); + has_horse = true; + } + } + } + + int runspeedcap = RuleI(Character,BaseRunSpeedCap); + runspeedcap += itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap; + + aa_mod = itembonuses.IncreaseRunSpeedCap + spellbonuses.IncreaseRunSpeedCap + aabonuses.IncreaseRunSpeedCap; + int spell_mod = spellbonuses.movementspeed + itembonuses.movementspeed; + int movemod = 0; + + if(spell_mod < 0) + { + movemod += spell_mod; + } + else if(spell_mod > aa_mod) + { + movemod = spell_mod; + } + else + { + movemod = aa_mod; + } + + if(movemod < -85) //cap it at moving very very slow + movemod = -85; + + if (!has_horse && movemod != 0) + { + if (IsClient()) + { + speed_mod += (speed_mod * movemod / 100); + } else { + if (movemod < 0) { + speed_mod += (50 * movemod / 100); + // basically stoped + if(speed_mod < 1) + { + return(0); + } + // moving slowly + if (speed_mod < 8) + return(8); + } else { + speed_mod += GetBaseWalkspeed(); + if (movemod > 50) + speed_mod += 4; + if (movemod > 40) + speed_mod += 3; + } + } + } + + if(speed_mod < 1) + { + return(0); + } + //runspeed cap. + if(IsClient()) + { + if(speed_mod > runspeedcap) + speed_mod = runspeedcap; + } + return speed_mod; +} + +int Mob::_GetFearSpeed() const { + + if (IsRooted() || IsStunned() || IsMezzed()) + return 0; + + //float speed_mod = fearspeed; + int speed_mod = GetBaseFearSpeed(); + + // use a max of 1.75f in calcs. + int base_run = std::min(GetBaseRunspeed(), 70); + + int spell_mod = spellbonuses.movementspeed + itembonuses.movementspeed; + int movemod = 0; + + if(spell_mod < 0) + { + movemod += spell_mod; + } + + if(movemod < -85) //cap it at moving very very slow + movemod = -85; + + if (IsClient()) { + if (CastToClient()->IsRunning()) + speed_mod = GetBaseRunspeed(); + else + speed_mod = GetBaseWalkspeed(); + if (movemod < 0) + return GetBaseWalkspeed(); + speed_mod += (base_run * movemod / 100); + return speed_mod; + } else { + int hp_ratio = GetIntHPRatio(); + // very large snares 50% or higher + if (movemod < -49) + { + if (hp_ratio < 25) + { + return (0); + } + if (hp_ratio < 50) + return (8); + else + return (12); + } + if (hp_ratio < 5) { + speed_mod = base_walkspeed / 3; + } else if (hp_ratio < 15) { + speed_mod = base_walkspeed / 2; + } else if (hp_ratio < 25) { + speed_mod = base_walkspeed + 1; // add the +1 so they do the run animation + } else if (hp_ratio < 50) { + speed_mod *= 82; + speed_mod /= 100; + } + if (movemod > 0) { + speed_mod += GetBaseWalkspeed(); + if (movemod > 50) + speed_mod += 4; + if (movemod > 40) + speed_mod += 3; + return speed_mod; + } + else if (movemod < 0) { + speed_mod += (base_run * movemod / 100); + } + } + if (speed_mod < 1) + return (0); + if (speed_mod < 9) + return (8); + if (speed_mod < 13) + return (12); return speed_mod; } @@ -740,7 +914,7 @@ void Mob::CreateSpawnPacket(EQApplicationPacket* app, Mob* ForWho) { NewSpawn_Struct* ns = (NewSpawn_Struct*)app->pBuffer; FillSpawnStruct(ns, ForWho); - if(strlen(ns->spawn.lastName) == 0) + if(strlen(ns->spawn.lastName) == 0) { switch(ns->spawn.class_) { @@ -916,6 +1090,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) ns->spawn.class_ = class_; ns->spawn.gender = gender; ns->spawn.level = level; + ns->spawn.PlayerState = m_PlayerState; ns->spawn.deity = deity; ns->spawn.animation = 0; ns->spawn.findable = findable?1:0; @@ -1092,7 +1267,7 @@ void Mob::CreateHPPacket(EQApplicationPacket* app) } // sends hp update of this mob to people who might care -void Mob::SendHPUpdate() +void Mob::SendHPUpdate(bool skip_self) { EQApplicationPacket hp_app; Group *group; @@ -1181,8 +1356,7 @@ void Mob::SendHPUpdate() } // send to self - we need the actual hps here - if(IsClient()) - { + if(IsClient() && !skip_self) { EQApplicationPacket* hp_app2 = new EQApplicationPacket(OP_HPUpdate,sizeof(SpawnHPUpdate_Struct)); SpawnHPUpdate_Struct* ds = (SpawnHPUpdate_Struct*)hp_app2->pBuffer; ds->cur_hp = CastToClient()->GetHP() - itembonuses.HP; @@ -1191,6 +1365,7 @@ void Mob::SendHPUpdate() CastToClient()->QueuePacket(hp_app2); safe_delete(hp_app2); } + ResetHPUpdateTimer(); // delay the timer } // this one just warps the mob to the current location @@ -1301,7 +1476,7 @@ void Mob::ShowStats(Client* client) if(n->respawn2 != 0) spawngroupid = n->respawn2->SpawnGroupID(); client->Message(0, " NPCID: %u SpawnGroupID: %u Grid: %i LootTable: %u FactionID: %i SpellsID: %u ", GetNPCTypeID(),spawngroupid, n->GetGrid(), n->GetLoottableID(), n->GetNPCFactionID(), n->GetNPCSpellsID()); - client->Message(0, " Accuracy: %i MerchantID: %i EmoteID: %i Runspeed: %f Walkspeed: %f", n->GetAccuracyRating(), n->MerchantType, n->GetEmoteID(), n->GetRunspeed(), n->GetWalkspeed()); + client->Message(0, " Accuracy: %i MerchantID: %i EmoteID: %i Runspeed: %u Walkspeed: %u", n->GetAccuracyRating(), n->MerchantType, n->GetEmoteID(), n->GetRunspeed(), n->GetWalkspeed()); n->QueryLoot(client); } if (IsAIControlled()) { @@ -1788,7 +1963,7 @@ bool Mob::IsPlayerRace(uint16 in_race) { uint8 Mob::GetDefaultGender(uint16 in_race, uint8 in_gender) { - if (Mob::IsPlayerRace(in_race) || in_race == 15 || in_race == 50 || in_race == 57 || in_race == 70 || in_race == 98 || in_race == 118) { + if (Mob::IsPlayerRace(in_race) || in_race == 15 || in_race == 50 || in_race == 57 || in_race == 70 || in_race == 98 || in_race == 118 || in_race == 23) { if (in_gender >= 2) { // Male default for PC Races return 0; @@ -1909,22 +2084,6 @@ void Mob::SendTargetable(bool on, Client *specific_target) { safe_delete(outapp); } -void Mob::QuestReward(Client *c, uint32 silver, uint32 gold, uint32 platinum) { - - EQApplicationPacket* outapp = new EQApplicationPacket(OP_Sound, sizeof(QuestReward_Struct)); - QuestReward_Struct* qr = (QuestReward_Struct*) outapp->pBuffer; - - qr->from_mob = GetID(); // Entity ID for the from mob name - qr->silver = silver; - qr->gold = gold; - qr->platinum = platinum; - - if(c) - c->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); - - safe_delete(outapp); -} - void Mob::CameraEffect(uint32 duration, uint32 intensity, Client *c, bool global) { @@ -2041,12 +2200,12 @@ const int32& Mob::SetMana(int32 amount) void Mob::SetAppearance(EmuAppearance app, bool iIgnoreSelf) { - if (_appearance != app) { - _appearance = app; - SendAppearancePacket(AT_Anim, GetAppearanceValue(app), true, iIgnoreSelf); - if (this->IsClient() && this->IsAIControlled()) - SendAppearancePacket(AT_Anim, ANIM_FREEZE, false, false); - } + if (_appearance == app) + return; + _appearance = app; + SendAppearancePacket(AT_Anim, GetAppearanceValue(app), true, iIgnoreSelf); + if (this->IsClient() && this->IsAIControlled()) + SendAppearancePacket(AT_Anim, ANIM_FREEZE, false, false); } bool Mob::UpdateActiveLight() @@ -2138,8 +2297,10 @@ void Mob::SetOwnerID(uint16 NewOwnerID) { if (NewOwnerID == GetID() && NewOwnerID != 0) // ok, no charming yourself now =p return; ownerid = NewOwnerID; - if (ownerid == 0 && this->IsNPC() && this->GetPetType() != petCharmed) - this->Depop(); + // if we're setting the owner ID to 0 and they're not either charmed or not-a-pet then + // they're a normal pet and should be despawned + if (ownerid == 0 && IsNPC() && GetPetType() != petCharmed && GetPetType() != petNone) + Depop(); } // used in checking for behind (backstab) and checking in front (melee LoS) @@ -2553,7 +2714,35 @@ uint32 NPC::GetEquipment(uint8 material_slot) const return equipment[invslot]; } -void Mob::SendWearChange(uint8 material_slot) +void Mob::SendArmorAppearance(Client *one_client) +{ + // one_client of 0 means sent to all clients + // + // Despite the fact that OP_NewSpawn and OP_ZoneSpawns include the + // armor being worn and its mats, the client doesn't update the display + // on arrival of these packets reliably. + // + // Send Wear changes if mob is a PC race and item is an armor slot. + // The other packets work for primary/secondary. + + if (IsPlayerRace(race)) + { + if (!IsClient()) + { + const Item_Struct *item; + for (int i=0; i< 7 ; ++i) + { + item=database.GetItem(GetEquipment(i)); + if (item != 0) + { + SendWearChange(i,one_client); + } + } + } + } +} + +void Mob::SendWearChange(uint8 material_slot, Client *one_client) { EQApplicationPacket* outapp = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); WearChange_Struct* wc = (WearChange_Struct*)outapp->pBuffer; @@ -2565,7 +2754,15 @@ void Mob::SendWearChange(uint8 material_slot) wc->color.Color = GetEquipmentColor(material_slot); wc->wear_slot_id = material_slot; - entity_list.QueueClients(this, outapp); + if (!one_client) + { + entity_list.QueueClients(this, outapp); + } + else + { + one_client->QueuePacket(outapp, false, Client::CLIENT_CONNECTED); + } + safe_delete(outapp); } @@ -2691,7 +2888,7 @@ int32 Mob::GetHerosForgeModel(uint8 material_slot) const const ItemData *item; item = database.GetItem(GetEquipment(material_slot)); int16 invslot = InventoryOld::CalcSlotFromMaterial(material_slot); - + if (item != 0 && invslot != INVALID_INDEX) { if (IsClient()) @@ -2949,10 +3146,10 @@ uint32 Mob::GetLevelHP(uint8 tlevel) } int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) { - + int32 cast_reducer = 0; cast_reducer += GetFocusEffect(focusSpellHaste, spell_id); - + if (level >= 60 && casttime > 1000) { casttime = casttime / 2; @@ -2970,7 +3167,7 @@ int32 Mob::GetActSpellCasttime(uint16 spell_id, int32 casttime) { return casttime; } -void Mob::ExecWeaponProc(const ItemInst *inst, uint16 spell_id, Mob *on) { +void Mob::ExecWeaponProc(const ItemInst *inst, uint16 spell_id, Mob *on, int level_override) { // Changed proc targets to look up based on the spells goodEffect flag. // This should work for the majority of weapons. if(spell_id == SPELL_UNKNOWN || on->GetSpecialAbility(NO_HARM_FROM_CLIENT)) { @@ -3009,14 +3206,14 @@ void Mob::ExecWeaponProc(const ItemInst *inst, uint16 spell_id, Mob *on) { twinproc = true; if (IsBeneficialSpell(spell_id)) { - SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff, true); + SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff, true, level_override); if(twinproc) - SpellOnTarget(spell_id, this, false, false, 0, true); + SpellOnTarget(spell_id, this, false, false, 0, true, level_override); } else if(!(on->IsClient() && on->CastToClient()->dead)) { //dont proc on dead clients - SpellFinished(spell_id, on, 10, 0, -1, spells[spell_id].ResistDiff, true); + SpellFinished(spell_id, on, 10, 0, -1, spells[spell_id].ResistDiff, true, level_override); if(twinproc) - SpellOnTarget(spell_id, on, false, false, 0, true); + SpellOnTarget(spell_id, on, false, false, 0, true, level_override); } return; } @@ -3566,7 +3763,7 @@ int16 Mob::GetSkillDmgTaken(const SkillUseTypes skill_used) // All skill dmg mod + Skill specific skilldmg_mod += itembonuses.SkillDmgTaken[HIGHEST_SKILL+1] + spellbonuses.SkillDmgTaken[HIGHEST_SKILL+1] + itembonuses.SkillDmgTaken[skill_used] + spellbonuses.SkillDmgTaken[skill_used]; - + skilldmg_mod += SkillDmgTaken_Mod[skill_used] + SkillDmgTaken_Mod[HIGHEST_SKILL+1]; @@ -4006,6 +4203,39 @@ int32 Mob::GetItemStat(uint32 itemid, const char *identifier) return stat; } +std::string Mob::GetGlobal(const char *varname) { + int qgCharid = 0; + int qgNpcid = 0; + + if (this->IsNPC()) + qgNpcid = this->GetNPCTypeID(); + + if (this->IsClient()) + qgCharid = this->CastToClient()->CharacterID(); + + QGlobalCache *qglobals = nullptr; + std::list globalMap; + + if (this->IsClient()) + qglobals = this->CastToClient()->GetQGlobals(); + + if (this->IsNPC()) + qglobals = this->CastToNPC()->GetQGlobals(); + + if(qglobals) + QGlobalCache::Combine(globalMap, qglobals->GetBucket(), qgNpcid, qgCharid, zone->GetZoneID()); + + std::list::iterator iter = globalMap.begin(); + while(iter != globalMap.end()) { + if ((*iter).name.compare(varname) == 0) + return (*iter).value; + + ++iter; + } + + return "Undefined"; +} + void Mob::SetGlobal(const char *varname, const char *newvalue, int options, const char *duration, Mob *other) { int qgZoneid = zone->GetZoneID(); @@ -5284,7 +5514,7 @@ int32 Mob::GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot) if (slot < 4){ if (id == "components") { return spells[spell_id].components[slot];} - else if (id == "component_counts") { return spells[spell_id].component_counts[slot];} + else if (id == "component_counts") { return spells[spell_id].component_counts[slot];} else if (id == "NoexpendReagent") {return spells[spell_id].NoexpendReagent[slot];} } @@ -5362,7 +5592,7 @@ int32 Mob::GetSpellStat(uint32 spell_id, const char *identifier, uint8 slot) else if (id == "max_dist") {return static_cast(spells[spell_id].max_dist); } else if (id == "min_range") {return static_cast(spells[spell_id].min_range); } else if (id == "DamageShieldType") {return spells[spell_id].DamageShieldType; } - + return stat; } @@ -5382,9 +5612,48 @@ bool Mob::CanClassEquipItem(uint32 item_id) int bitmask = 1; bitmask = bitmask << (GetClass() - 1); - + if(!(itm->Classes & bitmask)) return false; else return true; } + +void Mob::SendAddPlayerState(PlayerState new_state) +{ + auto app = new EQApplicationPacket(OP_PlayerStateAdd, sizeof(PlayerState_Struct)); + auto ps = (PlayerState_Struct *)app->pBuffer; + + ps->spawn_id = GetID(); + ps->state = static_cast(new_state); + + AddPlayerState(ps->state); + entity_list.QueueClients(nullptr, app); + safe_delete(app); +} + +void Mob::SendRemovePlayerState(PlayerState old_state) +{ + auto app = new EQApplicationPacket(OP_PlayerStateRemove, sizeof(PlayerState_Struct)); + auto ps = (PlayerState_Struct *)app->pBuffer; + + ps->spawn_id = GetID(); + ps->state = static_cast(old_state); + + RemovePlayerState(ps->state); + entity_list.QueueClients(nullptr, app); + safe_delete(app); +} + +void Mob::SetCurrentSpeed(int in){ + if (current_speed != in) + { + current_speed = in; + tar_ndx = 20; + if (in == 0) { + SetRunAnimSpeed(0); + SetMoving(false); + SendPosition(); + } + } +} diff --git a/zone/mob.h b/zone/mob.h index 85b4797f8..fa78c1cc2 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -172,7 +172,8 @@ public: void SendAppearanceEffect(uint32 parm1, uint32 parm2, uint32 parm3, uint32 parm4, uint32 parm5, Client *specific_target=nullptr); void SendTargetable(bool on, Client *specific_target = nullptr); - virtual void SendWearChange(uint8 material_slot); + virtual void SendArmorAppearance(Client *one_client = nullptr); + virtual void SendWearChange(uint8 material_slot, Client *one_client = nullptr); virtual void SendTextureWC(uint8 slot, uint16 texture, uint32 hero_forge_model = 0, uint32 elite_material = 0, uint32 unknown06 = 0, uint32 unknown18 = 0); virtual void SetSlotTint(uint8 material_slot, uint8 red_tint, uint8 green_tint, uint8 blue_tint); @@ -200,7 +201,7 @@ public: bool IsBeneficialAllowed(Mob *target); virtual int GetCasterLevel(uint16 spell_id); void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0, - uint8 WornType = 0, uint32 ticsremaining = 0, int buffslot = -1, + uint8 WornType = 0, int32 ticsremaining = 0, int buffslot = -1, int instrument_mod = 10, bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0); void NegateSpellsBonuses(uint16 spell_id); virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false); @@ -211,7 +212,8 @@ public: virtual int32 GetActSpellDuration(uint16 spell_id, int32 duration); virtual int32 GetActSpellCasttime(uint16 spell_id, int32 casttime); float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false, - int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false); + int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false, + int level_override = -1); int ResistPhysical(int level_diff, uint8 caster_level); uint16 GetSpecializeSkillValue(uint16 spell_id) const; void SendSpellBarDisable(); @@ -227,10 +229,10 @@ public: void CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, uint16 mana_used, uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0); bool SpellFinished(uint16 spell_id, Mob *target, uint16 slot = 10, uint16 mana_used = 0, - uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false); + uint32 inventory_slot = 0xFFFFFFFF, int16 resist_adjust = 0, bool isproc = false, int level_override = -1); virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect = false, - bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false); - virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100); + bool use_resist_adjust = false, int16 resist_adjust = 0, bool isproc = false, int level_override = -1); + virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100, int level_override = -1); virtual bool DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_center, CastAction_type &CastAction); virtual bool CheckFizzle(uint16 spell_id); @@ -253,7 +255,7 @@ public: //Buff void BuffProcess(); - virtual void DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster = 0); + virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr); void BuffFadeBySpellID(uint16 spell_id); void BuffFadeByEffect(int effectid, int skipslot = -1); void BuffFadeAll(); @@ -364,6 +366,7 @@ public: inline Mob* GetTarget() const { return target; } virtual void SetTarget(Mob* mob); virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)cur_hp/max_hp*100); } + virtual inline float GetIntHPRatio() const { return max_hp == 0 ? 0 : (cur_hp/max_hp*100); } inline virtual int32 GetAC() const { return AC + itembonuses.AC + spellbonuses.AC; } inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK; } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } @@ -441,9 +444,12 @@ public: virtual void SetMoving(bool move) { moving = move; m_Delta = glm::vec4(); } virtual void GoToBind(uint8 bindnum = 0) { } virtual void Gate(); - float GetWalkspeed() const { return(_GetMovementSpeed(-47)); } - float GetRunspeed() const { return(_GetMovementSpeed(0)); } - float GetBaseRunspeed() const { return runspeed; } + int GetWalkspeed() const { return(_GetWalkSpeed()); } + int GetRunspeed() const { return(_GetRunSpeed()); } + void SetCurrentSpeed(int in); + int GetBaseRunspeed() const { return base_runspeed; } + int GetBaseWalkspeed() const { return base_walkspeed; } + int GetBaseFearSpeed() const { return base_fearspeed; } float GetMovespeed() const { return IsRunning() ? GetRunspeed() : GetWalkspeed(); } bool IsRunning() const { return m_is_running; } void SetRunning(bool val) { m_is_running = val; } @@ -493,7 +499,6 @@ public: inline bool CheckLastLosState() const { return last_los_check; } //Quest - void QuestReward(Client *c = nullptr, uint32 silver = 0, uint32 gold = 0, uint32 platinum = 0); void CameraEffect(uint32 duration, uint32 intensity, Client *c = nullptr, bool global = false); inline bool GetQglobal() const { return qglobal; } @@ -504,7 +509,8 @@ public: static void CreateSpawnPacket(EQApplicationPacket* app, NewSpawn_Struct* ns); virtual void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); void CreateHPPacket(EQApplicationPacket* app); - void SendHPUpdate(); + void SendHPUpdate(bool skip_self = false); + virtual void ResetHPUpdateTimer() {}; // does nothing //Util static uint32 RandomTimer(int min, int max); @@ -533,7 +539,7 @@ public: bool HasDefensiveProcs() const; bool HasSkillProcs() const; bool HasSkillProcSuccess() const; - bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN); + bool AddProcToWeapon(uint16 spell_id, bool bPerma = false, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN, int level_override = -1); bool RemoveProcFromWeapon(uint16 spell_id, bool bAll = false); bool HasProcs() const; bool IsCombatProc(uint16 spell_id); @@ -802,9 +808,8 @@ public: //old fear function //void SetFeared(Mob *caster, uint32 duration, bool flee = false); - float GetFearSpeed(); - bool IsFeared() { return curfp; } // This returns true if the mob is feared or fleeing due to low HP - //old fear: inline void StartFleeing() { SetFeared(GetHateTop(), FLEE_RUN_DURATION, true); } + int GetFearSpeed() { return _GetFearSpeed(); } + bool IsFeared() { return (spellbonuses.IsFeared || flee_mode); } // This returns true if the mob is feared or fleeing due to low HP inline void StartFleeing() { flee_mode = true; CalculateNewFearpoint(); } void ProcessFlee(); void CheckFlee(); @@ -812,8 +817,8 @@ public: inline bool CheckAggro(Mob* other) {return hate_list.IsEntOnHateList(other);} float CalculateHeadingToTarget(float in_x, float in_y); - bool CalculateNewPosition(float x, float y, float z, float speed, bool checkZ = false); - virtual bool CalculateNewPosition2(float x, float y, float z, float speed, bool checkZ = true); + bool CalculateNewPosition(float x, float y, float z, int speed, bool checkZ = false, bool calcheading = true); + virtual bool CalculateNewPosition2(float x, float y, float z, int speed, bool checkZ = true, bool calcheading = true); float CalculateDistance(float x, float y, float z); float GetGroundZ(float new_x, float new_y, float z_offset=0.0); void SendTo(float new_x, float new_y, float new_z); @@ -859,7 +864,7 @@ public: virtual uint32 GetAA(uint32 aa_id) const { return(0); } uint32 GetInstrumentMod(uint16 spell_id) const; - int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, Mob *caster = nullptr, int ticsremaining = 0); + int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, uint32 instrument_mod = 10, Mob *caster = nullptr, int ticsremaining = 0); int CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining = 0); virtual int CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, int caster_level2, Mob* caster1 = nullptr, Mob* caster2 = nullptr, int buffslot = -1); uint32 GetCastedSpellInvSlot() const { return casting_spell_inventory_slot; } @@ -884,6 +889,8 @@ public: Timer *GetSpecialAbilityTimer(int ability); void ClearSpecialAbilities(); void ProcessSpecialAbilities(const std::string &str); + bool IsMoved() { return moved; } + void SetMoved(bool moveflag) { moved = moveflag; } Shielders_Struct shielder[MAX_SHIELDERS]; Trade* trade; @@ -906,6 +913,7 @@ public: inline virtual bool IsBlockedBuff(int16 SpellID) { return false; } inline virtual bool IsBlockedPetBuff(int16 SpellID) { return false; } + std::string GetGlobal(const char *varname); void SetGlobal(const char *varname, const char *newvalue, int options, const char *duration, Mob *other = nullptr); void TarGlobal(const char *varname, const char *value, const char *duration, int npcid, int charid, int zoneid); void DelGlobal(const char *varname); @@ -956,7 +964,10 @@ protected: void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillUseTypes attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic); static uint16 GetProcID(uint16 spell_id, uint8 effect_index); float _GetMovementSpeed(int mod) const; - virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, bool checkZ); + int _GetWalkSpeed() const; + int _GetRunSpeed() const; + int _GetFearSpeed() const; + virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ); virtual bool AI_EngagedCastCheck() { return(false); } virtual bool AI_PursueCastCheck() { return(false); } @@ -1030,6 +1041,13 @@ protected: uint32 follow_dist; bool no_target_hotkey; + uint32 m_PlayerState; + uint32 GetPlayerState() { return m_PlayerState; } + void AddPlayerState(uint32 new_state) { m_PlayerState |= new_state; } + void RemovePlayerState(uint32 old_state) { m_PlayerState &= ~old_state; } + void SendAddPlayerState(PlayerState new_state); + void SendRemovePlayerState(PlayerState old_state); + uint8 gender; uint16 race; uint8 base_gender; @@ -1046,6 +1064,13 @@ protected: float base_size; float size; float runspeed; + float walkspeed; + float fearspeed; + int base_runspeed; + int base_walkspeed; + int base_fearspeed; + int current_speed; + uint32 pLastChange; bool held; bool nocast; @@ -1059,7 +1084,7 @@ protected: void TryWeaponProc(const ItemInst* inst, const ItemData* weapon, Mob *on, uint16 hand = MainPrimary); void TrySpellProc(const ItemInst* inst, const ItemData* weapon, Mob *on, uint16 hand = MainPrimary); void TryWeaponProc(const ItemInst* weapon, Mob *on, uint16 hand = MainPrimary); - void ExecWeaponProc(const ItemInst* weapon, uint16 spell_id, Mob *on); + void ExecWeaponProc(const ItemInst* weapon, uint16 spell_id, Mob *on, int level_override = -1); virtual float GetProcChances(float ProcBonus, uint16 hand = MainPrimary); virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand = MainPrimary, Mob *on = nullptr); virtual float GetSpecialProcChances(uint16 hand); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 452ca71fd..045c2cb75 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -339,9 +339,7 @@ bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain //stop moving if were casting a spell and were not a bard... if(!IsBardSong(AIspells[i].spellid)) { - SetRunAnimSpeed(0); - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); } return CastSpell(AIspells[i].spellid, tar->GetID(), 1, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, 0, &(AIspells[i].resist_adjust)); @@ -698,9 +696,7 @@ void Client::AI_SpellCast() { if(!IsBardSong(spell_to_cast)) { - SetRunAnimSpeed(0); - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); } CastSpell(spell_to_cast, tar->GetID(), slot_to_use); return; @@ -714,9 +710,7 @@ void Client::AI_SpellCast() { if(!IsBardSong(spell_to_cast)) { - SetRunAnimSpeed(0); - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); } CastSpell(spell_to_cast, tar->GetID(), slot_to_use); return; @@ -772,16 +766,13 @@ void Client::AI_Process() { if(GetTarget()) SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); - SetRunAnimSpeed(0); - SendPosition(); - SetMoving(false); - moved=false; + SetCurrentSpeed(0); } //continue on to attack code, ensuring that we execute the engaged code engaged = true; } else { if(AImovement_timer->Check()) { - animation = GetRunspeed() * 21; + //animation = GetFearSpeed() * 21; // Check if we have reached the last fear point if ((std::abs(GetX() - m_FearWalkTarget.x) < 0.1) && (std::abs(GetY() - m_FearWalkTarget.y) < 0.1)) { @@ -839,16 +830,13 @@ void Client::AI_Process() } if (AImovement_timer->Check()) { - SetRunAnimSpeed(0); + if(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()) != m_Position.w) + { + SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SendPosition(); + } + SetCurrentSpeed(0); } - if(IsMoving()) { - SetMoving(false); - moved=false; - SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); - SendPosition(); - tar_ndx =0; - } - if(GetTarget() && !IsStunned() && !IsMezzed() && !GetFeigned()) { if(attack_timer.Check()) { Attack(GetTarget(), MainPrimary); @@ -944,28 +932,27 @@ void Client::AI_Process() { if(!IsRooted()) { - animation = 21 * GetRunspeed(); - if(!RuleB(Pathing, Aggro) || !zone->pathing) - CalculateNewPosition2(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetRunspeed()); - else + if(AImovement_timer->Check()) { - bool WaypointChanged, NodeReached; - glm::vec3 Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), - GetRunspeed(), WaypointChanged, NodeReached); + if(!RuleB(Pathing, Aggro) || !zone->pathing) + CalculateNewPosition2(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetRunspeed()); + else + { + bool WaypointChanged, NodeReached; + glm::vec3 Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), + GetRunspeed(), WaypointChanged, NodeReached); - if(WaypointChanged) - tar_ndx = 20; + if(WaypointChanged) + tar_ndx = 20; - CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetRunspeed()); + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetRunspeed()); + } } } else if(IsMoving()) { SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); - SetRunAnimSpeed(0); - SendPosition(); - SetMoving(false); - moved=false; + SetCurrentSpeed(0); } } AI_SpellCast(); @@ -998,21 +985,23 @@ void Client::AI_Process() return; float dist = DistanceSquared(m_Position, owner->GetPosition()); - if (dist >= 100) + if (dist >= 400) { - float speed = dist >= 225 ? GetRunspeed() : GetWalkspeed(); - animation = 21 * speed; - CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); + if(AImovement_timer->Check()) + { + int speed = GetWalkspeed(); + if (dist >= 5625) + speed = GetRunspeed(); + + CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); + } } else { - SetHeading(owner->GetHeading()); if(moved) { - moved=false; - SetMoving(false); - SendPosition(); - SetRunAnimSpeed(0); + SetCurrentSpeed(0); + moved = false; } } } @@ -1042,9 +1031,7 @@ void Mob::AI_Process() { { if(target) SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY())); - SetRunAnimSpeed(0); - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); moved=false; } //continue on to attack code, ensuring that we execute the engaged code @@ -1058,7 +1045,9 @@ void Mob::AI_Process() { CalculateNewFearpoint(); } if(!RuleB(Pathing, Fear) || !zone->pathing) + { CalculateNewPosition2(m_FearWalkTarget.x, m_FearWalkTarget.y, m_FearWalkTarget.z, GetFearSpeed(), true); + } else { bool WaypointChanged, NodeReached; @@ -1084,6 +1073,8 @@ void Mob::AI_Process() { if (engaged) { + if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) + SendAddPlayerState(PlayerState::Aggressive); // we are prevented from getting here if we are blind and don't have a target in range // from above, so no extra blind checks needed if ((IsRooted() && !GetSpecialAbility(IGNORE_ROOT_AGGRO_RULES)) || IsBlind()) @@ -1114,7 +1105,7 @@ void Mob::AI_Process() { } #ifdef BOTS - if (IsPet() && GetOwner()->IsBot() && target == GetOwner()) + if (IsPet() && GetOwner() && GetOwner()->IsBot() && target == GetOwner()) { // this blocks all pet attacks against owner..bot pet test (copied above check) RemoveFromHateList(this); @@ -1154,15 +1145,21 @@ void Mob::AI_Process() { { if (AImovement_timer->Check()) { - SetRunAnimSpeed(0); + if(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()) != m_Position.w) + { + SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SendPosition(); + } + SetCurrentSpeed(0); } if(IsMoving()) { - SetMoving(false); - moved=false; - SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY())); - SendPosition(); - tar_ndx =0; + if(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY()) != m_Position.w) + { + SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); + SendPosition(); + } + SetCurrentSpeed(0); } //casting checked above... @@ -1367,7 +1364,7 @@ void Mob::AI_Process() { CastToNPC()->DoClassAttacks(target); } AI_EngagedCastCheck(); - } //end is within combat range + } //end is within combat rangepet else { //we cannot reach our target... //underwater stuff only works with water maps in the zone! @@ -1423,10 +1420,7 @@ void Mob::AI_Process() { } else if(IsMoving()) { SetHeading(CalculateHeadingToTarget(target->GetX(), target->GetY())); - SetRunAnimSpeed(0); - SendPosition(); - SetMoving(false); - moved=false; + SetCurrentSpeed(0); } } @@ -1435,6 +1429,8 @@ void Mob::AI_Process() { } else { + if (m_PlayerState & static_cast(PlayerState::Aggressive)) + SendRemovePlayerState(PlayerState::Aggressive); if(AIfeignremember_timer->Check()) { // 6/14/06 // Improved Feign Death Memory @@ -1477,7 +1473,6 @@ void Mob::AI_Process() { } else if (AImovement_timer->Check() && !IsRooted()) { - SetRunAnimSpeed(0); if (IsPet()) { // we're a pet, do as we're told @@ -1496,18 +1491,18 @@ void Mob::AI_Process() { float dist = DistanceSquared(m_Position, owner->GetPosition()); if (dist >= 400) { - float speed = GetWalkspeed(); + int speed = GetWalkspeed(); if (dist >= 5625) speed = GetRunspeed(); + CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed); } else { if(moved) { - moved=false; - SetMoving(false); - SendPosition(); + SetCurrentSpeed(0); + moved = false; } } @@ -1553,19 +1548,15 @@ void Mob::AI_Process() { if (dist2 >= followdist) // Default follow distance is 100 { - float speed = GetWalkspeed(); + int speed = GetWalkspeed(); if (dist2 >= followdist + 150) speed = GetRunspeed(); CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); } else { - if(moved) - { - SendPosition(); - moved=false; - SetMoving(false); - } + moved = false; + SetCurrentSpeed(0); } } } @@ -1662,92 +1653,39 @@ void NPC::AI_DoMovement() { if (gridno > 0 || cur_wp==-2) { if (movetimercompleted==true) { // time to pause at wp is over - - int32 spawn_id = this->GetSpawnPointID(); - LinkedListIterator iterator(zone->spawn2_list); - iterator.Reset(); - Spawn2 *found_spawn = nullptr; - - while(iterator.MoreElements()) - { - Spawn2* cur = iterator.GetData(); - iterator.Advance(); - if(cur->GetID() == spawn_id) - { - found_spawn = cur; - break; - } - } - - if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) { - CastToNPC()->Depop(true); //depop and resart spawn timer - if(found_spawn) - found_spawn->SetNPCPointerNull(); - } - else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) { - CastToNPC()->Depop(false);//depop without spawn timer - if(found_spawn) - found_spawn->SetNPCPointerNull(); - } - else { - movetimercompleted=false; - - Log.Out(Logs::Detail, Logs::Pathing, "We are departing waypoint %d.", cur_wp); - - //if we were under quest control (with no grid), we are done now.. - if(cur_wp == -2) { - Log.Out(Logs::Detail, Logs::Pathing, "Non-grid quest mob has reached its quest ordered waypoint. Leaving pathing mode."); - roamer = false; - cur_wp = 0; - } - - if(GetAppearance() != eaStanding) - SetAppearance(eaStanding, false); - - entity_list.OpenDoorsNear(CastToNPC()); - - if(!DistractedFromGrid) { - //kick off event_waypoint depart - char temp[16]; - sprintf(temp, "%d", cur_wp); - parse->EventNPC(EVENT_WAYPOINT_DEPART, CastToNPC(), nullptr, temp, 0); - - //setup our next waypoint, if we are still on our normal grid - //remember that the quest event above could have done anything it wanted with our grid - if(gridno > 0) { - CastToNPC()->CalculateNewWaypoint(); - } - } - else { - DistractedFromGrid = false; - } - } + AI_SetupNextWaypoint(); } // endif (movetimercompleted==true) else if (!(AIwalking_timer->Enabled())) { // currently moving + bool doMove = true; if (m_CurrentWayPoint.x == GetX() && m_CurrentWayPoint.y == GetY()) { // are we there yet? then stop Log.Out(Logs::Detail, Logs::AI, "We have reached waypoint %d (%.3f,%.3f,%.3f) on grid %d", cur_wp, GetX(), GetY(), GetZ(), GetGrid()); - SetWaypointPause(); - if(GetAppearance() != eaStanding) + if (cur_wp_pause != 0) { + SetWaypointPause(); SetAppearance(eaStanding, false); - SetMoving(false); - if (m_CurrentWayPoint.w >= 0.0) { - SetHeading(m_CurrentWayPoint.w); + SetMoving(false); + if (m_CurrentWayPoint.w >= 0.0) { + SetHeading(m_CurrentWayPoint.w); + } + SendPosition(); } - SendPosition(); //kick off event_waypoint arrive char temp[16]; sprintf(temp, "%d", cur_wp); parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0); - + // start moving directly to next waypoint if we're at a 0 pause waypoint and we didn't get quest halted. + if (!AIwalking_timer->Enabled()) + AI_SetupNextWaypoint(); + else + doMove = false; // wipe feign memory since we reached our first waypoint if(cur_wp == 1) ClearFeignMemory(); } - else - { // not at waypoint yet, so keep moving + if (doMove) + { // not at waypoint yet or at 0 pause WP, so keep moving if(!RuleB(Pathing, AggroReturnToGrid) || !zone->pathing || (DistractedFromGrid == 0)) CalculateNewPosition2(m_CurrentWayPoint.x, m_CurrentWayPoint.y, m_CurrentWayPoint.z, walksp, true); else @@ -1775,8 +1713,7 @@ void NPC::AI_DoMovement() { SetGrid( 0 - GetGrid()); // revert to AI control Log.Out(Logs::Detail, Logs::Pathing, "Quest pathing is finished. Resuming on grid %d", GetGrid()); - if(GetAppearance() != eaStanding) - SetAppearance(eaStanding, false); + SetAppearance(eaStanding, false); CalculateNewWaypoint(); } @@ -1812,29 +1749,86 @@ void NPC::AI_DoMovement() { Log.Out(Logs::Detail, Logs::AI, "Reached guard point (%.3f,%.3f,%.3f)", m_GuardPoint.x, m_GuardPoint.y, m_GuardPoint.z); ClearFeignMemory(); moved=false; - SetMoving(false); if (GetTarget() == nullptr || DistanceSquared(m_Position, GetTarget()->GetPosition()) >= 5*5 ) { SetHeading(m_GuardPoint.w); } else { FaceTarget(GetTarget()); } - SendPosition(); + SetCurrentSpeed(0); SetAppearance(GetGuardPointAnim()); } } } } +void NPC::AI_SetupNextWaypoint() { + int32 spawn_id = this->GetSpawnPointID(); + LinkedListIterator iterator(zone->spawn2_list); + iterator.Reset(); + Spawn2 *found_spawn = nullptr; + + while (iterator.MoreElements()) + { + Spawn2* cur = iterator.GetData(); + iterator.Advance(); + if (cur->GetID() == spawn_id) + { + found_spawn = cur; + break; + } + } + + if (wandertype == 4 && cur_wp == CastToNPC()->GetMaxWp()) { + CastToNPC()->Depop(true); //depop and restart spawn timer + if (found_spawn) + found_spawn->SetNPCPointerNull(); + } + else if (wandertype == 6 && cur_wp == CastToNPC()->GetMaxWp()) { + CastToNPC()->Depop(false);//depop without spawn timer + if (found_spawn) + found_spawn->SetNPCPointerNull(); + } + else { + movetimercompleted = false; + + Log.Out(Logs::Detail, Logs::Pathing, "We are departing waypoint %d.", cur_wp); + + //if we were under quest control (with no grid), we are done now.. + if (cur_wp == -2) { + Log.Out(Logs::Detail, Logs::Pathing, "Non-grid quest mob has reached its quest ordered waypoint. Leaving pathing mode."); + roamer = false; + cur_wp = 0; + } + + SetAppearance(eaStanding, false); + + entity_list.OpenDoorsNear(CastToNPC()); + + if (!DistractedFromGrid) { + //kick off event_waypoint depart + char temp[16]; + sprintf(temp, "%d", cur_wp); + parse->EventNPC(EVENT_WAYPOINT_DEPART, CastToNPC(), nullptr, temp, 0); + + //setup our next waypoint, if we are still on our normal grid + //remember that the quest event above could have done anything it wanted with our grid + if (GetGrid() > 0) { + CastToNPC()->CalculateNewWaypoint(); + } + } + else { + DistractedFromGrid = false; + } + } +} + // Note: Mob that caused this may not get added to the hate list until after this function call completes void Mob::AI_Event_Engaged(Mob* attacker, bool iYellForHelp) { if (!IsAIControlled()) return; - if(GetAppearance() != eaStanding) - { - SetAppearance(eaStanding); - } + SetAppearance(eaStanding); if (iYellForHelp) { if(IsPet()) { @@ -1881,9 +1875,10 @@ void Mob::AI_Event_NoLongerEngaged() { pLastFightingDelayMoving += zone->random.Int(minLastFightingDelayMoving, maxLastFightingDelayMoving); // So mobs don't keep running as a ghost until AIwalking_timer fires // if they were moving prior to losing all hate - if(IsMoving()){ + // except if we're a pet, then we might run into some issues with pets backing off when they should immediately be moving + if(!IsPet()) + { SetRunAnimSpeed(0); - SetMoving(false); SendPosition(); } ClearRampage(); @@ -2557,11 +2552,9 @@ void NPC::ApplyAISpellEffects(StatBonuses* newbon) if (!AI_HasSpellsEffects()) return; - for(int i=0; i < AIspellsEffects.size(); i++) - { - ApplySpellsBonuses(0, 0, newbon, 0, 0, 0,-1, - true, AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); - } + for (int i = 0; i < AIspellsEffects.size(); i++) + ApplySpellsBonuses(0, 0, newbon, 0, 0, 0, -1, 10, true, AIspellsEffects[i].spelleffectid, + AIspellsEffects[i].base, AIspellsEffects[i].limit, AIspellsEffects[i].max); return; } diff --git a/zone/net.cpp b/zone/net.cpp index 92f568af0..ef6b46836 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -423,6 +423,7 @@ int main(int argc, char** argv) { entity_list.Process(); entity_list.MobProcess(); entity_list.BeaconProcess(); + entity_list.EncounterProcess(); if (zone) { if(!zone->Process()) { diff --git a/zone/npc.cpp b/zone/npc.cpp index c8dc15f58..eae905df1 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -115,7 +115,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if knightattack_timer(1000), assist_timer(AIassistcheck_delay), qglobal_purge_timer(30000), - sendhpupdate_timer(1000), + sendhpupdate_timer(2000), enraged_timer(1000), taunt_timer(TauntReuseTime * 1000), m_SpawnPoint(position), @@ -522,7 +522,7 @@ void NPC::QueryLoot(Client* to) linker.SetItemData(item); auto item_link = linker.GenerateLink(); - + to->Message(0, "%s, ID: %u, Level: (min: %u, max: %u)", item_link.c_str(), item->ID, (*cur)->min_level, (*cur)->max_level); } @@ -569,8 +569,7 @@ bool NPC::Process() { if (IsStunned() && stunned_timer.Check()) { - this->stunned = false; - this->stunned_timer.Disable(); + Mob::UnStun(); this->spun_timer.Disable(); } @@ -651,7 +650,8 @@ bool NPC::Process() } } - if (sendhpupdate_timer.Check() && (IsTargeted() || (IsPet() && GetOwner() && GetOwner()->IsClient()))) { + // we might actually want to reset in this check ... won't until issues arise at least :P + if (sendhpupdate_timer.Check(false) && (IsTargeted() || (IsPet() && GetOwner() && GetOwner()->IsClient()))) { if(!IsFullHP || cur_hpIsCorpse())){ Kill(); return; diff --git a/zone/npc.h b/zone/npc.h index 28dc6256a..25ecdc4ec 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -119,6 +119,7 @@ public: virtual void AI_Start(uint32 iMoveDelay = 0); virtual void AI_Stop(); void AI_DoMovement(); + void AI_SetupNextWaypoint(); bool AI_AddNPCSpells(uint32 iDBSpellsID); bool AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID); virtual bool AI_EngagedCastCheck(); @@ -406,6 +407,7 @@ public: void SetHeroForgeModel(uint32 model) { herosforgemodel = model; } bool IsRaidTarget() const { return raid_target; }; + void ResetHPUpdateTimer() { sendhpupdate_timer.Start(); } protected: diff --git a/zone/object.cpp b/zone/object.cpp index 53f206acd..0499cc753 100644 --- a/zone/object.cpp +++ b/zone/object.cpp @@ -485,7 +485,22 @@ bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object) buf[9] = '\0'; std::vector args; args.push_back(m_inst); - parse->EventPlayer(EVENT_PLAYER_PICKUP, sender, buf, 0, &args); + if(parse->EventPlayer(EVENT_PLAYER_PICKUP, sender, buf, this->GetID(), &args)) + { + EQApplicationPacket* outapp = new EQApplicationPacket(OP_ClickObject, sizeof(ClickObject_Struct)); + memcpy(outapp->pBuffer, click_object, sizeof(ClickObject_Struct)); + ClickObject_Struct* co = (ClickObject_Struct*)outapp->pBuffer; + co->drop_id = 0; + entity_list.QueueClients(nullptr, outapp, false); + safe_delete(outapp); + + // No longer using a tradeskill object + sender->SetTradeskillObject(nullptr); + user = nullptr; + + return true; + } + // Transfer item to client sender->PutItemInInventory(MainCursor, *m_inst, false); @@ -809,7 +824,7 @@ void Object::SetModelName(const char* modelname) void Object::SetSize(uint16 size) { - m_data.unknown008 = size; + m_data.size = size; EQApplicationPacket* app = new EQApplicationPacket(); EQApplicationPacket* app2 = new EQApplicationPacket(); this->CreateDeSpawnPacket(app); @@ -822,7 +837,7 @@ void Object::SetSize(uint16 size) void Object::SetSolidType(uint16 solidtype) { - m_data.unknown010 = solidtype; + m_data.solidtype = solidtype; EQApplicationPacket* app = new EQApplicationPacket(); EQApplicationPacket* app2 = new EQApplicationPacket(); this->CreateDeSpawnPacket(app); @@ -835,12 +850,12 @@ void Object::SetSolidType(uint16 solidtype) uint16 Object::GetSize() { - return m_data.unknown008; + return m_data.size; } uint16 Object::GetSolidType() { - return m_data.unknown010; + return m_data.solidtype; } const char* Object::GetModelName() diff --git a/zone/pathing.cpp b/zone/pathing.cpp index 8224f3692..32a55d73a 100644 --- a/zone/pathing.cpp +++ b/zone/pathing.cpp @@ -16,7 +16,7 @@ #define snprintf _snprintf #endif -//#define PATHDEBUG +//#define PATHDEBUG extern Zone *zone; @@ -205,10 +205,10 @@ glm::vec3 PathManager::GetPathNodeCoordinates(int NodeNumber, bool BestZ) } std::deque PathManager::FindRoute(int startID, int endID) -{ +{ Log.Out(Logs::Detail, Logs::None, "FindRoute from node %i to %i", startID, endID); - memset(ClosedListFlag, 0, sizeof(int) * Head.PathNodeCount); + memset(ClosedListFlag, 0, sizeof(int) * Head.PathNodeCount); std::deque OpenList, ClosedList; @@ -665,6 +665,9 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa bool SameDestination = (To == PathingDestination); + if (Speed <= 0) // our speed is 0, we cant move so lets return the dest + return To; // this will also avoid the teleports cleanly + int NextNode; if(To == From) diff --git a/zone/perl_client.cpp b/zone/perl_client.cpp index a885922e9..eed8305f4 100644 --- a/zone/perl_client.cpp +++ b/zone/perl_client.cpp @@ -6188,6 +6188,84 @@ XS(XS_Client_GetTargetRingZ) XSRETURN(1); } +XS(XS_Client_CalcEXP); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_CalcEXP) +{ + dXSARGS; + if (items < 1 || items > 2) + Perl_croak(aTHX_ "Usage: CalcEXP(THIS, uint8 conlevel)"); + { + Client * THIS; + uint8 conlevel = 0xFF; + uint32 RETVAL; + if(items == 2) + conlevel = (uint16)SvUV(ST(1)); + dXSTARG; + + if (sv_derived_from(ST(0), "Client")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client *,tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Client"); + if(THIS == NULL) + Perl_croak(aTHX_ "THIS is NULL, avoiding crash."); + + RETVAL = THIS->CalcEXP(conlevel); + XSprePUSH; PUSHi((IV)RETVAL); + } + XSRETURN(1); +} + +XS(XS_Client_QuestReward); /* prototype to pass -Wmissing-prototypes */ +XS(XS_Client_QuestReward) +{ + dXSARGS; + if (items < 1 || items > 9) + Perl_croak(aTHX_ "Usage: Client::QuestReward(THIS, mob, copper, silver, gold, platinum, itemid, exp, faction)"); + { + Client* THIS; + Mob * mob = nullptr; + int32 copper = 0; + int32 silver = 0; + int32 gold = 0; + int32 platinum = 0; + int32 itemid = 0; + int32 exp = 0; + bool faction = false; + + if (sv_derived_from(ST(0), "THIS")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Client *, tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type client"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (items > 1) { + if (sv_derived_from(ST(1), "mob")) { + IV tmp = SvIV((SV*)SvRV(ST(1))); + mob = INT2PTR(Mob *, tmp); + } + else + Perl_croak(aTHX_ "mob is not of type Mob"); + if (mob == nullptr) + Perl_croak(aTHX_ "mob is nullptr, avoiding crash."); + } + if (items > 2) { copper = (int32)SvIV(ST(2)); } + if (items > 3) { silver = (int32)SvIV(ST(3)); } + if (items > 4) { gold = (int32)SvIV(ST(4)); } + if (items > 5) { platinum = (int32)SvIV(ST(5)); } + if (items > 6) { itemid = (int32)SvIV(ST(6)); } + if (items > 7) { exp = (int32)SvIV(ST(7)); } + if (items > 8) { faction = (bool)SvIV(ST(8)); } + + THIS->QuestReward(mob, copper, silver, gold, platinum, itemid, exp, faction); + } + XSRETURN_EMPTY; +} + #ifdef __cplusplus extern "C" #endif @@ -6432,7 +6510,8 @@ XS(boot_Client) newXSproto(strcpy(buf, "GetTargetRingX"), XS_Client_GetTargetRingX, file, "$$"); newXSproto(strcpy(buf, "GetTargetRingY"), XS_Client_GetTargetRingY, file, "$$"); newXSproto(strcpy(buf, "GetTargetRingZ"), XS_Client_GetTargetRingZ, file, "$$"); - + newXSproto(strcpy(buf, "QuestReward"), XS_Client_QuestReward, file, "$$;$$$$$$$"); + newXSproto(strcpy(buf, "CalcEXP"), XS_Client_CalcEXP, file, "$"); XSRETURN_YES; } diff --git a/zone/perl_mob.cpp b/zone/perl_mob.cpp index dab3e17a0..df04831ae 100644 --- a/zone/perl_mob.cpp +++ b/zone/perl_mob.cpp @@ -7027,47 +7027,6 @@ XS(XS_Mob_SendAppearanceEffect) XSRETURN_EMPTY; } -XS(XS_Mob_QuestReward); /* prototype to pass -Wmissing-prototypes */ -XS(XS_Mob_QuestReward) -{ - dXSARGS; - if (items < 1 || items > 5) - Perl_croak(aTHX_ "Usage: Mob::QuestReward(THIS, client, silver, gold, platinum)"); - { - Mob * THIS; - Client* client = nullptr; - int32 silver = 0; - int32 gold = 0; - int32 platinum = 0; - - if (sv_derived_from(ST(0), "Mob")) { - IV tmp = SvIV((SV*)SvRV(ST(0))); - THIS = INT2PTR(Mob *,tmp); - } - else - Perl_croak(aTHX_ "THIS is not of type Mob"); - if(THIS == nullptr) - Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); - - if (items > 1) { - if (sv_derived_from(ST(1), "Client")) { - IV tmp = SvIV((SV*)SvRV(ST(1))); - client = INT2PTR(Client *,tmp); - } - else - Perl_croak(aTHX_ "client is not of type Client"); - if(client == nullptr) - Perl_croak(aTHX_ "client is nullptr, avoiding crash."); - } - if (items > 2) { silver = (int32)SvIV(ST(2)); } - if (items > 3) { gold = (int32)SvIV(ST(3)); } - if (items > 4) { platinum = (int32)SvIV(ST(4)); } - - THIS->QuestReward(client, silver, gold, platinum); - } - XSRETURN_EMPTY; -} - XS(XS_Mob_SetFlyMode); /* prototype to pass -Wmissing-prototypes */ XS(XS_Mob_SetFlyMode) { @@ -7362,6 +7321,37 @@ XS(XS_Mob_GetItemStat) XSRETURN(1); } +XS(XS_Mob_GetGlobal); +XS(XS_Mob_GetGlobal) +{ + dXSARGS; + if (items < 2) + Perl_croak(aTHX_ "Usage: GetGlobal(THIS, varname)"); + { + Mob* THIS; + Const_char* varname = (Const_char*)SvPV_nolen(ST(1)); + std::string ret_val = "Undefined"; + Const_char* RETVAL; + dXSTARG; + + if (sv_derived_from(ST(0), "Mob")) { + IV tmp = SvIV((SV*)SvRV(ST(0))); + THIS = INT2PTR(Mob *, tmp); + } + else + Perl_croak(aTHX_ "THIS is not of type Mob"); + if (THIS == nullptr) + Perl_croak(aTHX_ "THIS is nullptr, avoiding crash."); + + if (THIS->GetGlobal(varname) != "Undefined") + ret_val = THIS->GetGlobal(varname); + + RETVAL = ret_val.c_str(); + sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG; + } + XSRETURN(1); +} + XS(XS_Mob_SetGlobal); XS(XS_Mob_SetGlobal) { @@ -8660,11 +8650,11 @@ XS(boot_Mob) newXSproto(strcpy(buf, "SendIllusion"), XS_Mob_SendIllusion, file, "$$;$$$$$$$$$$$$"); newXSproto(strcpy(buf, "MakeTempPet"), XS_Mob_MakeTempPet, file, "$$;$$$$"); newXSproto(strcpy(buf, "TypesTempPet"), XS_Mob_TypesTempPet, file, "$$;$$$$$"); - newXSproto(strcpy(buf, "QuestReward"), XS_Mob_QuestReward, file, "$$;$$$"); newXSproto(strcpy(buf, "CameraEffect"), XS_Mob_CameraEffect, file, "$$;$$$"); newXSproto(strcpy(buf, "SpellEffect"), XS_Mob_SpellEffect, file, "$$;$$$$$$"); newXSproto(strcpy(buf, "TempName"), XS_Mob_TempName, file, "$:$"); newXSproto(strcpy(buf, "GetItemStat"), XS_Mob_GetItemStat, file, "$$$"); + newXSproto(strcpy(buf, "GetGlobal"), XS_Mob_GetGlobal, file, "$$"); newXSproto(strcpy(buf, "SetGlobal"), XS_Mob_SetGlobal, file, "$$$$$;$"); newXSproto(strcpy(buf, "TarGlobal"), XS_Mob_TarGlobal, file, "$$$$$$$"); newXSproto(strcpy(buf, "DelGlobal"), XS_Mob_DelGlobal, file, "$$"); diff --git a/zone/pets.cpp b/zone/pets.cpp index 617c5fe31..f09c78a04 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -28,6 +28,10 @@ #include "pets.h" #include "zonedb.h" +#ifdef BOTS +#include "bot.h" +#endif + #ifndef WIN32 #include #include "../common/unix.h" @@ -231,6 +235,10 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);//Client only act_power = CastToClient()->mod_pet_power(act_power, spell_id); } +#ifdef BOTS + else if (this->IsBot()) + act_power = CastToBot()->GetBotFocusEffect(Bot::BotfocusPetPower, spell_id); +#endif } else if (petpower > 0) act_power = petpower; @@ -260,7 +268,11 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, memcpy(npc_type, base, sizeof(NPCType)); // If pet power is set to -1 in the DB, use stat scaling - if (this->IsClient() && record.petpower == -1) + if ((this->IsClient() +#ifdef BOTS + || this->IsBot() +#endif + ) && record.petpower == -1) { float scale_power = (float)act_power / 100.0f; if(scale_power > 0) @@ -268,10 +280,10 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, npc_type->max_hp *= (1 + scale_power); npc_type->cur_hp = npc_type->max_hp; npc_type->AC *= (1 + scale_power); - npc_type->level += 1 + ((int)act_power / 25); // gains an additional level for every 25 pet power + npc_type->level += 1 + ((int)act_power / 25) > npc_type->level + RuleR(Pets, PetPowerLevelCap) ? RuleR(Pets, PetPowerLevelCap) : 1 + ((int)act_power / 25); // gains an additional level for every 25 pet power npc_type->min_dmg = (npc_type->min_dmg * (1 + (scale_power / 2))); npc_type->max_dmg = (npc_type->max_dmg * (1 + (scale_power / 2))); - npc_type->size *= (1 + (scale_power / 2)); + npc_type->size = npc_type->size * (1 + (scale_power / 2)) > npc_type->size * 3 ? npc_type->size * 3 : (1 + (scale_power / 2)); } record.petpower = act_power; } @@ -426,7 +438,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, entity_list.AddNPC(npc, true, true); SetPetID(npc->GetID()); // We need to handle PetType 5 (petHatelist), add the current target to the hatelist of the pet - + if (record.petcontrol == petTargetLock) { @@ -457,6 +469,22 @@ Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 po // Class should use npc constructor to set light properties } +void Pet::SetTarget(Mob *mob) +{ + if (mob == GetTarget()) + return; + + auto owner = GetOwner(); + if (owner && owner->IsClient() && owner->CastToClient()->GetClientVersionBit() & BIT_UFAndLater) { + auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct)); + auto ct = (ClientTarget_Struct *)app->pBuffer; + ct->new_target = mob ? mob->GetID() : 0; + owner->CastToClient()->QueuePacket(app); + safe_delete(app); + } + NPC::SetTarget(mob); +} + bool ZoneDatabase::GetPetEntry(const char *pet_type, PetRecord *into) { return GetPoweredPetEntry(pet_type, 0, into); } @@ -555,6 +583,7 @@ void NPC::GetPetState(SpellBuff_Struct *pet_buffs, uint32 *items, char *name) { pet_buffs[i].level = buffs[i].casterlevel; pet_buffs[i].effect = 10; pet_buffs[i].counters = buffs[i].counters; + pet_buffs[i].bard_modifier = buffs[i].instrument_mod; } else { pet_buffs[i].spellid = SPELL_UNKNOWN; @@ -588,6 +617,7 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) { buffs[i].casterid = 0; buffs[i].counters = pet_buffs[i].counters; buffs[i].numhits = spells[pet_buffs[i].spellid].numhits; + buffs[i].instrument_mod = pet_buffs[i].bard_modifier; } else { buffs[i].spellid = SPELL_UNKNOWN; diff --git a/zone/pets.h b/zone/pets.h index f0c71fbe7..32ca00eac 100644 --- a/zone/pets.h +++ b/zone/pets.h @@ -39,6 +39,7 @@ struct NPCType; class Pet : public NPC { public: Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power); + virtual void SetTarget(Mob *mob); }; diff --git a/zone/quest_interface.h b/zone/quest_interface.h index 68c79923f..c1cf3dc73 100644 --- a/zone/quest_interface.h +++ b/zone/quest_interface.h @@ -41,7 +41,7 @@ public: std::vector *extra_pointers) { return 0; } virtual int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, std::vector *extra_pointers) { return 0; } - virtual int EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, + virtual int EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers) { return 0; } virtual bool HasQuestSub(uint32 npcid, QuestEventID evt) { return false; } diff --git a/zone/quest_parser_collection.cpp b/zone/quest_parser_collection.cpp index 37e518c24..845769532 100644 --- a/zone/quest_parser_collection.cpp +++ b/zone/quest_parser_collection.cpp @@ -434,14 +434,14 @@ int QuestParserCollection::EventSpell(QuestEventID evt, NPC* npc, Client *client return 0; } -int QuestParserCollection::EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, +int QuestParserCollection::EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers) { auto iter = _encounter_quest_status.find(encounter_name); if(iter != _encounter_quest_status.end()) { //loaded or failed to load if(iter->second != QuestFailedToLoad) { std::map::iterator qiter = _interfaces.find(iter->second); - return qiter->second->EventEncounter(evt, encounter_name, extra_data, extra_pointers); + return qiter->second->EventEncounter(evt, encounter_name, data, extra_data, extra_pointers); } } else { std::string filename; @@ -449,7 +449,7 @@ int QuestParserCollection::EventEncounter(QuestEventID evt, std::string encounte if(qi) { _encounter_quest_status[encounter_name] = qi->GetIdentifier(); qi->LoadEncounterScript(filename, encounter_name); - return qi->EventEncounter(evt, encounter_name, extra_data, extra_pointers); + return qi->EventEncounter(evt, encounter_name, data, extra_data, extra_pointers); } else { _encounter_quest_status[encounter_name] = QuestFailedToLoad; } diff --git a/zone/quest_parser_collection.h b/zone/quest_parser_collection.h index 62cb034dc..3ebce378c 100644 --- a/zone/quest_parser_collection.h +++ b/zone/quest_parser_collection.h @@ -21,6 +21,7 @@ #include "../common/types.h" +#include "encounter.h" #include "beacon.h" #include "client.h" #include "corpse.h" @@ -71,7 +72,7 @@ public: std::vector *extra_pointers = nullptr); int EventSpell(QuestEventID evt, NPC* npc, Client *client, uint32 spell_id, uint32 extra_data, std::vector *extra_pointers = nullptr); - int EventEncounter(QuestEventID evt, std::string encounter_name, uint32 extra_data, + int EventEncounter(QuestEventID evt, std::string encounter_name, std::string data, uint32 extra_data, std::vector *extra_pointers = nullptr); void GetErrors(std::list &err); diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index b64d9e767..5dba6f00a 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -84,6 +84,8 @@ void QuestManager::Process() { if(entity_list.IsMobInZone(cur->mob)) { if(cur->mob->IsNPC()) { parse->EventNPC(EVENT_TIMER, cur->mob->CastToNPC(), nullptr, cur->name, 0); + } else if (cur->mob->IsEncounter()) { + parse->EventEncounter(EVENT_TIMER, cur->mob->CastToEncounter()->GetEncounterName(), cur->name, 0, nullptr); } else { //this is inheriently unsafe if we ever make it so more than npc/client start timers parse->EventPlayer(EVENT_TIMER, cur->mob->CastToClient(), cur->name, 0); @@ -668,6 +670,79 @@ void QuestManager::repopzone() { } } +void QuestManager::ConnectNodeToNode(int node1, int node2, int teleport, int doorid) { + if (!node1 || !node2) + { + Log.Out(Logs::General, Logs::Quests, "QuestManager::ConnectNodeToNode called without node1 or node2. Probably syntax error in quest file."); + } + else + { + if (!teleport) + { + teleport = 0; + } + else if (teleport == 1 || teleport == -1) + { + teleport = -1; + } + + if (!doorid) + { + doorid = 0; + } + + if (!zone->pathing) + { + // if no pathing bits available, make them available. + zone->pathing = new PathManager(); + } + + if (zone->pathing) + { + zone->pathing->ConnectNodeToNode(node1, node2, teleport, doorid); + Log.Out(Logs::Moderate, Logs::Quests, "QuestManager::ConnectNodeToNode connecting node %i to node %i.", node1, node2); + } + } +} + +void QuestManager::AddNode(float x, float y, float z, float best_z, int32 requested_id) +{ + if (!x || !y || !z) + { + Log.Out(Logs::General, Logs::Quests, "QuestManager::AddNode called without x, y, z. Probably syntax error in quest file."); + } + + if (!best_z || best_z == 0) + { + if (zone->zonemap) + { + glm::vec3 loc(x, y, z); + best_z = zone->zonemap->FindBestZ(loc, nullptr); + } + else + { + best_z = z; + } + } + + if (!requested_id) + { + requested_id = 0; + } + + if (!zone->pathing) + { + // if no pathing bits available, make them available. + zone->pathing = new PathManager(); + } + + if (zone->pathing) + { + zone->pathing->AddNode(x, y, z, best_z, requested_id); + Log.Out(Logs::Moderate, Logs::Quests, "QuestManager::AddNode adding node at (%i, %i, %i).", x, y, z); + } +} + void QuestManager::settarget(const char *type, int target_id) { QuestManagerCurrentQuestVars(); if (!owner || !owner->IsNPC()) @@ -1219,9 +1294,10 @@ void QuestManager::CreateGuild(const char *guild_name, const char *leader) { } } -void QuestManager::settime(uint8 new_hour, uint8 new_min) { +void QuestManager::settime(uint8 new_hour, uint8 new_min, bool update_world /*= true*/) +{ if (zone) - zone->SetTime(new_hour + 1, new_min); + zone->SetTime(new_hour + 1, new_min, update_world); } void QuestManager::itemlink(int item_id) { @@ -1997,6 +2073,11 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level return false; } + if(Bot::IsBotNameAvailable((char*)name,&TempErrorMessage)) { + initiator->Message(0, "The name %s is already being used or is invalid. Please choose a different name.", (char*)name); + return false; + } + NPCType DefaultNPCTypeStruct = Bot::CreateDefaultNPCTypeStructForBot(name, lastname, level, race, botclass, gender); Bot* NewBot = new Bot(DefaultNPCTypeStruct, initiator); @@ -2012,11 +2093,6 @@ bool QuestManager::createBot(const char *name, const char *lastname, uint8 level return false; } - if(!NewBot->IsBotNameAvailable(&TempErrorMessage)) { - initiator->Message(0, "The name %s is already being used. Please choose a different name.", NewBot->GetCleanName()); - return false; - } - if(!TempErrorMessage.empty()) { initiator->Message(13, "Database Error: %s", TempErrorMessage.c_str()); return false; diff --git a/zone/questmgr.h b/zone/questmgr.h index d2f018b8c..879407dc6 100644 --- a/zone/questmgr.h +++ b/zone/questmgr.h @@ -87,6 +87,8 @@ public: void depopall(int npc_type = 0); void depopzone(bool StartSpawnTimer = true); void repopzone(); + void ConnectNodeToNode(int node1, int node2, int teleport, int doorid); + void AddNode(float x, float y, float z, float best_z, int32 requested_id); void settarget(const char *type, int target_id); void follow(int entity_id, int distance); void sfollow(); @@ -124,7 +126,7 @@ public: void setsky(uint8 new_sky); void setguild(uint32 new_guild_id, uint8 new_rank); void CreateGuild(const char *guild_name, const char *leader); - void settime(uint8 new_hour, uint8 new_min); + void settime(uint8 new_hour, uint8 new_min, bool update_world = true); void itemlink(int item_id); void signal(int npc_id, int wait_ms = 0); void signalwith(int npc_id, int signal_id, int wait_ms = 0); diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index c0eafea80..c42356ea9 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -624,7 +624,7 @@ void SpawnConditionManager::Process() { //get our current time TimeOfDay_Struct tod; - zone->zone_time.getEQTimeOfDay(&tod); + zone->zone_time.GetCurrentEQTimeOfDay(&tod); //see if time is past our nearest event. if(EQTime::IsTimeBefore(&next_event, &tod)) @@ -673,7 +673,7 @@ void SpawnConditionManager::ExecEvent(SpawnEvent &event, bool send_update) { } TimeOfDay_Struct tod; - zone->zone_time.getEQTimeOfDay(&tod); + zone->zone_time.GetCurrentEQTimeOfDay(&tod); if(event.strict && (event.next.hour != tod.hour || event.next.day != tod.day || event.next.month != tod.month || event.next.year != tod.year)) { Log.Out(Logs::Detail, Logs::Spawns, "Event %d: Unable to execute. Condition is strict, and event time has already passed.", event.id); @@ -871,7 +871,7 @@ bool SpawnConditionManager::LoadSpawnConditions(const char* zone_name, uint32 in //better solution, and I just dont care thats much. //get our current time TimeOfDay_Struct tod; - zone->zone_time.getEQTimeOfDay(&tod); + zone->zone_time.GetCurrentEQTimeOfDay(&tod); for(auto cur = spawn_events.begin(); cur != spawn_events.end(); ++cur) { SpawnEvent &cevent = *cur; @@ -1096,7 +1096,7 @@ void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool stri if(reset_base) { Log.Out(Logs::Detail, Logs::Spawns, "Spawn event %d located in this zone. State set. Trigger time reset (period %d).", event_id, cevent.period); //start with the time now - zone->zone_time.getEQTimeOfDay(&cevent.next); + zone->zone_time.GetCurrentEQTimeOfDay(&cevent.next); //advance the next time by our period EQTime::AddMinutes(cevent.period, &cevent.next); } else { @@ -1141,7 +1141,7 @@ void SpawnConditionManager::ToggleEvent(uint32 event_id, bool enabled, bool stri if(reset_base) { Log.Out(Logs::Detail, Logs::Spawns, "Spawn event %d is in zone %s. State set. Trigger time reset (period %d). Notifying world.", event_id, zone_short_name.c_str(), e.period); //start with the time now - zone->zone_time.getEQTimeOfDay(&e.next); + zone->zone_time.GetCurrentEQTimeOfDay(&e.next); //advance the next time by our period EQTime::AddMinutes(e.period, &e.next); } else { diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 43d205044..eded4bf74 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -95,7 +95,7 @@ void Mob::ApplySpecialAttackMod(SkillUseTypes skill, int32 &dmg, int32 &mindmg) } } -void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int32 min_damage, int32 hate_override,int ReuseTime, +void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, int32 min_damage, int32 hate_override,int ReuseTime, bool HitChance, bool CanAvoid) { //this really should go through the same code as normal melee damage to //pick up all the special behavior there @@ -119,8 +119,12 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, { hate += item->GetItem()->AC; } + const ItemData *itm = item->GetItem(); - hate = hate * (100 + GetFuriousBash(itm->Focus.Effect)) / 100; + auto fbash = GetFuriousBash(itm->Focus.Effect); + hate = hate * (100 + fbash) / 100; + if (fbash) + Message_StringID(MT_Spells, GLOWS_RED, itm->Name); } } } @@ -181,6 +185,11 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { return; CombatAbility_Struct* ca_atk = (CombatAbility_Struct*) app->pBuffer; + pTimerType timer = pTimerCombatAbility; + // RoF2+ Tiger Claw is unlinked from other monk skills, if they ever do that for other classes there will need + // to be more checks here + if (GetClientVersion() >= ClientVersion::RoF2 && ca_atk->m_skill == SkillTigerClaw) + timer = pTimerCombatAbility2; /* Check to see if actually have skill */ if (!MaxSkill(static_cast(ca_atk->m_skill))) @@ -218,7 +227,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { if(!CombatRange(GetTarget())) return; - if(!p_timers.Expired(&database, pTimerCombatAbility, false)) { + if(!p_timers.Expired(&database, timer, false)) { Message(13,"Ability recovery time not yet met."); return; } @@ -268,7 +277,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { DoSpecialAttackDamage(GetTarget(), SkillBash, dmg, 1, ht, ReuseTime); if(ReuseTime > 0) { - p_timers.Start(pTimerCombatAbility, ReuseTime); + p_timers.Start(timer, ReuseTime); } } return; @@ -304,7 +313,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { } if(ReuseTime > 0) { - p_timers.Start(pTimerCombatAbility, ReuseTime); + p_timers.Start(timer, ReuseTime); } return; } @@ -390,7 +399,7 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { ReuseTime = (ReuseTime*HasteMod)/100; if(ReuseTime > 0){ - p_timers.Start(pTimerCombatAbility, ReuseTime); + p_timers.Start(timer, ReuseTime); } } @@ -558,7 +567,7 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { if (level > 54) { // Check for double attack with main hand assuming maxed DA Skill (MS) - if(IsClient() && CastToClient()->CheckDoubleAttack(false)) + if(IsClient() && CastToClient()->CheckDoubleAttack(false)) if(other->GetHP() > 0) RogueBackstab(other,true, ReuseTime); @@ -610,11 +619,11 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) if(primaryweapondamage > 0){ if(level > 25){ - max_hit = (((2*backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + ((level-25)/3) + 1; + max_hit = (((((2*backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + ((level-25)/3) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); hate = 20 * backstab_dmg * GetSkill(SkillBackstab) / 355; } else{ - max_hit = (((2*backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + 1;; + max_hit = (((((2*backstab_dmg) * GetDamageTable(SkillBackstab) / 100) * 10 * GetSkill(SkillBackstab) / 355) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); hate = 20 * backstab_dmg * GetSkill(SkillBackstab) / 355; } @@ -650,10 +659,10 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) } ndamage = mod_backstab_damage(ndamage); - + uint32 Assassinate_Dmg = 0; Assassinate_Dmg = TryAssassinate(other, SkillBackstab, ReuseTime); - + if (Assassinate_Dmg) { ndamage = Assassinate_Dmg; entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); @@ -797,14 +806,14 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow. int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; - if (!ChanceAvoidConsume || (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume)){ + if (!ChanceAvoidConsume || (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume)){ DeleteItemInInventory(ammo_slot, 1, true); Log.Out(Logs::Detail, Logs::Combat, "Consumed one arrow from slot %d", ammo_slot); } else { Log.Out(Logs::Detail, Logs::Combat, "Endless Quiver prevented ammo consumption."); } - CheckIncreaseSkill(SkillArchery, GetTarget(), -15); + CheckIncreaseSkill(SkillArchery, GetTarget(), -15); CommonBreakInvisible(); } @@ -816,7 +825,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite (other->IsClient() && other->CastToClient()->dead)) || HasDied() || (!IsAttackAllowed(other)) || - (other->GetInvul() || + (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { return; @@ -841,12 +850,12 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite LaunchProjectile = true; else{ /* - Item sync check on projectile landing. + Item sync check on projectile landing. Weapon damage is already calculated so this only affects procs! Ammo proc check will use database to find proc if you used up your last ammo. If you change range item mid projectile flight, you loose your chance to proc from bow (Deal with it!). */ - + if (!RangeWeapon && !Ammo && range_id && ammo_id){ ProjectileImpact = true; @@ -858,8 +867,8 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite _RangeWeapon = CastToClient()->m_inv[MainRange]; if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID == range_id) - RangeWeapon = _RangeWeapon; - + RangeWeapon = _RangeWeapon; + _Ammo = CastToClient()->m_inv[AmmoSlot]; if (_Ammo && _Ammo->GetItem() && _Ammo->GetItem()->ID == ammo_id) Ammo = _Ammo; @@ -985,7 +994,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite other->AddToHateList(this, hate, 0, false); other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery); - + //Skill Proc Success if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){ if (ReuseTime) @@ -1047,10 +1056,10 @@ bool Mob::TryProjectileAttack(Mob* other, const ItemData *item, SkillUseTypes sk ProjectileAtk[slot].origin_x = GetX(); ProjectileAtk[slot].origin_y = GetY(); ProjectileAtk[slot].origin_z = GetZ(); - + if (RangeWeapon && RangeWeapon->GetItem()) ProjectileAtk[slot].ranged_id = RangeWeapon->GetItem()->ID; - + if (Ammo && Ammo->GetItem()) ProjectileAtk[slot].ammo_id = Ammo->GetItem()->ID; @@ -1085,7 +1094,7 @@ void Mob::ProjectileAttack() disable = false; Mob* target = entity_list.GetMobID(ProjectileAtk[i].target_id); - + if (target && target->IsMoving()){ //Only recalculate hit increment if target moving //Due to frequency that we need to check increment the targets position variables may not be updated even if moving. Do a simple check before calculating distance. if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()){ @@ -1109,7 +1118,7 @@ void Mob::ProjectileAttack() else CastToNPC()->DoRangedAttackDmg(target, false, ProjectileAtk[i].wpn_dmg,0, static_cast(ProjectileAtk[i].skill)); } - + else { if (ProjectileAtk[i].skill == SkillArchery) @@ -1120,7 +1129,7 @@ void Mob::ProjectileAttack() SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); } } - + ProjectileAtk[i].increment = 0; ProjectileAtk[i].target_id = 0; ProjectileAtk[i].wpn_dmg = 0; @@ -1180,7 +1189,7 @@ float Mob::GetRangeDistTargetSizeMod(Mob* other) mod = 42.0f + (5.8f * (tsize - 15.0f)); else mod = 75.0f; - + return (mod + 2.0f); //Add 2.0f as buffer to prevent any chance of failures, client enforce range check regardless. } @@ -1210,7 +1219,7 @@ void NPC::RangedAttack(Mob* other) float min_range = static_cast(RuleI(Combat, MinRangedAttackDist)); float max_range = 250; // needs to be longer than 200(most spells) - + if (sa_max_range) max_range = static_cast(sa_max_range); @@ -1242,12 +1251,12 @@ void NPC::RangedAttack(Mob* other) } void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 chance_mod, SkillUseTypes skill, float speed, const char *IDFile) { - - if ((other == nullptr || - (other->HasDied())) || - HasDied() || + + if ((other == nullptr || + (other->HasDied())) || + HasDied() || (!IsAttackAllowed(other)) || - (other->GetInvul() || + (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE))) { return; @@ -1268,7 +1277,7 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha ammo = GetAmmoIDfile(); ProjectileAnimation(other, 0,false,speed,0,0,0,ammo,skillInUse); - + if (RuleB(Combat, ProjectileDmgOnImpact)) { TryProjectileAttack(other, nullptr, skillInUse, damage_mod, nullptr, nullptr, 0, speed); @@ -1276,7 +1285,7 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha } } - if (!chance_mod) + if (!chance_mod) chance_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2); if (!other->CheckHitChance(this, skillInUse, MainRange, chance_mod)) @@ -1298,11 +1307,11 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha if (!damage_mod) damage_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3);//Damage modifier - TotalDmg += TotalDmg * damage_mod / 100; + TotalDmg += TotalDmg * damage_mod / 100; other->AvoidDamage(this, TotalDmg, false); other->MeleeMitigation(this, TotalDmg, MinDmg); - + if (TotalDmg > 0) CommonOutgoingHitSuccess(other, TotalDmg, skillInUse); else if (TotalDmg < -4) @@ -1432,18 +1441,18 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 //consume ammo DeleteItemInInventory(ammo_slot, 1, true); - CheckIncreaseSkill(SkillThrowing, GetTarget()); + CheckIncreaseSkill(SkillThrowing, GetTarget()); CommonBreakInvisible(); } void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemData* AmmoItem, uint16 weapon_damage, int16 chance_mod,int16 focus, int ReuseTime, uint32 range_id, int AmmoSlot, float speed) { - if ((other == nullptr || - ((IsClient() && CastToClient()->dead) || - (other->IsClient() && other->CastToClient()->dead)) || - HasDied() || + if ((other == nullptr || + ((IsClient() && CastToClient()->dead) || + (other->IsClient() && other->CastToClient()->dead)) || + HasDied() || (!IsAttackAllowed(other)) || - (other->GetInvul() || + (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { return; @@ -1511,7 +1520,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite return; } } - else + else WDmg = weapon_damage; if (focus) //From FcBaseEffects @@ -1535,7 +1544,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Max Damage %d. Hit for damage %d", WDmg, MaxDmg, TotalDmg); if (!Assassinate_Dmg) other->AvoidDamage(this, TotalDmg, false); //CanRiposte=false - Can not riposte throw attacks. - + other->MeleeMitigation(this, TotalDmg, minDmg); if(TotalDmg > 0) CommonOutgoingHitSuccess(other, TotalDmg, SkillThrowing); @@ -1620,7 +1629,7 @@ void Mob::SendItemAnimation(Mob *to, const ItemData *item, SkillUseTypes skillIn safe_delete(outapp); } -void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile, SkillUseTypes skillInUse) { +void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile, SkillUseTypes skillInUse) { if (!to) return; @@ -2092,7 +2101,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) } ReuseTime = ReuseTime / HasteMod; - if(ReuseTime > 0 && !IsRiposte){ + if(ReuseTime > 0 && !IsRiposte){ p_timers.Start(pTimerCombatAbility, ReuseTime); } } @@ -2270,7 +2279,7 @@ float Mob::GetSpecialProcChances(uint16 hand) if (RuleB(Combat, AdjustSpecialProcPerMinute)) { ProcChance = (static_cast(weapon_speed) * - RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); + RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); ProcBonus += static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25); ProcChance += ProcChance * ProcBonus / 100.0f; } else { @@ -2363,7 +2372,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes int Hand = MainPrimary; if (hate == 0 && weapon_damage > 1) hate = weapon_damage; - if(weapon_damage > 0){ + if(weapon_damage > 0){ if (focus) //From FcBaseEffects weapon_damage += weapon_damage*focus/100; @@ -2411,7 +2420,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes } else { other->AvoidDamage(this, damage, CanRiposte); other->MeleeMitigation(this, damage, min_hit); - if(damage > 0) + if(damage > 0) CommonOutgoingHitSuccess(other, damage, skillinuse); } @@ -2449,7 +2458,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes if (CanSkillProc && HasSkillProcs()) TrySkillProc(other, skillinuse, ReuseTime); - + if (CanSkillProc && (damage > 0) && HasSkillProcSuccess()) TrySkillProc(other, skillinuse, ReuseTime, true); } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index a652651e7..a28c924c3 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -44,7 +44,7 @@ extern WorldServer worldserver; // the spell can still fail here, if the buff can't stack // in this case false will be returned, true otherwise -bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) +bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_override) { int caster_level, buffslot, effect, effect_value, i; ItemInst *SummonedItem=nullptr; @@ -59,31 +59,32 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) const SPDat_Spell_Struct &spell = spells[spell_id]; bool c_override = false; - if(caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) - { - const ItemInst* inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot()); - if(inst) - { - if(inst->GetItem()->Click.Level > 0) - { + if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) { + const ItemInst *inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot()); + if (inst) { + if (inst->GetItem()->Click.Level > 0) { caster_level = inst->GetItem()->Click.Level; c_override = true; - } - else - { + } else { caster_level = caster ? caster->GetCasterLevel(spell_id) : GetCasterLevel(spell_id); } - } - else + } else if (level_override > 0) { + caster_level = level_override; + c_override = true; + } else { caster_level = caster ? caster->GetCasterLevel(spell_id) : GetCasterLevel(spell_id); - } - else + } + } else if (level_override > 0) { + caster_level = level_override; + c_override = true; + } else { caster_level = caster ? caster->GetCasterLevel(spell_id) : GetCasterLevel(spell_id); + } if(c_override) { int durat = CalcBuffDuration(caster, this, spell_id, caster_level); - if(durat > 0) + if(durat) // negatives are perma buffs { buffslot = AddBuff(caster, spell_id, durat, caster_level); if(buffslot == -1) // stacking failure @@ -189,8 +190,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if (!IsPowerDistModSpell(spell_id)) SetSpellPowerDistanceMod(0); - - bool SE_SpellTrigger_HasCast = false; + + bool SE_SpellTrigger_HasCast = false; // iterate through the effects in the spell for (i = 0; i < EFFECT_COUNT; i++) @@ -199,7 +200,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) continue; effect = spell.effectid[i]; - effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster ? caster : this); + effect_value = CalcSpellEffectValue(spell_id, i, caster_level, buffslot > -1 ? buffs[buffslot].instrument_mod : 10, caster ? caster : this); if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands)) effect_value = GetMaxHP(); @@ -424,11 +425,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) } case SE_Succor: - { - + { + float x, y, z, heading; const char *target_zone; - + x = static_cast(spell.base[1]); y = static_cast(spell.base[0]); z = static_cast(spell.base[2]); @@ -772,6 +773,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) caster->SetPet(this); SetOwnerID(caster->GetID()); SetPetOrder(SPO_Follow); + SetPetType(petCharmed); if(caster->IsClient()){ EQApplicationPacket *app = new EQApplicationPacket(OP_Charm, sizeof(Charm_Struct)); @@ -787,8 +789,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if (IsClient()) { - AI_Start(); - SendAppearancePacket(14, 100, true, true); + CastToClient()->AI_Start(); } else if(IsNPC()) { CastToNPC()->SetPetSpellID(0); //not a pet spell. } @@ -872,13 +873,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if(buffs[buffslot].ticsremaining > RuleI(Character, MaxFearDurationForPlayerCharacter)) buffs[buffslot].ticsremaining = RuleI(Character, MaxFearDurationForPlayerCharacter); } - + if(RuleB(Combat, EnableFearPathing)){ if(IsClient()) { - AI_Start(); - animation = static_cast(GetRunspeed() * 21.0f); //set our animation to match our speed about + CastToClient()->AI_Start(); } CalculateNewFearpoint(); @@ -921,7 +921,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) cd->source = action->source; cd->type = action->type; cd->spellid = action->spell; - cd->sequence = action->sequence; + cd->meleepush_xy = action->sequence; CastToClient()->QueuePacket(action_packet); if(caster->IsClient() && caster != this) @@ -970,7 +970,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) cd->source = action->source; cd->type = action->type; cd->spellid = action->spell; - cd->sequence = action->sequence; + cd->meleepush_xy = action->sequence; CastToClient()->QueuePacket(action_packet); if(caster->IsClient() && caster != this) @@ -1006,7 +1006,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) cd->source = action->source; cd->type = action->type; cd->spellid = action->spell; - cd->sequence = action->sequence; + cd->meleepush_xy = action->sequence; CastToClient()->QueuePacket(action_packet); if(caster->IsClient() && caster != this) @@ -1291,7 +1291,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Spell Absorb Rune: %+i", effect_value); #endif - if(effect_value > 0) + if(effect_value > 0) buffs[buffslot].magic_rune = effect_value; break; @@ -1329,12 +1329,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) case SE_DistanceRemoval: { - buffs[buffslot].caston_x = int(GetX()); - buffs[buffslot].caston_y = int(GetY()); - buffs[buffslot].caston_z = int(GetZ()); + buffs[buffslot].caston_x = int(GetX()); + buffs[buffslot].caston_y = int(GetY()); + buffs[buffslot].caston_z = int(GetZ()); break; } - + case SE_Levitate: { #ifdef SPELL_EFFECT_SPAM @@ -1349,13 +1349,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) case SE_DeathSave: { int16 mod = 0; - + if(caster) { mod = caster->aabonuses.UnfailingDivinity + caster->itembonuses.UnfailingDivinity + caster->spellbonuses.UnfailingDivinity; } - + buffs[buffslot].ExtraDIChance = mod; break; } @@ -1440,9 +1440,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) for(int x = EmuConstants::MATERIAL_BEGIN; x <= EmuConstants::MATERIAL_TINT_END; x++) SendWearChange(x); - - if(caster && (caster->spellbonuses.IllusionPersistence || caster->aabonuses.IllusionPersistence - || caster->itembonuses.IllusionPersistence)) + + if (caster == this && + (spellbonuses.IllusionPersistence || aabonuses.IllusionPersistence || + itembonuses.IllusionPersistence)) buffs[buffslot].persistant_buff = 1; else buffs[buffslot].persistant_buff = 0; @@ -1555,8 +1556,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) { uint16 pet_spellid = CastToNPC()->GetPetSpellID(); uint16 pet_ActSpellCost = caster->GetActSpellCost(pet_spellid, spells[pet_spellid].mana); - int16 ImprovedReclaimMod = caster->spellbonuses.ImprovedReclaimEnergy + - caster->itembonuses.ImprovedReclaimEnergy + + int16 ImprovedReclaimMod = caster->spellbonuses.ImprovedReclaimEnergy + + caster->itembonuses.ImprovedReclaimEnergy + caster->aabonuses.ImprovedReclaimEnergy; if (!ImprovedReclaimMod) @@ -1666,9 +1667,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) // Only allow 2 size changes from Base Size float modifyAmount = (static_cast(effect_value) / 100.0f); float maxModAmount = GetBaseSize() * modifyAmount * modifyAmount; - if ((GetSize() <= GetBaseSize() && GetSize() > maxModAmount) || + if ((GetSize() <= GetBaseSize() && GetSize() > maxModAmount) || (GetSize() >= GetBaseSize() && GetSize() < maxModAmount) || - (GetSize() <= GetBaseSize() && maxModAmount > 1.0f) || + (GetSize() <= GetBaseSize() && maxModAmount > 1.0f) || (GetSize() >= GetBaseSize() && maxModAmount < 1.0f)) { ChangeSize(GetSize() * modifyAmount); @@ -1684,7 +1685,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) rooted = true; if (caster){ - buffs[buffslot].RootBreakChance = caster->aabonuses.RootBreakChance + + buffs[buffslot].RootBreakChance = caster->aabonuses.RootBreakChance + caster->itembonuses.RootBreakChance + caster->spellbonuses.RootBreakChance; } @@ -1787,9 +1788,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #endif if(spells[spell_id].base2[i] == 0) - AddProcToWeapon(procid, false, 100, spell_id); + AddProcToWeapon(procid, false, 100, spell_id, level_override); else - AddProcToWeapon(procid, false, spells[spell_id].base2[i]+100, spell_id); + AddProcToWeapon(procid, false, spells[spell_id].base2[i]+100, spell_id, level_override); break; } @@ -2249,7 +2250,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) float range = 0.0f; if (spells[spell_id].base2[i]) range = (float)spells[spell_id].base[i]; - + entity_list.AETaunt(caster->CastToClient(), range); } break; @@ -2540,7 +2541,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) int mana_damage = 0; int32 mana_to_use = GetMana() - spell.base[i]; if(mana_to_use > -1) { - SetMana(GetMana() - spell.base[i]); + SetMana(GetMana() - spell.base[i]); TryTriggerOnValueAmount(false, true); // we take full dmg(-10 to make the damage the right sign) mana_damage = spell.base[i] / -10 * spell.base2[i]; @@ -2661,7 +2662,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) { if (IsNPC()){ caster->Taunt(this->CastToNPC(), false, static_cast(spell.base[i])); - + if (spell.base2[i] > 0) CastToNPC()->SetHateAmountOnEnt(caster, (CastToNPC()->GetHateAmount(caster) + spell.base2[i])); } @@ -2972,7 +2973,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) case SE_TriggerOnReqTarget: case SE_LimitRace: case SE_FcLimitUse: - case SE_FcMute: + case SE_FcMute: case SE_LimitUseType: case SE_FcStunTimeMod: case SE_StunBashChance: @@ -2981,9 +2982,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) case SE_LimitCastTimeMax: case SE_TriggerOnReqCaster: case SE_FrenziedDevastation: - case SE_AStacker: - case SE_BStacker: - case SE_CStacker: + case SE_AStacker: + case SE_BStacker: + case SE_CStacker: case SE_DStacker: case SE_DoubleRiposte: case SE_Berserk: @@ -3029,48 +3030,44 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) return true; } -int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, Mob *caster, int ticsremaining) +int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, uint32 instrument_mod, Mob *caster, + int ticsremaining) { int formula, base, max, effect_value; - if - ( - !IsValidSpell(spell_id) || - effect_id < 0 || - effect_id >= EFFECT_COUNT - ) + if (!IsValidSpell(spell_id) || effect_id < 0 || effect_id >= EFFECT_COUNT) return 0; formula = spells[spell_id].formula[effect_id]; base = spells[spell_id].base[effect_id]; max = spells[spell_id].max[effect_id]; - if(IsBlankSpellEffect(spell_id, effect_id)) + if (IsBlankSpellEffect(spell_id, effect_id)) return 0; effect_value = CalcSpellEffectValue_formula(formula, base, max, caster_level, spell_id, ticsremaining); - if(caster && IsBardSong(spell_id) && - (spells[spell_id].effectid[effect_id] != SE_AttackSpeed) && - (spells[spell_id].effectid[effect_id] != SE_AttackSpeed2) && - (spells[spell_id].effectid[effect_id] != SE_AttackSpeed3) && - (spells[spell_id].effectid[effect_id] != SE_Lull) && - (spells[spell_id].effectid[effect_id] != SE_ChangeFrenzyRad) && - (spells[spell_id].effectid[effect_id] != SE_Harmony) && - (spells[spell_id].effectid[effect_id] != SE_CurrentMana)&& - (spells[spell_id].effectid[effect_id] != SE_ManaRegen_v2)) - { + // this doesn't actually need to be a song to get mods, just the right skill + if (EQEmu::IsBardInstrumentSkill(spells[spell_id].skill) && + spells[spell_id].effectid[effect_id] != SE_AttackSpeed && + spells[spell_id].effectid[effect_id] != SE_AttackSpeed2 && + spells[spell_id].effectid[effect_id] != SE_AttackSpeed3 && + spells[spell_id].effectid[effect_id] != SE_Lull && + spells[spell_id].effectid[effect_id] != SE_ChangeFrenzyRad && + spells[spell_id].effectid[effect_id] != SE_Harmony && + spells[spell_id].effectid[effect_id] != SE_CurrentMana && + spells[spell_id].effectid[effect_id] != SE_ManaRegen_v2) { int oval = effect_value; - int mod = caster->GetInstrumentMod(spell_id); - mod = ApplySpellEffectiveness(caster, spell_id, mod, true); + int mod = ApplySpellEffectiveness(caster, spell_id, instrument_mod, true); effect_value = effect_value * mod / 10; - Log.Out(Logs::Detail, Logs::Spells, "Effect value %d altered with bard modifier of %d to yeild %d", oval, mod, effect_value); + Log.Out(Logs::Detail, Logs::Spells, "Effect value %d altered with bard modifier of %d to yeild %d", + oval, mod, effect_value); } effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster); - return(effect_value); + return effect_value; } // generic formula calculations @@ -3365,30 +3362,19 @@ void Mob::BuffProcess() { if (buffs[buffs_i].spellid != SPELL_UNKNOWN) { - DoBuffTic(buffs[buffs_i].spellid, buffs_i, buffs[buffs_i].ticsremaining, buffs[buffs_i].casterlevel, entity_list.GetMob(buffs[buffs_i].casterid)); + DoBuffTic(buffs[buffs_i], buffs_i, entity_list.GetMob(buffs[buffs_i].casterid)); // If the Mob died during DoBuffTic, then the buff we are currently processing will have been removed if(buffs[buffs_i].spellid == SPELL_UNKNOWN) continue; - if(spells[buffs[buffs_i].spellid].buffdurationformula != DF_Permanent) - { + // DF_Permanent uses -1 DF_Aura uses -4 but we need to check negatives for some spells for some reason? + if (spells[buffs[buffs_i].spellid].buffdurationformula != DF_Permanent && + spells[buffs[buffs_i].spellid].buffdurationformula != DF_Aura) { if(!zone->BuffTimersSuspended() || !IsSuspendableSpell(buffs[buffs_i].spellid)) { --buffs[buffs_i].ticsremaining; - if (buffs[buffs_i].ticsremaining == 0) { - if (!IsShortDurationBuff(buffs[buffs_i].spellid) || - IsFearSpell(buffs[buffs_i].spellid) || - IsCharmSpell(buffs[buffs_i].spellid) || - IsMezSpell(buffs[buffs_i].spellid) || - IsBlindSpell(buffs[buffs_i].spellid)) - { - Log.Out(Logs::Detail, Logs::Spells, "Buff %d in slot %d has expired. Fading.", buffs[buffs_i].spellid, buffs_i); - BuffFadeBySlot(buffs_i); - } - } - else if (buffs[buffs_i].ticsremaining < 0) - { + if ((buffs[buffs_i].ticsremaining == 0 && !IsShortDurationBuff(buffs[buffs_i].spellid)) || buffs[buffs_i].ticsremaining < 0) { Log.Out(Logs::Detail, Logs::Spells, "Buff %d in slot %d has expired. Fading.", buffs[buffs_i].spellid, buffs_i); BuffFadeBySlot(buffs_i); } @@ -3418,334 +3404,311 @@ void Mob::BuffProcess() } } -void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster) { +void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) +{ int effect, effect_value; - if(!IsValidSpell(spell_id)) + if (!IsValidSpell(buff.spellid)) return; - const SPDat_Spell_Struct &spell = spells[spell_id]; + const SPDat_Spell_Struct &spell = spells[buff.spellid]; - if (spell_id == SPELL_UNKNOWN) - return; - - if(IsNPC()) - { + if (IsNPC()) { std::vector args; - args.push_back(&ticsremaining); - args.push_back(&caster_level); + args.push_back(&buff.ticsremaining); + args.push_back(&buff.casterlevel); args.push_back(&slot); - int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_NPC, CastToNPC(), nullptr, spell_id, caster ? caster->GetID() : 0, &args); - if(i != 0) { + int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_NPC, CastToNPC(), nullptr, buff.spellid, + caster ? caster->GetID() : 0, &args); + if (i != 0) { return; } - } - else - { + } else { std::vector args; - args.push_back(&ticsremaining); - args.push_back(&caster_level); + args.push_back(&buff.ticsremaining); + args.push_back(&buff.casterlevel); args.push_back(&slot); - int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_CLIENT, nullptr, CastToClient(), spell_id, caster ? caster->GetID() : 0, &args); - if(i != 0) { + int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_CLIENT, nullptr, CastToClient(), buff.spellid, + caster ? caster->GetID() : 0, &args); + if (i != 0) { return; } } // Check for non buff spell effects to fade // AE melee effects - if(IsClient()) + if (IsClient()) CastToClient()->CheckAAEffect(aaEffectRampage); - for (int i = 0; i < EFFECT_COUNT; i++) - { - if(IsBlankSpellEffect(spell_id, i)) + for (int i = 0; i < EFFECT_COUNT; i++) { + if (IsBlankSpellEffect(buff.spellid, i)) continue; effect = spell.effectid[i]; - //I copied the calculation into each case which needed it instead of - //doing it every time up here, since most buff effects dont need it + // I copied the calculation into each case which needed it instead of + // doing it every time up here, since most buff effects dont need it - switch(effect) - { - case SE_CurrentHP: - { - effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster, ticsremaining); - //Handle client cast DOTs here. - if (caster && effect_value < 0){ - - if (IsDetrimentalSpell(spell_id)){ - if (caster->IsClient()){ - if (!caster->CastToClient()->GetFeigned()) - AddToHateList(caster, -effect_value); - } - else if (!IsClient()) //Allow NPC's to generate hate if casted on other NPC's. + switch (effect) { + case SE_CurrentHP: { + effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod, + caster, buff.ticsremaining); + // Handle client cast DOTs here. + if (caster && effect_value < 0) { + + if (IsDetrimentalSpell(buff.spellid)) { + if (caster->IsClient()) { + if (!caster->CastToClient()->GetFeigned()) AddToHateList(caster, -effect_value); - } - - effect_value = caster->GetActDoTDamage(spell_id, effect_value, this); - - caster->ResourceTap(-effect_value, spell_id); - effect_value = -effect_value; - Damage(caster, effect_value, spell_id, spell.skill, false, i, true); - } else if(effect_value > 0) { - // Regen spell... - // handled with bonuses + } else if (!IsClient()) // Allow NPC's to generate hate if casted on other + // NPC's. + AddToHateList(caster, -effect_value); } - break; + + effect_value = caster->GetActDoTDamage(buff.spellid, effect_value, this); + + caster->ResourceTap(-effect_value, buff.spellid); + effect_value = -effect_value; + Damage(caster, effect_value, buff.spellid, spell.skill, false, i, true); + } else if (effect_value > 0) { + // Regen spell... + // handled with bonuses } - case SE_HealOverTime: - { - effect_value = CalcSpellEffectValue(spell_id, i, caster_level); - if(caster) - effect_value = caster->GetActSpellHealing(spell_id, effect_value); + break; + } + case SE_HealOverTime: { + effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod); + if (caster) + effect_value = caster->GetActSpellHealing(buff.spellid, effect_value); - HealDamage(effect_value, caster, spell_id); - //healing aggro would go here; removed for now + HealDamage(effect_value, caster, buff.spellid); + // healing aggro would go here; removed for now + break; + } + + case SE_CurrentEndurance: { + // Handled with bonuses + break; + } + + case SE_BardAEDot: { + effect_value = + CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod, caster); + + if ((!RuleB(Spells, PreNerfBardAEDoT) && IsMoving()) || invulnerable || + /*effect_value > 0 ||*/ DivineAura()) break; + + if (effect_value < 0) { + effect_value = -effect_value; + if (caster) { + if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) { + AddToHateList(caster, effect_value); + } else if (!caster->IsClient()) + AddToHateList(caster, effect_value); + } + Damage(caster, effect_value, buff.spellid, spell.skill, false, i, true); + } else if (effect_value > 0) { + // healing spell... + HealDamage(effect_value, caster); + // healing aggro would go here; removed for now } + break; + } - case SE_CurrentEndurance: { - // Handled with bonuses - break; - } - - case SE_BardAEDot: - { - effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster); - - if ((!RuleB(Spells, PreNerfBardAEDoT) && IsMoving()) || invulnerable || /*effect_value > 0 ||*/ DivineAura()) - break; - - if(effect_value < 0) { - effect_value = -effect_value; - if(caster){ - if(caster->IsClient() && !caster->CastToClient()->GetFeigned()){ + case SE_Hate: { + effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod); + if (caster) { + if (effect_value > 0) { + if (caster) { + if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) { AddToHateList(caster, effect_value); - } - else if(!caster->IsClient()) + } else if (!caster->IsClient()) AddToHateList(caster, effect_value); } - Damage(caster, effect_value, spell_id, spell.skill, false, i, true); - } else if(effect_value > 0) { - //healing spell... - HealDamage(effect_value, caster); - //healing aggro would go here; removed for now - } - break; - } - - case SE_Hate:{ - effect_value = CalcSpellEffectValue(spell_id, i, caster_level); - if(caster){ - if(effect_value > 0){ - if(caster){ - if(caster->IsClient() && !caster->CastToClient()->GetFeigned()){ - AddToHateList(caster, effect_value); - } - else if(!caster->IsClient()) - AddToHateList(caster, effect_value); - } - }else{ - int32 newhate = GetHateAmount(caster) + effect_value; - if (newhate < 1) { - SetHateAmountOnEnt(caster,1); - } else { - SetHateAmountOnEnt(caster,newhate); - } + } else { + int32 newhate = GetHateAmount(caster) + effect_value; + if (newhate < 1) { + SetHateAmountOnEnt(caster, 1); + } else { + SetHateAmountOnEnt(caster, newhate); } } + } + break; + } + + case SE_WipeHateList: { + if (IsMezSpell(buff.spellid)) break; + + int wipechance = spells[buff.spellid].base[i]; + int bonus = 0; + + if (caster) { + bonus = caster->spellbonuses.IncreaseChanceMemwipe + + caster->itembonuses.IncreaseChanceMemwipe + + caster->aabonuses.IncreaseChanceMemwipe; } - case SE_WipeHateList: - { - if (IsMezSpell(spell_id)) + wipechance += wipechance * bonus / 100; + + if (zone->random.Roll(wipechance)) { + if (IsAIControlled()) { + WipeHateList(); + } + Message(13, "Your mind fogs. Who are my friends? Who are my enemies?... it was all so " + "clear a moment ago..."); + } + break; + } + + case SE_Charm: { + if (!caster || !PassCharismaCheck(caster, buff.spellid)) { + BuffFadeByEffect(SE_Charm); + } + + break; + } + + case SE_Root: { + /* Root formula derived from extensive personal live parses - Kayen + ROOT has a 70% chance to do a resist check to break. + */ + + if (zone->random.Roll(RuleI(Spells, RootBreakCheckChance))) { + float resist_check = + ResistSpell(spells[buff.spellid].resisttype, buff.spellid, caster, 0, 0, 0, 0, true); + + if (resist_check == 100) break; - - int wipechance = spells[spell_id].base[i]; - int bonus = 0; - - if (caster){ - bonus = caster->spellbonuses.IncreaseChanceMemwipe + - caster->itembonuses.IncreaseChanceMemwipe + - caster->aabonuses.IncreaseChanceMemwipe; - } - - wipechance += wipechance*bonus/100; - - if(zone->random.Roll(wipechance)) - { - if(IsAIControlled()) - { - WipeHateList(); - } - Message(13, "Your mind fogs. Who are my friends? Who are my enemies?... it was all so clear a moment ago..."); - } - break; + else if (!TryFadeEffect(slot)) + BuffFadeBySlot(slot); } - case SE_Charm: { - if (!caster || !PassCharismaCheck(caster, spell_id)) { - BuffFadeByEffect(SE_Charm); - } + break; + } - break; - } + case SE_Fear: { + if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) { + float resist_check = ResistSpell(spells[buff.spellid].resisttype, buff.spellid, caster); - case SE_Root: { - /* Root formula derived from extensive personal live parses - Kayen - ROOT has a 70% chance to do a resist check to break. - */ - - if (zone->random.Roll(RuleI(Spells, RootBreakCheckChance))) { - float resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster, 0,0,0,0,true); - - if(resist_check == 100) - break; - else - if(!TryFadeEffect(slot)) - BuffFadeBySlot(slot); - } - - break; - } - - case SE_Fear: - { - if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) { - float resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster); - - if(resist_check == 100) - break; - else - if(!TryFadeEffect(slot)) - BuffFadeBySlot(slot); - } - - break; - } - - case SE_Hunger: { - // this procedure gets called 7 times for every once that the stamina update occurs so we add 1/7 of the subtraction. - // It's far from perfect, but works without any unnecessary buff checks to bog down the server. - if(IsClient()) { - CastToClient()->m_pp.hunger_level += 5; - CastToClient()->m_pp.thirst_level += 5; - } - break; - } - case SE_Invisibility: - case SE_InvisVsAnimals: - case SE_InvisVsUndead: - { - if(ticsremaining > 3) - { - if(!IsBardSong(spell_id)) - { - double break_chance = 2.0; - if(caster) - { - break_chance -= (2 * (((double)caster->GetSkill(SkillDivination) + ((double)caster->GetLevel() * 3.0)) / 650.0)); - } - else - { - break_chance -= (2 * (((double)GetSkill(SkillDivination) + ((double)GetLevel() * 3.0)) / 650.0)); - } - - if(zone->random.Real(0.0, 100.0) < break_chance) - { - BuffModifyDurationBySpellID(spell_id, 3); - } - } - } - } - case SE_Invisibility2: - case SE_InvisVsUndead2: - { - if(ticsremaining <= 3 && ticsremaining > 1) - { - Message_StringID(MT_Spells, INVIS_BEGIN_BREAK); - } - break; - } - case SE_InterruptCasting: - { - if(IsCasting()) - { - if(zone->random.Roll(spells[spell_id].base[i])) - { - InterruptSpell(); - } - } - break; - } - // These effects always trigger when they fade. - case SE_CastOnFadeEffect: - case SE_CastOnFadeEffectNPC: - case SE_CastOnFadeEffectAlways: - { - if (ticsremaining == 1) - { - SpellOnTarget(spells[spell_id].base[i], this); - } - break; - } - case SE_LocateCorpse: - { - // This is handled by the client prior to SoD. - - if(IsClient() && (CastToClient()->GetClientVersionBit() & BIT_SoDAndLater)) - CastToClient()->LocateCorpse(); - } - case SE_TotalHP: - { - if (spell.formula[i] > 1000 && spell.formula[i] < 1999) - { - // These formulas can affect Max HP each tick - // Maybe there is a more efficient way to recalculate this for just Max HP each tic... - //CalcBonuses(); - CalcSpellBonuses(&spellbonuses); - CalcMaxHP(); - } - break; - } - - case SE_DistanceRemoval: - { - if (spellbonuses.DistanceRemoval){ - - int distance = ((int(GetX()) - buffs[slot].caston_x) * (int(GetX()) - buffs[slot].caston_x)) + - ((int(GetY()) - buffs[slot].caston_y) * (int(GetY()) - buffs[slot].caston_y)) + - ((int(GetZ()) - buffs[slot].caston_z) * (int(GetZ()) - buffs[slot].caston_z)); - - if (distance > (spells[spell_id].base[i] * spells[spell_id].base[i])){ - - if(!TryFadeEffect(slot)) - BuffFadeBySlot(slot , true); - } + if (resist_check == 100) break; - } + else if (!TryFadeEffect(slot)) + BuffFadeBySlot(slot); } - case SE_AddHateOverTimePct: - { - if (IsNPC()){ - uint32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base[i]) / 100; - if (new_hate <= 0) - new_hate = 1; - - CastToNPC()->SetHateAmountOnEnt(caster, new_hate); - } - break; + break; + } + + case SE_Hunger: { + // this procedure gets called 7 times for every once that the stamina update occurs so we add + // 1/7 of the subtraction. + // It's far from perfect, but works without any unnecessary buff checks to bog down the server. + if (IsClient()) { + CastToClient()->m_pp.hunger_level += 5; + CastToClient()->m_pp.thirst_level += 5; } + break; + } + case SE_Invisibility: + case SE_InvisVsAnimals: + case SE_InvisVsUndead: { + if (buff.ticsremaining > 3) { + if (!IsBardSong(buff.spellid)) { + double break_chance = 2.0; + if (caster) { + break_chance -= (2 * (((double)caster->GetSkill(SkillDivination) + + ((double)caster->GetLevel() * 3.0)) / + 650.0)); + } else { + break_chance -= + (2 * + (((double)GetSkill(SkillDivination) + ((double)GetLevel() * 3.0)) / + 650.0)); + } - - default: - { - // do we need to do anyting here? + if (zone->random.Real(0.0, 100.0) < break_chance) { + BuffModifyDurationBySpellID(buff.spellid, 3); + } + } } } + case SE_Invisibility2: + case SE_InvisVsUndead2: { + if (buff.ticsremaining <= 3 && buff.ticsremaining > 1) { + Message_StringID(MT_Spells, INVIS_BEGIN_BREAK); + } + break; + } + case SE_InterruptCasting: { + if (IsCasting()) { + if (zone->random.Roll(spells[buff.spellid].base[i])) { + InterruptSpell(); + } + } + break; + } + // These effects always trigger when they fade. + case SE_CastOnFadeEffect: + case SE_CastOnFadeEffectNPC: + case SE_CastOnFadeEffectAlways: { + if (buff.ticsremaining == 1) { + SpellOnTarget(spells[buff.spellid].base[i], this); + } + break; + } + case SE_LocateCorpse: { + // This is handled by the client prior to SoD. + + if (IsClient() && (CastToClient()->GetClientVersionBit() & BIT_SoDAndLater)) + CastToClient()->LocateCorpse(); + } + case SE_TotalHP: { + if (spell.formula[i] > 1000 && spell.formula[i] < 1999) { + // These formulas can affect Max HP each tick + // Maybe there is a more efficient way to recalculate this for just Max HP each tic... + // CalcBonuses(); + CalcSpellBonuses(&spellbonuses); + CalcMaxHP(); + } + break; + } + + case SE_DistanceRemoval: { + if (spellbonuses.DistanceRemoval) { + + int distance = + ((int(GetX()) - buff.caston_x) * (int(GetX()) - buff.caston_x)) + + ((int(GetY()) - buff.caston_y) * (int(GetY()) - buff.caston_y)) + + ((int(GetZ()) - buff.caston_z) * (int(GetZ()) - buff.caston_z)); + + if (distance > (spells[buff.spellid].base[i] * spells[buff.spellid].base[i])) { + + if (!TryFadeEffect(slot)) + BuffFadeBySlot(slot, true); + } + break; + } + } + + case SE_AddHateOverTimePct: { + if (IsNPC()) { + uint32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base[i]) / 100; + if (new_hate <= 0) + new_hate = 1; + + CastToNPC()->SetHateAmountOnEnt(caster, new_hate); + } + break; + } + + default: { + // do we need to do anyting here? + } + } + if (!IsValidSpell(buff.spellid)) // if we faded we're no longer valid! + break; } } @@ -3950,11 +3913,12 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) if(IsNPC()) { CastToNPC()->RestoreGuardSpotCharm(); - SendAppearancePacket(AT_Pet, 0, true, true); } + SendAppearancePacket(AT_Pet, 0, true, true); Mob* tempmob = GetOwner(); SetOwnerID(0); + SetPetType(petNone); if(tempmob) { tempmob->SetPet(0); @@ -3982,12 +3946,12 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) { InterruptSpell(); if (this->CastToClient()->IsLD()) - AI_Start(CLIENT_LD_TIMEOUT); + CastToClient()->AI_Start(CLIENT_LD_TIMEOUT); else { bool feared = FindType(SE_Fear); if(!feared) - AI_Stop(); + CastToClient()->AI_Stop(); } } break; @@ -4012,7 +3976,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) { bool charmed = FindType(SE_Charm); if(!charmed) - AI_Stop(); + CastToClient()->AI_Stop(); } if(curfp) { @@ -4136,9 +4100,9 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) uint32 buff_max = GetMaxTotalSlots(); bool found_numhits = false; - + for(uint32 d = 0; d < buff_max; d++) { - + if(IsValidSpell(buffs[d].spellid) && (buffs[d].numhits > 0)) { Numhits(true); found_numhits = true; @@ -4148,7 +4112,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) if (!found_numhits) Numhits(false); } - + if (spells[buffs[slot].spellid].NimbusEffect > 0) RemoveNimbusEffect(spells[buffs[slot].spellid].NimbusEffect); @@ -4204,15 +4168,15 @@ int32 Client::GetAAEffectDataBySlot(uint32 aa_ID, uint32 slot_id, bool GetEffect base1 = iter->second.base1; base2 = iter->second.base2; slot = iter->second.slot; - + if (slot && slot == slot_id) { if (GetEffect) return effect; - + if (GetBase1) return base1; - + if (GetBase2) return base2; } @@ -4236,7 +4200,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) uint32 slot = 0; bool LimitFailure = false; - bool LimitInclude[MaxLimitInclude] = { false }; + bool LimitInclude[MaxLimitInclude] = { false }; /* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice spells. 0/1 SE_LimitResist 2/3 SE_LimitSpell @@ -4247,7 +4211,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) 12/13 SE_LimitSpellClass: 14/15 SE_LimitSpellSubClass: Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes - */ + */ int FocusCount = 0; std::map >::const_iterator find_iter = aa_effects.find(aa_ID); @@ -4262,7 +4226,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) base1 = iter->second.base1; base2 = iter->second.base2; slot = iter->second.slot; - + /* AA Foci's can contain multiple focus effects within the same AA. To handle this we will not automatically return zero if a limit is found. @@ -4283,7 +4247,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) if (LimitFailure){ value = 0; LimitFailure = false; - + for(int e = 0; e < MaxLimitInclude; e++) { LimitInclude[e] = false; //Reset array } @@ -4322,7 +4286,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) LimitFailure = true; break; - + case SE_LimitMaxLevel: spell_level = spell.classes[(GetClass()%16) - 1]; lvldiff = spell_level - base1; @@ -4333,7 +4297,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) if(lvlModifier < 1) LimitFailure = true; } - else + else LimitFailure = true; } break; @@ -4357,7 +4321,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) if(base1 < 0) { //Exclude if (spell_id == -base1) LimitFailure = true; - } + } else { LimitInclude[2] = true; if (spell_id == base1) //Include @@ -4450,7 +4414,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) if(base1 < 0) { //Exclude if (CheckSpellCategory(spell_id, base1, SE_LimitSpellClass)) return(0); - } + } else { LimitInclude[12] = true; if (CheckSpellCategory(spell_id, base1, SE_LimitSpellClass)) //Include @@ -4462,7 +4426,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) if(base1 < 0) { //Exclude if (CheckSpellCategory(spell_id, base1, SE_LimitSpellSubclass)) return(0); - } + } else { LimitInclude[14] = true; if (CheckSpellCategory(spell_id, base1, SE_LimitSpellSubclass)) //Include @@ -4599,7 +4563,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) //Note if using these as AA, make sure this is first focus used. case SE_SympatheticProc: - if(type == focusSympatheticProc) + if(type == focusSympatheticProc) value = base2; break; @@ -4657,12 +4621,12 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) if(type == focusIncreaseNumHits) value = base1; break; - + case SE_FcLimitUse: if(type == focusFcLimitUse) value = base1; break; - + case SE_FcMute: if(type == focusFcMute) value = base1; @@ -4683,7 +4647,7 @@ int16 Client::CalcAAFocus(focusType type, uint32 aa_ID, uint16 spell_id) if (LimitFailure) return 0; - + return(value*lvlModifier/100); } @@ -4694,7 +4658,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo if(!IsValidSpell(focus_id) || !IsValidSpell(spell_id)) return 0; - + const SPDat_Spell_Struct &focus_spell = spells[focus_id]; const SPDat_Spell_Struct &spell = spells[spell_id]; @@ -4704,7 +4668,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo int lvldiff = 0; uint32 Caston_spell_id = 0; - bool LimitInclude[MaxLimitInclude] = { false }; + bool LimitInclude[MaxLimitInclude] = { false }; /* Certain limits require only one of several Include conditions to be true. Ie. Add damage to fire OR ice spells. 0/1 SE_LimitResist 2/3 SE_LimitSpell @@ -4715,15 +4679,15 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo 12/13 SE_LimitSpellClass: 14/15 SE_LimitSpellSubClass: Remember: Update MaxLimitInclude in spdat.h if adding new limits that require Includes - */ - + */ + for (int i = 0; i < EFFECT_COUNT; i++) { switch (focus_spell.effectid[i]) { - + case SE_Blank: break; - + case SE_LimitResist: if(focus_spell.base[i] < 0){ if (spell.resisttype == -focus_spell.base[i]) //Exclude @@ -4735,7 +4699,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo LimitInclude[1] = true; } break; - + case SE_LimitInstant: if(focus_spell.base[i] == 1 && spell.buffduration) //Fail if not instant return 0; @@ -4760,7 +4724,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo return 0; } break; - + case SE_LimitMinLevel: if (IsNPC()) break; @@ -4777,12 +4741,12 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo if (spells[spell_id].cast_time > (uint16)focus_spell.base[i]) return(0); break; - + case SE_LimitSpell: if(focus_spell.base[i] < 0) { //Exclude if (spell_id == -focus_spell.base[i]) return(0); - } + } else { LimitInclude[2] = true; if (spell_id == focus_spell.base[i]) //Include @@ -4871,7 +4835,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo LimitInclude[11] = true; } break; - + case SE_LimitClass: //Do not use this limit more then once per spell. If multiple class, treat value like items would. if (!PassLimitClass(focus_spell.base[i], GetClass())) @@ -4989,7 +4953,7 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo if (type == focusSpellHaste && focus_spell.base[i] > value) value = focus_spell.base[i]; break; - + case SE_IncreaseSpellDuration: if (type == focusSpellDuration && focus_spell.base[i] > value) value = focus_spell.base[i]; @@ -5161,14 +5125,14 @@ int16 Mob::CalcFocusEffect(focusType type, uint16 focus_id, uint16 spell_id, boo Log.Out(Logs::General, Logs::Normal, "CalcFocusEffect: unknown effectid %d", focus_spell.effectid[i]); #endif } - + } for(int e = 0; e < MaxLimitInclude; e+=2) { if (LimitInclude[e] && !LimitInclude[e+1]) return 0; } - + if (Caston_spell_id){ if(IsValidSpell(Caston_spell_id) && (Caston_spell_id != spell_id)) SpellFinished(Caston_spell_id, this, 10, 0, -1, spells[Caston_spell_id].ResistDiff); @@ -5260,7 +5224,7 @@ uint16 Client::GetSympatheticFocusEffect(focusType type, uint16 spell_id) { } } - /*Note: At present, ff designing custom AA to have a sympathetic proc effect, only use one focus + /*Note: At present, ff designing custom AA to have a sympathetic proc effect, only use one focus effect within the aa_effects data for each AA*[No live AA's use this effect to my knowledge]*/ if (aabonuses.FocusEffects[type]){ @@ -5435,8 +5399,46 @@ int16 Client::GetFocusEffect(focusType type, uint16 spell_id) { if(UsedItem && rand_effectiveness && focus_max_real != 0) realTotal = CalcFocusEffect(type, UsedFocusID, spell_id); - if (realTotal != 0 && UsedItem) - Message_StringID(MT_Spells, BEGINS_TO_GLOW, UsedItem->Name); + if ((rand_effectiveness && UsedItem) || (realTotal != 0 && UsedItem)) { + // there are a crap ton more of these, I was able to verify these ones though + // the RNG effective ones appear to have a different message for failing to focus + uint32 string_id = BEGINS_TO_GLOW; // this is really just clicky message ... + switch (type) { + case focusSpellHaste: + string_id = SHIMMERS_BRIEFLY; + break; + case focusManaCost: // this might be GROWS_DIM for fail + string_id = FLICKERS_PALE_LIGHT; + break; + case focusSpellDuration: + string_id = SPARKLES; + break; + case focusImprovedDamage: + if (realTotal) + string_id = ALIVE_WITH_POWER; + else + string_id = SEEMS_DRAINED; + break; + case focusRange: + string_id = PULSES_WITH_LIGHT; + break; + case focusSpellHateMod: // GLOWS_RED for increasing hate + string_id = GLOWS_BLUE; + break; + case focusImprovedHeal: + if (realTotal) + string_id = FEEDS_WITH_POWER; + else + string_id = POWER_DRAIN_INTO; + break; + case focusReagentCost: // this might be GROWS_DIM for fail as well ... + string_id = BEGINS_TO_SHINE; + break; + default: + break; + } + Message_StringID(MT_Spells, string_id, UsedItem->Name); + } } //Check if spell focus effect exists for the client. @@ -5560,7 +5562,7 @@ int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { if(!cur) continue; - + TempItem = cur; if (TempItem && TempItem->Focus.Effect > 0 && TempItem->Focus.Effect != SPELL_UNKNOWN) { @@ -5590,7 +5592,7 @@ int16 NPC::GetFocusEffect(focusType type, uint16 spell_id) { } } } - + if(UsedItem && rand_effectiveness && focus_max_real != 0) realTotal = CalcFocusEffect(type, UsedFocusID, spell_id); } @@ -5909,21 +5911,21 @@ float Mob::GetSympatheticProcChances(uint16 spell_id, int16 ProcRateMod, int32 I int32 total_cast_time = 0; float cast_time_mod = 0.0f; ProcRateMod -= 100; - + if (spells[spell_id].recast_time >= spells[spell_id].recovery_time) total_cast_time = spells[spell_id].recast_time + spells[spell_id].cast_time; else total_cast_time = spells[spell_id].recovery_time + spells[spell_id].cast_time; - - if (total_cast_time > 0 && total_cast_time <= 2500) - cast_time_mod = 0.25f; - else if (total_cast_time > 2500 && total_cast_time < 7000) - cast_time_mod = 0.167f*((static_cast(total_cast_time) - 1000.0f)/1000.0f); - else - cast_time_mod = static_cast(total_cast_time) / 7000.0f; - ProcChance = (RuleR(Casting, AvgSpellProcsPerMinute)/100.0f) * (static_cast(100.0f + ProcRateMod) / 10.0f) + if (total_cast_time > 0 && total_cast_time <= 2500) + cast_time_mod = 0.25f; + else if (total_cast_time > 2500 && total_cast_time < 7000) + cast_time_mod = 0.167f*((static_cast(total_cast_time) - 1000.0f)/1000.0f); + else + cast_time_mod = static_cast(total_cast_time) / 7000.0f; + + ProcChance = (RuleR(Casting, AvgSpellProcsPerMinute)/100.0f) * (static_cast(100.0f + ProcRateMod) / 10.0f) * cast_time_mod * (static_cast(100.0f + ItemProcRate)/100.0f); return ProcChance; @@ -6063,7 +6065,7 @@ int32 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spel CheckNumHitsRemaining(NumHit::MatchingSpells, tmp_buffslot); } - + return value; } @@ -6147,8 +6149,8 @@ uint16 Mob::GetSpellEffectResistChance(uint16 spell_id) bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){ - /*Live 5-20-14 Patch Note: Updated all spells which use Remove Detrimental and - Cancel Beneficial spell effects to use a new method. The chances for those spells to + /*Live 5-20-14 Patch Note: Updated all spells which use Remove Detrimental and + Cancel Beneficial spell effects to use a new method. The chances for those spells to affect their targets have not changed unless otherwise noted.*/ /*This should provide a somewhat accurate conversion between pre 5/14 base values and post. @@ -6160,7 +6162,7 @@ bool Mob::TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier){ //Effect value of dispels are treated as a level modifier. //Values for scaling were obtain from live parses, best estimates. - caster_level += level_modifier - 1; + caster_level += level_modifier - 1; int dispel_chance = 32; //Baseline chance if no level difference and no modifier int level_diff = caster_level - buff_level; @@ -6201,7 +6203,7 @@ bool Mob::ImprovedTaunt(){ else { if(!TryFadeEffect(spellbonuses.ImprovedTaunt[2])) BuffFadeBySlot(spellbonuses.ImprovedTaunt[2], true); //If caster killed removed effect. - } + } } } @@ -6267,7 +6269,7 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama Range 845 - 847 : UNKNOWN Range 10000 - 11000 : Limit to Race [base2 - 10000 = Race] (*Not on live: Too useful a function to not implement) THIS IS A WORK IN PROGRESS - */ + */ if (value <= 0) return true; @@ -6276,174 +6278,174 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama switch(value) { - case 100: + case 100: if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Humanoid)) return true; break; - case 101: + case 101: if (GetBodyType() == BT_Dragon || GetBodyType() == BT_VeliousDragon || GetBodyType() == BT_Dragon3) return true; break; - case 102: + case 102: if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Insect)) return true; break; - case 104: + case 104: if (GetBodyType() == BT_Animal) return true; break; - case 105: + case 105: if (GetBodyType() == BT_Plant) return true; break; - case 106: + case 106: if (GetBodyType() == BT_Giant) return true; break; - case 108: + case 108: if ((GetBodyType() != BT_Animal) || (GetBodyType() != BT_Humanoid)) return true; break; - case 109: + case 109: if ((GetRace() == 520) ||(GetRace() == 79)) return true; break; - case 111: + case 111: if ((GetRace() == 527) ||(GetRace() == 11)) return true; break; - case 112: + case 112: if ((GetRace() == 456) ||(GetRace() == 28)) return true; break; - case 113: + case 113: if ((GetRace() == 456) ||(GetRace() == 48)) return true; break; - case 114: + case 114: if (GetRace() == 526) return true; break; - case 115: + case 115: if (GetRace() == 522) return true; break; - case 117: + case 117: if ((GetBodyType() == BT_Animal) || (GetBodyType() == BT_Plant)) return true; break; - case 118: + case 118: if (GetBodyType() == BT_Summoned) return true; break; - case 119: + case 119: if (IsPet() && ((GetRace() == 212) || ((GetRace() == 75) && GetTexture() == 1))) return true; break; - case 120: + case 120: if (GetBodyType() == BT_Undead) return true; break; - case 121: + case 121: if (GetBodyType() != BT_Undead) return true; break; - case 122: + case 122: if ((GetRace() == 473) || (GetRace() == 425)) return true; break; - case 123: + case 123: if (GetBodyType() == BT_Humanoid) return true; break; - case 124: + case 124: if ((GetBodyType() == BT_Undead) && (GetHPRatio() < 10)) return true; break; - case 125: + case 125: if ((GetRace() == 457 || GetRace() == 88) && (GetHPRatio() < 10)) return true; break; - case 126: + case 126: if ((GetRace() == 581 || GetRace() == 69) && (GetHPRatio() < 10)) return true; break; - case 201: + case 201: if (GetHPRatio() > 75) return true; break; - case 204: + case 204: if (GetHPRatio() < 20) return true; break; - case 216: + case 216: if (!IsEngaged()) return true; break; - case 250: + case 250: if (GetHPRatio() < 35) return true; break; - case 304: - if (IsClient() && + case 304: + if (IsClient() && ((GetClass() == WARRIOR) || (GetClass() == BARD) || (GetClass() == SHADOWKNIGHT) || (GetClass() == PALADIN) || (GetClass() == CLERIC) || (GetClass() == RANGER) || (GetClass() == SHAMAN) || (GetClass() == ROGUE) || (GetClass() == BERSERKER))) return true; break; - case 701: + case 701: if (!IsPet()) return true; break; - case 818: + case 818: if (GetBodyType() == BT_Undead) return true; break; - case 819: + case 819: if (GetBodyType() != BT_Undead) return true; break; - case 842: + case 842: if (GetBodyType() == BT_Humanoid && GetLevel() <= 84) return true; break; - case 843: + case 843: if (GetBodyType() == BT_Humanoid && GetLevel() <= 86) return true; break; - case 844: + case 844: if (GetBodyType() == BT_Humanoid && GetLevel() <= 88) return true; break; @@ -6452,7 +6454,7 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama //Limit to amount of pets if (value >= 221 && value <= 249){ int count = hate_list.GetSummonedPetCountOnHateList(this); - + for (int base2_value = 221; base2_value <= 249; ++base2_value){ if (value == base2_value){ if (count >= (base2_value - 220)){ @@ -6476,12 +6478,12 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama } //End Damage if (!IsDamage || UseCastRestriction) { - + //Heal only if HP within specified range. [Doesn't follow a set forumla for all values...] if (value >= 400 && value <= 408){ for (int base2_value = 400; base2_value <= 408; ++base2_value){ if (value == base2_value){ - + if (value == 400 && GetHPRatio() <= 25) return true; @@ -6492,11 +6494,11 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama } } } - + else if (value >= 500 && value <= 549){ for (int base2_value = 500; base2_value <= 520; ++base2_value){ if (value == base2_value){ - if (GetHPRatio() < (base2_value - 500)*5) + if (GetHPRatio() < (base2_value - 500)*5) return true; } } @@ -6507,8 +6509,8 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama return true; } } // End Heal - - + + return false; } @@ -6521,18 +6523,18 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target. -If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles). -The way this is written once a bolt is cast a the distance from the initial cast to the target repeatedly - check and if target is moving recalculates at what predicted time the bolt should hit that target in client_process + check and if target is moving recalculates at what predicted time the bolt should hit that target in client_process When bolt hits its predicted point the damage is then done to target. Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant. - Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units. + Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units. Pending Implementation: What this code can not do is prevent damage if the bolt hits a barrier after passing the initial LOS check because the target has moved while the bolt is in motion. (it is rare to actual get this to occur on live in normal game play) */ if (!spell_target) return false; - - uint8 anim = spells[spell_id].CastingAnim; + + uint8 anim = spells[spell_id].CastingAnim; int slot = -1; //Make sure there is an avialable bolt to be cast. @@ -6545,9 +6547,9 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ if (slot < 0) return false; - + if (CheckLosFN(spell_target)) { - + float speed_mod = speed * 0.45f; //Constant for adjusting speeds to match calculated impact time. float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ()); float hit = 60.0f + (distance / speed_mod); @@ -6570,18 +6572,18 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ ProjectileAnimation(spell_target,0, false, speed,0,0,0, spells[spell_id].player_1); } - //This allows limited support for server using older spell files that do not contain data for bolt graphics. + //This allows limited support for server using older spell files that do not contain data for bolt graphics. else { //Only use fire graphic for fire spells. if (spells[spell_id].resisttype == RESIST_FIRE) { - + if (IsClient()){ if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic. ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, speed); - else + else ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed); } - + else ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, speed); } @@ -6627,7 +6629,7 @@ void Mob::ResourceTap(int32 damage, uint16 spellid) } void Mob::TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker){ - + if (damage <= 0) return; @@ -6649,15 +6651,15 @@ void Mob::TryTriggerThreshHold(int32 damage, int effect_id, Mob* attacker){ uint16 spell_id = spells[buffs[slot].spellid].base[i]; if (damage > spells[buffs[slot].spellid].base2[i]){ - + BuffFadeBySlot(slot); if (IsValidSpell(spell_id)) { - if (IsBeneficialSpell(spell_id)) + if (IsBeneficialSpell(spell_id)) SpellFinished(spell_id, this, 10, 0, -1, spells[spell_id].ResistDiff); - - else if(attacker) + + else if(attacker) SpellFinished(spell_id, attacker, 10, 0, -1, spells[spell_id].ResistDiff); } } @@ -6734,7 +6736,7 @@ void Mob::CalcSpellPowerDistanceMod(uint16 spell_id, float range, Mob* caster) float dist_from_min = distance - spells[spell_id].min_dist; float mod = spells[spell_id].min_dist_mod + (dist_from_min * (dm_mod_interval/dm_range)); mod *= 100.0f; - + SetSpellPowerDistanceMod(static_cast(mod)); } } diff --git a/zone/spells.cpp b/zone/spells.cpp index bfaaaae30..5424fde25 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -82,6 +82,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org) #include #include +#include #ifndef WIN32 #include @@ -369,6 +370,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, // and a target wasn't provided, then it's us; unless TGB is on and this // is a TGB compatible spell. if((IsGroupSpell(spell_id) || + spell.targettype == ST_AEClientV1 || spell.targettype == ST_Self || spell.targettype == ST_AECaster || spell.targettype == ST_Ring || @@ -392,7 +394,7 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, // we checked for spells not requiring targets above if(target_id == 0) { - Log.Out(Logs::Detail, Logs::Spells, "Spell Error: no target. spell=%d\n", GetName(), spell_id); + Log.Out(Logs::Detail, Logs::Spells, "Spell Error: no target. spell=%d", spell_id); if(IsClient()) { //clients produce messages... npcs should not for this case Message_StringID(13, SPELL_NEED_TAR); @@ -488,6 +490,12 @@ bool Mob::DoCastSpell(uint16 spell_id, uint16 target_id, uint16 slot, safe_delete(outapp); outapp = nullptr; + if (IsClient() && slot == USE_ITEM_SPELL_SLOT &&item_slot != 0xFFFFFFFF) { + auto item = CastToClient()->GetInv().GetItem(item_slot); + if (item && item->GetItem()) + Message_StringID(MT_Spells, BEGINS_TO_GLOW, item->GetItem()->Name); + } + if (!DoCastingChecks()) { InterruptSpell(); return false; @@ -1164,12 +1172,14 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, uint16 slot, } else if (!bard_song_mode) { + int noexpend; for(int t_count = 0; t_count < 4; t_count++) { component = spells[spell_id].components[t_count]; - if (component == -1) + noexpend = spells[spell_id].NoexpendReagent[t_count]; + if (component == -1 || noexpend == component) continue; component_count = spells[spell_id].component_counts[t_count]; - Log.Out(Logs::Detail, Logs::Spells, "Spell %d: Consuming %d of spell component item id %d", spell_id, component, component_count); + Log.Out(Logs::Detail, Logs::Spells, "Spell %d: Consuming %d of spell component item id %d", spell_id, component_count, component); // Components found, Deleting // now we go looking for and deleting the items one by one for(int s = 0; s < component_count; s++) @@ -1477,7 +1487,10 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce { //invalid target Log.Out(Logs::Detail, Logs::Spells, "Spell %d canceled: invalid target of body type %d (undead)", spell_id, mob_body); - Message_StringID(13,SPELL_NEED_TAR); + if(!spell_target) + Message_StringID(13,SPELL_NEED_TAR); + else + Message_StringID(13,CANNOT_AFFECT_NPC); return false; } CastAction = SingleTarget; @@ -1606,6 +1619,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce case ST_AEBard: case ST_AECaster: + case ST_AEClientV1: { spell_target = nullptr; ae_center = this; @@ -1891,7 +1905,7 @@ bool Mob::DetermineSpellTargets(uint16 spell_id, Mob *&spell_target, Mob *&ae_ce // we can't interrupt in this, or anything called from this! // if you need to abort the casting, return false bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 mana_used, - uint32 inventory_slot, int16 resist_adjust, bool isproc) + uint32 inventory_slot, int16 resist_adjust, bool isproc, int level_override) { //EQApplicationPacket *outapp = nullptr; Mob *ae_center = nullptr; @@ -1933,6 +1947,12 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } } + if (IsClient() && CastToClient()->GetGM()){ + if (zone->IsSpellBlocked(spell_id, glm::vec3(GetPosition()))){ + Log.Out(Logs::Detail, Logs::Spells, "GM Cast Blocked Spell: %s (ID %i)", GetSpellName(spell_id), spell_id); + } + } + if ( this->IsClient() && @@ -2048,14 +2068,14 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 return(false); } if (isproc) { - SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, true); + SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, true, level_override); } else { if (spells[spell_id].targettype == ST_TargetOptional){ if (!TrySpellProjectile(spell_target, spell_id)) return false; } - else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) { + else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false, level_override)) { if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) { // Prevent mana usage/timers being set for beneficial buffs if(casting_spell_type == 1) @@ -2149,7 +2169,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 // caster if they're not using TGB // NOTE: this will always hit the caster, plus the target's group so // it can affect up to 7 people if the targeted group is not our own - + // Allow pets who cast group spells to affect the group. if (spell_target->IsPetOwnerClient() && IsPetOwnerClient()){ Mob* owner = spell_target->GetOwner(); @@ -2157,7 +2177,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 if (owner) spell_target = owner; } - + if(spell_target->IsGrouped()) { Group *target_group = entity_list.GetGroupByMob(spell_target); @@ -2252,7 +2272,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } //set our reuse timer on long ass reuse_time spells... - if(IsClient()) + if(IsClient() && !isproc) { if(spell_id == casting_spell_id && casting_spell_timer != 0xFFFFFFFF) { @@ -2560,7 +2580,7 @@ void Mob::BardPulse(uint16 spell_id, Mob *caster) { cd->source = action->source; cd->type = DamageTypeSpell; cd->spellid = action->spell; - cd->sequence = action->sequence; + cd->meleepush_xy = action->sequence; cd->damage = 0; if(!IsEffectInSpell(spell_id, SE_BindAffinity)) { @@ -2612,84 +2632,85 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste castlevel = caster_level_override; int res = CalcBuffDuration_formula(castlevel, formula, duration); + if (caster == target && (target->aabonuses.IllusionPersistence || target->spellbonuses.IllusionPersistence || + target->itembonuses.IllusionPersistence) && + IsEffectInSpell(spell_id, SE_Illusion)) + res = 10000; // ~16h override res = mod_buff_duration(res, caster, target, spell_id); Log.Out(Logs::Detail, Logs::Spells, "Spell %d: Casting level %d, formula %d, base_duration %d: result %d", spell_id, castlevel, formula, duration, res); - return(res); + return res; } // the generic formula calculations int CalcBuffDuration_formula(int level, int formula, int duration) { - int i; // temp variable + int temp; - switch(formula) - { - case 0: // not a buff - return 0; - - case 1: - i = (int)ceil(level / 2.0f); - return i < duration ? (i < 1 ? 1 : i) : duration; - - case 2: - i = (int)ceil(duration / 5.0f * 3); - return i < duration ? (i < 1 ? 1 : i) : duration; - - case 3: - i = level * 30; - return i < duration ? (i < 1 ? 1 : i) : duration; - - case 4: // only used by 'LowerElement' - return ((duration != 0) ? duration : 50); - - case 5: - i = duration; - // 0 value results in a 3 tick spell, else its between 1-3 ticks. - return i < 3 ? (i < 1 ? 3 : i) : 3; - - case 6: - i = (int)ceil(level / 2.0f); - return i < duration ? (i < 1 ? 1 : i) : duration; - - case 7: - i = level; - return (duration == 0) ? (i < 1 ? 1 : i) : duration; - - case 8: - i = level + 10; - return i < duration ? (i < 1 ? 1 : i) : duration; - - case 9: - i = level * 2 + 10; - return i < duration ? (i < 1 ? 1 : i) : duration; - - case 10: - i = level * 3 + 10; - return i < duration ? (i < 1 ? 1 : i) : duration; - - case 11: - return std::min((level + 3) * 30, duration); - - case 12: - return duration; - - case 15: // Don't know what the real formula for this should be. Used by Skinspikes potion. - return duration; - - case 50: // lucy says this is unlimited? - return 72000; // 5 days - - case 3600: - return duration ? duration : 3600; - - default: - Log.Out(Logs::General, Logs::None, "CalcBuffDuration_formula: unknown formula %d", formula); + switch (formula) { + case 1: + temp = level > 3 ? level / 2 : 1; + break; + case 2: + temp = level > 3 ? level / 2 + 5 : 6; + break; + case 3: + temp = 30 * level; + break; + case 4: // only used by 'LowerElement' + temp = 50; + break; + case 5: + temp = 2; + break; + case 6: + temp = level / 2 + 2; + break; + case 7: + temp = level; + break; + case 8: + temp = level + 10; + break; + case 9: + temp = 2 * level + 10; + break; + case 10: + temp = 3 * level + 10; + break; + case 11: + temp = 30 * (level + 3); + break; + case 12: + temp = level > 7 ? level / 4 : 1; + break; + case 13: + temp = 4 * level + 10; + break; + case 14: + temp = 5 * (level + 2); + break; + case 15: + temp = 10 * (level + 10); + break; + case 50: // Permanent. Cancelled by casting/combat for perm invis, non-lev zones for lev, curing poison/curse + // counters, etc. + return -1; + case 51: // Permanent. Cancelled when out of range of aura. + return -4; + default: + // the client function has another bool parameter that if true returns -2 -- unsure + if (formula < 200) return 0; + temp = formula; + break; } + if (duration && duration < temp) + temp = duration; + return temp; } // helper function for AddBuff to determine stacking @@ -3052,7 +3073,7 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid if (duration == 0) { duration = CalcBuffDuration(caster, this, spell_id); - if (caster) + if (caster && duration > 0) // negatives are perma buffs duration = caster->GetActSpellDuration(spell_id, duration); } @@ -3175,6 +3196,7 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid buffs[emptyslot].dot_rune = 0; buffs[emptyslot].ExtraDIChance = 0; buffs[emptyslot].RootBreakChance = 0; + buffs[emptyslot].instrument_mod = caster ? caster->GetInstrumentMod(spell_id) : 10; if (level_override > 0) { buffs[emptyslot].UpdateClient = true; @@ -3278,7 +3300,8 @@ int Mob::CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite) // and if you don't want effects just return false. interrupting here will // break stuff // -bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_resist_adjust, int16 resist_adjust, bool isproc) +bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_resist_adjust, int16 resist_adjust, + bool isproc, int level_override) { // well we can't cast a spell on target without a target @@ -3310,7 +3333,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r if(!IsValidSpell(spell_id)) return false; - uint16 caster_level = GetCasterLevel(spell_id); + uint16 caster_level = level_override > 0 ? level_override : GetCasterLevel(spell_id); Log.Out(Logs::Detail, Logs::Spells, "Casting spell %d on %s with effective caster level %d", spell_id, spelltar->GetName(), caster_level); @@ -3650,9 +3673,9 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r if(IsResistableSpell(spell_id)) { if (IsCharmSpell(spell_id) || IsMezSpell(spell_id) || IsFearSpell(spell_id)) - spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resisttype, spell_id, this, use_resist_adjust, resist_adjust,true); + spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resisttype, spell_id, this, use_resist_adjust, resist_adjust, true, false, false, level_override); else - spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resisttype, spell_id, this, use_resist_adjust, resist_adjust); + spell_effectiveness = spelltar->ResistSpell(spells[spell_id].resisttype, spell_id, this, use_resist_adjust, resist_adjust, false, false, false, level_override); if(spell_effectiveness < 100) { @@ -3669,22 +3692,16 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name); } - if(spelltar->IsAIControlled()){ + if (spelltar->IsAIControlled()) { int32 aggro = CheckAggroAmount(spell_id); - if(aggro > 0) { - if(!IsHarmonySpell(spell_id)) - spelltar->AddToHateList(this, aggro); - else - if(!spelltar->PassCharismaCheck(this, spell_id)) - spelltar->AddToHateList(this, aggro); - } - else{ - uint32 newhate = spelltar->GetHateAmount(this) + aggro; - if (newhate < 1) { - spelltar->SetHateAmountOnEnt(this,1); - } else { - spelltar->SetHateAmountOnEnt(this,newhate); - } + if (aggro > 0) { + if (!IsHarmonySpell(spell_id)) + spelltar->AddToHateList(this, aggro); + else if (!spelltar->PassCharismaCheck(this, spell_id)) + spelltar->AddToHateList(this, aggro); + } else { + int newhate = spelltar->GetHateAmount(this) + aggro; + spelltar->SetHateAmountOnEnt(this, std::max(1, newhate)); } } @@ -3731,7 +3748,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r } // cause the effects to the target - if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness)) + if(!spelltar->SpellEffect(this, spell_id, spell_effectiveness, level_override)) { // if SpellEffect returned false there's a problem applying the // spell. It's most likely a buff that can't stack. @@ -3830,7 +3847,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r cd->source = action->source; cd->type = action->type; cd->spellid = action->spell; - cd->sequence = action->sequence; + cd->meleepush_xy = action->sequence; cd->damage = 0; if(!IsEffectInSpell(spell_id, SE_BindAffinity)) { @@ -4199,7 +4216,7 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster) // pvp_resist_base // pvp_resist_calc // pvp_resist_cap -float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override, int resist_override, bool CharismaCheck, bool CharmTick, bool IsRoot) +float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override, int resist_override, bool CharismaCheck, bool CharmTick, bool IsRoot, int level_override) { if(!caster) @@ -4342,18 +4359,19 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use int level_mod = 0; //Adjust our resist chance based on level modifiers - int temp_level_diff = GetLevel() - caster->GetLevel(); + uint8 caster_level = level_override > 0 ? level_override : caster->GetLevel(); + int temp_level_diff = GetLevel() - caster_level; //Physical Resists are calclated using their own formula derived from extensive parsing. if (resist_type == RESIST_PHYSICAL) { - level_mod = ResistPhysical(temp_level_diff, caster->GetLevel()); + level_mod = ResistPhysical(temp_level_diff, caster_level); } else { if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff)) { - int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel(); + int a = (RuleI(Casting,ResistFalloff)-1) - caster_level; if(a > 0) { temp_level_diff = a; @@ -4380,7 +4398,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use level_mod = -level_mod; } - if(IsNPC() && (caster->GetLevel() - GetLevel()) < -20) + if(IsNPC() && (caster_level - GetLevel()) < -20) { level_mod = 1000; } @@ -4391,7 +4409,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use int level_diff; if(GetLevel() >= RuleI(Casting,ResistFalloff)) { - level_diff = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel(); + level_diff = (RuleI(Casting,ResistFalloff)-1) - caster_level; if(level_diff < 0) { level_diff = 0; @@ -4399,12 +4417,12 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } else { - level_diff = GetLevel() - caster->GetLevel(); + level_diff = GetLevel() - caster_level; } level_mod += (2 * level_diff); } } - + if (CharismaCheck) { /* @@ -4510,14 +4528,14 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if(IsNPC()) { - if(GetLevel() > caster->GetLevel() && GetLevel() >= 17 && caster->GetLevel() <= 50) + if(GetLevel() > caster_level && GetLevel() >= 17 && caster_level <= 50) { partial_modifier += 5; } - if(GetLevel() >= 30 && caster->GetLevel() < 50) + if(GetLevel() >= 30 && caster_level < 50) { - partial_modifier += (caster->GetLevel() - 25); + partial_modifier += (caster_level - 25); } if(GetLevel() < 15) @@ -4528,9 +4546,9 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use if(caster->IsNPC()) { - if((GetLevel() - caster->GetLevel()) >= 20) + if((GetLevel() - caster_level) >= 20) { - partial_modifier += (GetLevel() - caster->GetLevel()) * 1.5; + partial_modifier += (GetLevel() - caster_level) * 1.5; } } @@ -4708,7 +4726,7 @@ void Mob::Stun(int duration) { stunned = true; stunned_timer.Start(duration); - SendStunAppearance(); + SendAddPlayerState(PlayerState::Stunned); } } @@ -4716,6 +4734,7 @@ void Mob::UnStun() { if(stunned && stunned_timer.Enabled()) { stunned = false; stunned_timer.Disable(); + SendRemovePlayerState(PlayerState::Stunned); } } @@ -4745,14 +4764,12 @@ void Client::UnStun() { void NPC::Stun(int duration) { Mob::Stun(duration); - SetRunAnimSpeed(0); - SendPosition(); + SetCurrentSpeed(0); } void NPC::UnStun() { Mob::UnStun(); - SetRunAnimSpeed(static_cast(GetRunspeed())); - SendPosition(); + SetCurrentSpeed(GetRunspeed()); } void Mob::Mesmerize() @@ -5053,7 +5070,7 @@ bool Mob::FindType(uint16 type, bool bOffensive, uint16 threshold) { spells[buffs[i].spellid].base[j], spells[buffs[i].spellid].max[j], buffs[i].casterlevel, buffs[i].spellid); - Log.Out(Logs::General, Logs::Normal, + Log.Out(Logs::General, Logs::Normal, "FindType: type = %d; value = %d; threshold = %d", type, value, threshold); if (value < threshold) @@ -5091,7 +5108,7 @@ bool Mob::IsCombatProc(uint16 spell_id) { return false; } -bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id) { +bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 base_spell_id, int level_override) { if(spell_id == SPELL_UNKNOWN) return(false); @@ -5102,6 +5119,7 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b PermaProcs[i].spellID = spell_id; PermaProcs[i].chance = iChance; PermaProcs[i].base_spellID = base_spell_id; + PermaProcs[i].level_override = level_override; Log.Out(Logs::Detail, Logs::Spells, "Added permanent proc spell %d with chance %d to slot %d", spell_id, iChance, i); return true; @@ -5114,6 +5132,7 @@ bool Mob::AddProcToWeapon(uint16 spell_id, bool bPerma, uint16 iChance, uint16 b SpellProcs[i].spellID = spell_id; SpellProcs[i].chance = iChance; SpellProcs[i].base_spellID = base_spell_id;; + SpellProcs[i].level_override = level_override; Log.Out(Logs::Detail, Logs::Spells, "Added spell-granted proc spell %d with chance %d to slot %d", spell_id, iChance, i); return true; } @@ -5129,6 +5148,7 @@ bool Mob::RemoveProcFromWeapon(uint16 spell_id, bool bAll) { SpellProcs[i].spellID = SPELL_UNKNOWN; SpellProcs[i].chance = 0; SpellProcs[i].base_spellID = SPELL_UNKNOWN; + SpellProcs[i].level_override = -1; Log.Out(Logs::Detail, Logs::Spells, "Removed proc %d from slot %d", spell_id, i); } } @@ -5254,8 +5274,31 @@ void Client::SendBuffDurationPacket(Buffs_Struct &buff) sbf->slot = 2; sbf->spellid = buff.spellid; sbf->slotid = 0; - sbf->effect = 255; sbf->level = buff.casterlevel > 0 ? buff.casterlevel : GetLevel(); + + if (IsEffectInSpell(buff.spellid, SE_TotalHP)) + { + // If any of the lower 6 bits are set, the GUI changes MAX_HP AGAIN. + // If its set to 0 the effect is cancelled. + // 128 seems to work (ie: change only duration). + sbf->effect = 128; + } + else if (IsEffectInSpell(buff.spellid, SE_CurrentHP)) + { + // This is mostly a problem when we try and update duration on a + // dot or a hp->mana conversion. Zero cancels the effect, any + // other value has the GUI doing that value at the same time server + // is doing theirs. This makes the two match. + int index = GetSpellEffectIndex(buff.spellid, SE_CurrentHP); + sbf->effect = abs(spells[buff.spellid].base[index]); + } + else + { + // Default to what old code did until we find a better fix for + // other spell lines. + sbf->effect=sbf->level; + } + sbf->bufffade = 0; sbf->duration = buff.ticsremaining; sbf->num_hits = buff.numhits; @@ -5353,6 +5396,12 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target) buff->entity_id = GetID(); buff->count = count; buff->all_buffs = 1; + // there are more types, the client doesn't seem to really care though. The others are also currently hard to fill in here ... + // (see comment in common/eq_packet_structs.h) + if (for_target) + buff->type = IsClient() ? 5 : 7; + else + buff->type = 0; uint32 index = 0; for(unsigned int i = 0; i < buff_count; ++i) diff --git a/zone/string_ids.h b/zone/string_ids.h index ef758dba6..19d7436d0 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -231,6 +231,17 @@ #define MISSED_NOTE_OTHER 1219 //A missed note brings %1's song to a close! #define SPELL_LEVEL_REQ 1226 //This spell only works on people who are level %1 and under. #define CORPSE_DECAY_NOW 1227 //This corpse is waiting to expire. +#define CORPSE_ITEM_LOST 1228 //Your items will no longer stay with you when you respawn on death. You will now need to return to your corpse for your items. +#define CORPSE_EXP_LOST 1229 //You will now lose experience when you die. +#define FLICKERS_PALE_LIGHT 1230 //Your %1 flickers with a pale light. +#define PULSES_WITH_LIGHT 1231 //Your %1 pulses with light as your vision sharpens. +#define FEEDS_WITH_POWER 1232 //Your %1 feeds you with power. +#define POWER_DRAIN_INTO 1233 //You feel your power drain into your %1. +#define SEEMS_DRAINED 1234 //Your %1 seems drained of power. +#define ALIVE_WITH_POWER 1235 //Your %1 feels alive with power. +#define SPARKLES 1236 //Your %1 sparkles. +#define GROWS_DIM 1237 //Your %1 grows dim. +#define BEGINS_TO_SHINE 1238 //Your %1 begins to shine. #define SURNAME_REJECTED 1374 //Your new surname was rejected. Please try a different name. #define DUEL_DECLINE 1383 //%1 has declined your challenge to duel to the death. #define DUEL_ACCEPTED 1384 //%1 has already accepted a duel with someone else. @@ -339,6 +350,8 @@ #define YOU_HEAL 9068 //You have healed %1 for %2 points of damage. #define YOUR_HIT_DOT 9072 //%1 has taken %2 damage from your %3. #define HIT_NON_MELEE 9073 //%1 hit %2 for %3 points of non-melee damage. +#define GLOWS_BLUE 9074 //Your %1 glows blue. +#define GLOWS_RED 9075 //Your %1 glows red. #define SHAKE_OFF_STUN 9077 #define STRIKETHROUGH_STRING 9078 //You strike through your opponent's defenses! #define SPELL_REFLECT 9082 //%1's spell has been reflected by %2. @@ -373,6 +386,7 @@ #define GROUP_INVITEE_SELF 12270 //12270 You cannot invite yourself. #define NOT_IN_CONTROL 12368 //You do not have control of yourself right now. #define ALREADY_CASTING 12442 //You are already casting a spell! +#define SHIMMERS_BRIEFLY 12444 //Your %1 shimmers briefly. #define SENSE_CORPSE_NOT_NAME 12446 //You don't sense any corpses of that name. #define SENSE_CORPSE_NONE 12447 //You don't sense any corpses. #define SCREECH_BUFF_BLOCK 12448 //Your immunity buff protected you from the spell %1! diff --git a/zone/tradeskills.cpp b/zone/tradeskills.cpp index 750414fe3..37e4189bd 100644 --- a/zone/tradeskills.cpp +++ b/zone/tradeskills.cpp @@ -1561,6 +1561,8 @@ bool ZoneDatabase::EnableRecipe(uint32 recipe_id) if (!results.Success()) return results.RowsAffected() > 0; + + return false; } bool ZoneDatabase::DisableRecipe(uint32 recipe_id) @@ -1571,4 +1573,6 @@ bool ZoneDatabase::DisableRecipe(uint32 recipe_id) if (!results.Success()) return results.RowsAffected() > 0; + + return false; } diff --git a/zone/trap.cpp b/zone/trap.cpp index 9462e658a..1cce48e19 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -192,7 +192,7 @@ void Trap::Trigger(Mob* trigger) int dmg = zone->random.Int(effectvalue, effectvalue2); trigger->SetHP(trigger->GetHP() - dmg); a->damage = dmg; - a->sequence = zone->random.Int(0, 1234567); + a->meleepush_xy = zone->random.Int(0, 1234567); a->source = GetHiddenTrigger()!=nullptr ? GetHiddenTrigger()->GetID() : trigger->GetID(); a->spellid = 0; a->target = trigger->GetID(); diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index d3b85bf03..7570165a3 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -394,6 +394,7 @@ void NPC::SetWaypointPause() if (cur_wp_pause == 0) { AIwalking_timer->Start(100); + AIwalking_timer->Trigger(); } else { @@ -436,9 +437,8 @@ void NPC::NextGuardPosition() { { if(moved) { - moved=false; - SetMoving(false); - SendPosition(); + moved = false; + SetCurrentSpeed(0); } } } @@ -489,10 +489,16 @@ float Mob::CalculateHeadingToTarget(float in_x, float in_y) { return (256*(360-angle)/360.0f); } -bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, bool checkZ) { +bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ) { if(GetID()==0) return true; + if(speed <= 0) + { + SetCurrentSpeed(0); + return true; + } + if ((m_Position.x-x == 0) && (m_Position.y-y == 0)) {//spawn is at target coords if(m_Position.z-z != 0) { m_Position.z = z; @@ -514,6 +520,7 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b return true; } + bool send_update = false; int compare_steps = IsBoat() ? 1 : 20; if(tar_ndx < compare_steps && m_TargetLocation.x==x && m_TargetLocation.y==y) { @@ -586,7 +593,12 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b m_TargetV.x = x - nx; m_TargetV.y = y - ny; m_TargetV.z = z - nz; - + SetCurrentSpeed((int8)speed); + pRunAnimSpeed = speed; + if(IsClient()) + { + animation = speed; + } //pRunAnimSpeed = (int8)(speed*NPC_RUNANIM_RATIO); //speed *= NPC_SPEED_MULTIPLIER; @@ -596,10 +608,10 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b // 2: get unit vector // -------------------------------------------------------------------------- float mag = sqrtf (m_TargetV.x*m_TargetV.x + m_TargetV.y*m_TargetV.y + m_TargetV.z*m_TargetV.z); - tar_vector = speed / mag; + tar_vector = (float)speed / mag; // mob move fix - int numsteps = (int) ( mag * 20 / speed) + 1; + int numsteps = (int) ( mag * 16.0f / (float)speed); // mob move fix @@ -609,9 +621,9 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b if (numsteps>1) { tar_vector=1.0f ; - m_TargetV.x = m_TargetV.x/numsteps; - m_TargetV.y = m_TargetV.y/numsteps; - m_TargetV.z = m_TargetV.z/numsteps; + m_TargetV.x = 1.25f * m_TargetV.x/(float)numsteps; + m_TargetV.y = 1.25f * m_TargetV.y/(float)numsteps; + m_TargetV.z = 1.25f *m_TargetV.z/(float)numsteps; float new_x = m_Position.x + m_TargetV.x; float new_y = m_Position.y + m_TargetV.y; @@ -643,7 +655,7 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b } else { - tar_vector/=20; + tar_vector/=16.0f; float new_x = m_Position.x + m_TargetV.x*tar_vector; float new_y = m_Position.y + m_TargetV.y*tar_vector; @@ -699,25 +711,27 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, float speed, b m_Delta = glm::vec4(m_Position.x - nx, m_Position.y - ny, m_Position.z - nz, 0.0f); if (IsClient()) + { SendPosUpdate(1); + CastToClient()->ResetPositionTimer(); + } else + { + // force an update now + move_tic_count = RuleI(Zone, NPCPositonUpdateTicCount); SendPosUpdate(); + SetAppearance(eaStanding, false); + } - SetAppearance(eaStanding, false); pLastChange = Timer::GetCurrentTime(); return true; } -bool Mob::CalculateNewPosition2(float x, float y, float z, float speed, bool checkZ) { - if(IsNPC() || IsClient() || IsPet()) { - pRunAnimSpeed = (int8)(speed*NPC_RUNANIM_RATIO); - speed *= NPC_SPEED_MULTIPLIER; - } - +bool Mob::CalculateNewPosition2(float x, float y, float z, int speed, bool checkZ, bool calcHeading) { return MakeNewPositionAndSendUpdate(x, y, z, speed, checkZ); } -bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool checkZ) { +bool Mob::CalculateNewPosition(float x, float y, float z, int speed, bool checkZ, bool calcHeading) { if(GetID()==0) return true; @@ -726,14 +740,12 @@ bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool chec float nz = m_Position.z; // if NPC is rooted - if (speed == 0.0) { + if (speed == 0) { SetHeading(CalculateHeadingToTarget(x, y)); if(moved){ - SendPosition(); - SetMoving(false); + SetCurrentSpeed(0); moved=false; } - SetRunAnimSpeed(0); Log.Out(Logs::Detail, Logs::AI, "Rooted while calculating new position to (%.3f, %.3f, %.3f)", x, y, z); return true; } @@ -745,8 +757,8 @@ bool Mob::CalculateNewPosition(float x, float y, float z, float speed, bool chec if (m_TargetV.x == 0 && m_TargetV.y == 0) return false; - pRunAnimSpeed = (uint8)(speed*NPC_RUNANIM_RATIO); - speed *= NPC_SPEED_MULTIPLIER; + SetCurrentSpeed((int8)(speed)); //*NPC_RUNANIM_RATIO); + //speed *= NPC_SPEED_MULTIPLIER; Log.Out(Logs::Detail, Logs::AI, "Calculating new position to (%.3f, %.3f, %.3f) vector (%.3f, %.3f, %.3f) rate %.3f RAS %d", x, y, z, m_TargetV.x, m_TargetV.y, m_TargetV.z, speed, pRunAnimSpeed); @@ -1039,7 +1051,7 @@ void ZoneDatabase::AssignGrid(Client *client, int grid, int spawn2id) { if (!results.Success()) return; - + if (results.RowsAffected() != 1) { return; } diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index ec2893e05..517106aca 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -742,32 +742,36 @@ void WorldServer::Process() { break; } case ServerOP_SyncWorldTime: { - if(zone!=0) { + if (zone != 0 && !zone->is_zone_time_localized) { Log.Out(Logs::Moderate, Logs::Zone_Server, "%s Received Message SyncWorldTime", __FUNCTION__); - eqTimeOfDay* newtime = (eqTimeOfDay*) pack->pBuffer; - zone->zone_time.setEQTimeOfDay(newtime->start_eqtime, newtime->start_realtime); + + eqTimeOfDay* newtime = (eqTimeOfDay*)pack->pBuffer; + zone->zone_time.SetCurrentEQTimeOfDay(newtime->start_eqtime, newtime->start_realtime); EQApplicationPacket* outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); - TimeOfDay_Struct* tod = (TimeOfDay_Struct*)outapp->pBuffer; - zone->zone_time.getEQTimeOfDay(time(0), tod); + TimeOfDay_Struct* time_of_day = (TimeOfDay_Struct*)outapp->pBuffer; + zone->zone_time.GetCurrentEQTimeOfDay(time(0), time_of_day); entity_list.QueueClients(0, outapp, false); safe_delete(outapp); - //TEST - char timeMessage[255]; - time_t timeCurrent = time(nullptr); - TimeOfDay_Struct eqTime; - zone->zone_time.getEQTimeOfDay( timeCurrent, &eqTime); - //if ( eqTime.hour >= 0 && eqTime.minute >= 0 ) - //{ - sprintf(timeMessage,"EQTime [%02d:%s%d %s]", - ((eqTime.hour - 1) % 12) == 0 ? 12 : ((eqTime.hour - 1) % 12), - (eqTime.minute < 10) ? "0" : "", - eqTime.minute, - (eqTime.hour >= 13) ? "pm" : "am" - ); - Log.Out(Logs::General, Logs::Zone_Server, "Time Broadcast Packet: %s", timeMessage); - zone->GotCurTime(true); - //} - //Test + + /* Buffer garbage to generate debug message */ + char time_message[255]; + time_t current_time = time(nullptr); + TimeOfDay_Struct eq_time; + zone->zone_time.GetCurrentEQTimeOfDay(current_time, &eq_time); + + sprintf(time_message, "EQTime [%02d:%s%d %s]", + ((eq_time.hour - 1) % 12) == 0 ? 12 : ((eq_time.hour - 1) % 12), + (eq_time.minute < 10) ? "0" : "", + eq_time.minute, + (eq_time.hour >= 13) ? "pm" : "am" + ); + + Log.Out(Logs::General, Logs::Zone_Server, "Time Broadcast Packet: %s", time_message); + zone->SetZoneHasCurrentTime(true); + + } + if (zone->is_zone_time_localized){ + Log.Out(Logs::General, Logs::Zone_Server, "Received request to sync time from world, but our time is localized currently"); } break; } @@ -1406,17 +1410,16 @@ void WorldServer::Process() { // CONSENT_INVALID_NAME = 397 // TARGET_NOT_FOUND = 101 - safe_delete(pack); - pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); - ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)pack->pBuffer; + ServerPacket *scs_pack = new ServerPacket(ServerOP_Consent_Response, sizeof(ServerOP_Consent_Struct)); + ServerOP_Consent_Struct* scs = (ServerOP_Consent_Struct*)scs_pack->pBuffer; strcpy(scs->grantname, s->grantname); strcpy(scs->ownername, s->ownername); scs->permission = s->permission; scs->zone_id = s->zone_id; scs->instance_id = s->instance_id; scs->message_string_id = TARGET_NOT_FOUND; - worldserver.SendPacket(pack); - safe_delete(pack); + worldserver.SendPacket(scs_pack); + safe_delete(scs_pack); } break; } diff --git a/zone/zone.cpp b/zone/zone.cpp index d5fd2a0ab..469351ea5 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -241,8 +241,8 @@ bool Zone::LoadZoneObjects() { data.object_type = type; data.linked_list_addr[0] = 0; data.linked_list_addr[1] = 0; - data.unknown008 = (uint32)atoi(row[11]); - data.unknown010 = (uint32)atoi(row[12]); + data.size = (uint32)atoi(row[11]); + data.solidtype = (uint32)atoi(row[12]); data.unknown020 = (uint32)atoi(row[13]); data.unknown024 = (uint32)atoi(row[14]); data.unknown076 = (uint32)atoi(row[15]); @@ -697,7 +697,7 @@ void Zone::Shutdown(bool quite) Log.Out(Logs::General, Logs::Status, "Zone Shutdown: %s (%i)", zone->GetShortName(), zone->GetZoneID()); petition_list.ClearPetitions(); - zone->GotCurTime(false); + zone->SetZoneHasCurrentTime(false); if (!quite) Log.Out(Logs::General, Logs::Normal, "Zone shutdown: going to sleep"); ZoneLoaded = false; @@ -760,6 +760,8 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) qGlobals = nullptr; default_ruleset = 0; + is_zone_time_localized = false; + loglevelvar = 0; merchantvar = 0; tradevar = 0; @@ -805,7 +807,7 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) totalBS = 0; aas = nullptr; totalAAs = 0; - gottime = false; + zone_has_current_time = false; Instance_Shutdown_Timer = nullptr; bool is_perma = false; @@ -1484,7 +1486,7 @@ void Zone::Repop(uint32 delay) { void Zone::GetTimeSync() { - if (worldserver.Connected() && !gottime) { + if (worldserver.Connected() && !zone_has_current_time) { ServerPacket* pack = new ServerPacket(ServerOP_GetWorldTime, 0); worldserver.SendPacket(pack); safe_delete(pack); @@ -1508,17 +1510,42 @@ void Zone::SetDate(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute } } -void Zone::SetTime(uint8 hour, uint8 minute) +void Zone::SetTime(uint8 hour, uint8 minute, bool update_world /*= true*/) { if (worldserver.Connected()) { ServerPacket* pack = new ServerPacket(ServerOP_SetWorldTime, sizeof(eqTimeOfDay)); - eqTimeOfDay* eqtod = (eqTimeOfDay*)pack->pBuffer; - zone_time.getEQTimeOfDay(time(0), &eqtod->start_eqtime); - eqtod->start_eqtime.minute=minute; - eqtod->start_eqtime.hour=hour; - eqtod->start_realtime=time(0); - printf("Setting master time on world server to: %d:%d (%d)\n", hour, minute, (int)eqtod->start_realtime); - worldserver.SendPacket(pack); + eqTimeOfDay* eq_time_of_day = (eqTimeOfDay*)pack->pBuffer; + + zone_time.GetCurrentEQTimeOfDay(time(0), &eq_time_of_day->start_eqtime); + + eq_time_of_day->start_eqtime.minute = minute; + eq_time_of_day->start_eqtime.hour = hour; + eq_time_of_day->start_realtime = time(0); + + /* By Default we update worlds time, but we can optionally no update world which updates the rest of the zone servers */ + if (update_world){ + Log.Out(Logs::General, Logs::Zone_Server, "Setting master time on world server to: %d:%d (%d)\n", hour, minute, (int)eq_time_of_day->start_realtime); + worldserver.SendPacket(pack); + + /* Set Time Localization Flag */ + zone->is_zone_time_localized = false; + } + /* When we don't update world, we are localizing ourselves, we become disjointed from normal syncs and set time locally */ + else{ + + Log.Out(Logs::General, Logs::Zone_Server, "Setting zone localized time..."); + + zone->zone_time.SetCurrentEQTimeOfDay(eq_time_of_day->start_eqtime, eq_time_of_day->start_realtime); + EQApplicationPacket* outapp = new EQApplicationPacket(OP_TimeOfDay, sizeof(TimeOfDay_Struct)); + TimeOfDay_Struct* time_of_day = (TimeOfDay_Struct*)outapp->pBuffer; + zone->zone_time.GetCurrentEQTimeOfDay(time(0), time_of_day); + entity_list.QueueClients(0, outapp, false); + safe_delete(outapp); + + /* Set Time Localization Flag */ + zone->is_zone_time_localized = true; + } + safe_delete(pack); } } @@ -1900,7 +1927,7 @@ bool Zone::IsSpellBlocked(uint32 spell_id, const glm::vec3& location) } case 2: { - if (!IsWithinAxisAlignedBox(location, blocked_spells[x].m_Location - blocked_spells[x].m_Difference, blocked_spells[x].m_Location + blocked_spells[x].m_Difference)) + if (IsWithinAxisAlignedBox(location, blocked_spells[x].m_Location - blocked_spells[x].m_Difference, blocked_spells[x].m_Location + blocked_spells[x].m_Difference)) return true; break; } @@ -1934,7 +1961,7 @@ const char* Zone::GetSpellBlockedMessage(uint32 spell_id, const glm::vec3& locat } case 2: { - if(!IsWithinAxisAlignedBox(location, blocked_spells[x].m_Location - blocked_spells[x].m_Difference, blocked_spells[x].m_Location + blocked_spells[x].m_Difference)) + if(IsWithinAxisAlignedBox(location, blocked_spells[x].m_Location - blocked_spells[x].m_Difference, blocked_spells[x].m_Location + blocked_spells[x].m_Difference)) return blocked_spells[x].message; break; } @@ -2209,9 +2236,9 @@ void Zone::ReloadWorld(uint32 Option){ entity_list.ClearAreas(); parse->ReloadQuests(); } else if(Option == 1) { - zone->Repop(0); entity_list.ClearAreas(); parse->ReloadQuests(); + zone->Repop(0); } } diff --git a/zone/zone.h b/zone/zone.h index 80e0473f2..7b1b855a7 100644 --- a/zone/zone.h +++ b/zone/zone.h @@ -43,6 +43,7 @@ struct ZonePoint int32 target_zone_instance; uint32 client_version_mask; }; + struct ZoneClientAuth_Struct { uint32 ip; // client's IP address uint32 wid; // client's WorldID# @@ -85,6 +86,10 @@ public: Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name); ~Zone(); + + /* When zone has its own version of time */ + bool is_zone_time_localized; + bool Init(bool iStaticZone); bool LoadZoneCFG(const char* filename, uint16 instance_id, bool DontLoadDefault = false); bool SaveZoneCFG(); @@ -153,7 +158,7 @@ public: inline bool InstantGrids() { return(!initgrids_timer.Enabled()); } void SetStaticZone(bool sz) { staticzone = sz; } inline bool IsStaticZone() { return staticzone; } - inline void GotCurTime(bool time) { gottime = time; } + inline void SetZoneHasCurrentTime(bool time) { zone_has_current_time = time; } void SpawnConditionChanged(const SpawnCondition &c, int16 old_value); @@ -206,7 +211,7 @@ public: EQTime zone_time; void GetTimeSync(); void SetDate(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute); - void SetTime(uint8 hour, uint8 minute); + void SetTime(uint8 hour, uint8 minute, bool update_world = true); void weatherSend(); bool CanBind() const { return(can_bind); } @@ -319,7 +324,7 @@ private: bool staticzone; - bool gottime; + bool zone_has_current_time; uint32 pQueuedMerchantsWorkID; uint32 pQueuedTempMerchantsWorkID; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 46bddf25a..49c09ecd9 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -196,7 +196,7 @@ bool ZoneDatabase::GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct zone_data->time_type = atoi(row[30]); //not in the DB yet: - zone_data->gravity = atof(row[56]); + zone_data->gravity = atof(row[56]); Log.Out(Logs::General, Logs::Debug, "Zone Gravity is %f", zone_data->gravity); allow_mercs = true; @@ -248,7 +248,7 @@ void ZoneDatabase::UpdateRespawnTime(uint32 spawn2_id, uint16 instance_id, uint3 if(time_left == 0) { std::string query = StringFormat("DELETE FROM `respawn_times` WHERE `id` = %u AND `instance_id` = %u", spawn2_id, instance_id); - QueryDatabase(query); + QueryDatabase(query); return; } @@ -258,14 +258,14 @@ void ZoneDatabase::UpdateRespawnTime(uint32 spawn2_id, uint16 instance_id, uint3 "start, " "duration, " "instance_id) " - "VALUES " + "VALUES " "(%u, " "%u, " "%u, " "%u)", - spawn2_id, + spawn2_id, current_time, - time_left, + time_left, instance_id ); QueryDatabase(query); @@ -1054,8 +1054,8 @@ bool ZoneDatabase::LoadCharacterLanguages(uint32 character_id, PlayerProfile_Str for (i = 0; i < MAX_PP_LANGUAGE; ++i) pp->languages[i] = 0; - for (auto row = results.begin(); row != results.end(); ++row) { - i = atoi(row[0]); + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); if (i < MAX_PP_LANGUAGE){ pp->languages[i] = atoi(row[1]); } @@ -1102,14 +1102,14 @@ bool ZoneDatabase::LoadCharacterSkills(uint32 character_id, PlayerProfile_Struct "FROM " "`character_skills` " "WHERE `id` = %u ORDER BY `skill_id`", character_id); - auto results = database.QueryDatabase(query); + auto results = database.QueryDatabase(query); int i = 0; /* Initialize Skill */ for (i = 0; i < MAX_PP_SKILL; ++i) pp->skills[i] = 0; - for (auto row = results.begin(); row != results.end(); ++row) { - i = atoi(row[0]); + for (auto row = results.begin(); row != results.end(); ++row) { + i = atoi(row[0]); if (i < MAX_PP_SKILL) pp->skills[i] = atoi(row[1]); } @@ -1260,7 +1260,7 @@ bool ZoneDatabase::LoadCharacterPotions(uint32 character_id, PlayerProfile_Struc bool ZoneDatabase::LoadCharacterBindPoint(uint32 character_id, PlayerProfile_Struct* pp){ std::string query = StringFormat("SELECT `zone_id`, `instance_id`, `x`, `y`, `z`, `heading`, `is_home` FROM `character_bind` WHERE `id` = %u LIMIT 2", character_id); - auto results = database.QueryDatabase(query); + auto results = database.QueryDatabase(query); for (auto row = results.begin(); row != results.end(); ++row) { @@ -1706,15 +1706,15 @@ bool ZoneDatabase::SaveCharacterCurrency(uint32 character_id, PlayerProfile_Stru pp->careerRadCrystals, pp->currentEbonCrystals, pp->careerEbonCrystals); - auto results = database.QueryDatabase(query); - Log.Out(Logs::General, Logs::None, "Saving Currency for character ID: %i, done", character_id); + auto results = database.QueryDatabase(query); + Log.Out(Logs::General, Logs::None, "Saving Currency for character ID: %i, done", character_id); return true; } -bool ZoneDatabase::SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level){ - std::string rquery = StringFormat("REPLACE INTO `character_alternate_abilities` (id, aa_id, aa_value)" - " VALUES (%u, %u, %u)", - character_id, aa_id, current_level); +bool ZoneDatabase::SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level, uint32 charges){ + std::string rquery = StringFormat("REPLACE INTO `character_alternate_abilities` (id, aa_id, aa_value, charges)" + " VALUES (%u, %u, %u, %u)", + character_id, aa_id, current_level, charges); auto results = QueryDatabase(rquery); Log.Out(Logs::General, Logs::None, "Saving AA for character ID: %u, aa_id: %u current_level: %u", character_id, aa_id, current_level); return true; @@ -1803,7 +1803,7 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load std::string where_condition = ""; if (bulk_load){ - Log.Out(Logs::General, Logs::Debug, "Performing bulk NPC Types load"); + Log.Out(Logs::General, Logs::Debug, "Performing bulk NPC Types load"); where_condition = StringFormat( "INNER JOIN spawnentry ON npc_types.id = spawnentry.npcID " "INNER JOIN spawn2 ON spawnentry.spawngroupID = spawn2.spawngroupID " @@ -1911,7 +1911,7 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load "npc_types.handtexture, " "npc_types.legtexture, " "npc_types.feettexture " - "FROM npc_types %s", + "FROM npc_types %s", where_condition.c_str() ); @@ -2184,7 +2184,7 @@ const NPCType* ZoneDatabase::GetMercType(uint32 id, uint16 raceid, uint32 client return nullptr; } - const NPCType *npc; + const NPCType *npc = nullptr; // Process each row returned. for (auto row = results.begin(); row != results.end(); ++row) { @@ -2370,7 +2370,7 @@ bool ZoneDatabase::LoadCurrentMerc(Client *client) { if(!results.Success()) return false; - + if(results.RowCount() == 0) return false; @@ -2505,7 +2505,7 @@ void ZoneDatabase::SaveMercBuffs(Merc *merc) { "TicsRemaining, PoisonCounters, DiseaseCounters, CurseCounters, " "CorruptionCounters, HitCount, MeleeRune, MagicRune, dot_rune, " "caston_x, Persistent, caston_y, caston_z, ExtraDIChance) " - "VALUES (%u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %i, %u, %i, %i, %i);", + "VALUES (%u, %u, %u, %u, %u, %d, %u, %u, %u, %u, %u, %u, %u, %i, %u, %i, %i, %i);", merc->GetMercID(), buffs[buffCount].spellid, buffs[buffCount].casterlevel, spells[buffs[buffCount].spellid].buffdurationformula, buffs[buffCount].ticsremaining, CalculatePoisonCounters(buffs[buffCount].spellid) > 0 ? buffs[buffCount].counters : 0, @@ -2981,47 +2981,50 @@ void ZoneDatabase::SaveBuffs(Client *client) { query = StringFormat("INSERT INTO `character_buffs` (character_id, slot_id, spell_id, " "caster_level, caster_name, ticsremaining, counters, numhits, melee_rune, " - "magic_rune, persistent, dot_rune, caston_x, caston_y, caston_z, ExtraDIChance) " - "VALUES('%u', '%u', '%u', '%u', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', " - "'%i', '%i', '%i', '%i')", client->CharacterID(), index, buffs[index].spellid, + "magic_rune, persistent, dot_rune, caston_x, caston_y, caston_z, ExtraDIChance, " + "instrument_mod) " + "VALUES('%u', '%u', '%u', '%u', '%s', '%d', '%u', '%u', '%u', '%u', '%u', '%u', " + "'%i', '%i', '%i', '%i', '%i')", client->CharacterID(), index, buffs[index].spellid, buffs[index].casterlevel, buffs[index].caster_name, buffs[index].ticsremaining, buffs[index].counters, buffs[index].numhits, buffs[index].melee_rune, buffs[index].magic_rune, buffs[index].persistant_buff, buffs[index].dot_rune, buffs[index].caston_x, buffs[index].caston_y, buffs[index].caston_z, - buffs[index].ExtraDIChance); + buffs[index].ExtraDIChance, buffs[index].instrument_mod); QueryDatabase(query); } } -void ZoneDatabase::LoadBuffs(Client *client) { +void ZoneDatabase::LoadBuffs(Client *client) +{ Buffs_Struct *buffs = client->GetBuffs(); uint32 max_slots = client->GetMaxBuffSlots(); - for(int index = 0; index < max_slots; ++index) + for (int index = 0; index < max_slots; ++index) buffs[index].spellid = SPELL_UNKNOWN; std::string query = StringFormat("SELECT spell_id, slot_id, caster_level, caster_name, ticsremaining, " - "counters, numhits, melee_rune, magic_rune, persistent, dot_rune, " - "caston_x, caston_y, caston_z, ExtraDIChance " - "FROM `character_buffs` WHERE `character_id` = '%u'", client->CharacterID()); - auto results = QueryDatabase(query); - if (!results.Success()) { + "counters, numhits, melee_rune, magic_rune, persistent, dot_rune, " + "caston_x, caston_y, caston_z, ExtraDIChance, instrument_mod " + "FROM `character_buffs` WHERE `character_id` = '%u'", + client->CharacterID()); + auto results = QueryDatabase(query); + if (!results.Success()) { return; - } + } - for (auto row = results.begin(); row != results.end(); ++row) { - uint32 slot_id = atoul(row[1]); - if(slot_id >= client->GetMaxBuffSlots()) + for (auto row = results.begin(); row != results.end(); ++row) { + uint32 slot_id = atoul(row[1]); + if (slot_id >= client->GetMaxBuffSlots()) continue; - uint32 spell_id = atoul(row[0]); - if(!IsValidSpell(spell_id)) - continue; + uint32 spell_id = atoul(row[0]); + if (!IsValidSpell(spell_id)) + continue; - Client *caster = entity_list.GetClientByName(row[3]); + Client *caster = entity_list.GetClientByName(row[3]); uint32 caster_level = atoi(row[2]); - uint32 ticsremaining = atoul(row[4]); + int32 ticsremaining = atoi(row[4]); uint32 counters = atoul(row[5]); uint32 numhits = atoul(row[6]); uint32 melee_rune = atoul(row[7]); @@ -3032,53 +3035,54 @@ void ZoneDatabase::LoadBuffs(Client *client) { int32 caston_y = atoul(row[12]); int32 caston_z = atoul(row[13]); int32 ExtraDIChance = atoul(row[14]); + uint32 instrument_mod = atoul(row[15]); buffs[slot_id].spellid = spell_id; - buffs[slot_id].casterlevel = caster_level; + buffs[slot_id].casterlevel = caster_level; - if(caster) { - buffs[slot_id].casterid = caster->GetID(); - strcpy(buffs[slot_id].caster_name, caster->GetName()); - buffs[slot_id].client = true; - } else { - buffs[slot_id].casterid = 0; + if (caster) { + buffs[slot_id].casterid = caster->GetID(); + strcpy(buffs[slot_id].caster_name, caster->GetName()); + buffs[slot_id].client = true; + } else { + buffs[slot_id].casterid = 0; strcpy(buffs[slot_id].caster_name, ""); buffs[slot_id].client = false; - } + } - buffs[slot_id].ticsremaining = ticsremaining; + buffs[slot_id].ticsremaining = ticsremaining; buffs[slot_id].counters = counters; buffs[slot_id].numhits = numhits; buffs[slot_id].melee_rune = melee_rune; buffs[slot_id].magic_rune = magic_rune; - buffs[slot_id].persistant_buff = persistent? true: false; + buffs[slot_id].persistant_buff = persistent ? true : false; buffs[slot_id].dot_rune = dot_rune; buffs[slot_id].caston_x = caston_x; - buffs[slot_id].caston_y = caston_y; - buffs[slot_id].caston_z = caston_z; - buffs[slot_id].ExtraDIChance = ExtraDIChance; - buffs[slot_id].RootBreakChance = 0; - buffs[slot_id].UpdateClient = false; - - } + buffs[slot_id].caston_y = caston_y; + buffs[slot_id].caston_z = caston_z; + buffs[slot_id].ExtraDIChance = ExtraDIChance; + buffs[slot_id].RootBreakChance = 0; + buffs[slot_id].UpdateClient = false; + buffs[slot_id].instrument_mod = instrument_mod; + } max_slots = client->GetMaxBuffSlots(); - for(int index = 0; index < max_slots; ++index) { - if(!IsValidSpell(buffs[index].spellid)) + for (int index = 0; index < max_slots; ++index) { + if (!IsValidSpell(buffs[index].spellid)) continue; - for(int effectIndex = 0; effectIndex < 12; ++effectIndex) { + for (int effectIndex = 0; effectIndex < 12; ++effectIndex) { if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Charm) { - buffs[index].spellid = SPELL_UNKNOWN; - break; - } + buffs[index].spellid = SPELL_UNKNOWN; + break; + } - if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Illusion) { - if(buffs[index].persistant_buff) - break; + if (spells[buffs[index].spellid].effectid[effectIndex] == SE_Illusion) { + if (buffs[index].persistant_buff) + break; - buffs[index].spellid = SPELL_UNKNOWN; + buffs[index].spellid = SPELL_UNKNOWN; break; } } @@ -3124,16 +3128,16 @@ void ZoneDatabase::SavePetInfo(Client *client) if (query.length() == 0) query = StringFormat("INSERT INTO `character_pet_buffs` " "(`char_id`, `pet`, `slot`, `spell_id`, `caster_level`, " - "`ticsremaining`, `counters`) " - "VALUES (%u, %u, %u, %u, %u, %u, %d)", + "`ticsremaining`, `counters`, `instrument_mod`) " + "VALUES (%u, %u, %u, %u, %u, %d, %d, %u)", client->CharacterID(), pet, index, petinfo->Buffs[index].spellid, petinfo->Buffs[index].level, petinfo->Buffs[index].duration, - petinfo->Buffs[index].counters); + petinfo->Buffs[index].counters, petinfo->Buffs[index].bard_modifier); else - query += StringFormat(", (%u, %u, %u, %u, %u, %u, %d)", + query += StringFormat(", (%u, %u, %u, %u, %u, %d, %d, %u)", client->CharacterID(), pet, index, petinfo->Buffs[index].spellid, petinfo->Buffs[index].level, petinfo->Buffs[index].duration, - petinfo->Buffs[index].counters); + petinfo->Buffs[index].counters, petinfo->Buffs[index].bard_modifier); } database.QueryDatabase(query); query.clear(); @@ -3171,7 +3175,8 @@ void ZoneDatabase::UpdateItemRecastTimestamps(uint32 char_id, uint32 recast_type QueryDatabase(query); } -void ZoneDatabase::LoadPetInfo(Client *client) { +void ZoneDatabase::LoadPetInfo(Client *client) +{ // Load current pet and suspended pet PetInfo *petinfo = client->GetPetInfo(0); @@ -3180,17 +3185,18 @@ void ZoneDatabase::LoadPetInfo(Client *client) { memset(petinfo, 0, sizeof(PetInfo)); memset(suspended, 0, sizeof(PetInfo)); - std::string query = StringFormat("SELECT `pet`, `petname`, `petpower`, `spell_id`, " - "`hp`, `mana`, `size` FROM `character_pet_info` " - "WHERE `char_id` = %u", client->CharacterID()); - auto results = database.QueryDatabase(query); - if(!results.Success()) { + std::string query = StringFormat("SELECT `pet`, `petname`, `petpower`, `spell_id`, " + "`hp`, `mana`, `size` FROM `character_pet_info` " + "WHERE `char_id` = %u", + client->CharacterID()); + auto results = database.QueryDatabase(query); + if (!results.Success()) { return; } - PetInfo *pi; + PetInfo *pi; for (auto row = results.begin(); row != results.end(); ++row) { - uint16 pet = atoi(row[0]); + uint16 pet = atoi(row[0]); if (pet == 0) pi = petinfo; @@ -3199,7 +3205,7 @@ void ZoneDatabase::LoadPetInfo(Client *client) { else continue; - strncpy(pi->Name,row[1],64); + strncpy(pi->Name, row[1], 64); pi->petpower = atoi(row[2]); pi->SpellID = atoi(row[3]); pi->HP = atoul(row[4]); @@ -3207,56 +3213,60 @@ void ZoneDatabase::LoadPetInfo(Client *client) { pi->size = atof(row[6]); } - query = StringFormat("SELECT `pet`, `slot`, `spell_id`, `caster_level`, `castername`, " - "`ticsremaining`, `counters` FROM `character_pet_buffs` " - "WHERE `char_id` = %u", client->CharacterID()); - results = QueryDatabase(query); - if (!results.Success()) { - return; - } - - for (auto row = results.begin(); row != results.end(); ++row) { - uint16 pet = atoi(row[0]); - if (pet == 0) - pi = petinfo; - else if (pet == 1) - pi = suspended; - else - continue; - - uint32 slot_id = atoul(row[1]); - if(slot_id >= RuleI(Spells, MaxTotalSlotsPET)) - continue; - - uint32 spell_id = atoul(row[2]); - if(!IsValidSpell(spell_id)) - continue; - - uint32 caster_level = atoi(row[3]); - int caster_id = 0; - // The castername field is currently unused - uint32 ticsremaining = atoul(row[5]); - uint32 counters = atoul(row[6]); - - pi->Buffs[slot_id].spellid = spell_id; - pi->Buffs[slot_id].level = caster_level; - pi->Buffs[slot_id].player_id = caster_id; - pi->Buffs[slot_id].slotid = 2; // Always 2 in buffs struct for real buffs - - pi->Buffs[slot_id].duration = ticsremaining; - pi->Buffs[slot_id].counters = counters; - } - - query = StringFormat("SELECT `pet`, `slot`, `item_id` " - "FROM `character_pet_inventory` " - "WHERE `char_id`=%u",client->CharacterID()); - results = database.QueryDatabase(query); - if (!results.Success()) { + query = StringFormat("SELECT `pet`, `slot`, `spell_id`, `caster_level`, `castername`, " + "`ticsremaining`, `counters`, `instrument_mod` FROM `character_pet_buffs` " + "WHERE `char_id` = %u", + client->CharacterID()); + results = QueryDatabase(query); + if (!results.Success()) { return; } - for(auto row = results.begin(); row != results.end(); ++row) { - uint16 pet = atoi(row[0]); + for (auto row = results.begin(); row != results.end(); ++row) { + uint16 pet = atoi(row[0]); + if (pet == 0) + pi = petinfo; + else if (pet == 1) + pi = suspended; + else + continue; + + uint32 slot_id = atoul(row[1]); + if (slot_id >= RuleI(Spells, MaxTotalSlotsPET)) + continue; + + uint32 spell_id = atoul(row[2]); + if (!IsValidSpell(spell_id)) + continue; + + uint32 caster_level = atoi(row[3]); + int caster_id = 0; + // The castername field is currently unused + int32 ticsremaining = atoi(row[5]); + uint32 counters = atoul(row[6]); + uint8 bard_mod = atoul(row[7]); + + pi->Buffs[slot_id].spellid = spell_id; + pi->Buffs[slot_id].level = caster_level; + pi->Buffs[slot_id].player_id = caster_id; + pi->Buffs[slot_id].slotid = 2; // Always 2 in buffs struct for real buffs + + pi->Buffs[slot_id].duration = ticsremaining; + pi->Buffs[slot_id].counters = counters; + pi->Buffs[slot_id].bard_modifier = bard_mod; + } + + query = StringFormat("SELECT `pet`, `slot`, `item_id` " + "FROM `character_pet_inventory` " + "WHERE `char_id`=%u", + client->CharacterID()); + results = database.QueryDatabase(query); + if (!results.Success()) { + return; + } + + for (auto row = results.begin(); row != results.end(); ++row) { + uint16 pet = atoi(row[0]); if (pet == 0) pi = petinfo; else if (pet == 1) @@ -3266,11 +3276,10 @@ void ZoneDatabase::LoadPetInfo(Client *client) { int slot = atoi(row[1]); if (slot < EmuConstants::EQUIPMENT_BEGIN || slot > EmuConstants::EQUIPMENT_END) - continue; - - pi->Items[slot] = atoul(row[2]); - } + continue; + pi->Items[slot] = atoul(row[2]); + } } bool ZoneDatabase::GetFactionData(FactionMods* fm, uint32 class_mod, uint32 race_mod, uint32 deity_mod, int32 faction_id) { @@ -3390,7 +3399,7 @@ bool ZoneDatabase::SetCharacterFactionLevel(uint32 char_id, int32 faction_id, in "ON DUPLICATE KEY UPDATE `current_value`=%i,`temp`=%i", char_id, faction_id, value, temp, value, temp); auto results = QueryDatabase(query); - + if (!results.Success()) return false; else @@ -3535,7 +3544,7 @@ uint32 ZoneDatabase::GetCharacterCorpseDecayTimer(uint32 corpse_db_id){ auto results = QueryDatabase(query); auto row = results.begin(); if (results.Success() && results.RowsAffected() != 0) - return atoul(row[0]); + return atoul(row[0]); return 0; } @@ -3622,8 +3631,8 @@ uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, ui "`wc_6` = %u, " "`wc_7` = %u, " "`wc_8` = %u, " - "`wc_9` = %u ", - EscapeString(charname).c_str(), + "`wc_9` = %u ", + EscapeString(charname).c_str(), zoneid, instanceid, charid, @@ -3676,21 +3685,21 @@ uint32 ZoneDatabase::SaveCharacterCorpse(uint32 charid, const char* charname, ui corpse_items_query = StringFormat("REPLACE INTO `character_corpse_items` \n" " (corpse_id, equip_slot, item_id, charges, aug_1, aug_2, aug_3, aug_4, aug_5, aug_6, attuned) \n" " VALUES (%u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u) \n", - last_insert_id, + last_insert_id, dbpc->items[i].equip_slot, - dbpc->items[i].item_id, - dbpc->items[i].charges, - dbpc->items[i].aug_1, - dbpc->items[i].aug_2, - dbpc->items[i].aug_3, - dbpc->items[i].aug_4, + dbpc->items[i].item_id, + dbpc->items[i].charges, + dbpc->items[i].aug_1, + dbpc->items[i].aug_2, + dbpc->items[i].aug_3, + dbpc->items[i].aug_4, dbpc->items[i].aug_5, dbpc->items[i].aug_6, dbpc->items[i].attuned ); first_entry = 1; } - else{ + else{ corpse_items_query = corpse_items_query + StringFormat(", (%u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u) \n", last_insert_id, dbpc->items[i].equip_slot, diff --git a/zone/zonedb.h b/zone/zonedb.h index cac380a96..1fb4d3b29 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -278,7 +278,7 @@ public: bool SaveCharacterBindPoint(uint32 character_id, uint32 zone_id, uint32 instance_id, const glm::vec4& position, uint8 is_home); bool SaveCharacterCurrency(uint32 character_id, PlayerProfile_Struct* pp); bool SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp); - bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level); + bool SaveCharacterAA(uint32 character_id, uint32 aa_id, uint32 current_level, uint32 charges); bool SaveCharacterSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMemorizedSpell(uint32 character_id, uint32 spell_id, uint32 slot_id); bool SaveCharacterMaterialColor(uint32 character_id, uint32 slot_id, uint32 color);