1
1

2 Commits 0c2b39fb44 ... 5161c5d9dc

Autor SHA1 Nachricht Datum
  Reddo 5161c5d9dc Aggressive action processing correctly handled by the AI. vor 5 Jahren
  Reddo 93e2698569 Cleaning up vor 5 Jahren

+ 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" />
 
 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 {
@@ -16,9 +20,26 @@ class AI {
     public wandersOn : Region;
     public wanderChance = 50;
     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) {
         for (let key in options) {
@@ -34,30 +55,51 @@ class AI {
         let promise : Promise<Action>;
         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;
                     break;
                 }
             }
         }
 
+        let result : Action;
         if (inCombat) {
+            // TUNNEL VISION
             promise = AI.combatRules.execute({
                 noun : this.actor
             }, ...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;
     }
 
+    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>>) {
         this.extraRules.push(...books)
         arrayUnique(this.extraRules);
@@ -68,10 +110,54 @@ class AI {
         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 {
@@ -95,4 +181,21 @@ module AIRules {
      * @type {number}
      */
     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="../ContentPicker/ContentMarker.ts"/>
 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({
         name : "Grudge",
         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>) => {
             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" />
 module AIRules {
     export var attackInRange = AI.combatRules.createAndAddRule({
-        name : "Attack in range",
+        name : "Attack highest aggro",
         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]);
-                }
+            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>) => {
             let person = runner.noun;
-            let room = person.getRoom();
-            let visibleThings = room.getContainedAndVisibleTo(person);
+            let visibleThings = person.AI.newNoticed;
 
             if (visibleThings.length > 0) {
                 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!
         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;
         } else if(result instanceof Action) {
             console.debug(Rulebook.getIndentation() + "[ACTION] Instead of...");
@@ -74,6 +82,12 @@ class Action {
             noun : this
         }, ...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;
     }
 
@@ -85,6 +99,7 @@ class Action {
         this._actor = value;
     }
 
+    // TODO: The if shouldn't be necessary right? I don't wanna touch this right now.
     public getNoun (n : number) : any {
         if (this.nouns.length > 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.
-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 {
     public compareFunction : (noun : any) => boolean = () => false;
     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;
     }
 
@@ -19,7 +25,7 @@ class AdaptiveDifferential {
                 }
             }
             return false;
-        });
+        }, (new ContentDifferential(...acceptableValues).getScore()));
     }
 
     public static FULLYADAPTIVE (...acceptableValues) {
@@ -33,26 +39,26 @@ class AdaptiveDifferential {
             return noun.isMale();
         }
         return false;
-    });
+    }, new ContentDifferential(Humanoid).getScore());
 
     public static FEMALE = new AdaptiveDifferential((noun : any) => {
         if (noun instanceof Humanoid) {
             return noun.isMale();
         }
         return false;
-    });
+    }, new ContentDifferential(Humanoid).getScore());
 
     public static MASCULINE = new AdaptiveDifferential((noun : any) => {
         if (noun instanceof Humanoid) {
             return noun.getGenderValue().genderValueCorrected <= 50;
         }
         return false;
-    });
+    }, new ContentDifferential(Humanoid).getScore());
 
     public static FEMININE = new AdaptiveDifferential((noun : any) => {
         if (noun instanceof Humanoid) {
             return noun.getGenderValue().genderValueCorrected >= 50;
         }
         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_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);
 }

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

@@ -13,11 +13,11 @@ class CombatPokeDescription extends ContentDescription {
         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) => {
             // Combat only has one unit
             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;
         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/SexStick.ts" />
 class CombatPokeUnit extends ContentUnit {
+    private aggressor : ContentDifferential = new ContentDifferential(Person);
     private target : ContentDifferential = new ContentDifferential(Person);
     private markers : ContentDifferential = new ContentDifferential();
 
@@ -10,6 +11,14 @@ class CombatPokeUnit extends ContentUnit {
         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) {
         this.target = new ContentDifferential(it);
@@ -35,10 +44,8 @@ class CombatPokeUnit extends ContentUnit {
     }
 
     public isMatch (cu : CombatPokeUnit) {
-        console.warn("Chegking :)");
         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);
         }
         return false;

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

@@ -76,21 +76,19 @@ class ContentDifferential {
 
     public getScore () {
         let highest = 0;
-        let count = this.nouns.length;
 
         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) {
         if (noun == undefined || noun == null) {
             return 0;
+        } else if (noun instanceof AdaptiveDifferential) {
+            return noun.getScore();
         } else if (typeof noun == "function") {
             if (<any>noun.prototype instanceof 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>) {
         this.categories.push(new ContentDifferential(...nouns));
+        return this;
     }
 
     public isMatch (cu : ContentUnit) {

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

@@ -29,6 +29,7 @@ class Thing implements Printable {
     public breakable = false;
     public breakableOn = 5;
     public breaksTo;
+    public lastActionTurn = -1;
 
     protected setAlterations : Array<(thisObject : Thing, simpleAlterationObject : {[id : string] : any}) => void> = [];
     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 stance : PersonStance = PersonStance.STANDING;
+    public reputation : number = 0;
 
     public constructor (options : ThingOptions) {
         super(options);
@@ -44,6 +45,10 @@ class Person extends Thing implements AttributeBearer, SkillBearer {
                     Stats: this.attributeValue,
                     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) {
         let bodyparts = <Array<Bodypart>> this.getParts(Bodypart);
         for (let i = 0; i < bodyparts.length; i++) {

+ 10 - 11
app/World/EveryTurn.ts

@@ -10,7 +10,8 @@ module EveryTurn {
             function isAIAvailable (person : Person) {
                 return (person != WorldState.player
                     && ((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) {
                     let action = await people[i].AI.execute();
                     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 = () => {};
 
     export var hardDebug = false;
-    export var sayTurnTime = true;
+    export var sayTurnTime = false;
 
     export function setDebug (isDebug : boolean) {
         if (isDebug) {

+ 38 - 2
app/World/TurnSequence.ts

@@ -7,6 +7,24 @@ module TurnSequence {
 
     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) {
         // Only one action at a time
         if (playerActions.push(action) == 1) {
@@ -70,8 +88,8 @@ module TurnSequence {
      * @type {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",
         code: async function () {
             while (WorldState.isTurnWaiting()) {
@@ -81,6 +99,24 @@ module TurnSequence {
     });
     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.
      * @type {Rule}

+ 57 - 33
content/main.ts

@@ -322,38 +322,6 @@ spitroast.addUnit()
 //     .setTarget(Person)
 //     .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"))
     .setDescriptionFunction((actor, target, weapons, markers) => {
         let say = new Say("You attack ", new SayThe(), target, " with your fists");
@@ -379,4 +347,60 @@ spitroast.addUnit()
     .setActor(WorldState.player)
     .setTarget(Person)
     .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));

Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
dist/The Obelisk.html


+ 3 - 3
sass/__imagesInline.scss

@@ -1,3 +1,3 @@
-.introLogo {
-  background-image: inline-image("IntroLogo.svg");
-}
+//.introLogo {
+//  background-image: inline-image("IntroLogo.svg");
+//}

+ 7 - 7
sass/_animations.scss

@@ -4,13 +4,13 @@
  ** Profit
  **/
 
-@import "compass/utilities/sprites";
-$anims-layout:vertical;
-$anims-sprite-dimensions: true;
-@import "anims/*/**.png";
-@include all-anims-sprites;
-
-@import "_animationsBG";
+//@import "compass/utilities/sprites";
+//$anims-layout:vertical;
+//$anims-sprite-dimensions: true;
+//@import "anims/*/**.png";
+//@include all-anims-sprites;
+//
+//@import "_animationsBG";
 
 
 #sceneAnimation {

+ 1 - 1
sass/images.scss

@@ -1,4 +1,4 @@
-@import "_animations";
+//@import "_animations";
 
 #mainPage.mobile .contentImage {
   height: 10ex; // It's a good idea to make these smaller on mobile.

+ 5 - 1
sass/screen.scss

@@ -19,6 +19,7 @@ $linkHoverColor : #0000ff;
 $linkHoverColorLight : #ddf;
 $linkActiveColor : #000099;
 $linkActiveColorLight : #66d;
+$actionColor : #F00;
 
 // Font Families
 $fixedWidthFont : "Cousine";
@@ -38,4 +39,7 @@ $mainFont : "Source Sans Pro";
 @import "text/_room";
 @import "text/_say";
 @import "text/_inventory";
-@import "text/_appearance";
+@import "text/_appearance";
+
+// Images
+@import "_imagesInline"; // Lets go with inline for now.

+ 0 - 3
sass/screenInline.scss

@@ -1,3 +0,0 @@
-@import "__imagesInline.scss";
-@import "screen";
-@import "fonts/_ssans.scss";

+ 0 - 3
sass/screenLink.scss

@@ -1,3 +0,0 @@
-@import "screen";
-@import "__imagesLinks.scss";
-@import "fonts/_ssans.scss";

+ 0 - 5
sass/screenRelative.scss

@@ -1,5 +0,0 @@
-//@import "fonts/_cousineRelative.scss";
-@import "fonts/_ssansRelative.scss";
-@import "screen";
-@import "images";
-@import "__imagesRelative.scss";

+ 10 - 0
sass/text/_say.scss

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

+ 0 - 51
stylesheets/images.css

@@ -1,54 +1,3 @@
-/** Utilize JS getStyle to read width of spritesheet
- ** Calculate amount of steps from that
- ** Profit
- **/
-.anims-sprite, .anims-orcStand, .anims-mainBody, .anims-pixel0 {
-  background-image: url('images/anims-sad0922e873.png');
-  background-repeat: no-repeat;
-}
-
-.anims-orcStand {
-  background-position: 0 0;
-  height: 200px;
-  width: 800px;
-}
-
-.anims-mainBody {
-  background-position: 0 -200px;
-  height: 300px;
-  width: 1200px;
-}
-
-.anims-pixel0 {
-  background-position: 0 -500px;
-  height: 155px;
-  width: 262px;
-}
-
-.bg-Forest {
-  background-image: url("images/Forest.png");
-  width: 400px;
-  height: 200px;
-}
-
-#sceneAnimation {
-  display: block;
-  position: fixed;
-  bottom: 0px;
-  left: 50%;
-  z-index: 1;
-  pointer-events: none;
-  opacity: 0;
-}
-
-.sceneAnimation {
-  display: block;
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  image-rendering: pixelated;
-}
-
 #mainPage.mobile .contentImage {
   height: 10ex;
 }

+ 10 - 0
stylesheets/screen.css

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

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 1
stylesheets/screenInline.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 868
stylesheets/screenLink.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 1024
stylesheets/screenRelative.css


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.