Browse Source

Aggressive action processing correctly handled by the AI.
Combat correctly handled by the AI.
Multiple improvements to all of it.
Life is Good.

Reddo 5 years ago
parent
commit
5161c5d9dc

+ 15 - 0
app/Elements/Classes/Say/SayAction.ts

@@ -0,0 +1,15 @@
+/// <reference path="../Say.ts" />
+class SayAction extends Say {
+    public async getPureElements () : Promise<Array<Element | Text>> {
+        let paragraphs = await this.getParagraphs();
+
+        let elements = paragraphs.length == 1 ? paragraphs[0] : Array.prototype.concat.apply([], paragraphs);
+
+        let b = document.createElement("b");
+        b.classList.add("action");
+        elements.forEach((element) => {
+            b.appendChild(element);
+        });
+        return [b];
+    }
+}

+ 122 - 19
app/World/Classes/AI.ts

@@ -4,10 +4,14 @@
 /// <reference path="../../Functions.ts" />
 /// <reference path="../../Functions.ts" />
 
 
 interface AIOptions {
 interface AIOptions {
-    actor : Thing,
-    wanderer? : boolean,
-    wandersOn? : Region,
-    picksShinies? : boolean
+    actor : Thing;
+    wanderer? : boolean;
+    wandersOn? : Region;
+    picksShinies? : boolean;
+    grudgeRate? : number;
+    hostileThreshold? : number;
+    coolOffRate? : number;
+    retaliates? : boolean;
 }
 }
 
 
 class AI {
 class AI {
@@ -16,9 +20,26 @@ class AI {
     public wandersOn : Region;
     public wandersOn : Region;
     public wanderChance = 50;
     public wanderChance = 50;
     public picksShinies = true;
     public picksShinies = true;
-    public hostileTo : Array<Thing> = [];
-    public anger = 0;
-    public grudgeRate = 10;
+    public hostilityTargets : Array<Thing> = [];
+    public hostilityLevels : Array<number> = [];
+    public hostileTargets : Array<Thing> = []; // Cache hostilities over 100 to reduce processing usage.
+    public grudgeRate : number = 50; // Multiplies aggression level to determine anger.
+    public retaliates = false;
+    public warnedTimes = 0;
+    public hostileThreshold : number  = 100; // Amount of anger at which it becomes entirely hostile
+    public coolOffRate : number = 5; // Amount, per turn, that rage subdues
+    public noticed : Array<Thing> = [];
+    public newNoticed : Array<Thing> = [];
+    public newArrivals : Array<Thing> = [];
+    public oldLeavers : Array<Thing> = [];
+
+
+    public static rules = new Rulebook<Thing>("Default AI Rules");
+    public extraRules : Array<Rulebook<Thing>> = [];
+    public static combatRules = new Rulebook<Thing>("Default AI Combat Rules");
+    public extraCombatRules : Array<Rulebook<Thing>> = [];
+    public static talktoRules = new Rulebook<Thing>("Default Talk To Rules");
+    public extraTalktoRules : Array<Rulebook<Thing>> = [];
 
 
     public constructor (options : AIOptions) {
     public constructor (options : AIOptions) {
         for (let key in options) {
         for (let key in options) {
@@ -34,30 +55,51 @@ class AI {
         let promise : Promise<Action>;
         let promise : Promise<Action>;
         let inCombat = false;
         let inCombat = false;
 
 
-        if (this.hostileTo.length > 0) {
-            for (let i = this.hostileTo.length - 1; i >= 0; i--) {
-                if (this.actor.getRoom() == this.hostileTo[i].getRoom()) {
+        if (this.hostileTargets.length > 0) {
+            for (let i = this.hostileTargets.length - 1; i >= 0; i--) {
+                if (this.actor.getRoom() == this.hostileTargets[i].getRoom()) {
                     inCombat = true;
                     inCombat = true;
                     break;
                     break;
                 }
                 }
             }
             }
         }
         }
 
 
+        let result : Action;
         if (inCombat) {
         if (inCombat) {
+            // TUNNEL VISION
             promise = AI.combatRules.execute({
             promise = AI.combatRules.execute({
                 noun : this.actor
                 noun : this.actor
             }, ...this.extraCombatRules);
             }, ...this.extraCombatRules);
-        } else {
-            promise = AI.rules.execute({
-                noun : this.actor
-            }, ...this.extraRules);
+
+            result = await promise;
+            if (result != undefined) {
+                return result;
+            }
+            // No action was found?
         }
         }
 
 
-        let result : Action = await promise;
+        this.renoticeThings();
+        promise = AI.rules.execute({
+            noun : this.actor
+        }, ...this.extraRules);
+
+        result = await promise;
+        this.coolOff();
 
 
         return result;
         return result;
     }
     }
 
 
+    public renoticeThings () {
+        this.noticed = this.newNoticed;
+        this.newNoticed = [];
+        let stuff = this.actor.getRoom().getContainedAndVisibleTo(this.actor);
+        for (let i = stuff.length - 1; i >= 0; i--) {
+            this.newNoticed.push(stuff[i]);
+        }
+        this.newNoticed = this.newNoticed.filter( ( el ) => !this.noticed.includes( el ) );
+        this.oldLeavers = this.noticed.filter( ( el ) => !this.newNoticed.includes( el ) );
+    }
+
     public addRulesBook (...books : Array<Rulebook<Thing>>) {
     public addRulesBook (...books : Array<Rulebook<Thing>>) {
         this.extraRules.push(...books)
         this.extraRules.push(...books)
         arrayUnique(this.extraRules);
         arrayUnique(this.extraRules);
@@ -68,10 +110,54 @@ class AI {
         arrayUnique(this.extraCombatRules);
         arrayUnique(this.extraCombatRules);
     }
     }
 
 
-    public static rules = new Rulebook<Thing>("Default AI Rules");
-    public extraRules : Array<Rulebook<Thing>> = [];
-    public static combatRules = new Rulebook<Thing>("Default AI Combat Rules");
-    public extraCombatRules : Array<Rulebook<Thing>> = [];
+    public addHostility (target : Thing, amount : number) {
+        amount = amount * this.grudgeRate;
+        let index = this.hostilityTargets.indexOf(target);
+        if (index == -1) {
+            index = this.hostilityTargets.push(target);
+            this.hostilityLevels.push(amount);
+        } else {
+            this.hostilityLevels[index] += amount;
+        }
+        if (this.hostilityLevels[index] >= this.hostileThreshold && !this.hostileTargets.includes(target)) {
+            this.hostileTargets.push(target);
+        } else if (this.hostilityLevels[index] <= 0) {
+            this.removeHostility(index, target);
+        }
+    }
+
+    public removeHostility (index, target) {
+        let hostile = this.hostileTargets.indexOf(target);
+        if (hostile != -1) {
+            this.hostileTargets.splice(hostile, 1);
+        }
+        this.hostilityTargets.splice(index, 1);
+        this.hostilityLevels.splice(index, 1);
+    }
+
+    public coolOff () {
+        for (let i = this.hostilityTargets.length - 1; i >= 0; i--) {
+            this.hostilityLevels[i] -= this.coolOffRate;
+            if (this.hostilityLevels[i] <= 0) {
+                this.removeHostility(i, this.hostilityTargets[i]);
+            }
+            if (this.hostilityLevels[i] < this.hostileThreshold) {
+                let hostile = this.hostileTargets.indexOf(this.hostilityTargets[i]);
+                if (hostile != -1) {
+                    this.hostileTargets.splice(hostile, 1);
+                }
+            }
+        }
+    }
+
+    public getHostilityTo (target : Thing) {
+        let index = this.hostilityTargets.indexOf(target);
+        if (index != -1) {
+            return this.hostilityLevels[index];
+        } else {
+            return 0;
+        }
+    }
 }
 }
 
 
 module AIRules {
 module AIRules {
@@ -95,4 +181,21 @@ module AIRules {
      * @type {number}
      * @type {number}
      */
      */
     export var PRIORITY_ACTING_ON_IDLE = 1;
     export var PRIORITY_ACTING_ON_IDLE = 1;
+}
+
+module AIDialogueRules {
+    /**
+     * This is for when the NPC must talk about something that is currently happening.
+     * For "Talk To", this means that whatever is happening is much more important than whatever the player might want to talk about.
+     *  Like if the player is currently fighting the NPC, or the player has just killed the NPC's best friend and this is kind of more urgent.
+     * @type {number}
+     */
+    export var PRIORITY_TALKING_ABOUT_SITUATION = 5;
+
+    /**
+     * This is for rules for when the NPC has nothing better to do.
+     * For "Talk To", this is the default rule for when the player talks to the enemy. We'll probably only have one rule here for each npc type, possibly more than one only if the NPC's demeanor changes, idk.
+     * @type {number}
+     */
+    export var PRIORITY_ACTING_ON_IDLE = 1;
 }
 }

+ 53 - 8
app/World/Classes/AI/AIGrudge.ts

@@ -1,19 +1,64 @@
 /// <reference path="../AI.ts" />
 /// <reference path="../AI.ts" />
+///<reference path="../ContentPicker/ContentMarker.ts"/>
 module AIRules {
 module AIRules {
+    // Only one at a time
+    export var actionMin = new ContentMarker("That pissed me off a little", true);
+    export var actionMed = new ContentMarker("That pissed me off", true);
+    export var actionMax = new ContentMarker("That pissed me off HARD", true);
+
+    // Only one at a time
+    export var resultNotHostile = new ContentMarker("I'm not gonna do anything about it", true);
+    export var resultRetaliate = new ContentMarker("I'll hit you once so you can see how you like it", true);
+    export var resultHostile = new ContentMarker("I'll hit you until you drop dead.", true);
+
+    export function printGrudgeResult (aggressor : Thing, victim : Person, ...markers : Array<ContentMarker>) {
+        let group = new ContentGroup();
+        let unit = new CombatPokeUnit();
+        group.addUnit(unit);
+        unit.setTarget(victim);
+        unit.setAggressor(aggressor);
+        unit.addMarker(...markers);
+
+        victim.AI.warnedTimes++;
+        Elements.CurrentTurnHandler.printAsContent(new Say(...CombatPokeDescription.getDescription(group)));
+    }
+
     export var Grudge = AI.rules.createAndAddRule({
     export var Grudge = AI.rules.createAndAddRule({
         name : "Grudge",
         name : "Grudge",
         firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
         firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
-        conditions : (runner : RulebookRunner<Person>) => {
-            let person = runner.noun;
-            return person.AI.hostileTo.length > 0 && (Math.random() * 100) <= person.AI.grudgeRate;
-        },
+        priority : AIRules.PRIORITY_ACTING_ON_SITUATION - 3,
         code : (runner : RulebookRunner<Person>) => {
         code : (runner : RulebookRunner<Person>) => {
             let person = runner.noun;
             let person = runner.noun;
-            let hostileTo = person.AI.hostileTo;
+            for (let i = 0; i < TurnSequence.currentTurnActionsTargets.length; i++) {
+                if (TurnSequence.currentTurnActionsTargets[i] == person) {
+                    let action = TurnSequence.currentTurnActions[i];
+                    if (action.actingAgressively) {
+                        person.AI.addHostility(action.actor, action.aggressivenessRating);
+                        if (action.actor == WorldState.player) {
+                            person.reputation -= action.aggressivenessRating;
+                        }
+
+                        let ai = person.AI;
+                        let gain = ai.grudgeRate * action.aggressivenessRating;
+                        let actionLevel = actionMin;
+                        let result = resultNotHostile;
+                        if (ai.getHostilityTo(action.actor) > 100) {
+                            result = resultHostile;
+                        } else if (ai.retaliates && ai.getHostilityTo(action.actor) >= (ai.hostileThreshold / 2)) {
+                            result = resultRetaliate;
+                        }
+                        if (gain >= (ai.hostileThreshold / 2)) {
+                            actionLevel = actionMax;
+                        } else if (gain >= (ai.hostileThreshold / 4)) {
+                            actionLevel = actionMed;
+                        }
+
+                        printGrudgeResult(action.actor, person, actionLevel, result);
 
 
-            for (let i = 0; i < hostileTo.length; i++) {
-                if (ActionFollow.isCloseEnough(person, hostileTo[i])) {
-                    return new ActionFollow(person, hostileTo[i]);
+                        if (result == resultRetaliate) {
+                            return new ActionAttack(person, action.actor);
+                        }
+                    }
                 }
                 }
             }
             }
         }
         }

+ 10 - 6
app/World/Classes/AI/AIHitBack.ts

@@ -1,16 +1,20 @@
 /// <reference path="../AI.ts" />
 /// <reference path="../AI.ts" />
 module AIRules {
 module AIRules {
     export var attackInRange = AI.combatRules.createAndAddRule({
     export var attackInRange = AI.combatRules.createAndAddRule({
-        name : "Attack in range",
+        name : "Attack highest aggro",
         firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
         firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
         code : (runner : RulebookRunner<Person>) => {
         code : (runner : RulebookRunner<Person>) => {
             let person = runner.noun;
             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]);
-                }
+            let hostiles = [...person.AI.hostileTargets].filter(value => {
+                return person.AI.newNoticed.includes(value);
+            });
+
+            if (hostiles.length > 0) {
+                hostiles.sort((a, b) => {
+                    return person.AI.getHostilityTo(b) - person.AI.getHostilityTo(a);
+                });
+                return new ActionAttack(person, hostiles[0]);
             }
             }
         }
         }
     });
     });

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

@@ -9,8 +9,7 @@ module AIRules {
         },
         },
         code : (runner : RulebookRunner<Person>) => {
         code : (runner : RulebookRunner<Person>) => {
             let person = runner.noun;
             let person = runner.noun;
-            let room = person.getRoom();
-            let visibleThings = room.getContainedAndVisibleTo(person);
+            let visibleThings = person.AI.newNoticed;
 
 
             if (visibleThings.length > 0) {
             if (visibleThings.length > 0) {
                 for (let i = 0; i < visibleThings.length; i++) {
                 for (let i = 0; i < visibleThings.length; i++) {

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

@@ -0,0 +1,18 @@
+/// <reference path="../../AI.ts" />
+/// <reference path="../../Things/Person.ts" />
+module AIRules {
+    export var standUp = new Rule({
+        name: "Stand if down",
+        firstPriority : AIRules.PRIORITY_ACTING_ON_SITUATION,
+        priority : AIRules.PRIORITY_ACTING_ON_SITUATION,
+        conditions : (runner : RulebookRunner<Person>) => {
+            let person = runner.noun;
+            return (person.stance != PersonStance.STANDING)
+        },
+        code : (runner : RulebookRunner<Person>) => {
+            return new ActionStand(runner.noun);
+        }
+    });
+    AI.combatRules.addRule(standUp);
+    AI.rules.addRule(standUp);
+}

+ 44 - 29
app/World/Classes/Action.ts

@@ -55,6 +55,14 @@ class Action {
 
 
         // There are now multiple results! A false result means a fail Check! But it can also return a new action!
         // There are now multiple results! A false result means a fail Check! But it can also return a new action!
         if (result == false) {
         if (result == false) {
+            if (this.requiresTurn) {
+                /**
+                 * Add to the list of actions performed in turn.
+                 * Notice the position of this thing: If the action failed, but an attempt was made, it still gets added to the board.
+                 * The importance of ths is, if you do something to annoy an NPC and you get stopped midway, they still get to react.
+                 */
+                TurnSequence.addAction(this);
+            }
             return;
             return;
         } else if(result instanceof Action) {
         } else if(result instanceof Action) {
             console.debug(Rulebook.getIndentation() + "[ACTION] Instead of...");
             console.debug(Rulebook.getIndentation() + "[ACTION] Instead of...");
@@ -74,6 +82,12 @@ class Action {
             noun : this
             noun : this
         }, ...carryRulebooks);
         }, ...carryRulebooks);
 
 
+        /**
+         * Add to the list of actions performed in turn.
+         * Notice the position of this thing: if an action was replaced by another, it will instead add that action.
+         */
+        TurnSequence.addAction(this);
+
         return this.say;
         return this.say;
     }
     }
 
 
@@ -85,6 +99,7 @@ class Action {
         this._actor = value;
         this._actor = value;
     }
     }
 
 
+    // TODO: The if shouldn't be necessary right? I don't wanna touch this right now.
     public getNoun (n : number) : any {
     public getNoun (n : number) : any {
         if (this.nouns.length > n) {
         if (this.nouns.length > n) {
             return this.nouns[n];
             return this.nouns[n];
@@ -182,32 +197,32 @@ Action.check.addRule(
 );
 );
 
 
 // TODO: Pass everything on here directly to the AI so that IT can handle this.
 // 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",
-        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, ...CombatPokeDescription.getDescription(new ContentGroup(cu)));
-        },
-        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;
-        }
-    })
-);
+// Action.carry.addRule(
+//     new Rule({
+//         name : "Check any Action - Angery",
+//         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, ...CombatPokeDescription.getDescription(new ContentGroup(cu)));
+//         },
+//         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;
+//         }
+//     })
+// );

+ 12 - 6
app/World/Classes/ContentPicker/AdaptiveDifferential.ts

@@ -1,8 +1,14 @@
+///<reference path="ContentDifferential.ts"/>
 class AdaptiveDifferential {
 class AdaptiveDifferential {
     public compareFunction : (noun : any) => boolean = () => false;
     public compareFunction : (noun : any) => boolean = () => false;
     public countsAs = 0;
     public countsAs = 0;
+    public score = 0;
 
 
-    constructor (replacer : (noun : any) => boolean) {
+    public getScore () {
+        return this.score;
+    }
+
+    constructor (replacer : (noun : any) => boolean, score : number) {
         this.compareFunction = replacer;
         this.compareFunction = replacer;
     }
     }
 
 
@@ -19,7 +25,7 @@ class AdaptiveDifferential {
                 }
                 }
             }
             }
             return false;
             return false;
-        });
+        }, (new ContentDifferential(...acceptableValues).getScore()));
     }
     }
 
 
     public static FULLYADAPTIVE (...acceptableValues) {
     public static FULLYADAPTIVE (...acceptableValues) {
@@ -33,26 +39,26 @@ class AdaptiveDifferential {
             return noun.isMale();
             return noun.isMale();
         }
         }
         return false;
         return false;
-    });
+    }, new ContentDifferential(Humanoid).getScore());
 
 
     public static FEMALE = new AdaptiveDifferential((noun : any) => {
     public static FEMALE = new AdaptiveDifferential((noun : any) => {
         if (noun instanceof Humanoid) {
         if (noun instanceof Humanoid) {
             return noun.isMale();
             return noun.isMale();
         }
         }
         return false;
         return false;
-    });
+    }, new ContentDifferential(Humanoid).getScore());
 
 
     public static MASCULINE = new AdaptiveDifferential((noun : any) => {
     public static MASCULINE = new AdaptiveDifferential((noun : any) => {
         if (noun instanceof Humanoid) {
         if (noun instanceof Humanoid) {
             return noun.getGenderValue().genderValueCorrected <= 50;
             return noun.getGenderValue().genderValueCorrected <= 50;
         }
         }
         return false;
         return false;
-    });
+    }, new ContentDifferential(Humanoid).getScore());
 
 
     public static FEMININE = new AdaptiveDifferential((noun : any) => {
     public static FEMININE = new AdaptiveDifferential((noun : any) => {
         if (noun instanceof Humanoid) {
         if (noun instanceof Humanoid) {
             return noun.getGenderValue().genderValueCorrected >= 50;
             return noun.getGenderValue().genderValueCorrected >= 50;
         }
         }
         return false;
         return false;
-    });
+    }, new ContentDifferential(Humanoid).getScore());
 }
 }

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

@@ -18,10 +18,4 @@ class CombatResult extends ContentMarker {
     public static KNOCKED = new CombatResult("Target was knocked down by the attack", true);
     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 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);
     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);
 }
 }

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

@@ -13,11 +13,11 @@ class CombatPokeDescription extends ContentDescription {
         CombatPokeDescription.DESCRIPTIONS.push(this);
         CombatPokeDescription.DESCRIPTIONS.push(this);
     }
     }
 
 
-    public setDescriptionFunction (descriptionFor : (target : any, markers : Array<any>) => Say) {
+    public setDescriptionFunction (descriptionFor : (aggressor : any, target : any, markers : Array<any>) => Say) {
         let finalFunction = (description : CombatPokeDescription, group : ContentGroup) => {
         let finalFunction = (description : CombatPokeDescription, group : ContentGroup) => {
             // Combat only has one unit
             // Combat only has one unit
             let unit = <CombatPokeUnit> group.getUnit(0);
             let unit = <CombatPokeUnit> group.getUnit(0);
-            return descriptionFor (unit.getTarget().nouns[0], unit.getMarkers());
+            return descriptionFor (unit.getAggressor().nouns[0], unit.getTarget().nouns[0], unit.getMarkers());
         }
         }
         this.description = finalFunction;
         this.description = finalFunction;
         return this;
         return this;

+ 10 - 3
app/World/Classes/ContentPicker/Combat/CombatPokeUnit.ts

@@ -3,6 +3,7 @@
 /// <reference path="../../Things/Bodypart/SexHole.ts" />
 /// <reference path="../../Things/Bodypart/SexHole.ts" />
 /// <reference path="../../Things/Bodypart/SexStick.ts" />
 /// <reference path="../../Things/Bodypart/SexStick.ts" />
 class CombatPokeUnit extends ContentUnit {
 class CombatPokeUnit extends ContentUnit {
+    private aggressor : ContentDifferential = new ContentDifferential(Person);
     private target : ContentDifferential = new ContentDifferential(Person);
     private target : ContentDifferential = new ContentDifferential(Person);
     private markers : ContentDifferential = new ContentDifferential();
     private markers : ContentDifferential = new ContentDifferential();
 
 
@@ -10,6 +11,14 @@ class CombatPokeUnit extends ContentUnit {
         super();
         super();
     }
     }
 
 
+    public setAggressor (it : Thing | typeof Thing) {
+        this.aggressor = new ContentDifferential(it);
+        return this;
+    }
+
+    public getAggressor () {
+        return this.aggressor;
+    }
 
 
     public setTarget (it : Thing | typeof Thing) {
     public setTarget (it : Thing | typeof Thing) {
         this.target = new ContentDifferential(it);
         this.target = new ContentDifferential(it);
@@ -35,10 +44,8 @@ class CombatPokeUnit extends ContentUnit {
     }
     }
 
 
     public isMatch (cu : CombatPokeUnit) {
     public isMatch (cu : CombatPokeUnit) {
-        console.warn("Chegking :)");
         if (cu instanceof CombatPokeUnit) {
         if (cu instanceof CombatPokeUnit) {
-            console.log(this.markers.nouns, cu.markers.nouns);
-            return this.target.isMatch(cu.target) &&
+            return this.target.isMatch(cu.target) && this.aggressor.isMatch(cu.aggressor) &&
                 this.markers.isMatch(cu.markers);
                 this.markers.isMatch(cu.markers);
         }
         }
         return false;
         return false;

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

@@ -76,21 +76,19 @@ class ContentDifferential {
 
 
     public getScore () {
     public getScore () {
         let highest = 0;
         let highest = 0;
-        let count = this.nouns.length;
 
 
         this.nouns.forEach((noun) => {
         this.nouns.forEach((noun) => {
-            let level = ContentDifferential.getNounLevel(noun);
-            if (highest < level) {
-                highest = level;
-            }
+            highest += ContentDifferential.getNounLevel(noun);
         });
         });
 
 
-        return highest + (count / 100);
+        return highest;
     }
     }
 
 
     public static getNounLevel (noun : Thing | typeof Thing | ContentDifferential | ContentMarker) {
     public static getNounLevel (noun : Thing | typeof Thing | ContentDifferential | ContentMarker) {
         if (noun == undefined || noun == null) {
         if (noun == undefined || noun == null) {
             return 0;
             return 0;
+        } else if (noun instanceof AdaptiveDifferential) {
+            return noun.getScore();
         } else if (typeof noun == "function") {
         } else if (typeof noun == "function") {
             if (<any>noun.prototype instanceof Thing) {
             if (<any>noun.prototype instanceof Thing) {
                 let specifity = 2; // Vague Thing
                 let specifity = 2; // Vague Thing

+ 1 - 0
app/World/Classes/ContentPicker/ContentUnit.ts

@@ -4,6 +4,7 @@ class ContentUnit {
 
 
     public addCategory (...nouns : Array<Thing | typeof Thing | ContentDifferential>) {
     public addCategory (...nouns : Array<Thing | typeof Thing | ContentDifferential>) {
         this.categories.push(new ContentDifferential(...nouns));
         this.categories.push(new ContentDifferential(...nouns));
+        return this;
     }
     }
 
 
     public isMatch (cu : ContentUnit) {
     public isMatch (cu : ContentUnit) {

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

@@ -29,6 +29,7 @@ class Thing implements Printable {
     public breakable = false;
     public breakable = false;
     public breakableOn = 5;
     public breakableOn = 5;
     public breaksTo;
     public breaksTo;
+    public lastActionTurn = -1;
 
 
     protected setAlterations : Array<(thisObject : Thing, simpleAlterationObject : {[id : string] : any}) => void> = [];
     protected setAlterations : Array<(thisObject : Thing, simpleAlterationObject : {[id : string] : any}) => void> = [];
     protected getAlterations : Array<(thisObject : Thing) => {[id : string] : any}> = [];
     protected getAlterations : Array<(thisObject : Thing) => {[id : string] : any}> = [];

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

@@ -33,6 +33,7 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
     public static STRENGTH_SORENESS_MULTIPLIER = 4;
     public static STRENGTH_SORENESS_MULTIPLIER = 4;
 
 
     public stance : PersonStance = PersonStance.STANDING;
     public stance : PersonStance = PersonStance.STANDING;
+    public reputation : number = 0;
 
 
     public constructor (options : ThingOptions) {
     public constructor (options : ThingOptions) {
         super(options);
         super(options);
@@ -44,6 +45,10 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
                     Stats: this.attributeValue,
                     Stats: this.attributeValue,
                     Skills: this.skillValue
                     Skills: this.skillValue
                 }
                 }
+            } else {
+                return {
+                    reputation : this.reputation
+                }
             }
             }
         });
         });
 
 
@@ -71,14 +76,14 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
                         }
                         }
                     }
                     }
                 }
                 }
+            } else {
+                if (changes.reputation != undefined) {
+                    this.reputation = changes.reputation;
+                }
             }
             }
         });
         });
     }
     }
 
 
-    public isHostileTo (person : Thing) {
-        return this.AI.hostileTo.indexOf(person) != -1;
-    }
-
     public changeHealth (n : number) {
     public changeHealth (n : number) {
         let bodyparts = <Array<Bodypart>> this.getParts(Bodypart);
         let bodyparts = <Array<Bodypart>> this.getParts(Bodypart);
         for (let i = 0; i < bodyparts.length; i++) {
         for (let i = 0; i < bodyparts.length; i++) {

+ 10 - 11
app/World/EveryTurn.ts

@@ -10,7 +10,8 @@ module EveryTurn {
             function isAIAvailable (person : Person) {
             function isAIAvailable (person : Person) {
                 return (person != WorldState.player
                 return (person != WorldState.player
                     && ((person.getRoom() instanceof RoomRandom
                     && ((person.getRoom() instanceof RoomRandom
-                        && (<RoomRandom> person.getRoom()).placed)));
+                        && (<RoomRandom> person.getRoom()).placed))
+                            && (person.lastActionTurn < WorldState.getCurrentTurn()));
             }
             }
 
 
 
 
@@ -20,17 +21,15 @@ module EveryTurn {
                 if (person.getHealthOnScale() > 0) {
                 if (person.getHealthOnScale() > 0) {
                     let action = await people[i].AI.execute();
                     let action = await people[i].AI.execute();
                     let visible = people[i].isVisibleTo(WorldState.player);
                     let visible = people[i].isVisibleTo(WorldState.player);
+                    if (action == undefined) action = new ActionWait(person);
+                    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);
                     }
                     }
                 }
                 }
             }
             }

+ 1 - 1
app/World/Settings.ts

@@ -3,7 +3,7 @@ module Settings {
     var debugEmpty = () => {};
     var debugEmpty = () => {};
 
 
     export var hardDebug = false;
     export var hardDebug = false;
-    export var sayTurnTime = true;
+    export var sayTurnTime = false;
 
 
     export function setDebug (isDebug : boolean) {
     export function setDebug (isDebug : boolean) {
         if (isDebug) {
         if (isDebug) {

+ 38 - 2
app/World/TurnSequence.ts

@@ -7,6 +7,24 @@ module TurnSequence {
 
 
     export let lastTurnTime : number = 0;
     export let lastTurnTime : number = 0;
 
 
+    export let currentTurnActions : Array<Action> = [];
+    export let currentTurnActionsTargets : Array<any> = [];
+    export let currentTurnActionsActors : Array<Thing> = [];
+
+    export function clearActions () {
+        console.debug(Rulebook.getIndentation() + "Clearing Turn Sequence Actions");
+        currentTurnActions = [];
+        currentTurnActionsTargets = [];
+        currentTurnActionsActors = [];
+    }
+
+    export function addAction (action : Action) {
+        currentTurnActions.push(action);
+        currentTurnActionsTargets.push(action.getNoun(0));
+        currentTurnActionsActors.push(action.actor);
+        action.actor.lastActionTurn = WorldState.getCurrentTurn();
+    }
+
     export async function execute (action? : Action) {
     export async function execute (action? : Action) {
         // Only one action at a time
         // Only one action at a time
         if (playerActions.push(action) == 1) {
         if (playerActions.push(action) == 1) {
@@ -70,8 +88,8 @@ module TurnSequence {
      * @type {Rule}
      * @type {Rule}
      */
      */
     export var RunEveryTurnRulesRule = new Rule({
     export var RunEveryTurnRulesRule = new Rule({
-        firstPriority: Rule.PRIORITY_MEDIUM,
-        priority: Rule.PRIORITY_MEDIUM,
+        firstPriority: PlayerActionRule.firstPriority,
+        priority: PlayerActionRule.priority - 1,
         name: "Run Every Turn Rules",
         name: "Run Every Turn Rules",
         code: async function () {
         code: async function () {
             while (WorldState.isTurnWaiting()) {
             while (WorldState.isTurnWaiting()) {
@@ -81,6 +99,24 @@ module TurnSequence {
     });
     });
     rulebook.addRule(RunEveryTurnRulesRule);
     rulebook.addRule(RunEveryTurnRulesRule);
 
 
+    /**
+     * This is the Clean up Turn Actions Rule
+     */
+
+    /**
+     * This is the Run Every Turn Rules.
+     * @type {Rule}
+     */
+    export var CleanTurnActionsRule = new Rule({
+        firstPriority: RunEveryTurnRulesRule.firstPriority,
+        priority: RunEveryTurnRulesRule.priority - 1,
+        name: "Clear Turn Actions Rule",
+        code: async function () {
+            TurnSequence.clearActions();
+        }
+    });
+    rulebook.addRule(CleanTurnActionsRule);
+
     /**
     /**
      * This is the Inform Elements the turn has ended rule.
      * This is the Inform Elements the turn has ended rule.
      * @type {Rule}
      * @type {Rule}

+ 57 - 33
content/main.ts

@@ -322,38 +322,6 @@ spitroast.addUnit()
 //     .setTarget(Person)
 //     .setTarget(Person)
 //     .addMarker(CombatPokeResult.AGGROED);
 //     .addMarker(CombatPokeResult.AGGROED);
 
 
-(new CombatPokeDescription("Hitting Orc"))
-    .setDescriptionFunction((target, markers) => {
-        let say = new Say(new SayBold(target), ": ");
-
-        if (markers.includes(CombatPokeResult.NOHEAT)) {
-            say.add(
-                new OneOf(OneOf.PURELY_AT_RANDOM,
-                    "Heh, ain't that cute?",
-                    "Pathetic."
-                )
-            );
-        } else if (markers.includes(CombatPokeResult.ANNOYED)) {
-            say.add(
-                new OneOf(OneOf.PURELY_AT_RANDOM,
-                    "Oy, stahp that.",
-                    "Ya' betta' stahp that before I getting mad."
-                )
-            );
-        } else if (markers.includes(CombatPokeResult.AGGROED)) {
-            say.add(
-                new OneOf(OneOf.PURELY_AT_RANDOM,
-                    "Aight. The axe it is."
-                )
-            );
-        }
-        return say;
-    })
-    .addUnit()
-    .setTarget(OrcDebugger)
-    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatPokeResult.AGGROED, CombatPokeResult.NOHEAT, CombatPokeResult.ANNOYED));
-
-
 (new CombatDescription("Allranging Fists"))
 (new CombatDescription("Allranging Fists"))
     .setDescriptionFunction((actor, target, weapons, markers) => {
     .setDescriptionFunction((actor, target, weapons, markers) => {
         let say = new Say("You attack ", new SayThe(), target, " with your fists");
         let say = new Say("You attack ", new SayThe(), target, " with your fists");
@@ -379,4 +347,60 @@ spitroast.addUnit()
     .setActor(WorldState.player)
     .setActor(WorldState.player)
     .setTarget(Person)
     .setTarget(Person)
     .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatHit.HIT, CombatHit.CRITICAL, CombatHit.MISS))
     .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatHit.HIT, CombatHit.CRITICAL, CombatHit.MISS))
-    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatResult.KILLED, CombatResult.KNOCKED, CombatResult.KNOCKED_OFF));
+    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(CombatResult.KILLED, CombatResult.KNOCKED, CombatResult.KNOCKED_OFF));
+
+
+(new CombatPokeDescription("Hitting Orc"))
+    .setDescriptionFunction((aggressor, target, markers) => {
+        let say = new Say(new SayBold(target), ": ");
+
+        let action = new SayAction();
+        action.add(new SayThe(), target, " looks at ");
+        if (aggressor != WorldState.player) {
+            action.add(new SayThe(), aggressor)
+        } else {
+            action.add("you");
+        }
+        say.add(action);
+
+        if (markers.includes(AIRules.resultHostile)) {
+            say.add("Fucking seriously!? I'm going to rip your head off!");
+            return say;
+        } else if (AIRules.resultRetaliate) {
+            say.add("Fucking STOP that!");
+            return say;
+        }
+
+        if (markers.includes(AIRules.actionMin)) {
+            say.add(
+                new OneOf(OneOf.PURELY_AT_RANDOM,
+                    "Stop that.",
+                    "You better stop that.",
+                    "Look, I'm not very patient. Stop that."
+                )
+            );
+        } else if (markers.includes(AIRules.actionMed)) {
+            say.add(
+                new OneOf(OneOf.PURELY_AT_RANDOM,
+                    "Fucking do that again, see what happens.",
+                    "Watch it!",
+                    "I'm THIS close to ripping your head off!"
+                )
+            );
+        } else if (markers.includes(AIRules.actionMax)) {
+            say.add(
+                new OneOf(OneOf.PURELY_AT_RANDOM,
+                    "Goddamn it.",
+                    "Watch it!",
+                    "I'm THIS close to ripping your head off!"
+                )
+            );
+        }
+
+        return say;
+    })
+    .addUnit()
+    .setAggressor(Person)
+    .setTarget(OrcDebugger)
+    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(AIRules.resultRetaliate, AIRules.resultHostile, AIRules.resultNotHostile))
+    .addMarker(AdaptiveDifferential.FULLYADAPTIVE(AIRules.actionMin, AIRules.actionMed, AIRules.actionMax));

File diff suppressed because it is too large
+ 10 - 0
dist/The Obelisk.html


+ 1 - 0
sass/screen.scss

@@ -19,6 +19,7 @@ $linkHoverColor : #0000ff;
 $linkHoverColorLight : #ddf;
 $linkHoverColorLight : #ddf;
 $linkActiveColor : #000099;
 $linkActiveColor : #000099;
 $linkActiveColorLight : #66d;
 $linkActiveColorLight : #66d;
+$actionColor : #F00;
 
 
 // Font Families
 // Font Families
 $fixedWidthFont : "Cousine";
 $fixedWidthFont : "Cousine";

+ 10 - 0
sass/text/_say.scss

@@ -127,4 +127,14 @@ p.turnStart {
   flex: 0 1 auto;
   flex: 0 1 auto;
   width: 50%;
   width: 50%;
   align-self: auto;
   align-self: auto;
+}
+
+b.action {
+  color: $actionColor;
+  &::before{
+    content: " *";
+  }
+  &::after {
+    content: "* ";
+  }
 }
 }

+ 10 - 0
stylesheets/screen.css

@@ -809,6 +809,16 @@ p.turnStart::before, p.turnStart::after {
   align-self: auto;
   align-self: auto;
 }
 }
 
 
+b.action {
+  color: #F00;
+}
+b.action::before {
+  content: " *";
+}
+b.action::after {
+  content: "* ";
+}
+
 .inventoryHeader {
 .inventoryHeader {
   margin-top: 1em;
   margin-top: 1em;
   margin-left: 1ex;
   margin-left: 1ex;

Some files were not shown because too many files changed in this diff