Merge and compile fixes (non-bot, will do bots later)

This commit is contained in:
KimLS
2018-04-16 14:15:08 -07:00
184 changed files with 11873 additions and 4474 deletions
+4
View File
@@ -28,6 +28,7 @@ SET(zone_sources
encounter.cpp
entity.cpp
exp.cpp
fastmath.cpp
fearpath.cpp
forage.cpp
groups.cpp
@@ -68,6 +69,7 @@ SET(zone_sources
exp.cpp
fearpath.cpp
forage.cpp
global_loot_manager.cpp
groups.cpp
guild.cpp
guild_mgr.cpp
@@ -161,7 +163,9 @@ SET(zone_headers
entity.h
errmsg.h
event_codes.h
fastmath.h
forage.h
global_loot_manager.h
groups.h
guild_mgr.h
hate_list.h
+1
View File
@@ -424,6 +424,7 @@ void Mob::WakeTheDead(uint16 spell_id, Mob *target, uint32 duration)
npca->GetSwarmInfo()->duration->Start(duration*1000);
}
npca->StartSwarmTimer(duration * 1000);
npca->GetSwarmInfo()->owner_id = GetID();
//give the pet somebody to "love"
+28 -1
View File
@@ -253,7 +253,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
//sometimes if a client has some lag while zoning into a dangerous place while either invis or a GM
//they will aggro mobs even though it's supposed to be impossible, to lets make sure we've finished connecting
if (mob->IsClient()) {
if (!mob->CastToClient()->ClientFinishedLoading() || mob->CastToClient()->IsHoveringForRespawn() || mob->CastToClient()->zoning)
if (!mob->CastToClient()->ClientFinishedLoading() || mob->CastToClient()->IsHoveringForRespawn() || mob->CastToClient()->bZoning)
return false;
}
@@ -1007,6 +1007,28 @@ bool Mob::CheckLosFN(float posX, float posY, float posZ, float mobSize) {
return zone->zonemap->CheckLoS(myloc, oloc);
}
bool Mob::CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget) {
if (zone->zonemap == nullptr) {
//not sure what the best return is on error
//should make this a database variable, but im lazy today
#ifdef LOS_DEFAULT_CAN_SEE
return(true);
#else
return(false);
#endif
}
#define LOS_DEFAULT_HEIGHT 6.0f
posWatcher.z += (sizeWatcher == 0.0f ? LOS_DEFAULT_HEIGHT : sizeWatcher) / 2 * HEAD_POSITION;
posTarget.z += (sizeTarget == 0.0f ? LOS_DEFAULT_HEIGHT : sizeTarget) / 2 * SEE_POSITION;
#if LOSDEBUG>=5
Log(Logs::General, Logs::None, "LOS from (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f) sizes: (%.2f, %.2f) [static]", posWatcher.x, posWatcher.y, posWatcher.z, posTarget.x, posTarget.y, posTarget.z, sizeWatcher, sizeTarget);
#endif
return zone->zonemap->CheckLoS(posWatcher, posTarget);
}
//offensive spell aggro
int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc)
{
@@ -1274,6 +1296,11 @@ void Mob::ClearFeignMemory() {
AI_feign_remember_timer->Disable();
}
bool Mob::IsOnFeignMemory(Client *attacker) const
{
return feign_memory_list.find(attacker->CharacterID()) != feign_memory_list.end();
}
bool Mob::PassCharismaCheck(Mob* caster, uint16 spell_id) {
/*
+101 -35
View File
@@ -32,6 +32,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "worldserver.h"
#include "zone.h"
#include "lua_parser.h"
#include "fastmath.h"
#include <assert.h>
#include <stdio.h>
@@ -43,6 +44,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
extern QueryServ* QServ;
extern WorldServer worldserver;
extern FastMath g_Math;
#ifdef _WINDOWS
#define snprintf _snprintf
@@ -358,7 +360,7 @@ bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit)
Mob *attacker = other;
Mob *defender = this;
bool InFront = attacker->InFrontMob(this, attacker->GetX(), attacker->GetY());
bool InFront = !attacker->BehindMob(this, attacker->GetX(), attacker->GetY());
/*
This special ability adds a negative modifer to the defenders riposte/block/parry/chance
@@ -852,16 +854,56 @@ int Mob::ACSum()
return ac;
}
int Mob::GetBestMeleeSkill()
{
int bestSkill=0;
EQEmu::skills::SkillType meleeSkills[]=
{ EQEmu::skills::Skill1HBlunt,
EQEmu::skills::Skill1HSlashing,
EQEmu::skills::Skill2HBlunt,
EQEmu::skills::Skill2HSlashing,
EQEmu::skills::SkillHandtoHand,
EQEmu::skills::Skill1HPiercing,
EQEmu::skills::Skill2HPiercing,
EQEmu::skills::SkillCount
};
int i;
for (i=0; meleeSkills[i] != EQEmu::skills::SkillCount; ++i) {
int value;
value = GetSkill(meleeSkills[i]);
bestSkill = std::max(value, bestSkill);
}
return bestSkill;
}
int Mob::offense(EQEmu::skills::SkillType skill)
{
int offense = GetSkill(skill);
int stat_bonus = 0;
if (skill == EQEmu::skills::SkillArchery || skill == EQEmu::skills::SkillThrowing)
stat_bonus = GetDEX();
else
stat_bonus = GetSTR();
int stat_bonus = GetSTR();
switch (skill) {
case EQEmu::skills::SkillArchery:
case EQEmu::skills::SkillThrowing:
stat_bonus = GetDEX();
break;
// Mobs with no weapons default to H2H.
// Since H2H is capped at 100 for many many classes,
// lets not handicap mobs based on not spawning with a
// weapon.
//
// Maybe we tweak this if Disarm is actually implemented.
case EQEmu::skills::SkillHandtoHand:
offense = GetBestMeleeSkill();
break;
}
if (stat_bonus >= 75)
offense += (2 * stat_bonus - 150) / 3;
offense += GetATK();
return offense;
}
@@ -1688,6 +1730,15 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk
if (!RuleB(Character, UseDeathExpLossMult)) {
exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000);
}
if (RuleB(Zone, LevelBasedEXPMods)) {
// Death in levels with xp_mod (such as hell levels) was resulting
// in losing more that appropriate since the loss was the same but
// getting it back would take way longer. This makes the death the
// same amount of time to recover. Will also lose more if level is
// granting a bonus.
exploss *= zone->level_exp_mod[GetLevel()].ExpMod;
}
if ((GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC())
{
@@ -2533,6 +2584,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
if (other == this)
return;
if (other->IsTrap())
return;
if (damage < 0) {
hate = 1;
}
@@ -2632,7 +2686,7 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
hate_list.AddEntToHateList(other, hate, damage, bFrenzy, !iBuffTic);
if (other->IsClient() && !on_hatelist)
if (other->IsClient() && !on_hatelist && !IsOnFeignMemory(other->CastToClient()))
other->CastToClient()->AddAutoXTarget(this);
#ifdef BOTS
@@ -2673,9 +2727,9 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
// owner must get on list, but he's not actually gained any hate yet
if (!owner->GetSpecialAbility(IMMUNE_AGGRO))
{
hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic);
if (owner->IsClient() && !CheckAggro(owner))
owner->CastToClient()->AddAutoXTarget(this);
hate_list.AddEntToHateList(owner, 0, 0, false, !iBuffTic);
}
}
}
@@ -3311,6 +3365,12 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const
damage = DMG_INVULNERABLE;
}
// this should actually happen MUCH sooner, need to investigate though -- good enough for now
if ((skill_used == EQEmu::skills::SkillArchery || skill_used == EQEmu::skills::SkillThrowing) && GetSpecialAbility(IMMUNE_RANGED_ATTACKS)) {
Log(Logs::Detail, Logs::Combat, "Avoiding %d damage due to IMMUNE_RANGED_ATTACKS.", damage);
damage = DMG_INVULNERABLE;
}
if (spell_id != SPELL_UNKNOWN || attacker == nullptr)
avoidable = false;
@@ -3364,7 +3424,7 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const
// pets that have GHold will never automatically add NPCs
// pets that have Hold and no Focus will add NPCs if they're engaged
// pets that have Hold and Focus will not add NPCs
if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld())
if (pet && !pet->IsFamiliar() && !pet->GetSpecialAbility(IMMUNE_AGGRO) && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse() && !pet->IsGHeld() && !attacker->IsTrap())
{
if (!pet->IsHeld()) {
Log(Logs::Detail, Logs::Aggro, "Sending pet %s into battle due to attack.", pet->GetName());
@@ -3553,26 +3613,21 @@ void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const
a->special = 2;
else
a->special = 0;
a->meleepush_xy = attacker ? attacker->GetHeading() * 2.0f : 0.0f;
a->hit_heading = attacker ? attacker->GetHeading() : 0.0f;
if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() &&
(IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) {
a->force = EQEmu::skills::GetSkillMeleePushForce(skill_used);
// update NPC stuff
auto new_pos = glm::vec3(m_Position.x + (a->force * std::sin(a->meleepush_xy) + m_Delta.x),
m_Position.y + (a->force * std::cos(a->meleepush_xy) + m_Delta.y), m_Position.z);
if (zone->zonemap && zone->zonemap->CheckLoS(glm::vec3(m_Position), new_pos)) { // If we have LoS on the new loc it should be reachable.
if (IsNPC()) {
// Is this adequate?
Teleport(new_pos);
if (position_update_melee_push_timer.Check()) {
SendPositionUpdate();
}
if (IsNPC()) {
if (attacker->IsNPC())
a->force = 0.0f; // 2013 change that disabled NPC vs NPC push
else
a->force *= 0.10f; // force against NPCs is divided by 10 I guess? ex bash is 0.3, parsed 0.03 against an NPC
if (ForcedMovement == 0 && a->force != 0.0f && position_update_melee_push_timer.Check()) {
m_Delta.x += a->force * g_Math.FastSin(a->hit_heading);
m_Delta.y += a->force * g_Math.FastCos(a->hit_heading);
ForcedMovement = 3;
}
}
else {
a->force = 0.0f; // we couldn't move there, so lets not
}
}
//Note: if players can become pets, they will not receive damage messages of their own
@@ -3922,10 +3977,10 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *
float WPC = ProcChance * (100.0f + // Proc chance for this weapon
static_cast<float>(weapon->ProcRate)) / 100.0f;
if (zone->random.Roll(WPC)) { // 255 dex = 0.084 chance of proc. No idea what this number should be really.
if (weapon->Proc.Level > ourlevel) {
if (weapon->Proc.Level2 > ourlevel) {
Log(Logs::Detail, Logs::Combat,
"Tried to proc (%s), but our level (%d) is lower than required (%d)",
weapon->Name, ourlevel, weapon->Proc.Level);
weapon->Name, ourlevel, weapon->Proc.Level2);
if (IsPet()) {
Mob *own = GetOwner();
if (own)
@@ -3962,7 +4017,7 @@ void Mob::TryWeaponProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *
float APC = ProcChance * (100.0f + // Proc chance for this aug
static_cast<float>(aug->ProcRate)) / 100.0f;
if (zone->random.Roll(APC)) {
if (aug->Proc.Level > ourlevel) {
if (aug->Proc.Level2 > ourlevel) {
if (IsPet()) {
Mob *own = GetOwner();
if (own)
@@ -4387,6 +4442,12 @@ void Mob::DoRiposte(Mob *defender)
if (!defender)
return;
// so ahhh the angle you can riposte is larger than the angle you can hit :P
if (!defender->IsFacingMob(this)) {
defender->Message_StringID(MT_TooFarAway, CANT_SEE_TARGET);
return;
}
defender->Attack(this, EQEmu::inventory::slotPrimary, true);
if (HasDied())
return;
@@ -5247,19 +5308,30 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
// extra off hand non-sense, can only double with skill of 150 or above
// or you have any amount of GiveDoubleAttack
if (candouble && hand == EQEmu::inventory::slotSecondary)
candouble = GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 || (aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0;
candouble =
GetSkill(EQEmu::skills::SkillDoubleAttack) > 149 ||
(aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack) > 0;
if (candouble) {
CheckIncreaseSkill(EQEmu::skills::SkillDoubleAttack, target, -10);
if (CheckDoubleAttack()) {
Attack(target, hand, false, false, IsFromSpell);
// Modern AA description: Increases your chance of ... performing one additional hit with a 2-handed weapon when double attacking by 2%.
if (hand == EQEmu::inventory::slotPrimary) {
auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance +
itembonuses.ExtraAttackChance;
if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance))
Attack(target, hand, false, false, IsFromSpell);
}
// you can only triple from the main hand
if (hand == EQEmu::inventory::slotPrimary && CanThisClassTripleAttack()) {
CheckIncreaseSkill(EQEmu::skills::SkillTripleAttack, target, -10);
if (CheckTripleAttack()) {
Attack(target, hand, false, false, IsFromSpell);
auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance +
itembonuses.FlurryChance;
itembonuses.FlurryChance;
if (flurrychance && zone->random.Roll(flurrychance)) {
Attack(target, hand, false, false, IsFromSpell);
if (zone->random.Roll(flurrychance))
@@ -5270,12 +5342,6 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
}
}
}
if (hand == EQEmu::inventory::slotPrimary) {
auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance;
if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance))
Attack(target, hand, false, false, IsFromSpell);
}
}
bool Mob::CheckDualWield()
+3
View File
@@ -616,6 +616,7 @@ bool Aura::Process()
it = spawned_for.erase(it);
}
}
safe_delete(app);
}
// TODO: waypoints?
@@ -757,6 +758,8 @@ void Mob::MakeAura(uint16 spell_id)
auto npc = new Aura(npc_type, this, record);
npc->SetAuraID(spell_id);
if (trap)
npc->TryMoveAlong(5.0f, 0.0f, false); // try to place 5 units in front
entity_list.AddNPC(npc, false);
if (trap)
+12 -1
View File
@@ -120,6 +120,13 @@ void Client::CalcBonuses()
if (GetMaxXTargets() != 5 + aabonuses.extra_xtargets)
SetMaxXTargets(5 + aabonuses.extra_xtargets);
// hmm maybe a better way to do this
int metabolism = spellbonuses.Metabolism + itembonuses.Metabolism + aabonuses.Metabolism;
int timer = GetClass() == MONK ? CONSUMPTION_MNK_TIMER : CONSUMPTION_TIMER;
timer = timer * (100 + metabolism) / 100;
if (timer != consume_food_timer.GetTimerTime())
consume_food_timer.SetTimer(timer);
}
int Client::CalcRecommendedLevelBonus(uint8 level, uint8 reclevel, int basestat)
@@ -908,7 +915,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
newbon->GivePetGroupTarget = true;
break;
case SE_ItemHPRegenCapIncrease:
newbon->ItemHPRegenCap = +base1;
newbon->ItemHPRegenCap += base1;
break;
case SE_Ambidexterity:
newbon->Ambidexterity += base1;
@@ -2503,6 +2510,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
new_bonus->MagicWeapon = true;
break;
case SE_Hunger:
new_bonus->hunger = true;
break;
case SE_IncreaseBlockChance:
if (AdditiveWornBonus)
new_bonus->IncreaseBlockChance += effect_value;
+619 -405
View File
File diff suppressed because it is too large Load Diff
+7 -13
View File
@@ -42,6 +42,8 @@
#define BOT_FOLLOW_DISTANCE_DEFAULT_MAX 2500 // as DSq value (50 units)
#define BOT_FOLLOW_DISTANCE_WALK 1000 // as DSq value (~31.623 units)
#define BOT_LEASH_DISTANCE 250000 // as DSq value (500 units)
extern WorldServer worldserver;
const int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this
@@ -273,7 +275,7 @@ public:
static bool IsValidRaceClassCombo(uint16 r, uint8 c);
bool IsValidName();
static bool IsValidName(std::string& name);
void Spawn(Client* botCharacterOwner);
bool Spawn(Client* botCharacterOwner);
virtual void SetLevel(uint8 in_level, bool command = false);
virtual void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
virtual bool Process();
@@ -337,7 +339,7 @@ public:
bool IsStanding();
int GetBotWalkspeed() const { return (int)((float)_GetWalkSpeed() * 1.786f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143
int GetBotRunspeed() const { return (int)((float)_GetRunSpeed() * 1.786f); }
bool IsBotCasterAtCombatRange(Mob *target);
int GetBotFearSpeed() const { return (int)((float)_GetFearSpeed() * 1.786f); }
bool UseDiscipline(uint32 spell_id, uint32 target);
uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets);
bool GetNeedsCured(Mob *tar);
@@ -404,8 +406,8 @@ public:
bool AIHealRotation(Mob* tar, bool useFastHeals);
bool GetPauseAI() { return _pauseAI; }
void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; }
void SetGuardMode();
// Mob AI Virtual Override Methods
virtual void AI_Process();
virtual void AI_Stop();
@@ -532,9 +534,7 @@ public:
bool IsBotWISCaster() { return IsWISCasterClass(GetClass()); }
bool CanHeal();
int GetRawACNoShield(int &shield_ac);
bool GetHasBeenSummoned() { return _hasBeenSummoned; }
const glm::vec3 GetPreSummonLocation() const { return m_PreSummonLocation; }
// new heal rotation code
bool CreateHealRotation(uint32 cycle_duration_ms = 5000, bool fast_heals = false, bool adaptive_targeting = false, bool casting_override = false);
bool DestroyHealRotation();
@@ -628,9 +628,6 @@ public:
void SetBotStance(BotStanceType botStance) { _botStance = ((botStance != BotStanceUnknown) ? (botStance) : (BotStancePassive)); }
void SetSpellRecastTimer(int timer_index, int32 recast_delay);
void SetDisciplineRecastTimer(int timer_index, int32 recast_delay);
void SetHasBeenSummoned(bool s);
void SetPreSummonLocation(const glm::vec3& location) { m_PreSummonLocation = location; }
void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;}
void SetShowHelm(bool showhelm) { _showhelm = showhelm; }
void SetBeardColor(uint8 value) { beardcolor = value; }
@@ -688,7 +685,6 @@ protected:
virtual int32 CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16 spell_id);
virtual void PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client);
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
virtual float GetMaxMeleeRangeToTarget(Mob* target);
BotCastingRoles& GetCastingRoles() { return m_CastingRoles; }
void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; }
@@ -734,9 +730,7 @@ private:
int32 max_end;
int32 end_regen;
uint32 timers[MaxTimer];
bool _hasBeenSummoned;
glm::vec3 m_PreSummonLocation;
Timer evade_timer; // can be moved to pTimers at some point
BotCastingRoles m_CastingRoles;
+25 -20
View File
@@ -3046,13 +3046,7 @@ void bot_command_guard(Client *c, const Seperator *sep)
sbl.remove(nullptr);
for (auto bot_iter : sbl) {
bot_iter->WipeHateList();
bot_iter->SetFollowID(0);
if (!bot_iter->GetPet())
continue;
bot_iter->GetPet()->WipeHateList();
bot_iter->GetPet()->SetFollowID(0);
bot_iter->SetGuardMode();
}
if (sbl.size() == 1)
Bot::BotGroupSay(sbl.front(), "Guarding this position");
@@ -5054,7 +5048,11 @@ void bot_subcommand_bot_spawn(Client *c, const Seperator *sep)
return;
}
my_bot->Spawn(c);
if (!my_bot->Spawn(c)) {
c->Message(m_fail, "Failed to spawn bot '%s' (id: %i)", bot_name.c_str(), bot_id);
safe_delete(my_bot);
return;
}
static const char* bot_spawn_message[16] = {
"A solid weapon is my ally!", // WARRIOR / 'generic'
@@ -5145,10 +5143,10 @@ void bot_subcommand_bot_summon(Client *c, const Seperator *sep)
if (!bot_iter)
continue;
Bot::BotGroupSay(bot_iter, "Whee!");
//Bot::BotGroupSay(bot_iter, "Whee!");
bot_iter->WipeHateList();
bot_iter->SetTarget(bot_iter->GetBotOwner());
bot_iter->SetTarget(nullptr);
bot_iter->Warp(glm::vec3(c->GetPosition()));
bot_iter->DoAnim(0);
@@ -5156,7 +5154,7 @@ void bot_subcommand_bot_summon(Client *c, const Seperator *sep)
continue;
bot_iter->GetPet()->WipeHateList();
bot_iter->GetPet()->SetTarget(bot_iter);
bot_iter->GetPet()->SetTarget(nullptr);
bot_iter->GetPet()->Warp(glm::vec3(c->GetPosition()));
}
@@ -5810,18 +5808,22 @@ void bot_subcommand_botgroup_load(Client *c, const Seperator *sep)
return;
}
if (!leader_id) {
c->Message(m_fail, "Can not locate bot-group leader id for '%s'", botgroup_name_arg.c_str());
c->Message(m_fail, "Cannot locate bot-group leader id for '%s'", botgroup_name_arg.c_str());
return;
}
auto botgroup_leader = Bot::LoadBot(leader_id);
if (!botgroup_leader) {
c->Message(m_fail, "Could not spawn bot-group leader for '%s'", botgroup_name_arg.c_str());
c->Message(m_fail, "Could not load bot-group leader for '%s'", botgroup_name_arg.c_str());
safe_delete(botgroup_leader);
return;
}
botgroup_leader->Spawn(c);
if (!botgroup_leader->Spawn(c)) {
c->Message(m_fail, "Could not spawn bot-group leader %s for '%s'", botgroup_leader->GetName(), botgroup_name_arg.c_str());
safe_delete(botgroup_leader);
return;
}
Group* group_inst = new Group(botgroup_leader);
@@ -5840,7 +5842,12 @@ void bot_subcommand_botgroup_load(Client *c, const Seperator *sep)
return;
}
botgroup_member->Spawn(c);
if (!botgroup_member->Spawn(c)) {
c->Message(m_fail, "Could not spawn bot '%s' (id: %i)", botgroup_member->GetName(), member_iter);
safe_delete(botgroup_member);
return;
}
Bot::AddBotToGroup(botgroup_member, group_inst);
}
@@ -7077,7 +7084,6 @@ void bot_subcommand_inventory_list(Client *c, const Seperator *sep)
const EQEmu::ItemData* item = nullptr;
bool is2Hweapon = false;
std::string item_link;
EQEmu::SayLinkEngine linker;
linker.SetLinkType(EQEmu::saylink::SayLinkItemInst);
@@ -7098,8 +7104,7 @@ void bot_subcommand_inventory_list(Client *c, const Seperator *sep)
}
linker.SetItemInst(inst);
item_link = linker.GenerateLink();
c->Message(m_message, "Using %s in my %s (slot %i)", item_link.c_str(), GetBotEquipSlotName(i), (i == 22 ? EQEmu::inventory::slotPowerSource : i));
c->Message(m_message, "Using %s in my %s (slot %i)", linker.GenerateLink().c_str(), GetBotEquipSlotName(i), (i == 22 ? EQEmu::inventory::slotPowerSource : i));
++inventory_count;
}
@@ -7242,8 +7247,8 @@ void bot_subcommand_inventory_window(Client *c, const Seperator *sep)
std::string window_text;
//std::string item_link;
//Client::TextLink linker;
//linker.SetLinkType(linker.linkItemInst);
//EQEmu::SayLinkEngine linker;
//linker.SetLinkType(EQEmu::saylink::SayLinkItemInst);
for (int i = EQEmu::legacy::EQUIPMENT_BEGIN; i <= (EQEmu::legacy::EQUIPMENT_END + 1); ++i) {
const EQEmu::ItemData* item = nullptr;
+4 -1
View File
@@ -273,7 +273,10 @@ bool BotDatabase::LoadBotID(const uint32 owner_id, const std::string& bot_name,
if (!owner_id || bot_name.empty())
return false;
query = StringFormat("SELECT `bot_id` FROM `bot_data` WHERE `name` = '%s' LIMIT 1", bot_name.c_str());
query = StringFormat(
"SELECT `bot_id` FROM `bot_data` WHERE `owner_id` = '%u' AND `name` = '%s' LIMIT 1",
owner_id, bot_name.c_str()
);
auto results = QueryDatabase(query);
if (!results.Success())
return false;
+261 -74
View File
@@ -123,8 +123,7 @@ Client::Client(EQStreamInterface* ieqs)
hpupdate_timer(2000),
camp_timer(29000),
process_timer(100),
stamina_timer(40000),
consume_food_timer(60000),
consume_food_timer(CONSUMPTION_TIMER),
zoneinpacket_timer(1000),
linkdead_timer(RuleI(Zone,ClientLinkdeadMS)),
dead_timer(2000),
@@ -161,7 +160,8 @@ Client::Client(EQStreamInterface* ieqs)
npc_close_scan_timer(6000),
hp_self_update_throttle_timer(300),
hp_other_update_throttle_timer(500),
position_update_timer(10000)
position_update_timer(10000),
tmSitting(0)
{
for (int client_filter = 0; client_filter < _FilterCount; client_filter++)
@@ -215,7 +215,7 @@ Client::Client(EQStreamInterface* ieqs)
linkdead_timer.Disable();
zonesummon_id = 0;
zonesummon_ignorerestrictions = 0;
zoning = false;
bZoning = false;
zone_mode = ZoneUnsolicited;
casting_spell_id = 0;
npcflag = false;
@@ -254,7 +254,7 @@ Client::Client(EQStreamInterface* ieqs)
mercSlot = 0;
InitializeMercInfo();
SetMerc(0);
if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true, false);
logging_enabled = CLIENT_DEFAULT_LOGGING_ENABLED;
//for good measure:
@@ -271,9 +271,10 @@ Client::Client(EQStreamInterface* ieqs)
m_ClientVersion = EQEmu::versions::ClientVersion::Unknown;
m_ClientVersionBit = 0;
AggroCount = 0;
RestRegenHP = 0;
RestRegenMana = 0;
RestRegenEndurance = 0;
ooc_regen = false;
AreaHPRegen = 1.0f;
AreaManaRegen = 1.0f;
AreaEndRegen = 1.0f;
XPRate = 100;
current_endurance = 0;
@@ -330,6 +331,14 @@ Client::Client(EQStreamInterface* ieqs)
interrogateinv_flag = false;
trapid = 0;
for (int i = 0; i < InnateSkillMax; ++i)
m_pp.InnateSkills[i] = InnateDisabled;
temp_pvp = false;
is_client_moving = false;
AI_Init();
}
@@ -389,7 +398,7 @@ Client::~Client() {
GetTarget()->IsTargeted(-1);
//if we are in a group and we are not zoning, force leave the group
if(isgrouped && !zoning && is_zone_loaded)
if(isgrouped && !bZoning && is_zone_loaded)
LeaveGroup();
UpdateWho(2);
@@ -456,8 +465,8 @@ void Client::SendZoneInPackets()
if (!GetHideMe()) entity_list.QueueClients(this, outapp, true);
safe_delete(outapp);
SetSpawned();
if (GetPVP()) //force a PVP update until we fix the spawn struct
SendAppearancePacket(AT_PVP, GetPVP(), true, false);
if (GetPVP(false)) //force a PVP update until we fix the spawn struct
SendAppearancePacket(AT_PVP, GetPVP(false), true, false);
//Send AA Exp packet:
if (GetLevel() >= 51)
@@ -1217,18 +1226,18 @@ void Client::ChannelMessageSend(const char* from, const char* to, uint8 chan_num
EffSkill = 100;
cm->skill_in_language = EffSkill;
// Garble the message based on listener skill
if (ListenerSkill < 100) {
GarbleMessage(buffer, (100 - ListenerSkill));
}
cm->chan_num = chan_num;
strcpy(&cm->message[0], buffer);
QueuePacket(&app);
if ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up
if (m_pp.languages[language] <= lang_skill)
CheckLanguageSkillIncrease(language, lang_skill);
bool senderCanTrainSelf = RuleB(Client, SelfLanguageLearning);
bool weAreNotSender = strcmp(this->GetCleanName(), cm->sender);
if (senderCanTrainSelf || weAreNotSender) {
if ((chan_num == 2) && (ListenerSkill < 100)) { // group message in unmastered language, check for skill up
if (m_pp.languages[language] <= lang_skill)
CheckLanguageSkillIncrease(language, lang_skill);
}
}
}
@@ -1949,7 +1958,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
ns->spawn.gm = GetGM() ? 1 : 0;
ns->spawn.guildID = GuildID();
// ns->spawn.linkdead = IsLD() ? 1 : 0;
// ns->spawn.pvp = GetPVP() ? 1 : 0;
// ns->spawn.pvp = GetPVP(false) ? 1 : 0;
ns->spawn.show_name = true;
@@ -4594,7 +4603,7 @@ void Client::IncrementAggroCount() {
//
AggroCount++;
if(!RuleI(Character, RestRegenPercent))
if(!RuleB(Character, RestRegenEnabled))
return;
// If we already had aggro before this method was called, the combat indicator should already be up for SoF clients,
@@ -4631,7 +4640,7 @@ void Client::DecrementAggroCount() {
AggroCount--;
if(!RuleI(Character, RestRegenPercent))
if(!RuleB(Character, RestRegenEnabled))
return;
// Something else is still aggro on us, can't rest yet.
@@ -6718,7 +6727,7 @@ void Client::SendStatsWindow(Client* client, bool use_window)
cap_regen_field = itoa(CalcHPRegenCap());
spell_regen_field = itoa(spellbonuses.HPRegen);
aa_regen_field = itoa(aabonuses.HPRegen);
total_regen_field = itoa(CalcHPRegen());
total_regen_field = itoa(CalcHPRegen(true));
break;
}
case 1: {
@@ -6731,7 +6740,7 @@ void Client::SendStatsWindow(Client* client, bool use_window)
cap_regen_field = itoa(CalcManaRegenCap());
spell_regen_field = itoa(spellbonuses.ManaRegen);
aa_regen_field = itoa(aabonuses.ManaRegen);
total_regen_field = itoa(CalcManaRegen());
total_regen_field = itoa(CalcManaRegen(true));
}
else { continue; }
break;
@@ -6745,7 +6754,7 @@ void Client::SendStatsWindow(Client* client, bool use_window)
cap_regen_field = itoa(CalcEnduranceRegenCap());
spell_regen_field = itoa(spellbonuses.EnduranceRegen);
aa_regen_field = itoa(aabonuses.EnduranceRegen);
total_regen_field = itoa(CalcEnduranceRegen());
total_regen_field = itoa(CalcEnduranceRegen(true));
break;
}
default: { break; }
@@ -7899,7 +7908,7 @@ void Client::GarbleMessage(char *message, uint8 variance)
for (size_t i = 0; i < strlen(message); i++) {
// Client expects hex values inside of a text link body
if (message[i] == delimiter) {
if (!(delimiter_count & 1)) { i += EQEmu::legacy::TEXT_LINK_BODY_LENGTH; }
if (!(delimiter_count & 1)) { i += EQEmu::constants::SayLinkBodySize; }
++delimiter_count;
continue;
}
@@ -8591,64 +8600,52 @@ void Client::SetConsumption(int32 in_hunger, int32 in_thirst)
void Client::Consume(const EQEmu::ItemData *item, uint8 type, int16 slot, bool auto_consume)
{
if(!item) { return; }
if (!item)
return;
/*
Spell Bonuses 2 digit form - 10, 20, 25 etc. (10%, 20%, 25%)
AA Bonus Ranks, 110, 125, 150 etc. (10%, 25%, 50%)
*/
int increase = item->CastTime_ * 100;
if (!auto_consume) // force feeding is half as effective
increase /= 2;
float aa_bonus = ((float) aabonuses.Metabolism - 100) / 100;
float item_bonus = (float) itembonuses.Metabolism / 100;
float spell_bonus = (float) spellbonuses.Metabolism / 100;
float metabolism_mod = 1 + spell_bonus + item_bonus + aa_bonus;
Log(Logs::General, Logs::Food, "Client::Consume() Metabolism bonuses spell_bonus: (%.2f) item_bonus: (%.2f) aa_bonus: (%.2f) final: (%.2f)",
spell_bonus,
item_bonus,
aa_bonus,
metabolism_mod
);
if (increase < 0) // wasn't food? oh well
return;
if (type == EQEmu::item::ItemTypeFood) {
int hunger_change = item->CastTime_ * metabolism_mod;
hunger_change = mod_food_value(item, hunger_change);
increase = mod_food_value(item, increase);
if(hunger_change < 0)
return;
if (increase < 0)
return;
m_pp.hunger_level += hunger_change;
m_pp.hunger_level += increase;
Log(Logs::General, Logs::Food, "Consuming food, points added to hunger_level: %i - current_hunger: %i", hunger_change, m_pp.hunger_level);
DeleteItemInInventory(slot, 1, false);
if(!auto_consume) //no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name);
Log(Logs::General, Logs::Food, "Eating from slot: %i", (int)slot);
}
else {
int thirst_change = item->CastTime_ * metabolism_mod;
thirst_change = mod_drink_value(item, thirst_change);
if(thirst_change < 0)
return;
m_pp.thirst_level += thirst_change;
Log(Logs::General, Logs::Food, "Consuming food, points added to hunger_level: %i - current_hunger: %i",
increase, m_pp.hunger_level);
DeleteItemInInventory(slot, 1, false);
Log(Logs::General, Logs::Food, "Consuming drink, points added to thirst_level: %i current_thirst: %i", thirst_change, m_pp.thirst_level);
if (!auto_consume) // no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, EATING_MESSAGE, GetName(), item->Name);
if(!auto_consume) //no message if the client consumed for us
Log(Logs::General, Logs::Food, "Eating from slot: %i", (int)slot);
} else {
increase = mod_drink_value(item, increase);
if (increase < 0)
return;
m_pp.thirst_level += increase;
DeleteItemInInventory(slot, 1, false);
Log(Logs::General, Logs::Food, "Consuming drink, points added to thirst_level: %i current_thirst: %i",
increase, m_pp.thirst_level);
if (!auto_consume) // no message if the client consumed for us
entity_list.MessageClose_StringID(this, true, 50, 0, DRINKING_MESSAGE, GetName(), item->Name);
Log(Logs::General, Logs::Food, "Drinking from slot: %i", (int)slot);
}
Log(Logs::General, Logs::Food, "Drinking from slot: %i", (int)slot);
}
}
void Client::SendMarqueeMessage(uint32 type, uint32 priority, uint32 fade_in, uint32 fade_out, uint32 duration, std::string msg)
@@ -8895,9 +8892,9 @@ void Client::CheckRegionTypeChanges()
return;
if (last_region_type == RegionTypePVP)
SetPVP(true, false);
else if (GetPVP())
SetPVP(false, false);
temp_pvp = true;
else if (temp_pvp)
temp_pvp = false;
}
void Client::ProcessAggroMeter()
@@ -9074,3 +9071,193 @@ void Client::SetPetCommandState(int button, int state)
FastQueuePacket(&app);
}
bool Client::CanMedOnHorse()
{
// no horse is false
if (GetHorseId() == 0)
return false;
// can't med while attacking
if (auto_attack)
return false;
return animation == 0 && m_Delta.x == 0.0f && m_Delta.y == 0.0f; // TODO: animation is SpeedRun
}
void Client::EnableAreaHPRegen(int value)
{
AreaHPRegen = value * 0.001f;
SendAppearancePacket(AT_AreaHPRegen, value, false);
}
void Client::DisableAreaHPRegen()
{
AreaHPRegen = 1.0f;
SendAppearancePacket(AT_AreaHPRegen, 1000, false);
}
void Client::EnableAreaManaRegen(int value)
{
AreaManaRegen = value * 0.001f;
SendAppearancePacket(AT_AreaManaRegen, value, false);
}
void Client::DisableAreaManaRegen()
{
AreaManaRegen = 1.0f;
SendAppearancePacket(AT_AreaManaRegen, 1000, false);
}
void Client::EnableAreaEndRegen(int value)
{
AreaEndRegen = value * 0.001f;
SendAppearancePacket(AT_AreaEndRegen, value, false);
}
void Client::DisableAreaEndRegen()
{
AreaEndRegen = 1.0f;
SendAppearancePacket(AT_AreaEndRegen, 1000, false);
}
void Client::EnableAreaRegens(int value)
{
EnableAreaHPRegen(value);
EnableAreaManaRegen(value);
EnableAreaEndRegen(value);
}
void Client::DisableAreaRegens()
{
DisableAreaHPRegen();
DisableAreaManaRegen();
DisableAreaEndRegen();
}
void Client::InitInnates()
{
// this function on the client also inits the level one innate skills (like swimming, hide, etc)
// we won't do that here, lets just do the InnateSkills for now. Basically translation of what the client is doing
// A lot of these we could probably have ignored because they have no known use or are 100% client side
// but I figured just in case we'll do them all out
//
// The client calls this in a few places. When you remove a vision buff and in SetHeights, which is called in
// illusions, mounts, and a bunch of other cases. All of the calls to InitInnates are wrapped in restoring regen
// besides the call initializing the first time
auto race = GetRace();
auto class_ = GetClass();
for (int i = 0; i < InnateSkillMax; ++i)
m_pp.InnateSkills[i] = InnateDisabled;
m_pp.InnateSkills[InnateInspect] = InnateEnabled;
m_pp.InnateSkills[InnateOpen] = InnateEnabled;
if (race >= RT_FROGLOK_3) {
if (race == RT_SKELETON_2 || race == RT_FROGLOK_3)
m_pp.InnateSkills[InnateUltraVision] = InnateEnabled;
else
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
}
switch (race) {
case RT_BARBARIAN:
case RT_BARBARIAN_2:
m_pp.InnateSkills[InnateSlam] = InnateEnabled;
break;
case RT_ERUDITE:
case RT_ERUDITE_2:
m_pp.InnateSkills[InnateLore] = InnateEnabled;
break;
case RT_WOOD_ELF:
case RT_GUARD_3:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
break;
case RT_HIGH_ELF:
case RT_GUARD_2:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
m_pp.InnateSkills[InnateLore] = InnateEnabled;
break;
case RT_DARK_ELF:
case RT_DARK_ELF_2:
case RT_VAMPIRE_2:
m_pp.InnateSkills[InnateUltraVision] = InnateEnabled;
break;
case RT_TROLL:
case RT_TROLL_2:
m_pp.InnateSkills[InnateRegen] = InnateEnabled;
m_pp.InnateSkills[InnateSlam] = InnateEnabled;
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
break;
case RT_DWARF:
case RT_DWARF_2:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
break;
case RT_OGRE:
case RT_OGRE_2:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
m_pp.InnateSkills[InnateSlam] = InnateEnabled;
m_pp.InnateSkills[InnateNoBash] = InnateEnabled;
m_pp.InnateSkills[InnateBashDoor] = InnateEnabled;
break;
case RT_HALFLING:
case RT_HALFLING_2:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
break;
case RT_GNOME:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
m_pp.InnateSkills[InnateLore] = InnateEnabled;
break;
case RT_IKSAR:
m_pp.InnateSkills[InnateRegen] = InnateEnabled;
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
break;
case RT_VAH_SHIR:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
break;
case RT_FROGLOK_2:
case RT_GHOST:
case RT_GHOUL:
case RT_SKELETON:
case RT_VAMPIRE:
case RT_WILL_O_WISP:
case RT_ZOMBIE:
case RT_SPECTRE:
case RT_GHOST_2:
case RT_GHOST_3:
case RT_DRAGON_2:
case RT_INNORUUK:
m_pp.InnateSkills[InnateUltraVision] = InnateEnabled;
break;
case RT_HUMAN:
case RT_GUARD:
case RT_BEGGAR:
case RT_HUMAN_2:
case RT_HUMAN_3:
case RT_FROGLOK_3: // client does froglok weird, but this should work out fine
break;
default:
m_pp.InnateSkills[InnateInfravision] = InnateEnabled;
break;
}
switch (class_) {
case DRUID:
m_pp.InnateSkills[InnateHarmony] = InnateEnabled;
break;
case BARD:
m_pp.InnateSkills[InnateReveal] = InnateEnabled;
break;
case ROGUE:
m_pp.InnateSkills[InnateSurprise] = InnateEnabled;
m_pp.InnateSkills[InnateReveal] = InnateEnabled;
break;
case RANGER:
m_pp.InnateSkills[InnateAwareness] = InnateEnabled;
break;
case MONK:
m_pp.InnateSkills[InnateSurprise] = InnateEnabled;
m_pp.InnateSkills[InnateAwareness] = InnateEnabled;
default:
break;
}
}
+54 -9
View File
@@ -199,6 +199,27 @@ struct RespawnOption
float heading;
};
// do not ask what all these mean because I have no idea!
// named from the client's CEverQuest::GetInnateDesc, they're missing some
enum eInnateSkill {
InnateEnabled = 0,
InnateAwareness = 1,
InnateBashDoor = 2,
InnateBreathFire = 3,
InnateHarmony = 4,
InnateInfravision = 6,
InnateLore = 8,
InnateNoBash = 9,
InnateRegen = 10,
InnateSlam = 11,
InnateSurprise = 12,
InnateUltraVision = 13,
InnateInspect = 14,
InnateOpen = 15,
InnateReveal = 16,
InnateSkillMax = 25, // size of array in client
InnateDisabled = 255
};
const uint32 POPUPID_UPDATE_SHOWSTATSWINDOW = 1000000;
@@ -374,7 +395,7 @@ public:
void SetGM(bool toggle);
void SetPVP(bool toggle, bool message = true);
inline bool GetPVP() const { return m_pp.pvp != 0; }
inline bool GetPVP(bool inc_temp = true) const { return m_pp.pvp != 0 || (inc_temp && temp_pvp); }
inline bool GetGM() const { return m_pp.gm != 0; }
inline void SetBaseClass(uint32 i) { m_pp.class_=i; }
@@ -406,6 +427,16 @@ public:
const int32& SetMana(int32 amount);
int32 CalcManaRegenCap();
// guild pool regen shit. Sends a SpawnAppearance with a value that regens to value * 0.001
void EnableAreaHPRegen(int value);
void DisableAreaHPRegen();
void EnableAreaManaRegen(int value);
void DisableAreaManaRegen();
void EnableAreaEndRegen(int value);
void DisableAreaEndRegen();
void EnableAreaRegens(int value);
void DisableAreaRegens();
void ServerFilter(SetServerFilter_Struct* filter);
void BulkSendTraderInventory(uint32 char_id);
void SendSingleTraderItem(uint32 char_id, int uniqueid);
@@ -540,7 +571,7 @@ public:
/*Endurance and such*/
void CalcMaxEndurance(); //This calculates the maximum endurance we can have
int32 CalcBaseEndurance(); //Calculates Base End
int32 CalcEnduranceRegen(); //Calculates endurance regen used in DoEnduranceRegen()
int32 CalcEnduranceRegen(bool bCombat = false); //Calculates endurance regen used in DoEnduranceRegen()
int32 GetEndurance() const {return current_endurance;} //This gets our current endurance
int32 GetMaxEndurance() const {return max_end;} //This gets our endurance from the last CalcMaxEndurance() call
int32 CalcEnduranceRegenCap();
@@ -575,6 +606,10 @@ public:
uint32 GetExperienceForKill(Mob *against);
void AddEXP(uint32 in_add_exp, uint8 conlevel = 0xFF, bool resexp = false);
uint32 CalcEXP(uint8 conlevel = 0xFF);
void CalculateNormalizedAAExp(uint32 &add_aaxp, uint8 conlevel, bool resexp);
void CalculateStandardAAExp(uint32 &add_aaxp, uint8 conlevel, bool resexp);
void CalculateLeadershipExp(uint32 &add_exp, uint8 conlevel);
void CalculateExp(uint32 in_add_exp, uint32 &add_exp, uint32 &add_aaxp, uint8 conlevel, bool resexp);
void SetEXP(uint32 set_exp, uint32 set_aaxp, bool resexp=false);
void AddLevelBasedExp(uint8 exp_percentage, uint8 max_level=0);
void SetLeadershipEXP(uint32 group_exp, uint32 raid_exp);
@@ -620,6 +655,7 @@ public:
void Sacrifice(Client* caster);
void GoToDeath();
inline const int32 GetInstanceID() const { return zone->GetInstanceID(); }
void SetZoning(bool in) { bZoning = in; }
FACTION_VALUE GetReverseFactionCon(Mob* iOther);
FACTION_VALUE GetFactionLevel(uint32 char_id, uint32 npc_id, uint32 p_race, uint32 p_class, uint32 p_deity, int32 pFaction, Mob* tnpc);
@@ -719,6 +755,7 @@ public:
void SendTradeskillDetails(uint32 recipe_id);
bool TradeskillExecute(DBTradeskillRecipe_Struct *spec);
void CheckIncreaseTradeskill(int16 bonusstat, int16 stat_modifier, float skillup_modifier, uint16 success_modifier, EQEmu::skills::SkillType tradeskill);
void InitInnates();
void GMKill();
inline bool IsMedding() const {return medding;}
@@ -760,6 +797,9 @@ public:
void SummonHorse(uint16 spell_id);
void SetHorseId(uint16 horseid_in);
uint16 GetHorseId() const { return horseId; }
bool CanMedOnHorse();
bool CanFastRegen() const { return ooc_regen; }
void NPCSpawn(NPC *target_npc, const char *identifier, uint32 extra = 0);
@@ -862,6 +902,7 @@ public:
void SetHunger(int32 in_hunger);
void SetThirst(int32 in_thirst);
void SetConsumption(int32 in_hunger, int32 in_thirst);
bool IsStarved() const { if (GetGM() || !RuleB(Character, EnableHungerPenalties)) return false; return m_pp.hunger_level == 0 || m_pp.thirst_level == 0; }
bool CheckTradeLoreConflict(Client* other);
bool CheckTradeNonDroppable();
@@ -1266,6 +1307,8 @@ public:
int32 CalcATK();
uint32 trapid; //ID of trap player has triggered. This is cleared when the player leaves the trap's radius, or it despawns.
protected:
friend class Mob;
void CalcItemBonuses(StatBonuses* newbon);
@@ -1345,8 +1388,8 @@ private:
int32 CalcCorrup();
int32 CalcMaxHP();
int32 CalcBaseHP();
int32 CalcHPRegen();
int32 CalcManaRegen();
int32 CalcHPRegen(bool bCombat = false);
int32 CalcManaRegen(bool bCombat = false);
int32 CalcBaseManaRegen();
uint32 GetClassHPFactor();
void DoHPRegen();
@@ -1410,6 +1453,7 @@ private:
std::string BuyerWelcomeMessage;
bool AbilityTimer;
int Haste; //precalced value
uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004
int32 max_end;
int32 current_endurance;
@@ -1422,6 +1466,7 @@ private:
PetInfo m_suspendedminion; // pet data for our suspended minion.
MercInfo m_mercinfo[MAXMERCS]; // current mercenary
InspectMessage_Struct m_inspect_message;
bool temp_pvp;
void NPCSpawn(const Seperator* sep);
uint32 GetEXPForLevel(uint16 level);
@@ -1458,7 +1503,6 @@ private:
Timer hpupdate_timer;
Timer camp_timer;
Timer process_timer;
Timer stamina_timer;
Timer consume_food_timer;
Timer zoneinpacket_timer;
Timer linkdead_timer;
@@ -1503,7 +1547,7 @@ private:
bool npcflag;
uint8 npclevel;
bool feigned;
bool zoning;
bool bZoning;
bool tgb;
bool instalog;
int32 last_reported_mana;
@@ -1514,9 +1558,10 @@ private:
unsigned int AggroCount; // How many mobs are aggro on us.
unsigned int RestRegenHP;
unsigned int RestRegenMana;
unsigned int RestRegenEndurance;
bool ooc_regen;
float AreaHPRegen;
float AreaManaRegen;
float AreaEndRegen;
bool EngagedRaidTarget;
uint32 SavedRaidRestTimer;
+213 -34
View File
@@ -22,6 +22,8 @@
#include "../common/rulesys.h"
#include "../common/spdat.h"
#include "../common/data_verification.h"
#include "client.h"
#include "mob.h"
@@ -231,16 +233,81 @@ int32 Client::LevelRegen()
return hp;
}
int32 Client::CalcHPRegen()
int32 Client::CalcHPRegen(bool bCombat)
{
int32 regen = LevelRegen() + itembonuses.HPRegen + spellbonuses.HPRegen;
regen += aabonuses.HPRegen + GroupLeadershipAAHealthRegeneration();
int item_regen = itembonuses.HPRegen; // worn spells and +regen, already capped
item_regen += GetHeroicSTA() / 20;
item_regen += aabonuses.HPRegen;
int base = 0;
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data)
base = static_cast<int>(base_data->hp_regen);
auto level = GetLevel();
bool skip_innate = false;
if (IsSitting()) {
if (level >= 50) {
base++;
if (level >= 65)
base++;
}
if ((Timer::GetCurrentTime() - tmSitting) > 60000) {
if (!IsAffectedByBuffByGlobalGroup(GlobalGroup::Lich)) {
auto tic_diff = std::min((Timer::GetCurrentTime() - tmSitting) / 60000, static_cast<uint32>(9));
if (tic_diff != 1) { // starts at 2 mins
int tic_bonus = tic_diff * 1.5 * base;
if (m_pp.InnateSkills[InnateRegen] != InnateDisabled)
tic_bonus = tic_bonus * 1.2;
base = tic_bonus;
skip_innate = true;
} else if (m_pp.InnateSkills[InnateRegen] == InnateDisabled) { // no innate regen gets first tick
int tic_bonus = base * 1.5;
base = tic_bonus;
}
}
}
}
if (!skip_innate && m_pp.InnateSkills[InnateRegen] != InnateDisabled) {
if (level >= 50) {
++base;
if (level >= 55)
++base;
}
base *= 2;
}
if (IsStarved())
base = 0;
base += GroupLeadershipAAHealthRegeneration();
// some IsKnockedOut that sets to -1
base = base * 100.0f * AreaHPRegen * 0.01f + 0.5f;
// another check for IsClient && !(base + item_regen) && Cur_HP <= 0 do --base; do later
if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) {
auto fast_mod = RuleI(Character, RestRegenHP); // TODO: this is actually zone based
auto max_hp = GetMaxHP();
int fast_regen = 6 * (max_hp / fast_mod);
if (base < fast_regen) // weird, but what the client is doing
base = fast_regen;
}
int regen = base + item_regen + spellbonuses.HPRegen; // TODO: client does this in buff tick
return (regen * RuleI(Character, HPRegenMultiplier) / 100);
}
int32 Client::CalcHPRegenCap()
{
int cap = RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25;
int cap = RuleI(Character, ItemHealthRegenCap);
if (GetLevel() > 60)
cap = std::max(cap, GetLevel() - 30); // if the rule is set greater than normal I guess
if (GetLevel() > 65)
cap += GetLevel() - 65;
cap += aabonuses.ItemHPRegenCap + spellbonuses.ItemHPRegenCap + itembonuses.ItemHPRegenCap;
return (cap * RuleI(Character, HPRegenMultiplier) / 100);
}
@@ -1169,43 +1236,80 @@ int32 Client::CalcBaseManaRegen()
return regen;
}
int32 Client::CalcManaRegen()
int32 Client::CalcManaRegen(bool bCombat)
{
uint8 clevel = GetLevel();
int32 regen = 0;
//this should be changed so we dont med while camping, etc...
if (IsSitting() || (GetHorseId() != 0)) {
BuffFadeBySitModifier();
if (HasSkill(EQEmu::skills::SkillMeditate)) {
this->medding = true;
regen = (((GetSkill(EQEmu::skills::SkillMeditate) / 10) + (clevel - (clevel / 4))) / 4) + 4;
regen += spellbonuses.ManaRegen + itembonuses.ManaRegen;
CheckIncreaseSkill(EQEmu::skills::SkillMeditate, nullptr, -5);
}
else {
regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen;
int regen = 0;
auto level = GetLevel();
// so the new formulas break down with older skill caps where you don't have the skill until 4 or 8
// so for servers that want to use the old skill progression they can set this rule so they
// will get at least 1 for standing and 2 for sitting.
bool old = RuleB(Character, OldMinMana);
if (!IsStarved()) {
// client does some base regen for shrouds here
if (IsSitting() || CanMedOnHorse()) {
// kind of weird to do it here w/e
// client does some base medding regen for shrouds here
if (GetClass() != BARD) {
auto skill = GetSkill(EQEmu::skills::SkillMeditate);
if (skill > 0) {
regen++;
if (skill > 1)
regen++;
if (skill >= 15)
regen += skill / 15;
}
}
if (old)
regen = std::max(regen, 2);
} else if (old) {
regen = std::max(regen, 1);
}
}
else {
this->medding = false;
regen = 2 + spellbonuses.ManaRegen + itembonuses.ManaRegen;
if (level > 61) {
regen++;
if (level > 63)
regen++;
}
//AAs
regen += aabonuses.ManaRegen;
// add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet?
int item_bonus = itembonuses.ManaRegen; // this is capped already
int heroic_bonus = 0;
switch (GetCasterClass()) {
case 'W':
heroic_bonus = GetHeroicWIS();
break;
default:
heroic_bonus = GetHeroicINT();
break;
}
item_bonus += heroic_bonus / 25;
regen += item_bonus;
if (level <= 70 && regen > 65)
regen = 65;
regen = regen * 100.0f * AreaManaRegen * 0.01f + 0.5f;
if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) {
auto fast_mod = RuleI(Character, RestRegenMana); // TODO: this is actually zone based
auto max_mana = GetMaxMana();
int fast_regen = 6 * (max_mana / fast_mod);
if (regen < fast_regen) // weird, but what the client is doing
regen = fast_regen;
}
regen += spellbonuses.ManaRegen; // TODO: live does this in buff tick
return (regen * RuleI(Character, ManaRegenMultiplier) / 100);
}
int32 Client::CalcManaRegenCap()
{
int32 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap;
switch (GetCasterClass()) {
case 'I':
cap += (itembonuses.HeroicINT / 25);
break;
case 'W':
cap += (itembonuses.HeroicWIS / 25);
break;
}
return (cap * RuleI(Character, ManaRegenMultiplier) / 100);
}
@@ -2091,16 +2195,91 @@ int32 Client::CalcBaseEndurance()
return base_end;
}
int32 Client::CalcEnduranceRegen()
int32 Client::CalcEnduranceRegen(bool bCombat)
{
int32 regen = int32(GetLevel() * 4 / 10) + 2;
regen += aabonuses.EnduranceRegen + spellbonuses.EnduranceRegen + itembonuses.EnduranceRegen;
int base = 0;
if (!IsStarved()) {
auto base_data = database.GetBaseData(GetLevel(), GetClass());
if (base_data) {
base = static_cast<int>(base_data->end_regen);
if (!auto_attack && base > 0)
base += base / 2;
}
}
// so when we are mounted, our local client SpeedRun is always 0, so this is always false, but the packets we process it to our own shit :P
bool is_running = runmode && animation != 0 && GetHorseId() == 0; // TODO: animation is really what MQ2 calls SpeedRun
int weight_limit = GetSTR();
auto level = GetLevel();
if (GetClass() == MONK) {
if (level > 99)
weight_limit = 58;
else if (level > 94)
weight_limit = 57;
else if (level > 89)
weight_limit = 56;
else if (level > 84)
weight_limit = 55;
else if (level > 79)
weight_limit = 54;
else if (level > 64)
weight_limit = 53;
else if (level > 63)
weight_limit = 50;
else if (level > 61)
weight_limit = 47;
else if (level > 59)
weight_limit = 45;
else if (level > 54)
weight_limit = 40;
else if (level > 50)
weight_limit = 38;
else if (level > 44)
weight_limit = 36;
else if (level > 29)
weight_limit = 34;
else if (level > 14)
weight_limit = 32;
else
weight_limit = 30;
}
bool encumbered = (CalcCurrentWeight() / 10) >= weight_limit;
if (is_running)
base += level / -15;
if (encumbered)
base += level / -15;
auto item_bonus = GetHeroicAGI() + GetHeroicDEX() + GetHeroicSTA() + GetHeroicSTR();
item_bonus = item_bonus / 4 / 50;
item_bonus += itembonuses.EnduranceRegen; // this is capped already
base += item_bonus;
base = base * AreaEndRegen + 0.5f;
auto aa_regen = aabonuses.EnduranceRegen;
int regen = base;
if (!bCombat && CanFastRegen() && (IsSitting() || CanMedOnHorse())) {
auto fast_mod = RuleI(Character, RestRegenEnd); // TODO: this is actually zone based
auto max_end = GetMaxEndurance();
int fast_regen = 6 * (max_end / fast_mod);
if (aa_regen < fast_regen) // weird, but what the client is doing
aa_regen = fast_regen;
}
regen += aa_regen;
regen += spellbonuses.EnduranceRegen; // TODO: client does this in buff tick
return (regen * RuleI(Character, EnduranceRegenMultiplier) / 100);
}
int32 Client::CalcEnduranceRegenCap()
{
int cap = (RuleI(Character, ItemEnduranceRegenCap) + itembonuses.HeroicSTR / 25 + itembonuses.HeroicDEX / 25 + itembonuses.HeroicAGI / 25 + itembonuses.HeroicSTA / 25);
int cap = RuleI(Character, ItemEnduranceRegenCap);
return (cap * RuleI(Character, EnduranceRegenMultiplier) / 100);
}
+238 -89
View File
@@ -72,6 +72,7 @@ extern PetitionList petition_list;
extern EntityList entity_list;
typedef void (Client::*ClientPacketProc)(const EQApplicationPacket *app);
//Use a map for connecting opcodes since it dosent get used a lot and is sparse
std::map<uint32, ClientPacketProc> ConnectingOpcodes;
//Use a static array for connected, for speed
@@ -291,6 +292,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_MercenaryTimerRequest] = &Client::Handle_OP_MercenaryTimerRequest;
ConnectedOpcodes[OP_MoveCoin] = &Client::Handle_OP_MoveCoin;
ConnectedOpcodes[OP_MoveItem] = &Client::Handle_OP_MoveItem;
ConnectedOpcodes[OP_MoveMultipleItems] = &Client::Handle_OP_MoveMultipleItems;
ConnectedOpcodes[OP_OpenContainer] = &Client::Handle_OP_OpenContainer;
ConnectedOpcodes[OP_OpenGuildTributeMaster] = &Client::Handle_OP_OpenGuildTributeMaster;
ConnectedOpcodes[OP_OpenInventory] = &Client::Handle_OP_OpenInventory;
@@ -314,6 +316,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_PurchaseLeadershipAA] = &Client::Handle_OP_PurchaseLeadershipAA;
ConnectedOpcodes[OP_PVPLeaderBoardDetailsRequest] = &Client::Handle_OP_PVPLeaderBoardDetailsRequest;
ConnectedOpcodes[OP_PVPLeaderBoardRequest] = &Client::Handle_OP_PVPLeaderBoardRequest;
ConnectedOpcodes[OP_QueryUCSServerStatus] = &Client::Handle_OP_QueryUCSServerStatus;
ConnectedOpcodes[OP_RaidInvite] = &Client::Handle_OP_RaidCommand;
ConnectedOpcodes[OP_RandomReq] = &Client::Handle_OP_RandomReq;
ConnectedOpcodes[OP_ReadBook] = &Client::Handle_OP_ReadBook;
@@ -790,7 +793,7 @@ void Client::CompleteConnect()
}
if (zone)
zone->weatherSend();
zone->weatherSend(this);
TotalKarma = database.GetKarma(AccountID());
SendDisciplineTimers();
@@ -1410,6 +1413,12 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
drakkin_tattoo = m_pp.drakkin_tattoo;
drakkin_details = m_pp.drakkin_details;
// we know our class now, so we might have to fix our consume timer!
if (class_ == MONK)
consume_food_timer.SetTimer(CONSUMPTION_MNK_TIMER);
InitInnates();
/* If GM not set in DB, and does not meet min status to be GM, reset */
if (m_pp.gm && admin < minStatusToBeGM)
m_pp.gm = 0;
@@ -2139,8 +2148,8 @@ void Client::Handle_OP_AdventureMerchantRequest(const EQApplicationPacket *app)
ss << item->ID << "|";
ss << item->LDoNPrice << "|";
ss << theme << "|";
ss << "0|";
ss << "1|";
ss << (item->Stackable ? 1 : 0) << "|";
ss << (item->LoreFlag ? 1 : 0) << "|";
ss << item->Races << "|";
ss << item->Classes;
count++;
@@ -2854,41 +2863,60 @@ void Client::Handle_OP_ApplyPoison(const EQApplicationPacket *app)
DumpPacket(app);
return;
}
uint32 ApplyPoisonSuccessResult = 0;
ApplyPoison_Struct* ApplyPoisonData = (ApplyPoison_Struct*)app->pBuffer;
const EQEmu::ItemInstance* PrimaryWeapon = GetInv().GetItem(EQEmu::inventory::slotPrimary);
const EQEmu::ItemInstance* SecondaryWeapon = GetInv().GetItem(EQEmu::inventory::slotSecondary);
const EQEmu::ItemInstance* PoisonItemInstance = GetInv()[ApplyPoisonData->inventorySlot];
const EQEmu::ItemData* poison=PoisonItemInstance->GetItem();
const EQEmu::ItemData* primary=nullptr;
const EQEmu::ItemData* secondary=nullptr;
bool IsPoison = PoisonItemInstance &&
(poison->ItemType == EQEmu::item::ItemTypePoison);
bool IsPoison = PoisonItemInstance && (PoisonItemInstance->GetItem()->ItemType == EQEmu::item::ItemTypePoison);
if (!IsPoison)
{
Log(Logs::Detail, Logs::Spells, "Item used to cast spell effect from a poison item was missing from inventory slot %d "
"after casting, or is not a poison!", ApplyPoisonData->inventorySlot);
Message(0, "Error: item not found for inventory slot #%i or is not a poison", ApplyPoisonData->inventorySlot);
if (PrimaryWeapon) {
primary=PrimaryWeapon->GetItem();
}
else if (GetClass() == ROGUE)
{
if ((PrimaryWeapon && PrimaryWeapon->GetItem()->ItemType == EQEmu::item::ItemType1HPiercing) ||
(SecondaryWeapon && SecondaryWeapon->GetItem()->ItemType == EQEmu::item::ItemType1HPiercing)) {
float SuccessChance = (GetSkill(EQEmu::skills::SkillApplyPoison) + GetLevel()) / 400.0f;
if (SecondaryWeapon) {
secondary=SecondaryWeapon->GetItem();
}
if (IsPoison && GetClass() == ROGUE) {
// Live always checks for skillup, even when poison is too high
CheckIncreaseSkill(EQEmu::skills::SkillApplyPoison, nullptr, 10);
if (poison->Proc.Level2 > GetLevel()) {
// Poison is too high to apply.
Message_StringID(clientMessageTradeskill, POISON_TOO_HIGH);
}
else if ((primary &&
primary->ItemType == EQEmu::item::ItemType1HPiercing) ||
(secondary &&
secondary->ItemType == EQEmu::item::ItemType1HPiercing)) {
double ChanceRoll = zone->random.Real(0, 1);
CheckIncreaseSkill(EQEmu::skills::SkillApplyPoison, nullptr, 10);
// Poisons that use this skill (old world poisons) almost
// never fail to apply. I did 25 applies of a trivial 120+
// poison with an apply skill of 48 and they all worked.
// Also did 25 straight poisons at apply skill 248 for very
// high end and they never failed.
// Apply poison ranging from 1-9, 28/30 worked for a level 18..
// Poisons that don't proc until a level higher than the
// rogue simply won't apply at all, no skill check done.
if (ChanceRoll < SuccessChance) {
if (ChanceRoll < (.9 + GetLevel()/1000)) {
ApplyPoisonSuccessResult = 1;
// NOTE: Someone may want to tweak the chance to proc the poison effect that is added to the weapon here.
// My thinking was that DEX should be apart of the calculation.
AddProcToWeapon(PoisonItemInstance->GetItem()->Proc.Effect, false, (GetDEX() / 100) + 103);
AddProcToWeapon(poison->Proc.Effect, false,
(GetDEX() / 100) + 103);
}
DeleteItemInInventory(ApplyPoisonData->inventorySlot, 1, true);
Log(Logs::General, Logs::None, "Chance to Apply Poison was %f. Roll was %f. Result is %u.", SuccessChance, ChanceRoll, ApplyPoisonSuccessResult);
}
// Live always deletes the item, success or failure. Even if too high.
DeleteItemInInventory(ApplyPoisonData->inventorySlot, 1, true);
}
auto outapp = new EQApplicationPacket(OP_ApplyPoison, nullptr, sizeof(ApplyPoison_Struct));
@@ -3953,12 +3981,23 @@ void Client::Handle_OP_BuffRemoveRequest(const EQApplicationPacket *app)
void Client::Handle_OP_Bug(const EQApplicationPacket *app)
{
if (app->size != sizeof(BugStruct))
printf("Wrong size of BugStruct got %d expected %zu!\n", app->size, sizeof(BugStruct));
else {
BugStruct* bug = (BugStruct*)app->pBuffer;
database.UpdateBug(bug);
if (!RuleB(Bugs, ReportingSystemActive)) {
Message(0, "Bug reporting is disabled on this server.");
return;
}
if (app->size != sizeof(BugReport_Struct)) {
printf("Wrong size of BugReport_Struct got %d expected %zu!\n", app->size, sizeof(BugReport_Struct));
}
else {
BugReport_Struct* bug_report = (BugReport_Struct*)app->pBuffer;
if (RuleB(Bugs, UseOldReportingMethod))
database.RegisterBug(bug_report);
else
database.RegisterBug(this, bug_report);
}
return;
}
@@ -4396,7 +4435,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app)
return;
}
auto boat_delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading);
auto boat_delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading));
boat->SetDelta(boat_delta);
auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
@@ -4406,7 +4445,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app)
safe_delete(outapp);
/* Update the boat's position on the server, without sending an update */
boat->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ19toFloat(ppu->heading), false);
boat->GMMove(ppu->x_pos, ppu->y_pos, ppu->z_pos, EQ12toFloat(ppu->heading), false);
return;
}
else return;
@@ -4551,7 +4590,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app)
}
/* Update internal state */
m_Delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, ppu->delta_heading);
m_Delta = glm::vec4(ppu->delta_x, ppu->delta_y, ppu->delta_z, EQ10toFloat(ppu->delta_heading));
if (IsTracking() && ((m_Position.x != ppu->x_pos) || (m_Position.y != ppu->y_pos))) {
if (zone->random.Real(0, 100) < 70)//should be good
@@ -4604,7 +4643,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app)
}
}
float new_heading = EQ19toFloat(ppu->heading);
float new_heading = EQ12toFloat(ppu->heading);
int32 new_animation = ppu->animation;
/* Update internal server position from what the client has sent */
@@ -4623,7 +4662,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app)
if (is_client_moving || new_heading != m_Position.w || new_animation != animation) {
animation = ppu->animation;
m_Position.w = EQ19toFloat(ppu->heading);
m_Position.w = EQ12toFloat(ppu->heading);
/* Broadcast update to other clients */
auto outapp = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
@@ -4795,7 +4834,6 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app)
mod_consider(tmob, con);
QueuePacket(outapp);
safe_delete(outapp);
// only wanted to check raid target once
// and need con to still be around so, do it here!
if (tmob->IsRaidTarget()) {
@@ -4842,6 +4880,8 @@ void Client::Handle_OP_Consider(const EQApplicationPacket *app)
else if ((invisible || invisible_undead || hidden || invisible_animals) && !IsInvisible(tmob))
Message_StringID(10, SUSPECT_SEES_YOU);
safe_delete(outapp);
return;
}
@@ -5324,31 +5364,44 @@ void Client::Handle_OP_DisarmTraps(const EQApplicationPacket *app)
p_timers.Start(pTimerDisarmTraps, reuse - 1);
Trap* trap = entity_list.FindNearbyTrap(this, 60);
uint8 success = SKILLUP_FAILURE;
float curdist = 0;
Trap* trap = entity_list.FindNearbyTrap(this, 250, curdist, true);
if (trap && trap->detected)
{
int uskill = GetSkill(EQEmu::skills::SkillDisarmTraps);
if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill))
float max_radius = (trap->radius * 2) * (trap->radius * 2); // radius is used to trigger trap, so disarm radius should be a bit bigger.
Log(Logs::General, Logs::Traps, "%s is attempting to disarm trap %d. Curdist is %0.2f maxdist is %0.2f", GetName(), trap->trap_id, curdist, max_radius);
if (curdist <= max_radius)
{
Message(MT_Skills, "You disarm a trap.");
trap->disarmed = true;
trap->chkarea_timer.Disable();
trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000);
int uskill = GetSkill(EQEmu::skills::SkillDisarmTraps);
if ((zone->random.Int(0, 49) + uskill) >= (zone->random.Int(0, 49) + trap->skill))
{
success = SKILLUP_SUCCESS;
Message_StringID(MT_Skills, DISARMED_TRAP);
trap->disarmed = true;
Log(Logs::General, Logs::Traps, "Trap %d is disarmed.", trap->trap_id);
trap->UpdateTrap();
}
else
{
Message_StringID(MT_Skills, FAIL_DISARM_DETECTED_TRAP);
if (zone->random.Int(0, 99) < 25) {
trap->Trigger(this);
}
}
CheckIncreaseSkill(EQEmu::skills::SkillDisarmTraps, nullptr);
return;
}
else
{
if (zone->random.Int(0, 99) < 25) {
Message(MT_Skills, "You set off the trap while trying to disarm it!");
trap->Trigger(this);
}
else {
Message(MT_Skills, "You failed to disarm a trap.");
}
Message_StringID(MT_Skills, TRAP_TOO_FAR);
}
CheckIncreaseSkill(EQEmu::skills::SkillDisarmTraps, nullptr);
return;
}
Message(MT_Skills, "You did not find any traps close enough to disarm.");
else
{
Message_StringID(MT_Skills, LDON_SENSE_TRAP2);
}
return;
}
@@ -9820,6 +9873,11 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app)
return;
}
void Client::Handle_OP_MoveMultipleItems(const EQApplicationPacket *app)
{
Kick(); // TODO: lets not desync though
}
void Client::Handle_OP_OpenContainer(const EQApplicationPacket *app)
{
// Does not exist in Ti client
@@ -9919,22 +9977,18 @@ void Client::Handle_OP_PetCommands(const EQApplicationPacket *app)
Mob* mypet = this->GetPet();
Mob *target = entity_list.GetMob(pet->target);
if (!mypet || pet->command == PET_LEADER)
{
if (pet->command == PET_LEADER)
{
if (mypet && (!GetTarget() || GetTarget() == mypet))
{
if (!mypet || pet->command == PET_LEADER) {
if (pet->command == PET_LEADER) {
// we either send the ID of an NPC we're interested in or no ID for our own pet
if (target) {
auto owner = target->GetOwner();
if (owner)
target->Say_StringID(PET_LEADERIS, owner->GetCleanName());
else
target->Say_StringID(I_FOLLOW_NOONE);
} else if (mypet) {
mypet->Say_StringID(PET_LEADERIS, GetName());
}
else if ((mypet = GetTarget()))
{
Mob *Owner = mypet->GetOwner();
if (Owner)
mypet->Say_StringID(PET_LEADERIS, Owner->GetCleanName());
else if (mypet->IsNPC())
mypet->Say_StringID(I_FOLLOW_NOONE);
}
}
return;
@@ -10516,11 +10570,8 @@ void Client::Handle_OP_Petition(const EQApplicationPacket *app)
void Client::Handle_OP_PetitionBug(const EQApplicationPacket *app)
{
if (app->size != sizeof(PetitionBug_Struct))
printf("Wrong size of BugStruct! Expected: %zu, Got: %i\n", sizeof(PetitionBug_Struct), app->size);
else {
Message(0, "Petition Bugs are not supported, please use /bug.");
}
Message(0, "Petition Bugs are not supported, please use /bug.");
return;
}
@@ -10940,6 +10991,84 @@ void Client::Handle_OP_PVPLeaderBoardRequest(const EQApplicationPacket *app)
safe_delete(outapp);
}
void Client::Handle_OP_QueryUCSServerStatus(const EQApplicationPacket *app)
{
if (zone->IsUCSServerAvailable()) {
EQApplicationPacket* outapp = nullptr;
std::string buffer;
std::string MailKey = database.GetMailKey(CharacterID(), true);
EQEmu::versions::UCSVersion ConnectionType = EQEmu::versions::ucsUnknown;
// chat server packet
switch (ClientVersion()) {
case EQEmu::versions::ClientVersion::Titanium:
ConnectionType = EQEmu::versions::ucsTitaniumChat;
break;
case EQEmu::versions::ClientVersion::SoF:
ConnectionType = EQEmu::versions::ucsSoFCombined;
break;
case EQEmu::versions::ClientVersion::SoD:
ConnectionType = EQEmu::versions::ucsSoDCombined;
break;
case EQEmu::versions::ClientVersion::UF:
ConnectionType = EQEmu::versions::ucsUFCombined;
break;
case EQEmu::versions::ClientVersion::RoF:
ConnectionType = EQEmu::versions::ucsRoFCombined;
break;
case EQEmu::versions::ClientVersion::RoF2:
ConnectionType = EQEmu::versions::ucsRoF2Combined;
break;
default:
ConnectionType = EQEmu::versions::ucsUnknown;
break;
}
buffer = StringFormat("%s,%i,%s.%s,%c%s",
Config->ChatHost.c_str(),
Config->ChatPort,
Config->ShortName.c_str(),
GetName(),
ConnectionType,
MailKey.c_str()
);
outapp = new EQApplicationPacket(OP_SetChatServer, (buffer.length() + 1));
memcpy(outapp->pBuffer, buffer.c_str(), buffer.length());
outapp->pBuffer[buffer.length()] = '\0';
QueuePacket(outapp);
safe_delete(outapp);
// mail server packet
switch (ClientVersion()) {
case EQEmu::versions::ClientVersion::Titanium:
ConnectionType = EQEmu::versions::ucsTitaniumMail;
break;
default:
// retain value from previous switch
break;
}
buffer = StringFormat("%s,%i,%s.%s,%c%s",
Config->MailHost.c_str(),
Config->MailPort,
Config->ShortName.c_str(),
GetName(),
ConnectionType,
MailKey.c_str()
);
outapp = new EQApplicationPacket(OP_SetChatServer2, (buffer.length() + 1));
memcpy(outapp->pBuffer, buffer.c_str(), buffer.length());
outapp->pBuffer[buffer.length()] = '\0';
QueuePacket(outapp);
safe_delete(outapp);
}
}
void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app)
{
if (app->size < sizeof(RaidGeneral_Struct)) {
@@ -11458,7 +11587,7 @@ void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app)
Client *client_moved = entity_list.GetClientByName(raid_command_packet->leader_name);
if (client_moved) {
if (client_moved && client_moved->GetRaid()) {
client_moved->GetRaid()->SendHPManaEndPacketsTo(client_moved);
client_moved->GetRaid()->SendHPManaEndPacketsFrom(client_moved);
@@ -11649,10 +11778,24 @@ void Client::Handle_OP_RecipesFavorite(const EQApplicationPacket *app)
// make where clause segment for container(s)
std::string containers;
if (tsf->some_id == 0)
uint32 combineObjectSlots;
if (tsf->some_id == 0) {
containers += StringFormat(" = %u ", tsf->object_type); // world combiner so no item number
else
combineObjectSlots = 10;
}
else {
containers += StringFormat(" in (%u, %u) ", tsf->object_type, tsf->some_id); // container in inventory
auto item = database.GetItem(tsf->some_id);
if (!item)
{
Log(Logs::General, Logs::Error, "Invalid container ID: %d. GetItem returned null. Defaulting to BagSlots = 10.\n", tsf->some_id);
combineObjectSlots = 10;
}
else
{
combineObjectSlots = item->BagSlots;
}
}
std::string favoriteIDs; //gotta be big enough for 500 IDs
bool first = true;
@@ -11684,8 +11827,8 @@ void Client::Handle_OP_RecipesFavorite(const EQApplicationPacket *app)
"((tr.must_learn & 0x3 <> 0 AND crl.madecount IS NOT NULL) "
"OR (tr.must_learn & 0x3 = 0)) "
"GROUP BY tr.id "
"HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
"LIMIT 100 ", CharacterID(), favoriteIDs.c_str(), containers.c_str());
"HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 AND SUM(tre.componentcount) <= %u "
"LIMIT 100 ", CharacterID(), favoriteIDs.c_str(), containers.c_str(), combineObjectSlots);
TradeskillSearchResults(query, tsf->object_type, tsf->some_id);
return;
@@ -11707,13 +11850,25 @@ void Client::Handle_OP_RecipesSearch(const EQApplicationPacket *app)
// make where clause segment for container(s)
char containers[30];
uint32 combineObjectSlots;
if (rss->some_id == 0) {
// world combiner so no item number
snprintf(containers, 29, "= %u", rss->object_type);
combineObjectSlots = 10;
}
else {
// container in inventory
snprintf(containers, 29, "in (%u,%u)", rss->object_type, rss->some_id);
auto item = database.GetItem(rss->some_id);
if (!item)
{
Log(Logs::General, Logs::Error, "Invalid container ID: %d. GetItem returned null. Defaulting to BagSlots = 10.\n", rss->some_id);
combineObjectSlots = 10;
}
else
{
combineObjectSlots = item->BagSlots;
}
}
std::string searchClause;
@@ -11738,10 +11893,10 @@ void Client::Handle_OP_RecipesSearch(const EQApplicationPacket *app)
"AND crl.madecount IS NOT NULL) "
"OR (tr.must_learn & 0x3 = 0)) "
"GROUP BY tr.id "
"HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 "
"HAVING sum(if(tre.item_id %s AND tre.iscontainer > 0,1,0)) > 0 AND SUM(tre.componentcount) <= %u "
"LIMIT 200 ",
CharacterID(), searchClause.c_str(),
rss->mintrivial, rss->maxtrivial, containers);
rss->mintrivial, rss->maxtrivial, containers, combineObjectSlots);
TradeskillSearchResults(query, rss->object_type, rss->some_id);
return;
}
@@ -12063,15 +12218,6 @@ void Client::Handle_OP_SenseHeading(const EQApplicationPacket *app)
int chancemod = 0;
// The client seems to limit sense heading packets based on skill
// level. So if we're really low, we don't hit this routine very often.
// I think it's the GUI deciding when to skill you up.
// So, I'm adding a mod here which is larger at lower levels so
// very low levels get a much better chance to skill up when the GUI
// eventually sends a message.
if (GetLevel() <= 8)
chancemod += (9 - level) * 10;
CheckIncreaseSkill(EQEmu::skills::SkillSenseHeading, nullptr, chancemod);
return;
@@ -12094,7 +12240,8 @@ void Client::Handle_OP_SenseTraps(const EQApplicationPacket *app)
p_timers.Start(pTimerSenseTraps, reuse - 1);
Trap* trap = entity_list.FindNearbyTrap(this, 800);
float trap_curdist = 0;
Trap* trap = entity_list.FindNearbyTrap(this, 800, trap_curdist);
CheckIncreaseSkill(EQEmu::skills::SkillSenseTraps, nullptr);
@@ -13034,6 +13181,8 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
InterruptSpell();
SetFeigned(false);
BindWound(this, false, true);
tmSitting = Timer::GetCurrentTime();
BuffFadeBySitModifier();
}
else if (sa->parameter == ANIM_CROUCH) {
if (!UseBardSpellLogic())
+2
View File
@@ -204,6 +204,7 @@
void Handle_OP_MercenaryTimerRequest(const EQApplicationPacket *app);
void Handle_OP_MoveCoin(const EQApplicationPacket *app);
void Handle_OP_MoveItem(const EQApplicationPacket *app);
void Handle_OP_MoveMultipleItems(const EQApplicationPacket *app);
void Handle_OP_OpenContainer(const EQApplicationPacket *app);
void Handle_OP_OpenGuildTributeMaster(const EQApplicationPacket *app);
void Handle_OP_OpenInventory(const EQApplicationPacket *app);
@@ -227,6 +228,7 @@
void Handle_OP_PurchaseLeadershipAA(const EQApplicationPacket *app);
void Handle_OP_PVPLeaderBoardDetailsRequest(const EQApplicationPacket *app);
void Handle_OP_PVPLeaderBoardRequest(const EQApplicationPacket *app);
void Handle_OP_QueryUCSServerStatus(const EQApplicationPacket *app);
void Handle_OP_RaidCommand(const EQApplicationPacket *app);
void Handle_OP_RandomReq(const EQApplicationPacket *app);
void Handle_OP_ReadBook(const EQApplicationPacket *app);
+77 -82
View File
@@ -264,13 +264,21 @@ bool Client::Process() {
if (distance <= scan_range) {
close_mobs.insert(std::pair<Mob *, float>(mob, distance));
}
else if (mob->GetAggroRange() > scan_range) {
else if ((mob->GetAggroRange() * mob->GetAggroRange()) > scan_range) {
close_mobs.insert(std::pair<Mob *, float>(mob, distance));
}
}
if (force_spawn_updates && mob != this && distance <= client_update_range)
mob->SendPositionUpdateToClient(this);
if (force_spawn_updates && mob != this) {
if (mob->is_distance_roamer) {
mob->SendPositionUpdateToClient(this);
continue;
}
if (distance <= client_update_range)
mob->SendPositionUpdateToClient(this);
}
}
}
@@ -524,10 +532,9 @@ bool Client::Process() {
DoEnduranceUpkeep();
}
if (consume_food_timer.Check()) {
m_pp.hunger_level = m_pp.hunger_level - 1;
m_pp.thirst_level = m_pp.thirst_level - 1;
}
// this is independent of the tick timer
if (consume_food_timer.Check())
DoStaminaHungerUpdate();
if (tic_timer.Check() && !dead) {
CalcMaxHP();
@@ -539,7 +546,6 @@ bool Client::Process() {
DoManaRegen();
DoEnduranceRegen();
BuffProcess();
DoStaminaHungerUpdate();
if (tribute_timer.Check()) {
ToggleTribute(true); //re-activate the tribute.
@@ -648,17 +654,17 @@ bool Client::Process() {
{
//client logged out or errored out
//ResetTrade();
if (client_state != CLIENT_KICKED && !zoning && !instalog) {
if (client_state != CLIENT_KICKED && !bZoning && !instalog) {
Save();
}
client_state = CLIENT_LINKDEAD;
if (zoning || instalog || GetGM())
if (bZoning || instalog || GetGM())
{
Group *mygroup = GetGroup();
if (mygroup)
{
if (!zoning)
if (!bZoning)
{
entity_list.MessageGroup(this, true, 15, "%s logged out.", GetName());
LeaveGroup();
@@ -677,7 +683,7 @@ bool Client::Process() {
Raid *myraid = entity_list.GetRaidByClient(this);
if (myraid)
{
if (!zoning)
if (!bZoning)
{
//entity_list.MessageGroup(this,true,15,"%s logged out.",GetName());
myraid->MemberZoned(this);
@@ -880,7 +886,7 @@ void Client::BulkSendMerchantInventory(int merchant_id, int npcid) {
uint8 handychance = 0;
for (itr = merlist.begin(); itr != merlist.end() && i <= numItemSlots; ++itr) {
MerchantList ml = *itr;
if (merch->CastToNPC()->GetMerchantProbability() > ml.probability)
if (ml.probability != 100 && zone->random.Int(1, 100) > ml.probability)
continue;
if (GetLevel() < ml.level_required)
@@ -1056,7 +1062,8 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I
SetMana(0);
SetHP(GetMaxHP()/5);
int rez_eff = 756;
if (GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE)
if (RuleB(Character, UseOldRaceRezEffects) &&
(GetRace() == BARBARIAN || GetRace() == DWARF || GetRace() == TROLL || GetRace() == OGRE))
rez_eff = 757;
SpellOnTarget(rez_eff, this); // Rezz effects
}
@@ -1827,7 +1834,7 @@ void Client::OPGMSummon(const EQApplicationPacket *app)
}
void Client::DoHPRegen() {
SetHP(GetHP() + CalcHPRegen() + RestRegenHP);
SetHP(GetHP() + CalcHPRegen());
SendHPUpdate();
}
@@ -1835,46 +1842,55 @@ void Client::DoManaRegen() {
if (GetMana() >= max_mana && spellbonuses.ManaRegen >= 0)
return;
SetMana(GetMana() + CalcManaRegen() + RestRegenMana);
if (GetMana() < max_mana && (IsSitting() || CanMedOnHorse()) && HasSkill(EQEmu::skills::SkillMeditate))
CheckIncreaseSkill(EQEmu::skills::SkillMeditate, nullptr, -5);
SetMana(GetMana() + CalcManaRegen());
CheckManaEndUpdate();
}
void Client::DoStaminaHungerUpdate() {
if(!stamina_timer.Check())
return;
void Client::DoStaminaHungerUpdate()
{
auto outapp = new EQApplicationPacket(OP_Stamina, sizeof(Stamina_Struct));
Stamina_Struct* sta = (Stamina_Struct*)outapp->pBuffer;
Stamina_Struct *sta = (Stamina_Struct *)outapp->pBuffer;
Log(Logs::General, Logs::Food, "Client::DoStaminaHungerUpdate() hunger_level: %i thirst_level: %i before loss", m_pp.hunger_level, m_pp.thirst_level);
Log(Logs::General, Logs::Food, "Client::DoStaminaHungerUpdate() hunger_level: %i thirst_level: %i before loss",
m_pp.hunger_level, m_pp.thirst_level);
if (zone->GetZoneID() != 151) {
sta->food = m_pp.hunger_level > 6000 ? 6000 : m_pp.hunger_level;
sta->water = m_pp.thirst_level > 6000 ? 6000 : m_pp.thirst_level;
}
else {
if (zone->GetZoneID() != 151 && !GetGM()) {
int loss = RuleI(Character, FoodLossPerUpdate);
if (GetHorseId() != 0)
loss *= 3;
m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level - loss, 0, 6000);
m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level - loss, 0, 6000);
if (spellbonuses.hunger) {
m_pp.hunger_level = EQEmu::ClampLower(m_pp.hunger_level, 3500);
m_pp.thirst_level = EQEmu::ClampLower(m_pp.thirst_level, 3500);
}
sta->food = m_pp.hunger_level;
sta->water = m_pp.thirst_level;
} else {
// No auto food/drink consumption in the Bazaar
sta->food = 6000;
sta->water = 6000;
}
Log(Logs::General, Logs::Food,
"Client::DoStaminaHungerUpdate() Current hunger_level: %i = (%i minutes left) thirst_level: %i = (%i minutes left) - after loss",
m_pp.hunger_level,
m_pp.hunger_level,
m_pp.thirst_level,
m_pp.thirst_level
);
Log(Logs::General, Logs::Food,
"Client::DoStaminaHungerUpdate() Current hunger_level: %i = (%i minutes left) thirst_level: %i = (%i "
"minutes left) - after loss",
m_pp.hunger_level, m_pp.hunger_level, m_pp.thirst_level, m_pp.thirst_level);
FastQueuePacket(&outapp);
}
void Client::DoEnduranceRegen()
{
if(GetEndurance() >= GetMaxEndurance())
return;
// endurance has some negative mods that could result in a negative regen when starved
int regen = CalcEnduranceRegen();
SetEndurance(GetEndurance() + CalcEnduranceRegen() + RestRegenEndurance);
if (regen < 0 || (regen > 0 && GetEndurance() < GetMaxEndurance()))
SetEndurance(GetEndurance() + regen);
}
void Client::DoEnduranceUpkeep() {
@@ -1923,12 +1939,12 @@ void Client::CalcRestState() {
// The client must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds,
// must be sitting down, and must not have any detrimental spells affecting them.
//
if(!RuleI(Character, RestRegenPercent))
if(!RuleB(Character, RestRegenEnabled))
return;
RestRegenHP = RestRegenMana = RestRegenEndurance = 0;
ooc_regen = false;
if(AggroCount || !IsSitting())
if(AggroCount || !(IsSitting() || CanMedOnHorse()))
return;
if(!rest_timer.Check(false))
@@ -1943,67 +1959,46 @@ void Client::CalcRestState() {
}
}
RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100);
ooc_regen = true;
RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100);
if(RuleB(Character, RestRegenEndurance))
RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100);
}
void Client::DoTracking()
{
if(TrackingID == 0)
if (TrackingID == 0)
return;
Mob *m = entity_list.GetMob(TrackingID);
if(!m || m->IsCorpse())
{
if (!m || m->IsCorpse()) {
Message_StringID(MT_Skills, TRACK_LOST_TARGET);
TrackingID = 0;
return;
}
float RelativeHeading = GetHeading() - CalculateHeadingToTarget(m->GetX(), m->GetY());
if(RelativeHeading < 0)
RelativeHeading += 256;
if (RelativeHeading < 0)
RelativeHeading += 512;
if((RelativeHeading <= 16) || (RelativeHeading >= 240))
{
if (RelativeHeading > 480)
Message_StringID(MT_Skills, TRACK_STRAIGHT_AHEAD, m->GetCleanName());
}
else if((RelativeHeading > 16) && (RelativeHeading <= 48))
{
Message_StringID(MT_Skills, TRACK_AHEAD_AND_TO, m->GetCleanName(), "right");
}
else if((RelativeHeading > 48) && (RelativeHeading <= 80))
{
Message_StringID(MT_Skills, TRACK_TO_THE, m->GetCleanName(), "right");
}
else if((RelativeHeading > 80) && (RelativeHeading <= 112))
{
Message_StringID(MT_Skills, TRACK_BEHIND_AND_TO, m->GetCleanName(), "right");
}
else if((RelativeHeading > 112) && (RelativeHeading <= 144))
{
Message_StringID(MT_Skills, TRACK_BEHIND_YOU, m->GetCleanName());
}
else if((RelativeHeading > 144) && (RelativeHeading <= 176))
{
Message_StringID(MT_Skills, TRACK_BEHIND_AND_TO, m->GetCleanName(), "left");
}
else if((RelativeHeading > 176) && (RelativeHeading <= 208))
{
Message_StringID(MT_Skills, TRACK_TO_THE, m->GetCleanName(), "left");
}
else if((RelativeHeading > 208) && (RelativeHeading < 240))
{
else if (RelativeHeading > 416)
Message_StringID(MT_Skills, TRACK_AHEAD_AND_TO, m->GetCleanName(), "left");
}
else if (RelativeHeading > 352)
Message_StringID(MT_Skills, TRACK_TO_THE, m->GetCleanName(), "left");
else if (RelativeHeading > 288)
Message_StringID(MT_Skills, TRACK_BEHIND_AND_TO, m->GetCleanName(), "left");
else if (RelativeHeading > 224)
Message_StringID(MT_Skills, TRACK_BEHIND_YOU, m->GetCleanName());
else if (RelativeHeading > 160)
Message_StringID(MT_Skills, TRACK_BEHIND_AND_TO, m->GetCleanName(), "right");
else if (RelativeHeading > 96)
Message_StringID(MT_Skills, TRACK_TO_THE, m->GetCleanName(), "right");
else if (RelativeHeading > 32)
Message_StringID(MT_Skills, TRACK_AHEAD_AND_TO, m->GetCleanName(), "right");
else if (RelativeHeading >= 0)
Message_StringID(MT_Skills, TRACK_STRAIGHT_AHEAD, m->GetCleanName());
}
void Client::HandleRespawnFromHover(uint32 Option)
+506 -231
View File
@@ -66,10 +66,12 @@
#include "titles.h"
#include "water_map.h"
#include "worldserver.h"
#include "fastmath.h"
extern QueryServ* QServ;
extern WorldServer worldserver;
extern TaskManager *taskmanager;
extern FastMath g_Math;
void CatchSignal(int sig_num);
@@ -291,7 +293,7 @@ int command_init(void)
#endif
command_add("path", "- view and edit pathing", 200, command_path) ||
command_add("peekinv", "[worn/inv/cursor/trib/bank/trade/world/all] - Print out contents of your player target's inventory", 100, command_peekinv) ||
command_add("peekinv", "[equip/gen/cursor/poss/limbo/curlim/trib/bank/shbank/allbank/trade/world/all] - Print out contents of your player target's inventory", 100, command_peekinv) ||
command_add("peqzone", "[zonename] - Go to specified zone, if you have > 75% health", 0, command_peqzone) ||
command_add("permaclass", "[classnum] - Change your or your player target's class (target is disconnected)", 80, command_permaclass) ||
command_add("permagender", "[gendernum] - Change your or your player target's gender (zone to take effect)", 80, command_permagender) ||
@@ -305,6 +307,7 @@ int command_init(void)
command_add("profilereset", "- Reset profiling info", 250, command_profilereset) ||
#endif
command_add("push", "Lets you do spell push", 150, command_push) ||
command_add("pvp", "[on/off] - Set your or your player target's PVP status", 100, command_pvp) ||
command_add("qglobal", "[on/off/view] - Toggles qglobal functionality on an NPC", 100, command_qglobal) ||
command_add("questerrors", "Shows quest errors.", 100, command_questerrors) ||
@@ -316,10 +319,12 @@ int command_init(void)
command_add("reloadallrules", "Executes a reload of all rules.", 80, command_reloadallrules) ||
command_add("reloademote", "Reloads NPC Emotes", 80, command_reloademote) ||
command_add("reloadlevelmods", nullptr, 255, command_reloadlevelmods) ||
command_add("reloadmerchants", nullptr, 255, command_reloadmerchants) ||
command_add("reloadperlexportsettings", nullptr, 255, command_reloadperlexportsettings) ||
command_add("reloadqst", " - Clear quest cache (any argument causes it to also stop all timers)", 150, command_reloadqst) ||
command_add("reloadrulesworld", "Executes a reload of all rules in world specifically.", 80, command_reloadworldrules) ||
command_add("reloadstatic", "- Reload Static Zone Data", 150, command_reloadstatic) ||
command_add("reloadtraps", "- Repops all traps in the current zone.", 80, command_reloadtraps) ||
command_add("reloadtitles", "- Reload player titles from the database", 150, command_reloadtitles) ||
command_add("reloadworld", "[0|1] - Clear quest cache (0 - no repop, 1 - repop)", 255, command_reloadworld) ||
command_add("reloadzps", "- Reload zone points from database", 150, command_reloadzps) ||
@@ -355,9 +360,11 @@ int command_init(void)
command_add("showbonusstats", "[item|spell|all] Shows bonus stats for target from items or spells. Shows both by default.", 50, command_showbonusstats) ||
command_add("showbuffs", "- List buffs active on your target or you if no target", 50, command_showbuffs) ||
command_add("shownumhits", "Shows buffs numhits for yourself.", 0, command_shownumhits) ||
command_add("shownpcgloballoot", "Show GlobalLoot entires on this npc", 50, command_shownpcgloballoot) ||
command_add("showskills", "- Show the values of your or your player target's skills", 50, command_showskills) ||
command_add("showspellslist", "Shows spell list of targeted NPC", 100, command_showspellslist) ||
command_add("showstats", "- Show details about you or your target", 50, command_showstats) ||
command_add("showzonegloballoot", "Show GlobalLoot entires on this zone", 50, command_showzonegloballoot) ||
command_add("shutdown", "- Shut this zone process down", 150, command_shutdown) ||
command_add("size", "[size] - Change size of you or your target", 50, command_size) ||
command_add("spawn", "[name] [race] [level] [material] [hp] [gender] [class] [priweapon] [secweapon] [merchantid] - Spawn an NPC", 10, command_spawn) ||
@@ -374,6 +381,7 @@ int command_init(void)
command_add("task", "(subcommand) - Task system commands", 150, command_task) ||
command_add("tattoo", "- Change the tattoo of your target (Drakkin Only)", 80, command_tattoo) ||
command_add("tempname", "[newname] - Temporarily renames your target. Leave name blank to restore the original name.", 100, command_tempname) ||
command_add("petname", "[newname] - Temporarily renames your pet. Leave name blank to restore the original name.", 100, command_petname) ||
command_add("texture", "[texture] [helmtexture] - Change your or your target's appearance, use 255 to show equipment", 10, command_texture) ||
command_add("time", "[HH] [MM] - Set EQ time", 90, command_time) ||
command_add("timers", "- Display persistent timers for target", 200, command_timers) ||
@@ -381,7 +389,9 @@ int command_init(void)
command_add("title", "[text] [1 = create title table row] - Set your or your player target's title", 50, command_title) ||
command_add("titlesuffix", "[text] [1 = create title table row] - Set your or your player target's title suffix", 50, command_titlesuffix) ||
command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", 150, command_traindisc) ||
command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", 81, command_trapinfo) ||
command_add("tune", "Calculate ideal statical values related to combat.", 100, command_tune) ||
command_add("ucs", "- Attempts to reconnect to the UCS server", 0, command_ucs) ||
command_add("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) ||
command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) ||
command_add("unlock", "- Unlock the worldserver", 150, command_unlock) ||
@@ -2315,14 +2325,18 @@ void command_race(Client *c, const Seperator *sep)
{
Mob *t=c->CastToMob();
// Need to figure out max race for LoY/LDoN: going with upper bound of 500 now for testing
if (sep->IsNumber(1) && atoi(sep->arg[1]) >= 0 && atoi(sep->arg[1]) <= 2259) {
if ((c->GetTarget()) && c->Admin() >= commandRaceOthers)
t=c->GetTarget();
t->SendIllusionPacket(atoi(sep->arg[1]));
if (sep->IsNumber(1)) {
auto race = atoi(sep->arg[1]);
if ((race >= 0 && race <= 732) || (race >= 2253 && race <= 2259)) {
if ((c->GetTarget()) && c->Admin() >= commandRaceOthers)
t = c->GetTarget();
t->SendIllusionPacket(race);
} else {
c->Message(0, "Usage: #race [0-732, 2253-2259] (0 for back to normal)");
}
} else {
c->Message(0, "Usage: #race [0-732, 2253-2259] (0 for back to normal)");
}
else
c->Message(0, "Usage: #race [0-2259] (0 for back to normal)");
}
void command_gender(Client *c, const Seperator *sep)
@@ -2453,7 +2467,9 @@ void command_npctypespawn(Client *c, const Seperator *sep)
if (npc && sep->IsNumber(2))
npc->SetNPCFactionID(atoi(sep->arg[2]));
npc->AddLootTable();
npc->AddLootTable();
if (npc->DropsGlobalLoot())
npc->CheckGlobalLootTables();
entity_list.AddNPC(npc);
}
else
@@ -2503,244 +2519,347 @@ void command_nukeitem(Client *c, const Seperator *sep)
void command_peekinv(Client *c, const Seperator *sep)
{
// this can be cleaned up once inventory is cleaned up
enum {
peekWorn = 0x01,
peekInv = 0x02,
peekCursor = 0x04,
peekTrib = 0x08,
peekBank = 0x10,
peekTrade = 0x20,
peekWorld = 0x40
} ;
peekNone = 0x0000,
peekEquip = 0x0001,
peekGen = 0x0002,
peekCursor = 0x0004,
peekLimbo = 0x0008,
peekTrib = 0x0010,
peekBank = 0x0020,
peekShBank = 0x0040,
peekTrade = 0x0080,
peekWorld = 0x0100,
peekOutOfScope = (peekWorld * 2) // less than
};
if (!c->GetTarget() || !c->GetTarget()->IsClient()) {
c->Message(0, "You must have a PC target selected for this command");
static char* scope_prefix[] = { "Equip", "Gen", "Cursor", "Limbo", "Trib", "Bank", "ShBank", "Trade", "World" };
static int16 scope_range[][2] = {
{ EQEmu::legacy::EQUIPMENT_BEGIN, EQEmu::legacy::EQUIPMENT_END },
{ EQEmu::legacy::GENERAL_BEGIN, EQEmu::legacy::GENERAL_END },
{ EQEmu::legacy::SLOT_CURSOR, EQEmu::legacy::SLOT_CURSOR },
{ EQEmu::legacy::SLOT_INVALID, EQEmu::legacy::SLOT_INVALID },
{ EQEmu::legacy::TRIBUTE_BEGIN, EQEmu::legacy::TRIBUTE_END },
{ EQEmu::legacy::BANK_BEGIN, EQEmu::legacy::BANK_END },
{ EQEmu::legacy::SHARED_BANK_BEGIN, EQEmu::legacy::SHARED_BANK_END },
{ EQEmu::legacy::TRADE_BEGIN, EQEmu::legacy::TRADE_END },
{ EQEmu::inventory::slotBegin, (EQEmu::legacy::WORLD_SIZE - 1) }
};
static bool scope_bag[] = { false, true, true, true, false, true, true, true, true };
if (!c)
return;
if (c->GetTarget() && !c->GetTarget()->IsClient()) {
c->Message(0, "You must target a PC for this command.");
return;
}
int scopeWhere = 0;
int scopeMask = peekNone;
if (strcasecmp(sep->arg[1], "all") == 0) { scopeWhere = ~0; }
else if (strcasecmp(sep->arg[1], "worn") == 0) { scopeWhere |= peekWorn; }
else if (strcasecmp(sep->arg[1], "inv") == 0) { scopeWhere |= peekInv; }
else if (strcasecmp(sep->arg[1], "cursor") == 0) { scopeWhere |= peekCursor; }
else if (strcasecmp(sep->arg[1], "trib") == 0) { scopeWhere |= peekTrib; }
else if (strcasecmp(sep->arg[1], "bank") == 0) { scopeWhere |= peekBank; }
else if (strcasecmp(sep->arg[1], "trade") == 0) { scopeWhere |= peekTrade; }
else if (strcasecmp(sep->arg[1], "world") == 0) { scopeWhere |= peekWorld; }
if (strcasecmp(sep->arg[1], "all") == 0) { scopeMask = (peekOutOfScope - 1); }
else if (strcasecmp(sep->arg[1], "equip") == 0) { scopeMask |= peekEquip; }
else if (strcasecmp(sep->arg[1], "gen") == 0) { scopeMask |= peekGen; }
else if (strcasecmp(sep->arg[1], "cursor") == 0) { scopeMask |= peekCursor; }
else if (strcasecmp(sep->arg[1], "poss") == 0) { scopeMask |= (peekEquip | peekGen | peekCursor); }
else if (strcasecmp(sep->arg[1], "limbo") == 0) { scopeMask |= peekLimbo; }
else if (strcasecmp(sep->arg[1], "curlim") == 0) { scopeMask |= (peekCursor | peekLimbo); }
else if (strcasecmp(sep->arg[1], "trib") == 0) { scopeMask |= peekTrib; }
else if (strcasecmp(sep->arg[1], "bank") == 0) { scopeMask |= peekBank; }
else if (strcasecmp(sep->arg[1], "shbank") == 0) { scopeMask |= peekShBank; }
else if (strcasecmp(sep->arg[1], "allbank") == 0) { scopeMask |= (peekBank | peekShBank); }
else if (strcasecmp(sep->arg[1], "trade") == 0) { scopeMask |= peekTrade; }
else if (strcasecmp(sep->arg[1], "world") == 0) { scopeMask |= peekWorld; }
if (scopeWhere == 0) {
c->Message(0, "Usage: #peekinv [worn|inv|cursor|trib|bank|trade|world|all]");
c->Message(0, " Displays a portion of the targeted user's inventory");
c->Message(0, " Caution: 'all' is a lot of information!");
if (!scopeMask) {
c->Message(0, "Usage: #peekinv [equip|gen|cursor|poss|limbo|curlim|trib|bank|shbank|allbank|trade|world|all]");
c->Message(0, "- Displays a portion of the targeted user's inventory");
c->Message(0, "- Caution: 'all' is a lot of information!");
return;
}
Client* targetClient = c->GetTarget()->CastToClient();
Client* targetClient = c;
if (c->GetTarget())
targetClient = c->GetTarget()->CastToClient();
const EQEmu::ItemInstance* inst_main = nullptr;
const EQEmu::ItemInstance* inst_sub = nullptr;
const EQEmu::ItemInstance* inst_aug = nullptr;
const EQEmu::ItemData* item_data = nullptr;
std::string item_link;
EQEmu::SayLinkEngine linker;
linker.SetLinkType(EQEmu::saylink::SayLinkItemInst);
c->Message(0, "Displaying inventory for %s...", targetClient->GetName());
c->Message(0, "Displaying inventory for %s...", targetClient->GetName());
// worn
for (int16 indexMain = EQEmu::legacy::EQUIPMENT_BEGIN; (scopeWhere & peekWorn) && (indexMain <= EQEmu::legacy::EQUIPMENT_END); ++indexMain) {
inst_main = targetClient->GetInv().GetItem(indexMain);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
Object* objectTradeskill = targetClient->GetTradeskillObject();
item_link = linker.GenerateLink();
bool itemsFound = false;
c->Message((item_data == nullptr), "WornSlot: %i, Item: %i (%s), Charges: %i",
indexMain, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
}
for (int scopeIndex = 0, scopeBit = peekEquip; scopeBit < peekOutOfScope; ++scopeIndex, scopeBit <<= 1) {
if (scopeBit & ~scopeMask)
continue;
if ((scopeWhere & peekWorn) && (targetClient->ClientVersion() >= EQEmu::versions::ClientVersion::SoF)) {
inst_main = targetClient->GetInv().GetItem(EQEmu::inventory::slotPowerSource);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), "WornSlot: %i, Item: %i (%s), Charges: %i",
EQEmu::inventory::slotPowerSource, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
}
// inv
for (int16 indexMain = EQEmu::legacy::GENERAL_BEGIN; (scopeWhere & peekInv) && (indexMain <= EQEmu::legacy::GENERAL_END); ++indexMain) {
inst_main = targetClient->GetInv().GetItem(indexMain);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), "InvSlot: %i, Item: %i (%s), Charges: %i",
indexMain, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
for (uint8 indexSub = EQEmu::inventory::containerBegin; inst_main && inst_main->IsClassBag() && (indexSub < EQEmu::inventory::ContainerCount); ++indexSub) {
inst_sub = inst_main->GetItem(indexSub);
item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem();
linker.SetItemInst(inst_sub);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), " InvBagSlot: %i (Slot #%i, Bag #%i), Item: %i (%s), Charges: %i",
EQEmu::InventoryProfile::CalcSlotId(indexMain, indexSub), indexMain, indexSub, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()));
if (scopeBit & peekWorld) {
if (objectTradeskill == nullptr) {
c->Message(1, "No world tradeskill object selected...");
continue;
}
else {
c->Message(0, "[WorldObject DBID: %i (entityid: %i)]", objectTradeskill->GetDBID(), objectTradeskill->GetID());
}
}
}
// cursor
if (scopeWhere & peekCursor) {
if (targetClient->GetInv().CursorEmpty()) {
linker.SetItemInst(nullptr);
for (int16 indexMain = scope_range[scopeIndex][0]; indexMain <= scope_range[scopeIndex][1]; ++indexMain) {
if (indexMain == EQEmu::legacy::SLOT_INVALID)
continue;
item_link = linker.GenerateLink();
inst_main = ((scopeBit & peekWorld) ? objectTradeskill->GetItem(indexMain) : targetClient->GetInv().GetItem(indexMain));
if (inst_main) {
itemsFound = true;
item_data = inst_main->GetItem();
}
else {
item_data = nullptr;
}
c->Message(1, "CursorSlot: %i, Item: %i (%s), Charges: %i",
EQEmu::inventory::slotCursor, 0, item_link.c_str(), 0);
linker.SetItemInst(inst_main);
c->Message(
(item_data == nullptr),
"%sSlot: %i, Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
((scopeBit & peekWorld) ? (EQEmu::legacy::WORLD_BEGIN + indexMain) : indexMain),
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_main == nullptr) ? 0 : inst_main->GetCharges())
);
if (inst_main && inst_main->IsClassCommon()) {
for (uint8 indexAug = EQEmu::inventory::socketBegin; indexAug < EQEmu::inventory::SocketCount; ++indexAug) {
inst_aug = inst_main->GetItem(indexAug);
if (!inst_aug) // extant only
continue;
item_data = inst_aug->GetItem();
linker.SetItemInst(inst_aug);
c->Message(
(item_data == nullptr),
".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
INVALID_INDEX,
((scopeBit & peekWorld) ? (EQEmu::legacy::WORLD_BEGIN + indexMain) : indexMain),
indexAug,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
);
}
}
if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag()))
continue;
for (uint8 indexSub = EQEmu::inventory::containerBegin; indexSub < EQEmu::inventory::ContainerCount; ++indexSub) {
inst_sub = inst_main->GetItem(indexSub);
if (!inst_sub) // extant only
continue;
item_data = inst_sub->GetItem();
linker.SetItemInst(inst_sub);
c->Message(
(item_data == nullptr),
"..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
((scopeBit & peekWorld) ? INVALID_INDEX : EQEmu::InventoryProfile::CalcSlotId(indexMain, indexSub)),
((scopeBit & peekWorld) ? (EQEmu::legacy::WORLD_BEGIN + indexMain) : indexMain),
indexSub,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
);
if (inst_sub->IsClassCommon()) {
for (uint8 indexAug = EQEmu::inventory::socketBegin; indexAug < EQEmu::inventory::SocketCount; ++indexAug) {
inst_aug = inst_sub->GetItem(indexAug);
if (!inst_aug) // extant only
continue;
item_data = inst_aug->GetItem();
linker.SetItemInst(inst_aug);
c->Message(
(item_data == nullptr),
"...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
INVALID_INDEX,
((scopeBit & peekWorld) ? INVALID_INDEX : EQEmu::InventoryProfile::CalcSlotId(indexMain, indexSub)),
indexSub,
indexAug,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
);
}
}
}
}
else {
int cursorDepth = 0;
for (auto it = targetClient->GetInv().cursor_cbegin(); (it != targetClient->GetInv().cursor_cend()); ++it, ++cursorDepth) {
if ((scopeBit & peekEquip) && (targetClient->ClientVersion() >= EQEmu::versions::ClientVersion::SoF)) {
inst_main = targetClient->GetInv().GetItem(EQEmu::inventory::slotPowerSource);
if (inst_main) {
itemsFound = true;
item_data = inst_main->GetItem();
}
else {
item_data = nullptr;
}
linker.SetItemInst(inst_main);
c->Message(
(item_data == nullptr),
"%sSlot: %i, Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
EQEmu::inventory::slotPowerSource,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_main == nullptr) ? 0 : inst_main->GetCharges())
);
if (inst_main && inst_main->IsClassCommon()) {
for (uint8 indexAug = EQEmu::inventory::socketBegin; indexAug < EQEmu::inventory::SocketCount; ++indexAug) {
inst_aug = inst_main->GetItem(indexAug);
if (!inst_aug) // extant only
continue;
item_data = inst_aug->GetItem();
linker.SetItemInst(inst_aug);
c->Message(
(item_data == nullptr),
".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
INVALID_INDEX,
EQEmu::inventory::slotPowerSource,
indexAug,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
);
}
}
}
if (scopeBit & peekLimbo) {
int limboIndex = 0;
for (auto it = targetClient->GetInv().cursor_cbegin(); (it != targetClient->GetInv().cursor_cend()); ++it, ++limboIndex) {
if (it == targetClient->GetInv().cursor_cbegin())
continue;
inst_main = *it;
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
if (inst_main) {
itemsFound = true;
item_data = inst_main->GetItem();
}
else {
item_data = nullptr;
}
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message(
(item_data == nullptr),
"%sSlot: %i, Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
(8000 + limboIndex),
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_main == nullptr) ? 0 : inst_main->GetCharges())
);
c->Message((item_data == nullptr), "CursorSlot: %i, Depth: %i, Item: %i (%s), Charges: %i",
EQEmu::inventory::slotCursor, cursorDepth, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
if (inst_main && inst_main->IsClassCommon()) {
for (uint8 indexAug = EQEmu::inventory::socketBegin; indexAug < EQEmu::inventory::SocketCount; ++indexAug) {
inst_aug = inst_main->GetItem(indexAug);
if (!inst_aug) // extant only
continue;
for (uint8 indexSub = EQEmu::inventory::containerBegin; (cursorDepth == 0) && inst_main && inst_main->IsClassBag() && (indexSub < EQEmu::inventory::ContainerCount); ++indexSub) {
item_data = inst_aug->GetItem();
linker.SetItemInst(inst_aug);
c->Message(
(item_data == nullptr),
".%sAugSlot: %i (Slot #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
INVALID_INDEX,
(8000 + limboIndex),
indexAug,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
);
}
}
if (!scope_bag[scopeIndex] || !(inst_main && inst_main->IsClassBag()))
continue;
for (uint8 indexSub = EQEmu::inventory::containerBegin; indexSub < EQEmu::inventory::ContainerCount; ++indexSub) {
inst_sub = inst_main->GetItem(indexSub);
if (!inst_sub)
continue;
item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem();
linker.SetItemInst(inst_sub);
item_link = linker.GenerateLink();
c->Message(
(item_data == nullptr),
"..%sBagSlot: %i (Slot #%i, Bag idx #%i), Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
INVALID_INDEX,
(8000 + limboIndex),
indexSub,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
);
c->Message((item_data == nullptr), " CursorBagSlot: %i (Slot #%i, Bag #%i), Item: %i (%s), Charges: %i",
EQEmu::InventoryProfile::CalcSlotId(EQEmu::inventory::slotCursor, indexSub), EQEmu::inventory::slotCursor, indexSub, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()));
if (inst_sub->IsClassCommon()) {
for (uint8 indexAug = EQEmu::inventory::socketBegin; indexAug < EQEmu::inventory::SocketCount; ++indexAug) {
inst_aug = inst_sub->GetItem(indexAug);
if (!inst_aug) // extant only
continue;
item_data = inst_aug->GetItem();
linker.SetItemInst(inst_aug);
c->Message(
(item_data == nullptr),
"...%sAugSlot: %i (Slot #%i, Sub idx #%i, Aug idx #%i), Item: %i (%s), Charges: %i",
scope_prefix[scopeIndex],
INVALID_INDEX,
(8000 + limboIndex),
indexSub,
indexAug,
((item_data == nullptr) ? 0 : item_data->ID),
linker.GenerateLink().c_str(),
((inst_sub == nullptr) ? 0 : inst_sub->GetCharges())
);
}
}
}
}
}
}
// trib
for (int16 indexMain = EQEmu::legacy::TRIBUTE_BEGIN; (scopeWhere & peekTrib) && (indexMain <= EQEmu::legacy::TRIBUTE_END); ++indexMain) {
inst_main = targetClient->GetInv().GetItem(indexMain);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), "TributeSlot: %i, Item: %i (%s), Charges: %i",
indexMain, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
}
// bank
for (int16 indexMain = EQEmu::legacy::BANK_BEGIN; (scopeWhere & peekBank) && (indexMain <= EQEmu::legacy::BANK_END); ++indexMain) {
inst_main = targetClient->GetInv().GetItem(indexMain);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), "BankSlot: %i, Item: %i (%s), Charges: %i",
indexMain, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
for (uint8 indexSub = EQEmu::inventory::containerBegin; inst_main && inst_main->IsClassBag() && (indexSub < EQEmu::inventory::ContainerCount); ++indexSub) {
inst_sub = inst_main->GetItem(indexSub);
item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem();
linker.SetItemInst(inst_sub);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), " BankBagSlot: %i (Slot #%i, Bag #%i), Item: %i (%s), Charges: %i",
EQEmu::InventoryProfile::CalcSlotId(indexMain, indexSub), indexMain, indexSub, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()));
}
}
for (int16 indexMain = EQEmu::legacy::SHARED_BANK_BEGIN; (scopeWhere & peekBank) && (indexMain <= EQEmu::legacy::SHARED_BANK_END); ++indexMain) {
inst_main = targetClient->GetInv().GetItem(indexMain);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), "SharedBankSlot: %i, Item: %i (%s), Charges: %i",
indexMain, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
for (uint8 indexSub = EQEmu::inventory::containerBegin; inst_main && inst_main->IsClassBag() && (indexSub < EQEmu::inventory::ContainerCount); ++indexSub) {
inst_sub = inst_main->GetItem(indexSub);
item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem();
linker.SetItemInst(inst_sub);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), " SharedBankBagSlot: %i (Slot #%i, Bag #%i), Item: %i (%s), Charges: %i",
EQEmu::InventoryProfile::CalcSlotId(indexMain, indexSub), indexMain, indexSub, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()));
}
}
// trade
for (int16 indexMain = EQEmu::legacy::TRADE_BEGIN; (scopeWhere & peekTrade) && (indexMain <= EQEmu::legacy::TRADE_END); ++indexMain) {
inst_main = targetClient->GetInv().GetItem(indexMain);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), "TradeSlot: %i, Item: %i (%s), Charges: %i",
indexMain, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
for (uint8 indexSub = EQEmu::inventory::containerBegin; inst_main && inst_main->IsClassBag() && (indexSub < EQEmu::inventory::ContainerCount); ++indexSub) {
inst_sub = inst_main->GetItem(indexSub);
item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem();
linker.SetItemInst(inst_sub);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), " TradeBagSlot: %i (Slot #%i, Bag #%i), Item: %i (%s), Charges: %i",
EQEmu::InventoryProfile::CalcSlotId(indexMain, indexSub), indexMain, indexSub, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()));
}
}
// world
if (scopeWhere & peekWorld) {
Object* objectTradeskill = targetClient->GetTradeskillObject();
if (objectTradeskill == nullptr) {
c->Message(1, "No world tradeskill object selected...");
}
else {
c->Message(0, "[WorldObject DBID: %i (entityid: %i)]", objectTradeskill->GetDBID(), objectTradeskill->GetID());
for (int16 indexMain = EQEmu::inventory::slotBegin; indexMain < EQEmu::legacy::TYPE_WORLD_SIZE; ++indexMain) {
inst_main = objectTradeskill->GetItem(indexMain);
item_data = (inst_main == nullptr) ? nullptr : inst_main->GetItem();
linker.SetItemInst(inst_main);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), "WorldSlot: %i, Item: %i (%s), Charges: %i",
(EQEmu::legacy::WORLD_BEGIN + indexMain), ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_main == nullptr) ? 0 : inst_main->GetCharges()));
for (uint8 indexSub = EQEmu::inventory::containerBegin; inst_main && inst_main->IsType(EQEmu::item::ItemClassBag) && (indexSub < EQEmu::inventory::ContainerCount); ++indexSub) {
inst_sub = inst_main->GetItem(indexSub);
item_data = (inst_sub == nullptr) ? nullptr : inst_sub->GetItem();
linker.SetItemInst(inst_sub);
item_link = linker.GenerateLink();
c->Message((item_data == nullptr), " WorldBagSlot: %i (Slot #%i, Bag #%i), Item: %i (%s), Charges: %i",
INVALID_INDEX, indexMain, indexSub, ((item_data == nullptr) ? 0 : item_data->ID), item_link.c_str(), ((inst_sub == nullptr) ? 0 : inst_sub->GetCharges()));
}
}
}
}
if (!itemsFound)
c->Message(0, "No items found.");
}
void command_interrogateinv(Client *c, const Seperator *sep)
@@ -2992,6 +3111,11 @@ void command_reloadworld(Client *c, const Seperator *sep)
safe_delete(pack);
}
void command_reloadmerchants(Client *c, const Seperator *sep) {
entity_list.ReloadMerchants();
c->Message(15, "Reloading merchants.");
}
void command_reloadlevelmods(Client *c, const Seperator *sep)
{
if (sep->arg[1][0] == 0)
@@ -3847,6 +3971,12 @@ void command_showstats(Client *c, const Seperator *sep)
c->ShowStats(c);
}
void command_showzonegloballoot(Client *c, const Seperator *sep)
{
c->Message(0, "GlobalLoot for %s (%d:%d)", zone->GetShortName(), zone->GetZoneID(), zone->GetInstanceVersion());
zone->ShowZoneGlobalLoot(c);
}
void command_mystats(Client *c, const Seperator *sep)
{
if (c->GetTarget() && c->GetPet()) {
@@ -4059,6 +4189,33 @@ void command_unfreeze(Client *c, const Seperator *sep)
c->Message(0, "ERROR: Unfreeze requires a target.");
}
void command_push(Client *c, const Seperator *sep)
{
Mob *t = c;
if (c->GetTarget() != nullptr)
t = c->GetTarget();
if (!sep->arg[1] || !sep->IsNumber(1)) {
c->Message(0, "ERROR: Must provide at least a push back.");
return;
}
float back = atof(sep->arg[1]);
float up = 0.0f;
if (sep->arg[2] && sep->IsNumber(2))
up = atof(sep->arg[2]);
if (t->IsNPC()) {
t->IncDeltaX(back * g_Math.FastSin(c->GetHeading()));
t->IncDeltaY(back * g_Math.FastCos(c->GetHeading()));
t->IncDeltaZ(up);
t->SetForcedMovement(6);
} else if (t->IsClient()) {
// TODO: send packet to push
}
}
void command_pvp(Client *c, const Seperator *sep)
{
bool state=atobool(sep->arg[1]);
@@ -4156,6 +4313,26 @@ void command_tempname(Client *c, const Seperator *sep)
}
}
void command_petname(Client *c, const Seperator *sep)
{
Mob *target;
target = c->GetTarget();
if(!target)
c->Message(0, "Usage: #petname newname (requires a target)");
else if(target->IsPet() && (target->GetOwnerID() == c->GetID()) && strlen(sep->arg[1]) > 0)
{
char *oldname = strdup(target->GetName());
target->TempName(sep->arg[1]);
c->Message(0, "Renamed %s to %s", oldname, sep->arg[1]);
free(oldname);
}
else {
target->TempName();
c->Message(0, "Restored the original name");
}
}
void command_npcspecialattk(Client *c, const Seperator *sep)
{
if (c->GetTarget()==0 || c->GetTarget()->IsClient() || strlen(sep->arg[1]) <= 0 || strlen(sep->arg[2]) <= 0)
@@ -4360,9 +4537,7 @@ void command_iteminfo(Client *c, const Seperator *sep)
linker.SetLinkType(EQEmu::saylink::SayLinkItemInst);
linker.SetItemInst(inst);
auto item_link = linker.GenerateLink();
c->Message(0, "*** Item Info for [%s] ***", item_link.c_str());
c->Message(0, "*** Item Info for [%s] ***", linker.GenerateLink().c_str());
c->Message(0, ">> ID: %u, ItemUseType: %u, ItemClassType: %u", item->ID, item->ItemType, item->ItemClass);
c->Message(0, ">> IDFile: '%s', IconID: %u", item->IDFile, item->Icon);
c->Message(0, ">> Size: %u, Weight: %u, Price: %u, LDoNPrice: %u", item->Size, item->Weight, item->Price, item->LDoNPrice);
@@ -5506,9 +5681,9 @@ void command_summonitem(Client *c, const Seperator *sep)
std::string cmd_msg = sep->msg;
size_t link_open = cmd_msg.find('\x12');
size_t link_close = cmd_msg.find_last_of('\x12');
if (link_open != link_close && (cmd_msg.length() - link_open) > EQEmu::legacy::TEXT_LINK_BODY_LENGTH) {
if (link_open != link_close && (cmd_msg.length() - link_open) > EQEmu::constants::SayLinkBodySize) {
EQEmu::SayLinkBody_Struct link_body;
EQEmu::saylink::DegenerateLinkBody(link_body, cmd_msg.substr(link_open + 1, EQEmu::legacy::TEXT_LINK_BODY_LENGTH));
EQEmu::saylink::DegenerateLinkBody(link_body, cmd_msg.substr(link_open + 1, EQEmu::constants::SayLinkBodySize));
itemid = link_body.item_id;
}
else if (!sep->IsNumber(1)) {
@@ -5617,7 +5792,6 @@ void command_itemsearch(Client *c, const Seperator *sep)
const char *search_criteria=sep->argplus[1];
const EQEmu::ItemData* item = nullptr;
std::string item_link;
EQEmu::SayLinkEngine linker;
linker.SetLinkType(EQEmu::saylink::SayLinkItemData);
@@ -5626,9 +5800,7 @@ void command_itemsearch(Client *c, const Seperator *sep)
if (item) {
linker.SetItemData(item);
item_link = linker.GenerateLink();
c->Message(0, "%u: %s", item->ID, item_link.c_str());
c->Message(0, "%u: %s", item->ID, linker.GenerateLink().c_str());
}
else {
c->Message(0, "Item #%s not found", search_criteria);
@@ -5651,9 +5823,7 @@ void command_itemsearch(Client *c, const Seperator *sep)
if (pdest != nullptr) {
linker.SetItemData(item);
item_link = linker.GenerateLink();
c->Message(0, "%u: %s", item->ID, item_link.c_str());
c->Message(0, "%u: %s", item->ID, linker.GenerateLink().c_str());
++count;
}
@@ -6854,6 +7024,90 @@ void command_undye(Client *c, const Seperator *sep)
}
}
void command_ucs(Client *c, const Seperator *sep)
{
if (!c)
return;
Log(Logs::Detail, Logs::UCS_Server, "Character %s attempting ucs reconnect while ucs server is %savailable",
c->GetName(), (zone->IsUCSServerAvailable() ? "" : "un"));
if (zone->IsUCSServerAvailable()) {
EQApplicationPacket* outapp = nullptr;
std::string buffer;
std::string MailKey = database.GetMailKey(c->CharacterID(), true);
EQEmu::versions::UCSVersion ConnectionType = EQEmu::versions::ucsUnknown;
// chat server packet
switch (c->ClientVersion()) {
case EQEmu::versions::ClientVersion::Titanium:
ConnectionType = EQEmu::versions::ucsTitaniumChat;
break;
case EQEmu::versions::ClientVersion::SoF:
ConnectionType = EQEmu::versions::ucsSoFCombined;
break;
case EQEmu::versions::ClientVersion::SoD:
ConnectionType = EQEmu::versions::ucsSoDCombined;
break;
case EQEmu::versions::ClientVersion::UF:
ConnectionType = EQEmu::versions::ucsUFCombined;
break;
case EQEmu::versions::ClientVersion::RoF:
ConnectionType = EQEmu::versions::ucsRoFCombined;
break;
case EQEmu::versions::ClientVersion::RoF2:
ConnectionType = EQEmu::versions::ucsRoF2Combined;
break;
default:
ConnectionType = EQEmu::versions::ucsUnknown;
break;
}
buffer = StringFormat("%s,%i,%s.%s,%c%s",
Config->ChatHost.c_str(),
Config->ChatPort,
Config->ShortName.c_str(),
c->GetName(),
ConnectionType,
MailKey.c_str()
);
outapp = new EQApplicationPacket(OP_SetChatServer, (buffer.length() + 1));
memcpy(outapp->pBuffer, buffer.c_str(), buffer.length());
outapp->pBuffer[buffer.length()] = '\0';
c->QueuePacket(outapp);
safe_delete(outapp);
// mail server packet
switch (c->ClientVersion()) {
case EQEmu::versions::ClientVersion::Titanium:
ConnectionType = EQEmu::versions::ucsTitaniumMail;
break;
default:
// retain value from previous switch
break;
}
buffer = StringFormat("%s,%i,%s.%s,%c%s",
Config->MailHost.c_str(),
Config->MailPort,
Config->ShortName.c_str(),
c->GetName(),
ConnectionType,
MailKey.c_str()
);
outapp = new EQApplicationPacket(OP_SetChatServer2, (buffer.length() + 1));
memcpy(outapp->pBuffer, buffer.c_str(), buffer.length());
outapp->pBuffer[buffer.length()] = '\0';
c->QueuePacket(outapp);
safe_delete(outapp);
}
}
void command_undyeme(Client *c, const Seperator *sep)
{
if(c) {
@@ -8416,7 +8670,7 @@ void command_object(Client *c, const Seperator *sep)
od.x = c->GetX();
od.y = c->GetY();
od.z = c->GetZ() - (c->GetSize() * 0.625f);
od.heading = c->GetHeading() * 2.0f; // GetHeading() is half of actual. Compensate by doubling.
od.heading = c->GetHeading();
std::string query;
if (id) {
@@ -8521,11 +8775,9 @@ void command_object(Client *c, const Seperator *sep)
// Bump player back to avoid getting stuck inside new object
// GetHeading() returns half of the actual heading, for some reason, so we'll double it here for
// computation
x2 = 10.0f * sin(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
y2 = 10.0f * cos(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading() * 2);
x2 = 10.0f * sin(c->GetHeading() / 256.0f * 3.14159265f);
y2 = 10.0f * cos(c->GetHeading() / 256.0f * 3.14159265f);
c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading());
c->Message(0, "Spawning object with tentative id %u at location (%.1f, %.1f, %.1f heading %.1f). Use "
"'#object Save' to save to database when satisfied with placement.",
@@ -8843,14 +9095,13 @@ void command_object(Client *c, const Seperator *sep)
(c->GetSize() *
0.625f); // Compensate for #loc bumping up Z coordinate by 62.5% of character's size.
o->SetHeading(c->GetHeading() * 2.0f); // Compensate for GetHeading() returning half of actual
o->SetHeading(c->GetHeading());
// Bump player back to avoid getting stuck inside object
// GetHeading() returns half of the actual heading, for some reason
x2 = 10.0f * sin(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
y2 = 10.0f * cos(c->GetHeading() * 2.0f / 256.0f * 3.14159265f);
c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading() * 2.0f);
x2 = 10.0f * std::sin(c->GetHeading() / 256.0f * 3.14159265f);
y2 = 10.0f * std::cos(c->GetHeading() / 256.0f * 3.14159265f);
c->MovePC(c->GetX() - x2, c->GetY() - y2, c->GetZ(), c->GetHeading());
} // Move to x, y, z [h]
else {
od.x = atof(sep->arg[3]);
@@ -10108,6 +10359,20 @@ void command_shownumhits(Client *c, const Seperator *sep)
return;
}
void command_shownpcgloballoot(Client *c, const Seperator *sep)
{
auto tar = c->GetTarget();
if (!tar || !tar->IsNPC()) {
c->Message(0, "You must target an NPC to use this command.");
return;
}
auto npc = tar->CastToNPC();
c->Message(0, "GlobalLoot for %s (%d)", npc->GetName(), npc->GetNPCTypeID());
zone->ShowNPCGlobalLoot(c, npc);
}
void command_tune(Client *c, const Seperator *sep)
{
//Work in progress - Kayen
@@ -10481,7 +10746,7 @@ void command_hotfix(Client *c, const Seperator *sep) {
}
worldserver.SendPacket(&pack);
c->Message(0, "Hotfix applied");
if (c) c->Message(0, "Hotfix applied");
});
t1.detach();
@@ -10547,6 +10812,16 @@ void command_reloadperlexportsettings(Client *c, const Seperator *sep)
}
}
void command_trapinfo(Client *c, const Seperator *sep)
{
entity_list.GetTrapInfo(c);
}
void command_reloadtraps(Client *c, const Seperator *sep)
{
entity_list.UpdateAllTraps(true, true);
c->Message(CC_Default, "Traps reloaded for %s.", zone->GetShortName());
}
// All new code added to command.cpp should be BEFORE this comment line. Do no append code to this file below the BOTS code block.
#ifdef BOTS
+8
View File
@@ -211,6 +211,7 @@ void command_profiledump(Client *c, const Seperator *sep);
void command_profilereset(Client *c, const Seperator *sep);
#endif
void command_push(Client *c, const Seperator *sep);
void command_pvp(Client *c, const Seperator *sep);
void command_qglobal(Client *c, const Seperator *sep);
void command_qtest(Client *c, const Seperator *sep);
@@ -224,10 +225,12 @@ void command_reloadaa(Client *c, const Seperator *sep);
void command_reloadallrules(Client *c, const Seperator *sep);
void command_reloademote(Client* c, const Seperator *sep);
void command_reloadlevelmods(Client *c, const Seperator *sep);
void command_reloadmerchants(Client *c, const Seperator *sep);
void command_reloadperlexportsettings(Client *c, const Seperator *sep);
void command_reloadqst(Client *c, const Seperator *sep);
void command_reloadstatic(Client *c, const Seperator *sep);
void command_reloadtitles(Client *c, const Seperator *sep);
void command_reloadtraps(Client* c, const Seperator *sep);
void command_reloadworld(Client *c, const Seperator *sep);
void command_reloadworldrules(Client *c, const Seperator *sep);
void command_reloadzps(Client *c, const Seperator *sep);
@@ -265,10 +268,12 @@ void command_setxp(Client *c, const Seperator *sep);
void command_showbonusstats(Client *c, const Seperator *sep);
void command_showbuffs(Client *c, const Seperator *sep);
void command_shownumhits(Client *c, const Seperator *sep);
void command_shownpcgloballoot(Client *c, const Seperator *sep);
void command_showpetspell(Client *c, const Seperator *sep);
void command_showskills(Client *c, const Seperator *sep);
void command_showspellslist(Client *c, const Seperator *sep);
void command_showstats(Client *c, const Seperator *sep);
void command_showzonegloballoot(Client *c, const Seperator *sep);
void command_shutdown(Client *c, const Seperator *sep);
void command_size(Client *c, const Seperator *sep);
void command_spawn(Client *c, const Seperator *sep);
@@ -286,6 +291,7 @@ void command_synctod(Client *c, const Seperator *sep);
void command_task(Client *c, const Seperator *sep);
void command_tattoo(Client *c, const Seperator *sep);
void command_tempname(Client *c, const Seperator *sep);
void command_petname(Client *c, const Seperator *sep);
void command_testspawn(Client *c, const Seperator *sep);
void command_testspawnkill(Client *c, const Seperator *sep);
void command_texture(Client *c, const Seperator *sep);
@@ -295,7 +301,9 @@ void command_timezone(Client *c, const Seperator *sep);
void command_title(Client *c, const Seperator *sep);
void command_titlesuffix(Client *c, const Seperator *sep);
void command_traindisc(Client *c, const Seperator *sep);
void command_trapinfo(Client* c, const Seperator *sep);
void command_tune(Client *c, const Seperator *sep);
void command_ucs(Client *c, const Seperator *sep);
void command_undye(Client *c, const Seperator *sep);
void command_undyeme(Client *c, const Seperator *sep);
void command_unfreeze(Client *c, const Seperator *sep);
+8 -1
View File
@@ -194,7 +194,8 @@ enum {
CASTING_RESIST_DIFF = 43,
COUNTER_AVOID_DAMAGE = 44,
PROX_AGGRO = 45,
MAX_SPECIAL_ATTACK = 46
IMMUNE_RANGED_ATTACKS = 46,
MAX_SPECIAL_ATTACK = 47
};
typedef enum { //fear states
@@ -550,6 +551,7 @@ struct StatBonuses {
int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD
int aura_slots;
int trap_slots;
bool hunger; // Song of Sustenance -- min caps to 3500
};
typedef struct
@@ -603,6 +605,11 @@ enum { //type arguments to DoAnim
};
enum {
SKILLUP_UNKNOWN = 0,
SKILLUP_SUCCESS = 1,
SKILLUP_FAILURE = 2
};
typedef enum {
petFamiliar, //only listens to /pet get lost
+4 -4
View File
@@ -1253,20 +1253,20 @@ void Corpse::LootItem(Client *client, const EQApplicationPacket *app)
linker.SetLinkType(EQEmu::saylink::SayLinkItemInst);
linker.SetItemInst(inst);
auto item_link = linker.GenerateLink();
linker.GenerateLink();
client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, item_link.c_str());
client->Message_StringID(MT_LootMessages, LOOTED_MESSAGE, linker.Link().c_str());
if (!IsPlayerCorpse()) {
Group *g = client->GetGroup();
if (g != nullptr) {
g->GroupMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE,
client->GetName(), item_link.c_str());
client->GetName(), linker.Link().c_str());
} else {
Raid *r = client->GetRaid();
if (r != nullptr) {
r->RaidMessage_StringID(client, MT_LootMessages, OTHER_LOOTED_MESSAGE,
client->GetName(), item_link.c_str());
client->GetName(), linker.Link().c_str());
}
}
}
+4
View File
@@ -611,6 +611,10 @@ bool Client::UseDiscipline(uint32 spell_id, uint32 target) {
return false;
}
// the client does this check before calling CastSpell, should prevent discs being eaten
if (spell.buffdurationformula != 0 && spell.targettype == ST_Self && HasDiscBuff())
return false;
//Check the disc timer
pTimerType DiscTimer = pTimerDisciplineReuseStart + spell.EndurTimerIndex;
if(!p_timers.Expired(&database, DiscTimer, false)) { // lets not set the reuse timer in case CastSpell fails (or we would have to turn off the timer, but CastSpell will set it as well)
+399 -394
View File
File diff suppressed because it is too large Load Diff
+24 -11
View File
@@ -644,7 +644,6 @@ void EntityList::AddCorpse(Corpse *corpse, uint32 in_id)
void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue)
{
npc->SetID(GetFreeID());
npc->SetMerchantProbability((uint8) zone->random.Int(0, 99));
parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0);
@@ -1419,10 +1418,10 @@ void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets)
continue;
if (RemoveFromXTargets) {
if (m->IsClient() && mob->CheckAggro(m))
if (m->IsClient() && (mob->CheckAggro(m) || mob->IsOnFeignMemory(m->CastToClient())))
m->CastToClient()->RemoveXTarget(mob, false);
// FadingMemories calls this function passing the client.
else if (mob->IsClient() && m->CheckAggro(mob))
else if (mob->IsClient() && (m->CheckAggro(mob) || m->IsOnFeignMemory(mob->CastToClient())))
mob->CastToClient()->RemoveXTarget(m, false);
}
@@ -1461,7 +1460,7 @@ void EntityList::RefreshAutoXTargets(Client *c)
if (!m || m->GetHP() <= 0)
continue;
if (m->CheckAggro(c) && !c->IsXTarget(m)) {
if ((m->CheckAggro(c) || m->IsOnFeignMemory(c)) && !c->IsXTarget(m)) {
c->AddAutoXTarget(m, false); // we only call this before a bulk, so lets not send right away
break;
}
@@ -2617,12 +2616,13 @@ void EntityList::RemoveFromHateLists(Mob *mob, bool settoone)
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CheckAggro(mob)) {
if (!settoone)
if (!settoone) {
it->second->RemoveFromHateList(mob);
else
if (mob->IsClient())
mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping
} else {
it->second->SetHateAmountOnEnt(mob, 1);
if (mob->IsClient())
mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping
}
}
++it;
}
@@ -3059,7 +3059,10 @@ void EntityList::ClearAggro(Mob* targ)
c->RemoveXTarget(it->second, false);
it->second->RemoveFromHateList(targ);
}
it->second->RemoveFromFeignMemory(targ->CastToClient()); //just in case we feigned
if (c && it->second->IsOnFeignMemory(c)) {
it->second->RemoveFromFeignMemory(c); //just in case we feigned
c->RemoveXTarget(it->second, false);
}
++it;
}
}
@@ -3068,7 +3071,8 @@ void EntityList::ClearFeignAggro(Mob *targ)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CheckAggro(targ)) {
// add Feign Memory check because sometimes weird stuff happens
if (it->second->CheckAggro(targ) || (targ->IsClient() && it->second->IsOnFeignMemory(targ->CastToClient()))) {
if (it->second->GetSpecialAbility(IMMUNE_FEIGN_DEATH)) {
++it;
continue;
@@ -3232,7 +3236,7 @@ void EntityList::AddHealAggro(Mob *target, Mob *caster, uint16 hate)
for (auto &e : npc_list) {
auto &npc = e.second;
if (!npc->CheckAggro(target) || npc->IsFeared())
if (!npc->CheckAggro(target) || npc->IsFeared() || npc->IsPet())
continue;
if (zone->random.Roll(50)) // witness check -- place holder
@@ -4846,3 +4850,12 @@ void EntityList::SendAlternateAdvancementStats() {
c.second->SendAlternateAdvancementPoints();
}
}
void EntityList::ReloadMerchants() {
for (auto it = npc_list.begin();it != npc_list.end(); ++it) {
NPC *cur = it->second;
if (cur->MerchantType != 0) {
zone->LoadNewMerchantData(cur->MerchantType);
}
}
}
+6 -1
View File
@@ -244,6 +244,7 @@ public:
void AddArea(int id, int type, float min_x, float max_x, float min_y, float max_y, float min_z, float max_z);
void RemoveArea(int id);
void ClearAreas();
void ReloadMerchants();
void ProcessProximitySay(const char *Message, Client *c, uint8 language = 0);
void SendAATimer(uint32 charid,UseAA_Struct* uaa);
Doors *FindDoor(uint8 door_id);
@@ -365,7 +366,7 @@ public:
//trap stuff
Mob* GetTrapTrigger(Trap* trap);
void SendAlarm(Trap* trap, Mob* currenttarget, uint8 kos);
Trap* FindNearbyTrap(Mob* searcher, float max_dist);
Trap* FindNearbyTrap(Mob* searcher, float max_dist, float &curdist, bool detected = false);
void AddHealAggro(Mob* target, Mob* caster, uint16 hate);
Mob* FindDefenseNPC(uint32 npcid);
@@ -472,6 +473,10 @@ public:
void RefreshClientXTargets(Client *c);
void SendAlternateAdvancementStats();
void GetTrapInfo(Client* client);
bool IsTrapGroupSpawned(uint32 trap_id, uint8 group);
void UpdateAllTraps(bool respawn, bool repopnow = false);
void ClearTrapPointers();
protected:
friend class Zone;
void Depop(bool StartSpawnTimer = false);
+297 -137
View File
@@ -37,6 +37,49 @@
extern QueryServ* QServ;
static uint32 ScaleAAXPBasedOnCurrentAATotal(int earnedAA, uint32 add_aaxp)
{
float baseModifier = RuleR(AA, ModernAAScalingStartPercent);
int aaMinimum = RuleI(AA, ModernAAScalingAAMinimum);
int aaLimit = RuleI(AA, ModernAAScalingAALimit);
// Are we within the scaling window?
if (earnedAA >= aaLimit || earnedAA < aaMinimum)
{
Log(Logs::Detail, Logs::None, "Not within AA scaling window.");
// At or past the limit. We're done.
return add_aaxp;
}
// We're not at the limit yet. How close are we?
int remainingAA = aaLimit - earnedAA;
// We might not always be X - 0
int scaleRange = aaLimit - aaMinimum;
// Normalize and get the effectiveness based on the range and the character's
// current spent AA.
float normalizedScale = (float)remainingAA / scaleRange;
// Scale.
uint32 totalWithExpMod = add_aaxp * (baseModifier / 100) * normalizedScale;
// Are we so close to the scale limit that we're earning more XP without scaling? This
// will happen when we get very close to the limit. In this case, just grant the unscaled
// amount.
if (totalWithExpMod < add_aaxp)
{
return add_aaxp;
}
Log(Logs::Detail,
Logs::None,
"Total before the modifier %d :: NewTotal %d :: ScaleRange: %d, SpentAA: %d, RemainingAA: %d, normalizedScale: %0.3f",
add_aaxp, totalWithExpMod, scaleRange, earnedAA, remainingAA, normalizedScale);
return totalWithExpMod;
}
static uint32 MaxBankedGroupLeadershipPoints(int Level)
{
@@ -174,187 +217,302 @@ uint32 Client::GetExperienceForKill(Mob *against)
return 0;
}
void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) {
float static GetConLevelModifierPercent(uint8 conlevel)
{
switch (conlevel)
{
case CON_GREEN:
return (float)RuleI(Character, GreenModifier) / 100;
break;
case CON_LIGHTBLUE:
return (float)RuleI(Character, LightBlueModifier) / 100;
break;
case CON_BLUE:
return (float)RuleI(Character, BlueModifier) / 100;
break;
case CON_WHITE:
return (float)RuleI(Character, WhiteModifier) / 100;
break;
case CON_YELLOW:
return (float)RuleI(Character, YellowModifier) / 100;
break;
case CON_RED:
return (float)RuleI(Character, RedModifier) / 100;
break;
default:
return 0;
}
}
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) {
void Client::CalculateNormalizedAAExp(uint32 &add_aaxp, uint8 conlevel, bool resexp)
{
// Functionally this is the same as having the case in the switch, but this is
// cleaner to read.
if (CON_GRAY == conlevel || resexp)
{
add_aaxp = 0;
} else {
return;
}
// For this, we ignore the provided value of add_aaxp because it doesn't
// apply. XP per AA is normalized such that there are X white con kills
// per AA.
uint32 whiteConKillsPerAA = RuleI(AA, NormalizedAANumberOfWhiteConPerAA);
uint32 xpPerAA = RuleI(AA, ExpPerPoint);
float colorModifier = GetConLevelModifierPercent(conlevel);
float percentToAAXp = (float)m_epp.perAA / 100;
// Normalize the amount of AA XP we earned for this kill.
add_aaxp = percentToAAXp * (xpPerAA / (whiteConKillsPerAA / colorModifier));
}
void Client::CalculateStandardAAExp(uint32 &add_aaxp, uint8 conlevel, bool resexp)
{
if (!resexp)
{
//if XP scaling is based on the con of a monster, do that now.
if (RuleB(Character, UseXPConScaling))
{
if (conlevel != 0xFF && !resexp)
{
add_aaxp *= GetConLevelModifierPercent(conlevel);
}
}
} //end !resexp
float aatotalmod = 1.0;
if (zone->newzone_data.zone_exp_multiplier >= 0) {
aatotalmod *= zone->newzone_data.zone_exp_multiplier;
}
// Shouldn't race not affect AA XP?
if (RuleB(Character, UseRaceClassExpBonuses))
{
if (GetBaseRace() == HALFLING) {
aatotalmod *= 1.05;
}
if (GetClass() == ROGUE || GetClass() == WARRIOR) {
aatotalmod *= 1.05;
}
}
// why wasn't this here? Where should it be?
if (zone->IsHotzone())
{
aatotalmod += RuleR(Zone, HotZoneBonus);
}
if (RuleB(Zone, LevelBasedEXPMods)) {
if (zone->level_exp_mod[GetLevel()].ExpMod) {
add_aaxp *= zone->level_exp_mod[GetLevel()].AAExpMod;
}
}
add_aaxp = (uint32)(RuleR(Character, AAExpMultiplier) * add_aaxp * aatotalmod);
}
void Client::CalculateLeadershipExp(uint32 &add_exp, uint8 conlevel)
{
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);
}
}
}
}
}
void Client::CalculateExp(uint32 in_add_exp, uint32 &add_exp, uint32 &add_aaxp, uint8 conlevel, bool resexp)
{
add_exp = in_add_exp;
if (!resexp && (XPRate != 0))
{
add_exp = static_cast<uint32>(in_add_exp * (static_cast<float>(XPRate) / 100.0f));
}
// Make sure it was initialized.
add_aaxp = 0;
if (!resexp)
{
//figure out how much of this goes to AAs
add_aaxp = add_exp * m_epp.perAA / 100;
//take that amount away from regular exp
add_exp -= add_aaxp;
float totalmod = 1.0;
float zemmod = 1.0;
//get modifiers
if(RuleR(Character, ExpMultiplier) >= 0){
if (RuleR(Character, ExpMultiplier) >= 0) {
totalmod *= RuleR(Character, ExpMultiplier);
}
if(zone->newzone_data.zone_exp_multiplier >= 0){
//add the zone exp modifier.
if (zone->newzone_data.zone_exp_multiplier >= 0) {
zemmod *= zone->newzone_data.zone_exp_multiplier;
}
if(RuleB(Character,UseRaceClassExpBonuses))
if (RuleB(Character, UseRaceClassExpBonuses))
{
if(GetBaseRace() == HALFLING){
if (GetBaseRace() == HALFLING) {
totalmod *= 1.05;
}
if(GetClass() == ROGUE || GetClass() == WARRIOR){
if (GetClass() == ROGUE || GetClass() == WARRIOR) {
totalmod *= 1.05;
}
}
if(zone->IsHotzone())
//add hotzone modifier if one has been set.
if (zone->IsHotzone())
{
totalmod += RuleR(Zone, HotZoneBonus);
}
add_exp = uint32(float(add_exp) * totalmod * zemmod);
if(RuleB(Character,UseXPConScaling))
//if XP scaling is based on the con of a monster, do that now.
if (RuleB(Character, UseXPConScaling))
{
if (conlevel != 0xFF && !resexp) {
switch (conlevel)
{
case CON_GRAY:
add_exp = 0;
add_aaxp = 0;
return;
case CON_GREEN:
add_exp = add_exp * RuleI(Character, GreenModifier) / 100;
add_aaxp = add_aaxp * RuleI(Character, GreenModifier) / 100;
break;
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 (conlevel != 0xFF && !resexp)
{
add_exp = add_exp * GetConLevelModifierPercent(conlevel);
}
}
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);
}
}
}
}
// Calculate any changes to leadership experience.
CalculateLeadershipExp(add_exp, conlevel);
} //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){
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;
add_exp = GetEXP() + add_exp;
}
uint32 aaexp = (uint32)(RuleR(Character, AAExpMultiplier) * add_aaxp * aatotalmod);
void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) {
this->EVENT_ITEM_ScriptStopReturn();
uint32 exp = 0;
uint32 aaexp = 0;
if (m_epp.perAA<0 || m_epp.perAA>100)
m_epp.perAA=0; // stop exploit with sanity check
// Calculate regular XP
CalculateExp(in_add_exp, exp, aaexp, conlevel, resexp);
// Calculate regular AA XP
if (!RuleB(AA, NormalizedAAEnabled))
{
CalculateStandardAAExp(aaexp, conlevel, resexp);
}
else
{
CalculateNormalizedAAExp(aaexp, conlevel, resexp);
}
// Are we also doing linear AA acceleration?
if (RuleB(AA, ModernAAScalingEnabled) && aaexp > 0)
{
aaexp = ScaleAAXPBasedOnCurrentAATotal(GetAAPoints(), aaexp);
}
// Get current AA XP total
uint32 had_aaexp = GetAAXP();
aaexp += had_aaexp;
if(aaexp < had_aaexp)
aaexp = had_aaexp; //watch for wrap
// Add it to the XP we just earned.
aaexp += had_aaexp;
// Make sure our new total (existing + just earned) isn't lower than the
// existing total. If it is, we overflowed the bounds of uint32 and wrapped.
// Reset to the existing total.
if (aaexp < had_aaexp)
{
aaexp = had_aaexp; //watch for wrap
}
// Now update our character's normal and AA xp
SetEXP(exp, aaexp, resexp);
}
@@ -673,6 +831,8 @@ void Client::SetLevel(uint8 set_level, bool command)
SetHP(CalcMaxHP()); // Why not, lets give them a free heal
}
if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true);
DoTributeUpdate();
SendHPUpdate();
SetMana(CalcMaxMana());
+35
View File
@@ -0,0 +1,35 @@
#if defined(_MSC_VER)
#define _USE_MATH_DEFINES
#endif
#include <cmath>
#include "fastmath.h"
FastMath g_Math;
// This should match EQ's sin/cos LUTs
// Some values didn't match on linux, but they were the "same" float :P
FastMath::FastMath()
{
int ci = 0;
int si = 128;
float res;
do {
res = std::cos(static_cast<float>(ci) * M_PI * 2 / 512);
lut_cos[ci] = res;
if (si == 512)
si = 0;
lut_sin[si] = res;
++ci;
++si;
} while (ci < 512);
lut_sin[0] = 0.0f;
lut_sin[128] = 1.0f;
lut_sin[256] = 0.0f;
lut_sin[384] = -1.0f;
lut_cos[0] = 1.0f;
lut_cos[128] = 0.0f;
lut_cos[384] = 0.0f;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef FASTMATH_H
#define FASTMATH_H
class FastMath
{
private:
float lut_cos[512];
float lut_sin[512];
public:
FastMath();
inline float FastSin(float a) { return lut_sin[static_cast<int>(a) & 0x1ff]; }
inline float FastCos(float a) { return lut_cos[static_cast<int>(a) & 0x1ff]; }
};
#endif /* !FASTMATH_H */
+14 -11
View File
@@ -277,20 +277,23 @@ void Client::GoFish()
food_id = database.GetZoneFishing(m_pp.zone_id, fishing_skill, npc_id, npc_chance);
//check for add NPC
if(npc_chance > 0 && npc_id) {
if(npc_chance < zone->random.Int(0, 99)) {
const NPCType* tmp = database.LoadNPCTypesData(npc_id);
if(tmp != nullptr) {
auto positionNPC = GetPosition();
positionNPC.x = positionNPC.x + 3;
auto npc = new NPC(tmp, nullptr, positionNPC, FlyMode3);
npc->AddLootTable();
if (npc_chance > 0 && npc_id) {
if (zone->random.Roll(npc_chance)) {
const NPCType *tmp = database.LoadNPCTypesData(npc_id);
if (tmp != nullptr) {
auto positionNPC = GetPosition();
positionNPC.x = positionNPC.x + 3;
auto npc = new NPC(tmp, nullptr, positionNPC, FlyMode3);
npc->AddLootTable();
if (npc->DropsGlobalLoot())
npc->CheckGlobalLootTables();
npc->AddToHateList(this, 1, 0, false); // no help yelling
npc->AddToHateList(this, 1, 0, false); // no help yelling
entity_list.AddNPC(npc);
entity_list.AddNPC(npc);
Message(MT_Emote, "You fish up a little more than you bargained for...");
Message(MT_Emote,
"You fish up a little more than you bargained for...");
}
}
}
+98
View File
@@ -0,0 +1,98 @@
#include "global_loot_manager.h"
#include "npc.h"
#include "client.h"
std::vector<int> GlobalLootManager::GetGlobalLootTables(NPC *mob) const
{
// we may be able to add a cache here if performance is an issue, but for now
// just return NRVO'd vector
// The cache would have to be keyed by NPCType and level (for NPCs with Max Level set)
std::vector<int> tables;
for (auto &e : m_entries) {
if (e.PassesRules(mob)) {
tables.push_back(e.GetLootTableID());
}
}
return tables;
}
void GlobalLootManager::ShowZoneGlobalLoot(Client *to) const
{
for (auto &e : m_entries)
to->Message(0, " %s : %d table %d", e.GetDescription().c_str(), e.GetID(), e.GetLootTableID());
}
void GlobalLootManager::ShowNPCGlobalLoot(Client *to, NPC *who) const
{
for (auto &e : m_entries) {
if (e.PassesRules(who))
to->Message(0, " %s : %d table %d", e.GetDescription().c_str(), e.GetID(), e.GetLootTableID());
}
}
bool GlobalLootEntry::PassesRules(NPC *mob) const
{
bool bRace = false;
bool bPassesRace = false;
bool bBodyType = false;
bool bPassesBodyType = false;
bool bClass = false;
bool bPassesClass = false;
for (auto &r : m_rules) {
switch (r.type) {
case GlobalLoot::RuleTypes::LevelMin:
if (mob->GetLevel() < r.value)
return false;
break;
case GlobalLoot::RuleTypes::LevelMax:
if (mob->GetLevel() > r.value)
return false;
break;
case GlobalLoot::RuleTypes::Raid: // value == 0 must not be raid, value != 0 must be raid
if (mob->IsRaidTarget() && !r.value)
return false;
if (!mob->IsRaidTarget() && r.value)
return false;
break;
case GlobalLoot::RuleTypes::Rare:
if (mob->IsRareSpawn() && !r.value)
return false;
if (!mob->IsRareSpawn() && r.value)
return false;
break;
case GlobalLoot::RuleTypes::Race: // can have multiple races per rule set
bRace = true; // we must pass race
if (mob->GetRace() == r.value)
bPassesRace = true;
break;
case GlobalLoot::RuleTypes::Class: // can have multiple classes per rule set
bClass = true; // we must pass class
if (mob->GetClass() == r.value)
bPassesClass = true;
break;
case GlobalLoot::RuleTypes::BodyType: // can have multiple bodytypes per rule set
bBodyType = true; // we must pass BodyType
if (mob->GetBodyType() == r.value)
bPassesBodyType = true;
break;
default:
break;
}
}
if (bRace && !bPassesRace)
return false;
if (bClass && !bPassesClass)
return false;
if (bBodyType && !bPassesBodyType)
return false;
// we abort as early as possible if we fail a rule, so if we get here, we passed
return true;
}
+61
View File
@@ -0,0 +1,61 @@
#ifndef GLOBAL_LOOT_MANAGER_H
#define GLOBAL_LOOT_MANAGER_H
#include <vector>
#include <string>
class NPC;
class Client;
namespace GlobalLoot {
enum class RuleTypes {
LevelMin = 0,
LevelMax = 1,
Race = 2,
Class = 3,
BodyType = 4,
Rare = 5,
Raid = 6,
Max
};
struct Rule {
RuleTypes type;
int value;
Rule(RuleTypes t, int v) : type(t), value(v) { }
};
};
class GlobalLootEntry {
int m_id;
int m_loottable_id;
std::string m_description;
std::vector<GlobalLoot::Rule> m_rules;
public:
GlobalLootEntry(int id, int loottable, std::string des)
: m_id(id), m_loottable_id(loottable), m_description(std::move(des))
{ }
bool PassesRules(NPC *mob) const;
inline int GetLootTableID() const { return m_loottable_id; }
inline int GetID() const { return m_id; }
inline const std::string &GetDescription() const { return m_description; }
inline void SetLootTableID(int in) { m_loottable_id = in; }
inline void SetID(int in) { m_id = in; }
inline void SetDescription(const std::string &in) { m_description = in; }
inline void AddRule(GlobalLoot::RuleTypes rule, int value) { m_rules.emplace_back(rule, value); }
};
class GlobalLootManager {
std::vector<GlobalLootEntry> m_entries;
public:
std::vector<int> GetGlobalLootTables(NPC *mob) const;
inline void Clear() { m_entries.clear(); }
inline void AddEntry(GlobalLootEntry &in) { m_entries.push_back(in); }
void ShowZoneGlobalLoot(Client *to) const;
void ShowNPCGlobalLoot(Client *to, NPC *who) const;
};
#endif /* !GLOBAL_LOOT_MANAGER_H */
+3
View File
@@ -2471,6 +2471,9 @@ void Group::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_r
if (!members[i])
continue;
if (!members[i]->IsClient())
continue;
if (ignore_sender && members[i] == sender)
continue;
+46
View File
@@ -107,6 +107,7 @@ void HateList::SetHateAmountOnEnt(Mob* other, uint32 in_hate, uint32 in_damage)
entity->hatelist_damage = in_damage;
if (in_hate > 0)
entity->stored_hate_amount = in_hate;
entity->last_modified = Timer::GetCurrentTime();
}
}
@@ -192,6 +193,7 @@ void HateList::AddEntToHateList(Mob *in_entity, int32 in_hate, int32 in_damage,
entity->hatelist_damage += (in_damage >= 0) ? in_damage : 0;
entity->stored_hate_amount += in_hate;
entity->is_entity_frenzy = in_is_entity_frenzied;
entity->last_modified = Timer::GetCurrentTime();
}
else if (iAddIfNotExist) {
entity = new struct_HateList;
@@ -199,6 +201,8 @@ void HateList::AddEntToHateList(Mob *in_entity, int32 in_hate, int32 in_damage,
entity->hatelist_damage = (in_damage >= 0) ? in_damage : 0;
entity->stored_hate_amount = in_hate;
entity->is_entity_frenzy = in_is_entity_frenzied;
entity->oor_count = 0;
entity->last_modified = Timer::GetCurrentTime();
list.push_back(entity);
parse->EventNPC(EVENT_HATE_LIST, hate_owner->CastToNPC(), in_entity, "1", 0);
@@ -646,3 +650,45 @@ void HateList::SpellCast(Mob *caster, uint32 spell_id, float range, Mob* ae_cent
iter++;
}
}
void HateList::RemoveStaleEntries(int time_ms, float dist)
{
auto it = list.begin();
auto cur_time = Timer::GetCurrentTime();
auto dist2 = dist * dist;
while (it != list.end()) {
auto m = (*it)->entity_on_hatelist;
if (m) {
bool remove = false;
if (cur_time - (*it)->last_modified > time_ms)
remove = true;
if (!remove && DistanceSquaredNoZ(hate_owner->GetPosition(), m->GetPosition()) > dist2) {
(*it)->oor_count++;
if ((*it)->oor_count == 2)
remove = true;
} else if ((*it)->oor_count != 0) {
(*it)->oor_count = 0;
}
if (remove) {
parse->EventNPC(EVENT_HATE_LIST, hate_owner->CastToNPC(), m, "0", 0);
if (m->IsClient()) {
m->CastToClient()->DecrementAggroCount();
m->CastToClient()->RemoveXTarget(hate_owner, true);
}
delete (*it);
it = list.erase(it);
continue;
}
}
++it;
}
}
+3
View File
@@ -31,6 +31,8 @@ struct struct_HateList
int32 hatelist_damage;
uint32 stored_hate_amount;
bool is_entity_frenzy;
int8 oor_count; // count on how long we've been out of range
uint32 last_modified; // we need to remove this if it gets higher than 10 mins
};
class HateList
@@ -65,6 +67,7 @@ public:
void SetHateOwner(Mob *new_hate_owner) { hate_owner = new_hate_owner; }
void SpellCast(Mob *caster, uint32 spell_id, float range, Mob *ae_center = nullptr);
void WipeHateList();
void RemoveStaleEntries(int time_ms, float dist);
protected:
+17 -6
View File
@@ -169,9 +169,10 @@ bool HealRotation::ClearMemberPool()
m_casting_target_poke = false;
m_active_heal_target = false;
ClearTargetPool();
if (!ClearTargetPool())
Log(Logs::General, Logs::Error, "HealRotation::ClearTargetPool() failed to clear m_target_pool (size: %u)", m_target_pool.size());
auto clear_list = m_member_pool;
auto clear_list = const_cast<const std::list<Bot*>&>(m_member_pool);
for (auto member_iter : clear_list)
member_iter->LeaveHealRotationMemberPool();
@@ -183,13 +184,23 @@ bool HealRotation::ClearTargetPool()
m_hot_target = nullptr;
m_hot_active = false;
m_is_active = false;
auto clear_list = m_target_pool;
auto clear_list = const_cast<const std::list<Mob*>&>(m_target_pool);
for (auto target_iter : clear_list)
target_iter->LeaveHealRotationTargetPool();
m_casting_target_poke = false;
bias_targets();
//m_casting_target_poke = false;
//bias_targets();
// strange crash point...
// bias_targets() should be returning on m_target_pool.empty()
// and setting this two properties as below
m_casting_target_poke = true;
m_active_heal_target = false;
// instead, the list retains mob shared_ptrs and
// attempts to process them - and crashes program
// predominate when adaptive_healing = true
// (shared_ptr now has a delayed gc action? this did work before...)
return m_target_pool.empty();
}
+1
View File
@@ -151,6 +151,7 @@ void Client::SummonHorse(uint16 spell_id) {
uint16 tmpID = horse->GetID();
SetHorseId(tmpID);
BuffFadeBySitModifier();
}
+1 -1
View File
@@ -1326,7 +1326,7 @@ int Client::GetItemLinkHash(const EQEmu::ItemInstance* inst) {
return hash;
}
// This appears to still be in use... The core of this should be incorporated into class Client::TextLink
// This appears to still be in use... The core of this should be incorporated into class EQEmu::SayLinkEngine
void Client::SendItemLink(const EQEmu::ItemInstance* inst, bool send_to_all)
{
/*
+95 -13
View File
@@ -26,6 +26,7 @@
#include "mob.h"
#include "npc.h"
#include "zonedb.h"
#include "global_loot_manager.h"
#include <iostream>
#include <stdlib.h>
@@ -37,10 +38,14 @@
// Queries the loottable: adds item & coin to the npc
void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat) {
const LootTable_Struct* lts = nullptr;
*copper = 0;
*silver = 0;
*gold = 0;
*plat = 0;
// global loot passes nullptr for these
bool bGlobal = copper == nullptr && silver == nullptr && gold == nullptr && plat == nullptr;
if (!bGlobal) {
*copper = 0;
*silver = 0;
*gold = 0;
*plat = 0;
}
lts = database.GetLootTable(loottable_id);
if (!lts)
@@ -55,17 +60,19 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite
}
uint32 cash = 0;
if(max_cash > 0 && lts->avgcoin > 0 && EQEmu::ValueWithin(lts->avgcoin, min_cash, max_cash)) {
float upper_chance = (float)(lts->avgcoin - min_cash) / (float)(max_cash - min_cash);
float avg_cash_roll = (float)zone->random.Real(0.0, 1.0);
if (!bGlobal) {
if(max_cash > 0 && lts->avgcoin > 0 && EQEmu::ValueWithin(lts->avgcoin, min_cash, max_cash)) {
float upper_chance = (float)(lts->avgcoin - min_cash) / (float)(max_cash - min_cash);
float avg_cash_roll = (float)zone->random.Real(0.0, 1.0);
if(avg_cash_roll < upper_chance) {
cash = zone->random.Int(lts->avgcoin, max_cash);
if(avg_cash_roll < upper_chance) {
cash = zone->random.Int(lts->avgcoin, max_cash);
} else {
cash = zone->random.Int(min_cash, lts->avgcoin);
}
} else {
cash = zone->random.Int(min_cash, lts->avgcoin);
cash = zone->random.Int(min_cash, max_cash);
}
} else {
cash = zone->random.Int(min_cash, max_cash);
}
if(cash != 0) {
@@ -80,6 +87,7 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite
*copper = cash;
}
uint32 global_loot_multiplier = RuleI(Zone, GlobalLootMultiplier);
// Do items
@@ -336,7 +344,8 @@ void NPC::AddLootDrop(const EQEmu::ItemData *item2, ItemList* itemlist, int16 ch
eslot = EQEmu::textures::weaponPrimary;
if (item2->Damage > 0) {
SendAddPlayerState(PlayerState::PrimaryWeaponEquipped);
SetFacestab(true);
if (!RuleB(Combat, ClassicNPCBackstab))
SetFacestab(true);
}
if (item2->IsType2HWeapon())
SetTwoHanderEquipped(true);
@@ -443,3 +452,76 @@ void NPC::AddLootTable(uint32 ldid) {
database.AddLootTableToNPC(this,ldid, &itemlist, &copper, &silver, &gold, &platinum);
}
}
void NPC::CheckGlobalLootTables()
{
auto tables = zone->GetGlobalLootTables(this);
for (auto &id : tables)
database.AddLootTableToNPC(this, id, &itemlist, nullptr, nullptr, nullptr, nullptr);
}
void ZoneDatabase::LoadGlobalLoot()
{
auto query = StringFormat("SELECT id, loottable_id, description, min_level, max_level, rare, raid, race, "
"class, bodytype, zone FROM global_loot WHERE enabled = 1");
auto results = QueryDatabase(query);
if (!results.Success() || results.RowCount() == 0)
return;
// we might need this, lets not keep doing it in a loop
auto zoneid = std::to_string(zone->GetZoneID());
for (auto row = results.begin(); row != results.end(); ++row) {
// checking zone limits
if (row[10]) {
auto zones = SplitString(row[10], '|');
auto it = std::find(zones.begin(), zones.end(), zoneid);
if (it == zones.end()) // not in here, skip
continue;
}
GlobalLootEntry e(atoi(row[0]), atoi(row[1]), row[2] ? row[2] : "");
auto min_level = atoi(row[3]);
if (min_level)
e.AddRule(GlobalLoot::RuleTypes::LevelMin, min_level);
auto max_level = atoi(row[4]);
if (max_level)
e.AddRule(GlobalLoot::RuleTypes::LevelMax, max_level);
// null is not used
if (row[5])
e.AddRule(GlobalLoot::RuleTypes::Rare, atoi(row[5]));
// null is not used
if (row[6])
e.AddRule(GlobalLoot::RuleTypes::Raid, atoi(row[6]));
if (row[7]) {
auto races = SplitString(row[7], '|');
for (auto &r : races)
e.AddRule(GlobalLoot::RuleTypes::Race, std::stoi(r));
}
if (row[8]) {
auto classes = SplitString(row[8], '|');
for (auto &c : classes)
e.AddRule(GlobalLoot::RuleTypes::Class, std::stoi(c));
}
if (row[9]) {
auto bodytypes = SplitString(row[9], '|');
for (auto &b : bodytypes)
e.AddRule(GlobalLoot::RuleTypes::Class, std::stoi(b));
}
zone->AddGlobalLootEntry(e);
}
}
+57 -1
View File
@@ -1440,6 +1440,54 @@ void Lua_Client::FilteredMessage(Mob *sender, uint32 type, int filter, const cha
self->FilteredMessage(sender, type, (eqFilterType)filter, message);
}
void Lua_Client::EnableAreaHPRegen(int value)
{
Lua_Safe_Call_Void();
self->EnableAreaHPRegen(value);
}
void Lua_Client::DisableAreaHPRegen()
{
Lua_Safe_Call_Void();
self->DisableAreaHPRegen();
}
void Lua_Client::EnableAreaManaRegen(int value)
{
Lua_Safe_Call_Void();
self->EnableAreaManaRegen(value);
}
void Lua_Client::DisableAreaManaRegen()
{
Lua_Safe_Call_Void();
self->DisableAreaManaRegen();
}
void Lua_Client::EnableAreaEndRegen(int value)
{
Lua_Safe_Call_Void();
self->EnableAreaEndRegen(value);
}
void Lua_Client::DisableAreaEndRegen()
{
Lua_Safe_Call_Void();
self->DisableAreaEndRegen();
}
void Lua_Client::EnableAreaRegens(int value)
{
Lua_Safe_Call_Void();
self->EnableAreaRegens(value);
}
void Lua_Client::DisableAreaRegens()
{
Lua_Safe_Call_Void();
self->DisableAreaRegens();
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@@ -1712,7 +1760,15 @@ luabind::scope lua_register_client() {
.def("IsDead", &Lua_Client::IsDead)
.def("CalcCurrentWeight", &Lua_Client::CalcCurrentWeight)
.def("CalcATK", &Lua_Client::CalcATK)
.def("FilteredMessage", &Lua_Client::FilteredMessage);
.def("FilteredMessage", &Lua_Client::FilteredMessage)
.def("EnableAreaHPRegen", &Lua_Client::EnableAreaHPRegen)
.def("DisableAreaHPRegen", &Lua_Client::DisableAreaHPRegen)
.def("EnableAreaManaRegen", &Lua_Client::EnableAreaManaRegen)
.def("DisableAreaManaRegen", &Lua_Client::DisableAreaManaRegen)
.def("EnableAreaEndRegen", &Lua_Client::EnableAreaEndRegen)
.def("DisableAreaEndRegen", &Lua_Client::DisableAreaEndRegen)
.def("EnableAreaRegens", &Lua_Client::EnableAreaRegens)
.def("DisableAreaRegens", &Lua_Client::DisableAreaRegens);
}
luabind::scope lua_register_inventory_where() {
+8
View File
@@ -301,6 +301,14 @@ public:
int CalcCurrentWeight();
int CalcATK();
void FilteredMessage(Mob *sender, uint32 type, int filter, const char* message);
void EnableAreaHPRegen(int value);
void DisableAreaHPRegen();
void EnableAreaManaRegen(int value);
void DisableAreaManaRegen();
void EnableAreaEndRegen(int value);
void DisableAreaEndRegen();
void EnableAreaRegens(int value);
void DisableAreaRegens();
};
#endif
+5 -1
View File
@@ -493,6 +493,10 @@ void lua_set_proximity(float min_x, float max_x, float min_y, float max_y, float
quest_manager.set_proximity(min_x, max_x, min_y, max_y, min_z, max_z);
}
void lua_set_proximity(float min_x, float max_x, float min_y, float max_y, float min_z, float max_z, bool say) {
quest_manager.set_proximity(min_x, max_x, min_y, max_y, min_z, max_z, say);
}
void lua_clear_proximity() {
quest_manager.clear_proximity();
}
@@ -1464,7 +1468,6 @@ void lua_create_npc(luabind::adl::object table, float x, float y, float z, float
LuaCreateNPCParse(healscale, float, 0);
LuaCreateNPCParse(no_target_hotkey, bool, false);
LuaCreateNPCParse(raid_target, bool, false);
LuaCreateNPCParse(probability, uint8, 0);
NPC* npc = new NPC(npc_type, nullptr, glm::vec4(x, y, z, heading), FlyMode3);
npc->GiveNPCTypeData(npc_type);
@@ -1582,6 +1585,7 @@ luabind::scope lua_register_general() {
luabind::def("respawn", &lua_respawn),
luabind::def("set_proximity", (void(*)(float,float,float,float))&lua_set_proximity),
luabind::def("set_proximity", (void(*)(float,float,float,float,float,float))&lua_set_proximity),
luabind::def("set_proximity", (void(*)(float,float,float,float,float,float,bool))&lua_set_proximity),
luabind::def("clear_proximity", &lua_clear_proximity),
luabind::def("enable_proximity_say", &lua_enable_proximity_say),
luabind::def("disable_proximity_say", &lua_disable_proximity_say),
+104 -1
View File
@@ -282,6 +282,16 @@ void Lua_Mob::GMMove(double x, double y, double z, double heading, bool send_upd
self->GMMove(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z), static_cast<float>(heading), send_update);
}
void Lua_Mob::TryMoveAlong(float distance, float angle) {
Lua_Safe_Call_Void();
self->TryMoveAlong(distance, angle);
}
void Lua_Mob::TryMoveAlong(float distance, float angle, bool send) {
Lua_Safe_Call_Void();
self->TryMoveAlong(distance, angle, send);
}
bool Lua_Mob::HasProcs() {
Lua_Safe_Call_Bool();
return self->HasProcs();
@@ -1594,6 +1604,76 @@ void Lua_Mob::SendIllusionPacket(luabind::adl::object illusion) {
beard, aa_title, drakkin_heritage, drakkin_tattoo, drakkin_details, size);
}
void Lua_Mob::ChangeRace(int in) {
Lua_Safe_Call_Void();
self->ChangeRace(in);
}
void Lua_Mob::ChangeGender(int in) {
Lua_Safe_Call_Void();
self->ChangeGender(in);
}
void Lua_Mob::ChangeTexture(int in) {
Lua_Safe_Call_Void();
self->ChangeTexture(in);
}
void Lua_Mob::ChangeHelmTexture(int in) {
Lua_Safe_Call_Void();
self->ChangeHelmTexture(in);
}
void Lua_Mob::ChangeHairColor(int in) {
Lua_Safe_Call_Void();
self->ChangeHairColor(in);
}
void Lua_Mob::ChangeBeardColor(int in) {
Lua_Safe_Call_Void();
self->ChangeBeardColor(in);
}
void Lua_Mob::ChangeEyeColor1(int in) {
Lua_Safe_Call_Void();
self->ChangeEyeColor1(in);
}
void Lua_Mob::ChangeEyeColor2(int in) {
Lua_Safe_Call_Void();
self->ChangeEyeColor2(in);
}
void Lua_Mob::ChangeHairStyle(int in) {
Lua_Safe_Call_Void();
self->ChangeHairStyle(in);
}
void Lua_Mob::ChangeLuclinFace(int in) {
Lua_Safe_Call_Void();
self->ChangeLuclinFace(in);
}
void Lua_Mob::ChangeBeard(int in) {
Lua_Safe_Call_Void();
self->ChangeBeard(in);
}
void Lua_Mob::ChangeDrakkinHeritage(int in) {
Lua_Safe_Call_Void();
self->ChangeDrakkinHeritage(in);
}
void Lua_Mob::ChangeDrakkinTattoo(int in) {
Lua_Safe_Call_Void();
self->ChangeDrakkinTattoo(in);
}
void Lua_Mob::ChangeDrakkinDetails(int in) {
Lua_Safe_Call_Void();
self->ChangeDrakkinDetails(in);
}
void Lua_Mob::CameraEffect(uint32 duration, uint32 intensity) {
Lua_Safe_Call_Void();
self->CameraEffect(duration, intensity);
@@ -1675,6 +1755,11 @@ void Lua_Mob::DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup) {
self->DoKnockback(caster, pushback, pushup);
}
void Lua_Mob::AddNimbusEffect(int effect_id) {
Lua_Safe_Call_Void();
self->AddNimbusEffect(effect_id);
}
void Lua_Mob::RemoveNimbusEffect(int effect_id) {
Lua_Safe_Call_Void();
self->RemoveNimbusEffect(effect_id);
@@ -2111,6 +2196,8 @@ luabind::scope lua_register_mob() {
.def("GMMove", (void(Lua_Mob::*)(double,double,double))&Lua_Mob::GMMove)
.def("GMMove", (void(Lua_Mob::*)(double,double,double,double))&Lua_Mob::GMMove)
.def("GMMove", (void(Lua_Mob::*)(double,double,double,double,bool))&Lua_Mob::GMMove)
.def("TryMoveAlong", (void(Lua_Mob::*)(float,float))&Lua_Mob::TryMoveAlong)
.def("TryMoveAlong", (void(Lua_Mob::*)(float,float,bool))&Lua_Mob::TryMoveAlong)
.def("HasProcs", &Lua_Mob::HasProcs)
.def("IsInvisible", (bool(Lua_Mob::*)(void))&Lua_Mob::IsInvisible)
.def("IsInvisible", (bool(Lua_Mob::*)(Lua_Mob))&Lua_Mob::IsInvisible)
@@ -2338,6 +2425,20 @@ luabind::scope lua_register_mob() {
.def("SetRace", (void(Lua_Mob::*)(int))&Lua_Mob::SetRace)
.def("SetGender", (void(Lua_Mob::*)(int))&Lua_Mob::SetGender)
.def("SendIllusionPacket", (void(Lua_Mob::*)(luabind::adl::object))&Lua_Mob::SendIllusionPacket)
.def("ChangeRace", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeRace)
.def("ChangeGender", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeGender)
.def("ChangeTexture", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeTexture)
.def("ChangeHelmTexture", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHelmTexture)
.def("ChangeHairColor", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHairColor)
.def("ChangeBeardColor", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeBeardColor)
.def("ChangeEyeColor1", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeEyeColor1)
.def("ChangeEyeColor2", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeEyeColor2)
.def("ChangeHairStyle", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeHairStyle)
.def("ChangeLuclinFace", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeLuclinFace)
.def("ChangeBeard", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeBeard)
.def("ChangeDrakkinHeritage", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinHeritage)
.def("ChangeDrakkinTattoo", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinTattoo)
.def("ChangeDrakkinDetails", (void(Lua_Mob::*)(int))&Lua_Mob::ChangeDrakkinDetails)
.def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32))&Lua_Mob::CameraEffect)
.def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client))&Lua_Mob::CameraEffect)
.def("CameraEffect", (void(Lua_Mob::*)(uint32,uint32,Lua_Client,bool))&Lua_Mob::CameraEffect)
@@ -2354,6 +2455,7 @@ luabind::scope lua_register_mob() {
.def("SetSlotTint", (void(Lua_Mob::*)(int,int,int,int))&Lua_Mob::SetSlotTint)
.def("WearChange", (void(Lua_Mob::*)(int,int,uint32))&Lua_Mob::WearChange)
.def("DoKnockback", (void(Lua_Mob::*)(Lua_Mob,uint32,uint32))&Lua_Mob::DoKnockback)
.def("AddNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::AddNimbusEffect)
.def("RemoveNimbusEffect", (void(Lua_Mob::*)(int))&Lua_Mob::RemoveNimbusEffect)
.def("IsFeared", (bool(Lua_Mob::*)(void))&Lua_Mob::IsFeared)
.def("IsBlind", (bool(Lua_Mob::*)(void))&Lua_Mob::IsBlind)
@@ -2480,7 +2582,8 @@ luabind::scope lua_register_special_abilities() {
luabind::value("allow_to_tank", static_cast<int>(ALLOW_TO_TANK)),
luabind::value("ignore_root_aggro_rules", static_cast<int>(IGNORE_ROOT_AGGRO_RULES)),
luabind::value("casting_resist_diff", static_cast<int>(CASTING_RESIST_DIFF)),
luabind::value("counter_avoid_damage", static_cast<int>(COUNTER_AVOID_DAMAGE))
luabind::value("counter_avoid_damage", static_cast<int>(COUNTER_AVOID_DAMAGE)),
luabind::value("immune_ranged_attacks", static_cast<int>(IMMUNE_RANGED_ATTACKS))
];
}
+17
View File
@@ -74,6 +74,8 @@ public:
void GMMove(double x, double y, double z);
void GMMove(double x, double y, double z, double heading);
void GMMove(double x, double y, double z, double heading, bool send_update);
void TryMoveAlong(float distance, float heading);
void TryMoveAlong(float distance, float heading, bool send);
bool HasProcs();
bool IsInvisible();
bool IsInvisible(Lua_Mob other);
@@ -302,6 +304,20 @@ public:
void SetRace(int in);
void SetGender(int in);
void SendIllusionPacket(luabind::adl::object illusion);
void ChangeRace(int in);
void ChangeGender(int in);
void ChangeTexture(int in);
void ChangeHelmTexture(int in);
void ChangeHairColor(int in);
void ChangeBeardColor(int in);
void ChangeEyeColor1(int in);
void ChangeEyeColor2(int in);
void ChangeHairStyle(int in);
void ChangeLuclinFace(int in);
void ChangeBeard(int in);
void ChangeDrakkinHeritage(int in);
void ChangeDrakkinTattoo(int in);
void ChangeDrakkinDetails(int in);
void CameraEffect(uint32 duration, uint32 intensity);
void CameraEffect(uint32 duration, uint32 intensity, Lua_Client c);
void CameraEffect(uint32 duration, uint32 intensity, Lua_Client c, bool global);
@@ -321,6 +337,7 @@ public:
void SetSlotTint(int material_slot, int red_tint, int green_tint, int blue_tint);
void WearChange(int material_slot, int texture, uint32 color);
void DoKnockback(Lua_Mob caster, uint32 pushback, uint32 pushup);
void AddNimbusEffect(int effect_id);
void RemoveNimbusEffect(int effect_id);
bool IsRunning();
void SetRunning(bool running);
+7 -13
View File
@@ -420,7 +420,12 @@ void Lua_NPC::ModifyNPCStat(const char *stat, const char *value) {
void Lua_NPC::AddAISpell(int priority, int spell_id, int type, int mana_cost, int recast_delay, int resist_adjust) {
Lua_Safe_Call_Void();
self->AddSpellToNPCList(priority, spell_id, type, mana_cost, recast_delay, resist_adjust);
self->AddSpellToNPCList(priority, spell_id, type, mana_cost, recast_delay, resist_adjust, 0, 0);
}
void Lua_NPC::AddAISpell(int priority, int spell_id, int type, int mana_cost, int recast_delay, int resist_adjust, int min_hp, int max_hp) {
Lua_Safe_Call_Void();
self->AddSpellToNPCList(priority, spell_id, type, mana_cost, recast_delay, resist_adjust, min_hp, max_hp);
}
void Lua_NPC::RemoveAISpell(int spell_id) {
@@ -488,16 +493,6 @@ void Lua_NPC::MerchantCloseShop() {
self->MerchantCloseShop();
}
void Lua_NPC::SetMerchantProbability(uint8 amt) {
Lua_Safe_Call_Void();
self->SetMerchantProbability(amt);
}
uint8 Lua_NPC::GetMerchantProbability() {
Lua_Safe_Call_Int();
return self->GetMerchantProbability();
}
int Lua_NPC::GetRawAC() {
Lua_Safe_Call_Int();
return self->GetRawAC();
@@ -595,6 +590,7 @@ luabind::scope lua_register_npc() {
.def("SetSwarmTarget", (void(Lua_NPC::*)(int))&Lua_NPC::SetSwarmTarget)
.def("ModifyNPCStat", (void(Lua_NPC::*)(const char*,const char*))&Lua_NPC::ModifyNPCStat)
.def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int))&Lua_NPC::AddAISpell)
.def("AddAISpell", (void(Lua_NPC::*)(int,int,int,int,int,int,int,int))&Lua_NPC::AddAISpell)
.def("RemoveAISpell", (void(Lua_NPC::*)(int))&Lua_NPC::RemoveAISpell)
.def("SetSpellFocusDMG", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusDMG)
.def("SetSpellFocusHeal", (void(Lua_NPC::*)(int))&Lua_NPC::SetSpellFocusHeal)
@@ -608,8 +604,6 @@ luabind::scope lua_register_npc() {
.def("GetScore", (int(Lua_NPC::*)(void))&Lua_NPC::GetScore)
.def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop)
.def("MerchantCloseShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantCloseShop)
.def("SetMerchantProbability", (void(Lua_NPC::*)(void))&Lua_NPC::SetMerchantProbability)
.def("GetMerchantProbability", (uint8(Lua_NPC::*)(void))&Lua_NPC::GetMerchantProbability)
.def("GetRawAC", (int(Lua_NPC::*)(void))&Lua_NPC::GetRawAC)
.def("GetAvoidanceRating", &Lua_NPC::GetAvoidanceRating);
}
+1 -2
View File
@@ -110,6 +110,7 @@ public:
void SetSwarmTarget(int target);
void ModifyNPCStat(const char *stat, const char *value);
void AddAISpell(int priority, int spell_id, int type, int mana_cost, int recast_delay, int resist_adjust);
void AddAISpell(int priority, int spell_id, int type, int mana_cost, int recast_delay, int resist_adjust, int min_hp, int max_hp);
void RemoveAISpell(int spell_id);
void SetSpellFocusDMG(int focus);
void SetSpellFocusHeal(int focus);
@@ -123,8 +124,6 @@ public:
int GetScore();
void MerchantOpenShop();
void MerchantCloseShop();
void SetMerchantProbability(uint8 amt);
uint8 GetMerchantProbability();
int GetRawAC();
int GetAvoidanceRating();
};
+8
View File
@@ -192,6 +192,14 @@ bool Map::CheckLoS(glm::vec3 myloc, glm::vec3 oloc) const {
return !imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, nullptr, nullptr);
}
// returns true if a collision happens
bool Map::DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, glm::vec3 &outnorm, float &distance) const {
if(!imp)
return false;
return imp->rm->raycast((const RmReal*)&myloc, (const RmReal*)&oloc, nullptr, (RmReal *)&outnorm, (RmReal *)&distance);
}
inline bool file_exists(const std::string& name) {
std::ifstream f(name.c_str());
return f.good();
+1
View File
@@ -42,6 +42,7 @@ public:
bool LineIntersectsZone(glm::vec3 start, glm::vec3 end, float step, glm::vec3 *result) const;
bool LineIntersectsZoneNoZLeaps(glm::vec3 start, glm::vec3 end, float step_mag, glm::vec3 *result) const;
bool CheckLoS(glm::vec3 myloc, glm::vec3 oloc) const;
bool DoCollisionCheck(glm::vec3 myloc, glm::vec3 oloc, glm::vec3 &outnorm, float &distance) const;
#ifdef USE_MAP_MMFS
bool Load(std::string filename, bool force_mmf_overwrite = false);
+4 -5
View File
@@ -1154,7 +1154,7 @@ void Merc::CalcRestState() {
// The bot must have been out of combat for RuleI(Character, RestRegenTimeToActivate) seconds,
// must be sitting down, and must not have any detrimental spells affecting them.
//
if(!RuleI(Character, RestRegenPercent))
if(!RuleB(Character, RestRegenEnabled))
return;
RestRegenHP = RestRegenMana = RestRegenEndurance = 0;
@@ -1174,12 +1174,11 @@ void Merc::CalcRestState() {
}
}
RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100);
RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP));
RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100);
RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana));
if(RuleB(Character, RestRegenEndurance))
RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100);
RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd));
}
bool Merc::HasSkill(EQEmu::skills::SkillType skill_id) const {
+176 -129
View File
@@ -75,7 +75,6 @@ Mob::Mob(const char* in_name,
uint32 in_drakkin_tattoo,
uint32 in_drakkin_details,
EQEmu::TintProfile in_armor_tint,
uint8 in_aa_title,
uint8 in_see_invis, // see through invis/ivu
uint8 in_see_invis_undead,
@@ -91,24 +90,24 @@ Mob::Mob(const char* in_name,
uint8 in_handtexture,
uint8 in_legtexture,
uint8 in_feettexture
) :
) :
attack_timer(2000),
attack_dw_timer(2000),
ranged_timer(2000),
tic_timer(6000),
mana_timer(2000),
spellend_timer(0),
rewind_timer(30000), //Timer used for determining amount of time between actual player position updates for /rewind.
rewind_timer(30000),
bindwound_timer(10000),
stunned_timer(0),
spun_timer(0),
bardsong_timer(6000),
gravity_timer(1000),
viral_timer(0),
m_FearWalkTarget(-999999.0f,-999999.0f,-999999.0f),
m_FearWalkTarget(-999999.0f, -999999.0f, -999999.0f),
m_TargetLocation(glm::vec3()),
m_TargetV(glm::vec3()),
flee_timer(FLEE_CHECK_TIMER),
flee_timer(FLEE_CHECK_TIMER),
m_Position(position),
tmHidden(-1),
mitigation_ac(0),
@@ -116,50 +115,50 @@ Mob::Mob(const char* in_name,
fix_z_timer(300),
fix_z_timer_engaged(100),
attack_anim_timer(1000),
position_update_melee_push_timer(1000)
position_update_melee_push_timer(500),
mHateListCleanup(6000)
{
targeted = 0;
tar_ndx=0;
tar_vector=0;
tar_ndx = 0;
tar_vector = 0;
currently_fleeing = false;
last_z = 0;
last_major_update_position = m_Position;
is_distance_roamer = false;
AI_Init();
SetMoving(false);
moved=false;
moved = false;
m_RewindLocation = glm::vec3();
_egnode = nullptr;
name[0]=0;
orig_name[0]=0;
clean_name[0]=0;
lastname[0]=0;
if(in_name) {
strn0cpy(name,in_name,64);
strn0cpy(orig_name,in_name,64);
name[0] = 0;
orig_name[0] = 0;
clean_name[0] = 0;
lastname[0] = 0;
if (in_name) {
strn0cpy(name, in_name, 64);
strn0cpy(orig_name, in_name, 64);
}
if(in_lastname)
strn0cpy(lastname,in_lastname,64);
cur_hp = in_cur_hp;
max_hp = in_max_hp;
base_hp = in_max_hp;
gender = in_gender;
race = in_race;
base_gender = in_gender;
base_race = in_race;
class_ = in_class;
bodytype = in_bodytype;
if (in_lastname)
strn0cpy(lastname, in_lastname, 64);
cur_hp = in_cur_hp;
max_hp = in_max_hp;
base_hp = in_max_hp;
gender = in_gender;
race = in_race;
base_gender = in_gender;
base_race = in_race;
class_ = in_class;
bodytype = in_bodytype;
orig_bodytype = in_bodytype;
deity = in_deity;
level = in_level;
deity = in_deity;
level = in_level;
orig_level = in_level;
npctype_id = in_npctype_id;
size = in_size;
base_size = size;
runspeed = in_runspeed;
npctype_id = in_npctype_id;
size = in_size;
base_size = size;
runspeed = in_runspeed;
// neotokyo: sanity check
if (runspeed < 0 || runspeed > 20)
runspeed = 1.25f;
@@ -172,7 +171,8 @@ Mob::Mob(const char* in_name,
fearspeed = 0.625f;
base_fearspeed = 25;
// npcs
} else {
}
else {
base_walkspeed = base_runspeed * 100 / 265;
walkspeed = ((float)base_walkspeed) * 0.025f;
base_fearspeed = base_runspeed * 100 / 127;
@@ -184,7 +184,7 @@ Mob::Mob(const char* in_name,
current_speed = base_runspeed;
m_PlayerState = 0;
m_PlayerState = 0;
// sanity check
@@ -196,8 +196,8 @@ Mob::Mob(const char* in_name,
m_Light.Type[EQEmu::lightsource::LightActive] = m_Light.Type[EQEmu::lightsource::LightInnate];
m_Light.Level[EQEmu::lightsource::LightActive] = m_Light.Level[EQEmu::lightsource::LightInnate];
texture = in_texture;
helmtexture = in_helmtexture;
texture = in_texture;
helmtexture = in_helmtexture;
armtexture = in_armtexture;
bracertexture = in_bracertexture;
handtexture = in_handtexture;
@@ -205,21 +205,21 @@ Mob::Mob(const char* in_name,
feettexture = in_feettexture;
multitexture = (armtexture || bracertexture || handtexture || legtexture || feettexture);
haircolor = in_haircolor;
beardcolor = in_beardcolor;
eyecolor1 = in_eyecolor1;
eyecolor2 = in_eyecolor2;
hairstyle = in_hairstyle;
luclinface = in_luclinface;
beard = in_beard;
drakkin_heritage = in_drakkin_heritage;
drakkin_tattoo = in_drakkin_tattoo;
drakkin_details = in_drakkin_details;
haircolor = in_haircolor;
beardcolor = in_beardcolor;
eyecolor1 = in_eyecolor1;
eyecolor2 = in_eyecolor2;
hairstyle = in_hairstyle;
luclinface = in_luclinface;
beard = in_beard;
drakkin_heritage = in_drakkin_heritage;
drakkin_tattoo = in_drakkin_tattoo;
drakkin_details = in_drakkin_details;
attack_speed = 0;
attack_delay = 0;
slow_mitigation = 0;
findable = false;
trackable = true;
findable = false;
trackable = true;
has_shieldequiped = false;
has_twohandbluntequiped = false;
has_twohanderequipped = false;
@@ -230,19 +230,19 @@ Mob::Mob(const char* in_name,
SpellPowerDistanceMod = 0;
last_los_check = false;
if(in_aa_title>0)
aa_title = in_aa_title;
if (in_aa_title > 0)
aa_title = in_aa_title;
else
aa_title =0xFF;
AC = in_ac;
ATK = in_atk;
STR = in_str;
STA = in_sta;
DEX = in_dex;
AGI = in_agi;
INT = in_int;
WIS = in_wis;
CHA = in_cha;
aa_title = 0xFF;
AC = in_ac;
ATK = in_atk;
STR = in_str;
STA = in_sta;
DEX = in_dex;
AGI = in_agi;
INT = in_int;
WIS = in_wis;
CHA = in_cha;
MR = CR = FR = DR = PR = Corrup = 0;
ExtraHaste = 0;
@@ -263,9 +263,10 @@ Mob::Mob(const char* in_name,
hidden = false;
improved_hidden = false;
invulnerable = false;
IsFullHP = (cur_hp == max_hp);
qglobal=0;
IsFullHP = (cur_hp == max_hp);
qglobal = 0;
spawned = false;
rare_spawn = false;
InitializeBuffSlots();
@@ -305,7 +306,7 @@ Mob::Mob(const char* in_name,
logging_enabled = false;
isgrouped = false;
israidgrouped = false;
IsHorse = false;
entity_id_being_looted = 0;
@@ -376,13 +377,13 @@ Mob::Mob(const char* in_name,
}
destructibleobject = false;
wandertype=0;
pausetype=0;
wandertype = 0;
pausetype = 0;
cur_wp = 0;
m_CurrentWayPoint = glm::vec4();
cur_wp_pause = 0;
patrol=0;
follow=0;
patrol = 0;
follow = 0;
follow_dist = 100; // Default Distance for Follow
no_target_hotkey = false;
flee_mode = false;
@@ -392,11 +393,12 @@ Mob::Mob(const char* in_name,
permarooted = (runspeed > 0) ? false : true;
movetimercompleted = false;
ForcedMovement = 0;
roamer = false;
rooted = false;
charmed = false;
has_virus = false;
for (i=0; i<MAX_SPELL_TRIGGER*2; i++) {
for (i = 0; i < MAX_SPELL_TRIGGER * 2; i++) {
viral_spells[i] = 0;
}
pStandingPetOrder = SPO_Follow;
@@ -427,7 +429,7 @@ Mob::Mob(const char* in_name,
nimbus_effect3 = 0;
m_targetable = true;
m_TargetRing = glm::vec3();
m_TargetRing = glm::vec3();
flymode = FlyMode3;
DistractedFromGrid = false;
@@ -436,7 +438,7 @@ Mob::Mob(const char* in_name,
m_AllowBeneficial = false;
m_DisableMelee = false;
for (int i = 0; i < EQEmu::skills::HIGHEST_SKILL + 2; i++) { SkillDmgTaken_Mod[i] = 0; }
for (int i = 0; i < HIGHEST_RESIST+2; i++) { Vulnerability_Mod[i] = 0; }
for (int i = 0; i < HIGHEST_RESIST + 2; i++) { Vulnerability_Mod[i] = 0; }
emoteid = 0;
endur_upkeep = false;
@@ -1100,7 +1102,7 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName));
}
ns->spawn.heading = FloatToEQ19(m_Position.w);
ns->spawn.heading = FloatToEQ12(m_Position.w);
ns->spawn.x = FloatToEQ19(m_Position.x);//((int32)x_pos)<<3;
ns->spawn.y = FloatToEQ19(m_Position.y);//((int32)y_pos)<<3;
ns->spawn.z = FloatToEQ19(m_Position.z);//((int32)z_pos)<<3;
@@ -1436,6 +1438,21 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal
}
}
void Mob::StopMoving() {
FixZ();
SetCurrentSpeed(0);
if (moved)
moved = false;
}
void Mob::StopMoving(float new_heading) {
SetHeading(new_heading);
FixZ();
SetCurrentSpeed(0);
if (moved)
moved = false;
}
/* Used for mobs standing still - this does not send a delta */
void Mob::SendPosition() {
auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
@@ -1446,6 +1463,7 @@ void Mob::SendPosition() {
if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) {
entity_list.QueueClients(this, app, true, true);
last_major_update_position = m_Position;
is_distance_roamer = true;
}
else {
entity_list.QueueCloseClients(this, app, true, RuleI(Range, MobPositionUpdates), nullptr, false);
@@ -1479,6 +1497,11 @@ void Mob::SendPositionUpdate(uint8 iSendToSelf) {
CastToClient()->FastQueuePacket(&app, false);
}
}
else if (DistanceSquared(last_major_update_position, m_Position) >= (100 * 100)) {
entity_list.QueueClients(this, app, true, true);
last_major_update_position = m_Position;
is_distance_roamer = true;
}
else {
entity_list.QueueCloseClients(this, app, (iSendToSelf == 0), RuleI(Range, MobPositionUpdates), nullptr, false);
}
@@ -1492,12 +1515,12 @@ void Mob::MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct *spu) {
spu->x_pos = FloatToEQ19(m_Position.x);
spu->y_pos = FloatToEQ19(m_Position.y);
spu->z_pos = FloatToEQ19(m_Position.z);
spu->delta_x = NewFloatToEQ13(0);
spu->delta_y = NewFloatToEQ13(0);
spu->delta_z = NewFloatToEQ13(0);
spu->heading = FloatToEQ19(m_Position.w);
spu->delta_x = FloatToEQ13(0);
spu->delta_y = FloatToEQ13(0);
spu->delta_z = FloatToEQ13(0);
spu->heading = FloatToEQ12(m_Position.w);
spu->animation = 0;
spu->delta_heading = NewFloatToEQ13(0);
spu->delta_heading = FloatToEQ10(0);
spu->padding0002 = 0;
spu->padding0006 = 7;
spu->padding0014 = 0x7f;
@@ -1511,10 +1534,10 @@ void Mob::MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu) {
spu->x_pos = FloatToEQ19(m_Position.x);
spu->y_pos = FloatToEQ19(m_Position.y);
spu->z_pos = FloatToEQ19(m_Position.z);
spu->delta_x = NewFloatToEQ13(m_Delta.x);
spu->delta_y = NewFloatToEQ13(m_Delta.y);
spu->delta_z = NewFloatToEQ13(m_Delta.z);
spu->heading = FloatToEQ19(m_Position.w);
spu->delta_x = FloatToEQ13(m_Delta.x);
spu->delta_y = FloatToEQ13(m_Delta.y);
spu->delta_z = FloatToEQ13(m_Delta.z);
spu->heading = FloatToEQ12(m_Position.w);
spu->padding0002 = 0;
spu->padding0006 = 7;
spu->padding0014 = 0x7f;
@@ -1528,7 +1551,7 @@ void Mob::MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu) {
else
spu->animation = pRunAnimSpeed;//animation;
spu->delta_heading = NewFloatToEQ13(m_Delta.w);
spu->delta_heading = FloatToEQ10(m_Delta.w);
}
void Mob::ShowStats(Client* client)
@@ -2414,18 +2437,18 @@ float Mob::MobAngle(Mob *other, float ourx, float oury) const {
float mobx = -(other->GetX()); // mob xloc (inverse because eq)
float moby = other->GetY(); // mob yloc
float heading = other->GetHeading(); // mob heading
heading = (heading * 360.0f) / 256.0f; // convert to degrees
heading = (heading * 360.0f) / 512.0f; // convert to degrees
if (heading < 270)
heading += 90;
else
heading -= 270;
heading = heading * 3.1415f / 180.0f; // convert to radians
vectorx = mobx + (10.0f * cosf(heading)); // create a vector based on heading
vectory = moby + (10.0f * sinf(heading)); // of mob length 10
vectorx = mobx + (10.0f * std::cos(heading)); // create a vector based on heading
vectory = moby + (10.0f * std::sin(heading)); // of mob length 10
// length of mob to player vector
lengthb = (float) sqrtf(((-ourx - mobx) * (-ourx - mobx)) + ((oury - moby) * (oury - moby)));
lengthb = (float) std::sqrt(((-ourx - mobx) * (-ourx - mobx)) + ((oury - moby) * (oury - moby)));
// calculate dot product to get angle
// Handle acos domain errors due to floating point rounding errors
@@ -2438,7 +2461,7 @@ float Mob::MobAngle(Mob *other, float ourx, float oury) const {
else if (dotp < -1)
return 180.0f;
angle = acosf(dotp);
angle = std::acos(dotp);
angle = angle * 180.0f / 3.1415f;
return angle;
@@ -2607,7 +2630,7 @@ bool Mob::PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, fl
look_heading = target->GetHeading();
// Convert to sony heading to radians
look_heading = (look_heading / 256.0f) * 6.283184f;
look_heading = (look_heading / 512.0f) * 6.283184f;
float tempX = 0;
float tempY = 0;
@@ -2715,20 +2738,10 @@ bool Mob::HateSummon() {
if(summon_level == 1) {
entity_list.MessageClose(this, true, 500, MT_Say, "%s says,'You will not evade me, %s!' ", GetCleanName(), target->GetCleanName() );
if (target->IsClient()) {
if (target->IsClient())
target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), m_Position.x, m_Position.y, m_Position.z, target->GetHeading(), 0, SummonPC);
}
else {
#ifdef BOTS
if(target && target->IsBot()) {
// set pre summoning info to return to (to get out of melee range for caster)
target->CastToBot()->SetHasBeenSummoned(true);
target->CastToBot()->SetPreSummonLocation(glm::vec3(target->GetPosition()));
}
#endif //BOTS
else
target->GMMove(m_Position.x, m_Position.y, m_Position.z, target->GetHeading());
}
return true;
} else if(summon_level == 2) {
@@ -3395,13 +3408,18 @@ int Mob::GetHaste()
else // 1-25
h += itembonuses.haste > 10 ? 10 : itembonuses.haste;
// 60+ 100, 51-59 85, 1-50 level+25
if (level > 59) // 60+
cap = RuleI(Character, HasteCap);
else if (level > 50) // 51-59
cap = 85;
else // 1-50
cap = level + 25;
// mobs are different!
Mob *owner = nullptr;
if (IsPet())
owner = GetOwner();
else if (IsNPC() && CastToNPC()->GetSwarmOwner())
owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner());
if (owner) {
cap = 10 + level;
cap += std::max(0, owner->GetLevel() - 39) + std::max(0, owner->GetLevel() - 60);
} else {
cap = 150;
}
if(h > cap)
h = cap;
@@ -3437,6 +3455,19 @@ void Mob::SetTarget(Mob* mob) {
this->GetTarget()->SendHPUpdate(false, true);
}
// For when we want a Ground Z at a location we are not at yet
// Like MoveTo.
float Mob::FindDestGroundZ(glm::vec3 dest, float z_offset)
{
float best_z = BEST_Z_INVALID;
if (zone->zonemap != nullptr)
{
dest.z += z_offset;
best_z = zone->zonemap->FindBestZ(dest, nullptr);
}
return best_z;
}
float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
{
float ret = BEST_Z_INVALID;
@@ -3773,7 +3804,7 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP
if ((base2 >= 500 && base2 <= 520) && GetHPRatio() < (base2 - 500)*5)
use_spell = true;
else if (base2 = 1004 && GetHPRatio() < 80)
else if (base2 == 1004 && GetHPRatio() < 80)
use_spell = true;
}
@@ -3781,12 +3812,12 @@ void Mob::TryTriggerOnValueAmount(bool IsHP, bool IsMana, bool IsEndur, bool IsP
if ( (base2 = 521 && GetManaRatio() < 20) || (base2 = 523 && GetManaRatio() < 40))
use_spell = true;
else if (base2 = 38311 && GetManaRatio() < 10)
else if (base2 == 38311 && GetManaRatio() < 10)
use_spell = true;
}
else if (IsEndur){
if (base2 = 522 && GetEndurancePercent() < 40){
if (base2 == 522 && GetEndurancePercent() < 40){
use_spell = true;
}
}
@@ -3947,10 +3978,17 @@ int16 Mob::GetHealRate(uint16 spell_id, Mob* caster) {
bool Mob::TryFadeEffect(int slot)
{
if (!buffs[slot].spellid)
return false;
if(IsValidSpell(buffs[slot].spellid))
{
for(int i = 0; i < EFFECT_COUNT; i++)
{
if (!spells[buffs[slot].spellid].effectid[i])
continue;
if (spells[buffs[slot].spellid].effectid[i] == SE_CastOnFadeEffectAlways ||
spells[buffs[slot].spellid].effectid[i] == SE_CastOnRuneFadeEffect)
{
@@ -4637,16 +4675,16 @@ void Mob::DoKnockback(Mob *caster, uint32 pushback, uint32 pushup)
spu->x_pos = FloatToEQ19(GetX());
spu->y_pos = FloatToEQ19(GetY());
spu->z_pos = FloatToEQ19(GetZ());
spu->delta_x = NewFloatToEQ13(static_cast<float>(new_x));
spu->delta_y = NewFloatToEQ13(static_cast<float>(new_y));
spu->delta_z = NewFloatToEQ13(static_cast<float>(pushup));
spu->heading = FloatToEQ19(GetHeading());
spu->delta_x = FloatToEQ13(static_cast<float>(new_x));
spu->delta_y = FloatToEQ13(static_cast<float>(new_y));
spu->delta_z = FloatToEQ13(static_cast<float>(pushup));
spu->heading = FloatToEQ12(GetHeading());
spu->padding0002 =0;
spu->padding0006 =7;
spu->padding0014 =0x7f;
spu->padding0018 =0x5df27;
spu->animation = 0;
spu->delta_heading = NewFloatToEQ13(0);
spu->delta_heading = FloatToEQ10(0);
outapp_push->priority = 6;
entity_list.QueueClients(this, outapp_push, true);
CastToClient()->FastQueuePacket(&outapp_push);
@@ -4964,7 +5002,7 @@ void Mob::DoGravityEffect()
}
if(IsClient())
this->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), cur_x, cur_y, new_ground, GetHeading()*2); // I know the heading thing is weird(chance of movepc to halve the heading value, too lazy to figure out why atm)
this->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), cur_x, cur_y, new_ground, GetHeading());
else
this->GMMove(cur_x, cur_y, new_ground, GetHeading());
}
@@ -4992,6 +5030,18 @@ void Mob::SpreadVirus(uint16 spell_id, uint16 casterID)
}
}
void Mob::AddNimbusEffect(int effectid)
{
SetNimbusEffect(effectid);
auto outapp = new EQApplicationPacket(OP_AddNimbusEffect, sizeof(RemoveNimbusEffect_Struct));
auto ane = (RemoveNimbusEffect_Struct *)outapp->pBuffer;
ane->spawnid = GetID();
ane->nimbus_effect = effectid;
entity_list.QueueClients(this, outapp);
safe_delete(outapp);
}
void Mob::RemoveNimbusEffect(int effectid)
{
if (effectid == nimbus_effect1)
@@ -5589,8 +5639,7 @@ bool Mob::IsFacingMob(Mob *other)
if (!other)
return false;
float angle = HeadingAngleToMob(other);
// what the client uses appears to be 2x our internal heading
float heading = GetHeading() * 2.0f;
float heading = GetHeading();
if (angle > 472.0 && heading < 40.0)
angle = heading;
@@ -5604,15 +5653,13 @@ bool Mob::IsFacingMob(Mob *other)
}
// All numbers derived from the client
float Mob::HeadingAngleToMob(Mob *other)
float Mob::HeadingAngleToMob(float other_x, float other_y)
{
float mob_x = other->GetX();
float mob_y = other->GetY();
float this_x = GetX();
float this_y = GetY();
float y_diff = std::abs(this_y - mob_y);
float x_diff = std::abs(this_x - mob_x);
float y_diff = std::abs(this_y - other_y);
float x_diff = std::abs(this_x - other_x);
if (y_diff < 0.0000009999999974752427)
y_diff = 0.0000009999999974752427;
@@ -5620,13 +5667,13 @@ float Mob::HeadingAngleToMob(Mob *other)
// return the right thing based on relative quadrant
// I'm sure this could be improved for readability, but whatever
if (this_y >= mob_y) {
if (mob_x >= this_x)
if (this_y >= other_y) {
if (other_x >= this_x)
return (90.0f - angle + 90.0f) * 511.5f * 0.0027777778f;
if (mob_x <= this_x)
if (other_x <= this_x)
return (angle + 180.0f) * 511.5f * 0.0027777778f;
}
if (this_y > mob_y || mob_x > this_x)
if (this_y > other_y || other_x > this_x)
return angle * 511.5f * 0.0027777778f;
else
return (90.0f - angle + 270.0f) * 511.5f * 0.0027777778f;
+52 -8
View File
@@ -50,6 +50,8 @@ struct AuraRecord;
struct NewSpawn_Struct;
struct PlayerPositionUpdateServer_Struct;
const int COLLISION_BOX_SIZE = 8;
namespace EQEmu
{
struct ItemData;
@@ -162,6 +164,8 @@ public:
inline virtual bool IsMob() const { return true; }
inline virtual bool InZone() const { return true; }
bool is_distance_roamer;
//Somewhat sorted: needs documenting!
//Attack
@@ -175,7 +179,8 @@ public:
inline bool InFrontMob(Mob *other = 0, float ourx = 0.0f, float oury = 0.0f) const
{ return (!other || other == this) ? true : MobAngle(other, ourx, oury) < 56.0f; }
bool IsFacingMob(Mob *other); // kind of does the same as InFrontMob, but derived from client
float HeadingAngleToMob(Mob *other); // to keep consistent with client generated messages
float HeadingAngleToMob(Mob *other) { return HeadingAngleToMob(other->GetX(), other->GetY()); }
float HeadingAngleToMob(float other_x, float other_y); // to keep consistent with client generated messages
virtual void RangedAttack(Mob* other) { }
virtual void ThrowingAttack(Mob* other) { }
// 13 = Primary (default), 14 = secondary
@@ -199,6 +204,7 @@ public:
void ApplyMeleeDamageMods(uint16 skill, int &damage, Mob * defender = nullptr, ExtraAttackOptions *opts = nullptr);
int ACSum();
int offense(EQEmu::skills::SkillType skill);
int GetBestMeleeSkill();
void CalcAC() { mitigation_ac = ACSum(); }
int GetACSoftcap();
double GetSoftcapReturns();
@@ -278,6 +284,7 @@ public:
float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false,
int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false,
int level_override = -1);
int GetResist(uint8 resist_type);
int ResistPhysical(int level_diff, uint8 caster_level);
int ResistElementalWeaponDmg(const EQEmu::ItemInstance *item);
int CheckBaneDamage(const EQEmu::ItemInstance *item);
@@ -336,6 +343,7 @@ public:
void BuffFadeDetrimentalByCaster(Mob *caster);
void BuffFadeBySitModifier();
bool IsAffectedByBuff(uint16 spell_id);
bool IsAffectedByBuffByGlobalGroup(GlobalGroup group);
void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration);
int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1);
int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false);
@@ -348,6 +356,7 @@ public:
virtual int GetMaxSongSlots() const { return 0; }
virtual int GetMaxDiscSlots() const { return 0; }
virtual int GetMaxTotalSlots() const { return 0; }
bool HasDiscBuff();
virtual uint32 GetFirstBuffSlot(bool disc, bool song);
virtual uint32 GetLastBuffSlot(bool disc, bool song);
virtual void InitializeBuffSlots() { buffs = nullptr; current_buff_count = 0; }
@@ -376,6 +385,7 @@ public:
inline virtual uint32 GetNimbusEffect1() const { return nimbus_effect1; }
inline virtual uint32 GetNimbusEffect2() const { return nimbus_effect2; }
inline virtual uint32 GetNimbusEffect3() const { return nimbus_effect3; }
void AddNimbusEffect(int effectid);
void RemoveNimbusEffect(int effectid);
inline const glm::vec3& GetTargetRingLocation() const { return m_TargetRing; }
inline float GetTargetRingX() const { return m_TargetRing.x; }
@@ -432,6 +442,20 @@ public:
inline uint8 GetDrakkinHeritage() const { return drakkin_heritage; }
inline uint8 GetDrakkinTattoo() const { return drakkin_tattoo; }
inline uint8 GetDrakkinDetails() const { return drakkin_details; }
inline void ChangeRace(uint16 in) { race = in; }
inline void ChangeGender(uint8 in) { gender = in;}
inline void ChangeTexture(uint8 in) { texture = in; }
inline void ChangeHelmTexture(uint8 in) { helmtexture = in; }
inline void ChangeHairColor(uint8 in) { haircolor = in; }
inline void ChangeBeardColor(uint8 in) { beardcolor = in; }
inline void ChangeEyeColor1(uint8 in) { eyecolor1 = in; }
inline void ChangeEyeColor2(uint8 in) { eyecolor2 = in; }
inline void ChangeHairStyle(uint8 in) { hairstyle = in; }
inline void ChangeLuclinFace(uint8 in) { luclinface = in; }
inline void ChangeBeard(uint8 in) { beard = in; }
inline void ChangeDrakkinHeritage(uint8 in) { drakkin_heritage = in; }
inline void ChangeDrakkinTattoo(uint8 in) { drakkin_tattoo = in; }
inline void ChangeDrakkinDetails(uint8 in) { drakkin_details = in; }
inline uint32 GetArmorTint(uint8 i) const { return armor_tint.Slot[(i < EQEmu::textures::materialCount) ? i : 0].Color; }
inline uint8 GetClass() const { return class_; }
inline uint8 GetLevel() const { return level; }
@@ -548,12 +572,20 @@ public:
void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu);
void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu);
void SendPosition();
void StopMoving();
void StopMoving(float new_heading);
void SetSpawned() { spawned = true; };
bool Spawned() { return spawned; };
virtual bool ShouldISpawnFor(Client *c) { return true; }
void SetFlyMode(uint8 flymode);
inline void Teleport(glm::vec3 NewPosition) { m_Position.x = NewPosition.x; m_Position.y = NewPosition.y;
m_Position.z = NewPosition.z; };
void TryMoveAlong(float distance, float angle, bool send = true);
void ProcessForcedMovement();
inline void IncDeltaX(float in) { m_Delta.x += in; }
inline void IncDeltaY(float in) { m_Delta.y += in; }
inline void IncDeltaZ(float in) { m_Delta.z += in; }
inline void SetForcedMovement(int in) { ForcedMovement = in; }
//AI
static uint32 GetLevelCon(uint8 mylevel, uint8 iOtherLevel);
@@ -586,10 +618,12 @@ public:
void AddFeignMemory(Client* attacker);
void RemoveFromFeignMemory(Client* attacker);
void ClearFeignMemory();
bool IsOnFeignMemory(Client *attacker) const;
void PrintHateListToClient(Client *who) { hate_list.PrintHateListToClient(who); }
std::list<struct_HateList*>& GetHateList() { return hate_list.GetHateList(); }
bool CheckLosFN(Mob* other);
bool CheckLosFN(float posX, float posY, float posZ, float mobSize);
static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget);
inline void SetChanged() { pLastChange = Timer::GetCurrentTime(); }
inline const uint32 LastChange() const { return pLastChange; }
inline void SetLastLosState(bool value) { last_los_check = value; }
@@ -664,6 +698,8 @@ public:
void SetFollowDistance(uint32 dist) { follow_dist = dist; }
uint32 GetFollowID() const { return follow; }
uint32 GetFollowDistance() const { return follow_dist; }
inline bool IsRareSpawn() const { return rare_spawn; }
inline void SetRareSpawn(bool in) { rare_spawn = in; }
virtual void Message(uint32 type, const char* message, ...) { }
virtual void Message_StringID(uint32 type, uint32 string_id, uint32 distance = 0) { }
@@ -943,14 +979,16 @@ public:
inline bool IsBlind() { return spellbonuses.IsBlind; }
inline bool CheckAggro(Mob* other) {return hate_list.IsEntOnHateList(other);}
float CalculateHeadingToTarget(float in_x, float in_y);
float CalculateHeadingToTarget(float in_x, float in_y) { return HeadingAngleToMob(in_x, in_y); }
virtual bool CalculateNewPosition(float x, float y, float z, int speed, bool checkZ = true, bool calcheading = true);
float CalculateDistance(float x, float y, float z);
float GetGroundZ(float new_x, float new_y, float z_offset=0.0);
void SendTo(float new_x, float new_y, float new_z);
void SendToFixZ(float new_x, float new_y, float new_z);
void FixZ();
float GetModelOffset() const;
float GetZOffset() const;
void FixZ(int32 z_find_offset = 5);
float GetFixedZ(glm::vec3 position, int32 z_find_offset = 5);
void NPCSpecialAttacks(const char* parse, int permtag, bool reset = true, bool remove = false);
inline uint32 DontHealMeBefore() const { return pDontHealMeBefore; }
inline uint32 DontBuffMeBefore() const { return pDontBuffMeBefore; }
@@ -1104,8 +1142,6 @@ public:
int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item);
int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr);
float last_z;
// Bots HealRotation methods
#ifdef BOTS
bool IsHealRotationTarget() { return (m_target_of_heal_rotation.use_count() && m_target_of_heal_rotation.get()); }
@@ -1127,7 +1163,7 @@ protected:
int _GetWalkSpeed() const;
int _GetRunSpeed() const;
int _GetFearSpeed() const;
virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed);
virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ = true, bool calcHeading = true);
virtual bool AI_EngagedCastCheck() { return(false); }
virtual bool AI_PursueCastCheck() { return(false); }
@@ -1200,6 +1236,7 @@ protected:
uint32 follow;
uint32 follow_dist;
bool no_target_hotkey;
bool rare_spawn;
uint32 m_PlayerState;
uint32 GetPlayerState() { return m_PlayerState; }
@@ -1221,7 +1258,8 @@ protected:
uint32 npctype_id;
glm::vec4 m_Position;
/* Used to determine when an NPC has traversed so many units - to send a zone wide pos update */
glm::vec4 last_major_update_position;
glm::vec4 last_major_update_position;
int animation; // this is really what MQ2 calls SpeedRun just packed like (int)(SpeedRun * 40.0f)
float base_size;
float size;
@@ -1263,6 +1301,7 @@ protected:
virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; }
void CalculateNewFearpoint();
float FindGroundZ(float new_x, float new_y, float z_offset=0.0);
float FindDestGroundZ(glm::vec3 dest, float z_offset=0.0);
glm::vec3 UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached);
glm::vec3 HandleStuckPath(const glm::vec3 &To, const glm::vec3 &From);
@@ -1282,6 +1321,9 @@ protected:
char lastname[64];
glm::vec4 m_Delta;
// just locs around them to double check, if we do expand collision this should be cached on movement
// ideally we should use real models, but this should be quick and work mostly
glm::vec4 m_CollisionBox[COLLISION_BOX_SIZE];
EQEmu::LightSourceProfile m_Light;
@@ -1392,6 +1434,7 @@ protected:
std::unique_ptr<Timer> AI_movement_timer;
std::unique_ptr<Timer> AI_target_check_timer;
bool movetimercompleted;
int8 ForcedMovement; // push
bool permarooted;
std::unique_ptr<Timer> AI_scan_area_timer;
std::unique_ptr<Timer> AI_walking_timer;
@@ -1409,6 +1452,7 @@ protected:
void AddItemFactionBonus(uint32 pFactionID,int32 bonus);
int32 GetItemFactionBonus(uint32 pFactionID);
void ClearItemFactionBonuses();
Timer mHateListCleanup;
bool flee_mode;
Timer flee_timer;
+337 -186
View File
@@ -29,12 +29,16 @@
#include "quest_parser_collection.h"
#include "string_ids.h"
#include "water_map.h"
#include "fastmath.h"
#include <glm/gtx/projection.hpp>
#include <algorithm>
#include <iostream>
#include <limits>
#include <math.h>
extern EntityList entity_list;
extern FastMath g_Math;
extern Zone *zone;
@@ -47,7 +51,7 @@ extern Zone *zone;
#endif
//NOTE: do NOT pass in beneficial and detrimental spell types into the same call here!
bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates) {
if (!tar)
return false;
@@ -57,7 +61,12 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
if(AI_HasSpells() == false)
return false;
if (iChance < 100) {
// Rooted mobs were just standing around when tar out of range.
// Any sane mob would cast if they can.
bool cast_only_option = (IsRooted() && !CombatRange(tar));
// innates are always attempted
if (!cast_only_option && iChance < 100 && !bInnates) {
if (zone->random.Int(0, 100) >= iChance)
return false;
}
@@ -80,6 +89,19 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
//return false;
continue;
}
if ((AIspells[i].priority == 0 && !bInnates) || (AIspells[i].priority != 0 && bInnates)) {
// so "innate" spells are special and spammed a bit
// we define an innate spell as a spell with priority 0
continue;
}
if (AIspells[i].min_hp != 0 && GetIntHPRatio() < AIspells[i].min_hp)
continue;
if (AIspells[i].max_hp != 0 && GetIntHPRatio() > AIspells[i].max_hp)
continue;
if (iSpellTypes & AIspells[i].type) {
// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
int32 mana_cost = AIspells[i].manacost;
@@ -87,15 +109,19 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
mana_cost = spells[AIspells[i].spellid].mana;
else if (mana_cost == -2)
mana_cost = 0;
// this is ugly -- ignore distance for hatelist spells, looks like the client is only checking distance for some targettypes in CastSpell,
// should probably match that eventually. This should be good enough for now I guess ....
if (
((
(spells[AIspells[i].spellid].targettype==ST_AECaster || spells[AIspells[i].spellid].targettype==ST_AEBard)
&& dist2 <= spells[AIspells[i].spellid].aoerange*spells[AIspells[i].spellid].aoerange
) ||
dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range
)
(
(spells[AIspells[i].spellid].targettype == ST_HateList || spells[AIspells[i].spellid].targettype == ST_AETargetHateList) ||
(
(spells[AIspells[i].spellid].targettype==ST_AECaster || spells[AIspells[i].spellid].targettype==ST_AEBard)
&& dist2 <= spells[AIspells[i].spellid].aoerange*spells[AIspells[i].spellid].aoerange
) ||
dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range
)
&& (mana_cost <= GetMana() || GetMana() == GetMaxMana())
&& (AIspells[i].time_cancast + (zone->random.Int(0, 4) * 1000)) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time.
&& (AIspells[i].time_cancast + (zone->random.Int(0, 4) * 500)) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time.
) {
#if MobAI_DEBUG_Spells >= 21
@@ -123,7 +149,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_Root: {
Mob *rootee = GetHateRandom();
if (rootee && !rootee->IsRooted() && zone->random.Roll(50)
if (rootee && !rootee->IsRooted() && !rootee->IsFeared() && (bInnates || zone->random.Roll(50))
&& rootee->DontRootMeBefore() < Timer::GetCurrentTime()
&& rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
@@ -162,7 +188,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_InCombatBuff: {
if(zone->random.Roll(50))
if(bInnates || zone->random.Roll(50))
{
AIDoSpellCast(i, tar, mana_cost);
return true;
@@ -181,7 +207,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Slow:
case SpellType_Debuff: {
Mob * debuffee = GetHateRandom();
if (debuffee && manaR >= 10 && zone->random.Roll(70) &&
if (debuffee && manaR >= 10 && (bInnates || zone->random.Roll(70)) &&
debuffee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0) {
if (!checked_los) {
if (!CheckLosFN(debuffee))
@@ -195,8 +221,8 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_Nuke: {
if (
manaR >= 10 && zone->random.Roll(70)
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
manaR >= 10 && (bInnates || (zone->random.Roll(70)
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), false) >= 0)) // saying it's a nuke here, AI shouldn't care too much if overwriting
) {
if(!checked_los) {
if(!CheckLosFN(tar))
@@ -209,7 +235,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
break;
}
case SpellType_Dispel: {
if(zone->random.Roll(15))
if(bInnates || zone->random.Roll(15))
{
if(!checked_los) {
if(!CheckLosFN(tar))
@@ -225,7 +251,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
break;
}
case SpellType_Mez: {
if(zone->random.Roll(20))
if(bInnates || zone->random.Roll(20))
{
Mob * mezTar = nullptr;
mezTar = entity_list.GetTargetForMez(this);
@@ -241,7 +267,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Charm:
{
if(!IsPet() && zone->random.Roll(20))
if(!IsPet() && (bInnates || zone->random.Roll(20)))
{
Mob * chrmTar = GetHateRandom();
if(chrmTar && chrmTar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0)
@@ -255,7 +281,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Pet: {
//keep mobs from recasting pets when they have them.
if (!IsPet() && !GetPetID() && zone->random.Roll(25)) {
if (!IsPet() && !GetPetID() && (bInnates || zone->random.Roll(25))) {
AIDoSpellCast(i, tar, mana_cost);
return true;
}
@@ -263,7 +289,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_Lifetap: {
if (GetHPRatio() <= 95
&& zone->random.Roll(50)
&& (bInnates || zone->random.Roll(50))
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
if(!checked_los) {
@@ -279,7 +305,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Snare: {
if (
!tar->IsRooted()
&& zone->random.Roll(50)
&& (bInnates || zone->random.Roll(50))
&& tar->DontSnareMeBefore() < Timer::GetCurrentTime()
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
@@ -297,7 +323,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_DOT: {
if (
zone->random.Roll(60)
(bInnates || zone->random.Roll(60))
&& tar->DontDotMeBefore() < Timer::GetCurrentTime()
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
@@ -498,8 +524,8 @@ void NPC::AI_Start(uint32 iMoveDelay) {
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(1000));
AIautocastspell_timer->Disable();
} else {
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(750));
AIautocastspell_timer->Start(RandomTimer(0, 15000), false);
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(500));
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
if (NPCTypedata) {
@@ -892,29 +918,26 @@ void Client::AI_Process()
}
}
if(IsPet())
{
Mob* owner = GetOwner();
if(owner == nullptr)
if (IsPet()) {
Mob *owner = GetOwner();
if (owner == nullptr)
return;
float dist = DistanceSquared(m_Position, owner->GetPosition());
if (dist >= 400)
{
if(AI_movement_timer->Check())
{
int nspeed = (dist >= 5625 ? GetRunspeed() : GetWalkspeed());
if (dist >= 202500) { // >= 450 distance
Teleport(static_cast<glm::vec3>(owner->GetPosition()));
SendPositionUpdate(); // this shouldn't happen a lot (and hard to make it) so lets not rate limit
} else if (dist >= 400) { // >=20
if (AI_movement_timer->Check()) {
int nspeed = (dist >= 1225 ? GetRunspeed() : GetWalkspeed()); // >= 35
animation = nspeed;
nspeed *= 2;
SetCurrentSpeed(nspeed);
CalculateNewPosition(owner->GetX(), owner->GetY(), owner->GetZ(), nspeed);
}
}
else
{
if(moved)
{
} else {
if (moved) {
SetCurrentSpeed(0);
moved = false;
}
@@ -923,6 +946,91 @@ void Client::AI_Process()
}
}
void Mob::ProcessForcedMovement()
{
// we are being pushed, we will hijack this movement timer
// this also needs to be done before casting to have a chance to interrupt
// this flag won't be set if the mob can't be pushed (rooted etc)
if (AI_movement_timer->Check()) {
bool bPassed = true;
glm::vec3 normal;
// no zone map = fucked
if (zone->HasMap()) {
// in front
m_CollisionBox[0].x = m_Position.x + 3.0f * g_Math.FastSin(0.0f);
m_CollisionBox[0].y = m_Position.y + 3.0f * g_Math.FastCos(0.0f);
m_CollisionBox[0].z = m_Position.z;
// 45 right front
m_CollisionBox[1].x = m_Position.x + 3.0f * g_Math.FastSin(64.0f);
m_CollisionBox[1].y = m_Position.y + 3.0f * g_Math.FastCos(64.0f);
m_CollisionBox[1].z = m_Position.z;
// to right
m_CollisionBox[2].x = m_Position.x + 3.0f * g_Math.FastSin(128.0f);
m_CollisionBox[2].y = m_Position.y + 3.0f * g_Math.FastCos(128.0f);
m_CollisionBox[2].z = m_Position.z;
// 45 right back
m_CollisionBox[3].x = m_Position.x + 3.0f * g_Math.FastSin(192.0f);
m_CollisionBox[3].y = m_Position.y + 3.0f * g_Math.FastCos(192.0f);
m_CollisionBox[3].z = m_Position.z;
// behind
m_CollisionBox[4].x = m_Position.x + 3.0f * g_Math.FastSin(256.0f);
m_CollisionBox[4].y = m_Position.y + 3.0f * g_Math.FastCos(256.0f);
m_CollisionBox[4].z = m_Position.z;
// 45 left back
m_CollisionBox[5].x = m_Position.x + 3.0f * g_Math.FastSin(320.0f);
m_CollisionBox[5].y = m_Position.y + 3.0f * g_Math.FastCos(320.0f);
m_CollisionBox[5].z = m_Position.z;
// to left
m_CollisionBox[6].x = m_Position.x + 3.0f * g_Math.FastSin(384.0f);
m_CollisionBox[6].y = m_Position.y + 3.0f * g_Math.FastCos(384.0f);
m_CollisionBox[6].z = m_Position.z;
// 45 left front
m_CollisionBox[7].x = m_Position.x + 3.0f * g_Math.FastSin(448.0f);
m_CollisionBox[7].y = m_Position.y + 3.0f * g_Math.FastCos(448.0f);
m_CollisionBox[7].z = m_Position.z;
// collision happened, need to move along the wall
float distance = 0.0f, shortest = std::numeric_limits<float>::infinity();
glm::vec3 tmp_nrm;
for (auto &vec : m_CollisionBox) {
if (zone->zonemap->DoCollisionCheck(vec, vec + m_Delta, tmp_nrm, distance)) {
bPassed = false; // lets try with new projection next pass
if (distance < shortest) {
normal = tmp_nrm;
shortest = distance;
}
}
}
}
if (bPassed) {
ForcedMovement = 0;
Teleport(m_Position + m_Delta);
m_Delta = glm::vec4();
SendPositionUpdate();
pLastChange = Timer::GetCurrentTime();
FixZ(); // so we teleport to the ground locally, we want the client to interpolate falling etc
} else if (--ForcedMovement) {
if (normal.z < -0.15f) // prevent too much wall climbing. ex. OMM's room in anguish
normal.z = 0.0f;
auto proj = glm::proj(static_cast<glm::vec3>(m_Delta), normal);
m_Delta.x -= proj.x;
m_Delta.y -= proj.y;
m_Delta.z -= proj.z;
} else {
m_Delta = glm::vec4(); // well, we failed to find a spot to be forced to, lets give up
}
}
}
void Mob::AI_Process() {
if (!IsAIControlled())
return;
@@ -930,6 +1038,7 @@ void Mob::AI_Process() {
if (!(AI_think_timer->Check() || attack_timer.Check(false)))
return;
if (IsCasting())
return;
@@ -1013,6 +1122,18 @@ void Mob::AI_Process() {
if (!(m_PlayerState & static_cast<uint32>(PlayerState::Aggressive)))
SendAddPlayerState(PlayerState::Aggressive);
// NPCs will forget people after 10 mins of not interacting with them or out of range
// both of these maybe zone specific, hardcoded for now
if (mHateListCleanup.Check()) {
hate_list.RemoveStaleEntries(600000, 600.0f);
if (hate_list.IsHateListEmpty()) {
AI_Event_NoLongerEngaged();
zone->DelAggroMob();
if (IsNPC() && !RuleB(Aggro, AllowTickPulling))
ResetAssistCap();
}
}
// we are prevented from getting here if we are blind and don't have a target in range
// from above, so no extra blind checks needed
if ((IsRooted() && !GetSpecialAbility(IGNORE_ROOT_AGGRO_RULES)) || IsBlind())
@@ -1290,7 +1411,7 @@ void Mob::AI_Process() {
if (AI_PursueCastCheck()) {
//we did something, so do not process movement.
}
else if (AI_movement_timer->Check())
else if (AI_movement_timer->Check() && target)
{
if (!IsRooted()) {
Log(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", target->GetName());
@@ -1383,19 +1504,32 @@ void Mob::AI_Process() {
//if(owner->IsClient())
// printf("Pet start pos: (%f, %f, %f)\n", GetX(), GetY(), GetZ());
float dist = DistanceSquared(m_Position, owner->GetPosition());
if (dist >= 400)
glm::vec4 ownerPos = owner->GetPosition();
float dist = DistanceSquared(m_Position, ownerPos);
float distz = ownerPos.z - m_Position.z;
if (dist >= 400 || distz > 100)
{
int speed = GetWalkspeed();
if (dist >= 5625)
if (dist >= 1225) // 35
speed = GetRunspeed();
CalculateNewPosition(owner->GetX(), owner->GetY(), owner->GetZ(), speed);
if (dist >= 202500 || distz > 100) // dist >= 450
{
m_Position = ownerPos;
SendPositionUpdate();
moved = true;
}
else
{
CalculateNewPosition(owner->GetX(), owner->GetY(), owner->GetZ(), speed);
}
}
else
{
if(moved)
{
this->FixZ();
SetCurrentSpeed(0);
moved = false;
}
@@ -1527,15 +1661,20 @@ void NPC::AI_DoMovement() {
roambox_movingto_x = zone->random.Real(roambox_min_x+1,roambox_max_x-1);
if (roambox_movingto_y > roambox_max_y || roambox_movingto_y < roambox_min_y)
roambox_movingto_y = zone->random.Real(roambox_min_y+1,roambox_max_y-1);
Log(Logs::Detail, Logs::AI,
"Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)",
roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y,
roambox_max_y, roambox_movingto_x, roambox_movingto_y);
}
Log(Logs::Detail, Logs::AI, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)",
roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y);
float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5) + GetModelOffset();
float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5) + GetZOffset();
if (!CalculateNewPosition(roambox_movingto_x, roambox_movingto_y, new_z, walksp, true))
{
this->FixZ(); // FixZ on final arrival point.
roambox_movingto_x = roambox_max_x + 1; // force update
pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay);
SetMoving(false);
@@ -1572,18 +1711,22 @@ void NPC::AI_DoMovement() {
}
this->FixZ();
SendPosition();
//kick off event_waypoint arrive
char temp[16];
sprintf(temp, "%d", cur_wp);
parse->EventNPC(EVENT_WAYPOINT_ARRIVE, CastToNPC(), nullptr, temp, 0);
// start moving directly to next waypoint if we're at a 0 pause waypoint and we didn't get quest halted.
if (!AI_walking_timer->Enabled())
// No need to move as we are there. Next loop will
// take care of normal grids, even at pause 0.
// We do need to call and setup a wp if we're cur_wp=-2
// as that is where roamer is unset and we don't want
// the next trip through to move again based on grid stuff.
doMove = false;
if (cur_wp == -2) {
AI_SetupNextWaypoint();
else
doMove = false;
}
// wipe feign memory since we reached our first waypoint
if(cur_wp == 1)
ClearFeignMemory();
@@ -1734,6 +1877,12 @@ void Mob::AI_Event_Engaged(Mob* attacker, bool iYellForHelp) {
SetAppearance(eaStanding);
/*
Kick off auto cast timer
*/
if (this->IsNPC())
this->CastToNPC()->AIautocastspell_timer->Start(300, false);
if (iYellForHelp) {
if(IsPet()) {
GetOwner()->AI_Event_Engaged(attacker, iYellForHelp);
@@ -1839,14 +1988,17 @@ bool NPC::AI_EngagedCastCheck() {
Log(Logs::Detail, Logs::AI, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells.");
// try casting a heal or gate
if (!AICastSpell(this, AISpellVar.engaged_beneficial_self_chance, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) {
// try casting a heal on nearby
if (!entity_list.AICheckCloseBeneficialSpells(this, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) {
//nobody to heal, try some detrimental spells.
if(!AICastSpell(GetTarget(), AISpellVar.engaged_detrimental_chance, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root)) {
//no spell to cast, try again soon.
AIautocastspell_timer->Start(RandomTimer(AISpellVar.engaged_no_sp_recast_min, AISpellVar.engaged_no_sp_recast_max), false);
// first try innate (spam) spells
if(!AICastSpell(GetTarget(), 0, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root, true)) {
// try casting a heal or gate
if (!AICastSpell(this, AISpellVar.engaged_beneficial_self_chance, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) {
// try casting a heal on nearby
if (!entity_list.AICheckCloseBeneficialSpells(this, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) {
//nobody to heal, try some detrimental spells.
if(!AICastSpell(GetTarget(), AISpellVar.engaged_detrimental_chance, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root)) {
//no spell to cast, try again soon.
AIautocastspell_timer->Start(RandomTimer(AISpellVar.engaged_no_sp_recast_min, AISpellVar.engaged_no_sp_recast_max), false);
}
}
}
}
@@ -1861,10 +2013,13 @@ bool NPC::AI_PursueCastCheck() {
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
Log(Logs::Detail, Logs::AI, "Engaged (pursuing) autocast check triggered. Trying to cast offensive spells.");
if(!AICastSpell(GetTarget(), AISpellVar.pursue_detrimental_chance, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff)) {
//no spell cast, try again soon.
AIautocastspell_timer->Start(RandomTimer(AISpellVar.pursue_no_sp_recast_min, AISpellVar.pursue_no_sp_recast_max), false);
} //else, spell casting finishing will reset the timer.
// checking innate (spam) spells first
if(!AICastSpell(GetTarget(), AISpellVar.pursue_detrimental_chance, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff, true)) {
if(!AICastSpell(GetTarget(), AISpellVar.pursue_detrimental_chance, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff)) {
//no spell cast, try again soon.
AIautocastspell_timer->Start(RandomTimer(AISpellVar.pursue_no_sp_recast_min, AISpellVar.pursue_no_sp_recast_max), false);
} //else, spell casting finishing will reset the timer.
}
return(true);
}
return(false);
@@ -2004,15 +2159,34 @@ bool Mob::Rampage(ExtraAttackOptions *opts)
if (m_target) {
if (m_target == GetTarget())
continue;
if (CombatRange(m_target)) {
if (DistanceSquaredNoZ(GetPosition(), m_target->GetPosition()) <= NPC_RAMPAGE_RANGE2) {
ProcessAttackRounds(m_target, opts);
index_hit++;
}
}
}
if (RuleB(Combat, RampageHitsTarget) && index_hit < rampage_targets)
ProcessAttackRounds(GetTarget(), opts);
if (RuleB(Combat, RampageHitsTarget)) {
if (index_hit < rampage_targets)
ProcessAttackRounds(GetTarget(), opts);
} else { // let's do correct behavior here, if they set above rule we can assume they want non-live like behavior
if (index_hit < rampage_targets) {
// so we go over in reverse order and skip range check
// lets do it this way to still support non-live-like >1 rampage targets
// likely live is just a fall through of the last valid mob
for (auto i = RampageArray.crbegin(); i != RampageArray.crend(); ++i) {
if (index_hit >= rampage_targets)
break;
auto m_target = entity_list.GetMob(*i);
if (m_target) {
if (m_target == GetTarget())
continue;
ProcessAttackRounds(m_target, opts);
index_hit++;
}
}
}
}
m_specialattacks = eSpecialAttacks::None;
@@ -2307,14 +2481,13 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) {
return false;
}
DBnpcspells_Struct* parentlist = database.GetNPCSpells(spell_list->parent_list);
uint32 i;
#if MobAI_DEBUG_Spells >= 10
std::string debug_msg = StringFormat("Loading NPCSpells onto %s: dbspellsid=%u", this->GetName(), iDBSpellsID);
if (spell_list) {
debug_msg.append(StringFormat(" (found, %u), parentlist=%u", spell_list->numentries, spell_list->parent_list));
debug_msg.append(StringFormat(" (found, %u), parentlist=%u", spell_list->entries.size(), spell_list->parent_list));
if (spell_list->parent_list) {
if (parentlist)
debug_msg.append(StringFormat(" (found, %u)", parentlist->numentries));
debug_msg.append(StringFormat(" (found, %u)", parentlist->entries.size()));
else
debug_msg.append(" (not found)");
}
@@ -2362,14 +2535,11 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) {
_idle_no_sp_recast_min = parentlist->idle_no_sp_recast_min;
_idle_no_sp_recast_max = parentlist->idle_no_sp_recast_max;
_idle_beneficial_chance = parentlist->idle_beneficial_chance;
for (i=0; i<parentlist->numentries; i++) {
if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spellid > 0) {
if (!IsSpellInList(spell_list, parentlist->entries[i].spellid))
for (auto &e : parentlist->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) {
if (!IsSpellInList(spell_list, e.spellid))
{
AddSpellToNPCList(parentlist->entries[i].priority,
parentlist->entries[i].spellid, parentlist->entries[i].type,
parentlist->entries[i].manacost, parentlist->entries[i].recast_delay,
parentlist->entries[i].resist_adjust);
AddSpellToNPCList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp);
}
}
}
@@ -2408,14 +2578,12 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) {
_idle_beneficial_chance = spell_list->idle_beneficial_chance;
}
for (i=0; i<spell_list->numentries; i++) {
if (GetLevel() >= spell_list->entries[i].minlevel && GetLevel() <= spell_list->entries[i].maxlevel && spell_list->entries[i].spellid > 0) {
AddSpellToNPCList(spell_list->entries[i].priority,
spell_list->entries[i].spellid, spell_list->entries[i].type,
spell_list->entries[i].manacost, spell_list->entries[i].recast_delay,
spell_list->entries[i].resist_adjust);
for (auto &e : spell_list->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) {
AddSpellToNPCList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp);
}
}
std::sort(AIspells.begin(), AIspells.end(), [](const AISpells_Struct& a, const AISpells_Struct& b) {
return a.priority > b.priority;
});
@@ -2553,16 +2721,14 @@ bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSp
}
bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID) {
for (uint32 i=0; i < spell_list->numentries; i++) {
if (spell_list->entries[i].spellid == iSpellID)
return true;
}
return false;
auto it = std::find_if(spell_list->entries.begin(), spell_list->entries.end(),
[iSpellID](const DBnpcspells_entries_Struct &a) { return a.spellid == iSpellID; });
return it != spell_list->entries.end();
}
// adds a spell to the list, taking into account priority and resorting list as needed.
void NPC::AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType,
int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust)
int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp)
{
if(!IsValidSpell(iSpellID))
@@ -2578,12 +2744,14 @@ void NPC::AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType,
t.recast_delay = iRecastDelay;
t.time_cancast = 0;
t.resist_adjust = iResistAdjust;
t.min_hp = min_hp;
t.max_hp = max_hp;
AIspells.push_back(t);
// If we're going from an empty list, we need to start the timer
if (AIspells.size() == 1)
AIautocastspell_timer->Start(RandomTimer(0, 15000), false);
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
void NPC::RemoveSpellFromNPCList(int16 spell_id)
@@ -2606,131 +2774,114 @@ void NPC::AISpellsList(Client *c)
return;
for (auto it = AIspells.begin(); it != AIspells.end(); ++it)
c->Message(0, "%s (%d): Type %d, Priority %d",
spells[it->spellid].name, it->spellid, it->type, it->priority);
c->Message(0, "%s (%d): Type %d, Priority %d, Recast Delay %d, Resist Adjust %d, Min HP %d, Max HP %d",
spells[it->spellid].name, it->spellid, it->type, it->priority, it->recast_delay, it->resist_adjust, it->min_hp, it->max_hp);
return;
}
DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) {
DBnpcspells_Struct *ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID)
{
if (iDBSpellsID == 0)
return nullptr;
if (!npc_spells_cache) {
npc_spells_maxid = GetMaxNPCSpellsID();
npc_spells_cache = new DBnpcspells_Struct*[npc_spells_maxid+1];
npc_spells_loadtried = new bool[npc_spells_maxid+1];
for (uint32 i=0; i<=npc_spells_maxid; i++) {
npc_spells_cache[i] = nullptr;
npc_spells_loadtried[i] = false;
}
auto it = npc_spells_cache.find(iDBSpellsID);
if (it != npc_spells_cache.end()) { // it's in the cache, easy =)
return &it->second;
}
if (iDBSpellsID > npc_spells_maxid)
return nullptr;
if (npc_spells_cache[iDBSpellsID]) { // it's in the cache, easy =)
return npc_spells_cache[iDBSpellsID];
}
else if (!npc_spells_loadtried[iDBSpellsID]) { // no reason to ask the DB again if we have failed once already
npc_spells_loadtried[iDBSpellsID] = true;
if (!npc_spells_loadtried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already
npc_spells_loadtried.insert(iDBSpellsID);
std::string query = StringFormat("SELECT id, parent_list, attack_proc, proc_chance, "
"range_proc, rproc_chance, defensive_proc, dproc_chance, "
"fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, "
"engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, "
"pursue_no_sp_recast_min, pursue_no_sp_recast_max, "
"pursue_d_chance, idle_no_sp_recast_min, idle_no_sp_recast_max, "
"idle_b_chance FROM npc_spells WHERE id=%d", iDBSpellsID);
auto results = QueryDatabase(query);
if (!results.Success()) {
"range_proc, rproc_chance, defensive_proc, dproc_chance, "
"fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, "
"engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, "
"pursue_no_sp_recast_min, pursue_no_sp_recast_max, "
"pursue_d_chance, idle_no_sp_recast_min, idle_no_sp_recast_max, "
"idle_b_chance FROM npc_spells WHERE id=%d",
iDBSpellsID);
auto results = QueryDatabase(query);
if (!results.Success()) {
return nullptr;
}
}
if (results.RowCount() != 1)
return nullptr;
if (results.RowCount() != 1)
return nullptr;
auto row = results.begin();
uint32 tmpparent_list = atoi(row[1]);
uint16 tmpattack_proc = atoi(row[2]);
uint8 tmpproc_chance = atoi(row[3]);
uint16 tmprange_proc = atoi(row[4]);
int16 tmprproc_chance = atoi(row[5]);
uint16 tmpdefensive_proc = atoi(row[6]);
int16 tmpdproc_chance = atoi(row[7]);
uint32 tmppfail_recast = atoi(row[8]);
uint32 tmpengaged_no_sp_recast_min = atoi(row[9]);
uint32 tmpengaged_no_sp_recast_max = atoi(row[10]);
uint8 tmpengaged_b_self_chance = atoi(row[11]);
uint8 tmpengaged_b_other_chance = atoi(row[12]);
uint8 tmpengaged_d_chance = atoi(row[13]);
uint32 tmppursue_no_sp_recast_min = atoi(row[14]);
uint32 tmppursue_no_sp_recast_max = atoi(row[15]);
uint8 tmppursue_d_chance = atoi(row[16]);
uint32 tmpidle_no_sp_recast_min = atoi(row[17]);
uint32 tmpidle_no_sp_recast_max = atoi(row[18]);
uint8 tmpidle_b_chance = atoi(row[19]);
auto row = results.begin();
DBnpcspells_Struct spell_set;
spell_set.parent_list = atoi(row[1]);
spell_set.attack_proc = atoi(row[2]);
spell_set.proc_chance = atoi(row[3]);
spell_set.range_proc = atoi(row[4]);
spell_set.rproc_chance = atoi(row[5]);
spell_set.defensive_proc = atoi(row[6]);
spell_set.dproc_chance = atoi(row[7]);
spell_set.fail_recast = atoi(row[8]);
spell_set.engaged_no_sp_recast_min = atoi(row[9]);
spell_set.engaged_no_sp_recast_max = atoi(row[10]);
spell_set.engaged_beneficial_self_chance = atoi(row[11]);
spell_set.engaged_beneficial_other_chance = atoi(row[12]);
spell_set.engaged_detrimental_chance = atoi(row[13]);
spell_set.pursue_no_sp_recast_min = atoi(row[14]);
spell_set.pursue_no_sp_recast_max = atoi(row[15]);
spell_set.pursue_detrimental_chance = atoi(row[16]);
spell_set.idle_no_sp_recast_min = atoi(row[17]);
spell_set.idle_no_sp_recast_max = atoi(row[18]);
spell_set.idle_beneficial_chance = atoi(row[19]);
// pulling fixed values from an auto-increment field is dangerous...
query = StringFormat("SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, resist_adjust "
query = StringFormat(
"SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, min_hp, max_hp, resist_adjust "
#ifdef BOTS
"FROM %s "
"WHERE npc_spells_id=%d ORDER BY minlevel", (iDBSpellsID >= 3001 && iDBSpellsID <= 3016 ? "bot_spells_entries" : "npc_spells_entries"), iDBSpellsID);
"FROM %s "
"WHERE npc_spells_id=%d ORDER BY minlevel",
(iDBSpellsID >= 3001 && iDBSpellsID <= 3016 ? "bot_spells_entries" : "npc_spells_entries"),
iDBSpellsID);
#else
"FROM npc_spells_entries "
"WHERE npc_spells_id=%d ORDER BY minlevel", iDBSpellsID);
"FROM npc_spells_entries "
"WHERE npc_spells_id=%d ORDER BY minlevel",
iDBSpellsID);
#endif
results = QueryDatabase(query);
results = QueryDatabase(query);
if (!results.Success())
{
if (!results.Success()) {
return nullptr;
}
}
uint32 tmpSize = sizeof(DBnpcspells_Struct) + (sizeof(DBnpcspells_entries_Struct) * results.RowCount());
npc_spells_cache[iDBSpellsID] = (DBnpcspells_Struct*) new uchar[tmpSize];
memset(npc_spells_cache[iDBSpellsID], 0, tmpSize);
npc_spells_cache[iDBSpellsID]->parent_list = tmpparent_list;
npc_spells_cache[iDBSpellsID]->attack_proc = tmpattack_proc;
npc_spells_cache[iDBSpellsID]->proc_chance = tmpproc_chance;
npc_spells_cache[iDBSpellsID]->range_proc = tmprange_proc;
npc_spells_cache[iDBSpellsID]->rproc_chance = tmprproc_chance;
npc_spells_cache[iDBSpellsID]->defensive_proc = tmpdefensive_proc;
npc_spells_cache[iDBSpellsID]->dproc_chance = tmpdproc_chance;
npc_spells_cache[iDBSpellsID]->fail_recast = tmppfail_recast;
npc_spells_cache[iDBSpellsID]->engaged_no_sp_recast_min = tmpengaged_no_sp_recast_min;
npc_spells_cache[iDBSpellsID]->engaged_no_sp_recast_max = tmpengaged_no_sp_recast_max;
npc_spells_cache[iDBSpellsID]->engaged_beneficial_self_chance = tmpengaged_b_self_chance;
npc_spells_cache[iDBSpellsID]->engaged_beneficial_other_chance = tmpengaged_b_other_chance;
npc_spells_cache[iDBSpellsID]->engaged_detrimental_chance = tmpengaged_d_chance;
npc_spells_cache[iDBSpellsID]->pursue_no_sp_recast_min = tmppursue_no_sp_recast_min;
npc_spells_cache[iDBSpellsID]->pursue_no_sp_recast_max = tmppursue_no_sp_recast_max;
npc_spells_cache[iDBSpellsID]->pursue_detrimental_chance = tmppursue_d_chance;
npc_spells_cache[iDBSpellsID]->idle_no_sp_recast_min = tmpidle_no_sp_recast_min;
npc_spells_cache[iDBSpellsID]->idle_no_sp_recast_max = tmpidle_no_sp_recast_max;
npc_spells_cache[iDBSpellsID]->idle_beneficial_chance = tmpidle_b_chance;
npc_spells_cache[iDBSpellsID]->numentries = results.RowCount();
int entryIndex = 0;
for (row = results.begin(); row != results.end(); ++row, ++entryIndex) {
DBnpcspells_entries_Struct entry;
int spell_id = atoi(row[0]);
entry.spellid = spell_id;
entry.type = atoul(row[1]);
entry.minlevel = atoi(row[2]);
entry.maxlevel = atoi(row[3]);
entry.manacost = atoi(row[4]);
entry.recast_delay = atoi(row[5]);
entry.priority = atoi(row[6]);
entry.min_hp = atoi(row[7]);
entry.max_hp = atoi(row[8]);
int entryIndex = 0;
for (row = results.begin(); row != results.end(); ++row, ++entryIndex)
{
int spell_id = atoi(row[0]);
npc_spells_cache[iDBSpellsID]->entries[entryIndex].spellid = spell_id;
npc_spells_cache[iDBSpellsID]->entries[entryIndex].type = atoul(row[1]);
npc_spells_cache[iDBSpellsID]->entries[entryIndex].minlevel = atoi(row[2]);
npc_spells_cache[iDBSpellsID]->entries[entryIndex].maxlevel = atoi(row[3]);
npc_spells_cache[iDBSpellsID]->entries[entryIndex].manacost = atoi(row[4]);
npc_spells_cache[iDBSpellsID]->entries[entryIndex].recast_delay = atoi(row[5]);
npc_spells_cache[iDBSpellsID]->entries[entryIndex].priority = atoi(row[6]);
// some spell types don't make much since to be priority 0, so fix that
if (!(entry.type & SpellTypes_Innate) && entry.priority == 0)
entry.priority = 1;
if(row[7])
npc_spells_cache[iDBSpellsID]->entries[entryIndex].resist_adjust = atoi(row[7]);
else if(IsValidSpell(spell_id))
npc_spells_cache[iDBSpellsID]->entries[entryIndex].resist_adjust = spells[spell_id].ResistDiff;
}
if (row[9])
entry.resist_adjust = atoi(row[9]);
else if (IsValidSpell(spell_id))
entry.resist_adjust = spells[spell_id].ResistDiff;
return npc_spells_cache[iDBSpellsID];
spell_set.entries.push_back(entry);
}
npc_spells_cache.insert(std::make_pair(iDBSpellsID, spell_set));
return &npc_spells_cache[iDBSpellsID];
}
return nullptr;
+1 -1
View File
@@ -542,7 +542,7 @@ int main(int argc, char** argv) {
process_timer.Stop();
process_timer.Start(1000, true);
if (zone) {
if (zone && zone->GetZoneID() && zone->GetInstanceVersion()) {
uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion());
zone->StartShutdownTimer(shutdown_timer);
}
+103 -3
View File
@@ -135,6 +135,9 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if
respawn2 = in_respawn;
swarm_timer.Disable();
if (size < 0.0f)
size = GetRaceGenderDefaultHeight(race, gender);
taunting = false;
proximity = nullptr;
copper = 0;
@@ -208,6 +211,24 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if
avoidance_rating = d->avoidance_rating;
ATK = d->ATK;
// used for when switch back to charm
default_ac = d->AC;
default_min_dmg = min_dmg;
default_max_dmg = max_dmg;
default_attack_delay = d->attack_delay;
default_accuracy_rating = d->accuracy_rating;
default_avoidance_rating = d->avoidance_rating;
default_atk = d->ATK;
// used for when getting charmed, if 0, doesn't swap
charm_ac = d->charm_ac;
charm_min_dmg = d->charm_min_dmg;
charm_max_dmg = d->charm_max_dmg;
charm_attack_delay = d->charm_attack_delay;
charm_accuracy_rating = d->charm_accuracy_rating;
charm_avoidance_rating = d->charm_avoidance_rating;
charm_atk = d->charm_atk;
CalcMaxMana();
SetMana(GetMaxMana());
@@ -227,6 +248,8 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if
roambox_delay = 1000;
p_depop = false;
loottable_id = d->loottable_id;
skip_global_loot = d->skip_global_loot;
rare_spawn = d->rare_spawn;
no_target_hotkey = d->no_target_hotkey;
@@ -376,6 +399,19 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if
raid_target = d->raid_target;
ignore_despawn = d->ignore_despawn;
m_targetable = !d->untargetable;
AISpellVar.fail_recast = RuleI(Spells, AI_SpellCastFinishedFailRecast);
AISpellVar.engaged_no_sp_recast_min = RuleI(Spells, AI_EngagedNoSpellMinRecast);
AISpellVar.engaged_no_sp_recast_max = RuleI(Spells, AI_EngagedNoSpellMaxRecast);
AISpellVar.engaged_beneficial_self_chance = RuleI(Spells, AI_EngagedBeneficialSelfChance);
AISpellVar.engaged_beneficial_other_chance = RuleI(Spells, AI_EngagedBeneficialOtherChance);
AISpellVar.engaged_detrimental_chance = RuleI(Spells, AI_EngagedDetrimentalChance);
AISpellVar.pursue_no_sp_recast_min = RuleI(Spells, AI_PursueNoSpellMinRecast);
AISpellVar.pursue_no_sp_recast_max = RuleI(Spells, AI_PursueNoSpellMaxRecast);
AISpellVar.pursue_detrimental_chance = RuleI(Spells, AI_PursueDetrimentalChance);
AISpellVar.idle_no_sp_recast_min = RuleI(Spells, AI_IdleNoSpellMinRecast);
AISpellVar.idle_no_sp_recast_max = RuleI(Spells, AI_IdleNoSpellMaxRecast);
AISpellVar.idle_beneficial_chance = RuleI(Spells, AI_IdleBeneficialChance);
}
NPC::~NPC()
@@ -436,6 +472,26 @@ void NPC::SetTarget(Mob* mob) {
//attack_timer.Disable();
attack_dw_timer.Disable();
}
// either normal pet and owner is client or charmed pet and owner is client
Mob *owner = nullptr;
if (IsPet() && IsPetOwnerClient()) {
owner = GetOwner();
} else if (IsCharmed()) {
owner = GetOwner();
if (owner && !owner->IsClient())
owner = nullptr;
}
if (owner) {
auto client = owner->CastToClient();
if (client->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;
client->FastQueuePacket(&app);
}
}
Mob::SetTarget(mob);
}
@@ -543,9 +599,7 @@ void NPC::QueryLoot(Client* to)
linker.SetLinkType(EQEmu::saylink::SayLinkLootItem);
linker.SetLootData(*cur);
auto item_link = linker.GenerateLink();
to->Message(0, "%s, ID: %u, Level: (min: %u, max: %u)", item_link.c_str(), (*cur)->item_id, (*cur)->min_level, (*cur)->max_level);
to->Message(0, "%s, ID: %u, Level: (min: %u, max: %u)", linker.GenerateLink().c_str(), (*cur)->item_id, (*cur)->min_level, (*cur)->max_level);
}
to->Message(0, "%i items on %s.", x, GetName());
@@ -724,6 +778,10 @@ bool NPC::Process()
reface_timer->Disable();
}
// needs to be done before mez and stun
if (ForcedMovement)
ProcessForcedMovement();
if (IsMezzed())
return true;
@@ -900,6 +958,7 @@ bool NPC::SpawnZoneController(){
npc_type->d_melee_texture2 = 0;
npc_type->merchanttype = 0;
npc_type->bodytype = 11;
npc_type->skip_global_loot = true;
if (RuleB(Zone, EnableZoneControllerGlobals)) {
npc_type->qglobal = true;
@@ -2110,6 +2169,8 @@ void NPC::LevelScale() {
if(level > 15 && level <= 25)
scale_adjust = 2;
AC += (int)(AC * scaling);
ATK += (int)(ATK * scaling);
base_hp += (int)(base_hp * scaling);
max_hp += (int)(max_hp * scaling);
cur_hp = max_hp;
@@ -2616,3 +2677,42 @@ void NPC::DepopSwarmPets()
}
}
}
void NPC::ModifyStatsOnCharm(bool bRemoved)
{
if (bRemoved) {
if (charm_ac)
AC = default_ac;
if (charm_attack_delay)
attack_delay = default_attack_delay;
if (charm_accuracy_rating)
accuracy_rating = default_accuracy_rating;
if (charm_avoidance_rating)
avoidance_rating = default_avoidance_rating;
if (charm_atk)
ATK = default_atk;
if (charm_min_dmg || charm_max_dmg) {
base_damage = round((default_max_dmg - default_min_dmg) / 1.9);
min_damage = default_min_dmg - round(base_damage / 10.0);
}
} else {
if (charm_ac)
AC = charm_ac;
if (charm_attack_delay)
attack_delay = charm_attack_delay;
if (charm_accuracy_rating)
accuracy_rating = charm_accuracy_rating;
if (charm_avoidance_rating)
avoidance_rating = charm_avoidance_rating;
if (charm_atk)
ATK = charm_atk;
if (charm_min_dmg || charm_max_dmg) {
base_damage = round((charm_max_dmg - charm_min_dmg) / 1.9);
min_damage = charm_min_dmg - round(base_damage / 10.0);
}
}
// the rest of the stats aren't cached, so lets just do these two instead of full CalcBonuses()
SetAttackTimer();
CalcAC();
}
+31 -6
View File
@@ -61,6 +61,8 @@ struct AISpells_Struct {
int32 recast_delay;
int16 priority;
int16 resist_adjust;
int8 min_hp; // >0 won't cast if HP is below
int8 max_hp; // >0 won't cast if HP is above
};
struct AISpellsEffects_Struct {
@@ -183,6 +185,7 @@ public:
void AddItem(uint32 itemid, uint16 charges, bool equipitem = true, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0);
void AddLootTable();
void AddLootTable(uint32 ldid);
void CheckGlobalLootTables();
void DescribeAggro(Client *towho, Mob *mob, bool verbose);
void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0);
void CheckMinMaxLevel(Mob *them);
@@ -195,6 +198,7 @@ public:
uint32 CountLoot();
inline uint32 GetLoottableID() const { return loottable_id; }
virtual void UpdateEquipmentLight();
inline bool DropsGlobalLoot() const { return !skip_global_loot; }
inline uint32 GetCopper() const { return copper; }
inline uint32 GetSilver() const { return silver; }
@@ -280,6 +284,8 @@ public:
int32 GetNPCHPRegen() const { return hp_regen + itembonuses.HPRegen + spellbonuses.HPRegen; }
inline const char* GetAmmoIDfile() const { return ammo_idfile; }
void ModifyStatsOnCharm(bool bRemoved);
//waypoint crap
int GetMaxWp() const { return max_wp; }
void DisplayWaypointInfo(Client *to);
@@ -378,7 +384,7 @@ public:
void NPCSlotTexture(uint8 slot, uint16 texture); // Sets new material values for slots
uint32 GetAdventureTemplate() const { return adventure_template_id; }
void AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust);
void AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp);
void AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max);
void RemoveSpellFromNPCList(int16 spell_id);
Timer *GetRefaceTimer() const { return reface_timer; }
@@ -403,8 +409,6 @@ public:
uint32 GetSpawnKillCount();
int GetScore();
void SetMerchantProbability(uint8 amt) { probability = amt; }
uint8 GetMerchantProbability() { return probability; }
void mod_prespawn(Spawn2 *sp);
int mod_npc_damage(int damage, EQEmu::skills::SkillType skillinuse, int hand, const EQEmu::ItemData* weapon, Mob* other);
void mod_npc_killed_merit(Mob* c);
@@ -420,6 +424,8 @@ public:
bool IgnoreDespawn() { return ignore_despawn; }
std::unique_ptr<Timer> AIautocastspell_timer;
protected:
const NPCType* NPCTypedata;
@@ -453,11 +459,11 @@ protected:
uint32 npc_spells_id;
uint8 casting_spell_AIindex;
std::unique_ptr<Timer> AIautocastspell_timer;
uint32* pDontCastBefore_casting_spell;
std::vector<AISpells_Struct> AIspells;
bool HasAISpell;
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes);
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates = false);
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
AISpellsVar_Struct AISpellVar;
int16 GetFocusEffect(focusType type, uint16 spell_id);
@@ -480,6 +486,25 @@ protected:
int32 SpellFocusDMG;
int32 SpellFocusHeal;
// stats to switch back to after charm wears off
// could probably pick a better name, but these probably aren't taken so ...
int default_ac;
int default_min_dmg;
int default_max_dmg;
int default_attack_delay;
int default_accuracy_rating;
int default_avoidance_rating;
int default_atk;
// when charmed, switch to these
int charm_ac;
int charm_min_dmg;
int charm_max_dmg;
int charm_attack_delay;
int charm_accuracy_rating;
int charm_avoidance_rating;
int charm_atk;
//pet crap:
uint16 pet_spell_id;
bool taunting;
@@ -535,11 +560,11 @@ protected:
std::list<MercData> mercDataList;
bool raid_target;
uint8 probability;
bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup
private:
uint32 loottable_id;
bool skip_global_loot;
bool p_depop;
};
+1 -1
View File
@@ -70,7 +70,7 @@ Object::Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& object,
//creating a re-ocurring ground spawn.
Object::Object(const EQEmu::ItemInstance* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,uint32 respawntimer)
: respawn_timer(respawntimer), decay_timer(300000)
: respawn_timer(respawntimer * 1000), decay_timer(300000)
{
user = nullptr;
+4 -4
View File
@@ -42,7 +42,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa
bool partial = false;
bool stuck = false;
Route = zone->pathing->FindRoute(From, To, partial, stuck);
AdjustRoute(Route, flymode, GetModelOffset());
AdjustRoute(Route, flymode, GetZOffset());
PathingDestination = To;
WaypointChanged = true;
@@ -66,7 +66,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa
bool partial = false;
bool stuck = false;
Route = zone->pathing->FindRoute(From, To, partial, stuck);
AdjustRoute(Route, flymode, GetModelOffset());
AdjustRoute(Route, flymode, GetZOffset());
PathingDestination = To;
WaypointChanged = true;
@@ -133,7 +133,7 @@ glm::vec3 Mob::UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &Wa
bool partial = false;
bool stuck = false;
Route = zone->pathing->FindRoute(From, To, partial, stuck);
AdjustRoute(Route, flymode, GetModelOffset());
AdjustRoute(Route, flymode, GetZOffset());
PathingDestination = To;
WaypointChanged = true;
@@ -192,7 +192,7 @@ glm::vec3 Mob::HandleStuckPath(const glm::vec3 &To, const glm::vec3 &From)
auto final_node = r.back();
Route.push_back(final_node);
AdjustRoute(Route, flymode, GetModelOffset());
AdjustRoute(Route, flymode, GetZOffset());
return (*Route.begin()).pos;
}
+30
View File
@@ -9002,6 +9002,35 @@ XS(XS_Mob_GetMeleeMitigation) {
XSRETURN(1);
}
XS(XS_Mob_TryMoveAlong);
XS(XS_Mob_TryMoveAlong) {
dXSARGS;
if (items < 3 || items > 4)
Perl_croak(aTHX_ "Usage: Mob::TryMoveAlong(THIS, distance, angle, send?)");
{
Mob* THIS;
float distance = (float)SvNV(ST(1));
float angle = (float)SvNV(ST(2));
bool send = true;
if (sv_derived_from(ST(0), "Mob")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(Mob *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type Mob");
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
if (items == 4)
send = (bool)SvTRUE(ST(3));
THIS->TryMoveAlong(distance, angle, send);
}
XSRETURN_EMPTY;
}
#ifdef __cplusplus
extern "C"
#endif
@@ -9335,6 +9364,7 @@ XS(boot_Mob)
newXSproto(strcpy(buf, "IsSilenced"), XS_Mob_IsSilenced, file, "$");
newXSproto(strcpy(buf, "IsAmnesiad"), XS_Mob_IsAmnesiad, file, "$");
newXSproto(strcpy(buf, "GetMeleeMitigation"), XS_Mob_GetMeleeMitigation, file, "$");
newXSproto(strcpy(buf, "TryMoveAlong"), XS_Mob_TryMoveAlong, file, "$$$;$");
XSRETURN_YES;
}
+1 -51
View File
@@ -1993,7 +1993,7 @@ XS(XS_NPC_AddSpellToNPCList)
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->AddSpellToNPCList(priority, spell_id, type, mana_cost, recast_delay, resist_adjust);
THIS->AddSpellToNPCList(priority, spell_id, type, mana_cost, recast_delay, resist_adjust, 0, 0);
}
XSRETURN_EMPTY;
}
@@ -2304,54 +2304,6 @@ XS(XS_NPC_GetScore)
XSRETURN(1);
}
XS(XS_NPC_SetMerchantProbability);
XS(XS_NPC_SetMerchantProbability) {
dXSARGS;
if (items != 2)
Perl_croak(aTHX_ "Usage: NPC::SetMerchantProbability(THIS, Probability)");
{
NPC *THIS;
uint8 Probability = (uint8)SvIV(ST(1));
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(NPC *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type NPC");
if(THIS == nullptr)
Perl_croak(aTHX_ "THIS is nullptr, avoiding crash.");
THIS->SetMerchantProbability(Probability);
}
XSRETURN_EMPTY;
}
XS(XS_NPC_GetMerchantProbability);
XS(XS_NPC_GetMerchantProbability) {
dXSARGS;
if (items != 1)
Perl_croak(aTHX_ "Usage: NPC::GetMerchantProbability(THIS)");
{
NPC *THIS;
uint8 RETVAL;
dXSTARG;
if (sv_derived_from(ST(0), "NPC")) {
IV tmp = SvIV((SV*)SvRV(ST(0)));
THIS = INT2PTR(NPC *,tmp);
}
else
Perl_croak(aTHX_ "THIS is not of type NPC");
if(THIS == NULL)
Perl_croak(aTHX_ "THIS is NULL, avoiding crash.");
RETVAL = THIS->GetMerchantProbability();
XSprePUSH; PUSHu((UV)RETVAL);
}
XSRETURN(1);
}
XS(XS_NPC_AddMeleeProc);
XS(XS_NPC_AddMeleeProc) {
dXSARGS;
@@ -2677,8 +2629,6 @@ XS(boot_NPC)
newXSproto(strcpy(buf, "GetAvoidanceRating"), XS_NPC_GetAvoidanceRating, file, "$");
newXSproto(strcpy(buf, "GetSpawnKillCount"), XS_NPC_GetSpawnKillCount, file, "$");
newXSproto(strcpy(buf, "GetScore"), XS_NPC_GetScore, file, "$");
newXSproto(strcpy(buf, "SetMerchantProbability"), XS_NPC_SetMerchantProbability, file, "$$");
newXSproto(strcpy(buf, "GetMerchantProbability"), XS_NPC_GetMerchantProbability, file, "$");
newXSproto(strcpy(buf, "AddMeleeProc"), XS_NPC_AddMeleeProc, file, "$$$");
newXSproto(strcpy(buf, "AddRangedProc"), XS_NPC_AddRangedProc, file, "$$$");
newXSproto(strcpy(buf, "AddDefensiveProc"), XS_NPC_AddDefensiveProc, file, "$$$");
+47 -96
View File
@@ -28,6 +28,8 @@
#include "pets.h"
#include "zonedb.h"
#include <string>
#ifdef BOTS
#include "bot.h"
#endif
@@ -38,82 +40,42 @@
#endif
const char *GetRandPetName()
// need to pass in a char array of 64 chars
void GetRandPetName(char *name)
{
static const char *petnames[] = { "Gabaner","Gabann","Gabantik","Gabarab","Gabarer","Gabarn","Gabartik",
"Gabekab","Gabeker","Gabekn","Gaber","Gabn","Gabobab","Gabobn","Gabtik",
"Ganer","Gann","Gantik","Garab","Garaner","Garann","Garantik","Gararn",
"Garekn","Garer","Garn","Gartik","Gasaner","Gasann","Gasantik","Gasarer",
"Gasartik","Gasekn","Gaser","Gebann","Gebantik","Gebarer","Gebarn","Gebartik",
"Gebeker","Gebekn","Gebn","Gekab","Geker","Gekn","Genaner","Genann","Genantik",
"Genarer","Genarn","Gener","Genn","Genobtik","Gibaner","Gibann","Gibantik",
"Gibarn","Gibartik","Gibekn","Giber","Gibn","Gibobtik","Gibtik","Gobaber",
"Gobaner","Gobann","Gobarn","Gobartik","Gober","Gobn","Gobober","Gobobn",
"Gobobtik","Gobtik","Gonaner","Gonann","Gonantik","Gonarab","Gonarer",
"Gonarn","Gonartik","Gonekab","Gonekn","Goner","Gonobtik","Gontik","Gotik",
"Jabaner","Jabann","Jabantik","Jabarab","Jabarer","Jabarn","Jabartik",
"Jabekab","Jabeker","Jabekn","Jaber","Jabn","Jabobtik","Jabtik","Janab",
"Janer","Jann","Jantik","Jarab","Jaranab","Jaraner","Jararer","Jararn",
"Jarartik","Jareker","Jarekn","Jarer","Jarn","Jarobn","Jarobtik","Jartik",
"Jasab","Jasaner","Jasantik","Jasarer","Jasartik","Jasekab","Jaseker",
"Jasekn","Jaser","Jasn","Jasobab","Jasober","Jastik","Jebanab","Jebann",
"Jebantik","Jebarab","Jebarar","Jebarer","Jebarn","Jebartik","Jebeker",
"Jebekn","Jeber","Jebobn","Jebtik","Jekab","Jeker","Jekn","Jenann",
"Jenantik","Jenarer","Jeneker","Jenekn","Jentik","Jibaner","Jibann",
"Jibantik","Jibarer","Jibarn","Jibartik","Jibeker","Jibn","Jibobn",
"Jibtik","Jobab","Jobaner","Jobann","Jobantik","Jobarn","Jobartik",
"Jobekab","Jobeker","Jober","Jobn","Jobtik","Jonanab","Jonaner",
"Jonann","Jonantik","Jonarer","Jonarn","Jonartik","Jonekab","Joneker",
"Jonekn","Joner","Jonn","Jonnarn","Jonober","Jonobn","Jonobtik","Jontik",
"Kabanab","Kabaner","Kabann","Kabantik","Kabarer","Kabarn","Kabartik",
"Kabeker","Kabekn","Kaber","Kabn","Kabober","Kabobn","Kabobtik","Kabtik",
"Kanab","Kaner","Kann","Kantik","Karab","Karanab","Karaner","Karann",
"Karantik","Kararer","Karartik","Kareker","Karer","Karn","Karobab","Karobn",
"Kartik","Kasaner","Kasann","Kasarer","Kasartik","Kaseker","Kasekn","Kaser",
"Kasn","Kasober","Kastik","Kebann","Kebantik","Kebarab","Kebartik","Kebeker",
"Kebekn","Kebn","Kebobab","Kebtik","Kekab","Keker","Kekn","Kenab","Kenaner",
"Kenantik","Kenarer","Kenarn","Keneker","Kener","Kenn","Kenobn","Kenobtik",
"Kentik","Kibab","Kibaner","Kibantik","Kibarn","Kibartik","Kibekab","Kibeker",
"Kibekn","Kibn","Kibobn","Kibobtik","Kobab","Kobanab","Kobaner","Kobann",
"Kobantik","Kobarer","Kobarn","Kobartik","Kobeker","Kobekn","Kober","Kobn",
"Kobober","Kobobn","Kobtik","Konanab","Konaner","Konann","Konantik","Konarab",
"Konarer","Konarn","Konekab","Koneker","Konekn","Koner","Konn","Konobn",
"Konobtik","Kontik","Labanab","Labaner","Labann","Labarab","Labarer",
"Labarn","Labartik","Labeker","Labekn","Laner","Lann","Larab","Larantik",
"Lararer","Lararn","Larartik","Lareker","Larer","Larn","Lartik","Lasaner",
"Lasann","Lasarer","Laseker","Laser","Lasik","Lasn","Lastik","Lebaner",
"Lebarer","Lebartik","Lebekn","Lebtik","Lekab","Lekn","Lenanab","Lenaner",
"Lenann","Lenartik","Lenekab","Leneker","Lenekn","Lentik","Libab","Libaner",
"Libann","Libantik","Libarer","Libarn","Libartik","Libeker","Libekn","Lobann",
"Lobarab","Lobarn","Lobartik","Lobekn","Lobn","Lobober","Lobobn","Lobtik",
"Lonaner","Lonann","Lonantik","Lonarab","Lonarer","Lonarn","Lonartik","Lonekn",
"Loner","Lonobtik","Lontik","Vabanab","Vabaner","Vabann","Vabantik","Vabarer",
"Vabarn","Vabartik","Vabeker","Vabekn","Vabtik","Vanikk","Vann","Varartik","Varn",
"Vartik","Vasann","Vasantik","Vasarab","Vasarer","Vaseker","Vebaner","Vebantik",
"Vebarab","Vebeker","Vebekn","Vebobn","Vekab","Veker","Venaner","Venantik","Venar",
"Venarn","Vener","Ventik","Vibann","Vibantik","Viber","Vibobtik","Vobann",
"Vobarer","Vobartik","Vobekn","Vober","Vobn","Vobtik","Vonaner","Vonann",
"Vonantik","Vonarab","Vonarn","Vonartik","Voneker","Vonn","Xabanab","Xabaner",
"Xabarer","Xabarn","Xabartik","Xabekab","Xabeker","Xabekn","Xaber","Xabober",
"Xaner","Xann","Xarab","Xaranab","Xarann","Xarantik","Xararer","Xarartik","Xarer",
"Xarn","Xartik","Xasaner","Xasann","Xasarab","Xasarn","Xasekab","Xaseker",
"Xebarer","Xebarn","Xebeker","Xeber","Xebober","Xebtik","Xekab","Xeker",
"Xekn","Xenann","Xenantik","Xenarer","Xenartik","Xenekn","Xener","Xenober",
"Xentik","Xibantik","Xibarer","Xibekab","Xibeker","Xibobab","Xibober","Xibobn",
"Xobaner","Xobann","Xobarab","Xobarn","Xobekab","Xobeker","Xobekn","Xober",
"Xobn","Xobobn","Xobtik","Xonaner","Xonann","Xonantik","Xonarer","Xonartik",
"Xonekab","Xoneker","Xonekn","Xoner","Xonober","Xtik","Zabaner","Zabantik",
"Zabarab","Zabekab","Zabekn","Zaber","Zabn","Zabobab","Zabober","Zabtik",
"Zaner","Zantik","Zarann","Zarantik","Zararn","Zarartik","Zareker","Zarekn",
"Zarer","Zarn","Zarober","Zartik","Zasaner","Zasarer","Zaseker","Zasekn","Zasn",
"Zebantik","Zebarer","Zebarn","Zebartik","Zebobab","Zekab","Zekn","Zenann",
"Zenantik","Zenarer","Zenarn","Zenekab","Zeneker","Zenobtik","Zibanab","Zibaner",
"Zibann","Zibarer","Zibartik","Zibekn","Zibn","Zibobn","Zobaner","Zobann",
"Zobarn","Zober","Zobn","Zonanab","Zonaner","Zonann","Zonantik","Zonarer",
"Zonartik","Zonobn","Zonobtik","Zontik","Ztik" };
int r = zone->random.Int(0, (sizeof(petnames)/sizeof(const char *))-1);
printf("Pet being created: %s\n",petnames[r]); // DO NOT COMMENT THIS OUT!
return petnames[r];
std::string temp;
temp.reserve(64);
// note these orders are used to make the exclusions cheap :P
static const char *part1[] = {"G", "J", "K", "L", "V", "X", "Z"};
static const char *part2[] = {nullptr, "ab", "ar", "as", "eb", "en", "ib", "ob", "on"};
static const char *part3[] = {nullptr, "an", "ar", "ek", "ob"};
static const char *part4[] = {"er", "ab", "n", "tik"};
const char *first = part1[zone->random.Int(0, (sizeof(part1) / sizeof(const char *)) - 1)];
const char *second = part2[zone->random.Int(0, (sizeof(part2) / sizeof(const char *)) - 1)];
const char *third = part3[zone->random.Int(0, (sizeof(part3) / sizeof(const char *)) - 1)];
const char *fourth = part4[zone->random.Int(0, (sizeof(part4) / sizeof(const char *)) - 1)];
// if both of these are empty, we would get an illegally short name
if (second == nullptr && third == nullptr)
fourth = part4[(sizeof(part4) / sizeof(const char *)) - 1];
// "ektik" isn't allowed either I guess?
if (third == part3[3] && fourth == part4[3])
fourth = part4[zone->random.Int(0, (sizeof(part4) / sizeof(const char *)) - 2)];
// "Laser" isn't allowed either I guess?
if (first == part1[3] && second == part2[3] && third == nullptr && fourth == part4[0])
fourth = part4[zone->random.Int(1, (sizeof(part4) / sizeof(const char *)) - 2)];
temp += first;
if (second != nullptr)
temp += second;
if (third != nullptr)
temp += third;
temp += fourth;
strn0cpy(name, temp.c_str(), 64);
}
//not used anymore
@@ -325,7 +287,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower,
} else if (record.petnaming == 4) {
// Keep the DB name
} else if (record.petnaming == 3 && IsClient()) {
strcpy(npc_type->name, GetRandPetName());
GetRandPetName(npc_type->name);
} else if (record.petnaming == 5 && IsClient()) {
strcpy(npc_type->name, this->GetName());
npc_type->name[24] = '\0';
@@ -475,22 +437,6 @@ Pet::Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 po
// Class should use npc constructor to set light properties
}
void Pet::SetTarget(Mob *mob)
{
if (mob == GetTarget())
return;
auto owner = GetOwner();
if (owner && owner->IsClient() && owner->CastToClient()->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);
}
bool ZoneDatabase::GetPetEntry(const char *pet_type, PetRecord *into) {
return GetPoweredPetEntry(pet_type, 0, into);
}
@@ -671,10 +617,15 @@ void NPC::SetPetState(SpellBuff_Struct *pet_buffs, uint32 *items) {
continue;
const EQEmu::ItemData* item2 = database.GetItem(items[i]);
if (item2 && item2->NoDrop != 0) {
//dont bother saving item charges for now, NPCs never use them
//and nobody should be able to get them off the corpse..?
AddLootDrop(item2, &itemlist, 0, 1, 255, true, true);
if (item2) {
bool noDrop=(item2->NoDrop == 0); // Field is reverse logic
bool petCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
_CLIENTPET(this) && GetPetType() <= petOther);
if (!noDrop || petCanHaveNoDrop) {
AddLootDrop(item2, &itemlist, 0, 1, 255, true, true);
}
}
}
}
-1
View File
@@ -7,7 +7,6 @@ struct NPCType;
class Pet : public NPC {
public:
Pet(NPCType *type_data, Mob *owner, PetType type, uint16 spell_id, int16 power);
virtual void SetTarget(Mob *mob);
virtual bool CheckSpellLevelRestriction(uint16 spell_id);
};
+2 -2
View File
@@ -150,13 +150,13 @@ float GetReciprocalHeading(const float heading)
float result = 0;
// Convert to radians
float h = (heading / 256.0f) * 6.283184f;
float h = (heading / 512.0f) * 6.283184f;
// Calculate the reciprocal heading in radians
result = h + 3.141592f;
// Convert back to eq heading from radians
result = (result / 6.283184f) * 256.0f;
result = (result / 6.283184f) * 512.0f;
return result;
}
+16 -11
View File
@@ -210,6 +210,8 @@ Mob* QuestManager::spawn2(int npc_type, int grid, int unused, const glm::vec4& p
{
auto npc = new NPC(tmp, nullptr, position, FlyMode3);
npc->AddLootTable();
if (npc->DropsGlobalLoot())
npc->CheckGlobalLootTables();
entity_list.AddNPC(npc,true,true);
if(grid > 0)
{
@@ -232,6 +234,8 @@ Mob* QuestManager::unique_spawn(int npc_type, int grid, int unused, const glm::v
{
auto npc = new NPC(tmp, nullptr, position, FlyMode3);
npc->AddLootTable();
if (npc->DropsGlobalLoot())
npc->CheckGlobalLootTables();
entity_list.AddNPC(npc,true,true);
if(grid > 0)
{
@@ -308,6 +312,8 @@ Mob* QuestManager::spawn_from_spawn2(uint32 spawn2_id)
found_spawn->SetNPCPointer(npc);
npc->AddLootTable();
if (npc->DropsGlobalLoot())
npc->CheckGlobalLootTables();
npc->SetSp2(found_spawn->SpawnGroupID());
entity_list.AddNPC(npc);
entity_list.LimitAddNPC(npc);
@@ -1240,9 +1246,7 @@ void QuestManager::itemlink(int item_id) {
linker.SetLinkType(EQEmu::saylink::SayLinkItemData);
linker.SetItemData(item);
auto item_link = linker.GenerateLink();
initiator->Message(0, "%s tells you, %s", owner->GetCleanName(), item_link.c_str());
initiator->Message(0, "%s tells you, %s", owner->GetCleanName(), linker.GenerateLink().c_str());
}
}
@@ -1583,6 +1587,8 @@ void QuestManager::respawn(int npcTypeID, int grid) {
{
owner = new NPC(npcType, nullptr, owner->GetPosition(), FlyMode3);
owner->CastToNPC()->AddLootTable();
if (owner->CastToNPC()->DropsGlobalLoot())
owner->CastToNPC()->CheckGlobalLootTables();
entity_list.AddNPC(owner->CastToNPC(),true,true);
if(grid > 0)
owner->CastToNPC()->AssignWaypoints(grid);
@@ -1591,7 +1597,7 @@ void QuestManager::respawn(int npcTypeID, int grid) {
}
}
void QuestManager::set_proximity(float minx, float maxx, float miny, float maxy, float minz, float maxz) {
void QuestManager::set_proximity(float minx, float maxx, float miny, float maxy, float minz, float maxz, bool bSay) {
QuestManagerCurrentQuestVars();
if (!owner || !owner->IsNPC())
return;
@@ -1604,6 +1610,7 @@ void QuestManager::set_proximity(float minx, float maxx, float miny, float maxy,
owner->CastToNPC()->proximity->max_y = maxy;
owner->CastToNPC()->proximity->min_z = minz;
owner->CastToNPC()->proximity->max_z = maxz;
owner->CastToNPC()->proximity->say = bSay;
}
void QuestManager::clear_proximity() {
@@ -1908,8 +1915,8 @@ void QuestManager::npcfeature(char *feature, int setting)
QuestManagerCurrentQuestVars();
uint16 Race = owner->GetRace();
uint8 Gender = owner->GetGender();
uint8 Texture = 0xFF;
uint8 HelmTexture = 0xFF;
uint8 Texture = owner->GetTexture();
uint8 HelmTexture = owner->GetHelmTexture();
uint8 HairColor = owner->GetHairColor();
uint8 BeardColor = owner->GetBeardColor();
uint8 EyeColor1 = owner->GetEyeColor1();
@@ -2468,9 +2475,8 @@ const char* QuestManager::varlink(char* perltext, int item_id) {
linker.SetLinkType(EQEmu::saylink::SayLinkItemData);
linker.SetItemData(item);
auto item_link = linker.GenerateLink();
strcpy(perltext, item_link.c_str()); // link length is currently ranged from 1 to 250 in TextLink::GenerateLink()
strcpy(perltext, linker.GenerateLink().c_str());
return perltext;
}
@@ -2692,8 +2698,7 @@ const char* QuestManager::saylink(char* Phrase, bool silent, const char* LinkNam
linker.SetProxyAugment1ID(sayid);
linker.SetProxyText(LinkName);
auto say_link = linker.GenerateLink();
strcpy(Phrase, say_link.c_str()); // link length is currently ranged from 1 to 250 in TextLink::GenerateLink()
strcpy(Phrase, linker.GenerateLink().c_str());
return Phrase;
}
+1 -1
View File
@@ -148,7 +148,7 @@ public:
void setnexthpevent(int at);
void setnextinchpevent(int at);
void respawn(int npc_type, int grid);
void set_proximity(float minx, float maxx, float miny, float maxy, float minz=-999999, float maxz=999999);
void set_proximity(float minx, float maxx, float miny, float maxy, float minz=-999999, float maxz=999999, bool bSay = false);
void clear_proximity();
void enable_proximity_say();
void disable_proximity_say();
+4
View File
@@ -323,6 +323,10 @@ void Raid::SaveRaidLeaderAA()
void Raid::UpdateGroupAAs(uint32 gid)
{
if (gid < 0 || gid > MAX_RAID_GROUPS)
return;
Client *gl = GetGroupLeader(gid);
if (gl)
+2
View File
@@ -236,6 +236,8 @@ bool Spawn2::Process() {
npcthis = npc;
npc->AddLootTable();
if (npc->DropsGlobalLoot())
npc->CheckGlobalLootTables();
npc->SetSp2(spawngroup_id_);
npc->SaveGuardPointAnim(anim);
npc->SetAppearance((EmuAppearance)anim);
+2 -1
View File
@@ -37,6 +37,7 @@ int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target)
case EQEmu::skills::SkillDragonPunch:
case EQEmu::skills::SkillEagleStrike:
case EQEmu::skills::SkillTigerClaw:
case EQEmu::skills::SkillRoundKick:
if (skill_level >= 25)
base++;
if (skill_level >= 75)
@@ -1442,7 +1443,7 @@ void Mob::SendItemAnimation(Mob *to, const EQEmu::ItemData *item, EQEmu::skills:
//these angle and tilt used together seem to make the arrow/knife throw as straight as I can make it
as->launch_angle = CalculateHeadingToTarget(to->GetX(), to->GetY()) * 2;
as->launch_angle = CalculateHeadingToTarget(to->GetX(), to->GetY());
as->tilt = 125;
as->arc = 50;
+36 -87
View File
@@ -60,6 +60,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
const SPDat_Spell_Struct &spell = spells[spell_id];
if (spell.disallow_sit && IsBuffSpell(spell_id) && IsClient() && (CastToClient()->IsSitting() || CastToClient()->GetHorseId() != 0))
return false;
bool c_override = false;
if (caster && caster->IsClient() && GetCastedSpellInvSlot() > 0) {
const EQEmu::ItemInstance *inst = caster->CastToClient()->GetInv().GetItem(GetCastedSpellInvSlot());
@@ -134,6 +137,15 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
buffs[buffslot].magic_rune = 0;
buffs[buffslot].numhits = 0;
if (spells[spell_id].numhits > 0) {
int numhit = spells[spell_id].numhits;
numhit += numhit * caster->GetFocusEffect(focusFcLimitUse, spell_id) / 100;
numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id);
buffs[buffslot].numhits = numhit;
}
if (spells[spell_id].EndurUpkeep > 0)
SetEndurUpkeep(true);
@@ -181,14 +193,6 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
}
}
if(spells[spell_id].numhits > 0 && buffslot >= 0){
int numhit = spells[spell_id].numhits;
numhit += numhit*caster->GetFocusEffect(focusFcLimitUse, spell_id)/100;
numhit += caster->GetFocusEffect(focusIncreaseNumHits, spell_id);
buffs[buffslot].numhits = numhit;
}
if (!IsPowerDistModSpell(spell_id))
SetSpellPowerDistanceMod(0);
@@ -786,6 +790,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
CastToClient()->AI_Start();
} else if(IsNPC()) {
CastToNPC()->SetPetSpellID(0); //not a pet spell.
CastToNPC()->ModifyStatsOnCharm(false);
}
bool bBreak = false;
@@ -908,16 +913,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
action->source = caster ? caster->GetID() : GetID();
action->level = 65;
action->instrument_mod = 10;
action->sequence = static_cast<uint32>((GetHeading() * 12345 / 2));
action->hit_heading = GetHeading();
action->type = 231;
action->spell = spell_id;
action->buff_unknown = 4;
action->effect_flag = 4;
cd->target = action->target;
cd->source = action->source;
cd->type = action->type;
cd->spellid = action->spell;
cd->meleepush_xy = action->sequence;
cd->hit_heading = action->hit_heading;
CastToClient()->QueuePacket(action_packet);
if(caster && caster->IsClient() && caster != this)
@@ -959,16 +964,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
action->source = caster ? caster->GetID() : GetID();
action->level = 65;
action->instrument_mod = 10;
action->sequence = static_cast<uint32>((GetHeading() * 12345 / 2));
action->hit_heading = GetHeading();
action->type = 231;
action->spell = spell_id;
action->buff_unknown = 4;
action->effect_flag = 4;
cd->target = action->target;
cd->source = action->source;
cd->type = action->type;
cd->spellid = action->spell;
cd->meleepush_xy = action->sequence;
cd->hit_heading = action->hit_heading;
CastToClient()->QueuePacket(action_packet);
if(caster->IsClient() && caster != this)
@@ -997,16 +1002,16 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
action->source = caster ? caster->GetID() : GetID();
action->level = 65;
action->instrument_mod = 10;
action->sequence = static_cast<uint32>((GetHeading() * 12345 / 2));
action->hit_heading = GetHeading();
action->type = 231;
action->spell = spell_id;
action->buff_unknown = 4;
action->effect_flag = 4;
cd->target = action->target;
cd->source = action->source;
cd->type = action->type;
cd->spellid = action->spell;
cd->meleepush_xy = action->sequence;
cd->hit_heading = action->hit_heading;
CastToClient()->QueuePacket(action_packet);
if(caster->IsClient() && caster != this)
@@ -1620,13 +1625,14 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
break;
if(IsClient()) {
CastToClient()->SetHorseId(0); // dismount if have horse
if (zone->random.Int(0, 99) > spells[spell_id].base[i]) {
CastToClient()->SetFeigned(false);
entity_list.MessageClose_StringID(this, false, 200, 10, STRING_FEIGNFAILED, GetName());
}
else
} else {
CastToClient()->SetFeigned(true);
}
}
break;
}
@@ -1792,8 +1798,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
Message_StringID(4, CORPSE_CANT_SENSE);
}
}
else if (caster)
caster->Message_StringID(MT_SpellFailure, SPELL_LEVEL_REQ);
else if (caster) {
char level[4];
ConvertArray(effect_value, level);
caster->Message_StringID(MT_SpellFailure,
SPELL_LEVEL_REQ, level);
}
}
else {
Message_StringID(4, TARGET_NOT_FOUND);
@@ -2065,61 +2075,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
#ifdef SPELL_EFFECT_SPAM
snprintf(effect_desc, _EDLEN, "Toss Up: %d", effect_value);
#endif
double toss_amt = (double)spells[spell_id].base[i];
if(toss_amt < 0)
toss_amt = -toss_amt;
if(IsNPC())
{
Stun(static_cast<int>(toss_amt));
if (IsNPC()) {
Damage(caster, std::abs(effect_value), spell_id, spell.skill, false, buffslot, false);
}
toss_amt = sqrt(toss_amt)-2.0;
if(toss_amt < 0.0)
toss_amt = 0.0;
if(toss_amt > 20.0)
toss_amt = 20.0;
if(IsClient())
{
CastToClient()->SetKnockBackExemption(true);
}
double look_heading = GetHeading();
look_heading /= 256;
look_heading *= 360;
look_heading += 180;
if(look_heading > 360)
look_heading -= 360;
//x and y are crossed mkay
double new_x = spells[spell_id].pushback * sin(double(look_heading * 3.141592 / 180.0));
double new_y = spells[spell_id].pushback * cos(double(look_heading * 3.141592 / 180.0));
auto outapp_push =
new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer;
spu->spawn_id = GetID();
spu->x_pos = FloatToEQ19(GetX());
spu->y_pos = FloatToEQ19(GetY());
spu->z_pos = FloatToEQ19(GetZ());
spu->delta_x = NewFloatToEQ13(new_x);
spu->delta_y = NewFloatToEQ13(new_y);
spu->delta_z = NewFloatToEQ13(toss_amt);
spu->heading = FloatToEQ19(GetHeading());
spu->padding0002 =0;
spu->padding0006 =7;
spu->padding0014 =0x7f;
spu->padding0018 =0x5df27;
spu->animation = 0;
spu->delta_heading = NewFloatToEQ13(0);
outapp_push->priority = 5;
entity_list.QueueClients(this, outapp_push, true);
if(IsClient())
CastToClient()->FastQueuePacket(&outapp_push);
break;
}
@@ -2644,7 +2602,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
float new_ground = GetGroundZ(my_x, my_y);
if(caster->IsClient())
caster->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), my_x, my_y, new_ground, GetHeading()*2);
caster->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), my_x, my_y, new_ground, GetHeading());
else
caster->GMMove(my_x, my_y, new_ground, GetHeading());
}
@@ -3656,16 +3614,6 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
break;
}
case SE_Hunger: {
// this procedure gets called 7 times for every once that the stamina update occurs so we add
// 1/7 of the subtraction.
// It's far from perfect, but works without any unnecessary buff checks to bog down the server.
if (IsClient()) {
CastToClient()->m_pp.hunger_level += 5;
CastToClient()->m_pp.thirst_level += 5;
}
break;
}
case SE_Invisibility:
case SE_InvisVsAnimals:
case SE_InvisVsUndead: {
@@ -3711,7 +3659,7 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
case SE_CastOnFadeEffectNPC:
case SE_CastOnFadeEffectAlways: {
if (buff.ticsremaining == 0) {
SpellOnTarget(spells[buff.spellid].base[i], this);
SpellFinished(spells[buff.spellid].base[i], this, EQEmu::CastingSlot::Item, 0, -1, spells[spells[buff.spellid].base[i]].ResistDiff);
}
break;
}
@@ -3968,6 +3916,7 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses)
if(IsNPC())
{
CastToNPC()->RestoreGuardSpotCharm();
CastToNPC()->ModifyStatsOnCharm(true);
}
SendAppearancePacket(AT_Pet, 0, true, true);
+124 -170
View File
@@ -81,6 +81,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
#include "quest_parser_collection.h"
#include "string_ids.h"
#include "worldserver.h"
#include "fastmath.h"
#include <assert.h>
#include <math.h>
@@ -104,6 +105,7 @@ Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
extern Zone* zone;
extern volatile bool is_zone_loaded;
extern WorldServer worldserver;
extern FastMath g_Math;
using EQEmu::CastingSlot;
@@ -1208,7 +1210,10 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
// handle the components for traditional casters
else {
if(c->GetInv().HasItem(component, component_count, invWhereWorn|invWherePersonal) == -1) // item not found
if (!RuleB(Character, PetsUseReagents) && IsEffectInSpell(spell_id, SE_SummonPet)) {
//bypass reagent cost
}
else if(c->GetInv().HasItem(component, component_count, invWhereWorn|invWherePersonal) == -1) // item not found
{
if (!missingreags)
{
@@ -1238,6 +1243,9 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
return;
}
}
else if (!RuleB(Character, PetsUseReagents) && IsEffectInSpell(spell_id, SE_SummonPet)) {
//bypass reagent cost
}
else if (!bard_song_mode)
{
int noexpend;
@@ -2646,20 +2654,18 @@ void Mob::BardPulse(uint16 spell_id, Mob *caster) {
action->source = caster->GetID();
action->target = GetID();
action->spell = spell_id;
action->sequence = (uint32) (GetHeading() * 2); // just some random number
action->force = spells[spell_id].pushback;
action->hit_heading = GetHeading();
action->hit_pitch = spells[spell_id].pushup;
action->instrument_mod = caster->GetInstrumentMod(spell_id);
action->buff_unknown = 0;
action->level = buffs[buffs_i].casterlevel;
action->effect_flag = 0;
action->spell_level = action->level = buffs[buffs_i].casterlevel;
action->type = DamageTypeSpell;
entity_list.QueueCloseClients(this, packet, false, RuleI(Range, SongMessages), 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells);
action->buff_unknown = 4;
action->effect_flag = 4;
if(IsEffectInSpell(spell_id, SE_TossUp))
{
action->buff_unknown = 0;
}
else if(spells[spell_id].pushback > 0 || spells[spell_id].pushup > 0)
if(spells[spell_id].pushback != 0.0f || spells[spell_id].pushup != 0.0f)
{
if(IsClient())
{
@@ -2667,38 +2673,6 @@ void Mob::BardPulse(uint16 spell_id, Mob *caster) {
{
CastToClient()->SetKnockBackExemption(true);
action->buff_unknown = 0;
auto outapp_push = new EQApplicationPacket(
OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer;
double look_heading = caster->CalculateHeadingToTarget(GetX(), GetY());
look_heading /= 256;
look_heading *= 360;
if(look_heading > 360)
look_heading -= 360;
//x and y are crossed mkay
double new_x = spells[spell_id].pushback * sin(double(look_heading * 3.141592 / 180.0));
double new_y = spells[spell_id].pushback * cos(double(look_heading * 3.141592 / 180.0));
spu->spawn_id = GetID();
spu->x_pos = FloatToEQ19(GetX());
spu->y_pos = FloatToEQ19(GetY());
spu->z_pos = FloatToEQ19(GetZ());
spu->delta_x = NewFloatToEQ13(new_x);
spu->delta_y = NewFloatToEQ13(new_y);
spu->delta_z = NewFloatToEQ13(spells[spell_id].pushup);
spu->heading = FloatToEQ19(GetHeading());
spu->padding0002 =0;
spu->padding0006 =7;
spu->padding0014 =0x7f;
spu->padding0018 =0x5df27;
spu->animation = 0;
spu->delta_heading = NewFloatToEQ13(0);
outapp_push->priority = 6;
entity_list.QueueClients(this, outapp_push, true);
CastToClient()->FastQueuePacket(&outapp_push);
}
}
}
@@ -2719,7 +2693,9 @@ void Mob::BardPulse(uint16 spell_id, Mob *caster) {
cd->source = action->source;
cd->type = DamageTypeSpell;
cd->spellid = action->spell;
cd->meleepush_xy = action->sequence;
cd->force = action->force;
cd->hit_heading = action->hit_heading;
cd->hit_pitch = action->hit_pitch;
cd->damage = 0;
if(!IsEffectInSpell(spell_id, SE_BindAffinity))
{
@@ -3190,6 +3166,12 @@ uint32 Client::GetLastBuffSlot(bool disc, bool song)
return GetCurrentBuffSlots();
}
bool Mob::HasDiscBuff()
{
int slot = GetFirstBuffSlot(true, false);
return buffs[slot].spellid != SPELL_UNKNOWN;
}
// returns the slot the buff was added to, -1 if it wasn't added due to
// stacking problems, and -2 if this is not a buff
// if caster is null, the buff will be added with the caster level being
@@ -3309,8 +3291,8 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid
buffs[emptyslot].spellid = spell_id;
buffs[emptyslot].casterlevel = caster_level;
if (caster && caster->IsClient())
strcpy(buffs[emptyslot].caster_name, caster->GetName());
if (caster && !caster->IsAura()) // maybe some other things we don't want to ...
strcpy(buffs[emptyslot].caster_name, caster->GetCleanName());
else
memset(buffs[emptyslot].caster_name, 0, 64);
buffs[emptyslot].casterid = caster ? caster->GetID() : 0;
@@ -3521,12 +3503,14 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
action->target = spelltar->GetID();
}
action->level = caster_level; // caster level, for animation only
action->spell_level = action->level = caster_level; // caster level, for animation only
action->type = 231; // 231 means a spell
action->spell = spell_id;
action->sequence = (uint32) (GetHeading() * 2); // just some random number
action->force = spells[spell_id].pushback;
action->hit_heading = GetHeading();
action->hit_pitch = spells[spell_id].pushup;
action->instrument_mod = GetInstrumentMod(spell_id);
action->buff_unknown = 0;
action->effect_flag = 0;
if(spelltar != this && spelltar->IsClient()) // send to target
spelltar->CastToClient()->QueuePacket(action_packet);
@@ -3953,53 +3937,21 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
// NOTE: this is what causes the buff icon to appear on the client, if
// this is a buff - but it sortof relies on the first packet.
// the complete sequence is 2 actions and 1 damage message
action->buff_unknown = 0x04; // this is a success flag
action->effect_flag = 0x04; // this is a success flag
if(IsEffectInSpell(spell_id, SE_TossUp))
{
action->buff_unknown = 0;
}
else if(spells[spell_id].pushback > 0 || spells[spell_id].pushup > 0)
if(spells[spell_id].pushback != 0.0f || spells[spell_id].pushup != 0.0f)
{
if(spelltar->IsClient())
{
if(!IsBuffSpell(spell_id))
{
spelltar->CastToClient()->SetKnockBackExemption(true);
action->buff_unknown = 0;
auto outapp_push =
new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spu = (PlayerPositionUpdateServer_Struct*)outapp_push->pBuffer;
double look_heading = CalculateHeadingToTarget(spelltar->GetX(), spelltar->GetY());
look_heading /= 256;
look_heading *= 360;
if(look_heading > 360)
look_heading -= 360;
//x and y are crossed mkay
double new_x = spells[spell_id].pushback * sin(double(look_heading * 3.141592 / 180.0));
double new_y = spells[spell_id].pushback * cos(double(look_heading * 3.141592 / 180.0));
spu->spawn_id = spelltar->GetID();
spu->x_pos = FloatToEQ19(spelltar->GetX());
spu->y_pos = FloatToEQ19(spelltar->GetY());
spu->z_pos = FloatToEQ19(spelltar->GetZ());
spu->delta_x = NewFloatToEQ13(new_x);
spu->delta_y = NewFloatToEQ13(new_y);
spu->delta_z = NewFloatToEQ13(spells[spell_id].pushup);
spu->heading = FloatToEQ19(spelltar->GetHeading());
spu->padding0002 =0;
spu->padding0006 =7;
spu->padding0014 =0x7f;
spu->padding0018 =0x5df27;
spu->animation = 0;
spu->delta_heading = NewFloatToEQ13(0);
outapp_push->priority = 6;
entity_list.QueueClients(this, outapp_push, true);
spelltar->CastToClient()->FastQueuePacket(&outapp_push);
}
} else if (RuleB(Spells, NPCSpellPush) && !spelltar->IsRooted() && spelltar->ForcedMovement == 0) {
spelltar->m_Delta.x += action->force * g_Math.FastSin(action->hit_heading);
spelltar->m_Delta.y += action->force * g_Math.FastCos(action->hit_heading);
spelltar->m_Delta.z += action->hit_pitch;
spelltar->ForcedMovement = 6;
}
}
@@ -4027,7 +3979,9 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
cd->source = action->source;
cd->type = action->type;
cd->spellid = action->spell;
cd->meleepush_xy = action->sequence;
cd->force = action->force;
cd->hit_heading = action->hit_heading;
cd->hit_pitch = action->hit_pitch;
cd->damage = 0;
if(!IsEffectInSpell(spell_id, SE_BindAffinity)){
entity_list.QueueCloseClients(
@@ -4234,6 +4188,19 @@ bool Mob::IsAffectedByBuff(uint16 spell_id)
return false;
}
bool Mob::IsAffectedByBuffByGlobalGroup(GlobalGroup group)
{
int buff_count = GetMaxTotalSlots();
for (int i = 0; i < buff_count; ++i) {
if (buffs[i].spellid == SPELL_UNKNOWN)
continue;
if (spells[buffs[i].spellid].spell_category == static_cast<int>(group))
return true;
}
return false;
}
// checks if 'this' can be affected by spell_id from caster
// returns true if the spell should fail, false otherwise
bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)
@@ -4418,6 +4385,36 @@ bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)
return false;
}
int Mob::GetResist(uint8 resist_type)
{
switch(resist_type)
{
case RESIST_FIRE:
return GetFR();
case RESIST_COLD:
return GetCR();
case RESIST_MAGIC:
return GetMR();
case RESIST_DISEASE:
return GetDR();
case RESIST_POISON:
return GetPR();
case RESIST_CORRUPTION:
return GetCorrup();
case RESIST_PRISMATIC:
return (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5;
case RESIST_CHROMATIC:
return std::min({GetFR(), GetCR(), GetMR(), GetDR(), GetPR()});
case RESIST_PHYSICAL:
if (IsNPC())
return GetPhR();
else
return 0;
default:
return 0;
}
}
//
// Spell resists:
// returns an effectiveness index from 0 to 100. for most spells, 100 means
@@ -4501,68 +4498,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
return 100;
}
int target_resist;
switch(resist_type)
{
case RESIST_FIRE:
target_resist = GetFR();
break;
case RESIST_COLD:
target_resist = GetCR();
break;
case RESIST_MAGIC:
target_resist = GetMR();
break;
case RESIST_DISEASE:
target_resist = GetDR();
break;
case RESIST_POISON:
target_resist = GetPR();
break;
case RESIST_CORRUPTION:
target_resist = GetCorrup();
break;
case RESIST_PRISMATIC:
target_resist = (GetFR() + GetCR() + GetMR() + GetDR() + GetPR()) / 5;
break;
case RESIST_CHROMATIC:
{
target_resist = GetFR();
int temp = GetCR();
if(temp < target_resist)
{
target_resist = temp;
}
int target_resist = GetResist(resist_type);
temp = GetMR();
if(temp < target_resist)
{
target_resist = temp;
}
temp = GetDR();
if(temp < target_resist)
{
target_resist = temp;
}
temp = GetPR();
if(temp < target_resist)
{
target_resist = temp;
}
// JULY 24, 2002 changes
int level = GetLevel();
if (IsPetOwnerClient() && caster->IsNPC() && !caster->IsPetOwnerClient()) {
auto owner = GetOwner();
if (owner != nullptr) {
target_resist = std::max(target_resist, owner->GetResist(resist_type));
level = owner->GetLevel();
}
break;
case RESIST_PHYSICAL:
{
if (IsNPC())
target_resist = GetPhR();
else
target_resist = 0;
}
default:
target_resist = 0;
}
//Setup our base resist chance.
@@ -4571,7 +4516,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
//Adjust our resist chance based on level modifiers
uint8 caster_level = level_override > 0 ? level_override : caster->GetLevel();
int temp_level_diff = GetLevel() - caster_level;
int temp_level_diff = level - caster_level;
//Physical Resists are calclated using their own formula derived from extensive parsing.
if (resist_type == RESIST_PHYSICAL) {
@@ -4580,7 +4525,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
else {
if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff))
if(IsNPC() && level >= RuleI(Casting,ResistFalloff))
{
int a = (RuleI(Casting,ResistFalloff)-1) - caster_level;
if(a > 0)
@@ -4593,7 +4538,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
}
if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15)
if(IsClient() && level >= 21 && temp_level_diff > 15)
{
temp_level_diff = 15;
}
@@ -4609,16 +4554,16 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
level_mod = -level_mod;
}
if(IsNPC() && (caster_level - GetLevel()) < -20)
if(IsNPC() && (caster_level - level) < -20)
{
level_mod = 1000;
}
//Even more level stuff this time dealing with damage spells
if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17)
if(IsNPC() && IsDamageSpell(spell_id) && level >= 17)
{
int level_diff;
if(GetLevel() >= RuleI(Casting,ResistFalloff))
if(level >= RuleI(Casting,ResistFalloff))
{
level_diff = (RuleI(Casting,ResistFalloff)-1) - caster_level;
if(level_diff < 0)
@@ -4628,7 +4573,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
}
else
{
level_diff = GetLevel() - caster_level;
level_diff = level - caster_level;
}
level_mod += (2 * level_diff);
}
@@ -4739,17 +4684,17 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
if(IsNPC())
{
if(GetLevel() > caster_level && GetLevel() >= 17 && caster_level <= 50)
if(level > caster_level && level >= 17 && caster_level <= 50)
{
partial_modifier += 5;
}
if(GetLevel() >= 30 && caster_level < 50)
if(level >= 30 && caster_level < 50)
{
partial_modifier += (caster_level - 25);
}
if(GetLevel() < 15)
if(level < 15)
{
partial_modifier -= 5;
}
@@ -4757,9 +4702,9 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
if(caster->IsNPC())
{
if((GetLevel() - caster_level) >= 20)
if((level - caster_level) >= 20)
{
partial_modifier += (GetLevel() - caster_level) * 1.5;
partial_modifier += (level - caster_level) * 1.5;
}
}
@@ -5535,6 +5480,8 @@ void Client::SendBuffNumHitPacket(Buffs_Struct &buff, int slot)
bi->entries[0].spell_id = buff.spellid;
bi->entries[0].tics_remaining = buff.ticsremaining;
bi->entries[0].num_hits = buff.numhits;
strn0cpy(bi->entries[0].caster, buff.caster_name, 64);
bi->name_lengths = strlen(bi->entries[0].caster);
FastQueuePacket(&outapp);
}
@@ -5620,6 +5567,7 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target)
else
buff->type = 0;
buff->name_lengths = 0; // hacky shit
uint32 index = 0;
for(int i = 0; i < buff_count; ++i)
{
@@ -5629,6 +5577,8 @@ EQApplicationPacket *Mob::MakeBuffsPacket(bool for_target)
buff->entries[index].spell_id = buffs[i].spellid;
buff->entries[index].tics_remaining = buffs[i].ticsremaining;
buff->entries[index].num_hits = buffs[i].numhits;
strn0cpy(buff->entries[index].caster, buffs[i].caster_name, 64);
buff->name_lengths += strlen(buff->entries[index].caster);
++index;
}
}
@@ -5714,7 +5664,7 @@ void Client::SendSpellAnim(uint16 targetid, uint16 spell_id)
a->source = this->GetID();
a->type = 231;
a->spell = spell_id;
a->sequence = 231;
a->hit_heading = GetHeading();
app.priority = 1;
entity_list.QueueCloseClients(this, &app, false, RuleI(Range, SpellParticles));
@@ -5725,8 +5675,8 @@ void Mob::CalcDestFromHeading(float heading, float distance, float MaxZDiff, flo
if (!distance) { return; }
if (!MaxZDiff) { MaxZDiff = 5; }
float ReverseHeading = 256 - heading;
float ConvertAngle = ReverseHeading * 1.40625f;
float ReverseHeading = 512 - heading;
float ConvertAngle = ReverseHeading * 360.0f / 512.0f;
if (ConvertAngle <= 270)
ConvertAngle = ConvertAngle + 90;
else
@@ -5734,8 +5684,8 @@ void Mob::CalcDestFromHeading(float heading, float distance, float MaxZDiff, flo
float Radian = ConvertAngle * (3.1415927f / 180.0f);
float CircleX = distance * cos(Radian);
float CircleY = distance * sin(Radian);
float CircleX = distance * std::cos(Radian);
float CircleY = distance * std::sin(Radian);
dX = CircleX + StartX;
dY = CircleY + StartY;
dZ = FindGroundZ(dX, dY, MaxZDiff);
@@ -5800,7 +5750,8 @@ void Mob::BeamDirectional(uint16 spell_id, int16 resist_adjust)
maxtarget_count++;
}
if (maxtarget_count >= spells[spell_id].aemaxtargets)
// not sure if we need this check, but probably do, need to check if it should be default limited or not
if (spells[spell_id].aemaxtargets && maxtarget_count >= spells[spell_id].aemaxtargets)
return;
}
++iter;
@@ -5815,8 +5766,10 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust)
if (IsBeneficialSpell(spell_id) && IsClient())
beneficial_targets = true;
float angle_start = spells[spell_id].directional_start + (GetHeading() * 360.0f / 256.0f);
float angle_end = spells[spell_id].directional_end + (GetHeading() * 360.0f / 256.0f);
float heading = GetHeading() * 360.0f / 512.0f; // convert to degrees
float angle_start = spells[spell_id].directional_start + heading;
float angle_end = spells[spell_id].directional_end + heading;
while (angle_start > 360.0f)
angle_start -= 360.0f;
@@ -5837,7 +5790,7 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust)
}
float heading_to_target =
(CalculateHeadingToTarget((*iter)->GetX(), (*iter)->GetY()) * 360.0f / 256.0f);
(CalculateHeadingToTarget((*iter)->GetX(), (*iter)->GetY()) * 360.0f / 512.0f);
while (heading_to_target < 0.0f)
heading_to_target += 360.0f;
@@ -5881,7 +5834,8 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust)
}
}
if (maxtarget_count >= spells[spell_id].aemaxtargets)
// my SHM breath could hit all 5 dummies I could summon in arena
if (spells[spell_id].aemaxtargets && maxtarget_count >= spells[spell_id].aemaxtargets)
return;
++iter;
+6
View File
@@ -19,6 +19,7 @@
#define PROC_TOOLOW 126 //Your will is not sufficient to command this weapon.
#define PROC_PETTOOLOW 127 //Your pet's will is not sufficient to command its weapon.
#define YOU_FLURRY 128 //You unleash a flurry of attacks.
#define FAILED_DISARM_TRAP 129 //You failed to disarm the trap.
#define DOORS_LOCKED 130 //It's locked and you're not holding the key.
#define DOORS_CANT_PICK 131 //This lock cannot be picked.
#define DOORS_INSUFFICIENT_SKILL 132 //You are not sufficiently skilled to pick this lock.
@@ -98,6 +99,7 @@
#define DUP_LORE 290 //Duplicate lore items are not allowed.
#define TGB_ON 293 //Target other group buff is *ON*.
#define TGB_OFF 294 //Target other group buff is *OFF*.
#define DISARMED_TRAP 305 //You have disarmed the trap.
#define LDON_SENSE_TRAP1 306 //You do not Sense any traps.
#define TRADESKILL_NOCOMBINE 334 //You cannot combine these items in this container type!
#define TRADESKILL_FAILED 336 //You lacked the skills to fashion the items together.
@@ -114,8 +116,11 @@
#define MEND_WORSEN 351 //You have worsened your wounds!
#define MEND_FAIL 352 //You have failed to mend your wounds.
#define LDON_SENSE_TRAP2 367 //You have not detected any traps.
#define TRAP_TOO_FAR 368 //You are too far away from that trap to affect it.
#define FAIL_DISARM_DETECTED_TRAP 370 //You fail to disarm the detected trap.
#define LOOT_LORE_ERROR 371 //You cannot loot this Lore Item. You already have one.
#define PICK_LORE 379 //You cannot pick up a lore item you already possess.
#define POISON_TOO_HIGH 382 // This poison is too high level for you to apply.
#define CONSENT_DENIED 390 //You do not have consent to summon that corpse.
#define DISCIPLINE_RDY 393 //You are ready to use a new discipline now.
#define CONSENT_INVALID_NAME 397 //Not a valid consent name.
@@ -421,6 +426,7 @@
#define SENSE_ANIMAL 12472 //You sense an animal in this direction.
#define SENSE_SUMMONED 12473 //You sense a summoned being in this direction.
#define SENSE_NOTHING 12474 //You don't sense anything.
#define SENSE_TRAP 12475 //You sense a trap in this direction.
#define LDON_SENSE_TRAP3 12476 //You don't sense any traps.
#define INTERRUPT_SPELL_OTHER 12478 //%1's casting is interrupted!
#define YOU_HIT_NONMELEE 12481 //You were hit by non-melee for %1 damage.
+1 -2
View File
@@ -2804,8 +2804,7 @@ void TaskManager::SendActiveTaskDescription(Client *c, int TaskID, int SequenceN
if (strlen(Tasks[TaskID]->Reward) != 0)
linker.SetProxyText(Tasks[TaskID]->Reward);
auto reward_link = linker.GenerateLink();
reward_text.append(reward_link);
reward_text.append(linker.GenerateLink());
}
else {
reward_text.append(Tasks[TaskID]->Reward);
+5 -3
View File
@@ -523,7 +523,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
else
qs_audit->char1_count += detail->charges;
//for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) {
for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items
const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot);
@@ -743,7 +742,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
qs_audit->char1_count += detail->charges;
// 'step 3' should never really see containers..but, just in case...
//for (uint8 sub_slot = SUB_BEGIN; ((sub_slot < inst->GetItem()->BagSlots) && (sub_slot < EmuConstants::ITEM_CONTAINER_SIZE)); ++sub_slot) {
for (uint8 sub_slot = EQEmu::inventory::containerBegin; (sub_slot < EQEmu::inventory::ContainerCount); ++sub_slot) { // this is to catch ALL items
const EQEmu::ItemInstance* bag_inst = inst->GetItem(sub_slot);
@@ -888,8 +886,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
const EQEmu::ItemData* item = inst->GetItem();
if(item && quest_npc == false) {
bool isPetAndCanHaveNoDrop = (RuleB(Pets, CanTakeNoDrop) &&
_CLIENTPET(tradingWith) &&
tradingWith->GetPetType()<=petOther);
// if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it
if(GetGM() || (item->NoDrop != 0 && inst->IsAttuned() == false)) {
if(GetGM() || (inst->IsAttuned() == false &&
(item->NoDrop != 0 || isPetAndCanHaveNoDrop))) {
// pets need to look inside bags and try to equip items found there
if (item->IsClassBag() && item->BagSlots > 0) {
for (int16 bslot = EQEmu::inventory::containerBegin; bslot < item->BagSlots; bslot++) {
+269 -27
View File
@@ -52,10 +52,12 @@ CREATE TABLE traps (
Trap::Trap() :
Entity(),
respawn_timer(600000),
chkarea_timer(500),
chkarea_timer(1000),
reset_timer(5000),
m_Position(glm::vec3())
{
trap_id = 0;
db_id = 0;
maxzdiff = 0;
radius = 0;
effect = 0;
@@ -64,12 +66,20 @@ Trap::Trap() :
skill = 0;
level = 0;
respawn_timer.Disable();
reset_timer.Disable();
detected = false;
disarmed = false;
respawn_time = 0;
respawn_var = 0;
hiddenTrigger = nullptr;
ownHiddenTrigger = false;
chance = 0;
triggered_number = 0;
times_triggered = 0;
group = 0;
despawn_when_triggered = false;
charid = 0;
undetectable = false;
}
Trap::~Trap()
@@ -80,8 +90,7 @@ Trap::~Trap()
bool Trap::Process()
{
if (chkarea_timer.Enabled() && chkarea_timer.Check()
/*&& zone->GetClientCount() > 0*/ )
if (chkarea_timer.Enabled() && chkarea_timer.Check() && !reset_timer.Enabled())
{
Mob* trigger = entity_list.GetTrapTrigger(this);
if (trigger && !(trigger->IsClient() && trigger->CastToClient()->GetGM()))
@@ -89,6 +98,13 @@ bool Trap::Process()
Trigger(trigger);
}
}
else if (reset_timer.Enabled() && reset_timer.Check())
{
Log(Logs::General, Logs::Traps, "Reset timer disabled in Reset Check Process for trap %d.", trap_id);
reset_timer.Disable();
charid = 0;
}
if (respawn_timer.Enabled() && respawn_timer.Check())
{
detected = false;
@@ -96,11 +112,15 @@ bool Trap::Process()
chkarea_timer.Enable();
respawn_timer.Disable();
}
return true;
}
void Trap::Trigger(Mob* trigger)
{
Log(Logs::General, Logs::Traps, "Trap %d triggered by %s for the %d time!", trap_id, trigger->GetName(), times_triggered + 1);
int i = 0;
const NPCType* tmp = 0;
switch (effect)
@@ -128,7 +148,7 @@ void Trap::Trigger(Mob* trigger)
entity_list.MessageClose(trigger,false,effectvalue,13,"%s",message.c_str());
}
entity_list.SendAlarm(this,trigger,effectvalue);
entity_list.SendAlarm(this,trigger, effectvalue2);
break;
case trapTypeMysticSpawn:
if (message.empty())
@@ -148,6 +168,8 @@ void Trap::Trigger(Mob* trigger)
auto spawnPosition = randomOffset + glm::vec4(m_Position, 0.0f);
auto new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3);
new_npc->AddLootTable();
if (new_npc->DropsGlobalLoot())
new_npc->CheckGlobalLootTables();
entity_list.AddNPC(new_npc);
new_npc->AddToHateList(trigger,1);
}
@@ -171,6 +193,8 @@ void Trap::Trigger(Mob* trigger)
auto spawnPosition = randomOffset + glm::vec4(m_Position, 0.0f);
auto new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3);
new_npc->AddLootTable();
if (new_npc->DropsGlobalLoot())
new_npc->CheckGlobalLootTables();
entity_list.AddNPC(new_npc);
new_npc->AddToHateList(trigger,1);
}
@@ -192,7 +216,7 @@ void Trap::Trigger(Mob* trigger)
int dmg = zone->random.Int(effectvalue, effectvalue2);
trigger->SetHP(trigger->GetHP() - dmg);
a->damage = dmg;
a->meleepush_xy = zone->random.Int(0, 1234567);
a->hit_heading = 0.0f;
a->source = GetHiddenTrigger()!=nullptr ? GetHiddenTrigger()->GetID() : trigger->GetID();
a->spellid = 0;
a->target = trigger->GetID();
@@ -201,12 +225,41 @@ void Trap::Trigger(Mob* trigger)
safe_delete(outapp);
}
}
respawn_timer.Start((respawn_time + zone->random.Int(0, respawn_var)) * 1000);
chkarea_timer.Disable();
disarmed = true;
if (trigger && trigger->IsClient())
{
trigger->CastToClient()->trapid = trap_id;
charid = trigger->CastToClient()->CharacterID();
}
bool update = false;
if (despawn_when_triggered)
{
Log(Logs::General, Logs::Traps, "Trap %d is despawning after being triggered.", trap_id);
update = true;
}
else
{
reset_timer.Start(5000);
}
if (triggered_number > 0)
++times_triggered;
if (triggered_number > 0 && triggered_number <= times_triggered)
{
Log(Logs::General, Logs::Traps, "Triggered number for trap %d reached. %d/%d", trap_id, times_triggered, triggered_number);
update = true;
}
if (update)
{
UpdateTrap();
}
}
Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) {
Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist, float &trap_curdist, bool detected)
{
float dist = 999999;
Trap* current_trap = nullptr;
@@ -215,61 +268,161 @@ Trap* EntityList::FindNearbyTrap(Mob* searcher, float max_dist) {
for (auto it = trap_list.begin(); it != trap_list.end(); ++it) {
cur = it->second;
if(cur->disarmed)
if(cur->disarmed || (detected && !cur->detected) || cur->undetectable)
continue;
auto diff = glm::vec3(searcher->GetPosition()) - cur->m_Position;
float curdist = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
float curdist = diff.x*diff.x + diff.y*diff.y;
diff.z = std::abs(diff.z);
if (curdist < max_dist2 && curdist < dist)
if (curdist < max_dist2 && curdist < dist && diff.z <= cur->maxzdiff)
{
Log(Logs::General, Logs::Traps, "Trap %d is curdist %0.1f", cur->db_id, curdist);
dist = curdist;
current_trap = cur;
}
}
if (current_trap != nullptr)
{
Log(Logs::General, Logs::Traps, "Trap %d is the closest trap.", current_trap->db_id);
trap_curdist = dist;
}
else
trap_curdist = INVALID_INDEX;
return current_trap;
}
Mob* EntityList::GetTrapTrigger(Trap* trap) {
Mob* savemob = 0;
Mob* EntityList::GetTrapTrigger(Trap* trap)
{
float maxdist = trap->radius * trap->radius;
for (auto it = client_list.begin(); it != client_list.end(); ++it) {
for (auto it = client_list.begin(); it != client_list.end(); ++it)
{
Client* cur = it->second;
auto diff = glm::vec3(cur->GetPosition()) - trap->m_Position;
diff.z = std::abs(diff.z);
if ((diff.x*diff.x + diff.y*diff.y) <= maxdist
&& diff.z < trap->maxzdiff)
&& diff.z <= trap->maxzdiff)
{
if (zone->random.Roll(trap->chance))
return(cur);
else
savemob = cur;
}
//This prevents the trap from triggering on players while zoning.
if (strcmp(cur->GetName(), "No name") == 0)
continue;
if (cur->trapid == 0 && !cur->GetGM() && (trap->chance == 0 || zone->random.Roll(trap->chance)))
{
Log(Logs::General, Logs::Traps, "%s is about to trigger trap %d of chance %d. diff: %0.2f maxdist: %0.2f zdiff: %0.2f maxzdiff: %0.2f", cur->GetName(), trap->trap_id, trap->chance, (diff.x*diff.x + diff.y*diff.y), maxdist, diff.z, trap->maxzdiff);
return cur;
}
}
else
{
if (cur->trapid == trap->trap_id)
{
Log(Logs::General, Logs::Traps, "%s is clearing trapid for trap %d", cur->GetName(), trap->trap_id);
cur->trapid = 0;
}
}
}
return savemob;
return nullptr;
}
//todo: rewrite this to not need direct access to trap members.
bool EntityList::IsTrapGroupSpawned(uint32 trap_id, uint8 group)
{
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap() && cur->group == group && cur->trap_id != trap_id)
{
return true;
}
++it;
}
return false;
}
void EntityList::UpdateAllTraps(bool respawn, bool repopnow)
{
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap())
{
cur->UpdateTrap(respawn, repopnow);
}
++it;
}
Log(Logs::General, Logs::Traps, "All traps updated.");
}
void EntityList::GetTrapInfo(Client* client)
{
uint8 count = 0;
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap())
{
bool isset = (cur->chkarea_timer.Enabled() && !cur->reset_timer.Enabled());
client->Message(CC_Default, " Trap: (%d) found at %0.2f,%0.2f,%0.2f. Times Triggered: %d Is Active: %d Group: %d Message: %s", cur->trap_id, cur->m_Position.x, cur->m_Position.y, cur->m_Position.z, cur->times_triggered, isset, cur->group, cur->message.c_str());
++count;
}
++it;
}
client->Message(CC_Default, "%d traps found.", count);
}
void EntityList::ClearTrapPointers()
{
auto it = trap_list.begin();
while (it != trap_list.end())
{
Trap* cur = it->second;
if (cur->IsTrap())
{
cur->DestroyHiddenTrigger();
}
++it;
}
}
bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) {
std::string query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level "
"FROM traps WHERE zone='%s' AND version=%u", zonename, version);
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
"`group`, triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND version=%u", zonename, version);
auto results = QueryDatabase(query);
if (!results.Success()) {
return false;
}
for (auto row = results.begin(); row != results.end(); ++row) {
uint32 tid = atoi(row[0]);
uint8 grp = atoi(row[15]);
if (grp > 0)
{
// If a member of our group is already spawned skip loading this trap.
if (entity_list.IsTrapGroupSpawned(tid, grp))
{
continue;
}
}
auto trap = new Trap();
trap->trap_id = atoi(row[0]);
trap->trap_id = tid;
trap->db_id = tid;
trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3]));
trap->effect = atoi(row[4]);
trap->effectvalue = atoi(row[5]);
@@ -282,8 +435,13 @@ bool ZoneDatabase::LoadTraps(const char* zonename, int16 version) {
trap->respawn_time = atoi(row[12]);
trap->respawn_var = atoi(row[13]);
trap->level = atoi(row[14]);
trap->group = grp;
trap->triggered_number = atoi(row[16]);
trap->despawn_when_triggered = atobool(row[17]);
trap->undetectable = atobool(row[18]);
entity_list.AddTrap(trap);
trap->CreateHiddenTrigger();
Log(Logs::General, Logs::Traps, "Trap %d successfully loaded.", trap->trap_id);
}
return true;
@@ -318,3 +476,87 @@ void Trap::CreateHiddenTrigger()
hiddenTrigger = npca;
ownHiddenTrigger = true;
}
bool ZoneDatabase::SetTrapData(Trap* trap, bool repopnow) {
uint32 dbid = trap->db_id;
std::string query;
if (trap->group > 0)
{
query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
"triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND `group`=%d AND id != %d ORDER BY RAND() LIMIT 1", zone->GetShortName(), trap->group, dbid);
}
else
{
// We could just use the existing data here, but querying the DB is not expensive, and allows content developers to change traps without rebooting.
query = StringFormat("SELECT id, x, y, z, effect, effectvalue, effectvalue2, skill, "
"maxzdiff, radius, chance, message, respawn_time, respawn_var, level, "
"triggered_number, despawn_when_triggered, undetectable FROM traps WHERE zone='%s' AND id = %d", zone->GetShortName(), dbid);
}
auto results = QueryDatabase(query);
if (!results.Success()) {
return false;
}
for (auto row = results.begin(); row != results.end(); ++row) {
trap->db_id = atoi(row[0]);
trap->m_Position = glm::vec3(atof(row[1]), atof(row[2]), atof(row[3]));
trap->effect = atoi(row[4]);
trap->effectvalue = atoi(row[5]);
trap->effectvalue2 = atoi(row[6]);
trap->skill = atoi(row[7]);
trap->maxzdiff = atof(row[8]);
trap->radius = atof(row[9]);
trap->chance = atoi(row[10]);
trap->message = row[11];
trap->respawn_time = atoi(row[12]);
trap->respawn_var = atoi(row[13]);
trap->level = atoi(row[14]);
trap->triggered_number = atoi(row[15]);
trap->despawn_when_triggered = atobool(row[16]);
trap->undetectable = atobool(row[17]);
trap->CreateHiddenTrigger();
if (repopnow)
{
trap->chkarea_timer.Enable();
}
else
{
trap->respawn_timer.Start((trap->respawn_time + zone->random.Int(0, trap->respawn_var)) * 1000);
}
if (trap->trap_id != trap->db_id)
Log(Logs::General, Logs::Traps, "Trap (%d) DBID has changed from %d to %d", trap->trap_id, dbid, trap->db_id);
return true;
}
return false;
}
void Trap::UpdateTrap(bool respawn, bool repopnow)
{
respawn_timer.Disable();
chkarea_timer.Disable();
reset_timer.Disable();
if (hiddenTrigger)
{
hiddenTrigger->Depop();
hiddenTrigger = nullptr;
}
times_triggered = 0;
Client* trigger = entity_list.GetClientByCharID(charid);
if (trigger)
{
trigger->trapid = 0;
}
charid = 0;
if (respawn)
{
database.SetTrapData(this, repopnow);
}
}
+11 -2
View File
@@ -49,11 +49,14 @@ public:
NPC * GetHiddenTrigger() { return hiddenTrigger; }
void SetHiddenTrigger(NPC* n) { hiddenTrigger = n; }
void CreateHiddenTrigger();
void DestroyHiddenTrigger() { hiddenTrigger = nullptr; }
void UpdateTrap(bool respawn = true, bool repopnow = false);
//Trap data, leave this unprotected
Timer respawn_timer; //Respawn Time when Trap's been disarmed
Timer chkarea_timer;
uint32 trap_id; //Database ID of trap
Timer reset_timer; //How long a trap takes to reset before triggering again.
uint32 trap_id; //Original ID of the trap from DB. This value never changes.
uint32 db_id; //The DB ID of the trap that currently is spawned.
glm::vec3 m_Position;
float maxzdiff; //maximum z diff to be triggerable
float radius; //radius around trap to be triggerable
@@ -67,6 +70,12 @@ public:
bool disarmed;
uint32 respawn_time;
uint32 respawn_var;
uint8 triggered_number;
uint8 times_triggered;
uint8 group;
bool despawn_when_triggered;
uint32 charid; //ID of character that triggered trap. This is cleared when the trap despawns are resets.
bool undetectable;
std::string message;
protected:
+192 -161
View File
@@ -29,10 +29,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "npc.h"
#include "quest_parser_collection.h"
#include "water_map.h"
#include "fastmath.h"
#include <math.h>
#include <stdlib.h>
extern FastMath g_Math;
struct wp_distance
{
float dist;
@@ -176,9 +179,15 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot)
cur_wp = -2; // flag as quest controlled w/no grid
Log(Logs::Detail, Logs::AI, "MoveTo %s without a grid.", to_string(static_cast<glm::vec3>(position)).c_str());
}
glm::vec3 dest(position);
m_CurrentWayPoint = position;
m_CurrentWayPoint.z = GetFixedZ(dest);
if (saveguardspot)
{
m_GuardPoint = position;
m_GuardPoint = m_CurrentWayPoint;
if (m_GuardPoint.w == 0)
m_GuardPoint.w = 0.0001; //hack to make IsGuarding simpler
@@ -189,7 +198,6 @@ void NPC::MoveTo(const glm::vec4& position, bool saveguardspot)
Log(Logs::Detail, Logs::AI, "Setting guard position to %s", to_string(static_cast<glm::vec3>(m_GuardPoint)).c_str());
}
m_CurrentWayPoint = position;
cur_wp_pause = 0;
pLastFightingDelayMoving = 0;
if (AI_walking_timer->Enabled())
@@ -430,28 +438,7 @@ float Mob::CalculateDistance(float x, float y, float z) {
return (float)sqrtf(((m_Position.x - x)*(m_Position.x - x)) + ((m_Position.y - y)*(m_Position.y - y)) + ((m_Position.z - z)*(m_Position.z - z)));
}
float Mob::CalculateHeadingToTarget(float in_x, float in_y) {
float angle;
if (in_x - m_Position.x > 0)
angle = -90 + atan((float)(in_y - m_Position.y) / (float)(in_x - m_Position.x)) * 180 / M_PI;
else if (in_x - m_Position.x < 0)
angle = +90 + atan((float)(in_y - m_Position.y) / (float)(in_x - m_Position.x)) * 180 / M_PI;
else // Added?
{
if (in_y - m_Position.y > 0)
angle = 0;
else
angle = 180;
}
if (angle < 0)
angle += 360;
if (angle > 360)
angle -= 360;
return (256 * (360 - angle) / 360.0f);
}
bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed) {
bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ, bool calcHeading) {
if (GetID() == 0)
return true;
@@ -495,7 +482,7 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed) {
m_Position.y = new_y;
m_Position.z = new_z;
if(fix_z_timer.Check() &&
if(checkZ && fix_z_timer.Check() &&
(!this->IsEngaged() || flee_mode || currently_fleeing))
this->FixZ();
@@ -562,7 +549,8 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed) {
m_Position.x = new_x;
m_Position.y = new_y;
m_Position.z = new_z;
m_Position.w = CalculateHeadingToTarget(x, y);
if (calcHeading)
m_Position.w = CalculateHeadingToTarget(x, y);
tar_ndx = 20 - numsteps;
}
else
@@ -600,10 +588,11 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed) {
m_Position.x = new_x;
m_Position.y = new_y;
m_Position.z = new_z;
m_Position.w = CalculateHeadingToTarget(x, y);
if (calcHeading)
m_Position.w = CalculateHeadingToTarget(x, y);
}
if (fix_z_timer.Check() && !this->IsEngaged())
if (checkZ && fix_z_timer.Check() && !this->IsEngaged())
this->FixZ();
SetMoving(true);
@@ -757,157 +746,199 @@ void Mob::SendToFixZ(float new_x, float new_y, float new_z) {
}
}
void Mob::FixZ()
float Mob::GetFixedZ(glm::vec3 dest, int32 z_find_offset)
{
BenchTimer timer;
timer.reset();
float new_z = dest.z;
if (zone->HasMap() && RuleB(Map, FixZWhenMoving) && (flymode != 1 && flymode != 2))
if (zone->HasMap() && RuleB(Map, FixZWhenMoving) &&
(flymode != 1 && flymode != 2))
{
if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap() ||
(zone->HasWaterMap() && !zone->watermap->InWater(glm::vec3(m_Position))))
if (!RuleB(Watermap, CheckForWaterWhenMoving) || !zone->HasWaterMap()
|| (zone->HasWaterMap() &&
!zone->watermap->InWater(glm::vec3(m_Position))))
{
/* Any more than 5 in the offset makes NPC's hop/snap to ceiling in small corridors */
float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5);
new_z += GetModelOffset();
auto duration = timer.elapsed();
new_z = this->FindDestGroundZ(dest, z_find_offset);
if (new_z != BEST_Z_INVALID)
{
new_z += this->GetZOffset();
Log(
Logs::Moderate,
Logs::FixZ,
"Mob::FixZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf",
this->GetCleanName(),
new_z,
m_Position.x,
m_Position.y,
m_Position.z,
duration
);
if ((new_z > -2000) && new_z != BEST_Z_INVALID) {
if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(78, 0, 0, 0, 0);
m_Position.z = new_z;
// If bad new Z restore old one
if (new_z < -2000) {
new_z = m_Position.z;
}
}
else {
if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(103, 0, 0, 0, 0);
}
Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f", this->GetCleanName(), std::abs(m_Position.z - new_z));
}
auto duration = timer.elapsed();
last_z = m_Position.z;
Log(Logs::Moderate, Logs::FixZ,
"Mob::GetFixedZ() (%s) returned %4.3f at %4.3f, %4.3f, %4.3f - Took %lf",
this->GetCleanName(), new_z, dest.x, dest.y, dest.z, duration);
}
return new_z;
}
void Mob::FixZ(int32 z_find_offset /*= 5*/)
{
glm::vec3 current_loc(m_Position);
float new_z = GetFixedZ(current_loc, z_find_offset);
if (!IsClient() && new_z != m_Position.z)
{
if ((new_z > -2000) && new_z != BEST_Z_INVALID) {
if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(78, 0, 0, 0, 0);
m_Position.z = new_z;
}
else {
if (RuleB(Map, MobZVisualDebug))
this->SendAppearanceEffect(103, 0, 0, 0, 0);
Log(Logs::General, Logs::FixZ, "%s is failing to find Z %f",
this->GetCleanName(), std::abs(m_Position.z - new_z));
}
}
}
float Mob::GetModelOffset() const {
float offset = 3.125f;
float Mob::GetZOffset() const {
float offset = 3.125f;
switch (race) {
case 436:
offset = 0.577f;
break;
case 430:
offset = 0.5f;
break;
case 432:
offset = 1.9f;
break;
case 435:
offset = 0.93f;
break;
case 450:
offset = 0.938f;
break;
case 479:
offset = 0.8f;
break;
case 451:
offset = 0.816f;
break;
case 437:
offset = 0.527f;
break;
case 439:
offset = 1.536f;
break;
case 415:
offset = 1.0f;
break;
case 438:
offset = 0.776f;
break;
case 452:
offset = 0.776f;
break;
case 441:
offset = 0.816f;
break;
case 440:
offset = 0.938f;
break;
case 468:
offset = 1.0f;
break;
case 459:
offset = 1.0f;
break;
case 462:
offset = 1.5f;
break;
case 530:
offset = 1.2f;
break;
case 549:
offset = 0.5f;
break;
case 548:
offset = 0.5f;
break;
case 547:
offset = 0.5f;
break;
case 604:
offset = 1.2f;
break;
case 653:
offset = 5.9f;
break;
case 658:
offset = 4.0f;
break;
case 323:
offset = 5.0f;
break;
case 663:
offset = 5.0f;
break;
case 664:
offset = 4.0f;
break;
case 703:
offset = 9.0f;
break;
case 688:
offset = 5.0f;
break;
case 669:
offset = 7.0f;
break;
case 687:
offset = 2.0f;
break;
case 686:
offset = 2.0f;
break;
default:
offset = 3.125f;
switch (race) {
case 436:
offset = 0.577f;
break;
case 430:
offset = 0.5f;
break;
case 432:
offset = 1.9f;
break;
case 435:
offset = 0.93f;
break;
case 450:
offset = 0.938f;
break;
case 479:
offset = 0.8f;
break;
case 451:
offset = 0.816f;
break;
case 437:
offset = 0.527f;
break;
case 439:
offset = 1.536f;
break;
case 415:
offset = 1.0f;
break;
case 438:
offset = 0.776f;
break;
case 452:
offset = 0.776f;
break;
case 441:
offset = 0.816f;
break;
case 440:
offset = 0.938f;
break;
case 468:
offset = 1.0f;
break;
case 459:
offset = 1.0f;
break;
case 462:
offset = 1.5f;
break;
case 530:
offset = 1.2f;
break;
case 549:
offset = 0.5f;
break;
case 548:
offset = 0.5f;
break;
case 547:
offset = 0.5f;
break;
case 604:
offset = 1.2f;
break;
case 653:
offset = 5.9f;
break;
case 658:
offset = 4.0f;
break;
case 323:
offset = 5.0f;
break;
case 663:
offset = 5.0f;
break;
case 664:
offset = 4.0f;
break;
case 703:
offset = 9.0f;
break;
case 688:
offset = 5.0f;
break;
case 669:
offset = 7.0f;
break;
case 687:
offset = 2.0f;
break;
case 686:
offset = 2.0f;
break;
default:
offset = 3.125f;
}
return 0.2 * GetSize() * offset;
}
// This function will try to move the mob along the relative angle a set distance
// if it can't be moved, it will lower the distance and try again
// If we want to move on like say a spawn, we can pass send as false
void Mob::TryMoveAlong(float distance, float angle, bool send)
{
angle += GetHeading();
angle = FixHeading(angle);
glm::vec3 tmp_pos;
glm::vec3 new_pos = GetPosition();
new_pos.x += distance * g_Math.FastSin(angle);
new_pos.y += distance * g_Math.FastCos(angle);
new_pos.z += GetZOffset();
if (zone->HasMap()) {
auto new_z = zone->zonemap->FindClosestZ(new_pos, nullptr);
if (new_z != BEST_Z_INVALID)
new_pos.z = new_z;
if (zone->zonemap->LineIntersectsZone(GetPosition(), new_pos, 0.0f, &tmp_pos))
new_pos = tmp_pos;
}
return 0.2 * GetSize() * offset;
new_pos.z = GetFixedZ(new_pos);
Teleport(new_pos);
if (send)
SendPositionUpdate();
}
int ZoneDatabase::GetHighestGrid(uint32 zoneid) {
+9
View File
@@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdio.h>
#include <iomanip>
#include <stdarg.h>
#include <limits.h>
#ifdef _WINDOWS
#include <process.h>
@@ -338,6 +339,7 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
if (ztz->response <= 0) {
zc2->success = ZONE_ERROR_NOTREADY;
entity->CastToMob()->SetZone(ztz->current_zone_id, ztz->current_instance_id);
entity->CastToClient()->SetZoning(false);
}
else {
entity->CastToClient()->UpdateWho(1);
@@ -1812,6 +1814,13 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
break;
}
case ServerOP_UCSServerStatusReply:
{
auto ucsss = (UCSServerStatus_Struct*)pack->pBuffer;
if (zone)
zone->SetUCSServerAvailable((ucsss->available != 0), ucsss->timestamp);
break;
}
case ServerOP_CZSetEntityVariableByNPCTypeID:
{
CZSetEntVarByNPCTypeID_Struct* CZM = (CZSetEntVarByNPCTypeID_Struct*)pack->pBuffer;
+43 -4
View File
@@ -148,6 +148,8 @@ bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool iStaticZone) {
UpdateWindowTitle();
zone->GetTimeSync();
zone->RequestUCSServerStatus();
/* Set Logging */
LogSys.StartFileLogs(StringFormat("%s_version_%u_inst_id_%u_port_%u", zone->GetShortName(), zone->GetInstanceVersion(), zone->GetInstanceID(), ZoneConfig::get()->ZonePort));
@@ -849,6 +851,9 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
GuildBanks = new GuildBankManager;
else
GuildBanks = nullptr;
m_ucss_available = false;
m_last_ucss_update = 0;
}
Zone::~Zone() {
@@ -970,6 +975,8 @@ bool Zone::Init(bool iStaticZone) {
LoadAlternateAdvancement();
database.LoadGlobalLoot();
//Load merchant data
zone->GetMerchantDataForZoneLoad();
@@ -1430,7 +1437,8 @@ void Zone::StartShutdownTimer(uint32 set_time) {
bool Zone::Depop(bool StartSpawnTimer) {
std::map<uint32,NPCType *>::iterator itr;
entity_list.Depop(StartSpawnTimer);
entity_list.ClearTrapPointers();
entity_list.UpdateAllTraps(false);
/* Refresh npctable (cache), getting current info from database. */
while(!npctable.empty()) {
itr = npctable.begin();
@@ -1438,6 +1446,9 @@ bool Zone::Depop(bool StartSpawnTimer) {
npctable.erase(itr);
}
// clear spell cache
database.ClearNPCSpells();
return true;
}
@@ -1498,6 +1509,8 @@ void Zone::Repop(uint32 delay) {
iterator.RemoveCurrent();
}
entity_list.ClearTrapPointers();
quest_manager.ClearAllTimers();
if (!database.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion(), delay))
@@ -1505,6 +1518,8 @@ void Zone::Repop(uint32 delay) {
initgrids_timer.Start();
entity_list.UpdateAllTraps(true, true);
//MODDING HOOK FOR REPOP
mod_repop();
}
@@ -1512,7 +1527,7 @@ void Zone::Repop(uint32 delay) {
void Zone::GetTimeSync()
{
if (worldserver.Connected() && !zone_has_current_time) {
auto pack = new ServerPacket(ServerOP_GetWorldTime, 0);
auto pack = new ServerPacket(ServerOP_GetWorldTime, 1);
worldserver.SendPacket(pack);
safe_delete(pack);
}
@@ -1855,14 +1870,17 @@ bool ZoneDatabase::GetDecayTimes(npcDecayTimes_Struct* npcCorpseDecayTimes) {
return true;
}
void Zone::weatherSend()
void Zone::weatherSend(Client* client)
{
auto outapp = new EQApplicationPacket(OP_Weather, 8);
if(zone_weather>0)
outapp->pBuffer[0] = zone_weather-1;
if(zone_weather>0)
outapp->pBuffer[4] = zone->weather_intensity;
entity_list.QueueClients(0, outapp);
if (client)
client->QueuePacket(outapp);
else
entity_list.QueueClients(0, outapp);
safe_delete(outapp);
}
@@ -2223,6 +2241,8 @@ void Zone::DoAdventureActions()
{
NPC* npc = new NPC(tmp, nullptr, glm::vec4(ds->assa_x, ds->assa_y, ds->assa_z, ds->assa_h), FlyMode3);
npc->AddLootTable();
if (npc->DropsGlobalLoot())
npc->CheckGlobalLootTables();
entity_list.AddNPC(npc);
npc->Shout("Rarrrgh!");
did_adventure_actions = true;
@@ -2326,3 +2346,22 @@ void Zone::UpdateHotzone()
is_hotzone = atoi(row[0]) == 0 ? false: true;
}
void Zone::RequestUCSServerStatus() {
auto outapp = new ServerPacket(ServerOP_UCSServerStatusRequest, sizeof(UCSServerStatus_Struct));
auto ucsss = (UCSServerStatus_Struct*)outapp->pBuffer;
ucsss->available = 0;
ucsss->port = Config->ZonePort;
ucsss->unused = 0;
worldserver.SendPacket(outapp);
safe_delete(outapp);
}
void Zone::SetUCSServerAvailable(bool ucss_available, uint32 update_timestamp) {
if (m_last_ucss_update == update_timestamp && m_ucss_available != ucss_available) {
m_ucss_available = false;
RequestUCSServerStatus();
return;
}
if (m_last_ucss_update < update_timestamp)
m_ucss_available = ucss_available;
}
+17 -1
View File
@@ -29,6 +29,7 @@
#include "spawngroup.h"
#include "aa_ability.h"
#include "pathfinder_interface.h"
#include "global_loot_manager.h"
struct ZonePoint
{
@@ -209,6 +210,7 @@ public:
void LoadAlternateCurrencies();
void LoadNPCEmotes(LinkedList<NPC_Emote_Struct*>* NPCEmoteList);
void ReloadWorld(uint32 Option);
void ReloadMerchants();
Map* zonemap;
WaterMap* watermap;
@@ -222,7 +224,7 @@ public:
void SetDate(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute);
void SetTime(uint8 hour, uint8 minute, bool update_world = true);
void weatherSend();
void weatherSend(Client* client = nullptr);
bool CanBind() const { return(can_bind); }
bool IsCity() const { return(is_city); }
bool CanDoCombat() const { return(can_combat); }
@@ -268,6 +270,15 @@ public:
void UpdateHotzone();
std::unordered_map<int, item_tick_struct> tick_items;
inline std::vector<int> GetGlobalLootTables(NPC *mob) const { return m_global_loot.GetGlobalLootTables(mob); }
inline void AddGlobalLootEntry(GlobalLootEntry &in) { return m_global_loot.AddEntry(in); }
inline void ShowZoneGlobalLoot(Client *to) { m_global_loot.ShowZoneGlobalLoot(to); }
inline void ShowNPCGlobalLoot(Client *to, NPC *who) { m_global_loot.ShowNPCGlobalLoot(to, who); }
void RequestUCSServerStatus();
void SetUCSServerAvailable(bool ucss_available, uint32 update_timestamp);
bool IsUCSServerAvailable() { return m_ucss_available; }
// random object that provides random values for the zone
EQEmu::Random random;
@@ -346,6 +357,11 @@ private:
QGlobalCache *qGlobals;
Timer hotzone_timer;
GlobalLootManager m_global_loot;
bool m_ucss_available;
uint32 m_last_ucss_update;
};
#endif
+1 -1
View File
@@ -49,7 +49,7 @@ class ZoneConfig : public EQEmuConfig {
_zone_config=new ZoneConfig;
_config=_zone_config;
return _config->ParseFile(EQEmuConfig::ConfigFile.c_str(),"server");
return _config->parseFile();
}
// Accessors for the static private object
+279 -62
View File
@@ -34,28 +34,15 @@ ZoneDatabase::ZoneDatabase(const char* host, const char* user, const char* passw
void ZoneDatabase::ZDBInitVars() {
memset(door_isopen_array, 0, sizeof(door_isopen_array));
npc_spells_maxid = 0;
npc_spellseffects_maxid = 0;
npc_spells_cache = 0;
npc_spellseffects_cache = 0;
npc_spells_loadtried = 0;
npc_spellseffects_loadtried = 0;
max_faction = 0;
faction_array = nullptr;
}
ZoneDatabase::~ZoneDatabase() {
unsigned int x;
if (npc_spells_cache) {
for (x = 0; x <= npc_spells_maxid; x++) {
safe_delete_array(npc_spells_cache[x]);
}
safe_delete_array(npc_spells_cache);
}
safe_delete_array(npc_spells_loadtried);
if (npc_spellseffects_cache) {
for (x = 0; x <= npc_spellseffects_maxid; x++) {
for (int x = 0; x <= npc_spellseffects_maxid; x++) {
safe_delete_array(npc_spellseffects_cache[x]);
}
safe_delete_array(npc_spellseffects_cache);
@@ -63,7 +50,7 @@ ZoneDatabase::~ZoneDatabase() {
safe_delete_array(npc_spellseffects_loadtried);
if (faction_array != nullptr) {
for (x = 0; x <= max_faction; x++) {
for (int x = 0; x <= max_faction; x++) {
if (faction_array[x] != 0)
safe_delete(faction_array[x]);
}
@@ -338,62 +325,260 @@ bool ZoneDatabase::logevents(const char* accountname,uint32 accountid,uint8 stat
return true;
}
void ZoneDatabase::RegisterBug(BugReport_Struct* bug_report) {
if (!bug_report)
return;
void ZoneDatabase::UpdateBug(BugStruct* bug) {
uint32 len = strlen(bug->bug);
char* bugtext = nullptr;
if(len > 0)
{
bugtext = new char[2*len+1];
memset(bugtext, 0, 2*len+1);
DoEscapeString(bugtext, bug->bug, len);
size_t len = 0;
char* name_ = nullptr;
char* ui_ = nullptr;
char* type_ = nullptr;
char* target_ = nullptr;
char* bug_ = nullptr;
len = strlen(bug_report->reporter_name);
if (len) {
if (len > 63) // check against db column size
len = 63;
name_ = new char[(2 * len + 1)];
memset(name_, 0, (2 * len + 1));
DoEscapeString(name_, bug_report->reporter_name, len);
}
len = strlen(bug->ui);
char* uitext = nullptr;
if(len > 0)
{
uitext = new char[2*len+1];
memset(uitext, 0, 2*len+1);
DoEscapeString(uitext, bug->ui, len);
len = strlen(bug_report->ui_path);
if (len) {
if (len > 127)
len = 127;
ui_ = new char[(2 * len + 1)];
memset(ui_, 0, (2 * len + 1));
DoEscapeString(ui_, bug_report->ui_path, len);
}
len = strlen(bug->target_name);
char* targettext = nullptr;
if(len > 0)
{
targettext = new char[2*len+1];
memset(targettext, 0, 2*len+1);
DoEscapeString(targettext, bug->target_name, len);
len = strlen(bug_report->category_name);
if (len) {
if (len > 63)
len = 63;
type_ = new char[(2 * len + 1)];
memset(type_, 0, (2 * len + 1));
DoEscapeString(type_, bug_report->category_name, len);
}
//x and y are intentionally swapped because eq is inversexy coords
std::string query = StringFormat("INSERT INTO bugs (zone, name, ui, x, y, z, type, flag, target, bug, date) "
len = strlen(bug_report->target_name);
if (len) {
if (len > 63)
len = 63;
target_ = new char[(2 * len + 1)];
memset(target_, 0, (2 * len + 1));
DoEscapeString(target_, bug_report->target_name, len);
}
len = strlen(bug_report->bug_report);
if (len) {
if (len > 1023)
len = 1023;
bug_ = new char[(2 * len + 1)];
memset(bug_, 0, (2 * len + 1));
DoEscapeString(bug_, bug_report->bug_report, len);
}
//x and y are intentionally swapped because eq is inversexy coords //is this msg out-of-date or are the parameters wrong?
std::string query = StringFormat(
"INSERT INTO `bugs` (`zone`, `name`, `ui`, `x`, `y`, `z`, `type`, `flag`, `target`, `bug`, `date`) "
"VALUES('%s', '%s', '%s', '%.2f', '%.2f', '%.2f', '%s', %d, '%s', '%s', CURDATE())",
zone->GetShortName(), bug->name, uitext == nullptr ? "": uitext,
bug->x, bug->y, bug->z, bug->chartype, bug->type, targettext == nullptr? "Unknown Target": targettext,
bugtext==nullptr?"":bugtext);
safe_delete_array(bugtext);
safe_delete_array(uitext);
safe_delete_array(targettext);
zone->GetShortName(),
(name_ ? name_ : ""),
(ui_ ? ui_ : ""),
bug_report->pos_x,
bug_report->pos_y,
bug_report->pos_z,
(type_ ? type_ : ""),
bug_report->optional_info_mask,
(target_ ? target_ : "Unknown Target"),
(bug_ ? bug_ : "")
);
safe_delete_array(name_);
safe_delete_array(ui_);
safe_delete_array(type_);
safe_delete_array(target_);
safe_delete_array(bug_);
QueryDatabase(query);
}
void ZoneDatabase::UpdateBug(PetitionBug_Struct* bug){
void ZoneDatabase::RegisterBug(Client* client, BugReport_Struct* bug_report) {
if (!client || !bug_report)
return;
uint32 len = strlen(bug->text);
auto bugtext = new char[2 * len + 1];
memset(bugtext, 0, 2*len+1);
DoEscapeString(bugtext, bug->text, len);
size_t len = 0;
char* category_name_ = nullptr;
char* reporter_name_ = nullptr;
char* ui_path_ = nullptr;
char* target_name_ = nullptr;
char* bug_report_ = nullptr;
char* system_info_ = nullptr;
std::string query = StringFormat("INSERT INTO bugs (type, name, bugtext, flag) "
"VALUES('%s', '%s', '%s', %i)",
"Petition", bug->name, bugtext, 25);
safe_delete_array(bugtext);
QueryDatabase(query);
len = strlen(bug_report->category_name);
if (len) {
if (len > 63) // check against db column size
len = 63;
category_name_ = new char[(2 * len + 1)];
memset(category_name_, 0, (2 * len + 1));
DoEscapeString(category_name_, bug_report->category_name, len);
}
len = strlen(bug_report->reporter_name);
if (len) {
if (len > 63)
len = 63;
reporter_name_ = new char[(2 * len + 1)];
memset(reporter_name_, 0, (2 * len + 1));
DoEscapeString(reporter_name_, bug_report->reporter_name, len);
}
len = strlen(bug_report->ui_path);
if (len) {
if (len > 127)
len = 127;
ui_path_ = new char[(2 * len + 1)];
memset(ui_path_, 0, (2 * len + 1));
DoEscapeString(ui_path_, bug_report->ui_path, len);
}
len = strlen(bug_report->target_name);
if (len) {
if (len > 63)
len = 63;
target_name_ = new char[(2 * len + 1)];
memset(target_name_, 0, (2 * len + 1));
DoEscapeString(target_name_, bug_report->target_name, len);
}
len = strlen(bug_report->bug_report);
if (len) {
if (len > 1023)
len = 1023;
bug_report_ = new char[(2 * len + 1)];
memset(bug_report_, 0, (2 * len + 1));
DoEscapeString(bug_report_, bug_report->bug_report, len);
}
len = strlen(bug_report->system_info);
if (len) {
if (len > 1023)
len = 1023;
system_info_ = new char[(2 * len + 1)];
memset(system_info_, 0, (2 * len + 1));
DoEscapeString(system_info_, bug_report->system_info, len);
}
std::string query = StringFormat(
"INSERT INTO `bug_reports` "
"(`zone`,"
" `client_version_id`,"
" `client_version_name`,"
" `account_id`,"
" `character_id`,"
" `character_name`,"
" `reporter_spoof`,"
" `category_id`,"
" `category_name`,"
" `reporter_name`,"
" `ui_path`,"
" `pos_x`,"
" `pos_y`,"
" `pos_z`,"
" `heading`,"
" `time_played`,"
" `target_id`,"
" `target_name`,"
" `optional_info_mask`,"
" `_can_duplicate`,"
" `_crash_bug`,"
" `_target_info`,"
" `_character_flags`,"
" `_unknown_value`,"
" `bug_report`,"
" `system_info`) "
"VALUES "
"('%s',"
" '%u',"
" '%s',"
" '%u',"
" '%u',"
" '%s',"
" '%u',"
" '%u',"
" '%s',"
" '%s',"
" '%s',"
" '%1.1f',"
" '%1.1f',"
" '%1.1f',"
" '%u',"
" '%u',"
" '%u',"
" '%s',"
" '%u',"
" '%u',"
" '%u',"
" '%u',"
" '%u',"
" '%u',"
" '%s',"
" '%s')",
zone->GetShortName(),
client->ClientVersion(),
EQEmu::versions::ClientVersionName(client->ClientVersion()),
client->AccountID(),
client->CharacterID(),
client->GetName(),
(strcmp(client->GetName(), reporter_name_) != 0 ? 1 : 0),
bug_report->category_id,
(category_name_ ? category_name_ : ""),
(reporter_name_ ? reporter_name_ : ""),
(ui_path_ ? ui_path_ : ""),
bug_report->pos_x,
bug_report->pos_y,
bug_report->pos_z,
bug_report->heading,
bug_report->time_played,
bug_report->target_id,
(target_name_ ? target_name_ : ""),
bug_report->optional_info_mask,
((bug_report->optional_info_mask & EQEmu::bug::infoCanDuplicate) != 0 ? 1 : 0),
((bug_report->optional_info_mask & EQEmu::bug::infoCrashBug) != 0 ? 1 : 0),
((bug_report->optional_info_mask & EQEmu::bug::infoTargetInfo) != 0 ? 1 : 0),
((bug_report->optional_info_mask & EQEmu::bug::infoCharacterFlags) != 0 ? 1 : 0),
((bug_report->optional_info_mask & EQEmu::bug::infoUnknownValue) != 0 ? 1 : 0),
(bug_report_ ? bug_report_ : ""),
(system_info_ ? system_info_ : "")
);
safe_delete_array(category_name_);
safe_delete_array(reporter_name_);
safe_delete_array(ui_path_);
safe_delete_array(target_name_);
safe_delete_array(bug_report_);
safe_delete_array(system_info_);
auto result = QueryDatabase(query);
// TODO: Entity dumping [RuleB(Bugs, DumpTargetEntity)]
}
//void ZoneDatabase::UpdateBug(PetitionBug_Struct* bug) {
//
// uint32 len = strlen(bug->text);
// auto bugtext = new char[2 * len + 1];
// memset(bugtext, 0, 2 * len + 1);
// DoEscapeString(bugtext, bug->text, len);
//
// std::string query = StringFormat("INSERT INTO bugs (type, name, bugtext, flag) "
// "VALUES('%s', '%s', '%s', %i)",
// "Petition", bug->name, bugtext, 25);
// safe_delete_array(bugtext);
// QueryDatabase(query);
//}
bool ZoneDatabase::SetSpecialAttkFlag(uint8 id, const char* flag) {
std::string query = StringFormat("UPDATE npc_types SET npcspecialattks='%s' WHERE id = %i;", flag, id);
@@ -1433,6 +1618,13 @@ bool ZoneDatabase::SaveCharacterInventorySnapshot(uint32 character_id){
}
bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, PlayerProfile_Struct* pp, ExtendedProfile_Struct* m_epp){
/* If this is ever zero - the client hasn't fully loaded and potentially crashed during zone */
if (account_id <= 0)
return false;
std::string mail_key = database.GetMailKey(character_id);
clock_t t = std::clock(); /* Function timer start */
std::string query = StringFormat(
"REPLACE INTO `character_data` ("
@@ -1529,7 +1721,8 @@ bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, Pla
" e_aa_effects, "
" e_percent_to_aa, "
" e_expended_aa_spent, "
" e_last_invsnapshot "
" e_last_invsnapshot, "
" mailkey "
") "
"VALUES ("
"%u," // id " id, "
@@ -1625,7 +1818,8 @@ bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, Pla
"%u," // e_aa_effects
"%u," // e_percent_to_aa
"%u," // e_expended_aa_spent
"%u" // e_last_invsnapshot
"%u," // e_last_invsnapshot
"'%s'" // mailkey mail_key
")",
character_id, // " id, "
account_id, // " account_id, "
@@ -1720,7 +1914,8 @@ bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, Pla
m_epp->aa_effects,
m_epp->perAA,
m_epp->expended_aa,
m_epp->last_invsnapshot_time
m_epp->last_invsnapshot_time,
mail_key.c_str()
);
auto results = database.QueryDatabase(query);
Log(Logs::General, Logs::None, "ZoneDatabase::SaveCharacterData %i, done... Took %f seconds", character_id, ((float)(std::clock() - t)) / CLOCKS_PER_SEC);
@@ -1970,7 +2165,16 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
"npc_types.feettexture, "
"npc_types.ignore_despawn, "
"npc_types.show_name, "
"npc_types.untargetable "
"npc_types.untargetable, "
"npc_types.charm_ac, "
"npc_types.charm_min_dmg, "
"npc_types.charm_max_dmg, "
"npc_types.charm_attack_delay, "
"npc_types.charm_accuracy_rating, "
"npc_types.charm_avoidance_rating, "
"npc_types.charm_atk, "
"npc_types.skip_global_loot, "
"npc_types.rare_spawn "
"FROM npc_types %s",
where_condition.c_str()
);
@@ -2149,6 +2353,17 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
temp_npctype_data->show_name = atoi(row[98]) != 0 ? true : false;
temp_npctype_data->untargetable = atoi(row[99]) != 0 ? true : false;
temp_npctype_data->charm_ac = atoi(row[100]);
temp_npctype_data->charm_min_dmg = atoi(row[101]);
temp_npctype_data->charm_max_dmg = atoi(row[102]);
temp_npctype_data->charm_attack_delay = atoi(row[103]) * 100; // TODO: fix DB
temp_npctype_data->charm_accuracy_rating = atoi(row[104]);
temp_npctype_data->charm_avoidance_rating = atoi(row[105]);
temp_npctype_data->charm_atk = atoi(row[106]);
temp_npctype_data->skip_global_loot = atoi(row[107]) != 0;
temp_npctype_data->rare_spawn = atoi(row[108]) != 0;
// If NPC with duplicate NPC id already in table,
// free item we attempted to add.
if (zone->npctable.find(temp_npctype_data->npc_id) != zone->npctable.end()) {
@@ -2953,9 +3168,11 @@ uint32 ZoneDatabase::GetKarma(uint32 acct_id)
if (!results.Success())
return 0;
auto row = results.begin();
for (auto row = results.begin(); row != results.end(); ++row) {
return atoi(row[0]);
}
return atoi(row[0]);
return 0;
}
void ZoneDatabase::UpdateKarma(uint32 acct_id, uint32 amount)
+16 -10
View File
@@ -1,6 +1,8 @@
#ifndef ZONEDB_H_
#define ZONEDB_H_
#include <unordered_set>
#include "../common/shareddb.h"
#include "../common/eq_packet_structs.h"
#include "position.h"
@@ -16,6 +18,7 @@ class NPC;
class Petition;
class Spawn2;
class SpawnGroupList;
class Trap;
struct CharacterEventLog_Struct;
struct Door;
struct ExtendedProfile_Struct;
@@ -45,13 +48,15 @@ struct wplist {
#pragma pack(1)
struct DBnpcspells_entries_Struct {
int16 spellid;
uint32 type;
uint8 minlevel;
uint8 maxlevel;
uint32 type;
int16 manacost;
int32 recast_delay;
int16 priority;
int32 recast_delay;
int16 resist_adjust;
int8 min_hp;
int8 max_hp;
};
#pragma pack()
@@ -74,7 +79,6 @@ struct DBnpcspells_Struct {
int16 rproc_chance;
uint16 defensive_proc;
int16 dproc_chance;
uint32 numentries;
uint32 fail_recast;
uint32 engaged_no_sp_recast_min;
uint32 engaged_no_sp_recast_max;
@@ -87,7 +91,7 @@ struct DBnpcspells_Struct {
uint32 idle_no_sp_recast_min;
uint32 idle_no_sp_recast_max;
uint8 idle_beneficial_chance;
DBnpcspells_entries_Struct entries[0];
std::vector<DBnpcspells_entries_Struct> entries;
};
struct DBnpcspellseffects_Struct {
@@ -420,9 +424,11 @@ public:
uint32 GetMaxNPCSpellsID();
uint32 GetMaxNPCSpellsEffectsID();
bool GetAuraEntry(uint16 spell_id, AuraRecord &record);
void LoadGlobalLoot();
DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID);
DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID);
void ClearNPCSpells() { npc_spells_cache.clear(); npc_spells_loadtried.clear(); }
const NPCType* LoadNPCTypesData(uint32 id, bool bulk_load = false);
/* Mercs */
@@ -436,8 +442,9 @@ public:
bool DeleteMerc(uint32 merc_id);
/* Petitions */
void UpdateBug(BugStruct* bug);
void UpdateBug(PetitionBug_Struct* bug);
void RegisterBug(BugReport_Struct* bug_report); // old method
void RegisterBug(Client* client, BugReport_Struct* bug_report); // new method
//void UpdateBug(PetitionBug_Struct* bug);
void DeletePetitionFromDB(Petition* wpet);
void UpdatePetitionToDB(Petition* wpet);
void InsertPetitionToDB(Petition* wpet);
@@ -478,7 +485,7 @@ public:
/* Traps */
bool LoadTraps(const char* zonename, int16 version);
char* GetTrapMessage(uint32 trap_id);
bool SetTrapData(Trap* trap, bool repopnow = false);
/* Time */
uint32 GetZoneTZ(uint32 zoneid, uint32 version);
@@ -523,10 +530,9 @@ protected:
uint32 max_faction;
Faction** faction_array;
uint32 npc_spells_maxid;
uint32 npc_spellseffects_maxid;
DBnpcspells_Struct** npc_spells_cache;
bool* npc_spells_loadtried;
std::unordered_map<uint32, DBnpcspells_Struct> npc_spells_cache;
std::unordered_set<uint32> npc_spells_loadtried;
DBnpcspellseffects_Struct** npc_spellseffects_cache;
bool* npc_spellseffects_loadtried;
uint8 door_isopen_array[255];
+9 -1
View File
@@ -89,6 +89,13 @@ struct NPCType
EQEmu::TintProfile armor_tint;
uint32 min_dmg;
uint32 max_dmg;
uint32 charm_ac;
uint32 charm_min_dmg;
uint32 charm_max_dmg;
int charm_attack_delay;
int charm_accuracy_rating;
int charm_avoidance_rating;
int charm_atk;
int16 attack_count;
char special_abilities[512];
uint16 d_melee_texture1;
@@ -126,7 +133,6 @@ struct NPCType
float healscale;
bool no_target_hotkey;
bool raid_target;
uint8 probability;
uint8 armtexture;
uint8 bracertexture;
uint8 handtexture;
@@ -135,6 +141,8 @@ struct NPCType
bool ignore_despawn;
bool show_name; // should default on
bool untargetable;
bool skip_global_loot;
bool rare_spawn;
};
namespace player_lootitem {
+5 -1
View File
@@ -42,7 +42,7 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) {
Bot::ProcessClientZoneChange(this);
#endif
zoning = true;
bZoning = true;
if (app->size != sizeof(ZoneChange_Struct)) {
Log(Logs::General, Logs::None, "Wrong size: OP_ZoneChange, size=%d, expected %d", app->size, sizeof(ZoneChange_Struct));
return;
@@ -308,6 +308,8 @@ void Client::SendZoneCancel(ZoneChange_Struct *zc) {
//reset to unsolicited.
zone_mode = ZoneUnsolicited;
// reset since we're not zoning anymore
bZoning = false;
}
void Client::SendZoneError(ZoneChange_Struct *zc, int8 err)
@@ -327,6 +329,8 @@ void Client::SendZoneError(ZoneChange_Struct *zc, int8 err)
//reset to unsolicited.
zone_mode = ZoneUnsolicited;
// reset since we're not zoning anymore
bZoning = false;
}
void Client::DoZoneSuccess(ZoneChange_Struct *zc, uint16 zone_id, uint32 instance_id, float dest_x, float dest_y, float dest_z, float dest_h, int8 ignore_r) {