123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- /// <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 (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<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;
- }
- }
- });
- // 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<ActionAttack>) => {
- let action = runner.noun;
- let actorDex = (<Person> runner.noun.actor).getStat(Attributes.Agility);
- let actor = (<Person> 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++) {
- (<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 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<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 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 = <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 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<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));
- }
- }
- }
- ));
|