From 765f23febc45979678e4af367a8d14453236c43c Mon Sep 17 00:00:00 2001 From: badcaptain Date: Fri, 11 Oct 2013 23:37:46 -0400 Subject: [PATCH] Initial check-in of bard bot in combat song code. --- common/ruletypes.h | 1 + common/spdat.cpp | 15 +++++ common/spdat.h | 1 + zone/botspellsai.cpp | 133 +++++++++++++++++++++++++++++++++++++++---- zone/merc.cpp | 67 ++++++++++++---------- zone/zone.cpp | 6 +- zone/zone.h | 2 + zone/zonedb.cpp | 3 +- zone/zonedb.h | 2 +- 9 files changed, 184 insertions(+), 46 deletions(-) diff --git a/common/ruletypes.h b/common/ruletypes.h index bdba9f334..6a31d7c7a 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -422,6 +422,7 @@ RULE_BOOL ( Bots, BotGroupBuffing, false ) // Bots will cast single target buffs RULE_BOOL ( Bots, BotSpellQuest, false ) // Anita Thrall's (Anita_Thrall.pl) Bot Spell Scriber quests. RULE_INT ( Bots, BotAAExpansion, 8 ) // Bots get AAs through this expansion RULE_BOOL ( Bots, BotGroupXP, false ) // Determines whether client gets xp for bots outside their group. +RULE_BOOL ( Bots, BotBardUseOutOfCombatSongs, true) // Determines whether bard bots use additional out of combat songs. RULE_CATEGORY_END() #endif diff --git a/common/spdat.cpp b/common/spdat.cpp index 4cf4ab1a6..7b10f8d5d 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -1102,6 +1102,21 @@ bool IsShortDurationBuff(uint16 spell_id) return false; } +bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 zone_type) +{ + if((spell_id > 0) && (spell_id < SPDAT_RECORDS)) + { + //check if spell can be cast in any zone (-1 or 255), then if spell zonetype matches zone's zonetype + if(spells[spell_id].zonetype == -1 + || spells[spell_id].zonetype == 255 + || spells[spell_id].zonetype == zone_type) { + return true; + } + } + + return false; +} + const char* GetSpellName(int16 spell_id) { return( spells[spell_id].name ); diff --git a/common/spdat.h b/common/spdat.h index 5d317f693..668002e54 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -806,6 +806,7 @@ bool DetrimentalSpellAllowsRest(uint16 spell_id); uint32 GetNimbusEffect(uint16 spell_id); int32 GetFuriousBash(uint16 spell_id); bool IsShortDurationBuff(uint16 spell_id); +bool IsSpellUsableThisZoneType(uint16 spell_id, uint8 zone_type); const char *GetSpellName(int16 spell_id); #endif diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 5e7e5f023..359330262 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -332,7 +332,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { // Put the zone levitate and movement check here since bots are able to bypass the client casting check if((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate()) || (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) { - continue; + if(botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())){ + continue; + } } switch(tar->GetArchetype()) @@ -582,6 +584,86 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes) { break; } } + else if(botClass == BARD) { + if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { + std::list inCombatBuffList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); + + for(std::list::iterator itr = inCombatBuffList.begin(); itr != inCombatBuffList.end(); itr++) { + BotSpell selectedBotSpell = *itr; + + if(selectedBotSpell.SpellId == 0) + continue; + + if(CheckSpellRecastTimers(this, itr->SpellIndex)) { + uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); + + // no buffs with illusions.. use #bot command to cast illusions + if(IsEffectInSpell(selectedBotSpell.SpellId, SE_Illusion) && tar != this) + continue; + + //no teleport spells use #bot command to cast teleports + if(IsEffectInSpell(selectedBotSpell.SpellId, SE_Teleport) || IsEffectInSpell(selectedBotSpell.SpellId, SE_Succor)) + continue; + + // can not cast buffs for your own pet only on another pet that isn't yours + if((spells[selectedBotSpell.SpellId].targettype == ST_Pet) && (tar != this->GetPet())) + continue; + + // Validate target + + if(!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this || + spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport || + (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == ST_AEBard)) + && !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) + && (tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) { + continue; + } + + // Put the zone levitate and movement check here since bots are able to bypass the client casting check + if((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate()) + || (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) { + if(!IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) { + continue; + } + } + + if(!IsGroupSpell(selectedBotSpell.SpellId)) { + //Only check archetype if song is not a group spell + switch(tar->GetArchetype()) { + case ARCHETYPE_CASTER: + //TODO: probably more caster specific spell effects in here + if(IsEffectInSpell(selectedBotSpell.SpellId, SE_AttackSpeed) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ATK) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_STR) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ReverseDS)) + { + continue; + } + break; + case ARCHETYPE_MELEE: + if(IsEffectInSpell(selectedBotSpell.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaPool) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_CastingLevel) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaRegen_v2) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_CurrentMana)) + { + continue; + } + break; + case ARCHETYPE_HYBRID: + //Hybrids get all buffs + default: + break; + } + } + + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore); + + if(TempDontBuffMeBefore != tar->DontBuffMeBefore()) + tar->SetDontBuffMeBefore(TempDontBuffMeBefore); + } + + if(castedSpell) + break; + } + } + } break; } case SpellType_Lifetap: { @@ -838,7 +920,17 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain } else { //handle spell recast and recast timers if(GetClass() == BARD && IsGroupSpell(AIspells[i].spellid)) { - AIspells[i].time_cancast = (spells[AIspells[i].spellid].recast_time > (spells[AIspells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIspells[i].spellid].buffduration * 6000; + // Bard Clarity songs (spellids: 723 & 1287) are recast_time = 0 and buffduration = 0. + // This translates to an insta-recast in the above check and is essentially locking all idle bard songs + // between levels 20 & 33 to one of the clarity songs. + + // Hard-coded fix of '0/0' bard songs..watch for issues in other song lines + if(spells[AIspells[i].spellid].recast_time == 0 && spells[AIspells[i].spellid].buffduration == 0 && spells[AIspells[i].spellid].goodEffect > 0) { + AIspells[i].time_cancast = Timer::GetCurrentTime() + (3 * 6000); // pseudo 'buffduration' of 3 (value matches others in bard 'heal' song line) + } + else { + AIspells[i].time_cancast = (spells[AIspells[i].spellid].recast_time > (spells[AIspells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIspells[i].spellid].buffduration * 6000; + } //spellend_timer.Start(spells[AIspells[i].spellid].cast_time); } else @@ -933,9 +1025,11 @@ bool Bot::AI_IdleCastCheck() { // bard bots if(!AICastSpell(this, 100, SpellType_Cure)) { if(!AICastSpell(this, 100, SpellType_Heal)) { - if(!AICastSpell(this, 100, SpellType_Buff)) { - // - } + if(!RuleB(Bots, BotBardUseOutOfCombatSongs) || !AICastSpell(this, 100, SpellType_Buff)) { // skips if rule is false + if(!AICastSpell(this, 100, SpellType_InCombatBuff)) { // this tries to keep some combat buffs on the group until engaged code can pick up the buffing + // + } + } } } @@ -1136,11 +1230,11 @@ bool Bot::AI_EngagedCastCheck() { } } else if(botClass == BARD) { - if(!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Buff), SpellType_Buff)) { + if(!AICastSpell(this, GetChanceToCastBySpellType(SpellType_InCombatBuff), SpellType_InCombatBuff)) { if(!AICastSpell(this, GetChanceToCastBySpellType(SpellType_Heal), SpellType_Heal)) { - if(!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Dispel), SpellType_Dispel)) {// Bards will use their debuff songs - if(!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) {// Bards will use their debuff songs - if(!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) {// Bards will use their debuff songs + if(!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Dispel), SpellType_Dispel)) {// Bards will use their dispel songs + if(!AICastSpell(GetTarget(), mayGetAggro?0:GetChanceToCastBySpellType(SpellType_Nuke), SpellType_Nuke)) {// Bards will use their nuke songs + if(!AICastSpell(GetTarget(), GetChanceToCastBySpellType(SpellType_Escape), SpellType_Escape)) {// Bards will use their escape songs // failedToCast = true; } @@ -2969,11 +3063,30 @@ void Bot::CalcChanceToCast() { break; } break; + case BARD: + switch(botStance) + { + case BotStanceBalanced: + case BotStanceReactive: + castChance = 100; + break; + case BotStanceEfficient: + case BotStanceAggressive: + castChance = 50; + break; + case BotStanceBurn: + case BotStanceBurnAE: + castChance = 25; + break; + default: + castChance = 0; + break; + } + break; case BEASTLORD: case MAGICIAN: case DRUID: case ENCHANTER: - case BARD: case WIZARD: case NECROMANCER: case SHADOWKNIGHT: diff --git a/zone/merc.cpp b/zone/merc.cpp index 6905f6007..c57548f8e 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -5011,39 +5011,42 @@ Merc* Merc::LoadMerc(Client *c, MercTemplate* merc_template, uint32 merchant_id, npc_type->maxlevel = 0; //We should hard-set this to override scalerate's functionality in the NPC class when it is constructed. Merc* merc = new Merc(npc_type, c->GetX(), c->GetY(), c->GetZ(), 0); - merc->SetMercData( merc_template->MercTemplateID ); - database.LoadMercEquipment(merc); - merc->UpdateMercStats(c); - if(updateFromDB) { - database.LoadCurrentMerc(c); + if(merc) { + merc->SetMercData( merc_template->MercTemplateID ); + database.LoadMercEquipment(merc); + merc->UpdateMercStats(c); - 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 <= 0 ? merc->GetMaxHP() : c->GetMercInfo().hp); - merc->SetMana(c->GetMercInfo().hp <= 0 ? merc->GetMaxMana() : 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; + if(updateFromDB) { + database.LoadCurrentMerc(c); + + 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 <= 0 ? merc->GetMaxHP() : c->GetMercInfo().hp); + merc->SetMana(c->GetMercInfo().hp <= 0 ? merc->GetMaxMana() : 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; + } + + if(merc->GetMercID()) { + database.LoadMercBuffs(merc); + } + + merc->LoadMercSpells(); } - if(merc->GetMercID()) { - database.LoadMercBuffs(merc); - } - - merc->LoadMercSpells(); - return merc; } } @@ -5456,8 +5459,10 @@ void Client::SpawnMercOnZone() return; } Merc* merc = Merc::LoadMerc(this, &zone->merc_templates[GetMercInfo().MercTemplateID], 0, true); - SpawnMerc(merc, false); - SendMercTimerPacket(merc->GetID(), 5, GetMercInfo().SuspendedTime, GetMercInfo().MercTimerRemaining, RuleI(Mercs, SuspendIntervalMS)); + if(merc) { + SpawnMerc(merc, false); + SendMercTimerPacket(merc->GetID(), 5, GetMercInfo().SuspendedTime, GetMercInfo().MercTimerRemaining, RuleI(Mercs, SuspendIntervalMS)); + } } } else diff --git a/zone/zone.cpp b/zone/zone.cpp index 1a294e10b..98f0126eb 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -1157,7 +1157,7 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id, bool DontLoadDe { map_name = nullptr; if(!database.GetZoneCFG(database.GetZoneID(filename), 0, &newzone_data, can_bind, - can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, default_ruleset, &map_name)) + can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) { LogFile->write(EQEMuLog::Error, "Error loading the Zone Config."); return false; @@ -1168,11 +1168,11 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_id, bool DontLoadDe //Fall back to base zone if we don't find the instance version. map_name = nullptr; if(!database.GetZoneCFG(database.GetZoneID(filename), instance_id, &newzone_data, can_bind, - can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, default_ruleset, &map_name)) + can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) { safe_delete_array(map_name); if(!database.GetZoneCFG(database.GetZoneID(filename), 0, &newzone_data, can_bind, - can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, default_ruleset, &map_name)) + can_combat, can_levitate, can_castoutdoor, is_city, is_hotzone, allow_mercs, zone_type, default_ruleset, &map_name)) { LogFile->write(EQEMuLog::Error, "Error loading the Zone Config."); return false; diff --git a/zone/zone.h b/zone/zone.h index 0b340acaa..71f1c8f42 100644 --- a/zone/zone.h +++ b/zone/zone.h @@ -104,6 +104,7 @@ public: inline const uint32 GetZoneID() const { return zoneid; } inline const uint32 GetInstanceID() const { return instanceid; } inline const uint16 GetInstanceVersion() const { return instanceversion; } + inline const uint8 GetZoneType() const { return zone_type; } inline Timer* GetInstanceTimer() { return Instance_Timer; } @@ -286,6 +287,7 @@ private: bool can_castoutdoor; bool can_levitate; bool is_hotzone; + uint8 zone_type; bool allow_mercs; uint32 pgraveyard_id, pgraveyard_zoneid; float pgraveyard_x, pgraveyard_y, pgraveyard_z, pgraveyard_heading; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 47d88ae4d..31e1b5231 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -78,7 +78,7 @@ bool ZoneDatabase::SaveZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct return true; } -bool ZoneDatabase::GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct *zone_data, bool &can_bind, bool &can_combat, bool &can_levitate, bool &can_castoutdoor, bool &is_city, bool &is_hotzone, bool &allow_mercs, int &ruleset, char **map_filename) { +bool ZoneDatabase::GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct *zone_data, bool &can_bind, bool &can_combat, bool &can_levitate, bool &can_castoutdoor, bool &is_city, bool &is_hotzone, bool &allow_mercs, uint8 &zone_type, int &ruleset, char **map_filename) { char errbuf[MYSQL_ERRMSG_SIZE]; char *query = 0; MYSQL_RES *result; @@ -134,6 +134,7 @@ bool ZoneDatabase::GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct can_castoutdoor = atoi(row[r++])==0?false:true; is_hotzone = atoi(row[r++])==0?false:true; allow_mercs = true; + zone_type = zone_data->ztype; ruleset = atoi(row[r++]); zone_data->SuspendBuffs = atoi(row[r++]); char *file = row[r++]; diff --git a/zone/zonedb.h b/zone/zonedb.h index 76f528e29..33c96acfd 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -295,7 +295,7 @@ public: /* * Zone related */ - bool GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct *data, bool &can_bind, bool &can_combat, bool &can_levitate, bool &can_castoutdoor, bool &is_city, bool &is_hotzone, bool &allow_mercs, int &ruleset, char **map_filename); + bool GetZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct *data, bool &can_bind, bool &can_combat, bool &can_levitate, bool &can_castoutdoor, bool &is_city, bool &is_hotzone, bool &allow_mercs, uint8 &zone_type, int &ruleset, char **map_filename); bool SaveZoneCFG(uint32 zoneid, uint16 instance_id, NewZone_Struct* zd); bool LoadStaticZonePoints(LinkedList* zone_point_list,const char* zonename, uint32 version); bool UpdateZoneSafeCoords(const char* zonename, float x, float y, float z);