123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 |
- /// <reference path="../Action.ts" />
- /// <reference path="../Rule.ts" />
- /// <reference path="../Rulebook.ts" />
- /// <reference path="../Tests/DiceDangerous.ts" />
- enum ActionAttackReaction {
- DODGE, RESIST, COUNTERATTACK
- }
- interface Weaponized {
- getAttribute : () => Attribute; // What is the attribute used for the damage
- getAttributeDamageFactor : () => number; // How much of the attribute is added for the attack.
- getAttributeForceFactor : () => number; // How much of the attribute is added for the attack's pushing down capabilities
- getAccuracy : () => number; // How much to add/remove from the Dex check.
- getCost : () => number; // The cost of performing this attack.
- }
- 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;
- private actorDexRoll : number;
- private targetDexRoll : number;
- private actorDamageRoll : number;
- private targetDamageRoll : number;
- private actorForceRoll : number;
- private targetForceRoll : number;
- 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!",
- "starts 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);
- if (weapons.length == 0) {
- // If we ever have tentaclemonsters or stuff...
- // TODO: Add parts for other creature types here.
- if (this.actor instanceof Humanoid) {
- return [this.actor.getPart(HumanoidHands)];
- }
- }
- 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,
- priority : Rule.PRIORITY_MEDIUM,
- 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 : "Do Dex Rolls - Choose defenses",
- firstPriority : ActionAttack.checkAttackable.firstPriority,
- priority : ActionAttack.checkAttackable.priority - 1,
- code : async (runner : RulebookRunner<ActionAttack>) => {
- let actor = <Person> runner.noun.actor;
- let target = runner.noun.getTarget();
- let action = runner.noun;
- action.targetReaction = ActionAttackReaction.DODGE;
- action.actorDexRoll = ActionAttack.getEffectiveCombatDex(actor);
- action.targetDexRoll = 0;
- if (target instanceof Person) {
- action.targetDexRoll = ActionAttack.getEffectiveCombatDex(actor);
- if (WorldState.isPlayer(target)) {
- // Let player choose reaction
- let sayWarning = new Say(Say.Mention(actor), " ", ActionAttack.gettingAttacked.getOne());
- let elements = await Elements.CurrentTurnHandler.getSayElementsAsContent(sayWarning);
- Elements.CurrentTurnHandler.print(...elements);
- let choices = ["Counter Attack", "Dodge", "Resist"];
- let choice = await Controls.giveChoices(false, ...choices);
- // if choice != dodge
- if (choice[1] != 1) {
- action.targetReaction = choice[1] == 0 ? ActionAttackReaction.COUNTERATTACK : ActionAttackReaction.RESIST;
- action.targetDexRoll = action.targetDexRoll / 2;
- }
- Elements.CurrentTurnHandler.unprint(...elements);
- }
- }
- }
- });
- public static checkDamageRule = ActionAttack.check.createAndAddRule({
- name : "Choose damages",
- firstPriority : ActionAttack.checkAttackable.firstPriority,
- priority : ActionAttack.checkAttackable.priority - 1,
- code : async (runner : RulebookRunner<ActionAttack>) => {
- let actor = <Person> runner.noun.actor;
- let target = runner.noun.getTarget();
- let action = runner.noun;
- let getDamage = (person : Person, ...weapons : Array<Weaponized>) => {
- let damage = 0;
- weapons.forEach(weapon => {
- damage += weapon.getAttributeDamageFactor() * person.getStat(weapon.getAttribute());
- });
- return damage;
- };
- let getForce = (person : Person, ...weapons : Array<Weaponized>) => {
- let force = 0;
- weapons.forEach(weapon => {
- force += weapon.getAttributeForceFactor() * person.getStat(weapon.getAttribute());
- });
- return force;
- };
- action.actorDamageRoll = getDamage(actor, ...action.getWeapons());
- action.actorForceRoll = getForce(actor, ...action.getWeapons());
- if (action.targetReaction == ActionAttackReaction.COUNTERATTACK) {
- let attack = new ActionAttack(target);
- action.targetDamageRoll = getDamage(<Person> target, ...attack.getWeapons()); // most likely only players do this
- action.targetForceRoll = getForce(<Person> target, ...attack.getWeapons()); // most likely only players do this
- }
- }
- });
- public static carryOutAttack = ActionAttack.carry.createAndAddRule({
- name: "Attack - Actor Attacks Person",
- firstPriority : Rule.PRIORITY_HIGH,
- priority : Rule.PRIORITY_MEDIUM,
- code: async (runner: RulebookRunner<ActionAttack>) => {
- let action = runner.noun;
- ActionAttack.attackOnPerson(action);
- if (action.targetReaction == ActionAttackReaction.COUNTERATTACK && (<Person> action.getTarget()).stance != PersonStance.KNOCKEDOUT) { // Safeguard against targets that were killed
- let counter = new ActionAttack(action.getTarget(), action.actor);
- counter.actorDamageRoll = action.targetDamageRoll;
- counter.actorForceRoll = action.targetForceRoll;
- counter.actorDexRoll = 100; // Counter Attacks always hit
- counter.targetDexRoll = 0;
- counter.targetReaction = ActionAttackReaction.DODGE;
- ActionAttack.attackOnPerson(counter);
- action.say.add(Say.PARAGRAPH_BREAK, counter.say);
- }
- }
- });
- public static attackOnPerson (action : ActionAttack) {
- let actor = action.actor;
- let target = action.getTarget();
- let isHit = Dice.testAgainstRoll(
- {name : "Attacker's Hit", value : action.actorDexRoll},
- {name : "Target's Dodge", value : action.targetDexRoll}
- ) > 0;
- if (!(target instanceof Person)) {
- ActionAttack.breakThing(action);
- return;
- }
- if (!isHit) {
- action.generateDescription(
- (new ContentGroup())
- .addUnit(
- (new CombatUnit())
- .setActor(actor)
- .setTarget(target)
- .setWeapon(...action.getWeapons())
- .addMarker(CombatHit.MISS)
- )
- );
- return; // End of action
- }
- let dice = new Dice("Combat Roll");
- let actorDamage = dice.rollAndSum(action.actorDamageRoll);
- // TODO: Add torso armor.
- let targetRes = target.getStat(Attributes.Strength) * 0.25;
- let previousHealth = target.getHealthOnScale();
- let criticalHitThreshold = 4; // Chosen arbitrarily.
- let isKnockedDown = false;
- if (target.stance == PersonStance.STANDING) {
- isKnockedDown = Dice.testAgainstRoll(
- {name : "Attacker's Force", value : action.actorForceRoll},
- {name : "Target's Resistance", value : target.getStat(Attributes.Strength)}
- ) > 0;
- }
- let finalHealth = target.getHealthOnScale();
- let finalDamage = Math.floor(actorDamage - targetRes) * 10; // Multiplies by 10 to make it work with Torsos Soreness
- action.aggressivenessRating = Math.max(finalDamage / 10, 1); // no need to multiply by 10 here.
- if (action.targetReaction == ActionAttackReaction.RESIST) {
- finalDamage = finalDamage * 2 / 3; // TODO: Figure out if two thirds is enough.
- }
- if (target instanceof Humanoid) {
- (<HumanoidTorso> target.getPart(HumanoidTorso)).changeSoreness(finalDamage);
- } else{
- console.warn("CANT DAMAGE");
- }
- let markers = [];
- if (finalDamage >= criticalHitThreshold) {
- markers.push(CombatHit.CRITICAL);
- } else {
- markers.push(CombatHit.HIT);
- }
- if (previousHealth > 0 && finalHealth <= 0) {
- markers.push(CombatResult.KNOCKED_OFF);
- target.stance = PersonStance.KNOCKEDOUT;
- } else if (finalHealth <= -10) {
- markers.push(CombatResult.KILLED);
- target.stance = PersonStance.KNOCKEDOUT;
- target.die();
- } else if (isKnockedDown) {
- markers.push(CombatResult.KNOCKED);
- target.stance = PersonStance.ALLFOURS;
- }
- action.generateDescription(
- (new ContentGroup())
- .addUnit(
- (new CombatUnit())
- .setActor(actor)
- .setTarget(target)
- .setWeapon(...action.getWeapons())
- .addMarker(...markers)
- )
- );
- }
- public static breakThing (action : ActionAttack) {
- let actor = action.actor;
- let target = action.getTarget();
- let dice = new Dice("Combat Roll");
- let actorDamage = dice.rollAndSum(action.actorDamageRoll);
- if (actorDamage >= target.breakableOn) {
- action.generateDescription(
- (new ContentGroup())
- .addUnit(
- (new CombatUnit())
- .setActor(actor)
- .setTarget(target)
- .setWeapon(...action.getWeapons())
- .addMarker(CombatHit.CRITICAL)
- )
- );
- target.break();
- } else {
- action.generateDescription(
- (new ContentGroup())
- .addUnit(
- (new CombatUnit())
- .setActor(actor)
- .setTarget(target)
- .setWeapon(...action.getWeapons())
- .addMarker(CombatHit.HIT)
- )
- );
- }
- }
- public static getEffectiveCombatDex (person : Person) {
- let dex = person.getStat(Attributes.Agility);
- if (person.stance != PersonStance.STANDING) {
- return dex / 2;
- }
- return dex;
- }
- }
- 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));
- }
- }
- }
- ));
|