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