/// /// /// /// enum ActionAttackReaction { DODGE, RESIST, COUNTERATTACK } class ActionAttack extends Action { public static check = new Rulebook("Check Attack"); public static carry = new Rulebook("Carry out Attack"); public actingAgressively = true; public aggressivenessRating = 1; private targetReaction : ActionAttackReaction; public allowedStances = [PersonStance.STANDING]; public static randomness = 2; public static dodgeCost = 1; public static fistCost = 1; public static gettingAttacked = new OneOf(OneOf.PURELY_AT_RANDOM, "moves on to attack you!", "attacks you!", "tries to attack you!", "decides to attack you!", "initiates an attack!", "is attacking!", "approaches with malicious intent!", "aims at you with malicious intent!", "aims at you!", "goes for you!", "comes for you!", "seems hostile!", "seems to want to fight!", "wants to fight!", "is coming right for you!", "is coming right at you!", "is not friendly at all!" ); // TODO: Add infinitely more of these... public constructor (actor : Thing, ...nouns : Array) { super(actor, ...nouns); this.requiresNoun = true; this.requiresVisibility = false; } public getTarget () : Thing { return this.getNoun(0); } public getWeapons () : Array { let weaponNoun = this.getNoun(1); if (weaponNoun == undefined) { let weapons = > this.actor.getWorns(Weapon); return weapons; } return [weaponNoun]; } public getCommandText () { let weaponName; let weapons = this.getWeapons(); if (weapons.length == 0) { weaponName = "fists"; } else { weaponName = []; weapons.forEach((weapon => { weaponName.push(weapon.getPrintedName()); })); weaponName = weaponName.join(" and "); } return "attack " + this.getNoun(0).getPrintedName() + " with " + weaponName; } public generateDescription (group : ContentGroup) { this.say.add(...(CombatDescription.getDescription(group))); } public static checkAttackable = ActionAttack.check.createAndAddRule({ name : "Attack - Is it attackable?", firstPriority : Rule.PRIORITY_HIGHEST, code : async (runner : RulebookRunner) => { let action = runner.noun; let target = runner.noun.getTarget(); if (!(target instanceof Person) && !target.breakable) { if (action.actor == WorldState.player) { action.say.add("You can't possibly damage that."); } action.stop(); return false; } } }); // TODO: Reduce these god functions into smaller functions that are easier to modify by future changes. public static checkAccuracyRule = ActionAttack.check.createAndAddRule({ name : "Attack - Is it a hit?", code : async (runner : RulebookRunner) => { let action = runner.noun; let actorDex = ( runner.noun.actor).getStat(Attributes.Agility); let actor = ( runner.noun.actor); if (actor.stance == PersonStance.ALLFOURS) { actorDex = Math.floor(actorDex/2); } let targetDex = 0; let target = runner.noun.getTarget(); if (target instanceof Person) { targetDex = target.getStat(Attributes.Agility); if (target.stance == PersonStance.ALLFOURS) { targetDex = Math.floor(targetDex/2); } else if (target.stance == PersonStance.KNOCKEDOUT) { targetDex = 0; } let reaction = ActionAttackReaction.DODGE; if (target.stance == PersonStance.KNOCKEDOUT) { reaction = ActionAttackReaction.RESIST; } else { if (target == WorldState.player) { Elements.CurrentTurnHandler.printAsContent(new Say(action.actor, " ", ActionAttack.gettingAttacked.getOne())); let choices = ["Counter-Attack", "Dodge", "Resist"]; let choice = await Controls.giveChoices(false, ...choices); if (choice[1] == 0) { reaction = ActionAttackReaction.COUNTERATTACK; } else if (choice[1] == 2) { reaction = ActionAttackReaction.DODGE; } else { reaction = ActionAttackReaction.RESIST; } } } let weapons = action.getWeapons(); for (let i = 0; i < weapons.length; i++) { ( action.actor).changeStamina(- weapons[i].attackCost); } if (weapons.length == 0) { ( action.actor).changeStamina(- ActionAttack.fistCost) } action.targetReaction = reaction; if (reaction == ActionAttackReaction.DODGE) { ( target).changeStamina(- ActionAttack.dodgeCost); let attack = Math.floor(Math.random() * (ActionAttack.randomness + ActionAttack.randomness + 1)) - ActionAttack.randomness; let defense = Math.floor(Math.random() * (ActionAttack.randomness + ActionAttack.randomness + 1)) - ActionAttack.randomness; attack += actorDex; defense += targetDex; if (attack < defense) { action.generateDescription( (new ContentGroup()) .addUnit( (new CombatUnit()) .setActor(action.actor) .setTarget(target) .setWeapon(...weapons) .addMarker(CombatHit.MISS) ) ); return false; } } } } }); public static carryOutAttack = ActionAttack.carry.createAndAddRule({ name: "Attack - Go for the throat!", code: async (runner: RulebookRunner) => { let action = runner.noun; let target = runner.noun.getTarget(); let damage = 0; let weapons = action.getWeapons(); for (let i = 0; i < weapons.length; i++) { damage += weapons[i].getDamage(); } if (weapons.length == 0) { damage += ( action.actor).getStat(Attributes.Strength); } if (!(target instanceof Person)) { if (target.breakable && target.breakableOn <= damage) { target.break(); action.generateDescription( (new ContentGroup()) .addUnit( (new CombatUnit()) .setActor(action.actor) .setTarget(target) .setWeapon(...weapons) .addMarker(CombatHit.HIT, CombatResult.KILLED) ) ); } } else { let damageReduction = target.getStat(Attributes.Strength); if (action.targetReaction == ActionAttackReaction.RESIST) { damageReduction = Math.floor(damageReduction * 1.5) } let finalDamage = damage - damageReduction; if (finalDamage < 0) { finalDamage = 0; } action.aggressivenessRating = 1 + finalDamage; let torso = ( target).getPart(HumanoidTorso); if (torso != undefined) { torso.changeSoreness(finalDamage); } let hitType = (finalDamage * torso.getWeightedSoreness()) > (target.getMaxHealth() / 3) ? CombatHit.CRITICAL : CombatHit.HIT; let targetHealth = target.getHealthOnScale(); let knockedOff = hitType == CombatHit.CRITICAL; if (knockedOff) { target.stance = PersonStance.ALLFOURS; } let result = targetHealth < -9 ? CombatResult.KILLED : targetHealth <= 0 ? CombatResult.KNOCKED_OFF : knockedOff ? CombatResult.KNOCKED : undefined; if (targetHealth < -9) { target.die(); } action.generateDescription( (new ContentGroup()) .addUnit( (new CombatUnit()) .setActor(action.actor) .setTarget(target) .setWeapon(...weapons) .addMarker(hitType, result) ) ); } } }); } Elements.HyperlinkHandler.HyperlinkingRulebook.addRule(new Rule( { name : "Hyperlink - Attack", firstPriority : Rule.PRIORITY_HIGHEST, priority : Rule.PRIORITY_MEDIUM, code : (rulebook : RulebookRunner) => { let thing = rulebook.noun; if ((thing instanceof Person || thing.breakable) && thing.isVisibleTo(WorldState.player)) { Elements.HyperlinkHandler.addAvailableAction("Attack", new ActionAttack(WorldState.player, thing)); } } } ));