Bläddra i källkod

The new combat is even worse, but at least it works?

Reddo 5 år sedan
förälder
incheckning
f287323fef

+ 7 - 0
app/Elements/Modules/AppearanceHandler.ts

@@ -108,6 +108,13 @@ module Elements.AppearanceHandler {
             presentation.add(Say.PARAGRAPH_BREAK, "You are currently crouching.");
         }
 
+        let health = player.getHealthOnScale();
+        if (health < 3) {
+            presentation.add(Say.PARAGRAPH_BREAK, "You severely injured!");
+        } else if (health < 7) {
+            presentation.add(Say.PARAGRAPH_BREAK, "You are hurt.");
+        }
+
         await print(presentation);
     }
 }

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

@@ -83,11 +83,14 @@ class AI {
 
         if (this.hostileTargets.length > 0) {
             for (let i = this.hostileTargets.length - 1; i >= 0; i--) {
-                if (this.actor.getRoom() == this.hostileTargets[i].getRoom()) {
+                if (this.hostileTargets[i].isVisibleTo(this.actor)) {
                     inCombat = true;
                     break;
                 }
             }
+        } else if (this.actor.reputation < -9) {
+            this.hostileTargets.push(WorldState.player);
+            inCombat = WorldState.player.isVisibleTo(this.actor);
         }
 
         let result : Action;

+ 4 - 1
app/World/Classes/AI/AIHitBack.ts

@@ -7,7 +7,10 @@ module AIRules {
             let person = runner.noun;
 
             let hostiles = [...person.AI.hostileTargets].filter(value => {
-                return person.AI.newNoticed.includes(value);
+                return person.AI.newNoticed.includes(value) &&
+                        (
+                            !(value instanceof Person) || value.stance != PersonStance.KNOCKEDOUT
+                        ); // by default, stop hitting unconscious people.
             });
 
             if (hostiles.length > 0) {

+ 1 - 18
app/World/Classes/AI/Combat/BasicCombat.ts

@@ -33,24 +33,7 @@ module AIRules {
             }
         },
         conditions: (runner: RulebookRunner<Person>) => {
-            return runner.noun.AI.hostileTargets.length > 0;
-        }
-    });
-
-
-    export var rngHit = AI.rules.createAndAddRule({
-        name: "Hit someone else RNGly",
-        firstPriority: AIRules.PRIORITY_ACTING_ON_IDLE,
-        priority: AIRules.PRIORITY_ACTING_ON_SITUATION,
-        code: (runner: RulebookRunner<Person>) => {
-            for (let i = 0; i < runner.noun.AI.newNoticed.length; i++) {
-                if (runner.noun.AI.newNoticed[i] instanceof Person && runner.noun.AI.newNoticed[i] != WorldState.player) {
-                    return new ActionAttack(runner.noun, runner.noun.AI.newNoticed[i]);
-                }
-            }
-        },
-        conditions: (runner: RulebookRunner<Person>) => {
-            return (Math.random() * 100) >= 50;
+            return runner.noun.AI.hostileTargets.length > 0 && Math.random() < 0.5; // add randomness to allow escape
         }
     });
 }

+ 3 - 1
app/World/Classes/AI/Reaction/ReactToAggressive.ts

@@ -32,10 +32,12 @@ module AIRules {
             let pai = runner.noun.AI;
             let action = pai.reactingTo;
 
+            person.AI.addHostility(action.actor, action.aggressivenessRating);
+            person.reputation -= action.aggressivenessRating;
+
             if (person.AI.hostileTargets.includes(action.actor)) {
                 return; // Already hostile
             }
-            person.AI.addHostility(action.actor, action.aggressivenessRating);
             if (action.actor == WorldState.player) {
                 person.reputation -= action.aggressivenessRating;
             }

+ 16 - 1
app/World/Classes/Action.ts

@@ -207,7 +207,6 @@ Action.check.addRule(
     })
 );
 
-// TODO: Pass everything on here directly to the AI so that IT can handle this.
 Action.carry.addRule(
     new Rule({
         name : "Check any Action - Angery",
@@ -224,4 +223,20 @@ Action.carry.addRule(
             return action.actingAgressively && action.getNoun(0) instanceof Person;
         }
     })
+);
+
+Action.carry.addRule(
+    new Rule({
+        name : "Carry any Action - Interrupt through Aggressive",
+        firstPriority : Rule.PRIORITY_LOWEST,
+        priority: Rule.PRIORITY_LOWEST,
+        code : async (rulebook : RulebookRunner<Action>) => {
+            await Elements.CurrentTurnHandler.printAsContent(new Say("Interrupted."));
+            TurnSequence.repeatedAction = undefined;
+        },
+        conditions : (rulebook : RulebookRunner<Action>) => {
+            let action = <Action> rulebook.noun;
+            return action.actingAgressively && WorldState.isPlayer(action.getNoun(0));
+        }
+    })
 );

+ 215 - 123
app/World/Classes/Action/ActionAttack.ts

@@ -1,17 +1,32 @@
 /// <reference path="../Action.ts" />
 /// <reference path="../Rule.ts" />
 /// <reference path="../Rulebook.ts" />
-/// <reference path="../Things/Weapon/Weapon.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;
@@ -21,7 +36,7 @@ class ActionAttack extends Action {
     public static gettingAttacked = new OneOf(OneOf.PURELY_AT_RANDOM,
         "moves on to attack you!",
         "attacks you!",
-        "tries to attack you!",
+        "starts to attack you!",
         "decides to attack you!",
         "initiates an attack!",
         "is attacking!",
@@ -53,7 +68,14 @@ class ActionAttack extends Action {
         let weaponNoun = this.getNoun(1);
 
         if (weaponNoun == undefined) {
-            let weapons = <Array<Weapon>> this.actor.getWorns(Weapon);
+            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;
         }
 
@@ -82,6 +104,7 @@ class ActionAttack extends Action {
     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();
@@ -95,152 +118,221 @@ class ActionAttack extends Action {
         }
     });
 
-    // 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?",
+        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;
-            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);
-            }
+            action.targetReaction = ActionAttackReaction.DODGE;
+            action.actorDexRoll = ActionAttack.getEffectiveCombatDex(actor);
+            action.targetDexRoll = 0;
 
-            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;
-                }
-
+                action.targetDexRoll = ActionAttack.getEffectiveCombatDex(actor);
 
-                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;
-                        }
+                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 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)
-                }
+            let getDamage = (person : Person, ...weapons : Array<Weaponized>) => {
+                let damage = 0;
+                weapons.forEach(weapon => {
+                    damage += weapon.getAttributeDamageFactor() * person.getStat(weapon.getAttribute());
+                });
+                return damage;
+            };
 
-                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;
-                    }
-                }
+            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 - Go for the throat!",
+        name: "Attack - Actor Attacks Person",
+        firstPriority : Rule.PRIORITY_HIGH,
+        priority : Rule.PRIORITY_MEDIUM,
         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);
+            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);
             }
+        }
+    });
 
-            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);
-                }
+    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;
 
-                let hitType = (finalDamage * torso.getWeightedSoreness()) > (target.getMaxHealth() / 3) ? CombatHit.CRITICAL : CombatHit.HIT;
-                let targetHealth = target.getHealthOnScale();
-                let knockedOff = hitType == CombatHit.CRITICAL;
+        if (!(target instanceof Person)) {
+            ActionAttack.breakThing(action);
+            return;
+        }
 
-                if (knockedOff) {
-                    target.stance = PersonStance.ALLFOURS;
-                }
+        if (!isHit) {
+            action.generateDescription(
+                (new ContentGroup())
+                    .addUnit(
+                        (new CombatUnit())
+                            .setActor(actor)
+                            .setTarget(target)
+                            .setWeapon(...action.getWeapons())
+                            .addMarker(CombatHit.MISS)
+                    )
+            );
+            return; // End of action
+        }
 
-                let result = targetHealth < -9 ? CombatResult.KILLED : targetHealth <= 0 ? CombatResult.KNOCKED_OFF :
-                                knockedOff ? CombatResult.KNOCKED : undefined;
+        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.
 
-                if (targetHealth < -9) {
-                    target.die();
-                }
+        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();
 
 
-                action.generateDescription(
-                    (new ContentGroup())
-                        .addUnit(
-                            (new CombatUnit())
-                                .setActor(action.actor)
-                                .setTarget(target)
-                                .setWeapon(...weapons)
-                                .addMarker(hitType, result)
-                        )
-                );
-            }
+        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(

+ 4 - 16
app/World/Classes/Things/Bodypart.ts

@@ -54,30 +54,18 @@ class Bodypart extends Thing {
         });
     }
 
-    public updateSoreness () {
-        let cTurn = WorldState.getCurrentTurn();
-        if (cTurn > this.lastSorenessUpdate) {
-            if (this.soreness > 0) {
-                this.soreness -= (this.sorenessPerTurn * (cTurn - this.lastSorenessUpdate));
-
-                if (this.soreness < 0) {
-                    this.soreness = 0;
-                }
-            }
-            this.lastSorenessUpdate = cTurn;
-        }
-    }
-
     public changeSoreness (soreness : number) {
-        this.updateSoreness();
         this.soreness += soreness;
         if (this.soreness < 0) {
             this.soreness = 0;
         }
     }
 
+    public regenerate() {
+        this.changeSoreness(- this.sorenessPerTurn);
+    }
+
     public getSoreness () {
-        this.updateSoreness();
         return this.soreness;
     }
 

+ 22 - 1
app/World/Classes/Things/Bodypart/Humanoid/HumanoidHands.ts

@@ -4,7 +4,8 @@
 /// <reference path="../SexHole.ts" />
 /// <reference path="../../../Measure.ts" />
 /// <reference path="HumanoidExtremity.ts" />
-class HumanoidHands extends HumanoidExtremity {
+/// <reference path="../../Person/Attribute.ts" />
+class HumanoidHands extends HumanoidExtremity implements Weaponized {
     public sorenessWeight = Bodypart.WEIGHT_LOW;
     public genderWeight = Bodypart.WEIGHT_MEDIUM;
     public slots : Array<number> = [Humanoid.SLOT_HANDS];
@@ -21,4 +22,24 @@ class HumanoidHands extends HumanoidExtremity {
         }
         return say;
     }
+
+    public getAttribute () {
+        return Attributes.Strength;
+    }
+
+    public getAttributeDamageFactor () {
+        return 0.5;
+    }
+
+    public getAttributeForceFactor () {
+        return 0.75;
+    }
+
+    public getAccuracy () {
+        return 0;
+    }
+
+    public getCost () {
+        return 1;
+    }
 }

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

@@ -49,6 +49,7 @@ class Humanoid extends Person {
     public isButtVisible = false;
     public isButtTight = false;
 
+    // TODO : I want to remove these !!!!! We are no longer on fucking inform!
     public isGenderCached : boolean = false;
     public isSluttinessCached : boolean = false;
     public cachedGenderValue : HumanoidGender;

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

@@ -106,9 +106,7 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
      * @returns {number}
      */
     public getHealth (important? : boolean) {
-        if (important === true || this.lastHealthUpdate != WorldState.getCurrentTurn()) {
-            this.updateHealth();
-        }
+        this.updateHealth();
         return this.soreness / this.getMaxHealth()
     }
 

+ 32 - 22
app/World/Classes/Things/Weapon/Weapon.ts

@@ -4,14 +4,18 @@ interface WeaponOptions extends ClothingOptions {
     leftHand : boolean;
     rightHand : boolean;
     attribute? : Attribute;
-    baseDamage? : number;
     attackCost? : number;
+    attributeDamageFactor? : number;
+    attributeForceFactor? : number;
+    accuracy? : number;
 }
 
-class Weapon extends Clothing {
-    private baseDamage : number = 0;
-    private attribute = undefined;
-    public attackCost = 1;
+class Weapon extends Clothing implements Weaponized {
+    private attribute : Attribute;
+    private attributeDamageFactor : number;
+    private attackCost : number;
+    private attributeForceFactor : number;
+    private accuracy : number;
 
     constructor (t : WeaponOptions) {
         super(t);
@@ -21,27 +25,33 @@ class Weapon extends Clothing {
         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.attributeDamageFactor = t.attributeDamageFactor == undefined ? 1 : t.attributeDamageFactor;
+        this.attributeForceFactor = t.attributeForceFactor == undefined ? 1 : t.attributeForceFactor;
+        this.accuracy = t.accuracy == undefined ? 0 : t.accuracy;
         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));
+    /**
+     * Implements Weaponized
+     */
+    public getAttribute () {
+        return this.attribute;
+    }
 
-        if (this.slots.length == 2) {
-            return Math.floor(result * 5 / 3); // Twohanded modifier
-        } else {
-            return Math.floor(result * 2 / 3); // One-handed modifier
-        }
+    public getAttributeDamageFactor () {
+        return this.attributeDamageFactor;
+    }
+
+    public getAttributeForceFactor () {
+        return this.attributeForceFactor;
+    }
+
+    public getAccuracy () {
+        return this.accuracy;
+    }
+
+    public getCost () {
+        return this.attackCost;
     }
 }

+ 12 - 0
app/World/EveryTurn.ts

@@ -62,6 +62,18 @@ module EveryTurn {
         }
     });
 
+    export var regenerateBodyparts = EveryTurn.createAndAddRule({
+        firstPriority: Rule.PRIORITY_LOWEST,
+        priority: Rule.PRIORITY_MEDIUM,
+        name: "Regenerate Bodyparts",
+        code: function () {
+            let bodyparts = <Array<Bodypart>> Thing.PartRelation.getAnyRightType(Bodypart);
+            bodyparts.forEach(bodypart => {
+                bodypart.regenerate();
+            });
+        }
+    });
+
     export var incrementTurnCounterRule = EveryTurn.createAndAddRule({
         firstPriority: Rule.PRIORITY_LOWEST,
         priority: Rule.PRIORITY_LOWEST,