///
///
///
///
class Action {
public static check = new Rulebook("Check any Action");
public static carry = new Rulebook("Carry out any Action");
public extraChecks : Array> = [];
public extraCarries : Array> = [];
public _actor : Thing;
public nouns : Array = [];
public say : Say = new Say();
public actingAgressively = false;
public aggressivenessRating = 1;
public actingSubmissively = false;
public requiresTurn = true;
public requiresNoun = true;
public requiresVisibility = true; // First noun must be visible and in the same room
public allowedStances = [PersonStance.ALLFOURS, PersonStance.STANDING];
public constructor (actor : Thing, ...nouns : Array) {
this.actor = actor;
nouns.forEach((value, index, array) => {
this.setNoun(index, value);
});
}
public async execute () : Promise {
this.say = new Say();
let checkRulebooks = [];
let carryRulebooks = [];
let cClass = this.constructor;
while (cClass != Action) {
if (( cClass).check != undefined) {
checkRulebooks.push(( cClass).check);
}
if (( cClass).carry != undefined) {
carryRulebooks.push(( cClass).carry);
}
cClass = Object.getPrototypeOf(cClass);
}
/**
* Check if action goes through
*/
let result = await Action.check.execute({
noun : this
}, ...checkRulebooks);
// 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...");
let originalNouns = this.nouns;
await result.execute();
this.say.add(result.say);
this.nouns = result.nouns;
// Reset to initial state
this.nouns = originalNouns;
return;
}
/**
* Carry Out
*/
await Action.carry.execute({
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;
}
get actor(): Thing {
return this._actor;
}
set actor(value: Thing) {
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];
}
return undefined;
}
public setNoun (n : number, noun : any) {
while (this.nouns.length < n) {
this.nouns.push(undefined);
}
this.nouns[n] = noun;
}
/**
* Needs to return a string explaining what the player will do if he does this action.
* For instance, ActionTaking should return something like return "take " + this.nouns[0].getName(),
* which would read as "take thing".
* remember that things implement PRINTABLE interface, so you can get their names.
* @returns {string}
*/
public getCommandText () {
return "do";
}
/**
* If an action is stopped, it means it failed so spectacularly that it didn't even begin.
* Which means if the player is doing it, it'll not take a turn.
*/
public stop () {
this.requiresTurn = false;
}
}
// Action.addCarryRule(new Rule({
// name : "Testing say in actions rule",
// priority : Rule.PRIORITY_LOWEST,
// firstPriority : Rule.PRIORITY_LOWEST,
// code : (rule, rulebook) => {
// let action = rulebook.noun;
// action.say.add("You do nothing all turn. What was the point, really?");
// }
// }))
Action.check.addRule(
new Rule({
name : "Check any Action - Requires Noun",
firstPriority : Rule.PRIORITY_HIGHEST,
code : (rulebook : RulebookRunner) => {
let action = rulebook.noun;
if (action.getNoun(0) == undefined) {
return false;
}
},
conditions : runner => {
return runner.noun.requiresNoun;
}
})
);
Action.check.addRule(
new Rule({
name : "Check any Action - Stance",
firstPriority : Rule.PRIORITY_HIGHEST,
code : (rulebook : RulebookRunner) => {
let action = rulebook.noun;
let actor = action.actor;
if (actor instanceof Person) {
if (action.allowedStances.indexOf(actor.stance) == -1) {
if (action.actor == WorldState.player) {
action.say = new Say("You can't do that while ", PersonStanceNames[actor.stance], ", you need to be ", PersonStanceNames[action.allowedStances[0]], ".");
}
action.stop();
return false;
}
}
}
})
);
Action.check.addRule(
new Rule({
name : "Check any Action - Requires Visibility",
code : (rulebook : RulebookRunner) => {
let action = rulebook.noun;
let actor = action.actor;
if (!action.getNoun(0).isVisibleTo(actor)) {
return false;
}
},
conditions : runner => {
return runner.noun.requiresVisibility;
}
})
);
// 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) => {
// let action = rulebook.noun;
// let target = action.getNoun(0);
// let tai = ( 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);
// (target).AI.hostileTo.push(action.actor);
// }
//
//
// action.say.add(Say.PARAGRAPH_BREAK, ...CombatPokeDescription.getDescription(new ContentGroup(cu)));
// },
// conditions : (rulebook : RulebookRunner) => {
// let 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;
// }
// })
// );