Renderer/src/main/java/electrosphere/entity/btree/StateTransitionUtil.java
austin 920f3912de
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
attack animation sounds
2024-08-06 13:27:44 -04:00

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