Merge fixes

This commit is contained in:
Akkadius
2018-01-13 16:41:10 -06:00
92 changed files with 2787 additions and 1448 deletions
+5
View File
@@ -1274,6 +1274,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) {
/*
+91 -19
View File
@@ -852,16 +852,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;
}
@@ -1262,6 +1302,7 @@ int Client::DoDamageCaps(int base_damage)
return std::min(cap, base_damage);
}
// other is the defender, this is the attacker
void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts)
{
if (!other)
@@ -1288,6 +1329,20 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts)
if (hit.damage_done >= 0) {
if (other->CheckHitChance(this, hit)) {
if (IsNPC() && other->IsClient() && other->animation > 0 && GetLevel() >= 5 && BehindMob(other, GetX(), GetY())) {
// ~ 12% chance
if (zone->random.Roll(12)) {
int stun_resist2 = other->spellbonuses.FrontalStunResist + other->itembonuses.FrontalStunResist + other->aabonuses.FrontalStunResist;
int stun_resist = other->spellbonuses.StunResist + other->itembonuses.StunResist + other->aabonuses.StunResist;
if (zone->random.Roll(stun_resist2)) {
other->Message_StringID(MT_Stun, AVOID_STUNNING_BLOW);
} else if (zone->random.Roll(stun_resist)) {
other->Message_StringID(MT_Stun, SHAKE_OFF_STUN);
} else {
other->Stun(3000); // yuck -- 3 seconds
}
}
}
other->MeleeMitigation(this, hit, opts);
if (hit.damage_done > 0) {
ApplyDamageTable(hit);
@@ -1673,6 +1728,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())
{
@@ -2518,6 +2582,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;
}
@@ -2617,7 +2684,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
@@ -2658,9 +2725,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);
}
}
}
@@ -3349,7 +3416,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());
@@ -3907,10 +3974,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)
@@ -3947,7 +4014,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)
@@ -5232,19 +5299,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))
@@ -5255,12 +5333,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()
+27 -2
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;
@@ -1003,6 +1010,15 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
case SE_RiposteChance:
newbon->RiposteChance += base1;
break;
case SE_DodgeChance:
newbon->DodgeChance += base1;
break;
case SE_ParryChance:
newbon->ParryChance += base1;
break;
case SE_IncreaseBlockChance:
newbon->IncreaseBlockChance += base1;
break;
case SE_Flurry:
newbon->FlurryChance += base1;
break;
@@ -2494,8 +2510,17 @@ 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:
new_bonus->IncreaseBlockChance += effect_value;
if (AdditiveWornBonus)
new_bonus->IncreaseBlockChance += effect_value;
else if (effect_value < 0 && new_bonus->IncreaseBlockChance > effect_value)
new_bonus->IncreaseBlockChance = effect_value;
else if (new_bonus->IncreaseBlockChance < effect_value)
new_bonus->IncreaseBlockChance = effect_value;
break;
case SE_PersistantCasting:
+6 -5
View File
@@ -2841,6 +2841,8 @@ void Bot::Spawn(Client* botCharacterOwner) {
FaceTarget(botCharacterOwner);
UpdateEquipmentLight();
UpdateActiveLight();
this->m_targetable = true;
entity_list.AddBot(this, true, true);
// Load pet
LoadPet();
@@ -6725,7 +6727,7 @@ int32 Bot::CalcATK() {
}
void Bot::CalcRestState() {
if(!RuleI(Character, RestRegenPercent))
if(!RuleB(Character, RestRegenEnabled))
return;
RestRegenHP = RestRegenMana = RestRegenEndurance = 0;
@@ -6741,10 +6743,9 @@ void Bot::CalcRestState() {
}
}
RestRegenHP = (GetMaxHP() * RuleI(Character, RestRegenPercent) / 100);
RestRegenMana = (GetMaxMana() * RuleI(Character, RestRegenPercent) / 100);
if(RuleB(Character, RestRegenEndurance))
RestRegenEndurance = (GetMaxEndurance() * RuleI(Character, RestRegenPercent) / 100);
RestRegenHP = 6 * (GetMaxHP() / RuleI(Character, RestRegenHP));
RestRegenMana = 6 * (GetMaxMana() / RuleI(Character, RestRegenMana));
RestRegenEndurance = 6 * (GetMaxEndurance() / RuleI(Character, RestRegenEnd));
}
int32 Bot::LevelRegen() {
+257 -54
View File
@@ -123,7 +123,7 @@ Client::Client(EQStreamInterface* ieqs)
hpupdate_timer(2000),
camp_timer(29000),
process_timer(100),
stamina_timer(40000),
consume_food_timer(CONSUMPTION_TIMER),
zoneinpacket_timer(1000),
linkdead_timer(RuleI(Zone,ClientLinkdeadMS)),
dead_timer(2000),
@@ -160,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++)
@@ -270,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;
@@ -329,6 +331,11 @@ Client::Client(EQStreamInterface* ieqs)
interrogateinv_flag = false;
trapid = 0;
for (int i = 0; i < InnateSkillMax; ++i)
m_pp.InnateSkills[i] = InnateDisabled;
AI_Init();
}
@@ -658,13 +665,18 @@ bool Client::Save(uint8 iCommitNow) {
m_pp.tribute_time_remaining = 0xFFFFFFFF; m_pp.tribute_active = 0;
}
if (m_pp.hunger_level < 0)
m_pp.hunger_level = 0;
if (m_pp.thirst_level < 0)
m_pp.thirst_level = 0;
p_timers.Store(&database);
database.SaveCharacterTribute(this->CharacterID(), &m_pp);
SaveTaskState(); /* Save Character Task */
m_pp.hunger_level = EQEmu::Clamp(m_pp.hunger_level, 0, 50000);
m_pp.thirst_level = EQEmu::Clamp(m_pp.thirst_level, 0, 50000);
Log(Logs::General, Logs::Food, "Client::Save - hunger_level: %i thirst_level: %i", m_pp.hunger_level, m_pp.thirst_level);
// perform snapshot before SaveCharacterData() so that m_epp will contain the updated time
if (RuleB(Character, ActiveInvSnapshots) && time(nullptr) >= GetNextInvSnapshotTime()) {
@@ -1211,18 +1223,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);
}
}
}
@@ -4588,7 +4600,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,
@@ -4625,7 +4637,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.
@@ -6712,7 +6724,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: {
@@ -6725,7 +6737,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;
@@ -6739,7 +6751,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; }
@@ -8585,51 +8597,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;
uint32 cons_mod = 180;
int increase = item->CastTime_ * 100;
if (!auto_consume) // force feeding is half as effective
increase /= 2;
int32 metabolism_bonus = spellbonuses.Metabolism + itembonuses.Metabolism + aabonuses.Metabolism;
if (increase < 0) // wasn't food? oh well
return;
if (metabolism_bonus)
cons_mod = cons_mod * metabolism_bonus * RuleI(Character, ConsumptionMultiplier) / 10000;
else
cons_mod = cons_mod * RuleI(Character, ConsumptionMultiplier) / 100;
if (type == EQEmu::item::ItemTypeFood) {
increase = mod_food_value(item, increase);
if (type == EQEmu::item::ItemTypeFood)
{
int hchange = item->CastTime_ * cons_mod;
hchange = mod_food_value(item, hchange);
if (increase < 0)
return;
if(hchange < 0) { return; }
m_pp.hunger_level += increase;
m_pp.hunger_level += hchange;
DeleteItemInInventory(slot, 1, false);
Log(Logs::General, Logs::Food, "Consuming food, points added to hunger_level: %i - current_hunger: %i",
increase, m_pp.hunger_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 EQDEBUG >= 5
Log(Logs::General, Logs::None, "Eating from slot:%i", (int)slot);
#endif
}
else
{
int tchange = item->CastTime_ * cons_mod;
tchange = mod_drink_value(item, tchange);
if(tchange < 0) { return; }
m_pp.thirst_level += tchange;
DeleteItemInInventory(slot, 1, false);
if(!auto_consume) //no message if the client consumed for us
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 {
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);
#if EQDEBUG >= 5
Log(Logs::General, Logs::None, "Drinking from slot:%i", (int)slot);
#endif
}
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)
@@ -9055,3 +9068,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;
}
}
+51 -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;
@@ -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();
@@ -719,6 +750,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 +792,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 +897,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 +1302,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,13 +1383,13 @@ 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();
void DoManaRegen();
void DoStaminaUpdate();
void DoStaminaHungerUpdate();
void CalcRestState();
uint32 pLastUpdate;
@@ -1410,6 +1448,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;
@@ -1458,7 +1497,7 @@ private:
Timer hpupdate_timer;
Timer camp_timer;
Timer process_timer;
Timer stamina_timer;
Timer consume_food_timer;
Timer zoneinpacket_timer;
Timer linkdead_timer;
Timer dead_timer;
@@ -1489,7 +1528,9 @@ private:
Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */
Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */
Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */
glm::vec3 m_Proximity;
glm::vec3 m_Proximity;
glm::vec4 last_major_update_position;
void BulkSendInventoryItems();
@@ -1511,9 +1552,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);
}
+614 -558
View File
File diff suppressed because it is too large Load Diff
+1
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);
+68 -35
View File
@@ -38,6 +38,7 @@
#include <unistd.h>
#endif
#include "../common/data_verification.h"
#include "../common/rulesys.h"
#include "../common/skills.h"
#include "../common/spdat.h"
@@ -243,24 +244,42 @@ bool Client::Process() {
/* Build a close range list of NPC's */
if (npc_close_scan_timer.Check()) {
close_mobs.clear();
auto &mob_list = entity_list.GetMobList();
float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan));
float client_update_range = (RuleI(Range, MobPositionUpdates) * RuleI(Range, MobPositionUpdates));
/* Force spawn updates when traveled far */
bool force_spawn_updates = false;
float client_update_range = (RuleI(Range, ClientForceSpawnUpdateRange) * RuleI(Range, ClientForceSpawnUpdateRange));
if (DistanceSquared(last_major_update_position, m_Position) >= client_update_range) {
last_major_update_position = m_Position;
force_spawn_updates = true;
}
float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan));
auto &mob_list = entity_list.GetMobList();
for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) {
Mob* mob = itr->second;
float distance = DistanceSquared(m_Position, mob->GetPosition());
if (mob->IsNPC()) {
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) {
if (mob->is_distance_roamer) {
mob->SendPositionUpdateToClient(this);
continue;
}
if (distance <= client_update_range)
mob->SendPositionUpdateToClient(this);
}
}
}
@@ -513,6 +532,10 @@ bool Client::Process() {
DoEnduranceUpkeep();
}
// this is independent of the tick timer
if (consume_food_timer.Check())
DoStaminaHungerUpdate();
if (tic_timer.Check() && !dead) {
CalcMaxHP();
CalcMaxMana();
@@ -523,7 +546,6 @@ bool Client::Process() {
DoManaRegen();
DoEnduranceRegen();
BuffProcess();
DoStaminaUpdate();
if (tribute_timer.Check()) {
ToggleTribute(true); //re-activate the tribute.
@@ -1041,7 +1063,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
}
@@ -1806,7 +1829,7 @@ void Client::OPGMSummon(const EQApplicationPacket *app)
}
void Client::DoHPRegen() {
SetHP(GetHP() + CalcHPRegen() + RestRegenHP);
SetHP(GetHP() + CalcHPRegen());
SendHPUpdate();
}
@@ -1814,41 +1837,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::DoStaminaUpdate() {
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;
if(zone->GetZoneID() != 151) {
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 && !GetGM()) {
int loss = RuleI(Character, FoodLossPerUpdate);
if (m_pp.hunger_level > 0)
m_pp.hunger_level-=loss;
if (m_pp.thirst_level > 0)
m_pp.thirst_level-=loss;
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 (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);
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() {
@@ -1897,12 +1934,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))
@@ -1917,12 +1954,8 @@ 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()
+34 -1
View File
@@ -321,6 +321,7 @@ int command_init(void)
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) ||
@@ -375,6 +376,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) ||
@@ -382,6 +384,7 @@ 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("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) ||
command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) ||
@@ -4157,6 +4160,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)
@@ -7257,7 +7280,7 @@ void command_bestz(Client *c, const Seperator *sep) {
float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999)
if (best_z != BEST_Z_INVALID)
{
c->Message(0, "Z is %.3f at (%.3f, %.3f).", best_z, me.x, me.y);
}
@@ -10851,6 +10874,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
+3
View File
@@ -228,6 +228,7 @@ 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);
@@ -286,6 +287,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,6 +297,7 @@ 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_undye(Client *c, const Seperator *sep);
void command_undyeme(Client *c, const Seperator *sep);
+6
View File
@@ -550,6 +550,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 +604,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
+5 -1
View File
@@ -611,9 +611,13 @@ 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)) {
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)
/*char val1[20]={0};*/ //unused
/*char val2[20]={0};*/ //unused
uint32 remain = p_timers.GetRemainingTime(DiscTimer);
+6 -4
View File
@@ -119,7 +119,7 @@ void Embperl::DoInit() {
perl_run(my_perl);
//a little routine we use a lot.
eval_pv("sub my_eval {eval $_[0];}", TRUE); //dies on error
eval_pv("sub my_eval { eval $_[0];}", TRUE); //dies on error
//ruin the perl exit and command:
eval_pv("sub my_exit {}",TRUE);
@@ -149,7 +149,7 @@ void Embperl::DoInit() {
//make a tieable class to capture IO and pass it into EQEMuLog
eval_pv(
"package EQEmuIO; "
"sub TIEHANDLE { my $me = bless {}, $_[0]; $me->PRINT('Creating '.$me); return($me); } "
"sub TIEHANDLE { my $me = bless {}, $_[0]; $me->PRINT('Creating '. $me); return($me); } "
"sub WRITE { } "
//dunno why I need to shift off fmt here, but it dosent like without it
"sub PRINTF { my $me = shift; my $fmt = shift; $me->PRINT(sprintf($fmt, @_)); } "
@@ -237,6 +237,7 @@ void Embperl::init_eval_file(void)
{
eval_pv(
"our %Cache;"
"no warnings;"
"use Symbol qw(delete_package);"
"sub eval_file {"
"my($package, $filename) = @_;"
@@ -246,8 +247,9 @@ void Embperl::init_eval_file(void)
"if(defined $Cache{$package}{mtime}&&$Cache{$package}{mtime} <= $mtime && !($package eq 'plugin')){"
" return;"
"} else {"
//we 'my' $filename,$mtime,$package,$sub to prevent them from changing our state up here.
" eval(\"package $package; my(\\$filename,\\$mtime,\\$package,\\$sub); \\$isloaded = 1; require '$filename'; \");"
// we 'my' $filename,$mtime,$package,$sub to prevent them from changing our state up here.
" eval(\"package $package; my(\\$filename,\\$mtime,\\$package,\\$sub); \\$isloaded = 1; require './$filename'; \");"
// " print $@ if $@;"
/* "local *FH;open FH, $filename or die \"open '$filename' $!\";"
"local($/) = undef;my $sub = <FH>;close FH;"
"my $eval = qq{package $package; sub handler { $sub; }};"
+4
View File
@@ -99,6 +99,10 @@ XS(XS_EQEmuIO_PRINT)
/* Strip newlines from log message 'str' */
*std::remove(str, str + strlen(str), '\n') = '\0';
std::string log_string = str;
if (log_string.find("did not return a true") != std::string::npos)
return;;
int i;
int pos = 0;
int len = 0;
+16 -11
View File
@@ -648,7 +648,7 @@ void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue)
parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0);
npc->FixZ();
npc->FixZ(1);
uint16 emoteid = npc->GetEmoteID();
if (emoteid != 0)
@@ -1419,10 +1419,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 +1461,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 +2617,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;
}
@@ -3079,7 +3080,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;
}
}
@@ -3088,7 +3092,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;
@@ -3252,7 +3257,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
+5 -1
View File
@@ -365,7 +365,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);
@@ -473,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);
+7 -2
View File
@@ -327,8 +327,7 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) {
aatotalmod *= zone->newzone_data.zone_exp_multiplier;
}
// Shouldn't race not affect AA XP?
if(RuleB(Character,UseRaceClassExpBonuses))
{
if(GetBaseRace() == HALFLING){
@@ -340,6 +339,12 @@ void Client::AddEXP(uint32 in_add_exp, uint8 conlevel, bool resexp) {
}
}
// 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_exp *= zone->level_exp_mod[GetLevel()].ExpMod;
+1 -1
View File
@@ -163,7 +163,7 @@ void Mob::CalculateNewFearpoint()
ranx = GetX()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1);
rany = GetY()+zone->random.Int(0, ran-1)-zone->random.Int(0, ran-1);
ranz = FindGroundZ(ranx,rany);
if (ranz == -999999)
if (ranz == BEST_Z_INVALID)
continue;
float fdist = ranz - GetZ();
if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(),GetY(),GetZ(),ranx,rany,ranz))
+30
View File
@@ -2464,3 +2464,33 @@ bool Group::HasRole(Mob *m, uint8 Role)
return false;
}
void Group::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/) {
if (sender && sender->IsClient()) {
for (uint32 i = 0; i < MAX_GROUP_MEMBERS; i++) {
if (!members[i])
continue;
if (!members[i]->IsClient())
continue;
if (ignore_sender && members[i] == sender)
continue;
/* If we don't have a distance requirement - send to all members */
if (distance == 0) {
members[i]->CastToClient()->QueuePacket(app, ack_required);
}
else {
/* If negative distance - we check if current distance is greater than X */
if (distance <= 0 && DistanceSquared(sender->GetPosition(), members[i]->GetPosition()) >= (distance * distance)) {
members[i]->CastToClient()->QueuePacket(app, ack_required);
}
/* If positive distance - we check if current distance is less than X */
else if (distance >= 0 && DistanceSquared(sender->GetPosition(), members[i]->GetPosition()) <= (distance * distance)) {
members[i]->CastToClient()->QueuePacket(app, ack_required);
}
}
}
}
}
+1
View File
@@ -140,6 +140,7 @@ public:
inline int GetLeadershipAA(int AAID) { return LeaderAbilities.ranks[AAID]; }
void ClearAllNPCMarks();
void QueueHPPacketsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app);
void QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required = true, bool ignore_sender = true, float distance = 0);
void ChangeLeader(Mob* newleader);
const char *GetClientNameByIndex(uint8 index);
void UpdateXTargetMarkedNPC(uint32 Number, Mob *m);
+1
View File
@@ -151,6 +151,7 @@ void Client::SummonHorse(uint16 spell_id) {
uint16 tmpID = horse->GetID();
SetHorseId(tmpID);
BuffFadeBySitModifier();
}
+2 -1
View File
@@ -336,7 +336,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);
+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
+6
View File
@@ -1686,6 +1686,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);
@@ -2367,6 +2372,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)
+1
View File
@@ -323,6 +323,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);
+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 {
+144 -88
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),
@@ -119,47 +118,46 @@ Mob::Mob(const char* in_name,
position_update_melee_push_timer(1000)
{
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 +170,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 +183,7 @@ Mob::Mob(const char* in_name,
current_speed = base_runspeed;
m_PlayerState = 0;
m_PlayerState = 0;
// sanity check
@@ -196,8 +195,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 +204,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 +229,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,8 +262,8 @@ 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;
InitializeBuffSlots();
@@ -305,7 +304,7 @@ Mob::Mob(const char* in_name,
logging_enabled = false;
isgrouped = false;
israidgrouped = false;
IsHorse = false;
entity_id_being_looted = 0;
@@ -376,13 +375,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;
@@ -396,7 +395,7 @@ Mob::Mob(const char* in_name,
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 +426,7 @@ Mob::Mob(const char* in_name,
nimbus_effect3 = 0;
m_targetable = true;
m_TargetRing = glm::vec3();
m_TargetRing = glm::vec3();
flymode = FlyMode3;
// Pathing
@@ -444,7 +443,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;
@@ -1383,7 +1382,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal
if(IsClient()){
Raid *raid = entity_list.GetRaidByClient(CastToClient());
if (raid)
raid->SendHPPacketsFrom(this);
raid->SendHPManaEndPacketsFrom(this);
}
/* Pet - Update master - group and raid if exists */
@@ -1396,7 +1395,7 @@ void Mob::SendHPUpdate(bool skip_self /*= false*/, bool force_update_all /*= fal
Raid *raid = entity_list.GetRaidByClient(GetOwner()->CastToClient());
if(raid)
raid->SendHPPacketsFrom(this);
raid->SendHPManaEndPacketsFrom(this);
}
/* Send to pet */
@@ -1447,6 +1446,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);
@@ -1455,6 +1455,20 @@ void Mob::SendPosition() {
safe_delete(app);
}
void Mob::SendPositionUpdateToClient(Client *client) {
auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct* spawn_update = (PlayerPositionUpdateServer_Struct*)app->pBuffer;
if(this->IsMoving())
MakeSpawnUpdate(spawn_update);
else
MakeSpawnUpdateNoDelta(spawn_update);
client->QueuePacket(app, false);
safe_delete(app);
}
/* Position updates for mobs on the move */
void Mob::SendPositionUpdate(uint8 iSendToSelf) {
auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
@@ -1466,6 +1480,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);
}
@@ -3383,13 +3402,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;
@@ -3425,9 +3449,22 @@ 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 = -999999;
float ret = BEST_Z_INVALID;
if (zone->zonemap != nullptr)
{
glm::vec3 me;
@@ -3436,7 +3473,7 @@ float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
me.z = m_Position.z + z_offset;
glm::vec3 hit;
float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999)
if (best_z != BEST_Z_INVALID)
{
ret = best_z;
}
@@ -3447,7 +3484,7 @@ float Mob::FindGroundZ(float new_x, float new_y, float z_offset)
// Copy of above function that isn't protected to be exported to Perl::Mob
float Mob::GetGroundZ(float new_x, float new_y, float z_offset)
{
float ret = -999999;
float ret = BEST_Z_INVALID;
if (zone->zonemap != 0)
{
glm::vec3 me;
@@ -3456,7 +3493,7 @@ float Mob::GetGroundZ(float new_x, float new_y, float z_offset)
me.z = m_Position.z+z_offset;
glm::vec3 hit;
float best_z = zone->zonemap->FindBestZ(me, &hit);
if (best_z != -999999)
if (best_z != BEST_Z_INVALID)
{
ret = best_z;
}
@@ -3761,7 +3798,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;
}
@@ -3769,12 +3806,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;
}
}
@@ -3935,10 +3972,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)
{
@@ -4980,6 +5024,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)
+16 -5
View File
@@ -162,6 +162,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
@@ -199,6 +201,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 +281,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 +340,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 +353,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 +382,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; }
@@ -543,6 +550,7 @@ public:
virtual void GMMove(float x, float y, float z, float heading = 0.01, bool SendUpdate = true);
void SetDelta(const glm::vec4& delta);
void SetTargetDestSteps(uint8 target_steps) { tar_ndx = target_steps; }
void SendPositionUpdateToClient(Client *client);
void SendPositionUpdate(uint8 iSendToSelf = 0);
void MakeSpawnUpdateNoDelta(PlayerPositionUpdateServer_Struct* spu);
void MakeSpawnUpdate(PlayerPositionUpdateServer_Struct* spu);
@@ -585,6 +593,7 @@ 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);
@@ -949,7 +958,9 @@ public:
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 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; }
@@ -1103,8 +1114,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()); }
@@ -1220,8 +1229,9 @@ 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;
uint16 animation;
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;
float runspeed;
@@ -1262,6 +1272,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);
void PrintRoute();
+46 -24
View File
@@ -57,7 +57,11 @@ 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));
if (!cast_only_option && iChance < 100) {
if (zone->random.Int(0, 100) >= iChance)
return false;
}
@@ -123,7 +127,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() && zone->random.Roll(50)
&& rootee->DontRootMeBefore() < Timer::GetCurrentTime()
&& rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
@@ -499,7 +503,7 @@ void NPC::AI_Start(uint32 iMoveDelay) {
AIautocastspell_timer->Disable();
} else {
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(750));
AIautocastspell_timer->Start(RandomTimer(0, 15000), false);
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
if (NPCTypedata) {
@@ -1001,10 +1005,9 @@ void Mob::AI_Process() {
if (this->GetTarget()) {
/* If we are engaged, moving and following client, let's look for best Z more often */
float target_distance = DistanceNoZ(this->GetPosition(), this->GetTarget()->GetPosition());
if (target_distance >= 25) {
this->FixZ();
}
else if (!this->CheckLosFN(this->GetTarget())) {
this->FixZ();
if (target_distance <= 15 && !this->CheckLosFN(this->GetTarget())) {
Mob* target = this->GetTarget();
m_Position.x = target->GetX();
@@ -1295,7 +1298,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());
@@ -1388,19 +1391,33 @@ 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)
speed = GetRunspeed();
CalculateNewPosition2(owner->GetX(), owner->GetY(), owner->GetZ(), speed);
if (distz > 100)
{
m_Position = ownerPos;
SendPositionUpdate();
moved = true;
}
else
{
CalculateNewPosition2(owner->GetX(),
owner->GetY(), owner->GetZ(), speed);
}
}
else
{
if(moved)
{
this->FixZ();
SetCurrentSpeed(0);
moved = false;
}
@@ -1532,16 +1549,17 @@ 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);
new_z += (this->GetSize() / 1.55);
if (!CalculateNewPosition2(roambox_movingto_x, roambox_movingto_y, new_z, walksp, true))
// Keep calling with updates, using wherever we are in Z.
if (!MakeNewPositionAndSendUpdate(roambox_movingto_x,
roambox_movingto_y, m_Position.z, walksp))
{
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);
@@ -1578,18 +1596,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();
@@ -2589,7 +2611,7 @@ void NPC::AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint32 iType,
// 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)
+4 -2
View File
@@ -542,8 +542,10 @@ int main(int argc, char** argv) {
process_timer.Stop();
process_timer.Start(1000, true);
uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion());
zone->StartShutdownTimer(shutdown_timer);
if (zone && zone->GetZoneID() && zone->GetInstanceVersion()) {
uint32 shutdown_timer = database.getZoneShutDownDelay(zone->GetZoneID(), zone->GetInstanceVersion());
zone->StartShutdownTimer(shutdown_timer);
}
}
else if (!previous_loaded && current_loaded) {
process_timer.Stop();
+20
View File
@@ -436,6 +436,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);
}
+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;
+9 -20
View File
@@ -475,22 +475,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 +655,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);
};
+45 -2
View File
@@ -149,7 +149,7 @@ void Raid::AddMember(Client *c, uint32 group, bool rleader, bool groupleader, bo
raid_update = c->GetRaid();
if (raid_update) {
raid_update->SendHPManaEndPacketsTo(c);
raid_update->SendHPPacketsFrom(c);
raid_update->SendHPManaEndPacketsFrom(c);
}
auto pack = new ServerPacket(ServerOP_RaidAdd, sizeof(ServerRaidGeneralAction_Struct));
@@ -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)
@@ -1591,7 +1595,7 @@ void Raid::SendHPManaEndPacketsTo(Client *client)
}
}
void Raid::SendHPPacketsFrom(Mob *mob)
void Raid::SendHPManaEndPacketsFrom(Mob *mob)
{
if(!mob)
return;
@@ -1779,3 +1783,42 @@ void Raid::SetDirtyAutoHaters()
}
void Raid::QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required /*= true*/, bool ignore_sender /*= true*/, float distance /*= 0*/, bool group_only /*= true*/) {
if (sender && sender->IsClient()) {
uint32 group_id = this->GetGroup(sender->CastToClient());
/* If this is a group only packet and we're not in a group -- return */
if (!group_id == 0xFFFFFFFF && group_only)
return;
for (uint32 i = 0; i < MAX_RAID_MEMBERS; i++) {
if (!members[i].member)
continue;
if (!members[i].member->IsClient())
continue;
if (ignore_sender && members[i].member == sender)
continue;
if (group_only && members[i].GroupNumber != group_id)
continue;
/* If we don't have a distance requirement - send to all members */
if (distance == 0) {
members[i].member->CastToClient()->QueuePacket(app, ack_required);
}
else {
/* If negative distance - we check if current distance is greater than X */
if (distance <= 0 && DistanceSquared(sender->GetPosition(), members[i].member->GetPosition()) >= (distance * distance)) {
members[i].member->CastToClient()->QueuePacket(app, ack_required);
}
/* If positive distance - we check if current distance is less than X */
else if (distance >= 0 && DistanceSquared(sender->GetPosition(), members[i].member->GetPosition()) <= (distance * distance)) {
members[i].member->CastToClient()->QueuePacket(app, ack_required);
}
}
}
}
}
+3 -1
View File
@@ -174,7 +174,7 @@ public:
void VerifyRaid();
void MemberZoned(Client *c);
void SendHPManaEndPacketsTo(Client *c);
void SendHPPacketsFrom(Mob *mob);
void SendHPManaEndPacketsFrom(Mob *mob);
void SendManaPacketFrom(Mob *mob);
void SendEndurancePacketFrom(Mob *mob);
void RaidSay(const char *msg, Client *c);
@@ -237,6 +237,8 @@ public:
void SetDirtyAutoHaters();
inline XTargetAutoHaters *GetXTargetAutoMgr() { return &m_autohatermgr; }
void QueueClients(Mob *sender, const EQApplicationPacket *app, bool ack_required = true, bool ignore_sender = true, float distance = 0, bool group_only = true);
RaidMember members[MAX_RAID_MEMBERS];
char leadername[64];
protected:
+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)
+20 -21
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);
@@ -277,8 +281,9 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
caster->SetMana(0);
} else if (spell_id == 2755 && caster) //Lifeburn
{
dmg = -1 * caster->GetHP(); // just your current HP or should it be Max HP?
dmg = caster->GetHP(); // just your current HP
caster->SetHP(dmg / 4); // 2003 patch notes say ~ 1/4 HP. Should this be 1/4 your current HP or do 3/4 max HP dmg? Can it kill you?
dmg = -dmg;
}
//do any AAs apply to these spells?
@@ -1791,8 +1796,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);
@@ -3655,16 +3664,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: {
+79 -75
View File
@@ -1427,8 +1427,10 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo
ZeroCastingVars();
// set the rapid recast timer for next time around
// Why do we have this? It mostly just causes issues when things are working correctly
// It also needs to be <users's ping to not cause issues
delaytimer = true;
spellend_timer.Start(400,true);
spellend_timer.Start(10, true);
Log(Logs::Detail, Logs::Spells, "Spell casting of %d is finished.", spell_id);
@@ -3188,6 +3190,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
@@ -3307,8 +3315,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;
@@ -4232,6 +4240,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)
@@ -4416,6 +4437,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
@@ -4499,68 +4550,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.
@@ -4569,7 +4568,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) {
@@ -4578,7 +4577,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)
@@ -4591,7 +4590,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;
}
@@ -4607,16 +4606,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)
@@ -4626,7 +4625,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);
}
@@ -4737,17 +4736,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;
}
@@ -4755,9 +4754,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;
}
}
@@ -5533,6 +5532,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);
}
@@ -5618,6 +5619,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)
{
@@ -5627,6 +5629,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;
}
}
+5
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,6 +116,8 @@
#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 CONSENT_DENIED 390 //You do not have consent to summon that corpse.
@@ -421,6 +425,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.
+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++) {
+264 -26
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())
@@ -201,12 +221,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 +264,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 +431,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 +472,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:
+159 -34
View File
@@ -176,9 +176,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 +195,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())
@@ -838,52 +843,172 @@ 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 += (this->GetSize() / 1.55);
new_z = this->FindDestGroundZ(dest, z_find_offset);
if (new_z != BEST_Z_INVALID)
{
new_z += this->GetZOffset();
auto duration = timer.elapsed();
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 != -999999) {
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::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;
}
return 0.2 * GetSize() * offset;
}
int ZoneDatabase::GetHighestGrid(uint32 zoneid) {
std::string query = StringFormat("SELECT COALESCE(MAX(id), 0) FROM grid WHERE zoneid = %i", zoneid);
+7 -2
View File
@@ -1428,7 +1428,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();
@@ -1496,6 +1497,8 @@ void Zone::Repop(uint32 delay) {
iterator.RemoveCurrent();
}
entity_list.ClearTrapPointers();
quest_manager.ClearAllTimers();
if (!database.PopulateZoneSpawnList(zoneid, spawn2_list, GetInstanceVersion(), delay))
@@ -1503,6 +1506,8 @@ void Zone::Repop(uint32 delay) {
initgrids_timer.Start();
entity_list.UpdateAllTraps(true, true);
//MODDING HOOK FOR REPOP
mod_repop();
}
@@ -1510,7 +1515,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);
}
+9 -2
View File
@@ -1433,6 +1433,11 @@ 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;
clock_t t = std::clock(); /* Function timer start */
std::string query = StringFormat(
"REPLACE INTO `character_data` ("
@@ -2953,9 +2958,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)
+2 -1
View File
@@ -16,6 +16,7 @@ class NPC;
class Petition;
class Spawn2;
class SpawnGroupList;
class Trap;
struct CharacterEventLog_Struct;
struct Door;
struct ExtendedProfile_Struct;
@@ -478,7 +479,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);