private object _looterIdSyncLock = new object(); // Clients could try to loot at the same time (frequently do, actually) #endregion Fields #region Constructors /// <summary>For use with NPC mobs.</summary> /// <param name="mob">Mob that died.</param> /// <param name="decayTime">Miliseconds until corpse decay.</param> /// <param name="lootItems">Items which will be part of the corpse's loot.</param> internal Corpse(Mob mob, List<InventoryItem> lootItems) : base(mob.ID, mob.Name.RemoveDigits() + "'s corpse" + mob.ID.ToString(), "", mob.X, mob.Y, mob.Z, mob.Heading) { if (lootItems != null) _lootItems.AddRange(lootItems); this.Platinum = mob.Platinum; this.Gold = mob.Gold; this.Silver = mob.Silver; this.Copper = mob.Copper; _changed = false; if (IsEmpty()) _decayTimer = new SimpleTimer(DECAYMS_EMPTY_NPC_CORPSE); else _decayTimer = new SimpleTimer(mob.Level > 54 ? DECAYMS_BOSS_NPC_CORPSE : DECAYMS_NPC_CORPSE); _gender = mob.Gender; _race = mob.Race; _bodyType = mob.BodyType; _size = mob.Size; _texture = mob.Texture; _helmTexture = mob.HelmTexture; }
/// <summary>Removes the mob from all of the mobs's hate list.</summary> internal void RemoveFromAllHateLists(Mob mob) { _mobsListLock.EnterReadLock(); try { foreach (Mob m in this.AllMobs) m.HateMgr.RemoveHate(mob); } catch (Exception ex) { _log.Error("Error removing a mob from hate lists in the mob mgr... ", ex); } finally { _mobsListLock.ExitReadLock(); } }
protected override bool IsAbleToAttack(Mob target, bool spellAttack) { if (!base.IsAbleToAttack(target, spellAttack)) return false; // TODO: check if we're trying to beat on our pet (not allowed) // TODO: handle dueling if (target is ZonePlayer) return false; return true; }
/// <summary>Make sure to check if able to attack prior to calling as this routine doesn't check if we're able to attack.</summary> /// <returns>False if the attack isn't allowed or did not succeed, true if attack successful.</returns> internal override bool MeleeAttack(Mob target, bool isPrimaryHand, bool riposte) { if (!base.MeleeAttack(target, isPrimaryHand, riposte)) return false; // Are we using a weapon? Item weapon = isPrimaryHand ? this.GetEquipment(EquipableType.Primary) : this.GetEquipment(EquipableType.Secondary); if (weapon != null) { if (!weapon.IsWeapon) { _log.DebugFormat("Attack cancelled: {0} is not a weapon.", weapon.Name); return false; } } // Cool, so we're going to attack // First calculate the skill and animation to use Skill attackSkill; AttackAnimation attackAnim; GetSkillAndAnimForAttack(weapon, isPrimaryHand, out attackSkill, out attackAnim); OnAnimation(new AnimationEventArgs(attackAnim)); // Cue the animation //_log.DebugFormat("Attacking {0} with {1} in slot {2} using skill {3}", this.TargetMob.Name, weapon == null ? "Fist" : weapon.Name, // isPrimaryHand ? "Primary" : "Secondary", attackSkill.ToString("f")); // Next figure out the potential damage int potDamage = GetWeaponDamage(target, weapon); int damage = 0; //_log.DebugFormat("Potential damage calculated as {0}", potDamage); // If potential damage is more than zero we know it's POSSIBLE to hit the target if (potDamage > 0) { // TODO: Finishing blow attempt CheckForSkillUp(attackSkill, target, -5); CheckForSkillUp(Skill.Offense, target, -5); // Ok, so we know we CAN hit... let's see if we DO hit if (target.TryToHit(this, attackSkill, isPrimaryHand)) { // TODO: Augment potDamage with any Beserker damage bonuses int minHitDmg = 1; int maxHitDmg = this.GetMaxDamage(potDamage, attackSkill); // Level cap the damage if (this.Level < 10) maxHitDmg = Math.Min(maxHitDmg, 20); else if (this.Level < 20) maxHitDmg = Math.Min(maxHitDmg, 40); // TODO: apply damage bonuses (affects min and max dmg as well as hate) // TODO: adjust min damage with any applicable item or buff bonuses maxHitDmg = Math.Max(maxHitDmg, minHitDmg); // Ensure max is at least min Random rand = new Random(); damage = rand.Next(minHitDmg, maxHitDmg + 1); //_log.DebugFormat("Damage calculated to {0} (min {1}, max {2}, str {3}, skill {4}, pot dmg {5}, lvl {6})", damage, minHitDmg, // maxHitDmg, this.STR, GetSkillLevel(attackSkill), potDamage, this.Level); // With damage now calculated, see if the mob can avoid or mitigate damage = target.TryToAvoidDamage(this, damage); if (damage > 0) { damage = target.TryToMitigateDamage(this, damage, minHitDmg); // wasn't avoided, try to mitigate // TODO: apply any damage bonuses (is this the right term... bonuses?... wasn't that done by now? // TODO: try a critical hit (why are we trying this after the mitigation?) } //_log.DebugFormat("Damage after avoidance and mitigation: {0}", damage); } else { // We missed //_log.Debug("Missed."); } // TODO: riposte // TODO: strikethrough } else damage = (int)AttackAvoidanceType.Invulnerable; target.Damage(this, damage, 0, attackSkill); // Send attack damage - even zero and negative damage BreakSneakiness(); // TODO: weapon procs if (damage > 0) { // TODO: handle lifetap effects? // TODO: trigger defensive procs return true; } else return false; }
protected override bool IsAbleToAttack(Mob target, bool spellAttack) { if (!base.IsAbleToAttack(target, spellAttack)) return false; // TODO: handle attacking another NPC? return true; }
internal override bool MeleeAttack(Mob target, bool isPrimaryHand, bool riposte) { if (!base.MeleeAttack(target, isPrimaryHand, riposte)) return false; FaceMob(target); // Are we using a weapon? Item weapon = isPrimaryHand ? this.GetEquipment(EquipableType.Primary) : this.GetEquipment(EquipableType.Secondary); // Not much from the weapon is factored into the attack - just the skill type for the correct animations. if (weapon != null) { if (!isPrimaryHand && weapon.ItemType == (byte)ItemType.Shield) { _log.DebugFormat("{0} attacking with {1}({2}) in secondary - attack with shield cancelled.", this.Name, weapon.Name, weapon.ItemID); return false; } } // First calculate the skill and animation to use Skill attackSkill; AttackAnimation attackAnim; GetSkillAndAnimForAttack(weapon, isPrimaryHand, out attackSkill, out attackAnim); OnAnimation(new AnimationEventArgs(attackAnim)); // Cue the animation _log.DebugFormat("NPC attacking {0} with {1} in slot {2} using skill {3}", this.TargetMob.Name, weapon == null ? "Fist" : weapon.Name, isPrimaryHand ? "Primary" : "Secondary", attackSkill.ToString("f")); // Next figure out the potential damage int potDamage = GetWeaponDamage(target, weapon); int damage = 0; //_log.DebugFormat("Potential damage calculated as {0} for {1}", potDamage, this.Name); // If potential damage is more than zero we know it's POSSIBLE to hit the target if (potDamage > 0) { // Elemental & Bane dmg added differently than for PCs - where PCs would use the potential dmg (which has elem & bane included) // to figure max dmg, NPCs use the built-in min & max dmg values along with any elem and/or bane dmg. int elemBaneDmg = 0; if (weapon != null) { if (weapon.BaneDmgBody == (int)target.BodyType) elemBaneDmg += weapon.BaneDmgBodyAmt; if (weapon.BaneDmgRace == target.Race) elemBaneDmg += weapon.BaneDmgRaceAmt; if (weapon.ElemDmgAmt > 0) { // TODO: check for other's resist } } int minDmg = _minDmg + elemBaneDmg; int maxDmg = _maxDmg + elemBaneDmg; // Ok, so we know we CAN hit... let's see if we DO hit if (target is ZonePlayer && ((ZonePlayer)target).Sitting) { _log.DebugFormat("{0} is sitting - {1} hitting for max damage {2}", ((ZonePlayer)target).Name, this.Name, maxDmg); damage = maxDmg; } else if (target.TryToHit(this, attackSkill, isPrimaryHand)) { Random rand = new Random(); damage = rand.Next(minDmg, maxDmg + 1); //_log.DebugFormat("{4}'s damage calculated to {0} (min {1}, max {2}, pot dmg {3})", damage, minDmg, maxDmg, potDamage, this.Name); // With damage now calculated, see if the mob can avoid and mitigate damage = target.TryToAvoidDamage(this, damage); if (damage > 0) { damage = target.TryToMitigateDamage(this, damage, minDmg); // wasn't avoided, try to mitigate // TODO: apply any damage bonuses (is this the right term... bonuses?... wasn't that done by now? // TODO: try a critical hit (why are we trying this after the mitigation?) } //_log.DebugFormat("NPC damage after avoidance and mitigation: {0}", damage); } //else // _log.DebugFormat("{0} missed {1}.", this.Name, this.TargetMob.Name); // TODO: cancel a riposte of a riposte } else damage = (int)AttackAvoidanceType.Invulnerable; target.Damage(this, damage, 0, attackSkill); // Send attack damage - even zero and negative damage BreakSneakiness(); // TODO: weapon procs // TODO: check if we're getting a riposte back? if (damage > 0) { // TODO: handle lifetap effects? // TODO: trigger defensive procs return true; } else return false; }
/// <summary>Based upon several factors, the spell being cast can have different targets. This method determines those targets.</summary> protected bool DetermineSpellTargets(Spell spell, Mob target, ref Mob aeCenter, out CastActionType cat) { cat = CastActionType.AECaster; return false; // TODO: unfinished }
protected virtual bool IsAbleToAttack(Mob target, bool spellAttack) { if (target == null) throw new ArgumentNullException("other"); return target.IsAttackable; }
/// <summary>Determines if we are behind a given mob. Useful for hide, backstab and riposte checks.</summary> /// <param name="other">Mob we are seeing if we are behind.</param> internal bool IsBehindMob(Mob other) { float angle, lengthB, vectorX, vectorY; float mobX = -(other.X); // mob xlocation (inverse because eq is confused) float mobY = other.Y; // mobylocation float heading = other.Heading; // mob heading heading = (heading * 360.0f) / 256.0f; // convert to degrees if (heading < 270) heading += 90; else heading -= 270; heading = heading * 3.1415f / 180.0f; // convert to radians vectorX = mobX + (10.0f * (float)Math.Cos(heading)); // create a vector based on heading vectorY = mobY + (10.0f * (float)Math.Sin(heading)); // of mob length 10 //length of mob to player vector //lengthb = (float)sqrtf(pow((-playerx-mobx),2) + pow((playery-moby),2)); lengthB = (float)Math.Sqrt(((-(this.X) - mobX) * (-(this.X) - mobX)) + ((this.Y - mobY) * (this.Y - mobY))); // calculate dot product to get angle angle = (float)Math.Acos(((vectorX - mobX) * (-(this.X) - mobX) + (vectorY - mobY) * (this.Y - mobY)) / (10 * lengthB)); angle = angle * 180f / 3.1415f; if (angle > 90.0) //not sure what value to use (90*2=180 degrees is front) return true; else return false; }
internal float DistanceNoZ(Mob other) { float xDiff = other.X - this.X; float yDiff = other.Y - this.Y; return (float)Math.Sqrt((xDiff * xDiff) + (yDiff * yDiff)); }
internal float DistanceNoRoot(Mob other) { float xDiff = other.X - this.X; float yDiff = other.Y - this.Y; float zDiff = other.Z - this.Z; return ((xDiff * xDiff) + (yDiff * yDiff) + (zDiff * zDiff)); }
/// <summary></summary> /// <param name="attacker">Optional parameter of whom is causing the damage.</param> /// <param name="dmgAmt">Amount of damage caused.</param> /// <param name="spellId">Id of the spell responsible for the damage. Zero indicates no spell.</param> internal virtual void Damage(Mob attacker, int dmgAmt, int spellId, Skill attackSkill) { // TODO: is this from a damage shield? int hate = dmgAmt * 2; // TODO: this value may need tweaked if (attacker != null) { if (attacker is ZonePlayer) { if (!((ZonePlayer)attacker).IsFeigning) // only hate on the player if they aren't fd'ing _hateMgr.AddHate(attacker, hate, dmgAmt); } else _hateMgr.AddHate(attacker, hate, dmgAmt); // TODO: pets seem to have thier own calc for hate (see NPC::Attack in orig emu) } if (dmgAmt > 0) { // Is there some damage actually being done? if (attacker != null) { // Is someone doing the damage? // TODO: harm touch // TODO: lifetap spell effects? } // TODO: pet shit // TODO: rune stuff _log.DebugFormat("{0} has taken {1} damage. {2} HP left ({3:P}).", this.Name, dmgAmt, this.HP - dmgAmt, (this.HP - dmgAmt) / (float)this.MaxHP); if (dmgAmt >= this.HP) { // TODO: try death save via AA abilities, etc. (in a virtual method) // TODO: do we do the damage msg here or elsewhere? this.Die(attacker, dmgAmt, spellId, attackSkill); return; } this.HP -= dmgAmt; if (this.Sneaking) this.Sneaking = false; // TODO: remove mez // TODO: check stun chances if bashing // TODO: handle chance of spell breaking root - (spellId of 0 is no spell) // TODO: handle chance of casting interrupt } OnDamaged(new DamageEventArgs() { SourceId = (ushort)(attacker == null ? 0 : attacker.ID), Damage = (uint)dmgAmt, Type = Mob.GetDamageTypeForSkill(attackSkill), SpellId = (ushort)spellId } ); }
private void PlayerDeath(ZonePlayer zp, Mob lastAttacker, uint spellId, byte damageType, uint damage) { _log.DebugFormat("{0} bought the farm. Fatal blow dealt by {1} with {2} damage, skill {3}, spell {4}.", zp.Name, lastAttacker.Name, damage, damageType, spellId); if (zp.IsDePopped) { _log.WarnFormat("{0} is trying to die more than once or something... is already depopped!", zp.Name); return; } _zoneSvr.SendLogoutPackets(zp.Client); // Make the become corpse packet and queue to player before Death opCode packet BecomeCorpse bc = new BecomeCorpse() { SpawnId = (uint)zp.ID, X = zp.X, Y = zp.Y, Z = zp.Z }; EQApplicationPacket<BecomeCorpse> bcPack = new EQApplicationPacket<BecomeCorpse>(AppOpCode.BecomeCorpse, bc); _zoneSvr.QueuePacketToClient(zp.Client, bcPack, true, ZoneConnectionState.All); Death d = new Death() { SpawnId = (uint)zp.ID, KillerId = lastAttacker == null ? 0u : (uint)lastAttacker.ID, CorpseId = (uint)zp.ID, BindToZoneId = zp.PlayerProfile.Binds[0].ZoneId, SpellId = spellId == 0u ? 0xFFFFFFFF : spellId, AttackSkillId = spellId != 0u ? (uint)0xe7 : damageType, Damage = damage }; EQApplicationPacket<Death> dPack = new EQApplicationPacket<Death>(AppOpCode.Death, d); _zoneSvr.QueuePacketToClients(lastAttacker, dPack, false); RemoveFromAllTargets(zp); // orig emu removed self from its own hate list... don't understand why you'd do that, so skipping if (!zp.IsGM) { // GM's don't get penalized from dieing... muwhahaha // Figure out how much xp we lose, if any int xpLoss = 0; if (zp.Level >= WorldServer.ServerConfig.LevelDeathDoesXPLoss) // TODO: don't lose xp when we have become an NPC? xpLoss = (int)(zp.XP * WorldServer.ServerConfig.XPLossModifier); if (lastAttacker is ZonePlayer) // TODO: also check if the attacker is a pet owned by a player (no xp loss in that case) xpLoss = 0; // Don't lose xp when dueling _log.DebugFormat("{0} is losing {1} xp due to his ass died.", zp.Name, xpLoss); zp.XP -= xpLoss; // TODO: fade buffs (ALL - good and bad) // TODO: unmem spells (don't send packets) PlayerCorpse pc = new PlayerCorpse(zp, xpLoss, _zoneSvr.HasGraveyard()); AddCorpse(pc); _zoneSvr.QueuePacketToClients(zp, bcPack, true); // send the become corpse packet to all players in the zone } else { // TODO: fade just detrimental buffs } // Send player to bind point _zoneSvr.MovePlayer(zp, zp.PlayerProfile.Binds[0].ZoneId, 0, zp.PlayerProfile.Binds[0].X, zp.PlayerProfile.Binds[0].Y, zp.PlayerProfile.Binds[0].Z, zp.PlayerProfile.Binds[0].Heading, ZoneMode.ZoneToBindPoint); }
private void NpcDeath(NpcMob npc, Mob lastAttacker, uint spellId, byte damageType, uint damage) { RemoveFromAllTargets(npc); // Remove NPC from any entity's target & hate lists // Send Death Packet Death d = new Death() { SpawnId = (uint)npc.ID, KillerId = lastAttacker == null ? 0u : (uint)lastAttacker.ID, BindToZoneId = 0u, SpellId = spellId == 0u ? 0xFFFFFFFF : spellId, AttackSkillId = damageType, Damage = damage }; EQApplicationPacket<Death> dPack = new EQApplicationPacket<Death>(AppOpCode.Death, d); _zoneSvr.QueuePacketToClients(lastAttacker, dPack, false); // TODO: this should be cool with a null lastAttacker, right? // TODO: something about respawn? Mob killer = npc.HateMgr.GetTopHated(); Mob xpMob = npc.HateMgr.GetTopDamager(); // Give xp out if (xpMob == null) xpMob = killer; if (xpMob == null) xpMob = lastAttacker; // TODO: if xpMob is pet, get the owner ZonePlayer xpClient = xpMob as ZonePlayer; if (xpClient != null) { // TODO: handle raid split, LDON adventure crap, etc. float groupBonus = 1.0f; if (xpClient.IsGrouped) { // TODO: handle group split // TODO: add group xp bonus } else { ConLevel conLvl = Mob.GetConsiderDificulty(xpClient.Level, npc.Level); if (conLvl != ConLevel.Green) { // TODO: figure high con bonus, if any int baseXp = (int)(npc.Level * npc.Level * groupBonus * _zoneSvr.Zone.XPMultiplier); xpClient.GiveXP((int)(npc.Level * npc.Level * 75 * _zoneSvr.Zone.XPMultiplier), conLvl); } } // TODO: raise death merit event? } // TODO: faction hits // Make a corpse and add to the manager if (npc.IsLootable) { // TODO: add additional checks for stuff like a guard killing the mob, etc. Corpse c = new Corpse(npc, npc.LootItems); AddCorpse(c); // TODO: if killer is a pet, get the owner if (xpClient != null) c.AllowLooter(killer as ZonePlayer); } // TODO: raise script event _log.DebugFormat("NPC {0} has died", npc.ID); }
/// <summary>Removes the specified mob from the targets of all mobs, if they have it targeted.</summary> internal void RemoveFromAllTargets(Mob mob) { _mobsListLock.EnterReadLock(); try { foreach (Mob m in this.AllMobs) { if (m.TargetMob == mob) { m.HateMgr.RemoveHate(mob); m.TargetMob = null; } } } catch (Exception ex) { _log.Error("Error removing a mob from targets in the mob mgr... ", ex); } finally { _mobsListLock.ExitReadLock(); } }
/// <summary>Makes this mob turn to face the specified mob.</summary> /// <param name="mobToFace">The mob that should be faced. If null, faces the currently targeted mob.</param> protected void FaceMob(Mob other) { Mob mobToFace = other == null ? this.TargetMob : other; if (mobToFace == null) { _log.Error("Unable to face the mob - null was specified and we don't have a mob targeted."); return; } float oldHeading = this.Heading; float newHeading = CalculateHeadingToTarget(mobToFace.X, mobToFace.Y); if (oldHeading != newHeading) { this.Heading = newHeading; if (this.IsMoving) OnPositionUpdate(new PosUpdateEventArgs(true, true)); else OnPositionUpdate(new PosUpdateEventArgs(true, false)); } }
/// <summary>Gets the damage for the specified target mob with the specified weapon.</summary> /// <param name="target">Mob that is being attacked.</param> /// <param name="weapon">Null indicates fists.</param> /// <returns>Amount of damage delivered.</returns> protected int GetWeaponDamage(Mob target, Item weapon) { int damage = 0, baneDamage = 0, elemDamage = 0; if (target.Invulnerable || target.HasSpecialDefense(SpecialDefenses.ImmuneMelee)) return 0; if (target.HasSpecialDefense(SpecialDefenses.ImmuneMeleeNonmagical)) { if (weapon != null) { if (weapon.IsMagic) { // TODO: look for magic weapon buff as well if (this is ZonePlayer && this.Level < weapon.RecLevel) damage = ZonePlayer.CalcRecommendedLevelBonus(this.Level, weapon.RecLevel, weapon.Damage); else damage = weapon.Damage; // TODO: accumulate weapon augmentation damage bonuses damage = Math.Min(damage, 1); } else return 0; // Weapon not magical, but one is needed } else { // TODO: add check below for pet ability to hit if ((this.Class == (byte)CharClasses.Monk || this.Class == (byte)CharClasses.BeastLord) && this.Level >= 30) damage = GetMonkHandToHandDamage(); else if (this.HasSpecialAttack(SpecialAttacks.Magical)) damage = 1; else return 0; // No weapon and can't harm with hand to hand } } else { if (weapon != null) { if (this is ZonePlayer && this.Level < weapon.RecLevel) damage = ZonePlayer.CalcRecommendedLevelBonus(this.Level, weapon.RecLevel, weapon.Damage); else damage = weapon.Damage; // TODO: accumulate weapon augmentation damage bonuses damage = Math.Max(damage, 1); // Minimum weapon damage of 1 } else { if (this.Class == (byte)CharClasses.Monk || this.Class == (byte)CharClasses.BeastLord) damage = GetMonkHandToHandDamage(); else damage = UNARMED_DAMAGE; } } // TODO: elemental damage (Don't add resist checks - just calculating POTENTIAL damage here) // TODO: bane damage return damage + baneDamage + elemDamage; }
/// <summary>Determines if this mob is invisible to another mob.</summary> /// <param name="other">Mob that may or may not be capable of seeing this mob.</param> internal bool IsInvisibleTo(Mob other) { // check regular invisibility if (this.Invisible && !other.SeeInvis) return true; // check invis vs. undead if (other.BodyType == BodyType.Undead || other.BodyType == BodyType.SummonedUndead) { if (this.InvisibleToUndead && !other.SeeInvisToUndead) return true; } // check invis vs. animals if (other.BodyType == BodyType.Animal) if (this.InvisibleToAnimals && !other.SeeInvisToAnimals) return true; if (this.Hidden) { if (!other.SeeHide && !other.SeeImprovedHide) return true; } if (this.ImprovedHidden && !other.SeeImprovedHide) return true; if (this.Sneaking && IsBehindMob(other)) return true; return false; }
/// <summary>Actually executes a spell and its effects on a specified target.</summary> /// <param name="spell"></param> /// <param name="target"></param> /// <param name="slotId"></param> /// <param name="invSlotId">Optional slot a used item is in that caused the spell.</param> /// <returns></returns> internal virtual bool ExecuteSpell(Spell spell, Mob target, uint slotId, uint? invSlotId) { CastActionType cat; Mob aeCenter = null; if (!DetermineSpellTargets(spell, target, ref aeCenter, out cat)) return false; return true; // TODO: unfinished }
/// <summary>Determines if this mob is within combat range of another mob.</summary> /// <param name="other">Mob that may or may not be within combat range of this mob.</param> internal bool IsWithinCombatRangeOf(Mob other) { float sizeMod = this.Size; float otherSizeMod = other.Size; Races thisRace = (Races)this.Race; if (thisRace == Races.LavaDragon || thisRace == Races.Wurm || thisRace == Races.GhostDragon) // For races with fixed size sizeMod = 60.0f; else if (sizeMod < 6.0f) sizeMod = 8.0f; Races otherRace = (Races)this.Race; if (otherRace == Races.LavaDragon || otherRace == Races.Wurm || otherRace == Races.GhostDragon) // For races with fixed size otherSizeMod = 60.0f; else if (sizeMod < 6.0f) otherSizeMod = 8.0f; sizeMod = Math.Max(sizeMod, otherSizeMod); // not 100% sure what's going on here... seems to be scaling the size modifier a bit if (sizeMod > 29) sizeMod *= sizeMod; else if (sizeMod > 19) sizeMod *= sizeMod * 2; else sizeMod *= sizeMod * 4; // Prevent ridiculously sized hit boxes if (sizeMod > 10000.0f) sizeMod /= 7; if (this.DistanceNoRootNoZ(other) <= sizeMod) return true; return false; }
internal override void Damage(Mob attacker, int dmgAmt, int spellId, Skill attackSkill) { // TODO: raise attacked script event // TODO: handle LDON treasure traps, etc. base.Damage(attacker, dmgAmt, spellId, attackSkill); // TODO: upstream pet class should send a msg to its owner // TODO: upstream pet class should check if it should flee }
/// <summary>Performs a melee attack against the specified target mob.</summary> /// <param name="target">Mob being attacked.</param> /// <param name="isPrimaryHand">Is this the primary hand weapon as opposed to a dual wield attack.</param> /// <param name="riposte">Is this a riposte attack?</param> /// <returns>True if an attack succeeded, else false if the attack didn't succeed (for whatever reason).</returns> internal virtual bool MeleeAttack(Mob target, bool isPrimaryHand, bool riposte) { if (target == null) throw new ArgumentNullException("target"); return true; // Anything else should be handled in the specific entity type }
protected override void Die(Mob lastAttacker, int damage, int spellId, Skill attackSkill) { // TODO: kill pet - maybe handle in mob class? // TODO: fade buffs // TODO: something with LDON treasures base.Die(lastAttacker, damage, spellId, attackSkill); }
/// <summary>Tries to avoid an attack that has already been determined to otherwise land a hit.</summary> /// <param name="attacker"></param> /// <param name="damage"></param> /// <returns>Damage amount after avoidance methods are checked. If successfully avoided the damage will be one of the AttackAvoidanceType values.</returns> internal int TryToAvoidDamage(Mob attacker, int damage) { float bonus = 0.0f; uint skill = 0; float riposteChance = 0.0f, blockChance = 0.0f, parryChance = 0.0f, dodgeChance = 0.0f; // TODO: determine guaranteed hits bool autoHit = false; // If this mob is enraged, it auto-ripostes (even guaranteed hits apparently) if (this is NpcMob && ((NpcMob)this).Enraged && !attacker.IsBehindMob(this)) { damage = (int)AttackAvoidanceType.Riposte; } // Try to Riposte if (damage > 0 && this.GetSkillLevel(Skill.Riposte) > 0 && !attacker.IsBehindMob(this)) { skill = this.GetSkillLevel(Skill.Riposte); if (this is ZonePlayer) ((ZonePlayer)this).CheckForSkillUp(Skill.Riposte, attacker, 0); if (!autoHit) { // guaranteed hit discipline trumps riposte bonus = 2.0f + skill / 60.0f + (this.DEX / 200); // TODO: add in riposte related item and spell bonuses riposteChance = bonus; } } // Try to Block bool blockFromRear = false; // TODO: handle Hightened Awareness AA if (damage > 0 && this.GetSkillLevel(Skill.Block) > 0 && (!attacker.IsBehindMob(this) || blockFromRear)) { skill = this.GetSkillLevel(Skill.Block); if (this is ZonePlayer) ((ZonePlayer)this).CheckForSkillUp(Skill.Block, attacker, 0); if (!autoHit) { bonus = 2.0f + skill / 35.0f + (this.DEX / 200); blockChance = riposteChance + bonus; } // TODO: handle Shield Block AA } // TODO: Try to Parry // TODO: Try to Dodge Random rand = new Random(); int roll = rand.Next(1, 101); // Roll between 1-100 if (roll <= (riposteChance + blockChance + parryChance + dodgeChance)) { // Something worked, now which one was it... if (roll <= riposteChance) damage = (int)AttackAvoidanceType.Riposte; else if (roll < blockChance) damage = (int)AttackAvoidanceType.Block; else if (roll < parryChance) damage = (int)AttackAvoidanceType.Parry; else if (roll < dodgeChance) damage = (int)AttackAvoidanceType.Dodge; } return damage; }
/// <summary></summary> /// <param name="attacker"></param> /// <param name="dmgAmt"></param> /// <param name="spellId">Id of the damaging spell. Zero for no spell.</param> /// <param name="attackSkill"></param> internal override void Damage(Mob attacker, int dmgAmt, int spellId, Skill attackSkill) { if (this.Dead) throw new InvalidOperationException("Cannot damage a dead PC."); // TODO: do we just want to return instead of throw? base.Damage(attacker, dmgAmt, spellId, attackSkill); if (dmgAmt > 0) { if (spellId == 0) CheckForSkillUp(Skill.Defense, attacker, -5); } }
internal bool TryToHit(Mob attacker, Skill attackSkill, bool isPrimaryHand) { float hitChance = WorldServer.ServerConfig.BaseHitChance * 100; if (attacker is NpcMob) { hitChance += WorldServer.ServerConfig.NPCBonusHitChance; hitChance += (hitChance * ((NpcMob)attacker).Accuracy / 1000.0f); // Accuracy can be set in NPC table over 1000 to give the NPC a greater hit chance } //_log.DebugFormat("Base hit chance with NPC bonus & NPC Accuracy: {0}", hitChance); int levelDiff = attacker.Level - this.Level; float range = attacker.Level / 4.0f + 3; // This is changed from orig emu... I chose the attacker to base from for a tighter range if (levelDiff < 0) { // We are of higher level than the attacker if (levelDiff >= -range) hitChance += levelDiff / range * WorldServer.ServerConfig.HitFallOffMinor; // 5 else if (levelDiff >= -(range + 3.0f)) { hitChance -= WorldServer.ServerConfig.HitFallOffMinor; hitChance += ((levelDiff + range) / 3.0f) * WorldServer.ServerConfig.HitFallOffModerate; // 7 } else { hitChance -= WorldServer.ServerConfig.HitFallOffMinor + WorldServer.ServerConfig.HitFallOffModerate; hitChance += ((levelDiff + range + 3.0f) / 12.0f) * WorldServer.ServerConfig.HitFallOffMajor; // Basically no f*****g chance } } else hitChance += levelDiff * WorldServer.ServerConfig.HitBonusPerLevel; //_log.DebugFormat("Hit chance after level diff adj: {0} (attacker level: {1} defender level: {2} diff: {3})", hitChance, attacker.Level, this.Level, levelDiff); hitChance -= this.AGI * 0.05f; // Adjust for agility TODO: const or db //_log.DebugFormat("Hit chance after adj for AGI: {0} (AGI: {1})", hitChance, this.AGI); if (attacker is ZonePlayer) { // TODO: weapon skill & defense skill falloffs? // TODO: handle client specific AA bonuses } // TODO: Add spell and item bonuses relating to ATTACKER melee skill and hit chance skill (Factor this into virtual GetChanceToHitBonuses()) // TODO: subtract off the defender's avoidance (looks like spell and item bonus related) // TODO: defender's AA abilities which mitigate to hit chances if (hitChance < 1000.0f) { // As long as a discipline isn't involved... TODO: what about guaranteed riposte vs. guaranteed hit? hitChance = Math.Max(hitChance, TOHIT_MIN); // Chance to hit max: 95% min: 5% hitChance = Math.Min(hitChance, TOHIT_MAX); } Random rand = new Random(); int toHitRoll = rand.Next(0, 100); //_log.DebugFormat("Final hit chance: {0} Roll: {1})", hitChance, toHitRoll); return toHitRoll <= hitChance; }
protected override void Die(Mob lastAttacker, int damage, int spellId, Skill attackSkill) { // TODO: interrupt the spell being cast // TODO: clear pet // TODO: clear mount _deadTimer.Start(DEAD_TIMER); if (lastAttacker != null) { if (lastAttacker is NpcMob) { // TODO: raise slay event for the NPC } // TODO: handle dueling } if (this.IsGrouped) { // TODO: tell group we zoned (here or in mobMgr?) } if (this.IsRaidGrouped) { // TODO: tell raid we zoned (here or in mobMgr?) } // TODO: clear proximity? base.Die(lastAttacker, damage, spellId, attackSkill); // Player's shit is now on their corpse, not on them this.Platinum = 0u; this.Gold = 0u; this.Silver = 0u; this.Copper = 0u; this.InvMgr.ClearPersonalSlotItems(); // TODO: clear zone instance ID (when instancing is in) Save(); }
/// <summary>Attempts to mitigate the specified damage amount.</summary> /// <param name="attacker"></param> /// <param name="damage"></param> /// <returns>Resulting amount of damage after all mitigation has been applied.</returns> internal int TryToMitigateDamage(Mob attacker, int damage, int minDmg) { if (damage <= 0) throw new ArgumentOutOfRangeException("damage", "No need to mitigate damage of zero or less."); int totalMit = 0; // TODO: Accumulate bonuses from AA abilities related to damage mitigation // AC mitigation TODO: examine these calcs and see if they are wacky or not int attackRating = 0; int defenseRating = this.AC; defenseRating += 125 + (totalMit * defenseRating / 100); int acEq100 = 125; if (this.Level < 20) acEq100 += 15 * this.Level; else if (this.Level < 50) acEq100 += (285 + ((this.Level - 19) * 30)); else if (this.Level < 60) acEq100 += (1185 + ((this.Level - 49) * 60)); else if (this.Level < 70) acEq100 += (1785 + ((this.Level - 59) * 90)); else acEq100 += (2325 + ((this.Level - 69) * 125)); if (attacker is ZonePlayer) // TODO: factor below into an AttackRating property? attackRating = 10 + attacker.Attack + (int)(attacker.STR + attacker.GetSkillLevel(Skill.Offense) * 9 / 10); else attackRating = 10 + attacker.Attack + (attacker.STR * 9 / 10); float combatRating = defenseRating - attackRating; combatRating = 100 * combatRating / acEq100; float d1Chance = 6.0f + ((combatRating * 0.39f) / 3); float d2d19Chance = 48.0f + (((combatRating * 0.39f) / 3) * 2); d1Chance = Math.Max(d1Chance, 1.0f); // min chance of 1.0 d2d19Chance = Math.Max(d2d19Chance, 5.0f); // min chance of 5.0 Random rand = new Random(); double roll = rand.NextDouble() * 100; int interval = 0; if (roll <= d1Chance) interval = 1; else if (roll <= (d2d19Chance + d1Chance)) interval = 1 + (int)((((roll - d1Chance) / d2d19Chance) * 18) + 1); else interval = 20; // TODO: the f**k is this shit? int db = minDmg; double di = ((double)(damage - minDmg) / 19); damage = db + (int)(di * (interval - 1)); // TODO: reduce damage further from shielding item and AA which are based on the min dmg // TODO: reduce damage further from spells which are based upon pure damage (not min dmg) return Math.Max(damage, minDmg); // Can't mitigate below min dmg }
/// <summary>Checks if the player gains a skill point in a particular skill.</summary> /// <param name="skill">The skill to check for a skill up for.</param> /// <param name="target">The mob the check is being performed against. Null if not that kind of skill.</param> /// <param name="chanceMod">Modifier to add to the chance probability. A value of 80+ would almost ensure a skillup.</param> internal bool CheckForSkillUp(Skill skill, Mob target, int chanceMod) { if (this.IsAIControlled) return false; if (skill > Skill.Highest) throw new ArgumentException("Skill type is beyond the range of legal skill types.", "skill"); uint skillLvl = GetBaseSkillLevel(skill); uint maxLvl = GetSkillCap(skill); if (skillLvl < maxLvl) { if (target != null) { if (target is ZonePlayer || Mob.GetConsiderDificulty(this.Level, target.Level) == ConLevel.Green) return false; // TODO: add check for aggro immunity on target (spec attack) } // the higher the current skill level, the harder it is to skill up int chance = ((252 - (int)skillLvl) / 20) + chanceMod; chance = Math.Max(chance, 1); // Always have at least a slim chance // TODO: add configurable global skill up modifier Random rand = new Random(); if (rand.Next(100) < chance) { SetSkillLevel(skill, skillLvl + 1); _log.DebugFormat("{0} skilled up {1} from skill level {2} with a chance of {3}", this.Name, skill, skillLvl, chance); return true; } //else // _log.DebugFormat("{0} failed to skill up {1} from skill level {2} with a chance of {3}", this.Name, skill, skillLvl, chance); } else _log.DebugFormat("{0} unable to skill up {1} from skill level {2} due to a cap of {3}", this.Name, skill, skillLvl, maxLvl); return false; }
protected virtual void Die(Mob lastAttacker, int damage, int spellId, Skill attackSkill) { this.HP = 0; OnDamaged(new DamageEventArgs() { SourceId = (ushort)(lastAttacker == null ? 0 : lastAttacker.ID), Damage = (uint)damage, Type = Mob.GetDamageTypeForSkill(attackSkill), SpellId = (ushort)spellId }); _hateMgr.Clear(); DePop(); }