import { Goal } from './Goal.js';
/**
* Class representing a composite goal. Essentially it's a goal which consists of subgoals.
*
* @author {@link https://github.com/Mugen87|Mugen87}
* @augments Goal
*/
class CompositeGoal extends Goal {
/**
* Constructs a new composite goal.
*
* @param {GameEntity} owner - The owner of this composite goal.
*/
constructor( owner = null ) {
super( owner );
/**
* A list of subgoals.
* @type {Array<Goal>}
*/
this.subgoals = new Array();
}
/**
* Adds a goal as a subgoal to this instance.
*
* @param {Goal} goal - The subgoal to add.
* @return {Goal} A reference to this goal.
*/
addSubgoal( goal ) {
this.subgoals.unshift( goal );
return this;
}
/**
* Removes a subgoal from this instance.
*
* @param {Goal} goal - The subgoal to remove.
* @return {Goal} A reference to this goal.
*/
removeSubgoal( goal ) {
const index = this.subgoals.indexOf( goal );
this.subgoals.splice( index, 1 );
return this;
}
/**
* Removes all subgoals and ensures {@link Goal#terminate} is called
* for each subgoal.
*
* @return {Goal} A reference to this goal.
*/
clearSubgoals() {
const subgoals = this.subgoals;
for ( let i = 0, l = subgoals.length; i < l; i ++ ) {
const subgoal = subgoals[ i ];
subgoal.terminate();
}
subgoals.length = 0;
return this;
}
/**
* Returns the current subgoal. If no subgoals are defined, *null* is returned.
*
* @return {Goal} The current subgoal.
*/
currentSubgoal() {
const length = this.subgoals.length;
if ( length > 0 ) {
return this.subgoals[ length - 1 ];
} else {
return null;
}
}
/**
* Executes the current subgoal of this composite goal.
*
* @return {Status} The status of this composite subgoal.
*/
executeSubgoals() {
const subgoals = this.subgoals;
// remove all completed and failed goals from the back of the subgoal list
for ( let i = subgoals.length - 1; i >= 0; i -- ) {
const subgoal = subgoals[ i ];
if ( ( subgoal.completed() === true ) || ( subgoal.failed() === true ) ) {
// if the current subgoal is a composite goal, terminate its subgoals too
if ( subgoal instanceof CompositeGoal ) {
subgoal.clearSubgoals();
}
// terminate the subgoal itself
subgoal.terminate();
subgoals.pop();
} else {
break;
}
}
// if any subgoals remain, process the one at the back of the list
const subgoal = this.currentSubgoal();
if ( subgoal !== null ) {
subgoal.activateIfInactive();
subgoal.execute();
// if subgoal is completed but more subgoals are in the list, return 'ACTIVE'
// status in order to keep processing the list of subgoals
if ( ( subgoal.completed() === true ) && ( subgoals.length > 1 ) ) {
return Goal.STATUS.ACTIVE;
} else {
return subgoal.status;
}
} else {
return Goal.STATUS.COMPLETED;
}
}
/**
* Returns true if this composite goal has subgoals.
*
* @return {Boolean} Whether the composite goal has subgoals or not.
*/
hasSubgoals() {
return this.subgoals.length > 0;
}
/**
* Returns true if the given message was processed by the current subgoal.
*
* @return {Boolean} Whether the message was processed or not.
*/
handleMessage( telegram ) {
const subgoal = this.currentSubgoal();
if ( subgoal !== null ) {
return subgoal.handleMessage( telegram );
}
return false;
}
/**
* Transforms this instance into a JSON object.
*
* @return {Object} The JSON object.
*/
toJSON() {
const json = super.toJSON();
json.subgoals = new Array();
for ( let i = 0, l = this.subgoals.length; i < l; i ++ ) {
const subgoal = this.subgoals[ i ];
json.subgoals.push( subgoal.toJSON() );
}
return json;
}
/**
* Restores UUIDs with references to GameEntity objects.
*
* @param {Map<String,GameEntity>} entities - Maps game entities to UUIDs.
* @return {CompositeGoal} A reference to this composite goal.
*/
resolveReferences( entities ) {
super.resolveReferences( entities );
for ( let i = 0, l = this.subgoals.length; i < l; i ++ ) {
const subgoal = this.subgoals[ i ];
subgoal.resolveReferences( entities );
}
return this;
}
}
export { CompositeGoal };