import { Goal } from './Goal.js';
import { CompositeGoal } from './CompositeGoal.js';
import { Logger } from '../core/Logger.js';
/**
* Class for representing the brain of a game entity.
*
* @author {@link https://github.com/Mugen87|Mugen87}
* @augments CompositeGoal
*/
class Think extends CompositeGoal {
/**
* Constructs a new *Think* object.
*
* @param {GameEntity} owner - The owner of this instance.
*/
constructor( owner = null ) {
super( owner );
/**
* A list of goal evaluators.
* @type {Array<GoalEvaluator>}
*/
this.evaluators = new Array();
//
this._typesMap = new Map();
}
/**
* Executed when this goal is activated.
*/
activate() {
this.arbitrate();
}
/**
* Executed in each simulation step.
*/
execute() {
this.activateIfInactive();
const subgoalStatus = this.executeSubgoals();
if ( subgoalStatus === Goal.STATUS.COMPLETED || subgoalStatus === Goal.STATUS.FAILED ) {
this.status = Goal.STATUS.INACTIVE;
}
}
/**
* Executed when this goal is satisfied.
*/
terminate() {
this.clearSubgoals();
}
/**
* Adds the given goal evaluator to this instance.
*
* @param {GoalEvaluator} evaluator - The goal evaluator to add.
* @return {Think} A reference to this instance.
*/
addEvaluator( evaluator ) {
this.evaluators.push( evaluator );
return this;
}
/**
* Removes the given goal evaluator from this instance.
*
* @param {GoalEvaluator} evaluator - The goal evaluator to remove.
* @return {Think} A reference to this instance.
*/
removeEvaluator( evaluator ) {
const index = this.evaluators.indexOf( evaluator );
this.evaluators.splice( index, 1 );
return this;
}
/**
* This method represents the top level decision process of an agent.
* It iterates through each goal evaluator and selects the one that
* has the highest score as the current goal.
*
* @return {Think} A reference to this instance.
*/
arbitrate() {
const evaluators = this.evaluators;
let bestDesirability = - 1;
let bestEvaluator = null;
// try to find the best top-level goal/strategy for the entity
for ( let i = 0, l = evaluators.length; i < l; i ++ ) {
const evaluator = evaluators[ i ];
let desirability = evaluator.calculateDesirability( this.owner );
desirability *= evaluator.characterBias;
if ( desirability >= bestDesirability ) {
bestDesirability = desirability;
bestEvaluator = evaluator;
}
}
// use the evaluator to set the respective goal
if ( bestEvaluator !== null ) {
bestEvaluator.setGoal( this.owner );
} else {
Logger.error( 'YUKA.Think: Unable to determine goal evaluator for game entity:', this.owner );
}
return this;
}
/**
* Transforms this instance into a JSON object.
*
* @return {Object} The JSON object.
*/
toJSON() {
const json = super.toJSON();
json.evaluators = new Array();
for ( let i = 0, l = this.evaluators.length; i < l; i ++ ) {
const evaluator = this.evaluators[ i ];
json.evaluators.push( evaluator.toJSON() );
}
return json;
}
/**
* Restores this instance from the given JSON object.
*
* @param {Object} json - The JSON object.
* @return {Think} A reference to this instance.
*/
fromJSON( json ) {
super.fromJSON( json );
const typesMap = this._typesMap;
this.evaluators.length = 0;
this.terminate();
// evaluators
for ( let i = 0, l = json.evaluators.length; i < l; i ++ ) {
const evaluatorJSON = json.evaluators[ i ];
const type = evaluatorJSON.type;
const ctor = typesMap.get( type );
if ( ctor !== undefined ) {
const evaluator = new ctor().fromJSON( evaluatorJSON );
this.evaluators.push( evaluator );
} else {
Logger.warn( 'YUKA.Think: Unsupported goal evaluator type:', type );
continue;
}
}
// goals
function parseGoal( goalJSON ) {
const type = goalJSON.type;
const ctor = typesMap.get( type );
if ( ctor !== undefined ) {
const goal = new ctor().fromJSON( goalJSON );
const subgoalsJSON = goalJSON.subgoals;
if ( subgoalsJSON !== undefined ) {
// composite goal
for ( let i = 0, l = subgoalsJSON.length; i < l; i ++ ) {
const subgoal = parseGoal( subgoalsJSON[ i ] );
if ( subgoal ) goal.subgoals.push( subgoal );
}
}
return goal;
} else {
Logger.warn( 'YUKA.Think: Unsupported goal evaluator type:', type );
return;
}
}
for ( let i = 0, l = json.subgoals.length; i < l; i ++ ) {
const subgoal = parseGoal( json.subgoals[ i ] );
if ( subgoal ) this.subgoals.push( subgoal );
}
return this;
}
/**
* Registers a custom type for deserialization. When calling {@link Think#fromJSON}
* this instance is able to pick the correct constructor in order to create custom
* goals or goal evaluators.
*
* @param {String} type - The name of the goal or goal evaluator.
* @param {Function} constructor - The constructor function.
* @return {Think} A reference to this instance.
*/
registerType( type, constructor ) {
this._typesMap.set( type, constructor );
return this;
}
}
export { Think };