//Calculates bonus EXP for Links and Chains public static void AddBattleBonusEXP(Player attacker, BattleNpc defender, CommandResultContainer actionContainer) { ushort baseExp = GetBaseEXP(attacker, defender); //Only bother calculating the rest if there's actually exp to be gained. //0 exp sends no message if (baseExp > 0) { int totalBonus = 0;//GetMod(Modifier.bonusEXP) var linkCount = defender.GetMobMod(MobModifier.LinkCount); totalBonus += GetLinkBonus((byte)Math.Min(linkCount, 255)); StatusEffect effect = attacker.statusEffects.GetStatusEffectById((uint)StatusEffectId.EXPChain); ushort expChainNumber = 0; uint timeLimit = 100; if (effect != null) { expChainNumber = effect.GetTier(); timeLimit = (uint)(GetChainTimeLimit(expChainNumber)); actionContainer?.AddEXPAction(new CommandResult(attacker.actorId, 33919, 0, expChainNumber, (byte)timeLimit)); } totalBonus += GetChainBonus(expChainNumber); StatusEffect newChain = Server.GetWorldManager().GetStatusEffect((uint)StatusEffectId.EXPChain); newChain.SetSilent(true); newChain.SetDuration(timeLimit); newChain.SetTier((byte)(Math.Min(expChainNumber + 1, 255))); attacker.statusEffects.AddStatusEffect(newChain, attacker, true, true); actionContainer?.AddEXPActions(attacker.AddExp(baseExp, (byte)attacker.GetClass(), (byte)(totalBonus.Min(255)))); } }
public virtual void OnDamageTaken(Character attacker, CommandResult action, CommandResultContainer actionContainer = null) { switch (action.hitType) { case (HitType.Miss): OnEvade(attacker, action, actionContainer); break; case (HitType.Parry): OnParry(attacker, action, actionContainer); break; case (HitType.Block): OnBlock(attacker, action, actionContainer); break; } statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, action); //TP gain formula seems to be something like 5 * e ^ ( -0.667 * [defender's level] ) * damage taken, rounded up //This should be completely accurate at level 50, but isn't totally accurate at lower levels. //Don't know if store tp impacts this double tpModifier = 5 * Math.Pow(Math.E, (-0.0667 * GetLevel())); AddTP((int)Math.Ceiling(tpModifier * action.amount)); if (charaWork.parameterSave.hp[0] < 1) { Die(Program.Tick, actionContainer); } }
public override void OnDamageTaken(Character attacker, CommandResult action, CommandResultContainer actionContainer = null) { if (GetMobMod((uint)MobModifier.DefendScript) != 0) { lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount); } base.OnDamageTaken(attacker, action, actionContainer); }
public override void Die(DateTime tick, CommandResultContainer actionContainer = null) { if (IsAlive()) { // todo: does retail if (lastAttacker is Pet && lastAttacker.aiContainer.GetController <PetController>() != null && lastAttacker.aiContainer.GetController <PetController>().GetPetMaster() is Player) { lastAttacker = lastAttacker.aiContainer.GetController <PetController>().GetPetMaster(); } if (lastAttacker is Player) { //I think this is, or should be odne in DoBattleAction. Packet capture had the message in the same packet as an attack // <actor> defeat/defeats <target> if (actionContainer != null) { actionContainer.AddEXPAction(new CommandResult(actorId, 30108, 0)); } if (lastAttacker.currentParty != null && lastAttacker.currentParty is Party) { foreach (var memberId in ((Party)lastAttacker.currentParty).members) { var partyMember = zone.FindActorInArea <Character>(memberId); // onDeath(monster, player, killer) lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, partyMember, lastAttacker); // todo: add actual experience calculation and exp bonus values. if (partyMember is Player) { BattleUtils.AddBattleBonusEXP((Player)partyMember, this, actionContainer); } } } else { // onDeath(monster, player, killer) lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, lastAttacker, lastAttacker); //((Player)lastAttacker).QueuePacket(BattleActionX01Packet.BuildPacket(lastAttacker.actorId, 0, 0, new BattleAction(actorId, 30108, 0))); } } if (positionUpdates != null) { positionUpdates.Clear(); } aiContainer.InternalDie(tick, despawnTime); //this.ResetMoveSpeeds(); // todo: reset cooldowns lua.LuaEngine.GetInstance().OnSignal("mobkill"); } else { var err = String.Format("[{0}][{1}] {2} {3} {4} {5} tried to die ded", actorId, GetUniqueId(), positionX, positionY, positionZ, GetZone().GetName()); Program.Log.Error(err); //throw new Exception(err); } }
public override void OnComplete() { //BattleAction action = new BattleAction(target.actorId, 0x765D, (uint) HitEffect.Hit, 0, (byte) HitDirection.None); errorResult = null; // todo: implement auto attack damage bonus in Character.OnAttack /* * ≪Auto-attack Damage Bonus≫ * Class Bonus 1 Bonus 2 * Pugilist Intelligence Strength * Gladiator Mind Strength * Marauder Vitality Strength * Archer Dexterity Piety * Lancer Piety Strength * Conjurer Mind Piety * Thaumaturge Mind Piety * The above damage bonus also applies to “Shot” attacks by archers. */ // handle paralyze/intimidate/sleep/whatever in Character.OnAttack // todo: Change this to use a BattleCommand like the other states //List<BattleAction> actions = new List<BattleAction>(); CommandResultContainer actions = new CommandResultContainer(); var i = 0; for (int hitNum = 0; hitNum < 1 /* owner.GetMod((uint) Modifier.HitCount)*/; hitNum++) { CommandResult action = new CommandResult(target.actorId, 0x765D, (uint)HitEffect.Hit, 100, (byte)HitDirection.None, (byte)hitNum); action.commandType = CommandType.AutoAttack; action.actionType = ActionType.Physical; action.actionProperty = (ActionProperty)owner.GetMod(Modifier.AttackType); // evasion, miss, dodge, etc to be handled in script, calling helpers from scripts/weaponskills.lua // temporary evade/miss/etc function to test hit effects action.DoAction(owner, target, null, actions); } // todo: this is f****n stupid, probably only need *one* error packet, not an error for each action CommandResult[] errors = (CommandResult[])actions.GetList().ToArray().Clone(); CommandResult error = null;// new BattleAction(0, null, 0, 0); //owner.DoActions(null, actions.GetList(), ref error); //owner.OnAttack(this, actions[0], ref errorResult); var anim = (uint)(17 << 24 | 1 << 12); owner.DoBattleAction(22104, anim, actions.GetList()); }
public virtual void OnDamageDealt(Character defender, CommandResult action, CommandResultContainer actionContainer = null) { switch (action.hitType) { case (HitType.Miss): OnMiss(this, action, actionContainer); break; default: OnHit(defender, action, actionContainer); break; } //TP is only gained from autoattacks and abilities if (action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability) { //TP gained on an attack is usually 100 * delay. //Store TP seems to add .1% per point. double weaponDelay = GetMod(Modifier.AttackDelay) / 1000.0; var storeTPPercent = 1 + (GetMod(Modifier.StoreTP) * 0.001); AddTP((int)(weaponDelay * 100 * storeTPPercent)); } }
public static void SetHitEffectSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) { var hitEffect = HitEffect.MagicEffectType; HitType hitType = action.hitType; //Recoil levels for spells are a bit different than physical. Recoil levels are used for resists. //Lv1 is for larger resists, Lv2 is for smaller resists and Lv3 is for no resists. Crit is still used for crits if (hitType == HitType.Resist) { //todo: calculate resist levels and figure out what the difference between Lv1 and 2 in retail was. For now assuming a full resist with 0 damage dealt is Lv1, all other resists Lv2 if (action.amount == 0) { hitEffect |= HitEffect.RecoilLv1; } else { hitEffect |= HitEffect.RecoilLv2; } } else { hitEffect |= HitEffect.RecoilLv3; } hitEffect |= HitTypeEffects[hitType]; if (skill != null && skill.isCombo && !skill.comboEffectAdded) { hitEffect |= (HitEffect)(skill.comboStep << 15); skill.comboEffectAdded = true; } //if attack hit the target, take into account protective status effects if (hitType >= HitType.Block) { //Protect / Shell only show on physical/ magical attacks respectively. if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell)) { if (action != null) { hitEffect |= HitEffect.Shell; } } if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin)) { if (action != null) { hitEffect |= HitEffect.Stoneskin; } } } action.effectId = (uint)hitEffect; }
public static void DamageTarget(Character attacker, Character defender, CommandResult action, CommandResultContainer actionContainer = null) { if (defender != null) { defender.DelHP((short)action.amount); attacker.OnDamageDealt(defender, action, actionContainer); defender.OnDamageTaken(attacker, action, actionContainer); // todo: other stuff too if (defender is BattleNpc) { var bnpc = defender as BattleNpc; if (!bnpc.hateContainer.HasHateForTarget(attacker)) { bnpc.hateContainer.AddBaseHate(attacker); } bnpc.hateContainer.UpdateHate(attacker, action.enmity); bnpc.lastAttacker = attacker; } } }
//The order of messages that appears after using a command is: //1. Cast start messages. (ie "You begin casting... ") //2. Messages from buffs that activate before the command actually starts, like Power Surge or Presence of Mind. (This may be wrong and these could be the same as 4.) //3. If the command is a multi-hit command, this is where the "You use [command] on [target]" message goes //Then, for each hit: //4. Buffs that activate before a command hits, like Blindside //5. The hit itself. For single hit commands this message is "Your [command] hits [target] for x damage" for multi hits it's "[Target] takes x points of damage" //6. Stoneskin falling off //6. Buffs that activate after a command hits, like Aegis Boon and Divine Veil //After all hits //7. If it's a multi-hit command there's a "{numhits]fold attack..." message or if all hits miss an "All attacks missed" message //8. Buffs that fall off after the skill ends, like Excruciate //For every target defeated: //8. Defeat message //9. EXP message //10. EXP chain message //folder is probably temporary until move to cached scripts is complete public void DoBattleCommand(BattleCommand command, string folder) { //List<BattleAction> actions = new List<BattleAction>(); CommandResultContainer actions = new CommandResultContainer(); var targets = command.targetFind.GetTargets(); bool hitTarget = false; if (targets.Count > 0) { statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandStart, "onCommandStart", this, command, actions); foreach (var chara in targets) { ushort hitCount = 0; ushort totalDamage = 0; for (int hitNum = 1; hitNum <= command.numHits; hitNum++) { var action = new CommandResult(chara.actorId, command, (byte)GetHitDirection(chara), (byte)hitNum); //uncached script lua.LuaEngine.CallLuaBattleCommandFunction(this, command, folder, "onSkillFinish", this, chara, command, action, actions); //cached script //skill.CallLuaFunction(owner, "onSkillFinish", this, chara, command, action, actions); if (action.hitType > HitType.Evade && action.hitType != HitType.Resist) { hitTarget = true; hitCount++; totalDamage += action.amount; } } if (command.numHits > 1) { //30442: [hitCount]fold Attack! [chara] takes a total of totalDamage points of damage. //30450: All attacks miss! ushort textId = (ushort)(hitTarget ? 30442 : 30450); actions.AddAction(new CommandResult(chara.actorId, textId, 0, totalDamage, (byte)hitCount)); } } statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandFinish, "onCommandFinish", this, command, actions); } else { actions.AddAction(new CommandResult(actorId, 30202, 0)); } //Now that we know if we hit the target we can check if the combo continues if (this is Player) { if (command.isCombo && hitTarget) { ((Player)this).SetCombos(command.comboNextCommandId); } else { ((Player)this).SetCombos(); } } CommandResult error = new CommandResult(actorId, 0, 0); DelMP(command.CalculateMpCost(this)); DelTP(command.CalculateTpCost(this)); actions.CombineLists(); DoBattleAction(command.id, command.battleAnimation, actions.GetList()); }
//AdditionalActions is the list of actions that EXP/Chain messages are added to public virtual void Die(DateTime tick, CommandResultContainer actionContainer = null) { // todo: actual despawn timer aiContainer.InternalDie(tick, 10); ChangeSpeed(0.0f, 0.0f, 0.0f, 0.0f); }
//Called when this character misses public void OnMiss(Character defender, CommandResult action, CommandResultContainer actionContainer = null) { SetProc((ushort)HitType.Miss); statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, action, actionContainer); }
public void OnHit(Character defender, CommandResult action, CommandResultContainer actionContainer = null) { statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, action, actionContainer); }
public static void HealTarget(Character caster, Character target, CommandResult action, CommandResultContainer actionContainer = null) { if (target != null) { target.AddHP(action.amount); target.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, action, actionContainer); } }
//Called when this character parries attacker's action public void OnParry(Character attacker, CommandResult action, CommandResultContainer actionContainer = null) { SetProc((ushort)HitType.Parry); statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, action, actionContainer); }
/* * Hit Effecthelpers. Different types of hit effects hits use some flags for different things, so they're split into physical, magical, heal, and status */ public static void DoAction(Character caster, Character target, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) { switch (action.actionType) { case (ActionType.Physical): FinishActionPhysical(caster, target, skill, action, actionContainer); break; case (ActionType.Magic): FinishActionSpell(caster, target, skill, action, actionContainer); break; case (ActionType.Heal): FinishActionHeal(caster, target, skill, action, actionContainer); break; case (ActionType.Status): FinishActionStatus(caster, target, skill, action, actionContainer); break; default: actionContainer.AddAction(action); break; } }
//IsAdditional is needed because additional actions may be required for some actions' effects //For instance, Goring Blade's bleed effect requires another action so the first action can still show damage numbers //Sentinel doesn't require an additional action because it doesn't need to show those numbers //this is stupid public static void TryStatus(Character caster, Character target, BattleCommand skill, CommandResult action, CommandResultContainer results, bool isAdditional = true) { double rand = Program.Random.NextDouble(); //Statuses only land for non-resisted attacks and attacks that hit if (skill != null && skill.statusId != 0 && (action.hitType > HitType.Evade && action.hitType != HitType.Resist) && rand < skill.statusChance) { StatusEffect effect = Server.GetWorldManager().GetStatusEffect(skill.statusId); //Because combos might change duration or tier if (effect != null) { effect.SetDuration(skill.statusDuration); effect.SetTier(skill.statusTier); effect.SetMagnitude(skill.statusMagnitude); effect.SetOwner(target); effect.SetSource(caster); if (target.statusEffects.AddStatusEffect(effect, caster)) { //If we need an extra action to show the status text if (isAdditional) { results.AddAction(target.actorId, 30328, skill.statusId | (uint)HitEffect.StatusEffectType); } } else { action.worldMasterTextId = 32002;//Is this right? } } else { //until all effects are scripted and added to db just doing this if (target.statusEffects.AddStatusEffect(skill.statusId, skill.statusTier, skill.statusMagnitude, skill.statusDuration, 3000)) { //If we need an extra action to show the status text if (isAdditional) { results.AddAction(target.actorId, 30328, skill.statusId | (uint)HitEffect.StatusEffectType); } } else { action.worldMasterTextId = 32002;//Is this right? } } } }
public static void SetHitEffectPhysical(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer) { var hitEffect = HitEffect.HitEffectType; HitType hitType = action.hitType; //Don't know what recoil is actually based on, just guessing //Crit is 2 and 3 together if (hitType == HitType.Crit) { hitEffect |= HitEffect.CriticalHit; } else { //It's not clear what recoil level is based on for physical attacks double percentDealt = (100.0 * (action.amount / defender.GetMaxHP())); if (percentDealt > 5.0) { hitEffect |= HitEffect.RecoilLv2; } else if (percentDealt > 10) { hitEffect |= HitEffect.RecoilLv3; } } hitEffect |= HitTypeEffects[hitType]; //For combos that land, add the combo effect if (skill != null && skill.isCombo && hitType > HitType.Evade && hitType != HitType.Evade && !skill.comboEffectAdded) { hitEffect |= (HitEffect)(skill.comboStep << 15); skill.comboEffectAdded = true; } //if attack hit the target, take into account protective status effects if (hitType >= HitType.Parry) { //Protect / Shell only show on physical/ magical attacks respectively. if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect)) { if (action != null) { hitEffect |= HitEffect.Protect; } } if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin)) { if (action != null) { hitEffect |= HitEffect.Stoneskin; } } } action.effectId = (uint)hitEffect; }
public static void FinishActionStatus(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) { //Set the hit effect SetHitEffectStatus(attacker, defender, skill, action); TryStatus(attacker, defender, skill, action, actionContainer, false); actionContainer.AddAction(action); }
public static void FinishActionHeal(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) { //Set the hit effect SetHitEffectHeal(attacker, defender, skill, action); actionContainer.AddAction(action); HealTarget(attacker, defender, action, actionContainer); }
public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) { //Determine the hit type of the action if (!TryMiss(attacker, defender, skill, action)) { HandleStoneskin(defender, action); if (!TryCrit(attacker, defender, skill, action)) { if (!TryResist(attacker, defender, skill, action)) { action.hitType = HitType.Hit; } } } //There are no multi-hit spells action.worldMasterTextId = SingleHitTypeTextIds[action.hitType]; //Set the hit effect SetHitEffectSpell(attacker, defender, skill, action); HandleStoneskin(defender, action); CalculateSpellDamageTaken(attacker, defender, skill, action); actionContainer.AddAction(action); DamageTarget(attacker, defender, action, actionContainer); }
//Determine the hit type, set the hit effect, modify damage based on stoneskin and hit type, hit target public static void FinishActionPhysical(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) { //Figure out the hit type and change damage depending on hit type if (!TryMiss(attacker, defender, skill, action)) { //Handle Stoneskin here because it seems like stoneskin mitigates damage done before taking into consideration crit/block/parry damage reductions. //This is based on the fact that a 0 damage attack due to stoneskin will heal for 0 with Aegis Boon, meaning Aegis Boon didn't mitigate any damage HandleStoneskin(defender, action); //Crits can't be blocked (is this true for Aegis Boon and Divine Veil?) or parried so they are checked first. if (!TryCrit(attacker, defender, skill, action)) { //Block and parry order don't really matter because if you can block you can't parry and vice versa if (!TryBlock(attacker, defender, skill, action)) { if (!TryParry(attacker, defender, skill, action)) { //Finally if it's none of these, the attack was a hit action.hitType = HitType.Hit; } } } } //Actions have different text ids depending on whether they're a part of a multi-hit ws or not. Dictionary <HitType, ushort> textIds = SingleHitTypeTextIds; //If this is the first hit of a multi hit command, add the "You use [command] on [target]" action //Needs to be done here because certain buff messages appear before it. if (skill != null && skill.numHits > 1) { if (action.hitNum == 1) { actionContainer?.AddAction(new CommandResult(attacker.actorId, 30441, 0)); } textIds = MultiHitTypeTextIds; } //Set the correct textId action.worldMasterTextId = textIds[action.hitType]; //Set the hit effect SetHitEffectPhysical(attacker, defender, skill, action, actionContainer); //Modify damage based on defender's stats CalculatePhysicalDamageTaken(attacker, defender, skill, action); actionContainer.AddAction(action); action.enmity = (ushort)(action.enmity * (skill != null ? skill.enmityModifier : 1)); //Damage the target DamageTarget(attacker, defender, action, actionContainer); }