diff --git a/changelog.txt b/changelog.txt index 203909266..db0f9b7c9 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 02/18/2013 == +Bad_Captain: Moved merc save to merc table, save merc buffs, added cure and rez spells to healer merc. + +REQUIRED SQL: 2013_02_18_Merc_Rules_and_Tables.sql +OPTIONAL SQL: Re-run utils/sql/svn/merc.sql for latest spell lists. + == 02/17/2013 == Derision: Added optional guildid and minstatus parameters to quest::gmsay(, [color], [toworld], [guildid], [minstatus]) Derision: Client version is now returned by the stream proxy as a number. diff --git a/common/database.h b/common/database.h index a08b16b04..843e534da 100644 --- a/common/database.h +++ b/common/database.h @@ -47,6 +47,7 @@ class NPC; class SpawnGroupList; class Petition; class Client; +class Merc; struct Combine_Struct; //struct Faction; //struct FactionMods; diff --git a/common/ruletypes.h b/common/ruletypes.h index 04b5c0cc7..e5c0158f4 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -106,6 +106,7 @@ RULE_INT (Mercs, UpkeepIntervalS, 180) RULE_BOOL ( Mercs, AllowMercs, 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() diff --git a/utils/sql/git/required/2013_02_18_Merc_Rules_and_Tables.sql b/utils/sql/git/required/2013_02_18_Merc_Rules_and_Tables.sql new file mode 100644 index 000000000..66065bfab --- /dev/null +++ b/utils/sql/git/required/2013_02_18_Merc_Rules_and_Tables.sql @@ -0,0 +1,53 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Mercs:ResurrectRadius', '50', 'Determines the distance from which a healer merc will attempt to resurrect a corpse'); + +DROP TABLE IF EXISTS mercsbuffs; +DROP TABLE IF EXISTS mercs; + +CREATE TABLE mercs ( + MercID int(10) unsigned NOT NULL AUTO_INCREMENT, + OwnerCharacterID int(10) unsigned NOT NULL, + Slot tinyint(1) unsigned NOT NULL DEFAULT '0', + Name varchar(64) NOT NULL, + TemplateID int(10) unsigned NOT NULL DEFAULT '0', + SuspendedTime int(11) unsigned NOT NULL DEFAULT '0', + IsSuspended tinyint(1) unsigned NOT NULL default '0', + TimerRemaining int(11) unsigned NOT NULL DEFAULT '0', + Gender tinyint unsigned NOT NULL DEFAULT '0', + StanceID tinyint unsigned NOT NULL DEFAULT '0', + HP int(11) unsigned NOT NULL DEFAULT '0', + Mana int(11) unsigned NOT NULL DEFAULT '0', + Endurance int(11) unsigned NOT NULL DEFAULT '0', + Face int(10) unsigned NOT NULL DEFAULT '1', + LuclinHairStyle int(10) unsigned NOT NULL DEFAULT '1', + LuclinHairColor int(10) unsigned NOT NULL DEFAULT '1', + LuclinEyeColor int(10) unsigned NOT NULL DEFAULT '1', + LuclinEyeColor2 int(10) unsigned NOT NULL DEFAULT '1', + LuclinBeardColor int(10) unsigned NOT NULL DEFAULT '1', + LuclinBeard int(10) unsigned NOT NULL DEFAULT '0', + DrakkinHeritage int(10) unsigned NOT NULL DEFAULT '0', + DrakkinTattoo int(10) unsigned NOT NULL DEFAULT '0', + DrakkinDetails int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (MercID) +); + +CREATE TABLE mercbuffs ( + MercBuffId int(10) unsigned NOT NULL auto_increment, + MercId int(10) unsigned NOT NULL default '0', + SpellId int(10) unsigned NOT NULL default '0', + CasterLevel int(10) unsigned NOT NULL default '0', + DurationFormula int(10) unsigned NOT NULL default '0', + TicsRemaining int(11) unsigned NOT NULL default '0', + PoisonCounters int(11) unsigned NOT NULL default '0', + DiseaseCounters int(11) unsigned NOT NULL default '0', + CurseCounters int(11) unsigned NOT NULL default '0', + CorruptionCounters int(11) unsigned NOT NULL default '0', + HitCount int(10) unsigned NOT NULL default '0', + MeleeRune int(10) unsigned NOT NULL default '0', + MagicRune int(10) unsigned NOT NULL default '0', + DeathSaveSuccessChance int(10) unsigned NOT NULL default '0', + CasterAARank int(10) unsigned NOT NULL default '0', + Persistent tinyint(1) NOT NULL default '0', + PRIMARY KEY (MercBuffId), + KEY FK_mercbuff_1 (MercId), + CONSTRAINT FK_mercbuff_1 FOREIGN KEY (MercId) REFERENCES mercs (MercID) +); \ No newline at end of file diff --git a/utils/sql/svn/mercs.sql b/utils/sql/svn/mercs.sql index a3ffed789..2edf39e20 100644 --- a/utils/sql/svn/mercs.sql +++ b/utils/sql/svn/mercs.sql @@ -81,7 +81,7 @@ create table merc_spell_list_entries merc_spell_list_entry_id int UNSIGNED NOT NULL AUTO_INCREMENT, merc_spell_list_id int UNSIGNED NOT NULL, spell_id int UNSIGNED NOT NULL, - spell_type tinyint UNSIGNED NOT NULL default '0', + spell_type int UNSIGNED NOT NULL default '0', stance_id tinyint UNSIGNED NOT NULL default '0', minlevel tinyint UNSIGNED NOT NULL default '1', maxlevel tinyint UNSIGNED NOT NULL default '255', @@ -3707,38 +3707,57 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, stance_id, minlevel, maxlevel, slot, procChance) VALUES ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Minor Healing' ORDER BY id DESC LIMIT 1), 2, 0, 1, 3, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Courage' ORDER BY id DESC LIMIT 1), 8, 0, 1, 6, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Cure Poison' ORDER BY id DESC LIMIT 1), 32768, 0, 1, 21, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Light Healing' ORDER BY id DESC LIMIT 1), 2, 0, 4, 9, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Cure Disease' ORDER BY id DESC LIMIT 1), 32768, 0, 4, 27, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Center' ORDER BY id DESC LIMIT 1), 8, 0, 7, 21, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Remove Minor Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 8, 22, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Healing' ORDER BY id DESC LIMIT 1), 2, 0, 10, 19, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Symbol of Transal' ORDER BY id DESC LIMIT 1), 8, 0, 11, 20, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Reanimation' ORDER BY id DESC LIMIT 1), 65536, 0, 12, 17, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Blessing of Piety' ORDER BY id DESC LIMIT 1), 8, 0, 15, 34, 3, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Daring' ORDER BY id DESC LIMIT 1), 8, 0, 17, 20, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Reconstitution' ORDER BY id DESC LIMIT 1), 65536, 0, 18, 21, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Celestial Remedy' ORDER BY id DESC LIMIT 1), 2, 0, 19, 28, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Greater Healing' ORDER BY id DESC LIMIT 1), 2, 0, 20, 29, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Ward of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 20, 39, 4, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Symbol of Ryltan' ORDER BY id DESC LIMIT 1), 8, 0, 21, 30, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Bravery' ORDER BY id DESC LIMIT 1), 8, 0, 22, 31, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Counteract Poison' ORDER BY id DESC LIMIT 1), 32768, 0, 22, 47, 2, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Reparation' ORDER BY id DESC LIMIT 1), 65536, 0, 22, 26, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Remove Lesser Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 23, 37, 2, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Revive' ORDER BY id DESC LIMIT 1), 65536, 0, 27, 31, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Counteract Disease' ORDER BY id DESC LIMIT 1), 32768, 0, 28, 50, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Celestial Health' ORDER BY id DESC LIMIT 1), 2, 0, 29, 43, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Word of Health' ORDER BY id DESC LIMIT 1), 2, 0, 30, 44, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Superior Healing' ORDER BY id DESC LIMIT 1), 2, 0, 30, 52, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Symbol of Pinzarn' ORDER BY id DESC LIMIT 1), 8, 0, 31, 40, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Valor' ORDER BY id DESC LIMIT 1), 8, 0, 32, 39, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Renewal' ORDER BY id DESC LIMIT 1), 65536, 0, 32, 36, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Blessing of Faith' ORDER BY id DESC LIMIT 1), 8, 0, 35, 61, 3, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Resuscitate' ORDER BY id DESC LIMIT 1), 65536, 0, 37, 41, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Remove Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 38, 53, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Complete Healing' ORDER BY id DESC LIMIT 1), 2, 0, 39, 75, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Temperance' ORDER BY id DESC LIMIT 1), 8, 0, 40, 59, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Guard of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 40, 53, 4, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Symbol of Naltron' ORDER BY id DESC LIMIT 1), 8, 0, 41, 53, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Restoration' ORDER BY id DESC LIMIT 1), 65536, 0, 42, 46, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Celestial Healing' ORDER BY id DESC LIMIT 1), 2, 0, 44, 58, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Word of Healing' ORDER BY id DESC LIMIT 1), 2, 0, 45, 51, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Blessing of Temperance' ORDER BY id DESC LIMIT 1), 8, 0, 45, 59, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Resurrection' ORDER BY id DESC LIMIT 1), 65536, 0, 47, 255, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Abolish Poison' ORDER BY id DESC LIMIT 1), 32768, 0, 48, 57, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Remedy' ORDER BY id DESC LIMIT 1), 2, 0, 51, 58, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Pure Blood' ORDER BY id DESC LIMIT 1), 32768, 0, 51, 83, 3, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Word of Vigor' ORDER BY id DESC LIMIT 1), 2, 0, 52, 56, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Divine Light' ORDER BY id DESC LIMIT 1), 2, 0, 53, 57, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Symbol of Marzin' ORDER BY id DESC LIMIT 1), 8, 0, 54, 60, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Protection of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 54, 61, 4, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Remove Greater Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 54, 93, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Word of Restoration' ORDER BY id DESC LIMIT 1), 2, 0, 57, 63, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Naltron\'s Mark' ORDER BY id DESC LIMIT 1), 8, 0, 58, 59, 6, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Ethereal Light' ORDER BY id DESC LIMIT 1), 2, 0, 58, 62, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Antidote' ORDER BY id DESC LIMIT 1), 32768, 0, 58, 83, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Celestial Elixir' ORDER BY id DESC LIMIT 1), 2, 0, 59, 61, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Ethereal Remedy' ORDER BY id DESC LIMIT 1), 8, 0, 59, 60, 6, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Marzin\'s Mark' ORDER BY id DESC LIMIT 1), 8, 0, 60, 62, 6, 0 ), @@ -3781,6 +3800,7 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Sacred Light' ORDER BY id DESC LIMIT 1), 2, 0, 73, 77, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Promised Renewal' ORDER BY id DESC LIMIT 1), 2, 0, 73, 77, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Aegis of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 73, 77, 4, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Expunge Corruption' ORDER BY id DESC LIMIT 1), 32768, 0, 74, 78, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Hand of Tenacity' ORDER BY id DESC LIMIT 1), 8, 0, 75, 76, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Elushar\'s Mark' ORDER BY id DESC LIMIT 1), 8, 0, 75, 79, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Elixir of Redemption' ORDER BY id DESC LIMIT 1), 2, 0, 75, 79, 1, 0), @@ -3795,6 +3815,7 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Solemn Light' ORDER BY id DESC LIMIT 1), 2, 0, 78, 82, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Promised Restoration' ORDER BY id DESC LIMIT 1), 2, 0, 78, 82, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Shield of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 78, 82, 4, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Vitiate Corruption' ORDER BY id DESC LIMIT 1), 32768, 0, 79, 83, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Hand of Temerity' ORDER BY id DESC LIMIT 1), 8, 0, 80, 81, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Kaerra\'s Mark' ORDER BY id DESC LIMIT 1), 8, 0, 80, 84, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Elixir of Atonement' ORDER BY id DESC LIMIT 1), 2, 0, 80, 84, 1, 0), @@ -3810,6 +3831,8 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Devout Light' ORDER BY id DESC LIMIT 1), 2, 0, 83, 87, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Promised Recuperation' ORDER BY id DESC LIMIT 1), 2, 0, 83, 87, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Palladium of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 83, 87, 4, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Abolish Corruption' ORDER BY id DESC LIMIT 1), 32768, 0, 84, 86, 2, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Purified Blood' ORDER BY id DESC LIMIT 1), 32768, 0, 84, 88, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Hand of Gallantry' ORDER BY id DESC LIMIT 1), 8, 0, 85, 86, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Darianna\'s Mark' ORDER BY id DESC LIMIT 1), 8, 0, 85, 89, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 1), (SELECT id FROM spells_new WHERE name = 'Frenetic Renewal' ORDER BY id DESC LIMIT 1), 2, 0, 85, 89, 2, 0), @@ -3819,38 +3842,58 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, stance_id, minlevel, maxlevel, slot, procChance) VALUES ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Minor Healing' ORDER BY id DESC LIMIT 1), 2, 0, 1, 3, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Courage' ORDER BY id DESC LIMIT 1), 8, 0, 1, 6, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Cure Poison' ORDER BY id DESC LIMIT 1), 32768, 0, 1, 21, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Light Healing' ORDER BY id DESC LIMIT 1), 2, 0, 4, 9, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Cure Disease' ORDER BY id DESC LIMIT 1), 32768, 0, 4, 27, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Center' ORDER BY id DESC LIMIT 1), 8, 0, 7, 21, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Remove Minor Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 8, 22, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Healing' ORDER BY id DESC LIMIT 1), 2, 0, 10, 19, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Symbol of Transal' ORDER BY id DESC LIMIT 1), 8, 0, 11, 20, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Reanimation' ORDER BY id DESC LIMIT 1), 65536, 0, 12, 17, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Blessing of Piety' ORDER BY id DESC LIMIT 1), 8, 0, 15, 34, 3, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Daring' ORDER BY id DESC LIMIT 1), 8, 0, 17, 20, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Reconstitution' ORDER BY id DESC LIMIT 1), 65536, 0, 18, 21, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Celestial Remedy' ORDER BY id DESC LIMIT 1), 2, 0, 19, 28, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Greater Healing' ORDER BY id DESC LIMIT 1), 2, 0, 20, 29, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Ward of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 20, 39, 4, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Symbol of Ryltan' ORDER BY id DESC LIMIT 1), 8, 0, 21, 30, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Bravery' ORDER BY id DESC LIMIT 1), 8, 0, 22, 31, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Counteract Poison' ORDER BY id DESC LIMIT 1), 32768, 0, 22, 47, 2, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Reparation' ORDER BY id DESC LIMIT 1), 65536, 0, 22, 26, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Remove Lesser Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 23, 37, 2, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Revive' ORDER BY id DESC LIMIT 1), 65536, 0, 27, 31, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Counteract Disease' ORDER BY id DESC LIMIT 1), 32768, 0, 28, 50, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Celestial Health' ORDER BY id DESC LIMIT 1), 2, 0, 29, 43, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Word of Health' ORDER BY id DESC LIMIT 1), 2, 0, 30, 44, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Superior Healing' ORDER BY id DESC LIMIT 1), 2, 0, 30, 52, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Symbol of Pinzarn' ORDER BY id DESC LIMIT 1), 8, 0, 31, 40, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Valor' ORDER BY id DESC LIMIT 1), 8, 0, 32, 39, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Renewal' ORDER BY id DESC LIMIT 1), 65536, 0, 32, 36, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Blessing of Faith' ORDER BY id DESC LIMIT 1), 8, 0, 35, 61, 3, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Resuscitate' ORDER BY id DESC LIMIT 1), 65536, 0, 37, 41, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Remove Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 38, 53, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Complete Healing' ORDER BY id DESC LIMIT 1), 2, 0, 39, 75, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Temperance' ORDER BY id DESC LIMIT 1), 8, 0, 40, 59, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Guard of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 40, 53, 4, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Symbol of Naltron' ORDER BY id DESC LIMIT 1), 8, 0, 41, 53, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Restoration' ORDER BY id DESC LIMIT 1), 65536, 0, 42, 46, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Celestial Healing' ORDER BY id DESC LIMIT 1), 2, 0, 44, 58, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Word of Healing' ORDER BY id DESC LIMIT 1), 2, 0, 45, 51, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Blessing of Temperance' ORDER BY id DESC LIMIT 1), 8, 0, 45, 59, 1, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Resurrection' ORDER BY id DESC LIMIT 1), 65536, 0, 47, 55, 1, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Abolish Poison' ORDER BY id DESC LIMIT 1), 32768, 0, 48, 57, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Remedy' ORDER BY id DESC LIMIT 1), 2, 0, 51, 58, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Pure Blood' ORDER BY id DESC LIMIT 1), 32768, 0, 51, 83, 3, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Word of Vigor' ORDER BY id DESC LIMIT 1), 2, 0, 52, 56, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Divine Light' ORDER BY id DESC LIMIT 1), 2, 0, 53, 57, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Symbol of Marzin' ORDER BY id DESC LIMIT 1), 8, 0, 54, 60, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Protection of Vie' ORDER BY id DESC LIMIT 1), 8, 0, 54, 61, 4, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Remove Greater Curse' ORDER BY id DESC LIMIT 1), 32768, 0, 54, 93, 2, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Reviviscence' ORDER BY id DESC LIMIT 1), 65536, 0, 56, 255, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Word of Restoration' ORDER BY id DESC LIMIT 1), 2, 0, 57, 63, 2, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Naltron\'s Mark' ORDER BY id DESC LIMIT 1), 8, 0, 58, 59, 6, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Ethereal Light' ORDER BY id DESC LIMIT 1), 2, 0, 58, 62, 2, 0 ), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Antidote' ORDER BY id DESC LIMIT 1), 32768, 0, 58, 83, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Celestial Elixir' ORDER BY id DESC LIMIT 1), 2, 0, 59, 61, 1, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Ethereal Remedy' ORDER BY id DESC LIMIT 1), 8, 0, 59, 60, 6, 0 ), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Marzin\'s Mark' ORDER BY id DESC LIMIT 1), 8, 0, 60, 62, 6, 0 ), @@ -3894,6 +3937,7 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Sacred Light Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 73, 77, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Promised Renewal Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 73, 77, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Aegis of Vie Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 73, 77, 4, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Expunge Corruption Rk. II' ORDER BY id DESC LIMIT 1), 32768, 0, 74, 78, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Hand of Tenacity Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 75, 76, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Elushar\'s Mark Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 75, 79, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Elixir of Redemption Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 75, 79, 1, 0), @@ -3908,6 +3952,7 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Solemn Light Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 78, 82, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Promised Restoration Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 78, 82, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Shield of Vie Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 78, 82, 4, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Vitiate Corruption Rk. II' ORDER BY id DESC LIMIT 1), 32768, 0, 79, 83, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Hand of Temerity Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 80, 81, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Kaerra\'s Mark Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 80, 84, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Elixir of Atonement Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 80, 84, 1, 0), @@ -3923,6 +3968,8 @@ REPLACE INTO merc_spell_list_entries (merc_spell_list_id, spell_id, spell_type, ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Devout Light Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 83, 87, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Promised Recuperation Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 83, 87, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Palladium of Vie Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 83, 87, 4, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Abolish Corruption Rk. II' ORDER BY id DESC LIMIT 1), 32768, 0, 84, 86, 2, 0), + ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Purified Blood Rk. II' ORDER BY id DESC LIMIT 1), 32768, 0, 84, 88, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Hand of Gallantry Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 85, 86, 1, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Darianna\'s Mark Rk. II' ORDER BY id DESC LIMIT 1), 8, 0, 85, 89, 2, 0), ((SELECT merc_spell_list_id FROM merc_spell_lists WHERE class_id = 2 AND proficiency_id = 2), (SELECT id FROM spells_new WHERE name = 'Frenetic Renewal Rk. II' ORDER BY id DESC LIMIT 1), 2, 0, 85, 89, 2, 0), diff --git a/zone/QuestParserCollection.cpp b/zone/QuestParserCollection.cpp index 7cf837696..e2855770e 100644 --- a/zone/QuestParserCollection.cpp +++ b/zone/QuestParserCollection.cpp @@ -15,6 +15,7 @@ extern Zone* zone; QuestParserCollection::QuestParserCollection() { _player_quest_status = QuestUnloaded; _global_player_quest_status = QuestUnloaded; + _global_npc_quest_status = QuestUnloaded; } QuestParserCollection::~QuestParserCollection() { @@ -50,31 +51,42 @@ void QuestParserCollection::ReloadQuests(bool reset_timers) { bool QuestParserCollection::HasQuestSub(uint32 npcid, const char *subname) { std::map::iterator iter = _npc_quest_status.find(npcid); - if(_global_npc_quest_status == QuestUnloaded){ - QuestInterface *qi = GetQIByGlobalNPCQuest(); - if(qi) { - _global_npc_quest_status = qi->GetIdentifier(); - return qi->HasGlobalQuestSub(subname); - } - } + if(iter != _npc_quest_status.end()) { //loaded or failed to load if(iter->second != QuestFailedToLoad) { std::map::iterator qiter = _interfaces.find(iter->second); - return qiter->second->HasQuestSub(npcid, subname) || qiter->second->HasGlobalQuestSub(subname); + if(qiter->second->HasQuestSub(npcid, subname)) { + return true; + } } } else { + QuestInterface *qi = GetQIByNPCQuest(npcid); + if(qi) { + _npc_quest_status[npcid] = qi->GetIdentifier(); + if(qi->HasQuestSub(npcid, subname)) { + return true; + } + } else { + _npc_quest_status[npcid] = QuestFailedToLoad; + } + } + + if(_global_npc_quest_status == QuestUnloaded){ QuestInterface *qi = GetQIByGlobalNPCQuest(); if(qi) { _global_npc_quest_status = qi->GetIdentifier(); + if(qi->HasGlobalQuestSub(subname)) { + return true; + } } - - qi = GetQIByNPCQuest(npcid); + } else { + QuestInterface *qi = GetQIByGlobalNPCQuest(); if(qi) { - _npc_quest_status[npcid] = qi->GetIdentifier(); - return qi->HasQuestSub(npcid, subname) || qi->HasGlobalQuestSub(subname); - } else { - _npc_quest_status[npcid] = QuestFailedToLoad; + _global_npc_quest_status = qi->GetIdentifier(); + if(qi->HasGlobalQuestSub(subname)) { + return true; + } } } return false; @@ -169,16 +181,15 @@ void QuestParserCollection::EventNPC(QuestEventID evt, NPC* npc, Mob *init, std: // K, lets also parse templates/global_npc.pl if(_global_npc_quest_status != QuestUnloaded) { - QuestInterface *qi = GetQIByGlobalNPCQuest(); + std::map::iterator qiter = _interfaces.find(_global_npc_quest_status); + qiter->second->EventGlobalNPC(evt, npc, init, data, extra_data); + } else { + QuestInterface *qi = GetQIByGlobalNPCQuest(); if(qi) { _global_npc_quest_status = qi->GetIdentifier(); qi->EventGlobalNPC(evt, npc, init, data, extra_data); - } - } else { - if(_global_npc_quest_status != QuestFailedToLoad) { - std::map::iterator iter = _interfaces.find(_global_npc_quest_status); - if(iter != _interfaces.end()) - iter->second->EventGlobalNPC(evt, npc, init, data, extra_data); + } else { + _global_npc_quest_status = QuestFailedToLoad; } } } diff --git a/zone/client.cpp b/zone/client.cpp index aae1ae4cb..d546bbc6e 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -258,6 +258,8 @@ Client::Client(EQStreamInterface* ieqs) keyring.clear(); bind_sight_target = NULL; mercid = 0; + mercSlot = 0; + InitializeMercInfo(); SetMerc(0); logging_enabled = CLIENT_DEFAULT_LOGGING_ENABLED; @@ -557,12 +559,17 @@ bool Client::Save(uint8 iCommitNow) { m_pp.timePlayedMin = (TotalSecondsPlayed / 60); m_pp.RestTimer = rest_timer.GetRemainingTime() / 1000; - if(GetEPP().mercTimerRemaining > RuleI(Mercs, UpkeepIntervalMS)) - GetEPP().mercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + if(GetMercInfo().MercTimerRemaining > RuleI(Mercs, UpkeepIntervalMS)) + GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); - if(merc_timer.Enabled()) - { - GetEPP().mercTimerRemaining = merc_timer.GetRemainingTime(); + if(merc_timer.Enabled()) { + GetMercInfo().MercTimerRemaining = merc_timer.GetRemainingTime(); + } + + if (GetMerc() && !dead) { + + } else { + memset(&m_mercinfo, 0, sizeof(struct MercInfo)); } m_pp.lastlogin = time(NULL); @@ -7127,7 +7134,7 @@ void Client::SendMercPersonalInfo() { MercenaryDataUpdate_Struct* mdus = new MercenaryDataUpdate_Struct; - MercTemplate *mercData = &zone->merc_templates[GetEPP().mercTemplateID]; + MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID]; if (mercData) { @@ -7146,13 +7153,13 @@ void Client::SendMercPersonalInfo() mdus->MercData[i].AltCurrencyUpkeep = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); mdus->MercData[i].AltCurrencyType = altCurrentType; mdus->MercData[i].MercUnk01 = 0; - mdus->MercData[i].TimeLeft = GetEPP().mercTimerRemaining; //GetMercTimer().GetRemainingTime(); + mdus->MercData[i].TimeLeft = GetMercInfo().MercTimerRemaining; //GetMercTimer().GetRemainingTime(); mdus->MercData[i].MerchantSlot = i + 1; mdus->MercData[i].MercUnk02 = 1; mdus->MercData[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size(); mdus->MercData[i].MercUnk03 = 0; mdus->MercData[i].MercUnk04 = 1; - strn0cpy(mdus->MercData[i].MercName, GetEPP().merc_name , sizeof(mdus->MercData[i].MercName)); + strn0cpy(mdus->MercData[i].MercName, GetMercInfo().merc_name , sizeof(mdus->MercData[i].MercName)); uint32 stanceindex = 0; if (mdus->MercData[i].StanceCount != 0) { @@ -7179,7 +7186,7 @@ void Client::SendMercPersonalInfo() else { MercenaryMerchantList_Struct* mml = new MercenaryMerchantList_Struct; - MercTemplate *mercData = &zone->merc_templates[GetEPP().mercTemplateID]; + MercTemplate *mercData = &zone->merc_templates[GetMercInfo().MercTemplateID]; if(mercData) { @@ -7202,7 +7209,7 @@ void Client::SendMercPersonalInfo() mml->Mercs[i].AltCurrencyUpkeep = Merc::CalcPurchaseCost(mercData->MercTemplateID, GetLevel(), altCurrentType); mml->Mercs[i].AltCurrencyType = altCurrentType; mml->Mercs[i].MercUnk01 = 0; - mml->Mercs[i].TimeLeft = GetEPP().mercTimerRemaining; + mml->Mercs[i].TimeLeft = GetMercInfo().MercTimerRemaining; mml->Mercs[i].MerchantSlot = i + 1; mml->Mercs[i].MercUnk02 = 1; mml->Mercs[i].StanceCount = zone->merc_stance_list[mercData->MercTemplateID].size(); diff --git a/zone/client.h b/zone/client.h index 1cc8f4e42..caae713e3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1095,9 +1095,15 @@ public: void SendXTargetPacket(uint32 Slot, Mob *m); void RemoveGroupXTargets(); void ShowXTargets(Client *c); + void InitializeMercInfo(); inline uint32 GetMercID() const { return mercid; } + inline uint8 GetMercSlot() const { return mercSlot; } void SetMercID( uint32 newmercid) { mercid = newmercid; } + void SetMercSlot( uint8 newmercslot) { mercSlot = newmercslot; } Merc* GetMerc(); + MercInfo& GetMercInfo(uint8 slot) { return m_mercinfo[slot]; } + MercInfo& GetMercInfo() { return m_mercinfo[mercSlot]; } + uint8 GetNumMercs(); void SetMerc(Merc* newmerc); void SendMercMerchantResponsePacket(int32 response_type); void SendMercenaryUnknownPacket(uint8 type); @@ -1109,7 +1115,7 @@ public: void SendClearMercInfo(); void SuspendMercCommand(); void SpawnMercOnZone(); - void SpawnMerc(Merc* merc); + void SpawnMerc(Merc* merc, bool setMaxStats); void UpdateMercTimer(); void UpdateMercLevel(); void CheckMercSuspendTimer(); @@ -1257,7 +1263,8 @@ private: uint16 CustomerID; uint32 account_creation; uint8 firstlogon; - uint32 mercid; + uint32 mercid; // current merc + uint8 mercSlot; // selected merc slot bool Trader; bool Buyer; string BuyerWelcomeMessage; @@ -1273,7 +1280,7 @@ private: Object* m_tradeskill_object; PetInfo m_petinfo; // current pet data, used while loading from and saving to DB PetInfo m_suspendedminion; // pet data for our suspended minion. - MercInfo m_mercinfo; // current mercenary + MercInfo m_mercinfo[MAXMERCS]; // current mercenary InspectMessage_Struct m_inspect_message; void NPCSpawn(const Seperator* sep); diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 72780eaf7..77c39fd00 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -13629,11 +13629,12 @@ void Client::Handle_OP_MercenaryHire(const EQApplicationPacket *app) SendMercMerchantResponsePacket(0); // Set time remaining to max on Hire - GetEPP().mercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); // Get merc, assign it to client & spawn - Merc* merc = Merc::LoadMerc(this, merc_template, merchant_id); - SpawnMerc(merc); + Merc* merc = Merc::LoadMerc(this, merc_template, merchant_id, false); + SpawnMerc(merc, true); + merc->Save(); } } @@ -13677,7 +13678,7 @@ void Client::Handle_OP_MercenaryCommand(const EQApplicationPacket *app) if(option >= 0) { - GetEPP().mercState = option; + GetMercInfo().State = option; } DumpPacket(app); @@ -13777,10 +13778,10 @@ void Client::Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app) if(merc) { entityID = merc->GetID(); - - if(GetEPP().mercIsSuspended) { + + if(GetMercInfo().IsSuspended) { mercState = 1; - suspendedTime = GetEPP().mercSuspendedTime; + suspendedTime = GetMercInfo().SuspendedTime; } } } diff --git a/zone/client_process.cpp b/zone/client_process.cpp index 7b884309a..4f4bd4af2 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -195,6 +195,7 @@ bool Client::Process() { LeaveGroup(); if (GetMerc()) { + GetMerc()->Save(); GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); GetMerc()->Depop(); } @@ -211,6 +212,7 @@ bool Client::Process() { Save(); if (GetMerc()) { + GetMerc()->Save(); GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); GetMerc()->Depop(); } @@ -251,7 +253,7 @@ bool Client::Process() { UpdateMercTimer(); } - if(GetEPP().mercTemplateID != 0) + if(GetMercInfo().MercTemplateID != 0) { if(p_timers.Expired(&database, pTimerMercSuspend, false)) { CheckMercSuspendTimer(); @@ -679,6 +681,7 @@ bool Client::Process() { if (GetGM()) { if (GetMerc()) { + GetMerc()->Save(); GetMerc()->RemoveMercFromGroup(GetMerc(), GetMerc()->GetGroup()); GetMerc()->Depop(); } diff --git a/zone/entity.cpp b/zone/entity.cpp index c1080b1bb..1789f95de 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -1987,6 +1987,24 @@ Corpse* EntityList::GetCorpseByOwner(Client* client){ } return 0; } + +Corpse* EntityList::GetCorpseByOwnerWithinRange(Client* client, Mob* center, int range){ + LinkedListIterator iterator(corpse_list); + + iterator.Reset(); + while(iterator.MoreElements()) + { + if (iterator.GetData()->IsPlayerCorpse()) + { + if (center->DistNoRootNoZ(*iterator.GetData()) < range && strcasecmp(iterator.GetData()->GetOwnerName(), client->GetName()) == 0) { + return iterator.GetData(); + } + } + iterator.Advance(); + } + return 0; +} + Corpse* EntityList::GetCorpseByID(uint16 id){ LinkedListIterator iterator(corpse_list); iterator.Reset(); diff --git a/zone/entity.h b/zone/entity.h index 5b9af3884..3e3364e5a 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -156,6 +156,7 @@ public: Raid* GetRaidByLeaderName(const char *leader); Corpse* GetCorpseByOwner(Client* client); + Corpse* GetCorpseByOwnerWithinRange(Client* client, Mob* center, int range); Corpse* GetCorpseByID(uint16 id); Corpse* GetCorpseByDBID(uint32 dbid); Corpse* GetCorpseByName(const char* name); @@ -357,7 +358,7 @@ public: int GetHatedCount(Mob *attacker, Mob *exclude); void AIYellForHelp(Mob* sender, Mob* attacker); bool AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint16 iSpellTypes); - bool Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint16 iSpellTypes); + bool Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes); Mob* GetTargetForMez(Mob* caster); uint32 CheckNPCsClose(Mob *center); diff --git a/zone/merc.cpp b/zone/merc.cpp index dd11cbf47..bd38e0a64 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -16,7 +16,7 @@ extern volatile bool ZoneLoaded; Merc::Merc(const NPCType* d, float x, float y, float z, float heading) - : NPC(d, 0, x, y, z, heading, 0, false), endupkeep_timer(1000), rest_timer(1) + : NPC(d, 0, x, y, z, heading, 0, false), endupkeep_timer(1000), rest_timer(1), confidence_timer(6000) { _baseAC = d->AC; _baseSTR = d->STR; @@ -39,10 +39,13 @@ Merc::Merc(const NPCType* d, float x, float y, float z, float heading) _medding = false; _suspended = false; p_depop = false; + _check_confidence = false; + _lost_confidence = false; + _hatedCount = 0; ourNPCData = d; - LoadMercSpells(); + SetMercID(0); SetStance(MercStancePassive); rest_timer.Disable(); @@ -1070,7 +1073,7 @@ int32 Merc::CalcBaseMana() return base_mana; } -int32 Merc::CalcBaseManaRegen() +int32 Merc::CalcBaseManaRegen() { uint8 clevel = GetLevel(); int32 regen = 0; @@ -1087,7 +1090,7 @@ int32 Merc::CalcBaseManaRegen() return regen; } -int32 Merc::CalcManaRegen() +int32 Merc::CalcManaRegen() { int32 regen = 0; //this should be changed so we dont med while camping, etc... @@ -1451,7 +1454,7 @@ bool Merc::Process() if (p_depop) { SetOwnerID(0); - SetMercID(0); + SetID(0); return false; } @@ -1498,6 +1501,10 @@ bool Merc::Process() SetEndurance(GetEndurance() + CalcEnduranceRegen()); } + if(confidence_timer.Check()) { + _check_confidence = true; + } + if (sendhpupdate_timer.Check()) { SendHPUpdate(); } @@ -1623,9 +1630,52 @@ void Merc::AI_Process() { if(DivineAura()) return; + int hateCount = entity_list.GetHatedCount(this, NULL); + if(GetHatedCount() < hateCount) { + SetHatedCount(hateCount); + + if(!confidence_timer.Enabled()) { + confidence_timer.Start(10000); + } + } + + //Check specific conditions for merc to lose confidence and flee (or regain confidence once fleeing) + if(_check_confidence) { + //not already running + if(!_lost_confidence) { + //and fail confidence check + if(!CheckConfidence()) { + _lost_confidence = true; + + //move to bottome of hate lists? + //Iterate though hatelist + // SetHate(otherm hate, damage) + + if(RuleB(Combat, EnableFearPathing)) { + CalculateNewFearpoint(); + if(curfp) { + return; + } + } + else { + Stun(12000 - (6000 - tic_timer.GetRemainingTime())); + } + } + } + else { //are fleeing due to lost confidence + if(CheckConfidence()) { //passed test - regain confidence + _lost_confidence = false; + } + } + + //they are in flee mode + if(_lost_confidence) + return; + } + // Let's check if we have a los with our target. // If we don't, our hate_list is wiped. - // Else, it was causing the bot to aggro behind wall etc... causing massive trains. + // Else, it was causing the merc to aggro behind wall etc... causing massive trains. if(!CheckLosFN(GetTarget()) || GetTarget()->IsMezzed() || !IsAttackAllowed(GetTarget())) { WipeHateList(); @@ -1806,7 +1856,7 @@ void Merc::AI_Process() { if(GetTarget()->IsFeared() && !spellend_timer.Enabled()){ // This is a mob that is fleeing either because it has been feared or is low on hitpoints //TODO: Implement Stances. - //if(GetBotStance() != BotStancePassive) + //if(GetStance() != MercStancePassive) AI_PursueCastCheck(); } @@ -1827,7 +1877,7 @@ void Merc::AI_Process() { if(!IsMoving() && !spellend_timer.Enabled()) { //TODO: Implement Stances. - //if(GetBotStance() == BotStancePassive) + //if(GetStance() == MercStancePassive) // return; if(AI_EngagedCastCheck()) { @@ -1840,13 +1890,23 @@ void Merc::AI_Process() { else { // Not engaged in combat SetTarget(0); + SetHatedCount(0); + confidence_timer.Disable(); + _check_confidence = false; if(!IsMoving() && AIthink_timer->Check() && !spellend_timer.Enabled()) { //TODO: Implement passive stances. - //if(GetBotStance() != BotStancePassive) { - if(!AI_IdleCastCheck() && !IsCasting()) - MercMeditate(true); + //if(GetStance() != MercStancePassive) { + if(!AI_IdleCastCheck() && !IsCasting()) { + if(GetClass() == MELEEDPS && !hidden) { + TryHide(); + } + + if(GetArchetype() == ARCHETYPE_CASTER) { + MercMeditate(true); + } + } } if(AImovement_timer->Check()) { @@ -1983,9 +2043,13 @@ bool Merc::AI_IdleCastCheck() { failedToCast = true; break; case HEALER: - if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Heal)) { - if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Buff)) { - failedToCast = true; + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Cure)) { + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Heal)) { + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Resurrect)) { + if(!entity_list.Merc_AICheckCloseBeneficialSpells(this, 100, MercAISpellRange, SpellType_Buff)) { + failedToCast = true; + } + } } } result = true; @@ -2008,7 +2072,7 @@ bool Merc::AI_IdleCastCheck() { return result; } -bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint16 iSpellTypes) { +bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { _ZP(EntityList_Merc_AICheckCloseBeneficialSpells); if((iSpellTypes&SpellTypes_Detrimental) != 0) { @@ -2035,22 +2099,29 @@ bool EntityList::Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, int8 mercCasterClass = caster->GetClass(); - if( iSpellTypes == SpellType_Heal ) { - if( mercCasterClass == HEALER) { - // check in group - if(caster->HasGroup()) { - if(caster->AICastSpell(100, SpellType_Heal)) - return true; - } - } - } + if(caster->HasGroup()) { + if( mercCasterClass == HEALER) { + if( iSpellTypes == SpellType_Heal ) { + if(caster->AICastSpell(100, SpellType_Heal)) + return true; + } - //Ok for the buffs.. - if( iSpellTypes == SpellType_Buff) { - if(caster->HasGroup()) { - if(caster->AICastSpell(100, SpellType_Buff)) - return true; - } + if( iSpellTypes == SpellType_Cure ) { + if(caster->AICastSpell(100, SpellType_Cure)) + return true; + } + + if( iSpellTypes == SpellType_Resurrect ) { + if(caster->AICastSpell(100, SpellType_Resurrect)) + return true; + } + } + + //Ok for the buffs.. + if( iSpellTypes == SpellType_Buff) { + if(caster->AICastSpell(100, SpellType_Buff)) + return true; + } } return false; @@ -2104,14 +2175,14 @@ bool Merc::AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDon mercSpell.time_cancast = Timer::GetCurrentTime() + spells[spellid].recast_time; if(spells[spellid].EndurTimerIndex > 0) { - //SetSpellRecastTimer(spells[spellid].EndurTimerIndex, spells[spellid].recast_time); + SetSpellRecastTimer(spells[spellid].EndurTimerIndex, spellid, spells[spellid].recast_time); } } return result; } -bool Merc::AICastSpell(int8 iChance, int16 iSpellTypes) { +bool Merc::AICastSpell(int8 iChance, int32 iSpellTypes) { _ZP(Bot_AICastSpell); if(!AI_HasSpells()) @@ -2147,6 +2218,7 @@ bool Merc::AICastSpell(int8 iChance, int16 iSpellTypes) { Mob* tar = NULL; int8 numToHeal = g->GetNumberNeedingHealedInGroup(IsEngaged() ? 75 : 95, true); int8 checkHPR = IsEngaged() ? 95 : 99; + int8 checkPetHPR = IsEngaged() ? 95 : 99; //todo: check stance to determine healing spell selection @@ -2154,8 +2226,14 @@ bool Merc::AICastSpell(int8 iChance, int16 iSpellTypes) { if(g->members[i] && !g->members[i]->qglobal) { int8 hpr = (int8)g->members[i]->GetHPRatio(); + if(g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < checkHPR) { + if(!tar || ((g->members[i]->GetPet()->GetHPRatio() + 25) < tar->GetHPRatio())) { + tar = g->members[i]->GetPet(); + checkPetHPR = g->members[i]->GetPet()->GetHPRatio() + 25; + } + } + if(hpr > checkHPR) { - //they don't need to be healed continue; } @@ -2165,23 +2243,17 @@ bool Merc::AICastSpell(int8 iChance, int16 iSpellTypes) { continue; } - if(g->members[i] == GetMercOwner()) { - if(!tar || hpr < tar->GetHPRatio()) + if(hpr < checkHPR && g->members[i] == GetMercOwner()) { + if(!tar || (hpr < tar->GetHPRatio() || (tar->IsPet() && hpr < checkPetHPR))) tar = g->members[i]; //check owner first } - else if(g->HasRole(g->members[i], RoleTank) && g->members[i]->GetHPRatio() < checkHPR){ - if(!tar || hpr < tar->GetHPRatio()) + else if(hpr < checkHPR && g->HasRole(g->members[i], RoleTank)){ + if(!tar || (hpr < tar->GetHPRatio() || (tar->IsPet() && hpr < checkPetHPR))) tar = g->members[i]; } - else if(!tar || hpr < tar->GetHPRatio()) { + else if( hpr < checkHPR && (!tar || (hpr < tar->GetHPRatio() || (tar->IsPet() && hpr < checkPetHPR)))) { tar = g->members[i]; } - - if(g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 50) { - if(!tar || ((g->members[i]->GetPet()->GetHPRatio() + 25) < tar->GetHPRatio())) { - tar = g->members[i]->GetPet(); - } - } } } @@ -2281,6 +2353,7 @@ bool Merc::AICastSpell(int8 iChance, int16 iSpellTypes) { for( int i = 0; i < MAX_GROUP_MEMBERS; i++) { if(g->members[i]) { + int32 oDontDoAgainBefore; Mob* tar = g->members[i]; if( !tar->IsImmuneToSpell(selectedMercSpell.spellid, this) @@ -2366,6 +2439,71 @@ bool Merc::AICastSpell(int8 iChance, int16 iSpellTypes) { case SpellType_InCombatBuff: { break; } + case SpellType_Cure: { + Mob* tar = NULL; + for(int i = 0; i < MAX_GROUP_MEMBERS; i++) { + if(g->members[i] && !g->members[i]->qglobal) { + if(GetNeedsCured(g->members[i]) && (g->members[i]->DontCureMeBefore() < Timer::GetCurrentTime())) { + tar = g->members[i]; + } + } + } + + if(tar && !(g->GetNumberNeedingHealedInGroup(IsEngaged() ? 25 : 40, false) > 0) && !(g->GetNumberNeedingHealedInGroup(IsEngaged() ? 40 : 60, false) > 2)) + { + selectedMercSpell = GetBestMercSpellForCure(this, tar); + + if(selectedMercSpell.spellid == 0) + break; + + uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore(); + + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, tar, spells[selectedMercSpell.spellid].mana, &TempDontCureMeBeforeTime); + + if(castedSpell) { + if(IsGroupSpell(selectedMercSpell.spellid)){ + Group *g; + + if(this->HasGroup()) { + Group *g = this->GetGroup(); + + if(g) { + for( int i = 0; imembers[i] && !g->members[i]->qglobal) { + if(TempDontCureMeBeforeTime != tar->DontCureMeBefore()) + g->members[i]->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + } + } + else { + if(TempDontCureMeBeforeTime != tar->DontCureMeBefore()) + tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + break; + } + case SpellType_Resurrect: { + Corpse *corpse = GetGroupMemberCorpse(); + + if(corpse) { + selectedMercSpell = GetFirstMercSpellBySpellType(this, SpellType_Resurrect); + + if(selectedMercSpell.spellid == 0) + break; + + uint32 TempDontRootMeBeforeTime = corpse->DontRootMeBefore(); + + castedSpell = AIDoSpellCast(selectedMercSpell.spellid, corpse, spells[selectedMercSpell.spellid].mana, &TempDontRootMeBeforeTime); + + //CastSpell(selectedMercSpell.spellid, corpse->GetID(), 1, -1, -1, &TempDontRootMeBeforeTime); + corpse->SetDontRootMeBefore(TempDontRootMeBeforeTime); + } + + break; + } } } } @@ -3077,6 +3215,157 @@ MercSpell Merc::GetBestMercSpellForHate(Merc* caster) { return result; } +MercSpell Merc::GetBestMercSpellForCure(Merc* caster, Mob *tar) { + MercSpell result; + bool spellSelected = false; + + result.spellid = 0; + result.stance = 0; + result.type = 0; + result.slot = 0; + result.proc_chance = 0; + result.time_cancast = 0; + + if(!tar) + return result; + + int countNeedsCured = 0; + bool isPoisoned = tar->FindType(SE_PoisonCounter); + bool isDiseased = tar->FindType(SE_DiseaseCounter); + bool isCursed = tar->FindType(SE_CurseCounter); + bool isCorrupted = tar->FindType(SE_CorruptionCounter); + + if(caster && caster->AI_HasSpells()) { + std::list cureList = GetMercSpellsBySpellType(caster, SpellType_Cure); + + if(tar->HasGroup()) { + Group *g = tar->GetGroup(); + + if(g) { + for( int i = 0; imembers[i] && !g->members[i]->qglobal) { + if(caster->GetNeedsCured(g->members[i])) + countNeedsCured++; + } + } + } + } + + //Check for group cure first + if(countNeedsCured > 2) { + for(list::iterator itr = cureList.begin(); itr != cureList.end(); itr++) { + MercSpell selectedMercSpell = *itr; + + if(IsGroupSpell(itr->spellid) && CheckSpellRecastTimers(caster, itr->spellid)) { + if(selectedMercSpell.spellid == 0) + continue; + + if(isPoisoned && IsEffectInSpell(itr->spellid, SE_PoisonCounter)) { + spellSelected = true; + } + else if(isDiseased && IsEffectInSpell(itr->spellid, SE_DiseaseCounter)) { + spellSelected = true; + } + else if(isCursed && IsEffectInSpell(itr->spellid, SE_CurseCounter)) { + spellSelected = true; + } + else if(isCorrupted && IsEffectInSpell(itr->spellid, SE_CorruptionCounter)) { + spellSelected = true; + } + else if(IsEffectInSpell(itr->spellid, SE_DispelDetrimental)) { + spellSelected = true; + } + + if(spellSelected) + { + result.spellid = itr->spellid; + result.stance = itr->stance; + result.type = itr->type; + result.slot = itr->slot; + result.proc_chance = itr->proc_chance; + result.time_cancast = itr->time_cancast; + + break; + } + } + } + } + + //no group cure for target- try to find single target spell + if(!spellSelected) { + for(list::iterator itr = cureList.begin(); itr != cureList.end(); itr++) { + MercSpell selectedMercSpell = *itr; + + if(CheckSpellRecastTimers(caster, itr->spellid)) { + if(selectedMercSpell.spellid == 0) + continue; + + if(isPoisoned && IsEffectInSpell(itr->spellid, SE_PoisonCounter)) { + spellSelected = true; + } + else if(isDiseased && IsEffectInSpell(itr->spellid, SE_DiseaseCounter)) { + spellSelected = true; + } + else if(isCursed && IsEffectInSpell(itr->spellid, SE_CurseCounter)) { + spellSelected = true; + } + else if(isCorrupted && IsEffectInSpell(itr->spellid, SE_CorruptionCounter)) { + spellSelected = true; + } + else if(IsEffectInSpell(itr->spellid, SE_DispelDetrimental)) { + spellSelected = true; + } + + if(spellSelected) + { + result.spellid = itr->spellid; + result.stance = itr->stance; + result.type = itr->type; + result.slot = itr->slot; + result.proc_chance = itr->proc_chance; + result.time_cancast = itr->time_cancast; + + break; + } + } + } + } + } + + return result; +} + +bool Merc::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 = tar->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; + } + } + } + } + } + } + + return needCured; +} + void Merc::MercGroupSay(Mob *speaker, const char *msg, ...) { @@ -3128,7 +3417,7 @@ bool Merc::UseDiscipline(int32 spell_id, int32 target) { if(spell.recast_time > 0) { if(CheckDisciplineRecastTimers(this, spells[spell_id].EndurTimerIndex)) { - if(spells[spell_id].EndurTimerIndex > 0 && spells[spell_id].EndurTimerIndex < MAX_DISCIPLINE_TIMERS) { + if(spells[spell_id].EndurTimerIndex > 0) { SetDisciplineRecastTimer(spells[spell_id].EndurTimerIndex, spell_id, spell.recast_time); } } @@ -3233,9 +3522,171 @@ bool Merc::CheckTaunt() { bool Merc::CheckAETaunt() { //need to check area for mobs needing taunted + MercSpell mercSpell = GetBestMercSpellForAETaunt(this); + uint8 result = 0; + + if(mercSpell.spellid != 0) { + + std::list npc_list; + entity_list.GetNPCList(npc_list); + + for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); itr++) { + NPC* npc = *itr; + + if(npc->DistNoRootNoZ(*this) <= GetActSpellRange(mercSpell.spellid, spells[mercSpell.spellid].range)) { + if(!npc->IsMezzed()) { + if(HasGroup()) { + Group* g = GetGroup(); + + if(g) { + for(int i = 0; i < g->GroupCount(); i++) { + //if(npc->IsOnHatelist(g->members[i]) && g->members[i]->GetTarget() != npc && g->members[i]->IsEngaged()) { + if(GetTarget() != npc && g->members[i]->GetTarget() != npc && npc->IsOnHatelist(g->members[i])) { + result++; + } + } + } + } + } + } + } + + if(result >= 1) { + if(MERC_DEBUG > 0) + Message(7, "%s: Attempting AE Taunt", GetCleanName()); + return true; + } + } return false; } +Corpse* Merc::GetGroupMemberCorpse() { + Corpse* corpse = NULL; + + if(HasGroup()) { + Group* g = GetGroup(); + + if(g) { + for(int i = 0; i < g->GroupCount(); i++) { + if(g->members[i] && g->members[i]->IsClient()) { + corpse = entity_list.GetCorpseByOwnerWithinRange(g->members[i]->CastToClient(), this, RuleI(Mercs, ResurrectRadius)); + + if(corpse && !corpse->Rezzed()) { + return corpse; + } + } + } + } + } + return 0; +} + +bool Merc::TryHide() { + if(GetClass() != MELEEDPS) { + return false; + } + + //don't hide if already hidden + if(hidden == true) { + return false; + } + + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 1; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + hidden = true; + + return true; +} + +//Checks if Merc still has confidence. Can be checked to begin fleeing, or to regain confidence after confidence loss - true = confident, false = confidence loss +bool Merc::CheckConfidence() { + bool result = true; + int ConfidenceLossChance = 0; + float ConfidenceCheck = 0; + int ConfidenceRating = 2 * GetProficiencyID(); + + std::list npc_list; + entity_list.GetNPCList(npc_list); + + for(std::list::iterator itr = npc_list.begin(); itr != npc_list.end(); itr++) { + NPC* mob = *itr; + float ConRating = 1.0; + int CurrentCon = 0; + + if(!mob) continue; + + if(!mob->IsEngaged()) continue; + + if(mob->IsFeared() || mob->IsMezzed() || mob->IsStunned() || mob->IsRooted() || mob->IsCharmed()) continue; + + if(!mob->CheckAggro(this)) continue; + + float AggroRange = mob->GetAggroRange(); + + // Square it because we will be using DistNoRoot + + AggroRange = AggroRange * AggroRange; + + if(mob->DistNoRoot(*this) > AggroRange) continue; + + CurrentCon = this->GetLevelCon(mob->GetLevel()); + switch(CurrentCon) { + + case CON_GREEN: { + ConRating = 0; + break; + } + + case CON_LIGHTBLUE: { + ConRating = 0.2; + break; + } + + case CON_BLUE: { + ConRating = 0.6; + break; + } + + case CON_WHITE: { + ConRating = 1.0; + break; + } + + case CON_YELLOW: { + ConRating = 1.2; + break; + } + + case CON_RED: { + ConRating = 1.5; + break; + } + + default: { + ConRating = 0; + break; + } + } + + ConfidenceCheck += ConRating; + } + + if(ConfidenceRating < ConfidenceCheck) { + ConfidenceLossChance = 25 - ( 5 * (GetTierID() - 1)); + } + + if(MakeRandomInt(0 ,100) < ConfidenceLossChance) { + result = false; + } + + return result; +} + void Merc::MercMeditate(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 @@ -3527,8 +3978,8 @@ void Merc::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_sk //corpse->Depop(); //no corpse, no exp if we're a merc. We'll suspend instead, since that's what live does. I'm not actually sure live supports 'depopping' merc corpses. - //if(entity_list.GetCorpseByID(GetID())) - // entity_list.GetCorpseByID(GetID())->Depop(); + if(entity_list.GetCorpseByID(GetID())) + entity_list.GetCorpseByID(GetID())->Depop(); if(Suspend()) { @@ -3677,7 +4128,7 @@ bool Merc::LoadMercSpells() { return false; } - uint32 proficiency_id = GetProficiencyID(); + uint8 proficiency_id = GetProficiencyID(); int16 attack_proc_spell = -1; int8 proc_chance = 0; @@ -3707,17 +4158,33 @@ bool Merc::LoadMercSpells() { AIautocastspell_timer->Trigger(); } - if(MERC_DEBUG > 0) - Message(7, "Mercenary Debug: Loaded %i spells for merc.", merc_spells.size()); + if(MERC_DEBUG > 0) { + /*GetMercOwner()->Message(7, "Mercenary Debug: Loaded %i spells for merc.", merc_spells.size()); + + GetMercOwner()->Message(7, "Mercenary Debug: Spell list for merc."); + for (int i = merc_spells.size() - 1; i >= 0; i--) { + GetMercOwner()->Message(7, "%i] Slot: %i, SpellID: %i, Type: %i, Stance: %i, Proc Chance: %i", i, merc_spells[i].slot, merc_spells[i].spellid, merc_spells[i].type, merc_spells[i].stance, merc_spells[i].proc_chance); + }*/ + } return true; } -Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id) { - if(c) - { +bool Merc::Save() { + bool Result = false; + if(database.SaveMerc(this)){ + Result = true; + } + + return Result; +} + +Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id, bool updateFromDB) { + Merc* merc; + + if(c) { if(c->GetMercID()) { - merc_template = zone->GetMercTemplate(c->GetEPP().mercTemplateID); + merc_template = zone->GetMercTemplate(c->GetMercInfo().MercTemplateID); } } @@ -3729,13 +4196,14 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id) NPCType* npc_type = new NPCType; //This is actually a very terrible method of assigning stats, and should be changed at some point. See the comment in merc's deconstructor. memset(npc_type, 0, sizeof(NPCType)); memcpy(npc_type, npc_type_to_copy, sizeof(NPCType)); - if(c) + if(c && !updateFromDB) { - if(c->GetEPP().merc_name[0] == 0) + if(c->GetMercInfo().merc_name[0] == 0) { - snprintf(c->GetEPP().merc_name, 64, "%s", GetRandomName()); //sanity check. + snprintf(c->GetMercInfo().merc_name, 64, "%s", GetRandomName()); //sanity check. } - snprintf(npc_type->name, 64, "%s", c->GetEPP().merc_name); + snprintf(c->GetEPP().merc_name, 64, "%s", c->GetMercInfo().merc_name); + snprintf(npc_type->name, 64, "%s", c->GetMercInfo().merc_name); } uint8 gender; if(merchant_id > 0) { @@ -3745,7 +4213,7 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id) } } else { - gender = c->GetEPP().mercGender; + gender = c->GetMercInfo().Gender; } sprintf(npc_type->lastname, "%s's %s", c->GetName(), "Mercenary"); @@ -3759,6 +4227,32 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id) Merc* merc = new Merc(npc_type, c->GetX(), c->GetY(), c->GetZ(), 0); merc->SetMercData( merc_template->MercTemplateID ); merc->UpdateMercStats(c); + + if(updateFromDB) { + merc->SetMercID(c->GetMercInfo().mercid); + snprintf(merc->name, 64, "%s", c->GetMercInfo().merc_name); + snprintf(c->GetEPP().merc_name, 64, "%s", c->GetMercInfo().merc_name); + merc->SetSuspended(c->GetMercInfo().IsSuspended); + merc->gender = c->GetMercInfo().Gender; + merc->SetHP(c->GetMercInfo().hp); + merc->SetMana(c->GetMercInfo().mana); + merc->SetEndurance(c->GetMercInfo().endurance); + merc->luclinface = c->GetMercInfo().face; + merc->hairstyle = c->GetMercInfo().luclinHairStyle; + merc->haircolor = c->GetMercInfo().luclinHairColor; + merc->eyecolor1 = c->GetMercInfo().luclinEyeColor; + merc->eyecolor2 = c->GetMercInfo().luclinEyeColor2; + merc->beardcolor = c->GetMercInfo().luclinBeardColor; + merc->beard = c->GetMercInfo().luclinBeard; + merc->drakkin_heritage = c->GetMercInfo().drakkinHeritage; + merc->drakkin_tattoo = c->GetMercInfo().drakkinTattoo; + merc->drakkin_details = c->GetMercInfo().drakkinDetails; + + database.LoadMercBuffs(merc); + } + + merc->LoadMercSpells(); + return merc; } } @@ -3766,10 +4260,29 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id) return 0; } +void Merc::UpdateMercInfo(Client *c) { + snprintf(c->GetMercInfo().merc_name, 64, "%s", name); + c->GetMercInfo().IsSuspended = IsSuspended(); + c->GetMercInfo().Gender = gender; + c->GetMercInfo().hp = GetHP(); + c->GetMercInfo().mana = GetMana(); + c->GetMercInfo().endurance = GetEndurance(); + c->GetMercInfo().face = luclinface; + c->GetMercInfo().luclinHairStyle = hairstyle; + c->GetMercInfo().luclinHairColor = haircolor; + c->GetMercInfo().luclinEyeColor = eyecolor1; + c->GetMercInfo().luclinEyeColor2 = eyecolor2; + c->GetMercInfo().luclinBeardColor = beardcolor; + c->GetMercInfo().luclinBeard = beard; + c->GetMercInfo().drakkinHeritage = drakkin_heritage; + c->GetMercInfo().drakkinTattoo = drakkin_tattoo; + c->GetMercInfo().drakkinDetails = drakkin_details; +} + void Merc::UpdateMercStats(Client *c) { - if(c->GetEPP().mercTemplateID >0) + if(c->GetMercInfo().MercTemplateID >0) { - const NPCType* npc_type = database.GetMercType( zone->GetMercTemplate(c->GetEPP().mercTemplateID)->MercNPCID, GetRace(), c->GetLevel()); + const NPCType* npc_type = database.GetMercType( zone->GetMercTemplate(c->GetMercInfo().MercTemplateID)->MercNPCID, GetRace(), c->GetLevel()); if (npc_type) { max_hp = (npc_type->max_hp * npc_type->scalerate) / 100; @@ -3802,7 +4315,7 @@ void Merc::UpdateMercStats(Client *c) { healscale = npc_type->healscale; CalcBonuses(); - + CalcMaxEndurance(); CalcMaxHP(); CalcMaxMana(); @@ -3853,12 +4366,12 @@ void Client::UpdateMercTimer() Merc *merc = GetMerc(); if(merc && !merc->IsSuspended()) - { + { if(merc_timer.Check()) { uint32 upkeep = Merc::CalcUpkeepCost(merc->GetMercTemplateID(), GetLevel()); //TakeMoneyFromPP((upkeep * 100), true); // Upkeep is in gold - GetEPP().mercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + GetMercInfo().MercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); SendMercTimerPacket(GetMercID(), 5, 0, RuleI(Mercs, UpkeepIntervalMS), RuleI(Mercs, SuspendIntervalMS)); merc_timer.Start(RuleI(Mercs, UpkeepIntervalMS)); @@ -3891,10 +4404,10 @@ void Client::UpdateMercTimer() void Client::CheckMercSuspendTimer() { - if(GetEPP().mercSuspendedTime != 0) { - if(time(NULL) >= GetEPP().mercSuspendedTime){ - GetEPP().mercSuspendedTime = 0; - SendMercSuspendResponsePacket(GetEPP().mercSuspendedTime); + if(GetMercInfo().SuspendedTime != 0) { + if(time(NULL) >= GetMercInfo().SuspendedTime){ + GetMercInfo().SuspendedTime = 0; + SendMercSuspendResponsePacket(GetMercInfo().SuspendedTime); p_timers.Start(pTimerMercSuspend, RuleI(Mercs, SuspendIntervalS)); } } @@ -3902,35 +4415,35 @@ void Client::CheckMercSuspendTimer() void Client::SuspendMercCommand() { - bool ExistsMerc = GetEPP().mercTemplateID != 0; - if(ExistsMerc == true) + bool ExistsMerc = GetMercInfo().MercTemplateID != 0; + if(ExistsMerc == true) + { + if(GetMercInfo().IsSuspended) { + //p_timers.Enable(pTimerMercReuse); + + // Set time remaining to max on unsuspend - there is a charge for unsuspending as well + // GetEPP().mercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); + + // Get merc, assign it to client & spawn + Merc* merc = Merc::LoadMerc(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, false); + SpawnMerc(merc, true); + } + else { - if(GetEPP().mercIsSuspended) { - //p_timers.Enable(pTimerMercReuse); + Merc* CurrentMerc = GetMerc(); - // Set time remaining to max on unsuspend - there is a charge for unsuspending as well - // GetEPP().mercTimerRemaining = RuleI(Mercs, UpkeepIntervalMS); - - // Get merc, assign it to client & spawn - Merc* merc = Merc::LoadMerc(this, &zone->merc_templates[GetEPP().mercTemplateID], 0); - SpawnMerc(merc); - } - else - { - Merc* CurrentMerc = GetMerc(); - - if(CurrentMerc && GetMercID()) { - CurrentMerc->Suspend(); - } + if(CurrentMerc && GetMercID()) { + CurrentMerc->Save(); + CurrentMerc->Suspend(); } } + } } // Handles all client zone change event void Merc::ProcessClientZoneChange(Client* mercOwner) { - if(mercOwner) - { + if(mercOwner) { Zone(); } } @@ -3943,33 +4456,35 @@ void Client::SpawnMercOnZone() if (GetMerc()) return; - bool ExistsMerc = GetEPP().mercTemplateID != 0; + bool ExistsMerc = GetEPP().merc_name[0] != 0; if(ExistsMerc == true) { - if(!GetEPP().mercIsSuspended) { - GetEPP().mercSuspendedTime = 0; + if(!GetMercInfo().IsSuspended) { + GetMercInfo().SuspendedTime = 0; // Get merc, assign it to client & spawn - Merc* merc = Merc::LoadMerc(this, &zone->merc_templates[GetEPP().mercTemplateID], 0); - SpawnMerc(merc); + if(database.LoadMercInfo(this)) { + Merc* merc = Merc::LoadMerc(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true); + SpawnMerc(merc, false); + } } else { // Send Mercenary Status/Timer packet - SendMercTimerPacket(0, 1, GetEPP().mercSuspendedTime, RuleI(Mercs, UpkeepIntervalMS), RuleI(Mercs, SuspendIntervalMS)); + SendMercTimerPacket(0, 1, GetMercInfo().SuspendedTime, RuleI(Mercs, UpkeepIntervalMS), RuleI(Mercs, SuspendIntervalMS)); SendMercPersonalInfo(); - if(GetEPP().mercSuspendedTime != 0) { - if(time(NULL) >= GetEPP().mercSuspendedTime){ - GetEPP().mercSuspendedTime = 0; + if(GetMercInfo().SuspendedTime != 0) { + if(time(NULL) >= GetMercInfo().SuspendedTime){ + GetMercInfo().SuspendedTime = 0; } } - SendMercSuspendResponsePacket(GetEPP().mercSuspendedTime); + SendMercSuspendResponsePacket(GetMercInfo().SuspendedTime); } } } -void Client::SpawnMerc(Merc* merc) +void Client::SpawnMerc(Merc* merc, bool setMaxStats) { if(!RuleB(Mercs, AllowMercs)) return; @@ -3978,16 +4493,12 @@ void Client::SpawnMerc(Merc* merc) merc->Spawn(this); merc->SetSuspended(false); SetMerc(merc); - merc->Unsuspend(); + merc->Unsuspend(setMaxStats); } } bool Merc::Suspend() { - Client* mercOwner; - - if(GetMercOwner()) { - mercOwner = GetMercOwner(); - } + Client* mercOwner = GetMercOwner(); if(!mercOwner) return false; @@ -3998,22 +4509,23 @@ bool Merc::Suspend() { RemoveMercFromGroup(this, GetGroup()); } - //Save(); + Save(); - mercOwner->GetEPP().mercIsSuspended = true; - mercOwner->GetEPP().mercSuspendedTime = time(NULL) + RuleI(Mercs, SuspendIntervalS); - mercOwner->GetEPP().mercTimerRemaining = mercOwner->GetMercTimer().GetRemainingTime(); + mercOwner->GetMercInfo().IsSuspended = true; + mercOwner->GetMercInfo().SuspendedTime = time(NULL) + RuleI(Mercs, SuspendIntervalS); + mercOwner->GetMercInfo().MercTimerRemaining = mercOwner->GetMercTimer().GetRemainingTime(); mercOwner->GetMercTimer().Disable(); //mercOwner->UpdateMercTimer(); - mercOwner->SendMercSuspendResponsePacket(mercOwner->GetEPP().mercSuspendedTime); + mercOwner->SendMercSuspendResponsePacket(mercOwner->GetMercInfo().SuspendedTime); Depop(); return true; } -bool Merc::Unsuspend() { +bool Merc::Unsuspend(bool setMaxStats) { Client* mercOwner; + bool loaded = false; if(GetMercOwner()) { mercOwner = GetMercOwner(); @@ -4022,21 +4534,20 @@ bool Merc::Unsuspend() { if(!mercOwner) return false; - if(GetMercID()) { - uint32 entityID = 0; + if(GetID()) { uint32 mercState = 5; uint32 suspendedTime = 0; SetSuspended(false); - mercOwner->GetEPP().mercIsSuspended = false; - mercOwner->GetEPP().mercSuspendedTime = 0; + mercOwner->GetMercInfo().IsSuspended = false; + mercOwner->GetMercInfo().SuspendedTime = 0; mercOwner->SendMercenaryUnsuspendPacket(0); mercOwner->SendMercenaryUnknownPacket(1); - mercOwner->SendMercTimerPacket(GetMercID(), mercState, suspendedTime, RuleI(Mercs, UpkeepIntervalMS), RuleI(Mercs, SuspendIntervalMS)); + mercOwner->SendMercTimerPacket(GetID(), mercState, suspendedTime, RuleI(Mercs, UpkeepIntervalMS), RuleI(Mercs, SuspendIntervalMS)); - mercOwner->GetMercTimer().Start(mercOwner->GetEPP().mercTimerRemaining); + mercOwner->GetMercTimer().Start(mercOwner->GetMercInfo().MercTimerRemaining); if(!mercOwner->GetPTimers().Expired(&database, pTimerMercSuspend, false)) mercOwner->GetPTimers().Clear(&database, pTimerMercSuspend); @@ -4054,12 +4565,16 @@ bool Merc::Unsuspend() { database.SetGroupID(this->GetName(), g->GetID(), mercOwner->CharacterID(), true); database.RefreshGroupFromDB(mercOwner); g->SaveGroupLeaderAA(); + + loaded = true; } } else if (AddMercToGroup(this, mercOwner->GetGroup())) { database.SetGroupID(GetName(), mercOwner->GetGroup()->GetID(), mercOwner->CharacterID(), true); database.RefreshGroupFromDB(mercOwner); + + loaded = true; } else { @@ -4069,7 +4584,16 @@ bool Merc::Unsuspend() { Suspend(); } - LoadMercSpells(); + + if(loaded) { + LoadMercSpells(); + + if(setMaxStats) { + SetHP(GetMaxHP()); + SetMana(GetMaxMana()); + SetEndurance(GetMaxEndurance()); + } + } } return true; @@ -4086,19 +4610,21 @@ bool Merc::Dismiss() { return false; mercOwner->SendClearMercInfo(); - mercOwner->SendMercTimerPacket(GetMercID(), 5, 0, RuleI(Mercs, UpkeepIntervalMS), RuleI(Mercs, SuspendIntervalMS)); + mercOwner->SendMercTimerPacket(GetID(), 5, 0, RuleI(Mercs, UpkeepIntervalMS), RuleI(Mercs, SuspendIntervalMS)); - SetMercID(0); + //SetMercEntityID(0); mercOwner->SetMerc(0); + database.DeleteMerc(GetMercID()); + Depop(); return true; } void Merc::Zone() { - //Save(); + Save(); Depop(); } @@ -4186,6 +4712,12 @@ bool Merc::AddMercToGroup(Merc* merc, Group* group) { return Result; } +void Client::InitializeMercInfo() { + for(int i=0; iGetMercTemplate(template_id); SetMercTemplateID( merc_template->MercTemplateID ); SetMercType( merc_template->MercType ); SetMercSubType( merc_template->MercSubType ); SetProficiencyID( merc_template->ProficiencyID ); + SetTierID( merc_template->TierID ); SetCostFormula( merc_template->CostFormula ); SetMercNameType( merc_template->MercNameType ); } @@ -4225,24 +4770,26 @@ void Client::SetMerc(Merc* newmerc) { } if (!newmerc) { SetMercID(0); - GetEPP().mercTemplateID = 0; - GetEPP().mercIsSuspended = false; - GetEPP().mercSuspendedTime = 0; - GetEPP().mercGender = 0; - GetEPP().mercState = 0; - GetEPP().mercTimerRemaining = 0; + GetMercInfo().MercTemplateID = 0; + GetMercInfo().IsSuspended = false; + GetMercInfo().SuspendedTime = 0; + GetMercInfo().Gender = 0; + GetMercInfo().State = 0; + GetMercInfo().MercTimerRemaining = 0; + memset(GetMercInfo().merc_name, 0, 64); memset(GetEPP().merc_name, 0, 64); } else { SetMercID(newmerc->GetID()); - newmerc->SetMercID(newmerc->GetID()); + //newmerc->SetMercEntityID(newmerc->GetID()); //Client* oldowner = entity_list.GetClientByID(newmerc->GetOwnerID()); newmerc->SetOwnerID(this->GetID()); newmerc->SetMercCharacterID(this->CharacterID()); newmerc->SetClientVersion((uint8)this->GetClientVersion()); - GetEPP().mercTemplateID = newmerc->GetMercTemplateID(); - GetEPP().mercIsSuspended = newmerc->IsSuspended(); - GetEPP().mercSuspendedTime = 0; - GetEPP().mercGender = newmerc->GetGender(); + GetMercInfo().MercTemplateID = newmerc->GetMercTemplateID(); + GetMercInfo().myTemplate = zone->GetMercTemplate(GetMercInfo().MercTemplateID); + GetMercInfo().IsSuspended = newmerc->IsSuspended(); + GetMercInfo().SuspendedTime = 0; + GetMercInfo().Gender = newmerc->GetGender(); } } @@ -4291,7 +4838,7 @@ void Client::SendMercSuspendResponsePacket(uint32 suspended_time) { void Client::SendMercTimerPacket(int32 entity_id, int32 merc_state, int32 suspended_time, int32 update_interval, int32 unk01) { if (GetClientVersion() == EQClientSoD) { - update_interval = GetEPP().mercTimerRemaining; + update_interval = GetMercInfo().MercTimerRemaining; } // Send Mercenary Status/Timer packet @@ -4501,4 +5048,4 @@ uint32 Merc::CalcUpkeepCost( uint32 templateID , uint8 level, uint8 currency_typ } return cost/100; -} \ No newline at end of file +} diff --git a/zone/merc.h b/zone/merc.h index f5ef75360..3c8cdea61 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -5,6 +5,7 @@ #include "npc.h" using namespace std; +#define MAXMERCS 1 #define MERC_DEBUG 0 #define TANK 1 #define HEALER 2 @@ -26,7 +27,7 @@ enum MercStanceType { struct MercSpell { uint16 spellid; // <= 0 = no spell - uint16 type; // 0 = never, must be one (and only one) of the defined values + uint32 type; // 0 = never, must be one (and only one) of the defined values int16 stance; // 0 = all, + = only this stance, - = all except this stance int16 slot; uint16 proc_chance; @@ -60,7 +61,7 @@ public: virtual void AI_Process(); //virtual bool AICastSpell(Mob* tar, int8 iChance, int16 iSpellTypes); - virtual bool AICastSpell(int8 iChance, int16 iSpellTypes); + virtual bool AICastSpell(int8 iChance, int32 iSpellTypes); virtual bool AIDoSpellCast(uint16 spellid, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); virtual bool AI_EngagedCastCheck(); //virtual bool AI_PursueCastCheck(); @@ -73,6 +74,7 @@ public: static bool RemoveMercFromGroup(Merc* merc, Group* group); void ProcessClientZoneChange(Client* mercOwner); static void MercGroupSay(Mob *speaker, const char *msg, ...); + Corpse* GetGroupMemberCorpse(); // Merc Spell Casting Methods int8 GetChanceToCastBySpellType(int16 spellType); @@ -100,21 +102,25 @@ public: static MercSpell GetBestMercSpellForAETaunt(Merc* caster); static MercSpell GetBestMercSpellForTaunt(Merc* caster); static MercSpell GetBestMercSpellForHate(Merc* caster); + static MercSpell GetBestMercSpellForCure(Merc* caster, Mob* target); + static bool GetNeedsCured(Mob *tar); bool UseDiscipline(int32 spell_id, int32 target); virtual bool IsMerc() const { return true; } virtual void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho); - static Merc* LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id); + static Merc* LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id, bool updateFromDB = false); + void UpdateMercInfo(Client *c); void UpdateMercStats(Client *c); void UpdateMercAppearance(Client *c); static const char *GetRandomName(); bool Spawn(Client *owner); bool Dismiss(); bool Suspend(); - bool Unsuspend(); + bool Unsuspend(bool setMaxStats); void Zone(); virtual void Depop(); + virtual bool Save(); bool GetDepop() { return p_depop; } bool IsDead() { return GetHP() < 0;}; @@ -133,10 +139,12 @@ public: uint32 GetMercTemplateID() { return _MercTemplateID; } uint32 GetMercType() { return _MercType; } uint32 GetMercSubType() { return _MercSubType; } - uint32 GetProficiencyID() { return _ProficiencyID; } + uint8 GetProficiencyID() { return _ProficiencyID; } + uint8 GetTierID() { return _TierID; } uint32 GetCostFormula() { return _CostFormula; } uint32 GetMercNameType() { return _NameType; } uint32 GetStance() { return _currentStance; } + int GetHatedCount() { return _hatedCount; } inline const uint8 GetClientVersion() const { return _OwnerClientVersion; } @@ -148,9 +156,12 @@ public: virtual void DoClassAttacks(Mob *target); bool CheckTaunt(); bool CheckAETaunt(); + bool CheckConfidence(); + bool TryHide(); // stat functions virtual void CalcBonuses(); + int32 GetEndurance() const {return cur_end;} //This gets our current endurance inline virtual int16 GetAC() const { return AC; } inline virtual int16 GetATK() const { return ATK; } inline virtual int16 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } @@ -215,11 +226,13 @@ public: void SetMercType( uint32 type ) { _MercType = type; } void SetMercSubType( uint32 subtype ) { _MercSubType = subtype; } void SetProficiencyID( uint8 proficiency_id ) { _ProficiencyID = proficiency_id; } + void SetTierID( uint8 tier_id ) { _TierID = tier_id; } void SetCostFormula( uint8 costformula ) { _CostFormula = costformula; } void SetMercNameType( uint8 nametype ) { _NameType = nametype; } void SetClientVersion(uint8 clientVersion) { _OwnerClientVersion = clientVersion; } void SetSuspended(bool suspended) { _suspended = suspended; } void SetStance( uint32 stance ) { _currentStance = stance; } + void SetHatedCount( uint8 count ) { _hatedCount = count; } void Sit(); void Stand(); @@ -283,7 +296,6 @@ private: int32 CalcManaRegenCap(); void CalcMaxEndurance(); //This calculates the maximum endurance we can have int32 CalcBaseEndurance(); //Calculates Base End - int32 GetEndurance() const {return cur_end;} //This gets our current endurance int32 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call int32 CalcEnduranceRegen(); //Calculates endurance regen used in DoEnduranceRegen() int32 CalcEnduranceRegenCap(); @@ -331,6 +343,7 @@ private: uint32 _MercType; uint32 _MercSubType; uint8 _ProficiencyID; + uint8 _TierID; uint8 _CostFormula; uint8 _NameType; uint8 _OwnerClientVersion; @@ -342,11 +355,15 @@ private: bool _medding; bool _suspended; bool p_depop; + bool _check_confidence; + bool _lost_confidence; + int _hatedCount; uint32 owner_char_id; const NPCType* ourNPCData; Timer endupkeep_timer; Timer rest_timer; + Timer confidence_timer; }; -#endif // MERC_H \ No newline at end of file +#endif // MERC_H diff --git a/zone/spdat.h b/zone/spdat.h index 90b01e3d8..7222b6bd7 100644 --- a/zone/spdat.h +++ b/zone/spdat.h @@ -65,6 +65,7 @@ const int SpellType_Charm=4096; const int SpellType_Slow = 8192; const int SpellType_Debuff = 16384; const int SpellType_Cure = 32768; +const int SpellType_Resurrect = 65536; const int SpellTypes_Detrimental = SpellType_Nuke|SpellType_Root|SpellType_Lifetap|SpellType_Snare|SpellType_DOT|SpellType_Dispel|SpellType_Mez|SpellType_Charm|SpellType_Debuff|SpellType_Slow; const int SpellTypes_Beneficial = SpellType_Heal|SpellType_Buff|SpellType_Escape|SpellType_Pet|SpellType_InCombatBuff|SpellType_Cure; diff --git a/zone/zone.cpp b/zone/zone.cpp index ce4b38623..46bf13b33 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -660,7 +660,7 @@ void Zone::LoadMercTemplates(){ mysql_free_result(DatasetResult); } - if(!database.RunQuery(Query, MakeAnyLenString(&Query, "SELECT DISTINCT MTem.merc_template_id, MTyp.dbstring AS merc_type_id, MTem.dbstring AS merc_subtype_id, MTyp.race_id, MS.class_id, MTyp.proficiency_id, 0 AS CostFormula, MTem.clientversion, MTem.merc_npc_type_id FROM merc_types MTyp, merc_templates MTem, merc_subtypes MS WHERE MTem.merc_type_id = MTyp.merc_type_id AND MTem.merc_subtype_id = MS.merc_subtype_id ORDER BY MTyp.race_id, MS.class_id, MTyp.proficiency_id;"), TempErrorMessageBuffer, &DatasetResult)) { + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "SELECT DISTINCT MTem.merc_template_id, MTyp.dbstring AS merc_type_id, MTem.dbstring AS merc_subtype_id, MTyp.race_id, MS.class_id, MTyp.proficiency_id, MS.tier_id, 0 AS CostFormula, MTem.clientversion, MTem.merc_npc_type_id FROM merc_types MTyp, merc_templates MTem, merc_subtypes MS WHERE MTem.merc_type_id = MTyp.merc_type_id AND MTem.merc_subtype_id = MS.merc_subtype_id ORDER BY MTyp.race_id, MS.class_id, MTyp.proficiency_id;"), TempErrorMessageBuffer, &DatasetResult)) { errorMessage = std::string(TempErrorMessageBuffer); } else { @@ -673,9 +673,10 @@ void Zone::LoadMercTemplates(){ tempMercTemplate.RaceID = atoi(DataRow[3]); tempMercTemplate.ClassID = atoi(DataRow[4]); tempMercTemplate.ProficiencyID = atoi(DataRow[5]); - tempMercTemplate.CostFormula = atoi(DataRow[6]); - tempMercTemplate.ClientVersion = atoi(DataRow[7]); - tempMercTemplate.MercNPCID = atoi(DataRow[8]); + tempMercTemplate.TierID = atoi(DataRow[6]); + tempMercTemplate.CostFormula = atoi(DataRow[7]); + tempMercTemplate.ClientVersion = atoi(DataRow[8]); + tempMercTemplate.MercNPCID = atoi(DataRow[9]); for(std::list::iterator mercStanceListItr = merc_stances.begin(); mercStanceListItr != merc_stances.end(); mercStanceListItr++) { if(mercStanceListItr->ClassID == tempMercTemplate.ClassID && mercStanceListItr->ProficiencyID == tempMercTemplate.ProficiencyID) { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 7ef5b7ebe..f94960ac1 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -7,6 +7,7 @@ #include "../common/rulesys.h" #include "zone.h" #include "client.h" +#include "merc.h" #include "groups.h" #include "raids.h" #include @@ -1632,6 +1633,279 @@ const NPCType* ZoneDatabase::GetMercType(uint32 id, uint16 raceid, uint32 client return npc; } +bool ZoneDatabase::LoadMercInfo(Client *c) { + bool loaded = false; + + if(c->GetEPP().merc_name[0] != 0) { + std::string errorMessage; + char* Query = 0; + char TempErrorMessageBuffer[MYSQL_ERRMSG_SIZE]; + MYSQL_RES* DatasetResult; + MYSQL_ROW DataRow; + //char name[64]; + + //CleanMobName(c->GetEPP().merc_name, name); + + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "SELECT MercID, Slot, Name, TemplateID, SuspendedTime, IsSuspended, TimerRemaining, Gender, StanceID, HP, Mana, Endurance, Face, LuclinHairStyle, LuclinHairColor, LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, LuclinBeard, DrakkinHeritage, DrakkinTattoo, DrakkinDetails FROM mercs WHERE OwnerCharacterID = '%i' ORDER BY Slot", c->CharacterID()), TempErrorMessageBuffer, &DatasetResult)) { + errorMessage = std::string(TempErrorMessageBuffer); + } + else { + while(DataRow = mysql_fetch_row(DatasetResult)) { + uint8 slot = atoi(DataRow[1]); + c->GetMercInfo(slot).mercid = atoi(DataRow[0]); + c->GetMercInfo(slot).slot = slot; + snprintf(c->GetMercInfo(slot).merc_name, 64, "%s", std::string(DataRow[2]).c_str()); + c->GetMercInfo(slot).MercTemplateID = atoi(DataRow[3]); + c->GetMercInfo(slot).SuspendedTime = atoi(DataRow[4]); + c->GetMercInfo(slot).IsSuspended = atoi(DataRow[5]) == 1 ? true : false; + c->GetMercInfo(slot).MercTimerRemaining = atoi(DataRow[6]); + c->GetMercInfo(slot).Gender = atoi(DataRow[7]); + c->GetMercInfo(slot).State = atoi(DataRow[8]); + c->GetMercInfo(slot).hp = atoi(DataRow[9]); + c->GetMercInfo(slot).mana = atoi(DataRow[10]); + c->GetMercInfo(slot).endurance = atoi(DataRow[11]); + c->GetMercInfo(slot).face = atoi(DataRow[12]); + c->GetMercInfo(slot).luclinHairStyle = atoi(DataRow[13]); + c->GetMercInfo(slot).luclinHairColor = atoi(DataRow[14]); + c->GetMercInfo(slot).luclinEyeColor = atoi(DataRow[15]); + c->GetMercInfo(slot).luclinEyeColor2 = atoi(DataRow[16]); + c->GetMercInfo(slot).luclinBeardColor = atoi(DataRow[17]); + c->GetMercInfo(slot).luclinBeard = atoi(DataRow[18]); + c->GetMercInfo(slot).drakkinHeritage = atoi(DataRow[19]); + c->GetMercInfo(slot).drakkinTattoo = atoi(DataRow[20]); + c->GetMercInfo(slot).drakkinDetails = atoi(DataRow[21]); + loaded = true; + } + + mysql_free_result(DatasetResult); + } + + safe_delete_array(Query); + } + + return loaded; +} + +bool ZoneDatabase::SaveMerc(Merc *merc) { + Client *owner = merc->GetMercOwner(); + bool Result = false; + std::string errorMessage; + + if(!owner) { + return false; + } + + char* Query = 0; + char TempErrorMessageBuffer[MYSQL_ERRMSG_SIZE]; + uint32 affectedRows = 0; + + if(merc->GetMercID() == 0) { + // New merc record + uint32 TempNewMercID = 0; + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "INSERT INTO mercs (OwnerCharacterID, Slot, Name, TemplateID, SuspendedTime, IsSuspended, TimerRemaining, Gender, StanceID, HP, Mana, Endurance, Face, LuclinHairStyle, LuclinHairColor, LuclinEyeColor, LuclinEyeColor2, LuclinBeardColor, LuclinBeard, DrakkinHeritage, DrakkinTattoo, DrakkinDetails) VALUES('%u', '%u', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%u', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i', '%i')", merc->GetMercCharacterID(), owner->GetNumMercs(), merc->GetCleanName(), merc->GetMercTemplateID(), owner->GetMercInfo().SuspendedTime, merc->IsSuspended(), owner->GetMercInfo().MercTimerRemaining, merc->GetGender(), merc->GetStance(), merc->GetHP(), merc->GetMana(), merc->GetEndurance(), merc->GetLuclinFace(), merc->GetHairStyle(), merc->GetHairColor(), merc->GetEyeColor1(), merc->GetEyeColor2(), merc->GetBeardColor(), merc->GetBeard(), merc->GetDrakkinHeritage(), merc->GetDrakkinTattoo(), merc->GetDrakkinDetails() ), TempErrorMessageBuffer, 0, &affectedRows, &TempNewMercID)) { + errorMessage = std::string(TempErrorMessageBuffer); + } + else { + merc->SetMercID(TempNewMercID); + Result = true; + } + } + else { + // Update existing merc record + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "UPDATE mercs SET OwnerCharacterID = '%u', Slot = '%u', Name = '%s', TemplateID = '%u', SuspendedTime = '%u', IsSuspended = '%u', TimerRemaining = '%u', Gender = '%u', StanceID = '%u', HP = '%u', Mana = '%u', Endurance = '%u', Face = '%i', LuclinHairStyle = '%i', LuclinHairColor = '%i', LuclinEyeColor = '%i', LuclinEyeColor2 = '%i', LuclinBeardColor = '%i', LuclinBeard = '%i', DrakkinHeritage = '%i', DrakkinTattoo = '%i', DrakkinDetails = '%i' WHERE MercID = '%u'", merc->GetMercCharacterID(), owner->GetMercSlot(), merc->GetCleanName(), merc->GetMercTemplateID(), owner->GetMercInfo().SuspendedTime, merc->IsSuspended(), owner->GetMercInfo().MercTimerRemaining, merc->GetGender(), merc->GetStance(), merc->GetHP(), merc->GetMana(), merc->GetEndurance(), merc->GetLuclinFace(), merc->GetHairStyle(), merc->GetHairColor(), merc->GetEyeColor1(), merc->GetEyeColor2(), merc->GetBeardColor(), merc->GetBeard(), merc->GetDrakkinHeritage(), merc->GetDrakkinTattoo(), merc->GetDrakkinDetails(), merc->GetMercID()), TempErrorMessageBuffer, 0, &affectedRows)) { + errorMessage = std::string(TempErrorMessageBuffer); + } + else { + Result = true; + //time(&_startTotalPlayTime); + } + } + + safe_delete(Query); + + if(!errorMessage.empty() || (Result && affectedRows != 1)) { + if(owner && !errorMessage.empty()) + owner->Message(13, errorMessage.c_str()); + else if(owner) + owner->Message(13, std::string("Unable to save merc to the database.").c_str()); + + Result = false; + } + else { + merc->UpdateMercInfo(owner); + database.SaveMercBuffs(merc); + //database.SaveMercStance(this); + //database.SaveMercTimers(this); + } + + return Result; +} + +void ZoneDatabase::SaveMercBuffs(Merc *merc) { + Buffs_Struct *buffs = merc->GetBuffs(); + std::string errorMessage; + char* Query = 0; + char TempErrorMessageBuffer[MYSQL_ERRMSG_SIZE]; + int BuffCount = 0; + int InsertCount = 0; + + uint32 buff_count = merc->GetMaxBuffSlots(); + while(BuffCount < BUFF_COUNT) { + if(buffs[BuffCount].spellid > 0 && buffs[BuffCount].spellid != SPELL_UNKNOWN) { + if(InsertCount == 0) { + // Remove any existing buff saves + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "DELETE FROM mercbuffs WHERE MercId = %u", merc->GetMercID()), TempErrorMessageBuffer)) { + errorMessage = std::string(TempErrorMessageBuffer); + safe_delete(Query); + Query = 0; + break; + } + } + + int IsPersistent = 0; + + if(buffs[BuffCount].persistant_buff) + IsPersistent = 1; + else + IsPersistent = 0; + + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "INSERT INTO mercbuffs (MercId, SpellId, CasterLevel, DurationFormula, " + "TicsRemaining, PoisonCounters, DiseaseCounters, CurseCounters, CorruptionCounters, HitCount, MeleeRune, MagicRune, " + "DeathSaveSuccessChance, CasterAARank, Persistent) VALUES (%u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u, %u);", + 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, + CalculateDiseaseCounters(buffs[BuffCount].spellid) > 0 ? buffs[BuffCount].counters : 0, + CalculateCurseCounters(buffs[BuffCount].spellid) > 0 ? buffs[BuffCount].counters : 0, + CalculateCorruptionCounters(buffs[BuffCount].spellid) > 0 ? buffs[BuffCount].counters : 0, + buffs[BuffCount].numhits, buffs[BuffCount].melee_rune, buffs[BuffCount].magic_rune, + buffs[BuffCount].deathSaveSuccessChance, + buffs[BuffCount].deathsaveCasterAARank, IsPersistent), TempErrorMessageBuffer)) { + errorMessage = std::string(TempErrorMessageBuffer); + safe_delete(Query); + Query = 0; + break; + } + else { + safe_delete(Query); + Query = 0; + InsertCount++; + } + } + + BuffCount++; + } + + if(!errorMessage.empty()) { + LogFile->write(EQEMuLog::Error, "Error Saving Merc Buffs: %s", errorMessage.c_str()); + } +} + +void ZoneDatabase::LoadMercBuffs(Merc *merc) { + Buffs_Struct *buffs = merc->GetBuffs(); + uint32 max_slots = merc->GetMaxBuffSlots(); + std::string errorMessage; + char* Query = 0; + char TempErrorMessageBuffer[MYSQL_ERRMSG_SIZE]; + MYSQL_RES* DatasetResult; + MYSQL_ROW DataRow; + + bool BuffsLoaded = false; + + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "SELECT SpellId, CasterLevel, DurationFormula, TicsRemaining, PoisonCounters, DiseaseCounters, CurseCounters, CorruptionCounters, HitCount, MeleeRune, MagicRune, DeathSaveSuccessChance, CasterAARank, Persistent FROM mercbuffs WHERE MercId = %u", merc->GetMercID()), TempErrorMessageBuffer, &DatasetResult)) { + errorMessage = std::string(TempErrorMessageBuffer); + } + else { + int BuffCount = 0; + + while(DataRow = mysql_fetch_row(DatasetResult)) { + if(BuffCount == BUFF_COUNT) + break; + + buffs[BuffCount].spellid = atoi(DataRow[0]); + buffs[BuffCount].casterlevel = atoi(DataRow[1]); + buffs[BuffCount].ticsremaining = atoi(DataRow[3]); + + if(CalculatePoisonCounters(buffs[BuffCount].spellid) > 0) { + buffs[BuffCount].counters = atoi(DataRow[4]); + } else if(CalculateDiseaseCounters(buffs[BuffCount].spellid) > 0) { + buffs[BuffCount].counters = atoi(DataRow[5]); + } else if(CalculateCurseCounters(buffs[BuffCount].spellid) > 0) { + buffs[BuffCount].counters = atoi(DataRow[6]); + } else if(CalculateCorruptionCounters(buffs[BuffCount].spellid) > 0) { + buffs[BuffCount].counters = atoi(DataRow[7]); + } + buffs[BuffCount].numhits = atoi(DataRow[8]); + buffs[BuffCount].melee_rune = atoi(DataRow[9]); + buffs[BuffCount].magic_rune = atoi(DataRow[10]); + buffs[BuffCount].deathSaveSuccessChance = atoi(DataRow[11]); + buffs[BuffCount].deathsaveCasterAARank = atoi(DataRow[12]); + buffs[BuffCount].casterid = 0; + + bool IsPersistent = false; + + if(atoi(DataRow[13])) + IsPersistent = true; + + buffs[BuffCount].persistant_buff = IsPersistent; + + BuffCount++; + } + + mysql_free_result(DatasetResult); + + BuffsLoaded = true; + } + + safe_delete(Query); + Query = 0; + + if(errorMessage.empty() && BuffsLoaded) { + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "DELETE FROM mercbuffs WHERE MercId = %u", merc->GetMercID()), TempErrorMessageBuffer)) { + errorMessage = std::string(TempErrorMessageBuffer); + safe_delete(Query); + Query = 0; + } + } + + if(!errorMessage.empty()) { + LogFile->write(EQEMuLog::Error, "Error Loading Merc Buffs: %s", errorMessage.c_str()); + } +} + +bool ZoneDatabase::DeleteMerc(uint32 merc_id) { + std::string errorMessage; + bool Result = false; + int TempCounter = 0; + + if(merc_id > 0) { + char* Query = 0; + char TempErrorMessageBuffer[MYSQL_ERRMSG_SIZE]; + + // TODO: These queries need to be ran together as a transaction.. ie, if one or more fail then they all will fail to commit to the database. + + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "DELETE FROM mercbuffs WHERE MercID = '%u'", merc_id), TempErrorMessageBuffer)) { + errorMessage = std::string(TempErrorMessageBuffer); + } + else + TempCounter++; + + if(!database.RunQuery(Query, MakeAnyLenString(&Query, "DELETE FROM mercs WHERE MercID = '%u'", merc_id), TempErrorMessageBuffer)) { + errorMessage = std::string(TempErrorMessageBuffer); + } + else + TempCounter++; + + if(TempCounter == 2) + Result = true; + } + + if(!errorMessage.empty()) { + LogFile->write(EQEMuLog::Error, "Error Deleting Merc: %s", errorMessage.c_str()); + } + + return Result; +} uint8 ZoneDatabase::GetGridType(uint32 grid, uint32 zoneid ) { char *query = 0; diff --git a/zone/zonedb.h b/zone/zonedb.h index 84ba55af2..7708b386b 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -110,6 +110,7 @@ struct MercTemplate { uint8 ClassID; uint32 MercNPCID; uint8 ProficiencyID; + uint8 TierID; uint8 CostFormula; // To determine cost to client uint32 ClientVersion; // Only send valid mercs per expansion uint8 MercNameType; // Determines if merc gets random name or default text @@ -119,18 +120,35 @@ struct MercTemplate { }; struct MercInfo { - MercTemplate myTemplate; + uint32 mercid; + uint8 slot; + char merc_name[64]; + uint32 MercTemplateID; + const MercTemplate* myTemplate; uint32 SuspendedTime; bool IsSuspended; uint32 MercTimerRemaining; uint8 Gender; int32 State; + int32 hp; + int32 mana; + int32 endurance; + uint8 face; + uint8 luclinHairStyle; + uint8 luclinHairColor; + uint8 luclinEyeColor; + uint8 luclinEyeColor2; + uint8 luclinBeardColor; + uint8 luclinBeard; + uint32 drakkinHeritage; + uint32 drakkinTattoo; + uint32 drakkinDetails; }; struct MercSpellEntry { uint8 proficiencyid; uint16 spellid; // <= 0 = no spell - uint16 type; // 0 = never, must be one (and only one) of the defined values + uint32 type; // 0 = never, must be one (and only one) of the defined values int16 stance; // 0 = all, + = only this stance, - = all except this stance uint8 minlevel; uint8 maxlevel; @@ -325,7 +343,6 @@ public: * NPCs */ const NPCType* GetNPCType(uint32 id); - const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel); uint32 NPCSpawnDB(uint8 command, const char* zone, uint32 zone_version, Client *c, NPC* spawn = 0, uint32 extra = 0); // 0 = Create 1 = Add; 2 = Update; 3 = Remove; 4 = Delete bool SetSpecialAttkFlag(uint8 id, const char* flag); bool GetPetEntry(const char *pet_type, PetRecord *into); @@ -335,7 +352,19 @@ public: void AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop); uint32 GetMaxNPCSpellsID(); DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID); - + + /* + * Mercs + */ + const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel); + void SaveMercBuffs(Merc *merc); + void LoadMercBuffs(Merc *merc); + bool LoadMercInfo(Client *c); + bool SaveMerc(Merc *merc); + bool DeleteMerc(uint32 merc_id); + //void LoadMercTypesForMercMerchant(NPC *merchant); + //void LoadMercsForMercMerchant(NPC *merchant); + /* * Petitions */