bool Mob::CanUseAlternateAdvancementRank(AA::Rank *rank) { AA::Ability *ability = rank->base_ability; if(!ability) return false; if(!(ability->classes & (1 << GetClass()))) { return false; } // Passive and Active Shroud AAs // For now we skip them if(ability->category == 3 || ability->category == 4) { return false; } //the one titanium hack i will allow //just to make sure we dont crash the client with newer aas //we'll exclude any expendable ones if(IsClient() && CastToClient()->ClientVersionBit() & EQEmu::versions::maskTitaniumAndEarlier) { if(ability->charges > 0) { return false; } } if(IsClient()) { if(rank->expansion && !(CastToClient()->GetPP().expansions & (1 << (rank->expansion - 1)))) { return false; } } else { if(rank->expansion && !(RuleI(World, ExpansionSettings) & (1 << (rank->expansion - 1)))) { return false; } } auto race = GetPlayerRaceValue(GetBaseRace()); race = race > 16 ? 1 : race; if(!(ability->races & (1 << (race - 1)))) { return false; } auto deity = GetDeityBit(); if(!(ability->deities & deity)) { return false; } if(IsClient() && CastToClient()->Admin() < ability->status) { return false; } if(GetBaseRace() == 522) { //drakkin_heritage if(!(ability->drakkin_heritage & (1 << GetDrakkinHeritage()))) { return false; } } return true; }
int32 Client::LevelRegen() { bool sitting = IsSitting(); bool feigned = GetFeigned(); int level = GetLevel(); bool bonus = GetRaceBitmask(GetBaseRace()) & RuleI(Character, BaseHPRegenBonusRaces); uint8 multiplier1 = bonus ? 2 : 1; int32 hp = 0; //these calculations should match up with the info from Monkly Business, which was last updated ~05/2008: http://www.monkly-business.net/index.php?pageid=abilities if (level < 51) { if (sitting) { if (level < 20) hp += 2 * multiplier1; else if (level < 50) hp += 3 * multiplier1; else //level == 50 hp += 4 * multiplier1; } else //feigned or standing hp += 1 * multiplier1; } //there may be an easier way to calculate this next part, but I don't know what it is else { //level >= 51 int32 tmp = 0; float multiplier2 = 1; if (level < 56) { tmp = 2; if (bonus) multiplier2 = 3; } else if (level < 60) { tmp = 3; if (bonus) multiplier2 = 3.34; } else if (level < 61) { tmp = 4; if (bonus) multiplier2 = 3; } else if (level < 63) { tmp = 5; if (bonus) multiplier2 = 2.8; } else if (level < 65) { tmp = 6; if (bonus) multiplier2 = 2.67; } else { //level >= 65 tmp = 7; if (bonus) multiplier2 = 2.58; } hp += int32(float(tmp) * multiplier2); if (sitting) hp += 3 * multiplier1; else if (feigned) hp += 1 * multiplier1; } return hp; }
int32 Client::CalcFR() { //racial bases switch(GetBaseRace()) { case HUMAN: FR = 25; break; case BARBARIAN: FR = 25; break; case ERUDITE: FR = 25; break; case WOOD_ELF: FR = 25; break; case HIGH_ELF: FR = 25; break; case DARK_ELF: FR = 25; break; case HALF_ELF: FR = 25; break; case DWARF: FR = 25; break; case TROLL: FR = 5; break; case OGRE: FR = 25; break; case HALFLING: FR = 25; break; case GNOME: FR = 25; break; case IKSAR: FR = 30; break; case VAHSHIR: FR = 25; break; case FROGLOK: FR = 25; break; case DRAKKIN: FR = 25; break; default: FR = 20; } int c = GetClass(); if(c == RANGER) { FR += 4; int l = GetLevel(); if(l > 49) FR += l - 49; } FR += itembonuses.FR + spellbonuses.FR + aabonuses.FR; if(FR < 1) FR = 1; if(FR > GetMaxFR()) FR = GetMaxFR(); return(FR); }
//The AA multipliers are set to be 5, but were 2 on WR //The resistant discipline which I think should be here is implemented //in Mob::ResistSpell int32 Client::CalcMR() { //racial bases switch(GetBaseRace()) { case HUMAN: MR = 25; break; case BARBARIAN: MR = 25; break; case ERUDITE: MR = 30; break; case WOOD_ELF: MR = 25; break; case HIGH_ELF: MR = 25; break; case DARK_ELF: MR = 25; break; case HALF_ELF: MR = 25; break; case DWARF: MR = 30; break; case TROLL: MR = 25; break; case OGRE: MR = 25; break; case HALFLING: MR = 25; break; case GNOME: MR = 25; break; case IKSAR: MR = 25; break; case VAHSHIR: MR = 25; break; case FROGLOK: MR = 30; break; case DRAKKIN: MR = 35; break; default: MR = 20; } MR += itembonuses.MR + spellbonuses.MR + aabonuses.MR; if(GetClass() == WARRIOR) MR += GetLevel() / 2; if(MR < 1) MR = 1; if(MR > GetMaxMR()) MR = GetMaxMR(); return(MR); }
void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { uint32 add_exp = in_add_exp; if(!resexp && (XPRate != 0)) add_exp = static_cast<uint32>(in_add_exp * (static_cast<float>(XPRate) / 100.0f)); if (m_epp.perAA<0 || m_epp.perAA>100) m_epp.perAA=0; // stop exploit with sanity check uint32 add_aaxp; if(resexp) { add_aaxp = 0; } else { //figure out how much of this goes to AAs add_aaxp = add_exp * m_epp.perAA / 100; //take that ammount away from regular exp add_exp -= add_aaxp; float totalmod = 1.0; float zemmod = 1.0; //get modifiers if(RuleR(Character, ExpMultiplier) >= 0){ totalmod *= RuleR(Character, ExpMultiplier); } if(zone->newzone_data.zone_exp_multiplier >= 0){ zemmod *= zone->newzone_data.zone_exp_multiplier; } if(RuleB(Character,UseRaceClassExpBonuses)) { if(GetBaseRace() == HALFLING){ totalmod *= 1.05; } if(GetClass() == ROGUE || GetClass() == WARRIOR){ totalmod *= 1.05; } } if(zone->IsHotzone()) { totalmod += RuleR(Zone, HotZoneBonus); } add_exp = uint32(float(add_exp) * totalmod * zemmod); if(RuleB(Character,UseXPConScaling)) { if (conlevel != 0xFF && !resexp) { switch (conlevel) { case CON_GREEN: add_exp = 0; add_aaxp = 0; return; case CON_LIGHTBLUE: add_exp = add_exp * RuleI(Character, LightBlueModifier)/100; add_aaxp = add_aaxp * RuleI(Character, LightBlueModifier)/100; break; case CON_BLUE: add_exp = add_exp * RuleI(Character, BlueModifier)/100; add_aaxp = add_aaxp * RuleI(Character, BlueModifier)/100; break; case CON_WHITE: add_exp = add_exp * RuleI(Character, WhiteModifier)/100; add_aaxp = add_aaxp * RuleI(Character, WhiteModifier)/100; break; case CON_YELLOW: add_exp = add_exp * RuleI(Character, YellowModifier)/100; add_aaxp = add_aaxp * RuleI(Character, YellowModifier)/100; break; case CON_RED: add_exp = add_exp * RuleI(Character, RedModifier)/100; add_aaxp = add_aaxp * RuleI(Character, RedModifier)/100; break; } } } if(IsLeadershipEXPOn() && ((conlevel == CON_BLUE) || (conlevel == CON_WHITE) || (conlevel == CON_YELLOW) || (conlevel == CON_RED))) { add_exp = static_cast<uint32>(static_cast<float>(add_exp) * 0.8f); if(GetGroup()) { if((m_pp.group_leadership_points < MaxBankedGroupLeadershipPoints(GetLevel())) && (RuleI(Character, KillsPerGroupLeadershipAA) > 0)) { AddLeadershipEXP(GROUP_EXP_PER_POINT / RuleI(Character, KillsPerGroupLeadershipAA), 0); Message_StringID(MT_Leadership, GAIN_GROUP_LEADERSHIP_EXP); } else Message_StringID(MT_Leadership, MAX_GROUP_LEADERSHIP_POINTS); } else { if((m_pp.raid_leadership_points < MaxBankedRaidLeadershipPoints(GetLevel())) && (RuleI(Character, KillsPerRaidLeadershipAA) > 0)) { AddLeadershipEXP(0, RAID_EXP_PER_POINT / RuleI(Character, KillsPerRaidLeadershipAA)); Message_StringID(MT_Leadership, GAIN_RAID_LEADERSHIP_EXP); } else Message_StringID(MT_Leadership, MAX_RAID_LEADERSHIP_POINTS); } } } //end !resexp float aatotalmod = 1.0; if(zone->newzone_data.zone_exp_multiplier >= 0){ aatotalmod *= zone->newzone_data.zone_exp_multiplier; } if(RuleB(Character,UseRaceClassExpBonuses)) { if(GetBaseRace() == HALFLING){ aatotalmod *= 1.05; } if(GetClass() == ROGUE || GetClass() == WARRIOR){ aatotalmod *= 1.05; } } if(RuleB(Zone, LevelBasedEXPMods)){ if(zone->level_exp_mod[GetLevel()].ExpMod){ add_exp *= zone->level_exp_mod[GetLevel()].ExpMod; add_aaxp *= zone->level_exp_mod[GetLevel()].AAExpMod; } } uint32 exp = GetEXP() + add_exp; uint32 aaexp = (uint32)(RuleR(Character, AAExpMultiplier) * add_aaxp * aatotalmod); uint32 had_aaexp = GetAAXP(); aaexp += had_aaexp; if(aaexp < had_aaexp) aaexp = had_aaexp; //watch for wrap SetEXP(exp, aaexp, resexp); }
void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) { this->EVENT_ITEM_ScriptStopReturn(); uint32 add_exp = in_add_exp; if(!resexp && (XPRate != 0)) add_exp = static_cast<uint32>(in_add_exp * (static_cast<float>(XPRate) / 100.0f)); if (m_epp.perAA<0 || m_epp.perAA>100) m_epp.perAA=0; // stop exploit with sanity check uint32 add_aaxp; if(resexp) { add_aaxp = 0; } else { //figure out how much of this goes to AAs add_aaxp = add_exp * m_epp.perAA / 100; //take that ammount away from regular exp add_exp -= add_aaxp; float totalmod = 1.0; float zemmod = 1.0; //get modifiers if(RuleR(Character, ExpMultiplier) >= 0){ totalmod *= RuleR(Character, ExpMultiplier); } if(zone->newzone_data.zone_exp_multiplier >= 0){ zemmod *= zone->newzone_data.zone_exp_multiplier; } if(RuleB(Character,UseRaceClassExpBonuses)) { if(GetBaseRace() == HALFLING){ totalmod *= 1.05; } if(GetClass() == ROGUE || GetClass() == WARRIOR){ totalmod *= 1.05; } } if(zone->IsHotzone()) { totalmod += RuleR(Zone, HotZoneBonus); } add_exp = uint32(float(add_exp) * totalmod * zemmod); if(RuleB(Character,UseXPConScaling)) { if (conlevel != 0xFF && !resexp) { switch (conlevel) { case CON_GREEN: add_exp = 0; add_aaxp = 0; return; case CON_LIGHTBLUE: add_exp = add_exp * RuleI(Character, LightBlueModifier)/100; add_aaxp = add_aaxp * RuleI(Character, LightBlueModifier)/100; break; case CON_BLUE: add_exp = add_exp * RuleI(Character, BlueModifier)/100; add_aaxp = add_aaxp * RuleI(Character, BlueModifier)/100; break; case CON_WHITE: add_exp = add_exp * RuleI(Character, WhiteModifier)/100; add_aaxp = add_aaxp * RuleI(Character, WhiteModifier)/100; break; case CON_YELLOW: add_exp = add_exp * RuleI(Character, YellowModifier)/100; add_aaxp = add_aaxp * RuleI(Character, YellowModifier)/100; break; case CON_RED: add_exp = add_exp * RuleI(Character, RedModifier)/100; add_aaxp = add_aaxp * RuleI(Character, RedModifier)/100; break; } } } if (IsLeadershipEXPOn() && (conlevel == CON_BLUE || conlevel == CON_WHITE || conlevel == CON_YELLOW || conlevel == CON_RED)) { add_exp = static_cast<uint32>(static_cast<float>(add_exp) * 0.8f); if (GetGroup()) { if (m_pp.group_leadership_points < MaxBankedGroupLeadershipPoints(GetLevel()) && RuleI(Character, KillsPerGroupLeadershipAA) > 0) { uint32 exp = GROUP_EXP_PER_POINT / RuleI(Character, KillsPerGroupLeadershipAA); Client *mentoree = GetGroup()->GetMentoree(); if (GetGroup()->GetMentorPercent() && mentoree && mentoree->GetGroupPoints() < MaxBankedGroupLeadershipPoints(mentoree->GetLevel())) { uint32 mentor_exp = exp * (GetGroup()->GetMentorPercent() / 100.0f); exp -= mentor_exp; mentoree->AddLeadershipEXP(mentor_exp, 0); // ends up rounded down mentoree->Message_StringID(MT_Leadership, GAIN_GROUP_LEADERSHIP_EXP); } if (exp > 0) { // possible if you mentor 100% to the other client AddLeadershipEXP(exp, 0); // ends up rounded up if mentored, no idea how live actually does it Message_StringID(MT_Leadership, GAIN_GROUP_LEADERSHIP_EXP); } } else { Message_StringID(MT_Leadership, MAX_GROUP_LEADERSHIP_POINTS); } } else { Raid *raid = GetRaid(); // Raid leaders CAN NOT gain group AA XP, other group leaders can though! if (raid->IsLeader(this)) { if (m_pp.raid_leadership_points < MaxBankedRaidLeadershipPoints(GetLevel()) && RuleI(Character, KillsPerRaidLeadershipAA) > 0) { AddLeadershipEXP(0, RAID_EXP_PER_POINT / RuleI(Character, KillsPerRaidLeadershipAA)); Message_StringID(MT_Leadership, GAIN_RAID_LEADERSHIP_EXP); } else { Message_StringID(MT_Leadership, MAX_RAID_LEADERSHIP_POINTS); } } else { if (m_pp.group_leadership_points < MaxBankedGroupLeadershipPoints(GetLevel()) && RuleI(Character, KillsPerGroupLeadershipAA) > 0) { uint32 group_id = raid->GetGroup(this); uint32 exp = GROUP_EXP_PER_POINT / RuleI(Character, KillsPerGroupLeadershipAA); Client *mentoree = raid->GetMentoree(group_id); if (raid->GetMentorPercent(group_id) && mentoree && mentoree->GetGroupPoints() < MaxBankedGroupLeadershipPoints(mentoree->GetLevel())) { uint32 mentor_exp = exp * (raid->GetMentorPercent(group_id) / 100.0f); exp -= mentor_exp; mentoree->AddLeadershipEXP(mentor_exp, 0); mentoree->Message_StringID(MT_Leadership, GAIN_GROUP_LEADERSHIP_EXP); } if (exp > 0) { AddLeadershipEXP(exp, 0); Message_StringID(MT_Leadership, GAIN_GROUP_LEADERSHIP_EXP); } } else { Message_StringID(MT_Leadership, MAX_GROUP_LEADERSHIP_POINTS); } } } } } //end !resexp float aatotalmod = 1.0; if(zone->newzone_data.zone_exp_multiplier >= 0){ aatotalmod *= zone->newzone_data.zone_exp_multiplier; } if(RuleB(Character,UseRaceClassExpBonuses)) { if(GetBaseRace() == HALFLING){ aatotalmod *= 1.05; } if(GetClass() == ROGUE || GetClass() == WARRIOR){ aatotalmod *= 1.05; } } if(RuleB(Zone, LevelBasedEXPMods)){ if(zone->level_exp_mod[GetLevel()].ExpMod){ add_exp *= zone->level_exp_mod[GetLevel()].ExpMod; add_aaxp *= zone->level_exp_mod[GetLevel()].AAExpMod; } } uint32 exp = GetEXP() + add_exp; uint32 aaexp = (uint32)(RuleR(Character, AAExpMultiplier) * add_aaxp * aatotalmod); uint32 had_aaexp = GetAAXP(); aaexp += had_aaexp; if(aaexp < had_aaexp) aaexp = had_aaexp; //watch for wrap SetEXP(exp, aaexp, resexp); }
void Client::HandleAAAction(aaID activate) { if (activate < 0 || activate >= aaHighestID) return; uint8 activate_val = GetAA(activate); if (activate_val == 0) return; if (activate_val > MAX_AA_ACTION_RANKS) activate_val = MAX_AA_ACTION_RANKS; activate_val--; //to get array index. //get our current node, now that the indices are well bounded const AA_DBAction *caa = &AA_Actions[activate][activate_val]; uint16 timer_id = 0; uint16 timer_duration = caa->duration; aaTargetType target = aaTargetUser; uint16 spell_id = SPELL_UNKNOWN; //gets cast at the end if not still unknown switch (caa->action) { case aaActionAETaunt: entity_list.AETaunt(this); break; case aaActionMassBuff: EnableAAEffect(aaEffectMassGroupBuff, 3600); Message_StringID(MT_Disciplines, MGB_STRING); //The next group buff you cast will hit all targets in range. break; case aaActionFlamingArrows: //toggle it if (CheckAAEffect(aaEffectFlamingArrows)) EnableAAEffect(aaEffectFlamingArrows); else DisableAAEffect(aaEffectFlamingArrows); break; case aaActionFrostArrows: if (CheckAAEffect(aaEffectFrostArrows)) EnableAAEffect(aaEffectFrostArrows); else DisableAAEffect(aaEffectFrostArrows); break; case aaActionRampage: EnableAAEffect(aaEffectRampage, 10); break; case aaActionSharedHealth: if (CheckAAEffect(aaEffectSharedHealth)) EnableAAEffect(aaEffectSharedHealth); else DisableAAEffect(aaEffectSharedHealth); break; case aaActionCelestialRegen: { //special because spell_id depends on a different AA switch (GetAA(aaCelestialRenewal)) { case 1: spell_id = 3250; break; case 2: spell_id = 3251; break; default: spell_id = 2740; break; } target = aaTargetCurrent; break; } case aaActionDireCharm: { //special because spell_id depends on class switch (GetClass()) { case DRUID: spell_id = 2760; //2644? break; case NECROMANCER: spell_id = 2759; //2643? break; case ENCHANTER: spell_id = 2761; //2642? break; } target = aaTargetCurrent; break; } case aaActionImprovedFamiliar: { //Spell IDs might be wrong... if (GetAA(aaAllegiantFamiliar)) spell_id = 3264; //1994? else spell_id = 2758; //2155? break; } case aaActionActOfValor: if (GetTarget() != nullptr) { int curhp = GetTarget()->GetHP(); target = aaTargetCurrent; GetTarget()->HealDamage(curhp, this); Death(this, 0, SPELL_UNKNOWN, SkillHandtoHand); } break; case aaActionSuspendedMinion: if (GetPet()) { target = aaTargetPet; switch (GetAA(aaSuspendedMinion)) { case 1: spell_id = 3248; break; case 2: spell_id = 3249; break; } //do we really need to cast a spell? Message(0, "You call your pet to your side."); GetPet()->WipeHateList(); GetPet()->GMMove(GetX(), GetY(), GetZ()); if (activate_val > 1) entity_list.ClearFeignAggro(GetPet()); } else { Message(0, "You have no pet to call."); } break; case aaActionProjectIllusion: EnableAAEffect(aaEffectProjectIllusion, 3600); Message(10, "The power of your next illusion spell will flow to your grouped target in your place."); break; case aaActionEscape: Escape(); break; // Don't think this code is used any longer for Bestial Alignment as the aa.has a spell_id and no nonspell_action. case aaActionBeastialAlignment: switch (GetBaseRace()) { case BARBARIAN: spell_id = AA_Choose3(activate_val, 4521, 4522, 4523); break; case TROLL: spell_id = AA_Choose3(activate_val, 4524, 4525, 4526); break; case OGRE: spell_id = AA_Choose3(activate_val, 4527, 4527, 4529); break; case IKSAR: spell_id = AA_Choose3(activate_val, 4530, 4531, 4532); break; case VAHSHIR: spell_id = AA_Choose3(activate_val, 4533, 4534, 4535); break; } case aaActionLeechTouch: target = aaTargetCurrent; spell_id = SPELL_HARM_TOUCH2; EnableAAEffect(aaEffectLeechTouch, 1000); break; case aaActionFadingMemories: // Do nothing since spell effect works correctly, but mana isn't used. break; default: Log.Out(Logs::General, Logs::Error, "Unknown AA nonspell action type %d", caa->action); return; } uint16 target_id = 0; //figure out our target switch (target) { case aaTargetUser: case aaTargetGroup: target_id = GetID(); break; case aaTargetCurrent: case aaTargetCurrentGroup: if (GetTarget() == nullptr) { Message_StringID(MT_DefaultText, AA_NO_TARGET); //You must first select a target for this ability! p_timers.Clear(&database, timer_id + pTimerAAEffectStart); return; } target_id = GetTarget()->GetID(); break; case aaTargetPet: if (GetPet() == nullptr) { Message(0, "A pet is required for this skill."); return; } target_id = GetPetID(); break; } //cast the spell, if we have one if (IsValidSpell(spell_id)) { int aatid = GetAATimerID(activate); if (!CastSpell(spell_id, target_id, USE_ITEM_SPELL_SLOT, -1, -1, 0, -1, pTimerAAStart + aatid, CalcAAReuseTimer(caa), 1)) { SendAATimer(activate, 0, 0xFFFFFF); Message_StringID(CC_Yellow, ABILITY_FAILED); p_timers.Clear(&database, pTimerAAStart + aatid); return; } } //handle the duration timer if we have one. if (timer_id > 0 && timer_duration > 0) { p_timers.Start(pTimerAAEffectStart + timer_id, timer_duration); } }
int32 Client::CalcCR() { //racial bases switch(GetBaseRace()) { case HUMAN: CR = 25; break; case BARBARIAN: CR = 35; break; case ERUDITE: CR = 25; break; case WOOD_ELF: CR = 25; break; case HIGH_ELF: CR = 25; break; case DARK_ELF: CR = 25; break; case HALF_ELF: CR = 25; break; case DWARF: CR = 25; break; case TROLL: CR = 25; break; case OGRE: CR = 25; break; case HALFLING: CR = 25; break; case GNOME: CR = 25; break; case IKSAR: CR = 15; break; case VAHSHIR: CR = 25; break; case FROGLOK: CR = 25; break; case DRAKKIN: CR = 25; break; default: CR = 25; } int c = GetClass(); if(c == RANGER) { CR += 4; int l = GetLevel(); if(l > 49) CR += l - 49; } CR += itembonuses.CR + spellbonuses.CR + aabonuses.CR; if(CR < 1) CR = 1; if(CR > GetMaxCR()) CR = GetMaxCR(); return(CR); }
int32 Client::CalcCR() { //racial bases switch (GetBaseRace()) { case HUMAN: CR = 25; break; case BARBARIAN: CR = 35; break; case ERUDITE: CR = 25; break; case WOOD_ELF: CR = 25; break; case HIGH_ELF: CR = 25; break; case DARK_ELF: CR = 25; break; case HALF_ELF: CR = 25; break; case DWARF: CR = 25; break; case TROLL: CR = 25; break; case OGRE: CR = 25; break; case HALFLING: CR = 25; break; case GNOME: CR = 25; break; case IKSAR: CR = 15; break; case VAHSHIR: CR = 25; break; case FROGLOK: CR = 25; break; case DRAKKIN: { CR = 25; if (GetDrakkinHeritage() == 4) CR += 10; else if (GetDrakkinHeritage() == 5) CR += 2; break; } default: CR = 25; } int c = GetClass(); if (c == RANGER || c == BEASTLORD) { CR += 4; int l = GetLevel(); if (l > 49) { CR += l - 49; } } CR += itembonuses.CR + spellbonuses.CR + aabonuses.CR; if (CR < 1) { CR = 1; } if (CR > GetMaxCR()) { CR = GetMaxCR(); } return (CR); }
//o-------------------------------------------------------------- //| BuffFadeBySlot; Yeahlight, Nov 16, 2008 //o-------------------------------------------------------------- //| Adapted from EQEMU 7.0: Removes the buff in the supplied //| buff slot 'slot' //o-------------------------------------------------------------- void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) { bool debugFlag = true; if(slot < 0 || slot > BUFF_COUNT) return; if(!buffs[slot].spell || !buffs[slot].spell->IsValidSpell()) return; if(IsClient()) CastToClient()->MakeBuffFadePacket(buffs[slot].spell, slot); if(debugFlag && this->IsClient() && this->CastToClient()->GetDebugMe()) this->Message(LIGHTEN_BLUE, "Debug: Fading buff %d from slot %d", buffs[slot].spell->GetSpellID(), slot); for(int i = 0; i < EFFECT_COUNT; i++) { if(buffs[slot].spell->IsBlankSpellEffect(i)) continue; switch(buffs[slot].spell->GetSpellEffectID(i)) { case SE_WeaponProc: { SetBonusProcSpell(0); break; } case SE_Illusion: case SE_IllusionCopy: { SendIllusionPacket(GetBaseRace(), GetBaseGender(), GetTexture(), GetHelmTexture()); SendAppearancePacket(this->GetID(), SAT_Size, GetDefaultSize(), true); break; } case SE_Levitate: { SendAppearancePacket(this->GetID(), SAT_Levitate, 0, true); break; } case SE_Invisibility: { SetInvisible(false); break; } case SE_InvisVsUndead: { SetInvisibleUndead(false); break; } case SE_InvisVsAnimals: { SetInvisibleAnimal(false); break; } case SE_Silence: { break; } case SE_DivineAura: { SetInvulnerable(false); break; } case SE_Rune: { break; } case SE_AbsorbMagicAtt: { break; } case SE_Mez: { //Yeahlight: Unfreeze the PC's UI if(IsClient()) SendAppearancePacket(this->GetID(), SAT_Position_Update, SAPP_Sitting_To_Standing, true); this->mesmerized = false; break; } case SE_Charm: { Mob* charmer = entity_list.GetMob(this->GetOwnerID()); if(charmer && charmer->IsClient()) { char npcNameSuffix[] = "_CHARM00"; char npcNamePrefix[100] = ""; strcpy(npcNamePrefix, charmer->GetName()); strcat(npcNamePrefix, npcNameSuffix); Mob* charmPH = entity_list.GetMob(npcNamePrefix); if(charmPH && charmPH->IsNPC()) { charmPH->Depop(); } //Yeahlight: Check for _CHARM01 NPC, too npcNamePrefix[strlen(npcNamePrefix) - 1] = '1'; charmPH = entity_list.GetMob(npcNamePrefix); if(charmPH && charmPH->IsNPC()) { charmPH->Depop(); } //Yeahlight: Generate hate for towards the charmer if the charmer is not FD'ed if(this->IsNPC() && charmer->IsClient() && !charmer->CastToClient()->GetFeigned()) { CastToNPC()->AddToHateList(charmer, 0, GetSpellHate(SE_Charm, this->GetLevel(), false)); } } //Yeahlight: Unfreeze the PC's UI if(IsClient()) { SendAppearancePacket(this->GetID(), SAT_Position_Update, SAPP_Sitting_To_Standing, true); CastToClient()->SetCharmed(false); CastToClient()->charmPositionUpdate_timer->Disable(); } //Yeahlight: Deflag the NPC as charmed else if(IsNPC()) { CastToNPC()->SetCharmed(false); } this->SetOwnerID(0, false); break; } case SE_Root: { break; } case SE_Fear: { //Yeahlight: Unfreeze the PC's UI if(IsClient()) { CastToClient()->SetFeared(false); SendAppearancePacket(this->GetID(), SAT_Position_Update, SAPP_Sitting_To_Standing, true); } else if(IsNPC()) { CastToNPC()->SetFeared(false); CastToNPC()->SetOnFearPath(false); } break; } case SE_SpinTarget: { //Yeahlight: Unfreeze the PC's UI if(IsClient()) SendAppearancePacket(this->GetID(), SAT_Position_Update, SAPP_Sitting_To_Standing, true); break; } case SE_EyeOfZomm: { //Yeahlight: Clear the eye of zomm pointer and depop the eye if(IsClient() && CastToClient()->myEyeOfZomm) { CastToClient()->myEyeOfZomm->Depop(); CastToClient()->myEyeOfZomm = NULL; } break; } case SE_DeathSave: { //Yeahlight: Clear the death save chance if(IsClient()) CastToClient()->SetDeathSave(0); break; } case SE_VoiceGraft: { //Yeahlight: Drop the voice grafting flag from the client if(GetOwner() && GetOwner()->IsClient()) { GetOwner()->CastToClient()->SetVoiceGrafting(false); } break; } case SE_SeeInvis: { //Yeahlight: Mob may no longer see through invis SetCanSeeThroughInvis(false); break; } } } buffs[slot].spell = NULL; if(iRecalcBonuses) CalcBonuses(true, true); }
int32 Client::CalcPR() { //racial bases switch (GetBaseRace()) { case HUMAN: PR = 15; break; case BARBARIAN: PR = 15; break; case ERUDITE: PR = 15; break; case WOOD_ELF: PR = 15; break; case HIGH_ELF: PR = 15; break; case DARK_ELF: PR = 15; break; case HALF_ELF: PR = 15; break; case DWARF: PR = 20; break; case TROLL: PR = 15; break; case OGRE: PR = 15; break; case HALFLING: PR = 20; break; case GNOME: PR = 15; break; case IKSAR: PR = 15; break; case VAHSHIR: PR = 15; break; case FROGLOK: PR = 30; break; case DRAKKIN: { PR = 15; if (GetDrakkinHeritage() == 3) PR += 10; else if (GetDrakkinHeritage() == 5) PR += 2; break; } default: PR = 15; } int c = GetClass(); // this monk bonus is part of the base if (c == MONK) { int l = GetLevel(); if (l > 50) PR += l - 50; } if (c == ROGUE) { PR += 8; int l = GetLevel(); if (l > 49) { PR += l - 49; } } else if (c == SHADOWKNIGHT) { PR += 4; int l = GetLevel(); if (l > 49) { PR += l - 49; } } PR += itembonuses.PR + spellbonuses.PR + aabonuses.PR; if (PR < 1) { PR = 1; } if (PR > GetMaxPR()) { PR = GetMaxPR(); } return (PR); }
int32 Client::CalcDR() { //racial bases switch (GetBaseRace()) { case HUMAN: DR = 15; break; case BARBARIAN: DR = 15; break; case ERUDITE: DR = 10; break; case WOOD_ELF: DR = 15; break; case HIGH_ELF: DR = 15; break; case DARK_ELF: DR = 15; break; case HALF_ELF: DR = 15; break; case DWARF: DR = 15; break; case TROLL: DR = 15; break; case OGRE: DR = 15; break; case HALFLING: DR = 20; break; case GNOME: DR = 15; break; case IKSAR: DR = 15; break; case VAHSHIR: DR = 15; break; case FROGLOK: DR = 15; break; case DRAKKIN: { DR = 15; if (GetDrakkinHeritage() == 1) DR += 10; else if (GetDrakkinHeritage() == 5) DR += 2; break; } default: DR = 15; } int c = GetClass(); // the monk one is part of base resist if (c == MONK) { int l = GetLevel(); if (l > 50) DR += l - 50; } if (c == PALADIN) { DR += 8; int l = GetLevel(); if (l > 49) { DR += l - 49; } } else if (c == SHADOWKNIGHT || c == BEASTLORD) { DR += 4; int l = GetLevel(); if (l > 49) { DR += l - 49; } } DR += itembonuses.DR + spellbonuses.DR + aabonuses.DR; if (DR < 1) { DR = 1; } if (DR > GetMaxDR()) { DR = GetMaxDR(); } return (DR); }
//The AA multipliers are set to be 5, but were 2 on WR //The resistant discipline which I think should be here is implemented //in Mob::ResistSpell int32 Client::CalcMR() { //racial bases switch (GetBaseRace()) { case HUMAN: MR = 25; break; case BARBARIAN: MR = 25; break; case ERUDITE: MR = 30; break; case WOOD_ELF: MR = 25; break; case HIGH_ELF: MR = 25; break; case DARK_ELF: MR = 25; break; case HALF_ELF: MR = 25; break; case DWARF: MR = 30; break; case TROLL: MR = 25; break; case OGRE: MR = 25; break; case HALFLING: MR = 25; break; case GNOME: MR = 25; break; case IKSAR: MR = 25; break; case VAHSHIR: MR = 25; break; case FROGLOK: MR = 30; break; case DRAKKIN: { MR = 25; if (GetDrakkinHeritage() == 2) MR += 10; else if (GetDrakkinHeritage() == 5) MR += 2; break; } default: MR = 20; } MR += itembonuses.MR + spellbonuses.MR + aabonuses.MR; if (GetClass() == WARRIOR || GetClass() == BERSERKER) { MR += GetLevel() / 2; } if (MR < 1) { MR = 1; } if (MR > GetMaxMR()) { MR = GetMaxMR(); } return (MR); }
int32 Client::CalcDR() { //racial bases switch(GetBaseRace()) { case HUMAN: DR = 15; break; case BARBARIAN: DR = 15; break; case ERUDITE: DR = 10; break; case WOOD_ELF: DR = 15; break; case HIGH_ELF: DR = 15; break; case DARK_ELF: DR = 15; break; case HALF_ELF: DR = 15; break; case DWARF: DR = 15; break; case TROLL: DR = 15; break; case OGRE: DR = 15; break; case HALFLING: DR = 20; break; case GNOME: DR = 15; break; case IKSAR: DR = 15; break; case VAHSHIR: DR = 15; break; case FROGLOK: DR = 15; break; case DRAKKIN: DR = 15; break; default: DR = 15; } int c = GetClass(); if(c == PALADIN) { DR += 8; int l = GetLevel(); if(l > 49) DR += l - 49; } else if(c == SHADOWKNIGHT) { DR += 4; int l = GetLevel(); if(l > 49) DR += l - 49; } DR += itembonuses.DR + spellbonuses.DR + aabonuses.DR; if(DR < 1) DR = 1; if(DR > GetMaxDR()) DR = GetMaxDR(); return(DR); }
uint32 Client::GetEXPForLevel(uint16 check_level, bool aa) { // Warning: Changing anything in this method WILL cause levels to change in-game the first time a player // gains or loses XP. if(aa) { if(m_epp.perAA > 99) return (RuleI(AA, ExpPerPoint)); else check_level = 52; } check_level -= 1; float base = (check_level)*(check_level)*(check_level); // Classes: In the XP formula AK used, they WERE calculated in. This was due to Sony not being able to change their XP // formula drastically (see above comment.) Instead, they gave the penalized classes a bonus on gain. We've decided to go // the easy route, and simply not use a class mod at all. float playermod = 10; uint8 race = GetBaseRace(); if(race == HALFLING) playermod *= 95.0; else if(race == DARK_ELF || race == DWARF || race == ERUDITE || race == GNOME || race == HALF_ELF || race == HIGH_ELF || race == HUMAN || race == WOOD_ELF || race == VAHSHIR) playermod *= 100.0; else if(race == BARBARIAN) playermod *= 105.0; else if(race == OGRE) playermod *= 115.0; else if(race == IKSAR || race == TROLL) playermod *= 120.0; float mod; if (check_level <= 29) mod = 1.0; else if (check_level <= 34) mod = 1.1; else if (check_level <= 39) mod = 1.2; else if (check_level <= 44) mod = 1.3; else if (check_level <= 50) mod = 1.4; else if (check_level == 51) mod = 1.5; else if (check_level == 52) mod = 1.6; else if (check_level == 53) mod = 1.7; else if (check_level == 54) mod = 1.9; else if (check_level == 55) mod = 2.1; else if (check_level == 56) mod = 2.3; else if (check_level == 57) mod = 2.5; else if (check_level == 58) mod = 2.7; else if (check_level == 59) mod = 3.0; else if (check_level == 60) mod = 3.1; else if (check_level == 61) mod = 3.2; else if (check_level == 62) mod = 3.3; else if (check_level == 63) mod = 3.4; else if (check_level == 64) mod = 3.5; else mod = 3.6; uint32 finalxp = uint32(base * playermod * mod); if(aa) { uint32 aaxp; aaxp = finalxp - GetEXPForLevel(51); return aaxp; } finalxp = mod_client_xp_for_level(finalxp, check_level); return finalxp; }
int32 Client::CalcPR() { //racial bases switch(GetBaseRace()) { case HUMAN: PR = 15; break; case BARBARIAN: PR = 15; break; case ERUDITE: PR = 15; break; case WOOD_ELF: PR = 15; break; case HIGH_ELF: PR = 15; break; case DARK_ELF: PR = 15; break; case HALF_ELF: PR = 15; break; case DWARF: PR = 20; break; case TROLL: PR = 15; break; case OGRE: PR = 15; break; case HALFLING: PR = 20; break; case GNOME: PR = 15; break; case IKSAR: PR = 15; break; case VAHSHIR: PR = 15; break; case FROGLOK: PR = 30; break; case DRAKKIN: PR = 15; break; default: PR = 15; } int c = GetClass(); if(c == ROGUE) { PR += 8; int l = GetLevel(); if(l > 49) PR += l - 49; } else if(c == SHADOWKNIGHT) { PR += 4; int l = GetLevel(); if(l > 49) PR += l - 49; } PR += itembonuses.PR + spellbonuses.PR + aabonuses.PR; if(PR < 1) PR = 1; if(PR > GetMaxPR()) PR = GetMaxPR(); return(PR); }
// Split from the basic MakePet to allow backward compatiblity with existing code while also // making it possible for petpower to be retained without the focus item having to // stay equipped when the character zones. petpower of -1 means that the currently equipped petfocus // of a client is searched for and used instead. void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, const char *petname, float in_size) { // Sanity and early out checking first. if(HasPet() || pettype == nullptr) return; int16 act_power = 0; // The actual pet power we'll use. if (petpower == -1) { if (this->IsClient()) { act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);//Client only act_power = CastToClient()->mod_pet_power(act_power, spell_id); } #ifdef BOTS else if (this->IsBot()) act_power = CastToBot()->GetBotFocusEffect(Bot::BotfocusPetPower, spell_id); #endif } else if (petpower > 0) act_power = petpower; // optional rule: classic style variance in pets. Achieve this by // adding a random 0-4 to pet power, since it only comes in increments // of five from focus effects. //lookup our pets table record for this type PetRecord record; if(!database.GetPoweredPetEntry(pettype, act_power, &record)) { Message(13, "Unable to find data for pet %s", pettype); Log.Out(Logs::General, Logs::Error, "Unable to find data for pet %s, check pets table.", pettype); return; } //find the NPC data for the specified NPC type const NPCType *base = database.LoadNPCTypesData(record.npc_type); if(base == nullptr) { Message(13, "Unable to load NPC data for pet %s", pettype); Log.Out(Logs::General, Logs::Error, "Unable to load NPC data for pet %s (NPC ID %d), check pets and npc_types tables.", pettype, record.npc_type); return; } //we copy the npc_type data because we need to edit it a bit auto npc_type = new NPCType; memcpy(npc_type, base, sizeof(NPCType)); // If pet power is set to -1 in the DB, use stat scaling if ((this->IsClient() #ifdef BOTS || this->IsBot() #endif ) && record.petpower == -1) { float scale_power = (float)act_power / 100.0f; if(scale_power > 0) { npc_type->max_hp *= (1 + scale_power); npc_type->cur_hp = npc_type->max_hp; npc_type->AC *= (1 + scale_power); npc_type->level += 1 + ((int)act_power / 25) > npc_type->level + RuleR(Pets, PetPowerLevelCap) ? RuleR(Pets, PetPowerLevelCap) : 1 + ((int)act_power / 25); // gains an additional level for every 25 pet power npc_type->min_dmg = (npc_type->min_dmg * (1 + (scale_power / 2))); npc_type->max_dmg = (npc_type->max_dmg * (1 + (scale_power / 2))); npc_type->size = npc_type->size * (1 + (scale_power / 2)) > npc_type->size * 3 ? npc_type->size * 3 : npc_type-> size * (1 + (scale_power / 2)); } record.petpower = act_power; } //Live AA - Elemental Durability int16 MaxHP = aabonuses.PetMaxHP + itembonuses.PetMaxHP + spellbonuses.PetMaxHP; if (MaxHP){ npc_type->max_hp += (npc_type->max_hp*MaxHP)/100; npc_type->cur_hp = npc_type->max_hp; } //TODO: think about regen (engaged vs. not engaged) // Pet naming: // 0 - `s pet // 1 - `s familiar // 2 - `s Warder // 3 - Random name if client, `s pet for others // 4 - Keep DB name if (petname != nullptr) { // Name was provided, use it. strn0cpy(npc_type->name, petname, 64); } else if (record.petnaming == 0) { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } else if (record.petnaming == 1) { strcpy(npc_type->name, this->GetName()); npc_type->name[19] = '\0'; strcat(npc_type->name, "`s_familiar"); } else if (record.petnaming == 2) { strcpy(npc_type->name, this->GetName()); npc_type->name[21] = 0; strcat(npc_type->name, "`s_Warder"); } else if (record.petnaming == 4) { // Keep the DB name } else if (record.petnaming == 3 && IsClient()) { strcpy(npc_type->name, GetRandPetName()); } else { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } //handle beastlord pet appearance if(record.petnaming == 2) { switch(GetBaseRace()) { case VAHSHIR: npc_type->race = TIGER; npc_type->size *= 0.8f; break; case TROLL: npc_type->race = ALLIGATOR; npc_type->size *= 2.5f; break; case OGRE: npc_type->race = BEAR; npc_type->texture = 3; npc_type->gender = 2; break; case BARBARIAN: npc_type->race = WOLF; npc_type->texture = 2; break; case IKSAR: npc_type->race = WOLF; npc_type->texture = 0; npc_type->gender = 1; npc_type->size *= 2.0f; npc_type->luclinface = 0; break; default: npc_type->race = WOLF; npc_type->texture = 0; } } // handle monster summoning pet appearance if(record.monsterflag) { uint32 monsterid = 0; // get a random npc id from the spawngroups assigned to this zone auto query = StringFormat("SELECT npcID " "FROM (spawnentry INNER JOIN spawn2 ON spawn2.spawngroupID = spawnentry.spawngroupID) " "INNER JOIN npc_types ON npc_types.id = spawnentry.npcID " "WHERE spawn2.zone = '%s' AND npc_types.bodytype NOT IN (11, 33, 66, 67) " "AND npc_types.race NOT IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 44, " "55, 67, 71, 72, 73, 77, 78, 81, 90, 92, 93, 94, 106, 112, 114, 127, 128, " "130, 139, 141, 183, 236, 237, 238, 239, 254, 266, 329, 330, 378, 379, " "380, 381, 382, 383, 404, 522) " "ORDER BY RAND() LIMIT 1", zone->GetShortName()); auto results = database.QueryDatabase(query); if (!results.Success()) { safe_delete(npc_type); return; } if (results.RowCount() != 0) { auto row = results.begin(); monsterid = atoi(row[0]); } // since we don't have any monsters, just make it look like an earth pet for now if (monsterid == 0) monsterid = 567; // give the summoned pet the attributes of the monster we found const NPCType* monster = database.LoadNPCTypesData(monsterid); if(monster) { npc_type->race = monster->race; npc_type->size = monster->size; npc_type->texture = monster->texture; npc_type->gender = monster->gender; npc_type->luclinface = monster->luclinface; npc_type->helmtexture = monster->helmtexture; npc_type->herosforgemodel = monster->herosforgemodel; } else Log.Out(Logs::General, Logs::Error, "Error loading NPC data for monster summoning pet (NPC ID %d)", monsterid); } //this takes ownership of the npc_type data auto npc = new Pet(npc_type, this, (PetType)record.petcontrol, spell_id, record.petpower); // Now that we have an actual object to interact with, load // the base items for the pet. These are always loaded // so that a rank 1 suspend minion does not kill things // like the special back items some focused pets may receive. uint32 petinv[EQEmu::legacy::EQUIPMENT_SIZE]; memset(petinv, 0, sizeof(petinv)); const EQEmu::ItemData *item = 0; if (database.GetBasePetItems(record.equipmentset, petinv)) { for (int i = 0; i < EQEmu::legacy::EQUIPMENT_SIZE; i++) if (petinv[i]) { item = database.GetItem(petinv[i]); npc->AddLootDrop(item, &npc->itemlist, 0, 1, 127, true, true); } } npc->UpdateEquipmentLight(); // finally, override size if one was provided if (in_size > 0.0f) npc->size = in_size; entity_list.AddNPC(npc, true, true); SetPetID(npc->GetID()); // We need to handle PetType 5 (petHatelist), add the current target to the hatelist of the pet if (record.petcontrol == petTargetLock) { Mob* target = GetTarget(); if (target){ npc->AddToHateList(target, 1); npc->SetPetTargetLockID(target->GetID()); npc->SetSpecialAbility(IMMUNE_AGGRO, 1); } else npc->Kill(); //On live casts spell 892 Unsummon (Kayen - Too limiting to use that for emu since pet can have more than 20k HP) } }
// Split from the basic MakePet to allow backward compatiblity with existing code while also // making it possible for petpower to be retained without the focus item having to // stay equipped when the character zones. petpower of -1 means that the currently equipped petfocus // of a client is searched for and used instead. void Mob::MakePoweredPet(int16 spell_id, const char* pettype, sint16 petpower, const char *petname) { // Sanity and early out checking first. if(HasPet() || pettype == NULL) return; sint16 act_power = 0; // lets see if this works to randomize pet levels // The actual pet power we'll use. if (petpower == 0) { // was == -1 if (this->IsClient()) act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id); } if (petpower == -1) { act_power = petpower + MakeRandomInt(0,5); } // optional rule: classic style variance in pets. Achieve this by // adding a random 0-4 to pet power, since it only comes in increments // of five from focus effects. //lookup our pets table record for this type PetRecord record; if(!database.GetPoweredPetEntry(pettype, act_power, &record)) { Message(13, "Unable to find data for pet %s", pettype); LogFile->write(EQEMuLog::Error, "Unable to find data for pet %s, check pets table.", pettype); return; } //find the NPC data for the specified NPC type const NPCType *base = database.GetNPCType(record.npc_type); if(base == NULL) { Message(13, "Unable to load NPC data for pet %s", pettype); LogFile->write(EQEMuLog::Error, "Unable to load NPC data for pet %s (NPC ID %d), check pets and npc_types tables.", pettype, record.npc_type); return; } //we copy the npc_type data because we need to edit it a bit NPCType *npc_type = new NPCType; memcpy(npc_type, base, sizeof(NPCType)); // If pet power is set to -1 in the DB, use stat scaling if (this->IsClient() && petpower == -1) { float scale_power = (float)act_power / 100.0f; if(scale_power > 0) { npc_type->max_hp *= (1 + scale_power); npc_type->cur_hp = npc_type->max_hp; npc_type->AC *= (1 + scale_power); npc_type->level += (float)scale_power * 50; //(int)act_power; // gains an additional level for every 25 pet power npc_type->min_dmg = (npc_type->min_dmg * (1 + (scale_power / 2))); npc_type->max_dmg = (npc_type->max_dmg * (1 + (scale_power / 2))); npc_type->size *= (1 + (scale_power / 2)); } petpower = act_power; } switch (GetAA(aaElementalDurability)) { case 1: npc_type->max_hp *= 1.02; npc_type->cur_hp = npc_type->max_hp; break; case 2: npc_type->max_hp *= 1.05; npc_type->cur_hp = npc_type->max_hp; break; case 3: npc_type->max_hp *= 1.10; npc_type->cur_hp = npc_type->max_hp; break; } //TODO: think about regen (engaged vs. not engaged) // Pet naming: // 0 - `s pet // 1 - `s familiar // 2 - `s Warder // 3 - Random name if client, `s pet for others // 4 - Keep DB name if (petname != NULL) { // Name was provided, use it. strn0cpy(npc_type->name, petname, 64); } else if (record.petnaming == 0) { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } else if (record.petnaming == 1) { strcpy(npc_type->name, this->GetName()); npc_type->name[19] = '\0'; strcat(npc_type->name, "`s_familiar"); } else if (record.petnaming == 2) { strcpy(npc_type->name, this->GetName()); npc_type->name[21] = 0; strcat(npc_type->name, "`s_Warder"); } else if (record.petnaming == 4) { // Keep the DB name } else if (record.petnaming == 3 && IsClient()) { strcpy(npc_type->name, GetRandPetName()); } else { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } //handle beastlord pet appearance if(record.petnaming == 2) { switch(GetBaseRace()) { case VAHSHIR: npc_type->race = TIGER; npc_type->size *= 0.8f; break; case TROLL: npc_type->race = ALLIGATOR; npc_type->size *= 2.5f; break; case OGRE: npc_type->race = BEAR; npc_type->texture = 3; npc_type->gender = 2; break; case BARBARIAN: npc_type->race = WOLF; npc_type->texture = 2; break; case IKSAR: npc_type->race = WOLF; npc_type->texture = 0; npc_type->gender = 1; npc_type->size *= 2.0f; npc_type->luclinface = 0; break; default: npc_type->race = WOLF; npc_type->texture = 0; } } // handle monster summoning pet appearance if(record.monsterflag) { char errbuf[MYSQL_ERRMSG_SIZE]; char* query = 0; MYSQL_RES *result = NULL; MYSQL_ROW row = NULL; uint32 monsterid; // get a random npc id from the spawngroups assigned to this zone if (database.RunQuery(query, MakeAnyLenString(&query, "SELECT npcID FROM (spawnentry INNER JOIN spawn2 ON spawn2.spawngroupID = spawnentry.spawngroupID) " "INNER JOIN npc_types ON npc_types.id = spawnentry.npcID " "WHERE spawn2.zone = '%s' AND npc_types.bodytype NOT IN (11, 33, 66, 67) " "AND npc_types.race NOT IN (0,1,2,3,4,5,6,7,8,9,10,11,12,44,55,67,71,72,73,77,78,81,90,92,93,94,106,112,114,127,128,130,139,141,183,236,237,238,239,254,266,330,378,379,380,381,382,383,404,522) " "ORDER BY RAND() LIMIT 1", zone->GetShortName()), errbuf, &result)) { row = mysql_fetch_row(result); if (row) monsterid = atoi(row[0]); else monsterid = 567; // since we don't have any monsters, just make it look like an earth pet for now } else { // if the database query failed LogFile->write(EQEMuLog::Error, "Error querying database for monster summoning pet in zone %s (%s)", zone->GetShortName(), errbuf); monsterid = 567; } // give the summoned pet the attributes of the monster we found const NPCType* monster = database.GetNPCType(monsterid); if(monster) { npc_type->race = monster->race; npc_type->size = monster->size; npc_type->texture = monster->texture; npc_type->gender = monster->gender; } else { LogFile->write(EQEMuLog::Error, "Error loading NPC data for monster summoning pet (NPC ID %d)", monsterid); } safe_delete_array(query); } //this takes ownership of the npc_type data Pet *npc = new Pet(npc_type, this, (PetType)record.petcontrol, spell_id, record.petpower); // Now that we have an actual object to interact with, load // the base items for the pet. These are always loaded // so that a rank 1 suspend minion does not kill things // like the special back items some focused pets may receive. int32 petinv[MAX_WORN_INVENTORY]; memset(petinv, 0, sizeof(petinv)); const Item_Struct *item = 0; if (database.GetBasePetItems(record.equipmentset, petinv)) { for (int i=0; i<MAX_WORN_INVENTORY; i++) if (petinv[i]) { item = database.GetItem(petinv[i]); npc->AddLootDrop(item, &npc->itemlist, 0, true, true); } } entity_list.AddNPC(npc, true, true); SetPetID(npc->GetID()); // We need to handle PetType 5 (petHatelist), add the current target to the hatelist of the pet }
// Split from the basic MakePet to allow backward compatiblity with existing code while also // making it possible for petpower to be retained without the focus item having to // stay equipped when the character zones. petpower of -1 means that the currently equipped petfocus // of a client is searched for and used instead. void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, const char *petname, float in_size) { // Sanity and early out checking first. bool scale_pet = false; if(HasPet() || pettype == nullptr) return; //lookup our pets table record for this type PetRecord record; if(!database.GetPoweredPetEntry(pettype, petpower, &record)) { Message(CC_Red, "Unable to find data for pet %s", pettype); Log.Out(Logs::General, Logs::Error, "Unable to find data for pet %s, check pets table.", pettype); return; } //find the NPC data for the specified NPC type const NPCType *base = database.GetNPCType(record.npc_type); if(base == nullptr) { Message(CC_Red, "Unable to load NPC data for pet %s", pettype); Log.Out(Logs::General, Logs::Error, "Unable to load NPC data for pet %s (NPC ID %d), check pets and npc_types tables.", pettype, record.npc_type); return; } int act_power = 0; // The actual pet power we'll use. if (petpower == -1) { if (this->IsClient()) { //Message(CC_Red, "We are a client time to check for focus items"); uint16 focusItemId; FocusPetItem petItem; // Loop over all the focus items and figure out which on is the best to use // It will step down from PoP - Classic looking for the best focus item to use based on pet level int16 slot = 0; for(int i=0; i < FocusPetItemSize; i++) { petItem = Pet::focusItems[i]; // Look in out inventory int16 slot_id = this->CastToClient()->GetInv().HasItem(petItem.item_id, 1, invWhereWorn); if(slot_id != INVALID_INDEX) { //skip this focus item if its effect is out of rage for the pet we are casting if(base->level >= petItem.min_level && base->level <= petItem.max_level) { if(EQDEBUG>8) Message(CC_Red, "Found Focus Item: %d in Inventory: %d", petItem.item_id, slot_id); if(EQDEBUG>8) Message(CC_Red, "Npc spell levels: %d (%d - %d)", base->level, petItem.min_level, petItem.max_level); slot = slot_id; focusItemId = petItem.item_id; break; } else { if(EQDEBUG>8) Message(CC_Red, "Moving on Pet base level is out of range: %d (%d - %d)", base->level, petItem.min_level, petItem.max_level); } } } // we have a focus item if(focusItemId) { FocusPetType focusType; // Symbol or Gloves can be used by all NEC, MAG, BST if(petItem.pet_type == FocusPetType::ALL) { //Message(CC_Red, "Type is ALL"); focusType = FocusPetType::ALL; } else { // make sure we can use the focus item as the class .. client should never let us fail this but for sanity! if (GetClass() == MAGICIAN) { if(EQDEBUG>8) Message(CC_Red, "Looking up mage"); if(EQDEBUG>8) Message(CC_Red, "Looking up if spell: %d is allowed ot be focused", spell_id); focusType = Pet::GetPetItemPetTypeFromSpellId(spell_id); if(EQDEBUG>8) Message(CC_Red, "FocusType fround %i", focusType); } else if (GetClass() == NECROMANCER) { if(EQDEBUG>8) Message(CC_Red, "We are a necro"); focusType = FocusPetType::NECRO; } } // Sets the power to be what the focus item has as a mod if(EQDEBUG>8) { Message(CC_Red, "Pet Item Type ALL is %i", FocusPetType::ALL); Message(CC_Red, "Pet Item Type FIRE is %i", FocusPetType::FIRE); Message(CC_Red, "Pet Item Type WATER is %i", FocusPetType::WATER); Message(CC_Red, "Pet Item Type AIR is %i", FocusPetType::AIR); Message(CC_Red, "Pet Item Type EARTH is %i", FocusPetType::EARTH); Message(CC_Red, "Pet Item Type NECRO is %i", FocusPetType::NECRO); } if(EQDEBUG>8) Message(CC_Red, "Pet Item Type %i", petItem.pet_type); if (focusType == petItem.pet_type) { if(EQDEBUG>8) Message(CC_Red, "Setting power to: %d", petItem.power); act_power = petItem.power; scale_pet = true; } if(act_power > 0) { if(EQDEBUG>8) Message(CC_Yellow, "Debug: You have cast a powered pet. Unadjusted pet power is: %i. HasItem returned: %i.", act_power, slot); if(focusType == FocusPetType::NECRO ||focusType == FocusPetType::EARTH || focusType == FocusPetType::AIR || focusType == FocusPetType::FIRE || focusType == FocusPetType::WATER) { if(act_power > 10) act_power = 10; } else { if(act_power > 25) act_power = 25; } } } } } else if (petpower > 0) { act_power = petpower; scale_pet = true; } if(EQDEBUG>8) Message(CC_Red, "Power is: %d", act_power); // optional rule: classic style variance in pets. Achieve this by // adding a random 0-4 to pet power, since it only comes in increments // of five from focus effects. //we copy the npc_type data because we need to edit it a bit NPCType *npc_type = new NPCType; memcpy(npc_type, base, sizeof(NPCType)); // If pet power is set to -1 in the DB, use stat scaling if (this->IsClient() && record.petpower == -1) { if(scale_pet) { float scale_power = (float)act_power / 100.0f; if(scale_power > 0) { npc_type->max_hp = (int16) (npc_type->max_hp * (1 + scale_power)); npc_type->cur_hp = npc_type->max_hp; npc_type->AC = (int16) (npc_type->AC * (1 + scale_power)); npc_type->level += (int16) 1 + ((int16)act_power / 25); // gains an additional level for every 25 pet power npc_type->min_dmg = (int16) (npc_type->min_dmg * (1 + (scale_power / 2))); npc_type->max_dmg = (int16) (npc_type->max_dmg * (1 + (scale_power / 2))); npc_type->size = (npc_type->size * (1 + scale_power)); } record.petpower = act_power; } } //Live AA - Elemental Durability int16 MaxHP = aabonuses.PetMaxHP + itembonuses.PetMaxHP + spellbonuses.PetMaxHP; if (MaxHP){ npc_type->max_hp += (npc_type->max_hp*MaxHP)/100; npc_type->cur_hp = npc_type->max_hp; } //TODO: think about regen (engaged vs. not engaged) // Pet naming: // 0 - `s pet // 1 - `s familiar // 2 - `s Warder // 3 - Random name if client, `s pet for others // 4 - Keep DB name if (petname != nullptr) { // Name was provided, use it. strn0cpy(npc_type->name, petname, 64); } else if (record.petnaming == 0) { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } else if (record.petnaming == 1) { strcpy(npc_type->name, this->GetName()); npc_type->name[19] = '\0'; strcat(npc_type->name, "`s_familiar"); } else if (record.petnaming == 2) { strcpy(npc_type->name, this->GetName()); npc_type->name[21] = 0; strcat(npc_type->name, "`s_warder"); } else if (record.petnaming == 4) { // Keep the DB name } else if (record.petnaming == 3 && IsClient()) { strcpy(npc_type->name, GetRandPetName()); } else { strcpy(npc_type->name, this->GetCleanName()); npc_type->name[25] = '\0'; strcat(npc_type->name, "`s_pet"); } //handle beastlord pet appearance if(record.petnaming == 2) { switch(GetBaseRace()) { case VAHSHIR: npc_type->race = TIGER; npc_type->size *= 0.8f; break; case TROLL: npc_type->race = ALLIGATOR; npc_type->size *= 2.5f; break; case OGRE: npc_type->race = BEAR; npc_type->texture = 3; npc_type->gender = 2; break; case BARBARIAN: npc_type->race = WOLF; npc_type->texture = 2; break; case IKSAR: npc_type->race = WOLF; npc_type->texture = 0; npc_type->gender = 1; npc_type->luclinface = 0; break; default: npc_type->race = WOLF; npc_type->texture = 0; } } // handle monster summoning pet appearance if(record.monsterflag) { uint32 monsterid = 0; // get a random npc id from the spawngroups assigned to this zone auto query = StringFormat("SELECT npcID " "FROM (spawnentry INNER JOIN spawn2 ON spawn2.spawngroupID = spawnentry.spawngroupID) " "INNER JOIN npc_types ON npc_types.id = spawnentry.npcID " "WHERE spawn2.zone = '%s' AND npc_types.bodytype NOT IN (11, 33, 66, 67) " "AND npc_types.race NOT IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 44, " "55, 67, 71, 72, 73, 77, 78, 81, 90, 92, 93, 94, 106, 112, 114, 127, 128, " "130, 139, 141, 183, 236, 237, 238, 239, 254, 266, 329, 330, 378, 379, " "380, 381, 382, 383, 404, 522) " "ORDER BY RAND() LIMIT 1", zone->GetShortName()); auto results = database.QueryDatabase(query); if (!results.Success()) { // if the database query failed } if (results.RowCount() != 0) { auto row = results.begin(); monsterid = atoi(row[0]); } // since we don't have any monsters, just make it look like an earth pet for now if (monsterid == 0) monsterid = 567; // give the summoned pet the attributes of the monster we found const NPCType* monster = database.GetNPCType(monsterid); if(monster) { npc_type->race = monster->race; npc_type->size = monster->size; npc_type->texture = monster->texture; npc_type->gender = monster->gender; npc_type->luclinface = monster->luclinface; npc_type->helmtexture = monster->helmtexture; } else Log.Out(Logs::General, Logs::Error, "Error loading NPC data for monster summoning pet (NPC ID %d)", monsterid); } //this takes ownership of the npc_type data Pet *npc = new Pet(npc_type, this, (PetType)record.petcontrol, spell_id, record.petpower); // Now that we have an actual object to interact with, load // the base items for the pet. These are always loaded // so that a rank 1 suspend minion does not kill things // like the special back items some focused pets may receive. uint32 petinv[EmuConstants::EQUIPMENT_SIZE]; memset(petinv, 0, sizeof(petinv)); const Item_Struct *item = 0; if (database.GetBasePetItems(record.equipmentset, petinv)) { for (int i = 0; i<EmuConstants::EQUIPMENT_SIZE; i++) if (petinv[i]) { item = database.GetItem(petinv[i]); npc->AddLootDrop(item, &npc->itemlist, 0, 1, 127, true, true); } } npc->UpdateEquipmentLight(); // finally, override size if one was provided if (in_size > 0.0f) npc->size = in_size; entity_list.AddNPC(npc, true, true); SetPetID(npc->GetID()); // We need to handle PetType 5 (petHatelist), add the current target to the hatelist of the pet }