/// <summary> /// Performs a cast of a spell from the caster onto the target Actor instance. /// </summary> /// <param name="spell">The spell being cast.</param> /// <param name="caster">The avatar casting the spell.</param> /// <param name="target">The target Actor instance, avatar or item.</param> /// <returns>The results of the casting.</returns> public static CastResults PerformCast( ISpell spell, IAvatar caster, IActor target) { CastResults results = new CastResults(); // Used for sending messages to the defender's Context. IAvatar defender = target as IAvatar; // Ensure that the spell can be cast on the target. if (Object.ReferenceEquals(caster, target)) { if (spell.IsDamageSpell) { caster.Context.Add(new RdlErrorMessage(SR.CastNotSelf)); return(results); } } // To determine the effect’s difficulty rating, all foci scores are added together. int difficulty = spell.Foci.GetDifficulty(); //The sphere or discipline skill test is then made vs. Intelligence, opposed by the opposing successes of the difficulty //rating roll (see Section 1.2.1). If no successes are remain, then the effect fails and the character burns no mind. //If a disastrous failure is rolled, the character takes the effect himself (if it’s offensive), it has its opposite effect, or //whatever the GM decides; the character also burns extra mind equal to the extent of the critical failure (if two 10s //were rolled on 2d10, the character would eat the effect and burn 2 extra mind). //Note that offensive effects with no area foci require a successful combat roll to strike the target, on the sphere or //discipline skill in the case of ranged attacks, or on an unarmed combat (or similar) skill in the case of touch effects. //Called shots can be made as in normal combat. //The defender may be allowed a Dodge roll for defense against being hit by some effects. Generally, if the attacker //must make a perception or dexterity roll to hit, the defender will probably be allowed a Dodge roll. int casterSuccessCount; SkillManager.PerformSkillTest(caster, spell.Skill, AttributeType.Intelligence, difficulty, spell.SkillLevelRequiredToEquip, true, out casterSuccessCount); // Raise the OnCastSkillRoll event to determine if the success roll should be overwritten. caster.OnCastSkillRoll(spell, ref casterSuccessCount); results.CastSuccessCount = casterSuccessCount; //Producing an effect, if successful, burns Mind. The amount of Mind burned is equal to the effect’s difficulty //minus the number of effective successes rolled (that is, what successes are left after the opposing successes are taken //into account), for a minimum of zero. int mindValueUsed = (difficulty - casterSuccessCount); if (mindValueUsed <= 0) { mindValueUsed = 1; } // Caster needs to the required amount of mind value. if (caster.Mind < mindValueUsed) { caster.Context.Add(new RdlErrorMessage("You do not have the required Willpower to cast this spell.")); return(results); } if (casterSuccessCount < 0 && spell.IsDamageSpell) { // Disatrous failure, spell backfires! caster.SetMind(caster.Mind - mindValueUsed); caster.SetMind(caster.Mind + casterSuccessCount); // Apply damage to the caster. caster.SetBody(caster.Body - spell.Foci.Power); caster.Context.Add(new RdlErrorMessage(SR.CastBackfired)); if (caster.IsDead) { results.CasterDied = true; caster.Context.Add(new RdlErrorMessage(SR.CastBackfiredKilledCaster)); } else if (caster.IsUnconscious) { results.CasterUnconscious = true; caster.Context.Add(new RdlErrorMessage(SR.CastBackfiredUnconsciousCaster)); } } else if (casterSuccessCount > 0) { results.CastSuccessful = true; // Spell successful. caster.SetMind(caster.Mind - mindValueUsed); // Inform the caster of the successful cast. //caster.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Positive, // SR.CastSuccess(spell.Name, target.A()))); spell.Cast(caster, target, results); caster.OnCastSuccess(spell); // Handle death messages. if (defender.IsDead) { caster.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Cast, SR.AttackYouKilledDefender(defender.The()))); defender.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.AttackYouWereKilledByAttacker(caster.The()))); } else if (defender.IsUnconscious) { caster.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Cast, SR.AttackUnconsciousDefender(defender.TheUpper()))); defender.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.AttackYouAreUnconscious)); } // Send down both the body and mind values of both the caster and the target to both the // caster and the target. RdlTag[] casterTags = caster.GetRdlProperties(Avatar.BodyProperty, Avatar.MindProperty); RdlTag[] defenderTags = null; if (defender != null) { defenderTags = defender.GetRdlProperties(Avatar.BodyProperty, Avatar.MindProperty); } caster.Context.AddRange(casterTags); if (defenderTags != null) { caster.Context.AddRange(defenderTags); } if (defender != null) { defender.Context.AddRange(defenderTags); defender.Context.AddRange(casterTags); } } else { // Missed. caster.Context.Add(new RdlErrorMessage(SR.CastFailed)); if (defender != null) { defender.Context.Add(new RdlErrorMessage(SR.CasterCastFailed(caster.A()))); } } //If an effect burns more mind than the character has available, then any overflow causes wounds. This is called //channeling, and can be done even when the caster has zero mind. If a character channels to below 0 body, his life //begins seeping away (treat it as any other wound below zero), and he must be magically or psionically healed or he //will die when he passes his negative physical endurance. if (caster.Mind < 0) { int channelingDamage = caster.Mind; caster.SetMind(0); caster.SetBody(caster.Body - channelingDamage); caster.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.CastChanneling)); } // Regardless of outcome advance both the caster and target skills. SkillManager.AdvanceSkill(caster, spell.Skill, spell.SkillLevelRequiredToEquip, caster.Context); // NOTE: Do not have resist skills... //if (defender != null) //{ // // Use attackerWeapon as the required skill level so the defender can not elevate beyond the skill used // // for the attack. // SkillManager.AdvanceSkill(defender, defensiveSkill, spell.SkillLevelRequiredToEquip, defender.Context); //} return(results); }
/// <summary> /// Performs a simple combat turn. /// </summary> /// <param name="attacker">The attacking avatar.</param> /// <param name="attackerWeapon">The weapon to be used during the attack.</param> /// <param name="defender">The defending avatar.</param> /// <param name="defensiveSkill">The skill used to defender against the attack.</param> /// <param name="defenderAttributeType">The attribute used to defend.</param> /// <param name="range">The range from the attacker to the defender.</param> /// <returns>True if the defender was killed during the attack; otherwise false.</returns> public static bool PerformSimpleCombatTurn( IAvatar attacker, IWeapon attackerWeapon, string offensiveSkill, IAvatar defender, string defensiveSkill, AttributeType defenderAttributeType, int range) { // Only true if the defender gets killed. bool result = false; int defenderProtection = defender.GetArmor().Protection; //Phase 1: The Combat Skill Test //The attacker makes a combat skill test vs. Physical Dexterity (in the case of melee attacks or point blank ranged //attacks) or Mental Perception (in the case of ranged attacks at short range or greater), while the defender makes a //defense skill test against an appropriate combative or defensive skill, to determine the number of opposing successes. //If the number of successes scored by the attacker do not exceed the number of successes scored by the defender, then //the target has been hit. int attackerSkillLevel = (int)attacker.Skills[offensiveSkill]; int defenderSkillLevel = (int)defender.Skills[defensiveSkill]; if (attackerWeapon.Durability <= 0) { attackerSkillLevel = 0; } AttributeType attackerAttrType = AttributeType.Strength; // Was Dex, should it go back to Dex? if (attackerWeapon.Range > 1) { attackerAttrType = AttributeType.Perception; } // Perform skill rolls. int attackerSuccessCount; SkillManager.PerformSkillTest(attacker, offensiveSkill, attackerAttrType, 0, attackerWeapon.SkillLevelRequiredToEquip, false, out attackerSuccessCount); int defenderSuccessCount; SkillManager.PerformSkillTest(defender, defensiveSkill, defenderAttributeType, 0, defender.GetArmor().SkillLevelRequiredToEquip, false, out defenderSuccessCount); // TODO: Implement disatrous failure in combat. //• In the case of a disastrous failure, the character has fumbled and loses his next attack. A very high degree //(3+) disastrous failure may indicate weapon breakage. This applies to both the offensive and defense sides of //the combat. // Range //• While a ranged attack at point blank range suffers no penalty, other ranged attacks will incur a penalty to //the attack roll target number based on the distance to the target. Attacks attempted within short range for //the weapon will suffer a penalty of -1, while attacks within medium range will suffer a -2 penalty and attacks //within long range will suffer a penalty of -4. if (range > 1) { attackerSuccessCount -= range; if (attackerSuccessCount == 0) { attacker.Context.Add(new RdlSystemMessage(0, SR.AttackOutOfRange)); } } int outcome = attackerSuccessCount - defenderSuccessCount; // Raise an event on the attacker so that the outcome can be modified based on world specific conditions. attacker.OnAttack(ref outcome); if (outcome > 0) { // DEFENDER WAS HIT. //Phase 2: Damage Determination //The damage value of the attacker’s weapon is added to the number of combat successes that the attacker has //remaining. This is the Damage Value. //Small weapons will typically cause 1 point of damage, medium weapons 2 points, and heavy weapons 3 points of //damage. Additionally, melee weapon damage is modified by a value equal to the attacker’s strength value minus 5. //If a character is exceptionally weak, this will subtract damage from the final value (base damage plus successes). The //modified damage value can never be less than zero. int damage = attackerSuccessCount + attackerWeapon.Power; damage += attacker.Attributes[AttributeType.Strength] - 5; if (damage <= 0) { damage = 1; } //Phase 3: Application of Armor //A piece of armor has two statistics, coverage rating (CR) and absorption rating (AR). CR ranges from 1 (very //little coverage) to 5 (complete coverage). AR can range from as little as 1 for thin leather or heavy cloth to as much //as 8 or more for magical or unusual armors. //The attacker’s remaining successes are applied to the coverage rating. If the number of successes exceeds the //armor’s coverage rating, then the armor has been bypassed (a better executed hit is more likely to bypass armor), in //which case all damage goes straight to the defender. If the attack fails to bypass the armor, then the armor removes //one point of damage per point of absorption. Any damage equal to or beyond the armor’s absorption rating indicates //that the armor has been pierced or rendered ineffective in some way. When this happens, any excess damage is //applied to the character (see below), and the armor’s coverage rating is reduced by one (the armor is damaged) until //it can be repaired by someone with the appropriate skill (this could include the character if they have said skills). //Armor with a CR of 5+ cannot be damaged by penetration. This CR is usually only used for creatures with //natural armor or who are made of unnaturally hard substances. defender.OnApplyProtection(attacker, ref damage); //if (attackerSuccessCount <= (defenderProtection * 0.5)) //{ // // Let armor absrob some or all of the damage. // // Do not reduce damage more than half because of armor. // int newDmg = damage - (int)(defenderProtection * 0.5); // if (newDmg < (damage * 0.5)) // { // damage = (int)(damage * 0.5); // } // if (damage <= 0) damage = 1; //} if (damage > 0) { // Decrement the durability of the defender's armor. defender.GetArmor().Durability--; //Phase 4: Apply Damage //Any remaining wounds after the application of armor are subtracted from the character’s Body stat. defender.SetBody(defender.Body - damage); attacker.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Melee, SR.AttackHitDefender(defender.The(), damage))); defender.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.AttackHitByAttacker(attacker.The(), damage))); if (defender.IsDead) { result = true; attacker.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Melee, SR.AttackYouKilledDefender(defender.The()))); defender.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.AttackYouWereKilledByAttacker(attacker.The()))); } else if (defender.IsUnconscious) { attacker.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Melee, SR.AttackUnconsciousDefender(defender.TheUpper()))); defender.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.AttackYouAreUnconscious)); } } else { attacker.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.AttackFailed)); defender.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.None, SR.AttackAttackerFailed(attacker.A()))); } } else { // Attacker missed. attacker.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.Negative, SR.AttackMiss)); defender.Context.Add(new RdlSystemMessage(RdlSystemMessage.PriorityType.None, SR.AttackAttackerFailed(attacker.A()))); } // Regardless of outcome advance the skill of the attacker and the defender. SkillManager.AdvanceSkill(attacker, attackerWeapon.Skill, attackerWeapon.SkillLevelRequiredToEquip, attacker.Context); // Use attackerWeapon as the required skill level so the defender can not elevate beyond the skill used // for the attack. SkillManager.AdvanceSkill(defender, defensiveSkill, attackerWeapon.SkillLevelRequiredToEquip, defender.Context); // Return result of the combat action return(result); }