int32 Client::GetACMit() { int mitigation = 0; if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) { mitigation = (GetSkill(SkillDefense) + itembonuses.HeroicAGI/10)/4 + (itembonuses.AC+1); mitigation -= 4; } else { mitigation = (GetSkill(SkillDefense) + itembonuses.HeroicAGI/10)/3 + ((itembonuses.AC*4)/3); if(m_pp.class_ == MONK) mitigation += GetLevel() * 13/10; //the 13/10 might be wrong, but it is close... } // Shield AC bonus for HeroicSTR if(itembonuses.HeroicSTR) { bool equiped = CastToClient()->m_inv.GetItem(MainSecondary); if(equiped) { uint8 shield = CastToClient()->m_inv.GetItem(MainSecondary)->GetItem()->ItemType; if(shield == ItemTypeShield) mitigation += itembonuses.HeroicSTR/2; } } return(mitigation*1000/847); }
void Mob::RemoveAllAuras() { if (IsClient()) { database.SaveAuras(CastToClient()); EQApplicationPacket outapp(OP_UpdateAura, 4); outapp.WriteUInt32(2); CastToClient()->QueuePacket(&outapp); } // this is sent on camp/zone, so it just despawns? if (aura_mgr.count) { for (auto &e : aura_mgr.auras) { if (e.aura) e.aura->Depop(); } } aura_mgr.count = 0; if (trap_mgr.count) { for (auto &e : trap_mgr.auras) { if (e.aura) e.aura->Depop(); } } trap_mgr.count = 0; return; }
void Mob::ExpendAlternateAdvancementCharge(uint32 aa_id) { for(auto &iter : aa_ranks) { AA::Ability *ability = zone->GetAlternateAdvancementAbility(iter.first); if(ability && aa_id == ability->id) { if(iter.second.second > 0) { iter.second.second -= 1; if(iter.second.second == 0) { if(IsClient()) { AA::Rank *r = ability->GetRankByPointsSpent(iter.second.first); if(r) { CastToClient()->GetEPP().expended_aa += r->cost; } } if (IsClient()) { auto c = CastToClient(); c->RemoveExpendedAA(ability->first_rank_id); } aa_ranks.erase(iter.first); } if(IsClient()) { Client *c = CastToClient(); c->SaveAA(); c->SendAlternateAdvancementPoints(); } } return; } } }
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; }
void Mob::RemoveAura(int spawn_id, bool skip_strip, bool expired) { for (int i = 0; i < aura_mgr.count; ++i) { auto &aura = aura_mgr.auras[i]; if (aura.spawn_id == spawn_id) { if (aura.aura) aura.aura->Depop(skip_strip); if (expired && IsClient()) { CastToClient()->SendColoredText( CC_Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color // need to update client UI too auto app = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraDestory_Struct)); auto ads = (AuraDestory_Struct *)app->pBuffer; ads->action = 1; // delete ads->entity_id = spawn_id; CastToClient()->QueuePacket(app); safe_delete(app); } while (aura_mgr.count - 1 > i) { i++; aura.spawn_id = aura_mgr.auras[i].spawn_id; aura.icon = aura_mgr.auras[i].icon; aura.aura = aura_mgr.auras[i].aura; aura_mgr.auras[i].aura = nullptr; strn0cpy(aura.name, aura_mgr.auras[i].name, 64); } aura_mgr.count--; return; } } for (int i = 0; i < trap_mgr.count; ++i) { auto &aura = trap_mgr.auras[i]; if (aura.spawn_id == spawn_id) { if (aura.aura) aura.aura->Depop(skip_strip); if (expired && IsClient()) CastToClient()->SendColoredText( CC_Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color while (trap_mgr.count - 1 > i) { i++; aura.spawn_id = trap_mgr.auras[i].spawn_id; aura.icon = trap_mgr.auras[i].icon; aura.aura = trap_mgr.auras[i].aura; trap_mgr.auras[i].aura = nullptr; strn0cpy(aura.name, trap_mgr.auras[i].name, 64); } trap_mgr.count--; return; } } return; }
// This is a testing formula for AC, the value this returns should be the same value as the one the client shows... // ac1 and ac2 are probably the damage migitation and damage avoidance numbers, not sure which is which. // I forgot to include the iksar defense bonus and i cant find my notes now... // AC from spells are not included (cant even cast spells yet..) int32 Client::CalcAC() { // new formula int avoidance = (acmod() + ((GetSkill(SkillDefense) + itembonuses.HeroicAGI / 10) * 16) / 9); if (avoidance < 0) { avoidance = 0; } int mitigation = 0; if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) { //something is wrong with this, naked casters have the wrong natural AC // mitigation = (spellbonuses.AC/3) + (GetSkill(DEFENSE)/2) + (itembonuses.AC+1); mitigation = (GetSkill(SkillDefense) + itembonuses.HeroicAGI / 10) / 4 + (itembonuses.AC + 1); //this might be off by 4.. mitigation -= 4; } else { // mitigation = (spellbonuses.AC/4) + (GetSkill(DEFENSE)/3) + ((itembonuses.AC*4)/3); mitigation = (GetSkill(SkillDefense) + itembonuses.HeroicAGI / 10) / 3 + ((itembonuses.AC * 4) / 3); if (m_pp.class_ == MONK) { mitigation += GetLevel() * 13 / 10; //the 13/10 might be wrong, but it is close... } } int displayed = 0; displayed += ((avoidance + mitigation) * 1000) / 847; //natural AC //Iksar AC, untested if (GetRace() == IKSAR) { displayed += 12; int iksarlevel = GetLevel(); iksarlevel -= 10; if (iksarlevel > 25) { iksarlevel = 25; } if (iksarlevel > 0) { displayed += iksarlevel * 12 / 10; } } // Shield AC bonus for HeroicSTR if (itembonuses.HeroicSTR) { bool equiped = CastToClient()->m_inv.GetItem(MainSecondary); if (equiped) { uint8 shield = CastToClient()->m_inv.GetItem(MainSecondary)->GetItem()->ItemType; if (shield == ItemTypeShield) { displayed += itembonuses.HeroicSTR / 2; } } } //spell AC bonuses are added directly to natural total displayed += spellbonuses.AC; AC = displayed; return (AC); }
void Pet::SetTarget(Mob *mob) { if (mob == GetTarget()) return; auto owner = GetOwner(); if (owner && owner->IsClient() && owner->CastToClient()->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) { auto app = new EQApplicationPacket(OP_PetHoTT, sizeof(ClientTarget_Struct)); auto ct = (ClientTarget_Struct *)app->pBuffer; ct->new_target = mob ? mob->GetID() : 0; owner->CastToClient()->QueuePacket(app); safe_delete(app); } NPC::SetTarget(mob); }
//healing and buffing aggro int32 Mob::CheckHealAggroAmount(uint16 spellid, uint32 heal_possible) { uint16 spell_id = spellid; int32 AggroAmount = 0; for (int o = 0; o < EFFECT_COUNT; o++) { switch(spells[spell_id].effectid[o]) { case SE_CurrentHP: { AggroAmount += spells[spell_id].mana; break; } case SE_Rune: { AggroAmount += CalcSpellEffectValue_formula(spells[spell_id].formula[0], spells[spell_id].base[0], spells[spell_id].max[o], this->GetLevel(), spellid) * 2; break; } case SE_HealOverTime:{ AggroAmount += CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], this->GetLevel(), spell_id); break; } default:{ break; } } } if (IsBardSong(spell_id)) AggroAmount = AggroAmount * RuleI(Aggro, SongAggroMod) / 100; if (GetOwner() && IsPet()) AggroAmount = AggroAmount * RuleI(Aggro, PetSpellAggroMod) / 100; if(AggroAmount > 0) { int HateMod = RuleI(Aggro, SpellAggroMod); if(IsClient()) { HateMod += CastToClient()->GetFocusEffect(focusSpellHateMod, spell_id); } //Live AA - Spell casting subtlety HateMod += aabonuses.hatemod + spellbonuses.hatemod + itembonuses.hatemod; AggroAmount = (AggroAmount * HateMod) / 100; //made up number probably scales a bit differently on live but it seems like it will be close enough //every time you cast on live you get a certain amount of "this is a spell" aggro //confirmed by EQ devs to be 100 exactly at level 85. From their wording it doesn't seem like it's affected //by hate modifiers either. //AggroAmount += (slevel*slevel/72); // Moved Below } if(AggroAmount < 0) return 0; else return AggroAmount; }
void Mob::SetPetID(uint16 NewPetID) { if (NewPetID == GetID() && NewPetID != 0) return; petid = NewPetID; if(IsClient()) { Mob* NewPet = entity_list.GetMob(NewPetID); CastToClient()->UpdateXTargetType(MyPet, NewPet); } }
bool Aura::ShouldISpawnFor(Client *c) { if (spawn_type == AuraSpawns::Noone) return false; if (spawn_type == AuraSpawns::Everyone) return true; // hey, it's our owner! if (c->GetID() == m_owner) return true; // so this one is a bit trickier auto owner = GetOwner(); if (owner == nullptr) return false; // hmm owner = owner->GetOwnerOrSelf(); // pet auras we need the pet's owner if (owner == nullptr) // shouldn't really be needed return false; // gotta check again for pet aura case -.- if (owner == c) return true; if (owner->IsRaidGrouped() && owner->IsClient()) { auto raid = owner->GetRaid(); if (raid == nullptr) return false; // hmm auto group_id = raid->GetGroup(owner->CastToClient()); if (group_id == 0xFFFFFFFF) // owner handled above, and they're in a raid and groupless return false; auto idx = raid->GetPlayerIndex(c); if (idx == 0xFFFFFFFF) // they're not in our raid! return false; if (raid->members[idx].GroupNumber != group_id) // in our raid, but not our group return false; return true; // we got here so we know that 1 they're in our raid and 2 they're in our group! } else if (owner->IsGrouped()) { auto group = owner->GetGroup(); if (group == nullptr) return false; // hmm // easy, in our group return group->IsGroupMember(c); } // our owner is not raided or grouped, and they're handled above so we don't spawn! return false; }
void Mob::AddAura(Aura *aura, AuraRecord &record) { // this is called only when it's safe assert(aura != nullptr); strn0cpy(aura_mgr.auras[aura_mgr.count].name, aura->GetCleanName(), 64); aura_mgr.auras[aura_mgr.count].spawn_id = aura->GetID(); aura_mgr.auras[aura_mgr.count].aura = aura; if (record.icon == -1) aura_mgr.auras[aura_mgr.count].icon = spells[record.spell_id].new_icon; else aura_mgr.auras[aura_mgr.count].icon = record.icon; if (IsClient()) { auto outapp = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraCreate_Struct)); auto aura_create = (AuraCreate_Struct *)outapp->pBuffer; aura_create->action = 0; aura_create->type = 1; // this can be 0 sometimes too strn0cpy(aura_create->aura_name, aura_mgr.auras[aura_mgr.count].name, 64); aura_create->entity_id = aura_mgr.auras[aura_mgr.count].spawn_id; aura_create->icon = aura_mgr.auras[aura_mgr.count].icon; CastToClient()->FastQueuePacket(&outapp); } // we can increment this now aura_mgr.count++; }
void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); if(!rank) { return; } AA::Ability *ability = rank->base_ability; if(!ability) { return; } if(!IsValidSpell(rank->spell)) { return; } if(!CanUseAlternateAdvancementRank(rank)) { return; } //make sure it is not a passive if(!rank->effects.empty()) { return; } uint32 charges = 0; // We don't have the AA if (!GetAA(rank_id, &charges)) return; //if expendable make sure we have charges if(ability->charges > 0 && charges < 1) return; //check cooldown if(!p_timers.Expired(&database, rank->spell_type + pTimerAAStart)) { uint32 aaremain = p_timers.GetRemainingTime(rank->spell_type + pTimerAAStart); uint32 aaremain_hr = aaremain / (60 * 60); uint32 aaremain_min = (aaremain / 60) % 60; uint32 aaremain_sec = aaremain % 60; if(aaremain_hr >= 1) { Message(13, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", aaremain_hr, aaremain_min, aaremain_sec); } else { Message(13, "You can use this ability again in %u minute(s) %u seconds", aaremain_min, aaremain_sec); } return; } //calculate cooldown int cooldown = rank->recast_time - GetAlternateAdvancementCooldownReduction(rank); if(cooldown < 0) { cooldown = 0; } if (!IsCastWhileInvis(rank->spell)) CommonBreakInvisible(); // Bards can cast instant cast AAs while they are casting another song if(spells[rank->spell].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { if(!SpellFinished(rank->spell, entity_list.GetMob(target_id), ALTERNATE_ABILITY_SPELL_SLOT, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { return; } ExpendAlternateAdvancementCharge(ability->id); } else { if(!CastSpell(rank->spell, target_id, ALTERNATE_ABILITY_SPELL_SLOT, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { return; } } CastToClient()->GetPTimers().Start(rank->spell_type + pTimerAAStart, cooldown); SendAlternateAdvancementTimer(rank->spell_type, 0, 0); }
bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ) { if(GetID()==0) return true; if(speed <= 0) { SetCurrentSpeed(0); return true; } if ((m_Position.x-x == 0) && (m_Position.y-y == 0)) {//spawn is at target coords if(m_Position.z-z != 0) { m_Position.z = z; Log.Out(Logs::Detail, Logs::AI, "Calc Position2 (%.3f, %.3f, %.3f): Jumping pure Z.", x, y, z); return true; } Log.Out(Logs::Detail, Logs::AI, "Calc Position2 (%.3f, %.3f, %.3f) inWater=%d: We are there.", x, y, z, inWater); return false; } else if ((std::abs(m_Position.x - x) < 0.1) && (std::abs(m_Position.y - y) < 0.1)) { Log.Out(Logs::Detail, Logs::AI, "Calc Position2 (%.3f, %.3f, %.3f): X/Y difference <0.1, Jumping to target.", x, y, z); if(IsNPC()) { entity_list.ProcessMove(CastToNPC(), x, y, z); } m_Position.x = x; m_Position.y = y; m_Position.z = z; return true; } bool send_update = false; int compare_steps = 20; if(tar_ndx < compare_steps && m_TargetLocation.x==x && m_TargetLocation.y==y) { float new_x = m_Position.x + m_TargetV.x*tar_vector; float new_y = m_Position.y + m_TargetV.y*tar_vector; float new_z = m_Position.z + m_TargetV.z*tar_vector; if(IsNPC()) { entity_list.ProcessMove(CastToNPC(), new_x, new_y, new_z); } m_Position.x = new_x; m_Position.y = new_y; m_Position.z = new_z; Log.Out(Logs::Detail, Logs::AI, "Calculating new position2 to (%.3f, %.3f, %.3f), old vector (%.3f, %.3f, %.3f)", x, y, z, m_TargetV.x, m_TargetV.y, m_TargetV.z); uint8 NPCFlyMode = 0; if(IsNPC()) { if(CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2) NPCFlyMode = 1; } //fix up pathing Z if(!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving)) { if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) { glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z); float newz = zone->zonemap->FindBestZ(dest, nullptr) + 2.0f; Log.Out(Logs::Detail, Logs::AI, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,m_Position.x,m_Position.y,m_Position.z); if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check. { if ((std::abs(x - m_Position.x) < 0.5) && (std::abs(y - m_Position.y) < 0.5)) { if (std::abs(z - m_Position.z) <= RuleR(Map, FixPathingZMaxDeltaMoving)) m_Position.z = z; else m_Position.z = newz + 1; } else m_Position.z = newz + 1; } } } tar_ndx++; return true; } if (tar_ndx>50) { tar_ndx--; } else { tar_ndx=0; } m_TargetLocation = glm::vec3(x, y, z); float nx = this->m_Position.x; float ny = this->m_Position.y; float nz = this->m_Position.z; // float nh = this->heading; m_TargetV.x = x - nx; m_TargetV.y = y - ny; m_TargetV.z = z - nz; SetCurrentSpeed((int8)speed); pRunAnimSpeed = speed; if(IsClient()) { animation = speed / 2; } //pRunAnimSpeed = (int8)(speed*NPC_RUNANIM_RATIO); //speed *= NPC_SPEED_MULTIPLIER; Log.Out(Logs::Detail, Logs::AI, "Calculating new position2 to (%.3f, %.3f, %.3f), new vector (%.3f, %.3f, %.3f) rate %.3f, RAS %d", x, y, z, m_TargetV.x, m_TargetV.y, m_TargetV.z, speed, pRunAnimSpeed); // -------------------------------------------------------------------------- // 2: get unit vector // -------------------------------------------------------------------------- float mag = sqrtf (m_TargetV.x*m_TargetV.x + m_TargetV.y*m_TargetV.y + m_TargetV.z*m_TargetV.z); tar_vector = (float)speed / mag; // mob move fix int numsteps = (int) ( mag * 16.0f / (float)speed + 0.5f); // mob move fix if (numsteps<20) { if (numsteps>1) { tar_vector=1.0f ; m_TargetV.x = m_TargetV.x/(float)numsteps; m_TargetV.y = m_TargetV.y/(float)numsteps; m_TargetV.z = m_TargetV.z/(float)numsteps; float new_x = m_Position.x + m_TargetV.x; float new_y = m_Position.y + m_TargetV.y; float new_z = m_Position.z + m_TargetV.z; if(IsNPC()) { entity_list.ProcessMove(CastToNPC(), new_x, new_y, new_z); } m_Position.x = new_x; m_Position.y = new_y; m_Position.z = new_z; m_Position.w = CalculateHeadingToTarget(x, y); tar_ndx = 20 - numsteps; Log.Out(Logs::Detail, Logs::AI, "Next position2 (%.3f, %.3f, %.3f) (%d steps)", m_Position.x, m_Position.y, m_Position.z, numsteps); } else { if(IsNPC()) { entity_list.ProcessMove(CastToNPC(), x, y, z); } m_Position.x = x; m_Position.y = y; m_Position.z = z; Log.Out(Logs::Detail, Logs::AI, "Only a single step to get there... jumping."); } } else { tar_vector/=16.0f; float dur = Timer::GetCurrentTime() - pLastChange; if(dur < 1.0f) { dur = 1.0f; } tar_vector = (tar_vector * AImovement_duration) / 100.0f; float new_x = m_Position.x + m_TargetV.x*tar_vector; float new_y = m_Position.y + m_TargetV.y*tar_vector; float new_z = m_Position.z + m_TargetV.z*tar_vector; if(IsNPC()) { entity_list.ProcessMove(CastToNPC(), new_x, new_y, new_z); } m_Position.x = new_x; m_Position.y = new_y; m_Position.z = new_z; m_Position.w = CalculateHeadingToTarget(x, y); Log.Out(Logs::Detail, Logs::AI, "Next position2 (%.3f, %.3f, %.3f) (%d steps)", m_Position.x, m_Position.y, m_Position.z, numsteps); } uint8 NPCFlyMode = 0; if(IsNPC()) { if(CastToNPC()->GetFlyMode() == 1 || CastToNPC()->GetFlyMode() == 2) NPCFlyMode = 1; } //fix up pathing Z if(!NPCFlyMode && checkZ && zone->HasMap() && RuleB(Map, FixPathingZWhenMoving)) { if(!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() || (zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position)))) { glm::vec3 dest(m_Position.x, m_Position.y, m_Position.z); float newz = zone->zonemap->FindBestZ(dest, nullptr); Log.Out(Logs::Detail, Logs::AI, "BestZ returned %4.3f at %4.3f, %4.3f, %4.3f", newz,m_Position.x, m_Position.y, m_Position.z); if ((newz > -2000) && std::abs(newz - dest.z) < RuleR(Map, FixPathingZMaxDeltaMoving)) // Sanity check. { if (std::abs(x - m_Position.x) < 0.5 && std::abs(y - m_Position.y) < 0.5) { if (std::abs(z - m_Position.z) <= RuleR(Map, FixPathingZMaxDeltaMoving)) m_Position.z = z; else m_Position.z = newz + 1; } else m_Position.z = newz+1; } } } SetMoving(true); moved=true; m_Delta = glm::vec4(m_Position.x - nx, m_Position.y - ny, m_Position.z - nz, 0.0f); if (IsClient()) { SendPosUpdate(1); CastToClient()->ResetPositionTimer(); } else { SendPosUpdate(); SetAppearance(eaStanding, false); } pLastChange = Timer::GetCurrentTime(); return true; }
void Mob::TemporaryPets(uint16 spell_id, Mob *targ, const char *name_override, uint32 duration_override, bool followme, bool sticktarg) { //It might not be a bad idea to put these into the database, eventually.. //Dook- swarms and wards // do nothing if it's a corpse if (targ != nullptr && targ->IsCorpse()) return; // yep, even these need pet power! int act_power = 0; if (IsClient()) { act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id); act_power = CastToClient()->mod_pet_power(act_power, spell_id); } PetRecord record; if (!database.GetPoweredPetEntry(spells[spell_id].teleport_zone, act_power, &record)) { Log(Logs::General, Logs::Error, "Unknown swarm pet spell id: %d, check pets table", spell_id); Message(13, "Unable to find data for pet %s", spells[spell_id].teleport_zone); return; } SwarmPet_Struct pet; pet.count = 1; pet.duration = 1; for (int x = 0; x < MAX_SWARM_PETS; x++) { if (spells[spell_id].effectid[x] == SE_TemporaryPets) { pet.count = spells[spell_id].base[x]; pet.duration = spells[spell_id].max[x]; } } pet.duration += GetFocusEffect(focusSwarmPetDuration, spell_id) / 1000; pet.npc_id = record.npc_type; NPCType *made_npc = nullptr; const NPCType *npc_type = database.LoadNPCTypesData(pet.npc_id); if (npc_type == nullptr) { //log write Log(Logs::General, Logs::Error, "Unknown npc type for swarm pet spell id: %d", spell_id); Message(0, "Unable to find pet!"); return; } if (name_override != nullptr) { //we have to make a custom NPC type for this name change made_npc = new NPCType; memcpy(made_npc, npc_type, sizeof(NPCType)); strcpy(made_npc->name, name_override); npc_type = made_npc; } int summon_count = 0; summon_count = pet.count; if (summon_count > MAX_SWARM_PETS) summon_count = MAX_SWARM_PETS; static const glm::vec2 swarmPetLocations[MAX_SWARM_PETS] = { glm::vec2(5, 5), glm::vec2(-5, 5), glm::vec2(5, -5), glm::vec2(-5, -5), glm::vec2(10, 10), glm::vec2(-10, 10), glm::vec2(10, -10), glm::vec2(-10, -10), glm::vec2(8, 8), glm::vec2(-8, 8), glm::vec2(8, -8), glm::vec2(-8, -8) }; while (summon_count > 0) { int pet_duration = pet.duration; if (duration_override > 0) pet_duration = duration_override; //this is a little messy, but the only way to do it right //it would be possible to optimize out this copy for the last pet, but oh well NPCType *npc_dup = nullptr; if (made_npc != nullptr) { npc_dup = new NPCType; memcpy(npc_dup, made_npc, sizeof(NPCType)); } NPC* swarm_pet_npc = new NPC( (npc_dup != nullptr) ? npc_dup : npc_type, //make sure we give the NPC the correct data pointer 0, GetPosition() + glm::vec4(swarmPetLocations[summon_count], 0.0f, 0.0f), GravityBehavior::Water); if (followme) swarm_pet_npc->SetFollowID(GetID()); if (!swarm_pet_npc->GetSwarmInfo()) { auto nSI = new SwarmPet; swarm_pet_npc->SetSwarmInfo(nSI); swarm_pet_npc->GetSwarmInfo()->duration = new Timer(pet_duration * 1000); } else { swarm_pet_npc->GetSwarmInfo()->duration->Start(pet_duration * 1000); } swarm_pet_npc->StartSwarmTimer(pet_duration * 1000); //removing this prevents the pet from attacking swarm_pet_npc->GetSwarmInfo()->owner_id = GetID(); //give the pets somebody to "love" if (targ != nullptr) { swarm_pet_npc->AddToHateList(targ, 1000, 1000); if (RuleB(Spells, SwarmPetTargetLock) || sticktarg) swarm_pet_npc->GetSwarmInfo()->target = targ->GetID(); else swarm_pet_npc->GetSwarmInfo()->target = 0; } //we allocated a new NPC type object, give the NPC ownership of that memory if (npc_dup != nullptr) swarm_pet_npc->GiveNPCTypeData(npc_dup); entity_list.AddNPC(swarm_pet_npc, true, true); summon_count--; } //the target of these swarm pets will take offense to being cast on... if (targ != nullptr) targ->AddToHateList(this, 1, 0); // The other pointers we make are handled elsewhere. delete made_npc; }
bool Mob::CanPurchaseAlternateAdvancementRank(AA::Rank *rank, bool check_price, bool check_grant) { AA::Ability *ability = rank->base_ability; if(!ability) return false; if(!CanUseAlternateAdvancementRank(rank)) { return false; } //You can't purchase grant only AAs they can only be assigned if(check_grant && ability->grant_only) { return false; } //check level req if(rank->level_req > GetLevel()) { return false; } uint32 current_charges = 0; auto points = GetAA(rank->id, ¤t_charges); //check that we are on previous rank already (if exists) //grant ignores the req to own the previous rank. if(check_grant && rank->prev) { if(points != rank->prev->current_value) { return false; } } //check that we aren't already on this rank or one ahead of us if(points >= rank->current_value) { return false; } //if expendable only let us purchase if we have no charges already //not quite sure on how this functions client side atm //I intend to look into it later to make sure the behavior is right if(ability->charges > 0 && current_charges > 0) { return false; } //check prereqs for(auto &prereq : rank->prereqs) { AA::Ability *prereq_ability = zone->GetAlternateAdvancementAbility(prereq.first); if(prereq_ability) { auto ranks = GetAA(prereq_ability->first_rank_id); if(ranks < prereq.second) { return false; } } } //check price, if client if(check_price && IsClient()) { if(rank->cost > CastToClient()->GetAAPoints()) { return false; } } return true; }
/////////////////////////////////////////////////// // Quagmire - that case above getting to long and spells are gonna have a lot of cases of their own // Cofruben - Reorganised this a little. void Mob::SpellEffect(Mob* caster, Spell* spell, int8 caster_level, bool partialResist) { //Spells not loaded! if(!spells_handler.SpellsLoaded()) return; //Spell not loaded! if(!spell) return; //Yeahlight: Caster was not supplied if(!caster) return; int i = 0; const int16 spell_id = spell->GetSpellID(); const char* teleport_zone = spell->GetSpellTeleportZone(); // 1. Is it a buff? If so, handle its time based effects. if (spell->IsBuffSpell()) spells_handler.HandleBuffSpellEffects(caster, this, spell); // 2. Handle its single-time effect. for (i = 0; i < EFFECT_COUNT; i++) { TSpellEffect effect_id = spell->GetSpellEffectID(i); if(effect_id == SE_Blank || effect_id == 0xFF) continue; int8 formula = spell->GetSpellFormula(i); sint16 base = spell->GetSpellBase(i); sint16 max = spell->GetSpellMax(i); sint32 amount = spells_handler.CalcSpellValue(spell, i, caster_level); //Yeahlight: This is an NPC and had a detremental spell casted upon it if(this->IsNPC() && (spell->IsDetrimentalSpell() || spell->IsUtilitySpell())) { CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): aggroing %s because of the spell effect!", spell->GetSpellName(), this->GetName()); //Yeahlight: Generate hate based on the spells's effect type sint16 tempHate = GetSpellHate(effect_id, spell->GetMinLevel(), false, amount); if(tempHate) { this->CastToNPC()->AddToHateList(caster, 0, tempHate); } } switch(effect_id) { case SE_CurrentHP: case SE_CurrentHPOnce: { sint32 OldHP = this->GetHP(); sint32 damage = amount; //Yeahlight: Partial resist calculations if(partialResist) { damage = damage / 2; damage = damage * (float)((float)(rand()%90 + 10) / 100.00f); if(caster->IsClient() && caster->CastToClient()->GetDebugMe()) caster->Message(YELLOW, "Debug: Your direct damage spell resist has been upgrade to a partial resist."); } this->ChangeHP(caster, damage, spell_id); sint32 NewHP = this->GetHP(); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You changed %s's hp by %+i.", spell->GetSpellName(), this->GetName(), damage); break; } case SE_MovementSpeed: { //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Movement Speed spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_AttackSpeed: { //Yeahlight: There should not be any work to be done here CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Attack Speed spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Invisibility: { this->SetInvisible(true); //Yeahlight: Castee has a pet; remove it if(GetPet()) { Mob* myPet = GetPet(); //Yeahlight: Castee's pet is an NPC if(myPet->IsNPC()) { //Yeahlight: Castee's pet is a charmed NPC if(myPet->CastToNPC()->IsCharmed()) { myPet->CastToNPC()->BuffFadeByEffect(SE_Charm); } //Yeahlight: Castee's pet is a summoned NPC else { myPet->Depop(); } } //Yeahlight: Castee's pet is a charmed PC else if(myPet->IsClient() && myPet->CastToClient()->IsCharmed()) { myPet->CastToClient()->BuffFadeByEffect(SE_Charm); } } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an invisibility spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_CurrentMana: { SetMana(GetMana() + amount); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a mana recovery spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_AddFaction: { //Yeahlight: Only continue if the target is an NPC and the caster is a PC if(this->IsNPC() && caster->IsClient()) { caster->CastToClient()->SetCharacterFactionLevelModifier(this->CastToNPC()->GetPrimaryFactionID(), amount); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an add faction spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Stun: { if (IsClient()) { CastToClient()->Stun(base); } else if(IsNPC()) { //Yeahlight: NPC is immune to stun effects if(CastToNPC()->GetCannotBeStunned()) { if(caster->IsClient()) { caster->Message(RED, "Your target is immune to the stun portion of this effect"); } } else { CastToNPC()->Stun(base); } } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a stun spell, amount: %i.", spell->GetSpellName(), base); break; } case SE_Charm: { //Yeahlight: Can only charm a non-pet and the caster may only have one pet if(this->GetOwner() == NULL && caster->GetPet() == NULL && caster != this) { //Yeahlight: Flag the NPC as a pet if(this->IsNPC()) { caster->SetPet(this); this->SetOwnerID(caster->GetID()); this->CastToNPC()->SetCharmed(true); this->SetPetOrder(SPO_Follow); if(caster->IsClient()) caster->CastToClient()->SendCharmPermissions(); this->CastToNPC()->WhipeHateList(); this->CastToNPC()->StartTaunting(); } else if(this->IsClient()) { if(caster->IsNPC()) { caster->SetPet(this); this->SetOwnerID(caster->GetID()); Mob* myTarget = caster->CastToNPC()->GetHateTop(); if(!myTarget) myTarget = caster->CastToMob(); this->SetTarget(myTarget); this->animation = 0; this->delta_heading = 0; this->delta_x = 0; this->delta_y = 0; this->delta_z = 0; this->SendPosUpdate(true, PC_UPDATE_RANGE, false); this->CastToClient()->charmPositionUpdate_timer->Start(200); this->CastToClient()->SetCharmed(true); this->SendAppearancePacket(this->GetID(), SAT_Position_Update, SAPP_Lose_Control, false); this->SetPetOrder(SPO_Follow); } else if(caster->IsClient()) { caster->SetPet(this); this->SetOwnerID(caster->GetID()); this->SetTarget(caster); this->animation = 0; this->delta_heading = 0; this->delta_x = 0; this->delta_y = 0; this->delta_z = 0; this->SendPosUpdate(true, PC_UPDATE_RANGE, false); this->CastToClient()->charmPositionUpdate_timer->Start(200); this->CastToClient()->SetCharmed(true); this->SendAppearancePacket(this->GetID(), SAT_Position_Update, SAPP_Lose_Control, false); this->SetPetOrder(SPO_Follow); caster->CastToClient()->SendCharmPermissions(); } } } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a charm spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Fear: { //Yeahlight: Victim is a PC if(this->IsClient()) { this->CastToClient()->SetFeared(true); this->SendAppearancePacket(this->GetID(), SAT_Position_Update, SAPP_Lose_Control, false); this->CastToClient()->GetFearDestination(GetX(), GetY(), GetZ()); } //Yeahlight: Victim is an NPC else if(this->IsNPC()) { this->CastToNPC()->SetFeared(true); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a fear spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Stamina: { CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a stamina spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_BindAffinity: { //Yeahlight: Target of the bind affinity spell is a client if(this->IsClient()) { this->CastToClient()->SetBindPoint(); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Bind Affinity spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Gate: { if(IsClient()) CastToClient()->GoToBind(); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a gate spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_CancelMagic: { for(int i = 0; i < 15; i++) { //Yeahlight: Buff must exist and the buff may not have any poison or disease counters if(buffs[i].spell && buffs[i].spell->IsValidSpell() && buffs[i].casterlevel <= (caster_level + base) && buffs[i].spell->GetDiseaseCounters() == 0 && buffs[i].spell->GetPoisonCounters() == 0) { this->BuffFadeBySlot(i, true); break; } } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a cancel magic spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_InvisVsUndead: { this->SetInvisibleUndead(true); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an Invis VS Undead spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_InvisVsAnimals: { this->SetInvisibleAnimal(true); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an Invis VS Animal spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Mez: { // Pinedepain // When a mezz spell is casted, we mesmerize this mob Mesmerize(); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a mesmerize spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_SummonItem: { if(this->IsClient()) { if(amount == 0) this->CastToClient()->SummonItem(base, 1); else this->CastToClient()->SummonItem(base, (amount > 20) ? 20 : amount); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a summon item spell: %i.", spell->GetSpellName(), base); break; } case SE_NecPet: case SE_SummonPet: { if (this->GetPetID() != 0) { Message(RED, "You\'ve already got a pet."); break; } this->MakePet(teleport_zone); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Summon pet / nec pet spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_DivineAura: { this->SetInvulnerable(true); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Divine Aura, amount: %i.", spell->GetSpellName(), amount); break; } case SE_ShadowStep: { //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Shadow Step spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Rune: { //Yeahlight: Flag entity with rune for damage calculations hasRuneOn = true; CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Rune spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Levitate: { this->SendAppearancePacket(0, SAT_Levitate, 2, true); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a levitate spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_SummonCorpse: { bool permit = false; Mob* corpseOwner = target; //Yeahlight: The target of this spell is not a PC or the caster is targeting themself if(!target || (target && !target->IsClient()) || target == this) { corpseOwner = this; permit = true; } //Yeahlight: Can only summon a PC's corpse if(corpseOwner && corpseOwner->IsClient()) { //Yeahlight: PCs must be grouped to summon a corpse if(!permit) { Group* targetGroup = entity_list.GetGroupByClient(corpseOwner->CastToClient()); Group* myGroup = NULL; if(this->IsClient()) myGroup = entity_list.GetGroupByClient(this->CastToClient()); //Yeahlight: Caster is in a group and they share the same group as the target if(myGroup != NULL && myGroup == targetGroup) permit = true; } //Yeahlight: Caster may proceed with the summon if(permit) { Corpse *corpse = entity_list.GetCorpseByOwner(corpseOwner->CastToClient()); //Yeahlight: Corpse has been located if(corpse) { corpse->Summon(corpseOwner->CastToClient(), caster->CastToClient(), true); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a summon corpse spell: %s.", spell->GetSpellName(), this->GetName()); } //Yeahlight: There is no corpse available else { //Yeahlight: Caster failed to locate his/her corpse if(caster == corpseOwner) { caster->Message(RED, "You do not have a corpse in this zone."); } //Yeahlight: Caster failed to locate their target's corpse else { caster->Message(RED, "Your target does not have a corpse in this zone."); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): Summon corpse: you can't sense the corpse: %i.", spell->GetSpellName()); } } else { //Yeahlight: TODO: This is not the correct message Message(RED, "You and your target must be in the same group to perform this action."); } } break; } case SE_Illusion: { SendIllusionPacket(base, GetDefaultGender(base, GetBaseGender()), GetTexture(), GetHelmTexture()); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an illusion spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Identify: { //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an identify spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_WipeHateList: { //Yeahlight: NOTE: Do NOT wipe the rampage list here; that never goes away until the mob resets if(this->IsNPC()) { //Yeahlight: TODO: I don't remember this message, look into this entity_list.MessageClose(this, true, DEFAULT_MESSAGE_RANGE, DARK_BLUE, "My mind fogs. Who are my friends? Who are my enemies?... it was all so clear a moment ago..."); this->CastToNPC()->WhipeHateList(); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a whipe hate list spell, amount: %i.", spell->GetSpellName(), amount); } break; } case SE_SpinTarget: { Spin(caster, spell); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a spin target spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_EyeOfZomm: { //Yeahlignt: Only produce eyes of zomm for PCs if(this->IsClient()) { if(this->CastToClient()->myEyeOfZomm == 0) MakeEyeOfZomm(this); else Message(RED, "You may only have one eye of zomm out at a time!"); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a Eye Of Zomm spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_ReclaimPet: { //Yeahlight: Target of the spell is an uncharmed NPC, has an owner and the owner is the caster of the spell if(IsNPC() && CastToNPC()->IsCharmed() == false && GetOwnerID() && caster->GetID() == GetOwnerID()) { //Yeahlight: TODO: Research this formula caster->SetMana(caster->GetMana()+(GetLevel()*4)); if(caster->IsClient()) { caster->CastToClient()->SetPet(0); } SetOwnerID(0); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a reclaim pet spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_FeignDeath: { if(this->IsClient()) this->CastToClient()->FeignDeath(this->CastToClient()->GetSkill(ABJURATION)); break; } case SE_VoiceGraft: { //Yeahlight: Only allow voice graft to be casted on NPCs (we don't want PCs griefing other charmed PCs with /say) if(IsNPC() && caster->IsClient() && CastToNPC()->GetOwner() == caster) { caster->CastToClient()->SetVoiceGrafting(true); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a voice graft spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Revive: { //Yeahlight: Handled in client_process.cpp CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a revive spell, amount: %i, corpse: %s.", spell->GetSpellName(), amount, this->GetName()); break; } case SE_Teleport: { char teleport_zone_char[64]; if(this->IsClient()) strcpy(teleport_zone_char, teleport_zone); this->CastToClient()->MovePC(teleport_zone_char, spell->GetSpellBase(1), spell->GetSpellBase(0), spell->GetSpellBase(2), false, false); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a teleport spell to %s (%f, %f, %f).", spell->GetSpellName(), teleport_zone, spell->GetSpellBase(1), spell->GetSpellBase(0), spell->GetSpellBase(2)); break; } case SE_Translocate: { bool permit = false; Mob* translocatee = CastToMob(); //Enraged: The target of this spell is an NPC. //if(translocatee && !translocatee->IsClient()) //{ // //Enraged: TODO: This is not the correct message? // Message(RED, "You cannot cast that spell on your current target."); // break; //} //Enraged: The target of this spell is the caster. //TODO: Can players target themselves with translocate spells? if(translocatee && (translocatee == this)) permit = true; //Enraged: Check if the targetted client is in the casters group. //TODO: Translocate only worked on group players, right? if(!permit && translocatee) { Group* translocateeGroup = entity_list.GetGroupByClient(translocatee->CastToClient()); Group* casterGroup = NULL; if(this->IsClient()) casterGroup = entity_list.GetGroupByClient(this->CastToClient()); //Enraged: The translocatee is in a group and they share the same group as the target if(casterGroup != NULL && casterGroup == translocateeGroup) permit = true; } //Enraged: Target is clear to be translocated. if(permit) { CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a translocation spell on %s.", spell->GetSpellName(), translocatee->GetName()); //TODO: Translocate code here translocatee->CastToClient()->SendTranslocateConfirmation(caster, spell); } else { //The translocatee was not in the casters group. //Enraged: TODO: This is not the correct message Message(RED, "You can only cast that spell on players in your group."); } break; } case SE_InfraVision: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an infravision spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_UltraVision: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an ultravision spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_BindSight: CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a bind sight spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_SeeInvis: SetCanSeeThroughInvis(true); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a see invis spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_WaterBreathing: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted an water breathing spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_SenseDead: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a sense dead spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_SenseSummoned: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a sense summoned spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_TrueNorth: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a true north spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_SenseAnimals: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a sense animals spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_DamageShield: //Yeahlight: There should not be any work to be done here CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a damage shield spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_Sentinel: CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a sentinel spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_LocateCorpse: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a locate corpse spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_ModelSize: { //Yeahlight: Grow/Shrink float newSize = (GetSize() * (float)base) / 100.00f; //Yeahlight: Size of a gnome (minimum) if(newSize < 3) newSize = 3; //Yeahlight: Size of an ogre (maximum) else if(newSize > 9) newSize = 9; this->size = newSize; this->SendAppearancePacket(GetID(), SAT_Size, GetSize(), true); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a model size spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Root: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a root spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_Blind: //Yeahlight: Handled client side CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a blind spell, amount: %i.", spell->GetSpellName(), amount); break; case SE_DiseaseCounter: { //Yeahlight: Spell is a cure disease spell if(amount < 0) { //Yeahlight: Iterate through all the debuffs on the target and check for the chance to cure it for(int i = 0; i < 15; i++) { if(buffs[i].spell && buffs[i].diseasecounters) { buffs[i].diseasecounters = buffs[i].diseasecounters + amount; if(buffs[i].diseasecounters <= 0) BuffFadeBySlot(i, true); break; } } } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a disease counter spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_PoisonCounter: { //Yeahlight: Spell is a cure poison spell if(amount < 0) { //Yeahlight: Iterate through all the debuffs on the target and check for the chance to cure it for(int i = 0; i < 15; i++) { if(buffs[i].spell && buffs[i].poisoncounters) { buffs[i].poisoncounters = buffs[i].poisoncounters + amount; if(buffs[i].poisoncounters <= 0) BuffFadeBySlot(i, true); break; } } } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a poison counter spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_Calm: { //Yeahlight: Only add/remove hate from NPCs if(this->IsNPC()) { this->CastToNPC()->AddToHateList(caster, 0, amount); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): You casted a calm hate spell, amount: %i.", spell->GetSpellName(), amount); break; } case SE_WeaponProc: { Spell* spell = spells_handler.GetSpellPtr(base); //Yeahlight: Legit spell proc bonus found if(spell) SetBonusProcSpell(spell); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell name = %s): Weapon proc bonus of spell ID %i.", spell->GetSpellName(), base); break; } case SE_StopRain: { zone->zone_weather = 0; zone->weatherSend(); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::ApplySpellsBonuses(spell name = %s): You casted a stop rain spell, amount: %i.", spell->GetSpellName(), base); break; } case SE_CallOfHero: { //Yeahlight: Call of the Hero may only be used on PCs int32 zoneid = 0; if(this->IsClient()) this->CastToClient()->MovePC(zoneid, caster->GetX(), caster->GetY(), caster->GetZ(), false, true); else caster->Message(RED, "This spell may only be cast on players."); CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::ApplySpellsBonuses(spell name = %s): You casted a call of the hero spell, amount: %i.", spell->GetSpellName(), base); break; } case SE_CallPet: { //Yeahlight: This spell line may only be used on NPC pets if(GetPet() && GetPet()->IsNPC()) { GetPet()->CastToNPC()->GMMove(GetX(), GetY(), GetZ(), GetHeading()); GetPet()->pStandingPetOrder = SPO_Follow; } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::ApplySpellsBonuses(spell name = %s): You casted a call pet spell, amount: %i.", spell->GetSpellName(), base); break; } case SE_DeathSave: { //Yeahlight: Only apply divine intervention to players if(this->IsClient()) { sint16 successChance = 0; float baseChance = 0.00f; switch(base) { //Yeahlight: Death Pact (CLR: 51) case 1: { baseChance = 0.10f; break; } //Yeahlight: Divine Intervention (CLR: 60) case 2: { baseChance = 0.30f; break; } default: { baseChance = 0.10f; } } //Yeahlight: The target's CHA is calculated into the bonus save chance successChance = (((float)CastToClient()->GetCHA() * 0.0005f) + baseChance) * 100; //Yeahlight: The worst possible save chance is the spell's base chance if(successChance < baseChance) successChance = baseChance; else if(successChance > 100) successChance = 100; this->CastToClient()->SetDeathSave(successChance); } CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::ApplySpellsBonuses(spell name = %s): You casted a death save spell, amount: %i.", spell->GetSpellName(), base); break; } case SE_Succor: { //Yeahlight: There should be nothing to do here CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::ApplySpellsBonuses(spell name = %s): You casted a succor spell, amount: %i.", spell->GetSpellName(), base); break; } case 0xFE: case 0xFF: case SE_Harmony: case SE_ChangeFrenzyRad: case SE_Lull: case SE_TotalHP: case SE_ArmorClass: case SE_MagnifyVision: case SE_ATK: case SE_STR: case SE_DEX: case SE_AGI: case SE_STA: case SE_INT: case SE_WIS: case SE_CHA: case SE_ResistFire: case SE_ResistCold: case SE_ResistPoison: case SE_ResistDisease: case SE_ResistMagic: { // Buffs are handeled elsewhere break; } default: { CAST_CLIENT_DEBUG_PTR(caster)->Log(CP_SPELL, "Mob::SpellEffect(spell_name = %s): unknown effect (%i) for amount: %i.", spell->GetSpellName(), effect_id, amount); break; } } } if(this->IsClient()) this->CastToClient()->Save(); }
void Client::ActivateAlternateAdvancementAbility(int rank_id, int target_id) { AA::Rank *rank = zone->GetAlternateAdvancementRank(rank_id); if(!rank) { return; } AA::Ability *ability = rank->base_ability; if(!ability) { return; } if(!IsValidSpell(rank->spell)) { return; } if(!CanUseAlternateAdvancementRank(rank)) { return; } //make sure it is not a passive if(!rank->effects.empty()) { return; } uint32 charges = 0; // We don't have the AA if (!GetAA(rank_id, &charges)) return; //if expendable make sure we have charges if(ability->charges > 0 && charges < 1) return; //check cooldown if(!p_timers.Expired(&database, rank->spell_type + pTimerAAStart, false)) { uint32 aaremain = p_timers.GetRemainingTime(rank->spell_type + pTimerAAStart); uint32 aaremain_hr = aaremain / (60 * 60); uint32 aaremain_min = (aaremain / 60) % 60; uint32 aaremain_sec = aaremain % 60; if(aaremain_hr >= 1) { Message(13, "You can use this ability again in %u hour(s) %u minute(s) %u seconds", aaremain_hr, aaremain_min, aaremain_sec); } else { Message(13, "You can use this ability again in %u minute(s) %u seconds", aaremain_min, aaremain_sec); } return; } //calculate cooldown int cooldown = rank->recast_time - GetAlternateAdvancementCooldownReduction(rank); if(cooldown < 0) { cooldown = 0; } if (!IsCastWhileInvis(rank->spell)) CommonBreakInvisible(); if (spells[rank->spell].sneak && (!hidden || (hidden && (Timer::GetCurrentTime() - tmHidden) < 4000))) { Message_StringID(MT_SpellFailure, SNEAK_RESTRICT); return; } // // Modern clients don't require pet targeted for AA casts that are ST_Pet if (spells[rank->spell].targettype == ST_Pet || spells[rank->spell].targettype == ST_SummonedPet) target_id = GetPetID(); // extra handling for cast_not_standing spells if (!spells[rank->spell].cast_not_standing) { if (GetAppearance() == eaSitting) // we need to stand! SetAppearance(eaStanding, false); if (GetAppearance() != eaStanding) { Message_StringID(MT_SpellFailure, STAND_TO_CAST); return; } } // Bards can cast instant cast AAs while they are casting another song if(spells[rank->spell].cast_time == 0 && GetClass() == BARD && IsBardSong(casting_spell_id)) { if(!SpellFinished(rank->spell, entity_list.GetMob(target_id), EQEmu::spells::CastingSlot::AltAbility, spells[rank->spell].mana, -1, spells[rank->spell].ResistDiff, false)) { return; } ExpendAlternateAdvancementCharge(ability->id); } else { if(!CastSpell(rank->spell, target_id, EQEmu::spells::CastingSlot::AltAbility, -1, -1, 0, -1, rank->spell_type + pTimerAAStart, cooldown, nullptr, rank->id)) { return; } } CastToClient()->GetPTimers().Start(rank->spell_type + pTimerAAStart, cooldown); SendAlternateAdvancementTimer(rank->spell_type, 0, 0); }
//offensive spell aggro int32 Mob::CheckAggroAmount(uint16 spell_id, bool isproc) { int32 AggroAmount = 0; int32 nonModifiedAggro = 0; uint16 slevel = GetLevel(); for (int o = 0; o < EFFECT_COUNT; o++) { switch (spells[spell_id].effectid[o]) { case SE_CurrentHPOnce: case SE_CurrentHP: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if(val < 0) AggroAmount -= val; break; } case SE_MovementSpeed: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount += (2 + ((slevel * slevel) / 8)); break; } case SE_AttackSpeed: case SE_AttackSpeed2: case SE_AttackSpeed3: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 100) AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Stun: { int val = (5 + ((slevel * slevel) / 6)); if (isproc && RuleI(Aggro,MaxStunProcAggro) > -1 && (val > RuleI(Aggro,MaxStunProcAggro))) val = RuleI(Aggro,MaxStunProcAggro); AggroAmount += val; break; } case SE_Blind: { AggroAmount += (5 + ((slevel * slevel) / 6)); break; } case SE_Mez: { AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Charm: { AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Root: { AggroAmount += (2 + ((slevel * slevel) / 8)); break; } case SE_Fear: { AggroAmount += (5 + ((slevel * slevel) / 6)); break; } case SE_ATK: case SE_ACv2: case SE_ArmorClass: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_ResistMagic: case SE_ResistFire: case SE_ResistCold: case SE_ResistPoison: case SE_ResistDisease: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 3; break; } case SE_ResistAll: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 6; break; } case SE_STR: case SE_STA: case SE_DEX: case SE_AGI: case SE_INT: case SE_WIS: case SE_CHA: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_AllStats: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 6; break; } case SE_BardAEDot: { AggroAmount += slevel * 2; break; } case SE_SpinTarget: { AggroAmount += (5 + ((slevel * slevel) / 5)); break; } case SE_Amnesia: case SE_Silence: { AggroAmount += slevel * 2; break; } case SE_Destroy: { AggroAmount += slevel * 2; break; } case SE_Harmony: case SE_CastingLevel: case SE_MeleeMitigation: case SE_CriticalHitChance: case SE_AvoidMeleeChance: case SE_RiposteChance: case SE_DodgeChance: case SE_ParryChance: case SE_DualWieldChance: case SE_DoubleAttackChance: case SE_MeleeSkillCheck: case SE_HitChance: case SE_IncreaseArchery: case SE_DamageModifier: case SE_MinDamageModifier: case SE_IncreaseBlockChance: case SE_Accuracy: case SE_DamageShield: case SE_SpellDamageShield: case SE_ReverseDS: { AggroAmount += slevel * 2; break; } case SE_CurrentMana: case SE_ManaRegen_v2: case SE_ManaPool: case SE_CurrentEndurance: { int val = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); if (val < 0) AggroAmount -= val * 2; break; } case SE_CancelMagic: case SE_DispelDetrimental: { AggroAmount += slevel; break; } case SE_ReduceHate: case SE_InstantHate: { nonModifiedAggro = CalcSpellEffectValue_formula(spells[spell_id].formula[o], spells[spell_id].base[o], spells[spell_id].max[o], slevel, spell_id); break; } } } if (IsAEDurationSpell(spell_id)) AggroAmount /= 2; if (spells[spell_id].HateAdded > 0) AggroAmount = spells[spell_id].HateAdded; if (IsBardSong(spell_id)) AggroAmount = AggroAmount * RuleI(Aggro, SongAggroMod) / 100; if (GetOwner() && IsPet()) AggroAmount = AggroAmount * RuleI(Aggro, PetSpellAggroMod) / 100; if (AggroAmount > 0) { int HateMod = RuleI(Aggro, SpellAggroMod); if (IsClient()) HateMod += CastToClient()->GetFocusEffect(focusSpellHateMod, spell_id); AggroAmount = (AggroAmount * HateMod) / 100; //made up number probably scales a bit differently on live but it seems like it will be close enough //every time you cast on live you get a certain amount of "this is a spell" aggro //confirmed by EQ devs to be 100 exactly at level 85. From their wording it doesn't seem like it's affected //by hate modifiers either. //AggroAmount += (slevel*slevel/72); // Saved so I can reimplement it; // this should only be on the spell to aggro the npc not every spell } return AggroAmount + spells[spell_id].bonushate + nonModifiedAggro; }
//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); }
// 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. 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) } }
bool Client::UseDiscipline(uint32 spell_id, uint32 target) { // Dont let client waste a reuse timer if they can't use the disc if (IsStunned() || IsFeared() || IsMezzed() || IsAmnesiad() || IsPet()) { return(false); } //make sure we have the spell... int r; for(r = 0; r < MAX_PP_DISCIPLINES; r++) { if(m_pp.disciplines.values[r] == spell_id) break; } if(r == MAX_PP_DISCIPLINES) return(false); //not found. //Check the disc timer pTimerType DiscTimer = pTimerDisciplineReuseStart + spells[spell_id].EndurTimerIndex; if(!p_timers.Expired(&database, DiscTimer)) { /*char val1[20]={0};*/ //unused /*char val2[20]={0};*/ //unused uint32 remain = p_timers.GetRemainingTime(DiscTimer); //Message_StringID(0, DISCIPLINE_CANUSEIN, ConvertArray((remain)/60,val1), ConvertArray(remain%60,val2)); Message(0, "You can use this discipline in %d minutes %d seconds.", ((remain)/60), (remain%60)); return(false); } //make sure we can use it.. if(!IsValidSpell(spell_id)) { Message(13, "This tome contains invalid knowledge."); return(false); } //can we use the spell? const SPDat_Spell_Struct &spell = spells[spell_id]; uint8 level_to_use = spell.classes[GetClass() - 1]; if(level_to_use == 255) { Message(13, "Your class cannot learn from this tome."); //should summon them a new one... return(false); } if(level_to_use > GetLevel()) { Message_StringID(13, DISC_LEVEL_USE_ERROR); //should summon them a new one... return(false); } if(GetEndurance() > spell.EndurCost) { SetEndurance(GetEndurance() - spell.EndurCost); } else { Message(11, "You are too fatigued to use this skill right now."); return(false); } if(spell.recast_time > 0) { uint32 reduced_recast = spell.recast_time / 1000; reduced_recast -= CastToClient()->GetFocusEffect(focusReduceRecastTime, spell_id); if(reduced_recast < 0) reduced_recast = 0; CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT, -1, -1, 0, -1, (uint32)DiscTimer, reduced_recast); if(spells[spell_id].EndurTimerIndex < MAX_DISCIPLINE_TIMERS) { EQApplicationPacket *outapp = new EQApplicationPacket(OP_DisciplineTimer, sizeof(DisciplineTimer_Struct)); DisciplineTimer_Struct *dts = (DisciplineTimer_Struct *)outapp->pBuffer; dts->TimerID = spells[spell_id].EndurTimerIndex; dts->Duration = reduced_recast; QueuePacket(outapp); safe_delete(outapp); } } else { CastSpell(spell_id, target, DISCIPLINE_SPELL_SLOT); } return(true); }
void Aura::ProcessOnGroupMembersPets(Mob *owner) { auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline std::set<int> delayed_remove; bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter // This type can either live on the pet (level 55/70 MAG aura) or on the pet owner (level 85 MAG aura) auto group_member = owner->GetOwnerOrSelf(); if (group_member->IsRaidGrouped() && group_member->IsClient()) { // currently raids are just client, but safety check auto raid = group_member->GetRaid(); if (raid == nullptr) { // well shit owner->RemoveAura(GetID(), false, true); return; } auto group_id = raid->GetGroup(group_member->CastToClient()); // some lambdas so the for loop is less horrible ... auto verify_raid_client_pet = [&raid, &group_id, &group_member, this](Mob *m) { auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient()); if (m->GetOwner()->GetID() == group_member->GetID()) { return DistanceSquared(GetPosition(), m->GetPosition()) <= distance; } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; } else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) { return false; } return true; }; auto verify_raid_client_swarm = [&raid, &group_id, &group_member, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); if (owner == nullptr) return false; auto idx = raid->GetPlayerIndex(owner->CastToClient()); if (owner->GetID() == group_member->GetID()) { return DistanceSquared(GetPosition(), n->GetPosition()) <= distance; } else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) { return false; } else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) { return false; } return true; }; for (auto &e : mob_list) { auto mob = e.second; // step 1: check if we're already managing this NPC's buff auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // verify still good! if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) { if (!verify_raid_client_pet(mob)) delayed_remove.insert(mob->GetID()); } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { auto npc = mob->CastToNPC(); if (!verify_raid_client_swarm(npc)) delayed_remove.insert(mob->GetID()); } } else { // we're not on it! if (mob->IsClient()) { continue; // never hit client } else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } else if (mob->IsNPC() && mob->IsPetOwnerClient()) { auto npc = mob->CastToNPC(); if (verify_raid_client_swarm(npc)) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } } } } } else if (group_member->IsGrouped()) { auto group = group_member->GetGroup(); if (group == nullptr) { // uh oh owner->RemoveAura(GetID(), false, true); return; } // lambdas to make for loop less ugly auto verify_group_pet = [&group, this](Mob *m) { auto owner = m->GetOwner(); if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance) return true; return false; }; auto verify_group_swarm = [&group, this](NPC *n) { auto owner = entity_list.GetMob(n->GetSwarmOwner()); if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance) return true; return false; }; for (auto &e : mob_list) { auto mob = e.second; auto it = casted_on.find(mob->GetID()); if (it != casted_on.end()) { // make sure we're still valid if (mob->IsPet()) { if (!verify_group_pet(mob)) delayed_remove.insert(mob->GetID()); } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) { if (!verify_group_swarm(mob->CastToNPC())) delayed_remove.insert(mob->GetID()); } } else { // not on, check if we should be! if (mob->IsClient()) { continue; } else if (mob->IsPet() && verify_group_pet(mob)) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } } } } else { auto verify_solo = [&group_member, this](Mob *m) { if (m->IsPet() && m->GetOwnerID() == group_member->GetID()) return true; else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == group_member->GetID()) return true; else return false; }; for (auto &e : mob_list) { auto mob = e.second; auto it = casted_on.find(mob->GetID()); bool good = verify_solo(mob); if (it != casted_on.end()) { // make sure still valid if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) { delayed_remove.insert(mob->GetID()); } } else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) { casted_on.insert(mob->GetID()); if (is_buff) SpellFinished(spell_id, mob); } } } for (auto &e : delayed_remove) { auto mob = entity_list.GetMob(e); if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove mob->BuffFadeBySpellIDAndCaster(spell_id, GetID()); casted_on.erase(e); } // so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty()) cast_timer.Start(); if (!cast_timer.Enabled() || !cast_timer.Check()) return; // some auras have to recast (DRU for example, non-buff too) for (auto &e : casted_on) { auto mob = entity_list.GetMob(e); if (mob != nullptr) SpellFinished(spell_id, mob); } }