|
@@ -0,0 +1,236 @@
|
|
|
|
+/// <reference path="../Action.ts" />
|
|
|
|
+/// <reference path="../Rule.ts" />
|
|
|
|
+/// <reference path="../Rulebook.ts" />
|
|
|
|
+/// <reference path="../Things/Weapon/Weapon.ts" />
|
|
|
|
+enum ActionAttackReaction {
|
|
|
|
+ DODGE, RESIST, COUNTERATTACK
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+class ActionAttack extends Action {
|
|
|
|
+ public static check = new Rulebook<ActionAttack>("Check Attack");
|
|
|
|
+ public static carry = new Rulebook<ActionAttack>("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<any>) {
|
|
|
|
+ super(actor, ...nouns);
|
|
|
|
+ this.requiresNoun = true;
|
|
|
|
+ this.requiresVisibility = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public getTarget () : Thing {
|
|
|
|
+ return this.getNoun(0);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public getWeapons () : Array<Weapon> {
|
|
|
|
+ let weaponNoun = this.getNoun(1);
|
|
|
|
+
|
|
|
|
+ if (weaponNoun == undefined) {
|
|
|
|
+ let weapons = <Array<Weapon>> 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 (...cu : Array<CombatUnit>) {
|
|
|
|
+ let group = new ContentGroup(...cu);
|
|
|
|
+ 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<ActionAttack>) => {
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ public static checkAccuracyRule = ActionAttack.check.createAndAddRule({
|
|
|
|
+ name : "Attack - Is it a hit?",
|
|
|
|
+ code : async (runner : RulebookRunner<ActionAttack>) => {
|
|
|
|
+ let action = runner.noun;
|
|
|
|
+ let actorDex = (<Person> runner.noun.actor).getStat(Attributes.Agility);
|
|
|
|
+ let targetDex = 0;
|
|
|
|
+ let target = runner.noun.getTarget();
|
|
|
|
+ if (target instanceof Person) {
|
|
|
|
+ targetDex = target.getStat(Attributes.Agility);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let reaction = ActionAttackReaction.DODGE;
|
|
|
|
+ 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++) {
|
|
|
|
+ (<Person> action.actor).changeStamina(- weapons[i].attackCost);
|
|
|
|
+ }
|
|
|
|
+ if (weapons.length == 0) {
|
|
|
|
+ (<Person> action.actor).changeStamina(- ActionAttack.fistCost)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ action.targetReaction = reaction;
|
|
|
|
+
|
|
|
|
+ if (reaction == ActionAttackReaction.DODGE) {
|
|
|
|
+ (<Person> 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 CombatUnit())
|
|
|
|
+ .setActor(action.actor)
|
|
|
|
+ .setTarget(target)
|
|
|
|
+ .setWeapon(...action.getWeapons())
|
|
|
|
+ .addMarker(CombatHit.MISS)
|
|
|
|
+ );
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ public static carryOutAttack = ActionAttack.carry.createAndAddRule({
|
|
|
|
+ name: "Attack - Go for the throat!",
|
|
|
|
+ code: async (runner: RulebookRunner<ActionAttack>) => {
|
|
|
|
+ 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 += (<Person> action.actor).getStat(Attributes.Strength);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!(target instanceof Person)) {
|
|
|
|
+ if (target.breakable && target.breakableOn <= damage) {
|
|
|
|
+ target.break();
|
|
|
|
+ action.generateDescription(
|
|
|
|
+ (new CombatUnit())
|
|
|
|
+ .setActor(action.actor)
|
|
|
|
+ .setTarget(target)
|
|
|
|
+ .setWeapon(...weapons)
|
|
|
|
+ .addMarker(CombatHit.HIT)
|
|
|
|
+ .addMarker(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 = <HumanoidTorso> (<Person> 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 CombatUnit())
|
|
|
|
+ .setActor(action.actor)
|
|
|
|
+ .setTarget(target)
|
|
|
|
+ .setWeapon(...weapons)
|
|
|
|
+ .addMarker(hitType)
|
|
|
|
+ .addMarker(result)
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+Elements.HyperlinkHandler.HyperlinkingRulebook.addRule(new Rule(
|
|
|
|
+ {
|
|
|
|
+ name : "Hyperlink - Attack",
|
|
|
|
+ firstPriority : Rule.PRIORITY_HIGHEST,
|
|
|
|
+ code : (rulebook : RulebookRunner<Thing>) => {
|
|
|
|
+ let thing = <Thing> rulebook.noun;
|
|
|
|
+
|
|
|
|
+ if ((thing instanceof Person || thing.breakable) && thing.isVisibleTo(WorldState.player)) {
|
|
|
|
+ Elements.HyperlinkHandler.addAvailableAction("Attack", new ActionAttack(WorldState.player, thing));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+));
|