Преглед изворни кода

A lot about combat, some text is not working and can't figure out why

Reddo пре 5 година
родитељ
комит
bf18a05518

+ 31 - 0
app/Controls/Controls.ts

@@ -1,3 +1,34 @@
 module Controls {
+    export function createBigButton (text : string, resolve : (t : string) => void) {
+        let p = document.createElement("p");
+        p.classList.add("choice");
+        p.appendChild(document.createTextNode(text));
 
+        p.addEventListener("click", () => {
+            resolve(text);
+        });
+
+        Controls.KeyHandler.applyCode(p, Controls.KeyHandler.getFirstKeyCode());
+
+        return p;
+    }
+
+    export async function giveChoices (big? : boolean, ...choices : Array<string>) {
+        let buttons;
+        let chosenPromise = <Promise<string>> new Promise((async (resolve) => {
+            Controls.KeyHandler.reset();
+            let say = new Say();
+
+            choices.forEach(choice => {
+                say.add(createBigButton(choice, resolve))
+            });
+
+            buttons = await say.getHTMLContent();
+            Elements.CurrentTurnHandler.print(...(buttons));
+        }));
+
+        let chosen = await chosenPromise;
+        Elements.CurrentTurnHandler.unprint(...buttons);
+        return [chosen, choices.indexOf(chosen)];
+    }
 }

+ 2 - 1
app/World/Classes/AI.ts

@@ -17,7 +17,8 @@ class AI {
     public wanderChance = 50;
     public picksShinies = true;
     public hostileTo : Array<Thing> = [];
-    public grudgeRate = 50;
+    public anger = 0;
+    public grudgeRate = 10;
 
     public constructor (options : AIOptions) {
         for (let key in options) {

+ 17 - 0
app/World/Classes/AI/AIHitBack.ts

@@ -0,0 +1,17 @@
+/// <reference path="../AI.ts" />
+module AIRules {
+    export var attackInRange = AI.combatRules.createAndAddRule({
+        name : "Attack in range",
+        firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
+        code : (runner : RulebookRunner<Person>) => {
+            let person = runner.noun;
+            let hostileTo = person.AI.hostileTo;
+
+            for (let i = 0; i < hostileTo.length; i++) {
+                if (hostileTo[i].isVisibleTo(person)) {
+                    return new ActionAttack(person, hostileTo[i]);
+                }
+            }
+        }
+    });
+}

+ 57 - 0
app/World/Classes/Action.ts

@@ -1,5 +1,6 @@
 /// <reference path="Rulebook.ts" />
 /// <reference path="Rule.ts" />
+/// <reference path="Things/Person.ts" />
 /// <reference path="../../Elements/Classes/Say.ts" />
 class Action {
     public static check = new Rulebook<Action>("Check any Action");
@@ -13,10 +14,12 @@ class Action {
     public say : Say = new Say();
 
     public actingAgressively = false;
+    public aggressivenessRating = 1;
     public actingSubmissively = false;
     public requiresTurn = true;
     public requiresNoun = true;
     public requiresVisibility = true; // First noun must be visible and in the same room
+    public allowedStances = [PersonStance.ALLFOURS, PersonStance.STANDING];
 
 
     public constructor (actor : Thing, ...nouns : Array<any>) {
@@ -142,6 +145,26 @@ Action.check.addRule(
     })
 );
 
+Action.check.addRule(
+    new Rule({
+        name : "Check any Action - Stance",
+        firstPriority : Rule.PRIORITY_HIGHEST,
+        code : (rulebook : RulebookRunner<Action>) => {
+            let action = <Action> rulebook.noun;
+            let actor = action.actor;
+            if (actor instanceof Person) {
+                if (action.allowedStances.indexOf(actor.stance) == -1) {
+                    if (action.actor == WorldState.player) {
+                        action.say = new Say("You can't do that while ", PersonStanceNames[actor.stance], ", you need to be ", PersonStanceNames[actor.stance], ".");
+                    }
+                    return false;
+                    action.stop();
+                }
+            }
+        }
+    })
+);
+
 Action.check.addRule(
     new Rule({
         name : "Check any Action - Requires Visibility",
@@ -156,4 +179,38 @@ Action.check.addRule(
             return runner.noun.requiresVisibility;
         }
     })
+);
+
+Action.carry.addRule(
+    new Rule({
+        name : "Check any Action - Requires Visibility",
+        firstPriority : Rule.PRIORITY_LOWEST,
+        priority: Rule.PRIORITY_LOWEST,
+        code : (rulebook : RulebookRunner<Action>) => {
+            let action = <Action> rulebook.noun;
+            let target = action.getNoun(0);
+            let tai = (<Person> target).AI;
+            tai.anger += action.aggressivenessRating * tai.grudgeRate;
+            let cu = new CombatPokeUnit().setTarget(target);
+            if (tai.anger < 10) {
+                cu.addMarker(CombatPokeResult.NOHEAT);
+            } else if (tai.anger < 100) {
+                cu.addMarker(CombatPokeResult.ANNOYED);
+            } else {
+                cu.addMarker(CombatPokeResult.AGGROED);
+                (<Person>target).AI.hostileTo.push(action.actor);
+            }
+
+
+            action.say.add(Say.PARAGRAPH_BREAK, ...CombatDescription.getDescription(new ContentGroup(cu)));
+
+            action.say.getHTML("p", [], true).then((result:[HTMLElement])=> {
+                console.log(result[0].innerText);
+            });
+        },
+        conditions : (rulebook : RulebookRunner<Action>) => {
+            let action = <Action> rulebook.noun;
+            return action.actor == WorldState.player && action.actingAgressively && action.getNoun(0) instanceof Person && !action.getNoun(0).isHostileTo(action.actor) && action.getNoun(0).getHealthOnScale() > 0;
+        }
+    })
 );

+ 236 - 0
app/World/Classes/Action/ActionAttack.ts

@@ -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));
+            }
+        }
+    }
+));

+ 53 - 0
app/World/Classes/Action/ActionDropDown.ts

@@ -0,0 +1,53 @@
+/// <reference path="../Action.ts" />
+/// <reference path="../Rule.ts" />
+/// <reference path="../Rulebook.ts" />
+/// <reference path="../Things/Person.ts" />
+class ActionDropDown extends Action {
+    public static check = new Rulebook<ActionDropDown>("Check Stand");
+    public static carry = new Rulebook<ActionDropDown>("Carry out Stand");
+
+    public constructor (actor : Thing, ...nouns : Array<any>) {
+        super(actor, ...nouns);
+        this.requiresNoun = false;
+        this.requiresVisibility = false;
+    }
+
+    public getCommandText () {
+        return "stand up"
+    }
+
+    public checkStand = ActionDropDown.check.createAndAddRule({
+        name : "Stand - Can get down?",
+        code : (rulebook) => {
+            // TODO: Check if being held up
+        }
+    });
+
+    public carryStand = ActionDropDown.carry.createAndAddRule({
+        name : "Stand - Rise up!",
+        code : (runner : RulebookRunner<ActionDropDown>) => {
+            let actor = runner.noun.actor;
+            if (actor instanceof Person) {
+                actor.stance = PersonStance.ALLFOURS;
+
+                if (actor == WorldState.player) {
+                    runner.noun.say.add("You get down on all fours.");
+                } else {
+                    runner.noun.say.add(runner.noun.actor, " gets down.");
+                }
+            }
+        }
+    });
+}
+
+Elements.HyperlinkHandler.CommonActionsRulebook.addRule(new Rule(
+    {
+        name : "Hyperlink - Stand up!",
+        firstPriority : Rule.PRIORITY_MEDIUM,
+        code : (rulebook : RulebookRunner<void>) => {
+            if (WorldState.player.stance != PersonStance.ALLFOURS) {
+                Elements.HyperlinkHandler.addCommonAction("Get down", new ActionDropDown(WorldState.player));
+            }
+        }
+    }
+));

+ 5 - 3
app/World/Classes/Action/ActionFollow.ts

@@ -18,10 +18,12 @@ class ActionFollow extends Action {
     public static isCloseEnough (actor : Person, stalked : Thing) {
         let cRoom = <RoomRandom> actor.getRoom();
         let tRoom = <RoomRandom> stalked.getRoom();
-        let distance = tRoom.getDistanceTo(cRoom);
+        if (cRoom instanceof RoomRandom && tRoom instanceof RoomRandom) {
+            let distance = tRoom.getDistanceTo(cRoom);
 
-        if (distance == 1 || distance < (actor.getStat(Attributes.Intelligence)/2)) {
-            return true;
+            if (distance == 1 || distance < (actor.getStat(Attributes.Intelligence)/2)) {
+                return true;
+            }
         }
         return false;
     }

+ 54 - 0
app/World/Classes/Action/ActionStand.ts

@@ -0,0 +1,54 @@
+/// <reference path="../Action.ts" />
+/// <reference path="../Rule.ts" />
+/// <reference path="../Rulebook.ts" />
+/// <reference path="../Things/Person.ts" />
+class ActionStand extends Action {
+    public static check = new Rulebook<ActionStand>("Check Stand");
+    public static carry = new Rulebook<ActionStand>("Carry out Stand");
+
+    public constructor (actor : Thing, ...nouns : Array<any>) {
+        super(actor, ...nouns);
+        this.requiresNoun = false;
+        this.requiresVisibility = false;
+    }
+
+    public getCommandText () {
+        return "stand up"
+    }
+
+    public checkStand = ActionStand.check.createAndAddRule({
+        name : "Stand - Can stand?",
+        code : (rulebook) => {
+            // TODO: Check if being held down
+            // TODO: Check if incapacitated
+        }
+    });
+
+    public carryStand = ActionStand.carry.createAndAddRule({
+        name : "Stand - Rise up!",
+        code : (runner : RulebookRunner<ActionStand>) => {
+            let actor = runner.noun.actor;
+            if (actor instanceof Person) {
+                actor.stance = PersonStance.STANDING;
+
+                if (actor == WorldState.player) {
+                    runner.noun.say.add("You get up.");
+                } else {
+                    runner.noun.say.add(runner.noun.actor, " rises up.");
+                }
+            }
+        }
+    });
+}
+
+Elements.HyperlinkHandler.CommonActionsRulebook.addRule(new Rule(
+    {
+        name : "Hyperlink - Stand up!",
+        firstPriority : Rule.PRIORITY_MEDIUM,
+        code : (rulebook : RulebookRunner<void>) => {
+            if (WorldState.player.stance == PersonStance.ALLFOURS) {
+                Elements.HyperlinkHandler.addCommonAction("Get up", new ActionStand(WorldState.player));
+            }
+        }
+    }
+));

+ 8 - 0
app/World/Classes/ContentPicker/Combat/CombatDescription.ts

@@ -1,4 +1,6 @@
 /// <reference path="../ContentDescription.ts" />
+/// <reference path="CombatPokeUnit.ts" />
+/// <reference path="CombatUnit.ts" />
 /**
  * Quick Cheat Sheet of markers!
  * When making a description take these markers into account while describing the action! If a marker describes something
@@ -34,6 +36,12 @@ class CombatDescription extends ContentDescription {
         return unit;
     }
 
+    public addPokeUnit () {
+        let unit = new CombatPokeUnit();
+        (<ContentGroup> this.group).addUnit(unit);
+        return unit;
+    }
+
     public static getDescription (target : ContentGroup) {
         return ContentDescription.pickDescriptions(CombatDescription.DESCRIPTIONS, target);
     }

+ 6 - 0
app/World/Classes/ContentPicker/Combat/CombatMarker.ts

@@ -18,4 +18,10 @@ class CombatResult extends ContentMarker {
     public static KNOCKED = new CombatResult("Target was knocked down by the attack", true);
     public static KNOCKED_OFF = new CombatResult("Target was knocked off by the attack, becoming unconscious", true);
     public static KILLED = new CombatResult("Target was killed by this attack", true);
+}
+
+class CombatPokeResult extends ContentMarker {
+    public static AGGROED = new CombatResult("Target is now hostile", true);
+    public static ANNOYED = new CombatResult("Target is almost hostile", true);
+    public static NOHEAT = new CombatResult("Target didn't mind", true);
 }

+ 36 - 0
app/World/Classes/ContentPicker/Combat/CombatPokeUnit.ts

@@ -0,0 +1,36 @@
+/// <reference path="../ContentUnit.ts" />
+/// <reference path="../../Things/Person.ts" />
+/// <reference path="../../Things/Bodypart/SexHole.ts" />
+/// <reference path="../../Things/Bodypart/SexStick.ts" />
+class CombatPokeUnit extends ContentUnit {
+    private target : ContentDifferential = new ContentDifferential(Person);
+    private markers : ContentDifferential = new ContentDifferential();
+
+    public constructor () {
+        super();
+    }
+
+
+    public setTarget (it : Thing | typeof Thing) {
+        this.target = new ContentDifferential(it);
+        return this;
+    }
+
+
+    public addMarker (marker : ContentMarker) {
+        this.markers.addNoun(marker);
+        return this;
+    }
+
+    public getScore () {
+        return this.target.getScore() + this.markers.getScore();
+    }
+
+    public isMatch (cu : CombatPokeUnit) {
+        if (cu instanceof CombatPokeUnit) {
+            return this.target.isMatch(cu.target) &&
+                this.markers.isMatch(cu.markers);
+        }
+        return false;
+    }
+}

+ 2 - 2
app/World/Classes/ContentPicker/Combat/CombatUnit.ts

@@ -22,8 +22,8 @@ class CombatUnit extends ContentUnit {
 		return this;
 	}
 
-	public setWeapon (it : Thing | typeof Thing) {
-		this.weapon = new ContentDifferential(it);
+	public setWeapon (...it : Array<Thing | typeof Thing>) {
+		this.weapon = new ContentDifferential(...it);
 		return this;
 	}
 

+ 4 - 1
app/World/Classes/ContentPicker/ContentDifferential.ts

@@ -13,7 +13,9 @@ class ContentDifferential {
 
     public addNoun (...nouns : Array<any>) {
         nouns.forEach(noun => {
-            this.nouns.push(noun);
+            if (noun != undefined) {
+                this.nouns.push(noun);
+            }
         });
         this.score = this.getScore();
         return this;
@@ -30,6 +32,7 @@ class ContentDifferential {
 
     public isMatch (cd : ContentDifferential, allowPartial = false) {
         let check = this.getUnmatched(cd);
+        console.warn("Comparing", cd , "to ", check);
         if ((allowPartial || check.unmatched.length == 0) && check.matching.length == 0) {
             return true;
         } else if (check.matching.length == 0) {

+ 32 - 0
app/World/Classes/Thing.ts

@@ -9,6 +9,9 @@ interface ThingOptions {
     unique? : boolean;
     image? : SayImage | string;
     shiny? : boolean;
+    breakable? : boolean;
+    resistance? : number;
+    breaksTo? : string;
 }
 
 // A thing is something that exists in the World
@@ -23,6 +26,9 @@ class Thing implements Printable {
     public unique : boolean = false;
     public image : SayImage;
     protected shiny : boolean = false;
+    public breakable = false;
+    public breakableOn = 5;
+    public breaksTo;
 
     protected setAlterations : Array<(thisObject : Thing, simpleAlterationObject : {[id : string] : any}) => void> = [];
     protected getAlterations : Array<(thisObject : Thing) => {[id : string] : any}> = [];
@@ -50,6 +56,10 @@ class Thing implements Printable {
     public constructor (options? : ThingOptions) {
         options = options == undefined ? {} : options;
 
+        this.breakable = options.breakable === true;
+        this.breakableOn = options.resistance == undefined ? 5 : options.resistance;
+        this.breaksTo = options.breaksTo;
+
         if (options.properName != undefined) {
             this.name = options.properName;
             this.properlyNamed = true;
@@ -284,6 +294,13 @@ class Thing implements Printable {
         }
     }
 
+    public getWorns (wornType? : any) {
+        if (wornType != undefined) {
+            return Thing.WearRelation.getRightType(this, wornType);
+        }
+        return Thing.WearRelation.getRight(this);
+    }
+
     public getParts (partType? : any) {
         if (partType != undefined) {
             return Thing.PartRelation.getRightType(this, partType);
@@ -376,6 +393,21 @@ class Thing implements Printable {
         }
     }
 
+    public break () {
+        if (this.breaksTo != undefined) {
+            let unique = Thing.getUnique(this.breaksTo);
+            if (unique != undefined) {
+                this.getRoom().place(unique);
+            } else {
+                let nonunique = Thing.getOneThing(this.breaksTo);
+                if (nonunique != undefined) {
+                    this.getRoom().place(nonunique.clone())
+                }
+            }
+        }
+        this.destroy();
+    }
+
     /**
      * So long as a thing is in a relation, it can't be garbage cleaned.
      * If you're throwing a thing away, do run destroy.

+ 9 - 0
app/World/Classes/Things/Clothing.ts

@@ -3,6 +3,8 @@ interface ClothingOptions extends ThingOptions {
     topDescription? : Say;
     bottomDescription? : Say;
     feetDescription? : Say;
+    slots? : Array<number>;
+    layer? : number;
 }
 
 interface ClothingWearerValue {
@@ -50,6 +52,13 @@ class Clothing extends Thing {
     public tightButtSize : number = -1;
     public looseButtSize : number = 0;
 
+    constructor (t : ClothingOptions) {
+        super(t);
+        this.layer = t.layer == undefined ? Clothing.LAYER_MEDIUM : t.layer;
+        this.slots = t.slots == undefined ? [] : [...t.slots];
+
+    }
+
     /**
      * This function must be called any time anything could change clothing on a person.
      * Ripped a clothing? Update all clothes.

+ 3 - 1
app/World/Classes/Things/Humanoid/Humanoid.ts

@@ -602,7 +602,9 @@ class Humanoid extends Person {
     public static SLOT_FEET = 23;
     public static SLOT_FEET_NAILS = 24;
     public static SLOT_BREASTS = 25;
-    public static SLOT_SLOT_COUNT = 26;
+    public static SLOT_LEFTHAND = 26;
+    public static SLOT_RIGHTHAND = 27;
+    public static SLOT_SLOT_COUNT = 28;
 
     public static cacheInvalidationActionRule = new Rule({
         name : "Invalidate humanoid caches",

+ 21 - 4
app/World/Classes/Things/Person.ts

@@ -10,6 +10,15 @@
  *
  * Fuck your standards.
  */
+enum PersonStance {
+    STANDING, ALLFOURS, KNOCKEDOUT
+}
+
+let PersonStanceNames = {};
+PersonStanceNames[PersonStance.STANDING] = "standing";
+PersonStanceNames[PersonStance.ALLFOURS] = "kneeling";
+PersonStanceNames[PersonStance.KNOCKEDOUT] = "passed out";
+
 class Person extends Thing implements AttributeBearer, SkillBearer {
     public AI = new AI({actor: this});
     public animated = true;
@@ -23,6 +32,8 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
     public static MAX_STAMINA = 10;
     public static STRENGTH_SORENESS_MULTIPLIER = 4;
 
+    public stance : PersonStance = PersonStance.STANDING;
+
     public constructor (options : ThingOptions) {
         super(options);
 
@@ -64,6 +75,10 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
         });
     }
 
+    public isHostileTo (person : Thing) {
+        return this.AI.hostileTo.indexOf(person) != -1;
+    }
+
     public changeHealth (n : number) {
         let bodyparts = <Array<Bodypart>> this.getParts(Bodypart);
         for (let i = 0; i < bodyparts.length; i++) {
@@ -76,9 +91,7 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
      * Returns health as a number from 0 to 10.
      */
     public getHealthOnScale () {
-        return Math.round(
-            ((this.getHealth() * 10) / (this.getStat(Attributes.Strength) * 2))
-        );
+        return Math.ceil(10 - (this.getHealth() * 10));
     }
 
     /**
@@ -91,7 +104,11 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
         if (important === true || this.lastHealthUpdate != WorldState.getCurrentTurn()) {
             this.updateHealth();
         }
-        return this.soreness / (this.getStat(Attributes.Strength) * Person.STRENGTH_SORENESS_MULTIPLIER)
+        return this.soreness / this.getMaxHealth()
+    }
+
+    public getMaxHealth () {
+        return (this.getStat(Attributes.Strength) * Person.STRENGTH_SORENESS_MULTIPLIER);
     }
 
     /**

+ 47 - 0
app/World/Classes/Things/Weapon/Weapon.ts

@@ -0,0 +1,47 @@
+/// <reference path="../Clothing.ts" />
+/// <reference path="../Person/Attribute.ts" />
+interface WeaponOptions extends ClothingOptions {
+    leftHand : boolean;
+    rightHand : boolean;
+    attribute? : Attribute;
+    baseDamage? : number;
+    attackCost? : number;
+}
+
+class Weapon extends Clothing {
+    private baseDamage : number = 0;
+    private attribute = undefined;
+    public attackCost = 1;
+
+    constructor (t : WeaponOptions) {
+        super(t);
+        if (t.leftHand) {
+            this.slots.push(Humanoid.SLOT_LEFTHAND);
+        }
+        if (t.rightHand) {
+            this.slots.push(Humanoid.SLOT_RIGHTHAND);
+        }
+        this.baseDamage = t.baseDamage == undefined ? 0 : t.baseDamage;
+        this.attackCost = t.attackCost == undefined ? 1 : t.attackCost;
+        this.attribute = t.attribute;
+    }
+
+    public getDamage () {
+        let damage = this.baseDamage;
+        let wielder = <Person> this.getWearOne();
+        let attrValue : number;
+        if (this.attribute != undefined) {
+            attrValue = wielder.getStat(this.attribute);
+        } else {
+            attrValue = 0;
+        }
+        // TODO: Reconsider RNG.
+        let result = attrValue + (1 + Math.floor(Math.random() * damage));
+
+        if (this.slots.length == 2) {
+            return Math.floor(result * 5 / 3); // Twohanded modifier
+        } else {
+            return Math.floor(result * 2 / 3); // One-handed modifier
+        }
+    }
+}

+ 12 - 10
app/World/EveryTurn.ts

@@ -16,19 +16,21 @@ module EveryTurn {
 
             let people = <Array<Person>> Thing.InsideRoomRelation.getAnyRightType(Person).filter(isAIAvailable);
             for (let i = 0; i < people.length; i++) {
-                let action = await people[i].AI.execute();
                 let person = people[i];
-                let visible = people[i].isVisibleTo(WorldState.player);
+                if (person.getHealthOnScale() > 0) {
+                    let action = await people[i].AI.execute();
+                    let visible = people[i].isVisibleTo(WorldState.player);
 
-                if (action != undefined) {
-                    let printValue: Say = await action.execute();
+                    if (action != undefined) {
+                        let printValue: Say = await action.execute();
 
-                    if (
-                        (
-                            visible ||
-                            person.isVisibleTo(WorldState.player)
-                        ) && printValue != undefined) {
-                        Elements.CurrentTurnHandler.printAsContent(printValue);
+                        if (
+                            (
+                                visible ||
+                                person.isVisibleTo(WorldState.player)
+                            ) && printValue != undefined) {
+                            Elements.CurrentTurnHandler.printAsContent(printValue);
+                        }
                     }
                 }
             }