/// /// /// /// 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; // } // }) // );