/// <summary> /// Called when a player hits a target /// </summary> public override void OnDamageTarget(WorldObject target, AttackType attackType) { var attackSkill = GetCreatureSkill(GetCurrentWeaponSkill()); var difficulty = GetTargetEffectiveDefenseSkill(target); Proficiency.OnSuccessUse(this, attackSkill, difficulty); }
public bool CalculateManaUsage(CastingPreCheckStatus castingPreCheckStatus, Spell spell, WorldObject target, bool isWeaponSpell, out uint manaUsed) { manaUsed = 0; if (castingPreCheckStatus == CastingPreCheckStatus.Success) { manaUsed = CalculateManaUsage(this, spell, target); } else if (castingPreCheckStatus == CastingPreCheckStatus.CastFailed) { manaUsed = 5; // todo: verify with retail } var currentMana = Mana.Current; if (isWeaponSpell) { var caster = GetEquippedWand(); currentMana = (uint)(caster.ItemCurMana ?? 0); } if (manaUsed > currentMana) { SendUseDoneEvent(WeenieError.YouDontHaveEnoughManaToCast); return(false); } Proficiency.OnSuccessUse(this, GetCreatureSkill(Skill.ManaConversion), spell.PowerMod); return(true); }
/// <summary> /// Called when player successfully avoids an attack /// </summary> public override void OnEvade(WorldObject attacker, CombatType attackType) { if (UnderLifestoneProtection) { return; } // http://asheron.wikia.com/wiki/Attributes // Endurance will also make it less likely that you use a point of stamina to successfully evade a missile or melee attack. // A player is required to have Melee Defense for melee attacks or Missile Defense for missile attacks trained or specialized // in order for this specific ability to work. This benefit is tied to Endurance only, and it caps out at around a 75% chance // to avoid losing a point of stamina per successful evasion. var defenseSkillType = attackType == CombatType.Missile ? Skill.MissileDefense : Skill.MeleeDefense; var defenseSkill = GetCreatureSkill(defenseSkillType); if (CombatMode != CombatMode.NonCombat) { if (defenseSkill.AdvancementClass >= SkillAdvancementClass.Trained) { var enduranceBase = Endurance.Base; // TODO: find exact formula / where it caps out at 75% var enduranceCap = 400; var effective = Math.Min(enduranceBase, enduranceCap); var noStaminaUseChance = effective / enduranceCap * 0.75f; if (noStaminaUseChance < ThreadSafeRandom.Next(0.0f, 1.0f)) { UpdateVitalDelta(Stamina, -1); } } else { UpdateVitalDelta(Stamina, -1); } } else { UpdateVitalDelta(Stamina, -1); } Session.Network.EnqueueSend(new GameMessageSystemChat($"You evaded {attacker.Name}!", ChatMessageType.CombatEnemy)); var creature = attacker as Creature; if (creature == null) { return; } var difficulty = creature.GetCreatureSkill(creature.GetCurrentWeaponSkill()).Current; // attackMod? Proficiency.OnSuccessUse(this, defenseSkill, difficulty); }
/// <summary> /// Called when a player hits a target /// </summary> public override void OnDamageTarget(WorldObject target, CombatType attackType, bool critical) { if (critical) { target.EmoteManager.OnReceiveCritical(this); } var attackSkill = GetCreatureSkill(GetCurrentWeaponSkill()); var difficulty = GetTargetEffectiveDefenseSkill(target); Proficiency.OnSuccessUse(this, attackSkill, difficulty); }
/// <summary> /// Performs a melee attack for the monster /// </summary> /// <returns>The length in seconds for the attack animation</returns> public float MeleeAttack() { var target = AttackTarget as Creature; var targetPlayer = AttackTarget as Player; var targetPet = AttackTarget as CombatPet; var combatPet = this as CombatPet; if (target == null || !target.IsAlive) { FindNextTarget(); return(0.0f); } if (CurrentMotionState.Stance == MotionStance.NonCombat) { DoAttackStance(); } // choose a random combat maneuver var maneuver = GetCombatManeuver(); if (maneuver == null) { Console.WriteLine($"Combat maneuver null! Stance {CurrentMotionState.Stance}, MotionTable {MotionTableId:X8}"); return(0.0f); } AttackHeight = maneuver.AttackHeight; DoSwingMotion(AttackTarget, maneuver, out float animLength, out var attackFrames); PhysicsObj.stick_to_object(AttackTarget.PhysicsObj.ID); var numStrikes = attackFrames.Count; var actionChain = new ActionChain(); var prevTime = 0.0f; for (var i = 0; i < numStrikes; i++) { actionChain.AddDelaySeconds(attackFrames[i] * animLength - prevTime); prevTime = attackFrames[i] * animLength; actionChain.AddAction(this, () => { if (AttackTarget == null || IsDead) { return; } if (WeenieType == WeenieType.GamePiece) { target.TakeDamage(this, DamageType.Slash, target.Health.Current); (this as GamePiece).OnDealtDamage(); return; } var weapon = GetEquippedWeapon(); var damageEvent = DamageEvent.CalculateDamage(this, target, weapon, maneuver); //var damage = CalculateDamage(ref damageType, maneuver, bodyPart, ref critical, ref shieldMod); if (damageEvent.HasDamage) { if (combatPet != null || targetPet != null) { // combat pet inflicting or receiving damage //Console.WriteLine($"{target.Name} taking {Math.Round(damage)} {damageType} damage from {Name}"); target.TakeDamage(this, damageEvent.DamageType, damageEvent.Damage); EmitSplatter(target, damageEvent.Damage); } else if (targetPlayer != null) { // this is a player taking damage targetPlayer.TakeDamage(this, damageEvent); if (damageEvent.ShieldMod != 1.0f) { var shieldSkill = targetPlayer.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(targetPlayer, shieldSkill, shieldSkill.Current); // ? } } } else { target.OnEvade(this, CombatType.Melee); } if (combatPet != null) { combatPet.PetOnAttackMonster(target); } }); } actionChain.EnqueueChain(); // TODO: figure out exact speed / delay formula var meleeDelay = ThreadSafeRandom.Next(MeleeDelayMin, MeleeDelayMax); NextAttackTime = Timers.RunningTime + animLength + meleeDelay; return(animLength); }
public override void OnCollideObject(WorldObject _target) { //Console.WriteLine($"{Name}.OnCollideObject({_target.Name})"); var player = ProjectileSource as Player; if (player != null) { player.LastHitSpellProjectile = Spell; } // ensure valid creature target // non-target objects will be excluded beforehand from collision detection var target = _target as Creature; if (target == null || player == target) { OnCollideEnvironment(); return; } ProjectileImpact(); // for untargeted multi-projectile war spells launched by monsters, // ensure monster can damage target var sourceCreature = ProjectileSource as Creature; if (sourceCreature != null && !sourceCreature.CanDamage(target)) { return; } // if player target, ensure matching PK status var targetPlayer = target as Player; var pkError = CheckPKStatusVsTarget(player, targetPlayer, Spell); if (pkError != null) { if (player != null) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, pkError[0], target.Name)); } if (targetPlayer != null) { targetPlayer.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(targetPlayer.Session, pkError[1], ProjectileSource.Name)); } return; } var critical = false; var damage = CalculateDamage(ProjectileSource, target, ref critical); // null damage -> target resisted; damage of -1 -> target already dead if (damage != null && damage != -1) { // handle void magic DoTs: // instead of instant damage, add DoT to target's enchantment registry if (Spell.School == MagicSchool.VoidMagic && Spell.Duration > 0) { var dot = ProjectileSource.CreateEnchantment(target, ProjectileSource, Spell); if (dot.Message != null && player != null) { player.Session.Network.EnqueueSend(dot.Message); } // corruption / corrosion playscript? //target.EnqueueBroadcast(new GameMessageScript(target.Guid, ACE.Entity.Enum.PlayScript.HealthDownVoid)); //target.EnqueueBroadcast(new GameMessageScript(target.Guid, ACE.Entity.Enum.PlayScript.DirtyFightingDefenseDebuff)); } else { DamageTarget(target, damage, critical); } if (player != null) { Proficiency.OnSuccessUse(player, player.GetCreatureSkill(Spell.School), Spell.PowerMod); } // handle target procs // note that for untargeted multi-projectile spells, // ProjectileTarget will be null here, so procs will not apply if (sourceCreature != null && ProjectileTarget != null) { sourceCreature.TryProcEquippedItems(target, false); } } // also called on resist if (player != null && targetPlayer == null) { player.OnAttackMonster(target); } }
public static void UseUnlocker(Player player, WorldObject unlocker, WorldObject target) { ActionChain chain = new ActionChain(); chain.AddAction(player, () => { if (unlocker.WeenieType == WeenieType.Lockpick && player.Skills[Skill.Lockpick].AdvancementClass != SkillAdvancementClass.Trained && player.Skills[Skill.Lockpick].AdvancementClass != SkillAdvancementClass.Specialized) { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouArentTrainedInLockpicking)); return; } if (target is Lock @lock) { UnlockResults result = UnlockResults.IncorrectKey; var difficulty = 0; if (unlocker.WeenieType == WeenieType.Lockpick) { result = @lock.Unlock(player.Skills[Skill.Lockpick].Current, ref difficulty); } else if (unlocker is Key woKey) { if (target is Door woDoor) { if (woDoor.LockCode == "") // the door isn't to be opened with keys { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); return; } } result = @lock.Unlock(woKey.KeyCode); } switch (result) { case UnlockResults.UnlockSuccess: if (unlocker.WeenieType == WeenieType.Lockpick) { player.HandleActionApplySoundEffect(Sound.Lockpicking); // Sound.Lockpicking doesn't work via EnqueueBroadcastSound for some reason. } player.Session.Network.EnqueueSend(new GameMessageSystemChat($"You have successfully picked the lock! It is now unlocked.", ChatMessageType.Craft)); var lockpickSkill = player.GetCreatureSkill(Skill.Lockpick); Proficiency.OnSuccessUse(player, lockpickSkill, (uint)difficulty); ConsumeUnlocker(player, unlocker); break; case UnlockResults.Open: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockWhatIsOpen)); break; case UnlockResults.AlreadyUnlocked: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.LockAlreadyUnlocked)); break; case UnlockResults.PickLockFailed: target.EnqueueBroadcast(new GameMessageSound(target.Guid, Sound.PicklockFail, 1.0f)); ConsumeUnlocker(player, unlocker); break; case UnlockResults.CannotBePicked: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); break; case UnlockResults.IncorrectKey: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.KeyDoesntFitThisLock)); break; } } else { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); } }); chain.EnqueueChain(); }
/// <summary> /// Method used for handling player targeted spell casts /// </summary> public void CreatePlayerSpell(WorldObject target, TargetCategory targetCategory, uint spellId) { var player = this; var creatureTarget = target as Creature; if (player.IsBusy == true) { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YoureTooBusy)); return; } player.IsBusy = true; var spell = new Spell(spellId); if (spell.NotFound) { if (spell._spellBase == null) { Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, $"SpellId {spellId} Invalid.")); Session.Network.EnqueueSend(new GameEventUseDone(Session, WeenieError.None)); } else { Session.Network.EnqueueSend(new GameMessageSystemChat($"{spell.Name} spell not implemented, yet!", ChatMessageType.System)); Session.Network.EnqueueSend(new GameEventUseDone(Session, WeenieError.MagicInvalidSpellType)); } player.IsBusy = false; return; } if (IsInvalidTarget(spell, target)) { player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, $"{spell.Name} cannot be cast on {target.Name}.")); player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.None)); player.IsBusy = false; return; } // if casting implement has spell built in, // use spellcraft from the item, instead of player's magic skill? var caster = GetEquippedWand(); var isWeaponSpell = IsWeaponSpell(spell); // Grab player's skill level in the spell's Magic School var magicSkill = player.GetCreatureSkill(spell.School).Current; if (isWeaponSpell && caster.ItemSpellcraft != null) { magicSkill = (uint)caster.ItemSpellcraft; } if (targetCategory == TargetCategory.WorldObject) { if (target.Guid != Guid) { var targetLoc = target; if (targetLoc.WielderId != null) { targetLoc = CurrentLandblock?.GetObject(targetLoc.WielderId.Value); } float distanceTo = Location.Distance2D(targetLoc.Location); if (distanceTo > spell.BaseRangeConstant + magicSkill * spell.BaseRangeMod) { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.None), new GameMessageSystemChat($"Target is out of range!", ChatMessageType.Magic)); player.IsBusy = false; return; } } } var difficulty = spell.Power; // is this needed? should talismans remain the same, regardless of player spell formula? spell.Formula.GetPlayerFormula(player); var castingPreCheckStatus = CastingPreCheckStatus.CastFailed; if (magicSkill > 0 && magicSkill >= (int)difficulty - 50) { var chance = 1.0f - SkillCheck.GetMagicSkillChance((int)magicSkill, (int)difficulty); var rng = ThreadSafeRandom.Next(0.0f, 1.0f); if (chance < rng || isWeaponSpell) { castingPreCheckStatus = CastingPreCheckStatus.Success; } } // limit casting time between war and void if (spell.School == MagicSchool.VoidMagic && LastSuccessCast_School == MagicSchool.WarMagic || spell.School == MagicSchool.WarMagic && LastSuccessCast_School == MagicSchool.VoidMagic) { // roll each time? var timeLimit = ThreadSafeRandom.Next(3.0f, 5.0f); if (Time.GetUnixTime() - LastSuccessCast_Time < timeLimit) { var curType = spell.School == MagicSchool.WarMagic ? "War" : "Void"; var prevType = LastSuccessCast_School == MagicSchool.VoidMagic ? "Nether" : "Elemental"; Session.Network.EnqueueSend(new GameMessageSystemChat($"The {prevType} energies permeating your blood cause this {curType} magic to fail.", ChatMessageType.Magic)); castingPreCheckStatus = CastingPreCheckStatus.CastFailed; } } // Calculate mana usage uint manaUsed = CalculateManaUsage(player, spell, target); var currentMana = player.Mana.Current; if (isWeaponSpell) { currentMana = (uint)(caster.ItemCurMana ?? 0); } if (manaUsed > currentMana) { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouDontHaveEnoughManaToCast)); IsBusy = false; // delay? return; } // begin spellcasting Proficiency.OnSuccessUse(player, player.GetCreatureSkill(Skill.ManaConversion), spell.PowerMod); if (!isWeaponSpell) { player.UpdateVitalDelta(player.Mana, -(int)manaUsed); } else { caster.ItemCurMana -= (int)manaUsed; } spell.Formula.GetPlayerFormula(player); string spellWords = spell._spellBase.GetSpellWords(DatManager.PortalDat.SpellComponentsTable); if (spellWords != null && !isWeaponSpell) { EnqueueBroadcast(new GameMessageCreatureMessage(spellWords, Name, Guid.Full, ChatMessageType.Spellcasting)); } var spellChain = new ActionChain(); var castSpeed = 2.0f; // hardcoded for player spell casting? var startPos = new Position(Location); // do wind-up gestures: fastcast has no windup (creature enchantments) if (!spell.Flags.HasFlag(SpellFlags.FastCast) && !isWeaponSpell) { // note that ACE is currently sending the windup motion and the casting gesture // at the same time. the client is automatically queueing these animations to run at the correct time. foreach (var windupGesture in spell.Formula.WindupGestures) { spellChain.AddAction(this, () => { var motionWindUp = new Motion(MotionStance.Magic, windupGesture, castSpeed); EnqueueBroadcastMotion(motionWindUp); }); } } // cast spell spellChain.AddAction(this, () => { var castGesture = spell.Formula.CastGesture; if (isWeaponSpell && caster.UseUserAnimation != 0) { castGesture = caster.UseUserAnimation; } var motionCastSpell = new Motion(MotionStance.Magic, castGesture, castSpeed); EnqueueBroadcastMotion(motionCastSpell); }); var castingDelay = spell.Formula.GetCastTime(MotionTableId, castSpeed, isWeaponSpell); spellChain.AddDelaySeconds(castingDelay); bool movedTooFar = false; spellChain.AddAction(this, () => { if (!isWeaponSpell) { TryBurnComponents(spell); } // check windup move distance cap var endPos = new Position(Location); var dist = startPos.DistanceTo(endPos); if (dist > Windup_MaxMove) { castingPreCheckStatus = CastingPreCheckStatus.CastFailed; movedTooFar = true; } var pk_error = CheckPKStatusVsTarget(player, target, spell); if (pk_error != null) { castingPreCheckStatus = CastingPreCheckStatus.InvalidPKStatus; } var useDone = WeenieError.None; switch (castingPreCheckStatus) { case CastingPreCheckStatus.Success: if ((spell.Flags & SpellFlags.FellowshipSpell) == 0) { CreatePlayerSpell(target, spell); } else { var fellows = GetFellowshipTargets(); foreach (var fellow in fellows) { CreatePlayerSpell(fellow, spell); } } break; case CastingPreCheckStatus.InvalidPKStatus: if (spell.NumProjectiles > 0) { switch (spell.School) { case MagicSchool.WarMagic: WarMagic(target, spell); break; case MagicSchool.VoidMagic: VoidMagic(target, spell); break; case MagicSchool.LifeMagic: LifeMagic(target, spell, out uint damage, out bool critical, out var enchantmentStatus); break; }
public override void OnCollideObject(WorldObject _target) { //Console.WriteLine($"{Name}.OnCollideObject({_target.Name})"); var player = ProjectileSource as Player; if (player != null) { player.LastHitSpellProjectile = Spell; } // ensure valid creature target // non-target objects will be excluded beforehand from collision detection var target = _target as Creature; if (target == null) { OnCollideEnvironment(); return; } ProjectileImpact(); // if player target, ensure matching PK status var targetPlayer = target as Player; var checkPKStatusVsTarget = CheckPKStatusVsTarget(player, targetPlayer, Spell); if (checkPKStatusVsTarget != null && checkPKStatusVsTarget == false) { player.Session.Network.EnqueueSend(new GameEventWeenieError(player.Session, WeenieError.InvalidPkStatus)); return; } var critical = false; var damage = CalculateDamage(ProjectileSource, target, ref critical); // null damage -> target resisted; damage of -1 -> target already dead if (damage != null && damage != -1) { // handle void magic DoTs: // instead of instant damage, add DoT to target's enchantment registry if (Spell.School == MagicSchool.VoidMagic && Spell.Duration > 0) { var dot = ProjectileSource.CreateEnchantment(target, ProjectileSource, Spell); if (dot.message != null && player != null) { player.Session.Network.EnqueueSend(dot.message); } // corruption / corrosion playscript? //target.EnqueueBroadcast(new GameMessageScript(target.Guid, ACE.Entity.Enum.PlayScript.HealthDownVoid)); //target.EnqueueBroadcast(new GameMessageScript(target.Guid, ACE.Entity.Enum.PlayScript.DirtyFightingDefenseDebuff)); } else { DamageTarget(target, damage, critical); } if (player != null) { Proficiency.OnSuccessUse(player, player.GetCreatureSkill(Spell.School), Spell.PowerMod); } } // also called on resist if (player != null && targetPlayer == null) { player.OnAttackMonster(target); } }
public override void OnCollideObject(WorldObject target) { //Console.WriteLine($"{Name}.OnCollideObject({target.Name})"); var player = ProjectileSource as Player; if (Info != null && player != null && player.DebugSpell) { player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name}.OnCollideObject({target?.Name} ({target?.Guid}))", ChatMessageType.Broadcast)); player.Session.Network.EnqueueSend(new GameMessageSystemChat(Info.ToString(), ChatMessageType.Broadcast)); } ProjectileImpact(); // ensure valid creature target var creatureTarget = target as Creature; if (creatureTarget == null || target == ProjectileSource) { return; } if (player != null) { player.LastHitSpellProjectile = Spell; } // ensure caster can damage target var sourceCreature = ProjectileSource as Creature; if (sourceCreature != null && !sourceCreature.CanDamage(creatureTarget)) { return; } // if player target, ensure matching PK status var targetPlayer = creatureTarget as Player; var pkError = ProjectileSource?.CheckPKStatusVsTarget(creatureTarget, Spell); if (pkError != null) { if (player != null) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, pkError[0], creatureTarget.Name)); } if (targetPlayer != null) { targetPlayer.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(targetPlayer.Session, pkError[1], ProjectileSource.Name)); } return; } var critical = false; var critDefended = false; var overpower = false; var damage = CalculateDamage(ProjectileSource, Caster, creatureTarget, ref critical, ref critDefended, ref overpower); if (damage != null) { // handle void magic DoTs: // instead of instant damage, add DoT to target's enchantment registry if (Spell.School == MagicSchool.VoidMagic && Spell.Duration > 0) { var dot = ProjectileSource.CreateEnchantment(creatureTarget, ProjectileSource, Spell); if (dot.Message != null && player != null) { player.Session.Network.EnqueueSend(dot.Message); } // corruption / corrosion playscript? //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.HealthDownVoid)); //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.DirtyFightingDefenseDebuff)); } else { DamageTarget(creatureTarget, damage.Value, critical, critDefended, overpower); } if (player != null) { Proficiency.OnSuccessUse(player, player.GetCreatureSkill(Spell.School), Spell.PowerMod); } // handle target procs // note that for untargeted multi-projectile spells, // ProjectileTarget will be null here, so procs will not apply if (sourceCreature != null && ProjectileTarget != null) { // TODO figure out why cross-landblock group operations are happening here. We shouldn't need this code Mag-nus 2021-02-09 bool threadSafe = true; if (LandblockManager.CurrentlyTickingLandblockGroupsMultiThreaded) { // Ok... if we got here, we're likely in the parallel landblock physics processing. if (sourceCreature.CurrentLandblock == null || creatureTarget.CurrentLandblock == null || sourceCreature.CurrentLandblock.CurrentLandblockGroup != creatureTarget.CurrentLandblock.CurrentLandblockGroup) { threadSafe = false; } } if (threadSafe) { // This can result in spell projectiles being added to either sourceCreature or creatureTargets landblock. sourceCreature.TryProcEquippedItems(creatureTarget, false); } else { // sourceCreature and creatureTarget are now in different landblock groups. // What has likely happened is that sourceCreature sent a projectile toward creatureTarget. Before impact, sourceCreature was teleported away. // To perform this fully thread safe, we would enqueue the work onto worldManager. // WorldManager.EnqueueAction(new ActionEventDelegate(() => sourceCreature.TryProcEquippedItems(creatureTarget, false))); // But, to keep it simple, we will just ignore it and not bother with TryProcEquippedItems for this particular impact. } } } // also called on resist if (player != null && targetPlayer == null) { player.OnAttackMonster(creatureTarget); } if (player == null && targetPlayer == null) { // check for faction combat if (sourceCreature != null && creatureTarget != null && (sourceCreature.AllowFactionCombat(creatureTarget) || sourceCreature.PotentialFoe(creatureTarget))) { sourceCreature.MonsterOnAttackMonster(creatureTarget); } } }
public static void OnCollideObject(WorldObject worldObject, WorldObject target) { if (!worldObject.PhysicsObj.is_active()) { return; } //Console.WriteLine($"Projectile.OnCollideObject - {WorldObject.Name} ({WorldObject.Guid}) -> {target.Name} ({target.Guid})"); if (worldObject.ProjectileTarget == null || worldObject.ProjectileTarget != target) { //Console.WriteLine("Unintended projectile target! (should be " + ProjectileTarget.Guid.Full.ToString("X8") + " - " + ProjectileTarget.Name + ")"); OnCollideEnvironment(worldObject); return; } // take damage var sourceCreature = worldObject.ProjectileSource as Creature; var sourcePlayer = worldObject.ProjectileSource as Player; var targetCreature = target as Creature; DamageEvent damageEvent = null; if (targetCreature != null) { if (sourcePlayer != null) { // player damage monster or player damageEvent = sourcePlayer.DamageTarget(targetCreature, worldObject); if (damageEvent != null && damageEvent.HasDamage) { worldObject.EnqueueBroadcast(new GameMessageSound(worldObject.Guid, Sound.Collision, 1.0f)); } } else if (sourceCreature != null && sourceCreature.AttackTarget != null) { var targetPlayer = sourceCreature.AttackTarget as Player; damageEvent = DamageEvent.CalculateDamage(sourceCreature, targetCreature, worldObject); if (targetPlayer != null) { // monster damage player if (damageEvent.HasDamage) { targetPlayer.TakeDamage(sourceCreature, damageEvent); // blood splatter? if (damageEvent.ShieldMod != 1.0f) { var shieldSkill = targetPlayer.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(targetPlayer, shieldSkill, shieldSkill.Current); // ?? } } else { targetPlayer.OnEvade(sourceCreature, CombatType.Missile); } } else { // monster damage pet if (damageEvent.HasDamage) { targetCreature.TakeDamage(sourceCreature, damageEvent.DamageType, damageEvent.Damage); // blood splatter? } if (!(targetCreature is CombatPet)) { // faction mobs and foetype sourceCreature.MonsterOnAttackMonster(targetCreature); } } } // handle target procs if (damageEvent != null && damageEvent.HasDamage) { // Ok... if we got here, we're likely in the parallel landblock physics processing. // We're currently on the thread for worldObject, but we're wanting to perform some work on sourceCreature which can result in a new spell being created // and added to the sourceCreature's current landblock, which, could be on a separate thread. // Any chance of a cross landblock group work (and thus cross thread), should be enqueued onto the target object to maintain thread safety. if (sourceCreature.CurrentLandblock == null || sourceCreature.CurrentLandblock == worldObject.CurrentLandblock) { sourceCreature.TryProcEquippedItems(targetCreature, false); } else { sourceCreature.EnqueueAction(new ActionEventDelegate(() => sourceCreature.TryProcEquippedItems(targetCreature, false))); } } } worldObject.CurrentLandblock?.RemoveWorldObject(worldObject.Guid, showError: !worldObject.PhysicsObj.entering_world); worldObject.PhysicsObj.set_active(false); worldObject.HitMsg = true; }
public static void OnCollideObject(WorldObject worldObject, WorldObject target) { if (!worldObject.PhysicsObj.is_active()) { return; } //Console.WriteLine($"Projectile.OnCollideObject - {WorldObject.Name} ({WorldObject.Guid}) -> {target.Name} ({target.Guid})"); if (worldObject.ProjectileTarget == null || worldObject.ProjectileTarget != target) { //Console.WriteLine("Unintended projectile target! (should be " + ProjectileTarget.Guid.Full.ToString("X8") + " - " + ProjectileTarget.Name + ")"); OnCollideEnvironment(worldObject); return; } // take damage var sourceCreature = worldObject.ProjectileSource as Creature; var sourcePlayer = worldObject.ProjectileSource as Player; var targetCreature = target as Creature; DamageEvent damageEvent = null; if (targetCreature != null) { if (sourcePlayer != null) { // player damage monster or player damageEvent = sourcePlayer.DamageTarget(targetCreature, worldObject); if (damageEvent != null && damageEvent.HasDamage) { worldObject.EnqueueBroadcast(new GameMessageSound(worldObject.Guid, Sound.Collision, 1.0f)); } } else if (sourceCreature != null && sourceCreature.AttackTarget != null) { // todo: clean this up var targetPlayer = sourceCreature.AttackTarget as Player; damageEvent = DamageEvent.CalculateDamage(sourceCreature, targetCreature, worldObject); if (targetPlayer != null) { // monster damage player if (damageEvent.HasDamage) { targetPlayer.TakeDamage(sourceCreature, damageEvent); // blood splatter? if (damageEvent.ShieldMod != 1.0f) { var shieldSkill = targetPlayer.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(targetPlayer, shieldSkill, shieldSkill.Current); // ?? } // handle Dirty Fighting if (sourceCreature.GetCreatureSkill(Skill.DirtyFighting).AdvancementClass >= SkillAdvancementClass.Trained) { sourceCreature.FightDirty(targetPlayer, damageEvent.Weapon); } } else { targetPlayer.OnEvade(sourceCreature, CombatType.Missile); } } else { // monster damage pet if (damageEvent.HasDamage) { targetCreature.TakeDamage(sourceCreature, damageEvent.DamageType, damageEvent.Damage); // blood splatter? // handle Dirty Fighting if (sourceCreature.GetCreatureSkill(Skill.DirtyFighting).AdvancementClass >= SkillAdvancementClass.Trained) { sourceCreature.FightDirty(targetCreature, damageEvent.Weapon); } } if (!(targetCreature is CombatPet)) { // faction mobs and foetype sourceCreature.MonsterOnAttackMonster(targetCreature); } } } // handle target procs if (damageEvent != null && damageEvent.HasDamage) { bool threadSafe = true; if (LandblockManager.CurrentlyTickingLandblockGroupsMultiThreaded) { // Ok... if we got here, we're likely in the parallel landblock physics processing. if (worldObject.CurrentLandblock == null || sourceCreature.CurrentLandblock == null || targetCreature.CurrentLandblock == null || worldObject.CurrentLandblock.CurrentLandblockGroup != sourceCreature.CurrentLandblock.CurrentLandblockGroup || sourceCreature.CurrentLandblock.CurrentLandblockGroup != targetCreature.CurrentLandblock.CurrentLandblockGroup) { threadSafe = false; } } if (threadSafe) { // This can result in spell projectiles being added to either sourceCreature or targetCreature landblock. // worldObject is hitting targetCreature, so they should almost always be in the same landblock worldObject.TryProcEquippedItems(sourceCreature, targetCreature, false, worldObject.ProjectileLauncher); } else { // sourceCreature and creatureTarget are now in different landblock groups. // What has likely happened is that sourceCreature sent a projectile toward creatureTarget. Before impact, sourceCreature was teleported away. // To perform this fully thread safe, we would enqueue the work onto worldManager. // WorldManager.EnqueueAction(new ActionEventDelegate(() => sourceCreature.TryProcEquippedItems(targetCreature, false))); // But, to keep it simple, we will just ignore it and not bother with TryProcEquippedItems for this particular impact. } } } worldObject.CurrentLandblock?.RemoveWorldObject(worldObject.Guid, showError: !worldObject.PhysicsObj.entering_world); worldObject.PhysicsObj.set_active(false); worldObject.HitMsg = true; }
public static void OnCollideObject(WorldObject worldObject, WorldObject target) { if (!worldObject.PhysicsObj.is_active()) { return; } //Console.WriteLine($"Projectile.OnCollideObject - {WorldObject.Name} ({WorldObject.Guid}) -> {target.Name} ({target.Guid})"); if (worldObject.ProjectileTarget == null || worldObject.ProjectileTarget != target) { //Console.WriteLine("Unintended projectile target! (should be " + ProjectileTarget.Guid.Full.ToString("X8") + " - " + ProjectileTarget.Name + ")"); OnCollideEnvironment(worldObject); return; } // take damage var sourceCreature = worldObject.ProjectileSource as Creature; var sourcePlayer = worldObject.ProjectileSource as Player; var targetCreature = target as Creature; DamageEvent damageEvent = null; if (targetCreature != null) { if (sourcePlayer != null) { var weapon = sourcePlayer.GetEquippedMissileWeapon(); if (weapon != null && weapon.NumTimesTinkered > 0) { var maxdmg = 0; if (weapon.W_WeaponType == WeaponType.Bow) { maxdmg = (int)PropertyManager.GetDouble("bow_damage").Item; } else if (weapon.W_WeaponType == WeaponType.Crossbow) { maxdmg += (int)PropertyManager.GetDouble("xbow_damage").Item; } else if (weapon.W_WeaponType == WeaponType.Thrown) { maxdmg += (int)PropertyManager.GetDouble("thrown_damage").Item; } maxdmg *= weapon.NumTimesTinkered; var dmgrng = maxdmg / 2; worldObject.Damage += dmgrng; } // player damage monster or player damageEvent = sourcePlayer.DamageTarget(targetCreature, worldObject); var targetPlayer = targetCreature as Player; if (targetPlayer != null) { if (damageEvent != null && damageEvent.HasDamage) { var damageCap = PropertyManager.GetLong("pvp_damage_cap").Item; if (damageEvent.Damage > damageCap) { damageEvent.Damage = damageCap; } } } if (damageEvent != null && damageEvent.HasDamage) { worldObject.EnqueueBroadcast(new GameMessageSound(worldObject.Guid, Sound.Collision, 1.0f)); } } else if (sourceCreature != null && sourceCreature.AttackTarget != null) { var targetPlayer = sourceCreature.AttackTarget as Player; damageEvent = DamageEvent.CalculateDamage(sourceCreature, targetCreature, worldObject); if (targetPlayer != null) { // monster damage player if (damageEvent.HasDamage) { targetPlayer.TakeDamage(sourceCreature, damageEvent); // blood splatter? if (damageEvent.ShieldMod != 1.0f) { var shieldSkill = targetPlayer.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(targetPlayer, shieldSkill, shieldSkill.Current); // ?? } } else { targetPlayer.OnEvade(sourceCreature, CombatType.Missile); } } else { // monster damage pet if (damageEvent.HasDamage) { targetCreature.TakeDamage(sourceCreature, damageEvent.DamageType, damageEvent.Damage); // blood splatter? } if (!(targetCreature is CombatPet)) { // faction mobs and foetype sourceCreature.MonsterOnAttackMonster(targetCreature); } } } // handle target procs if (damageEvent != null && damageEvent.HasDamage) { bool threadSafe = true; if (LandblockManager.CurrentlyTickingLandblockGroupsMultiThreaded) { // Ok... if we got here, we're likely in the parallel landblock physics processing. if (sourceCreature.CurrentLandblock == null || targetCreature.CurrentLandblock == null || sourceCreature.CurrentLandblock.CurrentLandblockGroup != targetCreature.CurrentLandblock.CurrentLandblockGroup) { threadSafe = false; } } if (threadSafe) { // This can result in spell projectiles being added to either sourceCreature or targetCreature landblock. sourceCreature.TryProcEquippedItems(targetCreature, false); } else { // sourceCreature and creatureTarget are now in different landblock groups. // What has likely happened is that sourceCreature sent a projectile toward creatureTarget. Before impact, sourceCreature was teleported away. // To perform this fully thread safe, we would enqueue the work onto worldManager. // WorldManager.EnqueueAction(new ActionEventDelegate(() => sourceCreature.TryProcEquippedItems(targetCreature, false))); // But, to keep it simple, we will just ignore it and not bother with TryProcEquippedItems for this particular impact. } } } worldObject.CurrentLandblock?.RemoveWorldObject(worldObject.Guid, showError: !worldObject.PhysicsObj.entering_world); worldObject.PhysicsObj.set_active(false); worldObject.HitMsg = true; }
private void CreatePlayerSpell(WorldObject target, Spell spell) { var targetCreature = target as Creature; var targetPlayer = target as Player; bool targetDeath; var enchantmentStatus = new EnchantmentStatus(spell); LastSuccessCast_School = spell.School; LastSuccessCast_Time = Time.GetUnixTime(); switch (spell.School) { case MagicSchool.WarMagic: WarMagic(target, spell); break; case MagicSchool.VoidMagic: VoidMagic(target, spell); break; case MagicSchool.CreatureEnchantment: if (targetPlayer == null) { OnAttackMonster(targetCreature); } if (spell.IsHarmful) { var resisted = ResistSpell(target, spell); if (resisted == true) { break; } if (resisted == null) { log.Error("Something went wrong with the Magic resistance check"); break; } } if (targetCreature != null && targetCreature.NonProjectileMagicImmune) { Session.Network.EnqueueSend(new GameMessageSystemChat($"You fail to affect {targetCreature.Name} with {spell.Name}", ChatMessageType.Magic)); break; } EnqueueBroadcast(new GameMessageScript(target.Guid, spell.TargetEffect, spell.Formula.Scale)); enchantmentStatus = CreatureMagic(target, spell); if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } if (spell.IsHarmful) { if (targetCreature != null) { Proficiency.OnSuccessUse(this, GetCreatureSkill(Skill.CreatureEnchantment), targetCreature.GetCreatureSkill(Skill.MagicDefense).Current); } // handle target procs if (targetCreature != null && targetCreature != this) { TryProcEquippedItems(targetCreature, false); } if (targetPlayer != null) { UpdatePKTimers(this, targetPlayer); } } else { Proficiency.OnSuccessUse(this, GetCreatureSkill(Skill.CreatureEnchantment), spell.PowerMod); } break; case MagicSchool.LifeMagic: if (targetPlayer == null) { OnAttackMonster(targetCreature); } if (spell.MetaSpellType != SpellType.LifeProjectile) { if (spell.IsHarmful) { var resisted = ResistSpell(target, spell); if (resisted == true) { break; } if (resisted == null) { log.Error("Something went wrong with the Magic resistance check"); break; } } if (targetCreature != null && targetCreature.NonProjectileMagicImmune) { Session.Network.EnqueueSend(new GameMessageSystemChat($"You fail to affect {targetCreature.Name} with {spell.Name}", ChatMessageType.Magic)); break; } } if (target != null) { EnqueueBroadcast(new GameMessageScript(target.Guid, spell.TargetEffect, spell.Formula.Scale)); } targetDeath = LifeMagic(spell, out uint damage, out bool critical, out enchantmentStatus, target); if (spell.MetaSpellType != SpellType.LifeProjectile) { if (spell.IsHarmful) { if (targetCreature != null) { Proficiency.OnSuccessUse(this, GetCreatureSkill(Skill.LifeMagic), targetCreature.GetCreatureSkill(Skill.MagicDefense).Current); } // handle target procs if (targetCreature != null && targetCreature != this) { TryProcEquippedItems(targetCreature, false); } if (targetPlayer != null) { UpdatePKTimers(this, targetPlayer); } } else { Proficiency.OnSuccessUse(this, GetCreatureSkill(Skill.LifeMagic), spell.PowerMod); } } if (targetDeath == true) { targetCreature.OnDeath(new DamageHistoryInfo(this), DamageType.Health, false); targetCreature.Die(); } else { if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } } break; case MagicSchool.ItemEnchantment: // if negative item spell, can be resisted by the wielder if (spell.IsHarmful) { var targetResist = targetCreature; if (targetResist == null && target?.WielderId != null) { targetResist = CurrentLandblock?.GetObject(target.WielderId.Value) as Creature; } if (targetResist != null) { var resisted = ResistSpell(targetResist, spell); if (resisted == true) { break; } if (resisted == null) { log.Error("Something went wrong with the Magic resistance check"); break; } } } if (spell.IsImpenBaneType) { // impen / bane / brittlemail / lure // a lot of these will already be filtered out by IsInvalidTarget() if (targetCreature == null) { // targeting an individual item / wo enchantmentStatus = ItemMagic(target, spell); if (target != null) { EnqueueBroadcast(new GameMessageScript(target.Guid, spell.TargetEffect, spell.Formula.Scale)); } if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } } else { // targeting a creature if (targetPlayer == this) { // targeting self var items = EquippedObjects.Values.Where(i => (i.WeenieType == WeenieType.Clothing || i.IsShield) && i.IsEnchantable); foreach (var item in items) { enchantmentStatus = ItemMagic(item, spell); if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } } if (items.Count() > 0) { EnqueueBroadcast(new GameMessageScript(Guid, spell.TargetEffect, spell.Formula.Scale)); } } else { // targeting another player or monster var item = targetCreature.EquippedObjects.Values.FirstOrDefault(i => i.IsShield && i.IsEnchantable); if (item != null) { enchantmentStatus = ItemMagic(item, spell); EnqueueBroadcast(new GameMessageScript(item.Guid, spell.TargetEffect, spell.Formula.Scale)); if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } } else { // 'fails to affect'? if (targetCreature != null) { Session.Network.EnqueueSend(new GameMessageSystemChat($"You fail to affect {targetCreature.Name} with {spell.Name}", ChatMessageType.Magic)); } if (targetPlayer != null && !targetPlayer.SquelchManager.Squelches.Contains(this, ChatMessageType.Magic)) { targetPlayer.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} fails to affect you with {spell.Name}", ChatMessageType.Magic)); } } } } } else if (spell.IsOtherNegativeRedirectable) { // blood loather, spirit loather, lure blade, turn blade, leaden weapon, hermetic void if (targetCreature == null) { // targeting an individual item / wo enchantmentStatus = ItemMagic(target, spell); if (target != null) { EnqueueBroadcast(new GameMessageScript(target.Guid, spell.TargetEffect, spell.Formula.Scale)); } if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } } else { // targeting a creature, try to redirect to primary weapon var weapon = targetCreature.GetEquippedWeapon() ?? targetCreature.GetEquippedWand(); if (weapon != null && weapon.IsEnchantable) { enchantmentStatus = ItemMagic(weapon, spell); EnqueueBroadcast(new GameMessageScript(weapon.Guid, spell.TargetEffect, spell.Formula.Scale)); if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } } else { // 'fails to affect'? Session.Network.EnqueueSend(new GameMessageSystemChat($"You fail to affect {targetCreature.Name} with {spell.Name}", ChatMessageType.Magic)); if (targetPlayer != null && !targetPlayer.SquelchManager.Squelches.Contains(this, ChatMessageType.Magic)) { targetPlayer.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} fails to affect you with {spell.Name}", ChatMessageType.Magic)); } } } } else { // all other item spells, cast directly on target enchantmentStatus = ItemMagic(target, spell); if (target != null) { EnqueueBroadcast(new GameMessageScript(target.Guid, spell.TargetEffect, spell.Formula.Scale)); } if (enchantmentStatus.Message != null) { Session.Network.EnqueueSend(enchantmentStatus.Message); } } // use target resistance? Proficiency.OnSuccessUse(this, GetCreatureSkill(Skill.ItemEnchantment), spell.PowerMod); if (spell.IsHarmful) { var playerRedirect = targetPlayer; if (playerRedirect == null && target?.WielderId != null) { playerRedirect = CurrentLandblock?.GetObject(target.WielderId.Value) as Player; } if (playerRedirect != null) { UpdatePKTimers(this, playerRedirect); } } break; } }
/// <summary> /// Performs a melee attack for the monster /// </summary> /// <returns>The length in seconds for the attack animation</returns> public float MeleeAttack() { var target = AttackTarget as Creature; if (target == null || !target.IsAlive) { Sleep(); return(0.0f); } // choose a random combat maneuver var maneuver = GetCombatManeuver(); if (maneuver == null) { Console.WriteLine($"Combat maneuver null! Stance {CurrentMotionState.Stance}, MotionTable {MotionTableId:X8}"); return(0.0f); } AttackHeight = maneuver.AttackHeight; // select random body part @ current attack height var bodyPart = BodyParts.GetBodyPart(AttackHeight.Value); DoSwingMotion(AttackTarget, maneuver, out float animLength); PhysicsObj.stick_to_object(AttackTarget.PhysicsObj.ID); var actionChain = new ActionChain(); actionChain.AddDelaySeconds(animLength / 3.0f); // TODO: get attack frame? actionChain.AddAction(this, () => { if (AttackTarget == null) { return; } var critical = false; var damageType = DamageType.Undef; var shieldMod = 1.0f; var damage = CalculateDamage(ref damageType, maneuver, bodyPart, ref critical, ref shieldMod); var player = AttackTarget as Player; if (damage > 0.0f) { player.TakeDamage(this, damageType, damage, bodyPart, critical); if (shieldMod != 1.0f) { var shieldSkill = player.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(player, shieldSkill, shieldSkill.Current); // ? } } else { player.OnEvade(this, AttackType.Melee); } }); actionChain.EnqueueChain(); // TODO: figure out exact speed / delay formula var meleeDelay = Physics.Common.Random.RollDice(MeleeDelayMin, MeleeDelayMax); NextAttackTime = Timers.RunningTime + animLength + meleeDelay;; return(animLength); }
public static void UseUnlocker(Player player, WorldObject unlocker, WorldObject target) { ActionChain chain = new ActionChain(); chain.AddAction(player, () => { if (unlocker.WeenieType == WeenieType.Lockpick && player.Skills[Skill.Lockpick].AdvancementClass != SkillAdvancementClass.Trained && player.Skills[Skill.Lockpick].AdvancementClass != SkillAdvancementClass.Specialized) { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouArentTrainedInLockpicking)); return; } if (target is Lock @lock) { UnlockResults result = UnlockResults.IncorrectKey; var difficulty = 0; if (unlocker.WeenieType == WeenieType.Lockpick) { var effectiveLockpickSkill = GetEffectiveLockpickSkill(player, unlocker); result = @lock.Unlock(player.Guid.Full, effectiveLockpickSkill, ref difficulty); } else if (unlocker is Key woKey) { if (target is Door woDoor) { if (woDoor.LockCode == "") // the door isn't to be opened with keys { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); return; } } result = @lock.Unlock(player.Guid.Full, woKey); } switch (result) { case UnlockResults.UnlockSuccess: if (unlocker.WeenieType == WeenieType.Lockpick) { // the source guid for this sound must be the player, else the sound will not play // which differs from PicklockFail and LockSuccess being in the target sound table player.EnqueueBroadcast(new GameMessageSound(player.Guid, Sound.Lockpicking, 1.0f)); var lockpickSkill = player.GetCreatureSkill(Skill.Lockpick); Proficiency.OnSuccessUse(player, lockpickSkill, difficulty); } ConsumeUnlocker(player, unlocker, target, true); break; case UnlockResults.Open: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockWhatIsOpen)); break; case UnlockResults.AlreadyUnlocked: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.LockAlreadyUnlocked)); break; case UnlockResults.PickLockFailed: target.EnqueueBroadcast(new GameMessageSound(target.Guid, Sound.PicklockFail, 1.0f)); ConsumeUnlocker(player, unlocker, target, false); break; case UnlockResults.CannotBePicked: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); break; case UnlockResults.IncorrectKey: player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.KeyDoesntFitThisLock)); break; } } else { player.Session.Network.EnqueueSend(new GameEventUseDone(player.Session, WeenieError.YouCannotLockOrUnlockThat)); } }); chain.EnqueueChain(); }
public override void OnCollideObject(WorldObject target) { //Console.WriteLine($"{Name}.OnCollideObject({target.Name})"); var player = ProjectileSource as Player; if (Info != null && player != null && player.DebugSpell) { player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name}.OnCollideObject({target?.Name} ({target?.Guid}))", ChatMessageType.Broadcast)); player.Session.Network.EnqueueSend(new GameMessageSystemChat(Info.ToString(), ChatMessageType.Broadcast)); } ProjectileImpact(); // ensure valid creature target var creatureTarget = target as Creature; if (creatureTarget == null || target == ProjectileSource) { return; } if (player != null) { player.LastHitSpellProjectile = Spell; } // ensure caster can damage target var sourceCreature = ProjectileSource as Creature; if (sourceCreature != null && !sourceCreature.CanDamage(creatureTarget)) { return; } // if player target, ensure matching PK status var targetPlayer = creatureTarget as Player; var pkError = CheckPKStatusVsTarget(player, targetPlayer, Spell); if (pkError != null) { if (player != null) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, pkError[0], creatureTarget.Name)); } if (targetPlayer != null) { targetPlayer.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(targetPlayer.Session, pkError[1], ProjectileSource.Name)); } return; } var critical = false; var critDefended = false; var overpower = false; var damage = CalculateDamage(ProjectileSource, Caster, creatureTarget, ref critical, ref critDefended, ref overpower); if (damage != null) { // handle void magic DoTs: // instead of instant damage, add DoT to target's enchantment registry if (Spell.School == MagicSchool.VoidMagic && Spell.Duration > 0) { var dot = ProjectileSource.CreateEnchantment(creatureTarget, ProjectileSource, Spell); if (dot.Message != null && player != null) { player.Session.Network.EnqueueSend(dot.Message); } // corruption / corrosion playscript? //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.HealthDownVoid)); //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.DirtyFightingDefenseDebuff)); } else { DamageTarget(creatureTarget, damage.Value, critical, critDefended, overpower); } if (player != null) { Proficiency.OnSuccessUse(player, player.GetCreatureSkill(Spell.School), Spell.PowerMod); } // handle target procs // note that for untargeted multi-projectile spells, // ProjectileTarget will be null here, so procs will not apply if (sourceCreature != null && ProjectileTarget != null) { // Ok... if we got here, we're likely in the parallel landblock physics processing. // We're currently on the thread for this, but we're wanting to perform some work on sourceCreature which can result in a new spell being created // and added to the sourceCreature's current landblock, which, could be on a separate thread. // Any chance of a cross landblock group work (and thus cross thread), should be enqueued onto the target object to maintain thread safety. if (sourceCreature.CurrentLandblock == null || sourceCreature.CurrentLandblock == CurrentLandblock) { sourceCreature.TryProcEquippedItems(creatureTarget, false); } else { sourceCreature.EnqueueAction(new ActionEventDelegate(() => sourceCreature.TryProcEquippedItems(creatureTarget, false))); } } } // also called on resist if (player != null && targetPlayer == null) { player.OnAttackMonster(creatureTarget); } }
public void OnCollideObject(WorldObject target) { if (!PhysicsObj.is_active()) { return; } //Console.WriteLine($"Projectile.OnCollideObject - {WorldObject.Name} ({WorldObject.Guid}) -> {target.Name} ({target.Guid})"); if (ProjectileTarget == null || ProjectileTarget != target) { //Console.WriteLine("Unintended projectile target! (should be " + ProjectileTarget.Guid.Full.ToString("X8") + " - " + ProjectileTarget.Name + ")"); OnCollideEnvironment(); return; } // take damage var sourceCreature = ProjectileSource as Creature; var sourcePlayer = ProjectileSource as Player; var targetCreature = target as Creature; DamageEvent damageEvent = null; if (targetCreature != null) { if (sourcePlayer != null) { // player damage monster or player damageEvent = sourcePlayer.DamageTarget(targetCreature, WorldObject); if (damageEvent != null && damageEvent.HasDamage) { WorldObject.EnqueueBroadcast(new GameMessageSound(WorldObject.Guid, Sound.Collision, 1.0f)); } } else if (sourceCreature != null && sourceCreature.AttackTarget != null) { var targetPlayer = sourceCreature.AttackTarget as Player; damageEvent = DamageEvent.CalculateDamage(sourceCreature, targetCreature, WorldObject); if (targetPlayer != null) { // monster damage player if (damageEvent.HasDamage) { targetPlayer.TakeDamage(sourceCreature, damageEvent); // blood splatter? if (damageEvent.ShieldMod != 1.0f) { var shieldSkill = targetPlayer.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(targetPlayer, shieldSkill, shieldSkill.Current); // ?? } } else { targetPlayer.OnEvade(sourceCreature, CombatType.Missile); } } else { // monster damage pet if (damageEvent.HasDamage) { targetCreature.TakeDamage(sourceCreature, damageEvent.DamageType, damageEvent.Damage); // blood splatter? } } } // handle target procs if (damageEvent != null && damageEvent.HasDamage) { sourceCreature?.TryProcEquippedItems(targetCreature, false); } } WorldObject.CurrentLandblock?.RemoveWorldObject(WorldObject.Guid, showError: !PhysicsObj.entering_world); PhysicsObj.set_active(false); WorldObject.HitMsg = true; }
public override void OnCollideObject(WorldObject target) { //Console.WriteLine($"{Name}.OnCollideObject({target.Name})"); var player = ProjectileSource as Player; if (Info != null && player != null && player.DebugSpell) { player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name}.OnCollideObject({target?.Name} ({target?.Guid}))", ChatMessageType.Broadcast)); player.Session.Network.EnqueueSend(new GameMessageSystemChat(Info.ToString(), ChatMessageType.Broadcast)); } ProjectileImpact(); // ensure valid creature target var creatureTarget = target as Creature; if (creatureTarget == null || target == ProjectileSource) { return; } if (player != null) { player.LastHitSpellProjectile = Spell; } // ensure caster can damage target var sourceCreature = ProjectileSource as Creature; if (sourceCreature != null && !sourceCreature.CanDamage(creatureTarget)) { return; } // if player target, ensure matching PK status var targetPlayer = creatureTarget as Player; var pkError = CheckPKStatusVsTarget(player, targetPlayer, Spell); if (pkError != null) { if (player != null) { player.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(player.Session, pkError[0], creatureTarget.Name)); } if (targetPlayer != null) { targetPlayer.Session.Network.EnqueueSend(new GameEventWeenieErrorWithString(targetPlayer.Session, pkError[1], ProjectileSource.Name)); } return; } var critical = false; var critDefended = false; var overpower = false; var damage = CalculateDamage(ProjectileSource, Caster, creatureTarget, ref critical, ref critDefended, ref overpower); if (damage != null) { // handle void magic DoTs: // instead of instant damage, add DoT to target's enchantment registry if (Spell.School == MagicSchool.VoidMagic && Spell.Duration > 0) { var dot = ProjectileSource.CreateEnchantment(creatureTarget, ProjectileSource, Spell); if (dot.Message != null && player != null) { player.Session.Network.EnqueueSend(dot.Message); } // corruption / corrosion playscript? //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.HealthDownVoid)); //target.EnqueueBroadcast(new GameMessageScript(target.Guid, PlayScript.DirtyFightingDefenseDebuff)); } else { DamageTarget(creatureTarget, damage.Value, critical, critDefended, overpower); } if (player != null) { Proficiency.OnSuccessUse(player, player.GetCreatureSkill(Spell.School), Spell.PowerMod); } // handle target procs // note that for untargeted multi-projectile spells, // ProjectileTarget will be null here, so procs will not apply if (sourceCreature != null && ProjectileTarget != null) { sourceCreature.TryProcEquippedItems(creatureTarget, false); } } // also called on resist if (player != null && targetPlayer == null) { player.OnAttackMonster(creatureTarget); } }
/// <summary> /// Performs a melee attack for the monster /// </summary> /// <returns>The length in seconds for the attack animation</returns> public float MeleeAttack() { var target = AttackTarget as Creature; var targetPlayer = AttackTarget as Player; var targetPet = AttackTarget as CombatPet; var combatPet = this as CombatPet; if (target == null || !target.IsAlive) { Sleep(); return(0.0f); } // choose a random combat maneuver var maneuver = GetCombatManeuver(); if (maneuver == null) { Console.WriteLine($"Combat maneuver null! Stance {CurrentMotionState.Stance}, MotionTable {MotionTableId:X8}"); return(0.0f); } AttackHeight = maneuver.AttackHeight; // select random body part @ current attack height var bodyPart = BodyParts.GetBodyPart(AttackHeight.Value); DoSwingMotion(AttackTarget, maneuver, out float animLength, out var attackFrames); PhysicsObj.stick_to_object(AttackTarget.PhysicsObj.ID); var numStrikes = attackFrames.Count; var actionChain = new ActionChain(); var prevTime = 0.0f; for (var i = 0; i < numStrikes; i++) { actionChain.AddDelaySeconds(attackFrames[i] * animLength - prevTime); prevTime = attackFrames[i] * animLength; actionChain.AddAction(this, () => { if (AttackTarget == null) { return; } var critical = false; var damageType = DamageType.Undef; var shieldMod = 1.0f; var damage = CalculateDamage(ref damageType, maneuver, bodyPart, ref critical, ref shieldMod); if (damage != null) { if (combatPet != null || targetPet != null) { // combat pet inflicting or receiving damage //Console.WriteLine($"{target.Name} taking {Math.Round(damage)} {damageType} damage from {Name}"); target.TakeDamage(this, damageType, damage.Value); EmitSplatter(target, damage.Value); } else { // this is a player taking damage targetPlayer.TakeDamage(this, damageType, damage.Value, bodyPart, critical); if (shieldMod != 1.0f) { var shieldSkill = targetPlayer.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(targetPlayer, shieldSkill, shieldSkill.Current); // ? } } } else { target.OnEvade(this, CombatType.Melee); } }); } actionChain.EnqueueChain(); // TODO: figure out exact speed / delay formula var meleeDelay = ThreadSafeRandom.Next(MeleeDelayMin, MeleeDelayMax); NextAttackTime = Timers.RunningTime + animLength + meleeDelay; return(animLength); }
/// <summary> /// Performs a melee attack for the monster /// </summary> /// <returns>The length in seconds for the attack animation</returns> public float MeleeAttack() { var player = AttackTarget as Player; if (player.Health.Current <= 0) { return(0.0f); } // choose a random combat maneuver var maneuver = GetCombatManeuver(); if (maneuver == null) { return(0.0f); } AttackHeight = maneuver.AttackHeight; // select random body part @ current attack height var bodyPart = BodyParts.GetBodyPart(AttackHeight.Value); DoSwingMotion(AttackTarget, maneuver, out float animLength); PhysicsObj.stick_to_object(AttackTarget.PhysicsObj.ID); var actionChain = new ActionChain(); actionChain.AddDelaySeconds(animLength / 2.0f); actionChain.AddAction(this, () => { if (AttackTarget == null) { return; } var critical = false; var damageType = DamageType.Undef; var shieldMod = 1.0f; var damage = CalculateDamage(ref damageType, maneuver, bodyPart, ref critical, ref shieldMod); if (damage > 0.0f) { player.TakeDamage(this, damageType, damage, bodyPart, critical); if (shieldMod != 1.0f) { var shieldSkill = player.GetCreatureSkill(Skill.Shield); Proficiency.OnSuccessUse(player, shieldSkill, shieldSkill.Current); // ? } } else { player.OnEvade(this, AttackType.Melee); } }); actionChain.EnqueueChain(); // TODO: figure out exact speed / delay formula NextAttackTime = DateTime.UtcNow.AddSeconds(animLength + MeleeDelay); return(animLength); }