diff --git a/assets/Data/creatures/human.json b/assets/Data/creatures/human.json index 55788c59..d8c69955 100644 --- a/assets/Data/creatures/human.json +++ b/assets/Data/creatures/human.json @@ -290,9 +290,8 @@ "variants": [ { "variantId": "blockWeaponRight", - "windUpAnimation" : "Jump", "mainAnimation" : "Fall", - "cooldownAnimation" : "Land", + "mainFirstPersonAnimation" : "HoldItemR2HBlock", "defaults" : [ { "equipPoint" : "handRight", diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 4e741b20..425a966c 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -454,6 +454,7 @@ Devtools for updating first person attachment rotations (07/26/2024) Viewmodel equipped item rotates inverted to bone rotation +Visually block # TODO diff --git a/src/main/java/electrosphere/audio/AudioUtils.java b/src/main/java/electrosphere/audio/AudioUtils.java index 66618b93..ad7d7d85 100644 --- a/src/main/java/electrosphere/audio/AudioUtils.java +++ b/src/main/java/electrosphere/audio/AudioUtils.java @@ -4,6 +4,7 @@ import electrosphere.engine.Globals; import electrosphere.logger.LoggerInterface; import electrosphere.util.FileUtils; +import org.joml.Vector3d; import org.joml.Vector3f; /** @@ -33,6 +34,17 @@ public class AudioUtils { return rVal; } + /** + * Plays a audio at a given audio file path at a given position + * @param audioFile The audio file's path + * @param position The position to play it at + * @param loops If true, loops the source + * @return The audio source + */ + protected static AudioSource playAudioAtLocation(String audioFile, Vector3d position, boolean loops){ + return playAudioAtLocation(audioFile,new Vector3f((float)position.x,(float)position.y,(float)position.z),loops); + } + /** * Plays an audio file * @param audioFile The audio file path diff --git a/src/main/java/electrosphere/entity/btree/StateTransitionUtil.java b/src/main/java/electrosphere/entity/btree/StateTransitionUtil.java new file mode 100644 index 00000000..155db3d0 --- /dev/null +++ b/src/main/java/electrosphere/entity/btree/StateTransitionUtil.java @@ -0,0 +1,352 @@ +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.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){ + throw new IllegalArgumentException("Trying to simulate state that is not registered with this util object"); + } 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){ + //determine the third person animation to play + String animationToPlay = state.thirdPersonAnimation; + if(animationToPlay == null && state.getThirdPersonAnimation != null){ + animationToPlay = state.getThirdPersonAnimation.get(); + } + + //determine the first person animation to play + String firstPersonAnimation = state.firstPersonAnimation; + if(firstPersonAnimation == null && state.getFirstPersonAnimation != null){ + firstPersonAnimation = state.getFirstPersonAnimation.get(); + } + + + + + //Main simulation + 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(animationToPlay)){ + //play animation, audio, etc, for state + if(parent == Globals.playerEntity && !Globals.controlHandler.cameraIsThirdPerson()){ + //first person + //play first person audio + if(state.audioPath != null){ + Globals.virtualAudioSourceManager.createVirtualAudioSource(state.audioPath, VirtualAudioSourceType.CREATURE, false); + } + FirstPersonTree.conditionallyPlayAnimation(Globals.firstPersonEntity, firstPersonAnimation); + } else { + //play third person audio + if(state.audioPath != null){ + Globals.virtualAudioSourceManager.createVirtualAudioSource(state.audioPath, VirtualAudioSourceType.CREATURE, false, EntityUtils.getPosition(parent)); + } + } + + actor.playAnimation(animationToPlay,1); + actor.incrementAnimationTime(0.0001); + state.startedAnimation = true; + } else if(animationToPlay == null && state.onComplete != null){ + state.onComplete.run(); + state.startedAnimation = false; + } + } + } + + /** + * 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){ + //determine the third person animation to play + String animationToPlay = state.thirdPersonAnimation; + if(animationToPlay == null && state.getThirdPersonAnimation != null){ + animationToPlay = state.getThirdPersonAnimation.get(); + } + + + //Main simulation + if(!poseActor.isPlayingAnimation() && state.onComplete != null && state.startedAnimation == true){ + //state transition if this isn't set to loop + state.onComplete.run(); + state.startedAnimation = false; + } else if(animationToPlay != null && (!poseActor.isPlayingAnimation() || !poseActor.isPlayingAnimation(animationToPlay))){ + //play animation for state + poseActor.playAnimation(animationToPlay,1); + poseActor.incrementAnimationTime(0.0001); + state.startedAnimation = true; + } else if(animationToPlay == null && state.onComplete != null){ + state.onComplete.run(); + state.startedAnimation = false; + } + } + } + + /** + * A parameter used to construct a StateTransitionUtil + */ + public static class StateTransitionUtilItem { + + //The enum value for this state + Object stateEnum; + + //T1he animation to play in first person + String firstPersonAnimation; + + //Gets the first person animation's name + Supplier getFirstPersonAnimation; + + //The animation to play in third person + String thirdPersonAnimation; + + //Gets the third person animation's name + Supplier getThirdPersonAnimation; + + //The audio path + String audioPath; + + //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 + * @param stateEnum The enum value for this state + * @param firstPersonAnimation The animation to play in first person. If this is null, it will not play any animation in first person. + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + * @param audioPath The audio path to play when the animation starts. If this is null, it will not play any audio. + * @param onComplete The function to fire on completion (ie to transition to the next state). If this is null, the animation and audio will be looped on completion. + */ + public StateTransitionUtilItem( + Object stateEnum, + String firstPersonAnimation, + String thirdPersonAnimation, + String audioPath, + Runnable onComplete + ){ + this.stateEnum = stateEnum; + this.firstPersonAnimation = firstPersonAnimation; + this.thirdPersonAnimation = thirdPersonAnimation; + this.audioPath = audioPath; + this.onComplete = onComplete; + } + + /** + * Constructor + * @param stateEnum The enum value for this state + * @param firstPersonAnimation The animation to play in first person. If this is null, it will not play any animation in first person. + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + * @param onComplete The function to fire on completion (ie to transition to the next state). If this is null, the animation and audio will be looped on completion. + */ + public StateTransitionUtilItem( + Object stateEnum, + String firstPersonAnimation, + String thirdPersonAnimation, + Runnable onComplete + ){ + this.stateEnum = stateEnum; + this.firstPersonAnimation = firstPersonAnimation; + this.thirdPersonAnimation = thirdPersonAnimation; + this.onComplete = onComplete; + } + + /** + * Constructor + * @param stateEnum The enum value for this state + * @param firstPersonAnimation The animation to play in first person. If this is null, it will not play any animation in first person. + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + * @param audioPath The audio path to play when the animation starts. If this is null, it will not play any audio. + * @param onComplete The function to fire on completion (ie to transition to the next state). If this is null, the animation and audio will be looped on completion. + */ + public StateTransitionUtilItem( + Object stateEnum, + Supplier getFirstPersonAnimation, + Supplier getThirdPersonAnimation, + String audioPath, + Runnable onComplete + ){ + this.stateEnum = stateEnum; + this.getFirstPersonAnimation = getFirstPersonAnimation; + this.getThirdPersonAnimation = getThirdPersonAnimation; + this.audioPath = audioPath; + this.onComplete = onComplete; + } + + /** + * Constructor + * @param stateEnum The enum value for this state + * @param firstPersonAnimation The animation to play in first person. If this is null, it will not play any animation in first person. + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + * @param onComplete The function to fire on completion (ie to transition to the next state). If this is null, the animation and audio will be looped on completion. + */ + public StateTransitionUtilItem( + Object stateEnum, + Supplier getFirstPersonAnimation, + Supplier getThirdPersonAnimation, + Runnable onComplete + ){ + this.stateEnum = stateEnum; + this.getFirstPersonAnimation = getFirstPersonAnimation; + this.getThirdPersonAnimation = getThirdPersonAnimation; + this.onComplete = onComplete; + } + + /** + * Constructor + * @param stateEnum The enum value for this state + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + * @param onComplete The function to fire on completion (ie to transition to the next state). If this is null, the animation and audio will be looped on completion. + */ + public StateTransitionUtilItem( + Object stateEnum, + Supplier getThirdPersonAnimation, + Runnable onComplete + ){ + this.stateEnum = stateEnum; + this.getThirdPersonAnimation = getThirdPersonAnimation; + this.onComplete = onComplete; + } + + /** + * Constructor + * @param stateEnum The enum value for this state + * @param firstPersonAnimation The animation to play in first person. If this is null, it will not play any animation in first person. + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + */ + public StateTransitionUtilItem( + Object stateEnum, + Supplier getFirstPersonAnimation, + Supplier getThirdPersonAnimation + ){ + this.stateEnum = stateEnum; + this.getFirstPersonAnimation = getFirstPersonAnimation; + this.getThirdPersonAnimation = getThirdPersonAnimation; + } + + /** + * Constructor + * @param stateEnum The enum value for this state + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + */ + public StateTransitionUtilItem( + Object stateEnum, + Supplier getThirdPersonAnimation + ){ + this.stateEnum = stateEnum; + this.getThirdPersonAnimation = getThirdPersonAnimation; + } + + /** + * Constructor + * @param stateEnum The enum value for this state + * @param thirdPersonAnimation The animation to play in third person. If this is null, it will not play any animation in third person. + * @param onComplete The function to fire on completion (ie to transition to the next state). If this is null, the animation and audio will be looped on completion. + */ + public StateTransitionUtilItem( + Object stateEnum, + String thirdPersonAnimation, + Runnable onComplete + ){ + this.stateEnum = stateEnum; + this.thirdPersonAnimation = thirdPersonAnimation; + this.onComplete = onComplete; + } + + } + +} diff --git a/src/main/java/electrosphere/entity/state/block/ClientBlockTree.java b/src/main/java/electrosphere/entity/state/block/ClientBlockTree.java index 9c93e7c9..ee9ea0dd 100644 --- a/src/main/java/electrosphere/entity/state/block/ClientBlockTree.java +++ b/src/main/java/electrosphere/entity/state/block/ClientBlockTree.java @@ -4,15 +4,15 @@ package electrosphere.entity.state.block; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; -import electrosphere.entity.EntityUtils; import electrosphere.entity.btree.BehaviorTree; +import electrosphere.entity.btree.StateTransitionUtil; +import electrosphere.entity.btree.StateTransitionUtil.StateTransitionUtilItem; import electrosphere.game.data.creature.type.block.BlockSystem; import electrosphere.net.synchronization.BehaviorTreeIdEnums; import electrosphere.net.synchronization.annotation.SyncedField; import electrosphere.net.synchronization.annotation.SynchronizableEnum; import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree; -import electrosphere.renderer.actor.Actor; @SynchronizedBehaviorTree(name = "clientBlockTree", isServer = false, correspondingTree="serverBlockTree") /** @@ -43,50 +43,45 @@ public class ClientBlockTree implements BehaviorTree { //The data for block animations BlockSystem blockSystem; + //The state transition util + StateTransitionUtil stateTransitionUtil; + /** * Constructor */ private ClientBlockTree(Entity parent, BlockSystem blockSystem){ this.parent = parent; this.blockSystem = blockSystem; + this.stateTransitionUtil = StateTransitionUtil.create(parent, false, new StateTransitionUtilItem[]{ + new StateTransitionUtilItem( + BlockState.WIND_UP, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getWindUpFirstPersonAnimation();}, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getWindUpAnimation();} + ), + new StateTransitionUtilItem( + BlockState.BLOCKING, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getMainFirstPersonAnimation();}, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getMainAnimation();} + ), + new StateTransitionUtilItem( + BlockState.COOLDOWN, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getCooldownFirstPersonAnimation();}, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getCooldownAnimation();} + ), + }); } @Override public void simulate(float deltaTime) { - Actor actor = EntityUtils.getActor(parent); switch(state){ case WIND_UP: { - if(actor != null && blockSystem.getBlockVariant(currentBlockVariant) != null){ - String animationToPlay = blockSystem.getBlockVariant(currentBlockVariant).getWindUpAnimation(); - if( - !actor.isPlayingAnimation() || !actor.isPlayingAnimation(animationToPlay) - ){ - actor.playAnimation(animationToPlay,1); - actor.incrementAnimationTime(0.0001); - } - } + this.stateTransitionUtil.simulate(BlockState.WIND_UP); } break; case BLOCKING: { - if(actor != null && blockSystem.getBlockVariant(currentBlockVariant) != null){ - String animationToPlay = blockSystem.getBlockVariant(currentBlockVariant).getMainAnimation(); - if( - !actor.isPlayingAnimation() || !actor.isPlayingAnimation(animationToPlay) - ){ - actor.playAnimation(animationToPlay,1); - actor.incrementAnimationTime(0.0001); - } - } + this.stateTransitionUtil.simulate(BlockState.BLOCKING); } break; case COOLDOWN: { - if(actor != null && blockSystem.getBlockVariant(currentBlockVariant) != null){ - String animationToPlay = blockSystem.getBlockVariant(currentBlockVariant).getCooldownAnimation(); - if( - !actor.isPlayingAnimation() || !actor.isPlayingAnimation(animationToPlay) - ){ - actor.playAnimation(animationToPlay,1); - actor.incrementAnimationTime(0.0001); - } - } + this.stateTransitionUtil.simulate(BlockState.COOLDOWN); } break; case NOT_BLOCKING: { diff --git a/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java b/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java index 05e5f6b5..017abaac 100644 --- a/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java +++ b/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java @@ -5,12 +5,12 @@ import electrosphere.net.synchronization.FieldIdEnums; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; -import electrosphere.entity.EntityUtils; import electrosphere.entity.btree.BehaviorTree; +import electrosphere.entity.btree.StateTransitionUtil; +import electrosphere.entity.btree.StateTransitionUtil.StateTransitionUtilItem; import electrosphere.net.synchronization.BehaviorTreeIdEnums; import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils; -import electrosphere.server.poseactor.PoseActor; import electrosphere.net.parser.net.message.SynchronizationMessage; import electrosphere.server.datacell.utils.DataCellSearchUtils; @@ -38,18 +38,38 @@ public class ServerBlockTree implements BehaviorTree { //The data for block animations BlockSystem blockSystem; + //The state transition util + StateTransitionUtil stateTransitionUtil; + /** * Constructor */ private ServerBlockTree(Entity parent, BlockSystem blockSystem){ this.parent = parent; this.blockSystem = blockSystem; + this.stateTransitionUtil = StateTransitionUtil.create(parent, true, new StateTransitionUtilItem[]{ + new StateTransitionUtilItem( + BlockState.WIND_UP, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getWindUpAnimation();}, + () -> {this.setState(BlockState.BLOCKING);} + ), + new StateTransitionUtilItem( + BlockState.BLOCKING, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getMainAnimation();} + ), + new StateTransitionUtilItem( + BlockState.COOLDOWN, + () -> {return this.blockSystem.getBlockVariant(this.currentBlockVariant).getCooldownAnimation();}, + () -> {this.setState(BlockState.NOT_BLOCKING);} + ), + }); } /** * Starts the block tree */ public void start(){ + this.stateTransitionUtil.reset(); setState(BlockState.WIND_UP); } @@ -57,45 +77,21 @@ public class ServerBlockTree implements BehaviorTree { * Stops the block tree */ public void stop(){ + this.stateTransitionUtil.reset(); setState(BlockState.COOLDOWN); } @Override public void simulate(float deltaTime) { - PoseActor poseActor = EntityUtils.getPoseActor(parent); switch(state){ case WIND_UP: { - if(poseActor != null && blockSystem.getBlockVariant(currentBlockVariant) != null){ - String animationToPlay = blockSystem.getBlockVariant(currentBlockVariant).getWindUpAnimation(); - if( - !poseActor.isPlayingAnimation() || !poseActor.isPlayingAnimation(animationToPlay) - ){ - poseActor.playAnimation(animationToPlay,1); - poseActor.incrementAnimationTime(0.0001); - } - } + this.stateTransitionUtil.simulate(BlockState.WIND_UP); } break; case BLOCKING: { - if(poseActor != null && blockSystem.getBlockVariant(currentBlockVariant) != null){ - String animationToPlay = blockSystem.getBlockVariant(currentBlockVariant).getMainAnimation(); - if( - !poseActor.isPlayingAnimation() || !poseActor.isPlayingAnimation(animationToPlay) - ){ - poseActor.playAnimation(animationToPlay,1); - poseActor.incrementAnimationTime(0.0001); - } - } + this.stateTransitionUtil.simulate(BlockState.BLOCKING); } break; case COOLDOWN: { - if(poseActor != null && blockSystem.getBlockVariant(currentBlockVariant) != null){ - String animationToPlay = blockSystem.getBlockVariant(currentBlockVariant).getCooldownAnimation(); - if( - !poseActor.isPlayingAnimation() || !poseActor.isPlayingAnimation(animationToPlay) - ){ - poseActor.playAnimation(animationToPlay,1); - poseActor.incrementAnimationTime(0.0001); - } - } + this.stateTransitionUtil.simulate(BlockState.COOLDOWN); } break; case NOT_BLOCKING: { diff --git a/src/main/java/electrosphere/game/data/creature/type/block/BlockVariant.java b/src/main/java/electrosphere/game/data/creature/type/block/BlockVariant.java index 2d87a748..b1fde802 100644 --- a/src/main/java/electrosphere/game/data/creature/type/block/BlockVariant.java +++ b/src/main/java/electrosphere/game/data/creature/type/block/BlockVariant.java @@ -21,6 +21,15 @@ public class BlockVariant { //the animation to play when cooling down String cooldownAnimation; + //the animation to play in first person when winding up + String windUpFirstPersonAnimation; + + //the main animation to play in first person while blocking + String mainFirstPersonAnimation; + + //the animation to play in first person when cooling down + String cooldownFirstPersonAnimation; + //the list of default equipment cases that this variant should be used for List defaults; @@ -56,6 +65,30 @@ public class BlockVariant { return cooldownAnimation; } + /** + * The animation to play in first person when winding up + * @return + */ + public String getWindUpFirstPersonAnimation(){ + return windUpFirstPersonAnimation; + } + + /** + * The main animation to play in first person while blocking + * @return + */ + public String getMainFirstPersonAnimation(){ + return mainFirstPersonAnimation; + } + + /** + * The animation to play in first person when cooling down + * @return + */ + public String getCooldownFirstPersonAnimation(){ + return cooldownFirstPersonAnimation; + } + /** * the list of default equipment cases that this variant should be used for * @return