385 lines
14 KiB
Java
385 lines
14 KiB
Java
package electrosphere.entity.btree;
|
|
|
|
import java.util.function.Supplier;
|
|
|
|
import electrosphere.audio.VirtualAudioSourceManager.VirtualAudioSourceType;
|
|
import electrosphere.engine.Globals;
|
|
import electrosphere.entity.Entity;
|
|
import electrosphere.entity.EntityUtils;
|
|
import electrosphere.entity.state.client.firstPerson.FirstPersonTree;
|
|
import electrosphere.game.data.common.TreeDataAnimation;
|
|
import electrosphere.game.data.common.TreeDataAudio;
|
|
import electrosphere.game.data.common.TreeDataState;
|
|
import electrosphere.logger.LoggerInterface;
|
|
import electrosphere.renderer.actor.Actor;
|
|
import electrosphere.server.poseactor.PoseActor;
|
|
|
|
/**
|
|
* For a lot of behavior trees, there are states where we simply want to play an animation (ie a cooldown)
|
|
* Those states can be registered with this utility object
|
|
* When you simulate that specific state in the tree, instead ccall the simulation function from this handler
|
|
* It will handle playing the animation in first and third person, playing audio, and transitioning to the next state once the animation has completed
|
|
*/
|
|
public class StateTransitionUtil {
|
|
|
|
//The list of simplified states within this util object
|
|
StateTransitionUtilItem[] states;
|
|
|
|
//The parent entity
|
|
Entity parent;
|
|
|
|
//tracks if this is the server or not
|
|
boolean isServer;
|
|
|
|
/**
|
|
* Private constructor
|
|
* @param states
|
|
*/
|
|
private StateTransitionUtil(Entity entity, boolean isServer, StateTransitionUtilItem[] states){
|
|
this.states = states;
|
|
this.parent = entity;
|
|
this.isServer = isServer;
|
|
}
|
|
|
|
/**
|
|
* Creates a state transition util
|
|
* @param entity The entity
|
|
* @param states The states
|
|
* @return The util object
|
|
*/
|
|
public static StateTransitionUtil create(Entity entity, boolean isServer, StateTransitionUtilItem[] states){
|
|
StateTransitionUtil rVal = new StateTransitionUtil(entity,isServer,states);
|
|
|
|
//error checking
|
|
if(!isServer && Globals.clientSceneWrapper.getScene().getEntityFromId(entity.getId()) == null){
|
|
throw new IllegalArgumentException("Tried to create state transition util with isServer=false and passed in a server entity");
|
|
} else if(isServer && Globals.clientSceneWrapper.getScene().getEntityFromId(entity.getId()) != null){
|
|
throw new IllegalArgumentException("Tried to create state transition util with isServer=true and passed in a client entity");
|
|
}
|
|
|
|
|
|
return rVal;
|
|
}
|
|
|
|
/**
|
|
* Should be called every time the natural state progression is interrupted (ie if you call start() or stop())
|
|
*/
|
|
public void reset(){
|
|
for(StateTransitionUtilItem state : states){
|
|
state.startedAnimation = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Simulates a given state
|
|
* @param stateEnum The enum for the state
|
|
*/
|
|
public void simulate(Object stateEnum){
|
|
StateTransitionUtilItem state = null;
|
|
for(StateTransitionUtilItem targetState : states){
|
|
if(targetState.stateEnum == stateEnum){
|
|
state = targetState;
|
|
break;
|
|
}
|
|
}
|
|
if(state == null){
|
|
LoggerInterface.loggerEngine.DEBUG("Skipping state " + stateEnum + " because there is not a state registered to that enum value!");
|
|
} else {
|
|
if(this.isServer){
|
|
simulateServerState(this.parent,state);
|
|
} else {
|
|
simulateClientState(this.parent,state);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs animation logic for client tree
|
|
* @param parent The parent entity
|
|
* @param state The state
|
|
*/
|
|
private static void simulateClientState(Entity parent, StateTransitionUtilItem state){
|
|
Actor actor = EntityUtils.getActor(parent);
|
|
if(actor != null){
|
|
|
|
//get the animation to play
|
|
TreeDataAnimation animation = state.animation;
|
|
if(state.getAnimation != null && state.getAnimation.get() != null){
|
|
animation = state.getAnimation.get();
|
|
}
|
|
|
|
TreeDataAudio audioData = state.audioData;
|
|
if(state.getAudio != null && state.getAudio.get() != null){
|
|
audioData = state.getAudio.get();
|
|
}
|
|
|
|
|
|
//
|
|
//Play main animation
|
|
//
|
|
if(!actor.isPlayingAnimation() && state.onComplete != null && state.startedAnimation == true){
|
|
//state transition if this isn't set to loop
|
|
state.onComplete.run();
|
|
state.startedAnimation = false;
|
|
} else if(!actor.isPlayingAnimation() || !actor.isPlayingAnimation(animation)){
|
|
//play animation, audio, etc, for state
|
|
if(parent == Globals.playerEntity && !Globals.controlHandler.cameraIsThirdPerson() && animation != null){
|
|
//first person
|
|
//play first person audio
|
|
if(Globals.audioEngine.initialized() && audioData != null && audioData.getAudioPath() != null){
|
|
Globals.virtualAudioSourceManager.createVirtualAudioSource(audioData.getAudioPath(), VirtualAudioSourceType.CREATURE, false);
|
|
}
|
|
} else {
|
|
//play third person audio
|
|
if(Globals.audioEngine.initialized() && audioData != null && audioData.getAudioPath() != null){
|
|
Globals.virtualAudioSourceManager.createVirtualAudioSource(audioData.getAudioPath(), VirtualAudioSourceType.CREATURE, false, EntityUtils.getPosition(parent));
|
|
}
|
|
}
|
|
|
|
if(animation != null){
|
|
actor.playAnimation(animation,true);
|
|
}
|
|
actor.incrementAnimationTime(0.0001);
|
|
state.startedAnimation = true;
|
|
} else if(state.animation == null && state.onComplete != null){
|
|
state.onComplete.run();
|
|
state.startedAnimation = false;
|
|
}
|
|
|
|
//
|
|
//Play animation in first person
|
|
//
|
|
if(animation != null){
|
|
FirstPersonTree.conditionallyPlayAnimation(parent, animation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs animation logic for server tree
|
|
* @param parent The parent entity
|
|
* @param state The state
|
|
*/
|
|
private static void simulateServerState(Entity parent, StateTransitionUtilItem state){
|
|
PoseActor poseActor = EntityUtils.getPoseActor(parent);
|
|
if(poseActor != null){
|
|
|
|
//get the animation to play
|
|
TreeDataAnimation animation = state.animation;
|
|
if(state.getAnimation != null && state.getAnimation.get() != null){
|
|
animation = state.getAnimation.get();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
//Play main animation
|
|
//
|
|
if(animation == null){
|
|
state.startedAnimation = true;
|
|
if(state.onComplete != null){
|
|
state.onComplete.run();
|
|
}
|
|
} else if(!poseActor.isPlayingAnimation(animation) && state.onComplete != null && state.startedAnimation == true){
|
|
//state transition if this isn't set to loop
|
|
state.onComplete.run();
|
|
state.startedAnimation = false;
|
|
} else if(!poseActor.isPlayingAnimation() || !poseActor.isPlayingAnimation(animation)){
|
|
//play animation for state
|
|
if(animation != null){
|
|
poseActor.playAnimation(animation);
|
|
}
|
|
poseActor.incrementAnimationTime(0.0001);
|
|
state.startedAnimation = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interrupts a given state
|
|
* @param stateEnum The state enum
|
|
*/
|
|
public void interrupt(Object stateEnum){
|
|
StateTransitionUtilItem state = null;
|
|
for(StateTransitionUtilItem targetState : states){
|
|
if(targetState.stateEnum == stateEnum){
|
|
state = targetState;
|
|
break;
|
|
}
|
|
}
|
|
if(state == null){
|
|
LoggerInterface.loggerEngine.DEBUG("Skipping state " + stateEnum + " because there is not a state registered to that enum value!");
|
|
} else {
|
|
if(this.isServer){
|
|
interruptServerState(this.parent,state);
|
|
} else {
|
|
interruptClientState(this.parent,state);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interrupts animation logic for client tree
|
|
* @param parent The parent entity
|
|
* @param state The state
|
|
*/
|
|
private static void interruptClientState(Entity parent, StateTransitionUtilItem state){
|
|
Actor actor = EntityUtils.getActor(parent);
|
|
if(actor != null){
|
|
|
|
//get the animation to play
|
|
TreeDataAnimation animation = state.animation;
|
|
if(state.getAnimation != null && state.getAnimation.get() != null){
|
|
animation = state.getAnimation.get();
|
|
}
|
|
|
|
|
|
//
|
|
//Interrupt main animation
|
|
//
|
|
if(animation != null && actor.isPlayingAnimation() && actor.isPlayingAnimation(animation)){
|
|
actor.interruptAnimation(animation, true);
|
|
}
|
|
|
|
//
|
|
//Interrupt animation in first person
|
|
//
|
|
if(animation != null){
|
|
FirstPersonTree.conditionallyInterruptAnimation(Globals.firstPersonEntity, animation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interrupts animation logic for server tree
|
|
* @param parent The parent entity
|
|
* @param state The state
|
|
*/
|
|
private static void interruptServerState(Entity parent, StateTransitionUtilItem state){
|
|
PoseActor poseActor = EntityUtils.getPoseActor(parent);
|
|
if(poseActor != null){
|
|
|
|
//get the animation to play
|
|
TreeDataAnimation animation = state.animation;
|
|
if(state.getAnimation != null && state.getAnimation.get() != null){
|
|
animation = state.getAnimation.get();
|
|
}
|
|
|
|
|
|
|
|
//
|
|
//Interrupt main animation
|
|
//
|
|
if(animation != null && poseActor.isPlayingAnimation() && poseActor.isPlayingAnimation(animation)){
|
|
poseActor.interruptAnimation(animation, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A parameter used to construct a StateTransitionUtil
|
|
*/
|
|
public static class StateTransitionUtilItem {
|
|
|
|
//The enum value for this state
|
|
Object stateEnum;
|
|
|
|
//The animation to play
|
|
TreeDataAnimation animation;
|
|
|
|
//Gets the first person animation
|
|
Supplier<TreeDataAnimation> getAnimation;
|
|
|
|
//The audio data
|
|
TreeDataAudio audioData;
|
|
|
|
//Gets the audio to play
|
|
Supplier<TreeDataAudio> getAudio;
|
|
|
|
//The function to fire on completion (ie to transition to the next state)
|
|
Runnable onComplete;
|
|
|
|
//Tracks whether the animation has been played or not
|
|
boolean startedAnimation = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private StateTransitionUtilItem(
|
|
Object stateEnum,
|
|
TreeDataAnimation animation,
|
|
TreeDataAudio audioData,
|
|
Runnable onComplete
|
|
){
|
|
this.stateEnum = stateEnum;
|
|
this.animation = animation;
|
|
this.audioData = audioData;
|
|
this.onComplete = onComplete;
|
|
}
|
|
|
|
/**
|
|
* Constructor for supplier type
|
|
*/
|
|
private StateTransitionUtilItem(
|
|
Object stateEnum,
|
|
Supplier<TreeDataAnimation> getAnimation,
|
|
Supplier<TreeDataAudio> getAudio,
|
|
Runnable onComplete
|
|
){
|
|
this.stateEnum = stateEnum;
|
|
this.getAnimation = getAnimation;
|
|
this.getAudio = getAudio;
|
|
this.onComplete = onComplete;
|
|
}
|
|
|
|
/**
|
|
* Constructor for a supplier-based approach. This takes suppliers that will provide animation data on demand.
|
|
* This decouples the animations from the initialization of the tree.
|
|
* The intended usecase is if the animation could change based on some state in the tree.
|
|
* @param stateEnum The enum value for this state
|
|
* @param getAnimationData The supplier for the animation data. If it is null, it will not play any animation
|
|
* @param getAudio The supplier for path to an audio file to play on starting the animation. If null, no audio will be played
|
|
* @param onComplete !!Must transition to the next state!! Fires when the animation completes. If not supplied, animations and autio will loop
|
|
*/
|
|
public static StateTransitionUtilItem create(
|
|
Object stateEnum,
|
|
Supplier<TreeDataAnimation> getAnimation,
|
|
Supplier<TreeDataAudio> getAudio,
|
|
Runnable onComplete
|
|
){
|
|
return new StateTransitionUtilItem(
|
|
stateEnum,
|
|
getAnimation,
|
|
getAudio,
|
|
onComplete
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates a state transition based on tree data for the state
|
|
* @param stateEnum The enum value for this state in particular in the tree
|
|
* @param treeData The tree data for this state
|
|
* @return The item for the transition util
|
|
*/
|
|
public static StateTransitionUtilItem create(
|
|
Object stateEnum,
|
|
TreeDataState treeData,
|
|
Runnable onComplete
|
|
){
|
|
StateTransitionUtilItem rVal = null;
|
|
if(treeData != null){
|
|
rVal = new StateTransitionUtilItem(
|
|
stateEnum,
|
|
treeData.getAnimation(),
|
|
treeData.getAudioData(),
|
|
onComplete
|
|
);
|
|
}
|
|
return rVal;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|