From 3d6b71bf01c5d7249e195c944e208d5946a7f5b9 Mon Sep 17 00:00:00 2001 From: austin Date: Tue, 17 Sep 2024 21:58:15 -0400 Subject: [PATCH] initial hitstun implementation --- assets/Data/entity/creatures/human.json | 1 + assets/Data/entity/creatures/skeleton.json | 1 + assets/Data/entity/items.json | 2 +- assets/Textures/icons/greatsword.png | Bin 0 -> 4982 bytes docs/src/progress/currenttarget.md | 17 ++- docs/src/progress/renderertodo.md | 3 + .../collision/ClientLocalHitboxCollision.java | 99 ++++++++++++++++++ ...java => ClientNetworkHitboxCollision.java} | 6 +- .../client/scene/ClientSceneWrapper.java | 4 +- .../menu/ingame/MenuGeneratorsInventory.java | 17 ++- .../collision/hitbox/HitboxUtils.java | 80 -------------- .../entity/state/attack/ClientAttackTree.java | 44 ++++++++ .../data/creature/type/attack/AttackMove.java | 14 +++ .../net/client/protocol/CombatProtocol.java | 4 +- .../electrosphere/renderer/actor/Actor.java | 27 ++++- .../renderer/actor/ActorAnimationMask.java | 21 ++++ .../ui/components/NaturalInventoryPanel.java | 8 +- .../ServerHitboxResolutionCallback.java | 10 +- 18 files changed, 255 insertions(+), 103 deletions(-) create mode 100644 assets/Textures/icons/greatsword.png create mode 100644 src/main/java/electrosphere/client/collision/ClientLocalHitboxCollision.java rename src/main/java/electrosphere/client/collision/{ClientHitboxCollision.java => ClientNetworkHitboxCollision.java} (93%) diff --git a/assets/Data/entity/creatures/human.json b/assets/Data/entity/creatures/human.json index 3e818850..1f7efcc0 100644 --- a/assets/Data/entity/creatures/human.json +++ b/assets/Data/entity/creatures/human.json @@ -469,6 +469,7 @@ "driftFrameStart" : 1, "driftFrameEnd" : 10, "initialMove" : true, + "hitstun" : 7, "attackState" : { "animation" : { "nameFirstPerson" : "SwordR2HSlash", diff --git a/assets/Data/entity/creatures/skeleton.json b/assets/Data/entity/creatures/skeleton.json index 99e4d279..e79cb6f1 100644 --- a/assets/Data/entity/creatures/skeleton.json +++ b/assets/Data/entity/creatures/skeleton.json @@ -353,6 +353,7 @@ "driftFrameStart" : 1, "driftFrameEnd" : 10, "initialMove" : true, + "hitstun" : 7, "attackState" : { "animation" : { "nameFirstPerson" : "SwordR2HSlash1", diff --git a/assets/Data/entity/items.json b/assets/Data/entity/items.json index 5d041638..45bd3553 100644 --- a/assets/Data/entity/items.json +++ b/assets/Data/entity/items.json @@ -125,7 +125,7 @@ "offsetY" : 0.0, "offsetZ" : 0.0 }, - "iconPath" : "Textures/icons/itemIconWeapon.png" + "iconPath" : "Textures/icons/greatsword.png" }, { "id" : "bow1", diff --git a/assets/Textures/icons/greatsword.png b/assets/Textures/icons/greatsword.png new file mode 100644 index 0000000000000000000000000000000000000000..0883b12e873f51f06a4a9a89c047d4bb6574b1fd GIT binary patch literal 4982 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 AkN^Mx literal 0 HcmV?d00001 diff --git a/docs/src/progress/currenttarget.md b/docs/src/progress/currenttarget.md index fec39e19..db80d87d 100644 --- a/docs/src/progress/currenttarget.md +++ b/docs/src/progress/currenttarget.md @@ -16,14 +16,21 @@ Ticketed randomizer node for BTs to more heavily weight attacking and waiting + feedback driven requirements - UI spacing and scaling - Come up with a title for the game and create a title menu for it (ideally with some animation and music) - Better skybox - Model clothing, hair for the human - Hitstun, particles, light on sword collision Idle viewmodel does not show hands Add punching/unarmed combat + UI spacing and scaling + Better skybox + - Fix transparency calculations for far-out objects Crouching + Model clothing, hair for the human + particles, light on sword collision + - Requires particle manager overhaul + Come up with a title for the game and create a title menu for it (ideally with some animation and music) + - Flames moving up the screen + - Requires particle manager overhaul + - Requires actor panel that can play scenes + - Requires rendering overhaul + - Requires finding an sfx for flames Objectives - PVP arena mode initially? - Spawn player at start of a dungeon diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 6ea5f646..cfa53079 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -792,6 +792,9 @@ Start proliferating audio through ui Item-based ui audio Better sfx for opening/closing inventory menu Different title menu audio +New katana icon +UI fix +Initial hitstun implementation # TODO diff --git a/src/main/java/electrosphere/client/collision/ClientLocalHitboxCollision.java b/src/main/java/electrosphere/client/collision/ClientLocalHitboxCollision.java new file mode 100644 index 00000000..50350bc7 --- /dev/null +++ b/src/main/java/electrosphere/client/collision/ClientLocalHitboxCollision.java @@ -0,0 +1,99 @@ +package electrosphere.client.collision; + +import org.joml.Vector3d; +import org.ode4j.ode.DContactGeom; +import org.ode4j.ode.DGeom; + +import electrosphere.collision.collidable.Collidable; +import electrosphere.entity.Entity; +import electrosphere.entity.state.attach.AttachUtils; +import electrosphere.entity.state.attack.ClientAttackTree; +import electrosphere.entity.state.hitbox.HitboxCollectionState; +import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxState; +import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxType; + +public class ClientLocalHitboxCollision { + + /** + * Handles a damage collision on the client + * @param impactor the entity initiating the collision + * @param receiver the entity receiving the collision + */ + public static void clientDamageHitboxColision(DContactGeom contactGeom, DGeom impactorGeom, DGeom receiverGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude){ + + Entity impactorParent = impactor.getParent(); + Entity receiverParent = receiver.getParent(); + HitboxCollectionState impactorState = HitboxCollectionState.getHitboxState(impactorParent); + HitboxCollectionState receiverState = HitboxCollectionState.getHitboxState(receiverParent); + HitboxState impactorShapeStatus = impactorState.getShapeStatus(impactorGeom); + HitboxState receiverShapeStatus = receiverState.getShapeStatus(receiverGeom); + + + //currently, impactor needs to be an item, and the receiver must not be an item + boolean isDamageEvent = + impactorShapeStatus != null && + receiverShapeStatus != null && + impactorShapeStatus.getType() == HitboxType.HIT && + receiverShapeStatus.getType() == HitboxType.HURT && + AttachUtils.getParent(impactorParent) != receiverParent + ; + + if(impactorShapeStatus != null){ + impactorShapeStatus.setHadCollision(true); + } + if(receiverShapeStatus != null){ + receiverShapeStatus.setHadCollision(true); + } + + if(isDamageEvent){ + //TODO: client logic for audio etc + if(AttachUtils.hasParent(impactorParent)){ + Entity parent = AttachUtils.getParent(impactorParent); + if(ClientAttackTree.getClientAttackTree(parent) != null){ + ClientAttackTree clientAttackTree = ClientAttackTree.getClientAttackTree(parent); + if(clientAttackTree.canCollideEntity(receiverParent)){ + clientAttackTree.collideEntity(receiverParent); + clientAttackTree.freezeFrame(); + } + } + } + } + + // Entity hitboxParent = (Entity)impactor.getData(EntityDataStrings.COLLISION_ENTITY_DATA_PARENT); + // Entity hurtboxParent = (Entity)receiver.getData(EntityDataStrings.COLLISION_ENTITY_DATA_PARENT); + + //if the entity is attached to is an item, we need to compare with the parent of the item + //to make sure you don't stab yourself for instance + // boolean isItem = ItemUtils.isItem(hitboxParent);//hitboxParent.containsKey(EntityDataStrings.ITEM_IS_ITEM); + // Entity hitboxAttachParent = AttachUtils.getParent(hitboxParent); + + // if(isItem){ + // if(hitboxAttachParent != hurtboxParent){ + // Vector3d hurtboxPos = EntityUtils.getPosition(receiver); + // ParticleEffects.spawnBloodsplats(new Vector3f((float)hurtboxPos.x,(float)hurtboxPos.y,(float)hurtboxPos.z).add(0,0.1f,0), 20, 40); + // } + // } else { + + // //client no longer manages damage; however, keeping this code around for the moment to show how we + // //might approach adding client-side effects as soon as impact occurs (ie play a sound, shoot sparks, etc) + // //before the server responds with a valid collision event or not + + // // int damage = 0; + // // //for entities using attacktree + // // if(CreatureUtils.clientGetAttackTree(hitboxParent) != null){ + // // damage = ItemUtils.getWeaponDataRaw(hitboxParent).getDamage(); + // // } else { + // // //for entities using shooter tree + // // if(ProjectileTree.getProjectileTree(hitboxParent) != null){ + // // damage = (int)ProjectileTree.getProjectileTree(hitboxParent).getDamage(); + // // } + // // } + // // LifeUtils.getLifeState(hurtboxParent).damage(damage); + // // if(!LifeUtils.getLifeState(hurtboxParent).isIsAlive()){ + // // EntityUtils.getPosition(hurtboxParent).set(Globals.spawnPoint); + // // LifeUtils.getLifeState(hurtboxParent).revive(); + // // } + // } + } + +} diff --git a/src/main/java/electrosphere/client/collision/ClientHitboxCollision.java b/src/main/java/electrosphere/client/collision/ClientNetworkHitboxCollision.java similarity index 93% rename from src/main/java/electrosphere/client/collision/ClientHitboxCollision.java rename to src/main/java/electrosphere/client/collision/ClientNetworkHitboxCollision.java index 83aab7e9..57382230 100644 --- a/src/main/java/electrosphere/client/collision/ClientHitboxCollision.java +++ b/src/main/java/electrosphere/client/collision/ClientNetworkHitboxCollision.java @@ -13,7 +13,7 @@ import electrosphere.logger.LoggerInterface; /** * Client methods for handling hitbox collisions reported by the server */ -public class ClientHitboxCollision { +public class ClientNetworkHitboxCollision { /** * Performs client logic for a collision that the server reports @@ -29,11 +29,11 @@ public class ClientHitboxCollision { case HitboxData.HITBOX_TYPE_HURT: case HitboxData.HITBOX_TYPE_HURT_CONNECTED: { Globals.hitboxAudioService.playAudioPositional(senderEntity, receiverEntity, hitboxType, hurtboxType, position); - ClientHitboxCollision.conditionallySpawnParticles(senderEntity, receiverEntity, hitboxType, hurtboxType, position); + ClientNetworkHitboxCollision.conditionallySpawnParticles(senderEntity, receiverEntity, hitboxType, hurtboxType, position); } break; case HitboxData.HITBOX_TYPE_BLOCK_CONNECTED: { Globals.hitboxAudioService.playAudioPositional(senderEntity, receiverEntity, hitboxType, hurtboxType, position); - ClientHitboxCollision.conditionallySpawnParticles(senderEntity, receiverEntity, hitboxType, hurtboxType, position); + ClientNetworkHitboxCollision.conditionallySpawnParticles(senderEntity, receiverEntity, hitboxType, hurtboxType, position); } break; default: { LoggerInterface.loggerEngine.WARNING("Client handling undefined hurtbox type: " + hurtboxType); diff --git a/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java b/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java index e48f8403..8eb2a933 100644 --- a/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java +++ b/src/main/java/electrosphere/client/scene/ClientSceneWrapper.java @@ -7,11 +7,11 @@ import org.joml.Vector3d; import org.ode4j.ode.DContactGeom; import org.ode4j.ode.DGeom; +import electrosphere.client.collision.ClientLocalHitboxCollision; import electrosphere.collision.CollisionEngine; import electrosphere.collision.CollisionEngine.CollisionResolutionCallback; import electrosphere.collision.collidable.Collidable; import electrosphere.collision.hitbox.HitboxManager; -import electrosphere.collision.hitbox.HitboxUtils; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.scene.Scene; @@ -225,7 +225,7 @@ public class ClientSceneWrapper { CollisionResolutionCallback resolutionCallback = new CollisionResolutionCallback() { @Override public void resolve(DContactGeom geom, DGeom impactorGeom, DGeom receiverGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude) { - HitboxUtils.clientDamageHitboxColision(geom, impactorGeom, receiverGeom, impactor, receiver, normal, localPosition, worldPos, magnitude); + ClientLocalHitboxCollision.clientDamageHitboxColision(geom, impactorGeom, receiverGeom, impactor, receiver, normal, localPosition, worldPos, magnitude); } }; diff --git a/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInventory.java b/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInventory.java index 997e3b90..888cce41 100644 --- a/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInventory.java +++ b/src/main/java/electrosphere/client/ui/menu/ingame/MenuGeneratorsInventory.java @@ -1,9 +1,12 @@ package electrosphere.client.ui.menu.ingame; +import electrosphere.audio.VirtualAudioSourceManager.VirtualAudioSourceType; import electrosphere.client.ui.menu.WindowStrings; import electrosphere.client.ui.menu.WindowUtils; import electrosphere.engine.Globals; import electrosphere.entity.state.inventory.InventoryUtils; +import electrosphere.entity.types.item.ItemUtils; +import electrosphere.game.data.item.type.Item; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.ui.components.PlayerInventoryWindow; import electrosphere.renderer.ui.elements.Div; @@ -23,15 +26,23 @@ public class MenuGeneratorsInventory { if(Globals.draggedItem != null){ //drop item InventoryUtils.clientAttemptEjectItem(Globals.playerEntity,Globals.draggedItem); + //play sound effect + if(Globals.virtualAudioSourceManager != null){ + Item itemData = Globals.gameConfigCurrent.getItemMap().getItem(ItemUtils.getType(Globals.draggedItem)); + if(itemData.getItemAudio() != null && itemData.getItemAudio().getUIReleaseAudio() != null){ + Globals.virtualAudioSourceManager.createVirtualAudioSource(itemData.getItemAudio().getUIReleaseAudio(), VirtualAudioSourceType.UI, false); + } else { + Globals.virtualAudioSourceManager.createVirtualAudioSource("/Audio/inventorySlotItem.ogg", VirtualAudioSourceType.UI, false); + } + } //clear ui WindowUtils.cleanItemDraggingWindow(); - String sourceWindowId = WindowStrings.WINDOW_CHARACTER; - WindowUtils.replaceWindow(sourceWindowId,PlayerInventoryWindow.createPlayerInventoryWindow(Globals.playerEntity)); + //re-render inventory + WindowUtils.replaceWindow(WindowStrings.WINDOW_CHARACTER, PlayerInventoryWindow.createPlayerInventoryWindow(Globals.playerEntity)); //null globals Globals.dragSourceInventory = null; Globals.draggedItem = null; return false; - // } } return true; }}); diff --git a/src/main/java/electrosphere/collision/hitbox/HitboxUtils.java b/src/main/java/electrosphere/collision/hitbox/HitboxUtils.java index e442a9fb..6988cbfe 100644 --- a/src/main/java/electrosphere/collision/hitbox/HitboxUtils.java +++ b/src/main/java/electrosphere/collision/hitbox/HitboxUtils.java @@ -1,96 +1,16 @@ package electrosphere.collision.hitbox; -import electrosphere.collision.collidable.Collidable; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; -import electrosphere.entity.state.attach.AttachUtils; -import electrosphere.entity.state.hitbox.HitboxCollectionState; -import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxState; -import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxType; import electrosphere.game.data.collidable.HitboxData; import org.joml.Vector3d; -import org.ode4j.ode.DContactGeom; -import org.ode4j.ode.DGeom; /** * Utilities for working with hitboxes */ public class HitboxUtils { - - /** - * Handles a damage collision on the client - * @param impactor the entity initiating the collision - * @param receiver the entity receiving the collision - */ - public static void clientDamageHitboxColision(DContactGeom contactGeom, DGeom impactorGeom, DGeom receiverGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude){ - - Entity impactorParent = impactor.getParent(); - Entity receiverParent = receiver.getParent(); - HitboxCollectionState impactorState = HitboxCollectionState.getHitboxState(impactorParent); - HitboxCollectionState receiverState = HitboxCollectionState.getHitboxState(receiverParent); - HitboxState impactorShapeStatus = impactorState.getShapeStatus(impactorGeom); - HitboxState receiverShapeStatus = receiverState.getShapeStatus(receiverGeom); - - - //currently, impactor needs to be an item, and the receiver must not be an item - boolean isDamageEvent = - impactorShapeStatus != null && - receiverShapeStatus != null && - impactorShapeStatus.getType() == HitboxType.HIT && - receiverShapeStatus.getType() == HitboxType.HURT && - AttachUtils.getParent(impactorParent) != receiverParent - ; - - if(impactorShapeStatus != null){ - impactorShapeStatus.setHadCollision(true); - } - if(receiverShapeStatus != null){ - receiverShapeStatus.setHadCollision(true); - } - - if(isDamageEvent){ - //TODO: client logic for audio etc - } - - // Entity hitboxParent = (Entity)impactor.getData(EntityDataStrings.COLLISION_ENTITY_DATA_PARENT); - // Entity hurtboxParent = (Entity)receiver.getData(EntityDataStrings.COLLISION_ENTITY_DATA_PARENT); - - //if the entity is attached to is an item, we need to compare with the parent of the item - //to make sure you don't stab yourself for instance - // boolean isItem = ItemUtils.isItem(hitboxParent);//hitboxParent.containsKey(EntityDataStrings.ITEM_IS_ITEM); - // Entity hitboxAttachParent = AttachUtils.getParent(hitboxParent); - - // if(isItem){ - // if(hitboxAttachParent != hurtboxParent){ - // Vector3d hurtboxPos = EntityUtils.getPosition(receiver); - // ParticleEffects.spawnBloodsplats(new Vector3f((float)hurtboxPos.x,(float)hurtboxPos.y,(float)hurtboxPos.z).add(0,0.1f,0), 20, 40); - // } - // } else { - - // //client no longer manages damage; however, keeping this code around for the moment to show how we - // //might approach adding client-side effects as soon as impact occurs (ie play a sound, shoot sparks, etc) - // //before the server responds with a valid collision event or not - - // // int damage = 0; - // // //for entities using attacktree - // // if(CreatureUtils.clientGetAttackTree(hitboxParent) != null){ - // // damage = ItemUtils.getWeaponDataRaw(hitboxParent).getDamage(); - // // } else { - // // //for entities using shooter tree - // // if(ProjectileTree.getProjectileTree(hitboxParent) != null){ - // // damage = (int)ProjectileTree.getProjectileTree(hitboxParent).getDamage(); - // // } - // // } - // // LifeUtils.getLifeState(hurtboxParent).damage(damage); - // // if(!LifeUtils.getLifeState(hurtboxParent).isIsAlive()){ - // // EntityUtils.getPosition(hurtboxParent).set(Globals.spawnPoint); - // // LifeUtils.getLifeState(hurtboxParent).revive(); - // // } - // } - } - /** * Gets the data for a hitbox * @param e the entity encapsulating the hitbox diff --git a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java index 78ebf912..6c3865bd 100644 --- a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java +++ b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java @@ -4,6 +4,7 @@ package electrosphere.entity.state.attack; 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; @@ -25,7 +26,9 @@ import electrosphere.net.synchronization.annotation.SyncedField; import electrosphere.net.synchronization.annotation.SynchronizableEnum; import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree; import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums; +import electrosphere.renderer.actor.Actor; +import java.util.LinkedList; import java.util.List; import org.joml.Vector3d; @@ -93,6 +96,11 @@ public class ClientAttackTree implements BehaviorTree { String projectileToFire = null; String attackingPoint = null; + /** + * The list of entities that have collided with the current attack + */ + List collidedEntities = new LinkedList(); + //The state transition util StateTransitionUtil stateTransitionUtil; @@ -505,6 +513,39 @@ public class ClientAttackTree implements BehaviorTree { } return 1.0; } + + /** + * Freezes the animation for a frame or two when a collision occurs + */ + public void freezeFrame(){ + Actor actor = EntityUtils.getActor(parent); + if(this.currentMove != null && this.currentMove.getHitstun() != null){ + String animName = this.currentMove.getAttackState().getAnimation().getNameThirdPerson(); + actor.setFreezeFrames(animName, this.currentMove.getHitstun()); + if(parent == Globals.playerEntity && !Globals.controlHandler.cameraIsThirdPerson()){ + Actor viewmodelActor = EntityUtils.getActor(Globals.firstPersonEntity); + animName = this.currentMove.getAttackState().getAnimation().getNameFirstPerson(); + viewmodelActor.setFreezeFrames(animName, this.currentMove.getHitstun()); + } + } + } + + /** + * Checks if the target can be collided with + * @param target The target + * @return true if can be collided with, false otherwise (ie it has already collided) + */ + public boolean canCollideEntity(Entity target){ + return !this.collidedEntities.contains(target); + } + + /** + * Sets that the current attack has collided with the provided entity + * @param target The target entity + */ + public void collideEntity(Entity target){ + this.collidedEntities.add(target); + } /** *

Automatically generated

@@ -694,6 +735,9 @@ public class ClientAttackTree implements BehaviorTree { if(newState == AttackTreeState.BLOCK_RECOIL){ this.stateTransitionUtil.interrupt(AttackTreeState.ATTACK); } + if(newState == AttackTreeState.ATTACK){ + this.collidedEntities.clear(); + } this.setState(newState); } diff --git a/src/main/java/electrosphere/game/data/creature/type/attack/AttackMove.java b/src/main/java/electrosphere/game/data/creature/type/attack/AttackMove.java index 7155bcc4..b7dc5cc7 100644 --- a/src/main/java/electrosphere/game/data/creature/type/attack/AttackMove.java +++ b/src/main/java/electrosphere/game/data/creature/type/attack/AttackMove.java @@ -46,6 +46,11 @@ public class AttackMove { int driftFrameStart; //when do we start drifting int driftFrameEnd; //when do we stop drifting + /** + * Hitstun + */ + Integer hitstun; + /** * Gets the id of the attack move * @return the id of the attack move @@ -189,6 +194,15 @@ public class AttackMove { public boolean getFiresProjectile(){ return firesProjectile; } + + + /** + * The number of frames to freeze for on successfully landing this attack + * @return The number of frames or null + */ + public Integer getHitstun(){ + return hitstun; + } } diff --git a/src/main/java/electrosphere/net/client/protocol/CombatProtocol.java b/src/main/java/electrosphere/net/client/protocol/CombatProtocol.java index 961eec8b..9ea18736 100644 --- a/src/main/java/electrosphere/net/client/protocol/CombatProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/CombatProtocol.java @@ -2,7 +2,7 @@ package electrosphere.net.client.protocol; import org.joml.Vector3d; -import electrosphere.client.collision.ClientHitboxCollision; +import electrosphere.client.collision.ClientNetworkHitboxCollision; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.net.parser.net.message.CombatMessage; @@ -26,7 +26,7 @@ public class CombatProtocol implements ClientProtocolTemplate { Entity senderEntity = Globals.clientSceneWrapper.getEntityFromServerId(message.getentityID()); Entity receiverEntity = Globals.clientSceneWrapper.getEntityFromServerId(message.getreceiverEntityID()); if(senderEntity != null && receiverEntity != null){ - ClientHitboxCollision.handleHitboxCollision(senderEntity, receiverEntity, position, message.gethitboxType(), message.gethurtboxType()); + ClientNetworkHitboxCollision.handleHitboxCollision(senderEntity, receiverEntity, position, message.gethitboxType(), message.gethurtboxType()); } } break; } diff --git a/src/main/java/electrosphere/renderer/actor/Actor.java b/src/main/java/electrosphere/renderer/actor/Actor.java index b143ae73..74a7e6fc 100644 --- a/src/main/java/electrosphere/renderer/actor/Actor.java +++ b/src/main/java/electrosphere/renderer/actor/Actor.java @@ -98,9 +98,13 @@ public class Actor { public void incrementAnimationTime(double deltaTime){ toRemoveMasks.clear(); for(ActorAnimationMask mask : animationQueue){ - mask.setTime(mask.getTime() + deltaTime * animationScalar); - if(mask.getTime() > mask.getDuration()){ - toRemoveMasks.add(mask); + if(mask.getFreezeFrames() > 0){ + mask.setFreezeFrames(mask.getFreezeFrames() - 1); + } else { + mask.setTime(mask.getTime() + deltaTime * animationScalar); + if(mask.getTime() > mask.getDuration()){ + toRemoveMasks.add(mask); + } } } for(ActorAnimationMask mask : toRemoveMasks){ @@ -673,6 +677,23 @@ public class Actor { this.boneGroups = boneGroups; } + /** + * Sets the number of freeze frames for a given animation path if the animation is being played + * @param animationPath The path to the animation + * @param numFrames The number of frames to freeze for + */ + public void setFreezeFrames(String animationPath, int numFrames){ + if(numFrames < 1){ + throw new Error("Num frames less than 1 !" + numFrames); + } + for(ActorAnimationMask mask : this.animationQueue){ + if(mask.getAnimationName().contains(animationPath)){ + mask.setFreezeFrames(numFrames); + break; + } + } + } + /** diff --git a/src/main/java/electrosphere/renderer/actor/ActorAnimationMask.java b/src/main/java/electrosphere/renderer/actor/ActorAnimationMask.java index e4cf30d2..fb2106c7 100644 --- a/src/main/java/electrosphere/renderer/actor/ActorAnimationMask.java +++ b/src/main/java/electrosphere/renderer/actor/ActorAnimationMask.java @@ -23,6 +23,11 @@ public class ActorAnimationMask implements Comparable { //The mask of bones to apply this animation to List boneMask; + /** + * The number of frames to freeze this animation mask for + */ + int freezeFrames = 0; + /** * Constructor * @param priority @@ -130,6 +135,22 @@ public class ActorAnimationMask implements Comparable { return timeMax; } + /** + * Gets the current freeze frame count + * @return The number of freeze frames + */ + public int getFreezeFrames(){ + return freezeFrames; + } + + /** + * Sets the number of freeze frames remaining in the mask + * @param frameCount The number of frames + */ + public void setFreezeFrames(int frameCount){ + this.freezeFrames = frameCount; + } + @Override public int compareTo(ActorAnimationMask o) { ActorAnimationMask otherMask = (ActorAnimationMask)o; diff --git a/src/main/java/electrosphere/renderer/ui/components/NaturalInventoryPanel.java b/src/main/java/electrosphere/renderer/ui/components/NaturalInventoryPanel.java index a8a0352d..4c0da874 100644 --- a/src/main/java/electrosphere/renderer/ui/components/NaturalInventoryPanel.java +++ b/src/main/java/electrosphere/renderer/ui/components/NaturalInventoryPanel.java @@ -51,7 +51,13 @@ public class NaturalInventoryPanel { LoggerInterface.loggerUI.INFO("Natural inventory received drag release event"); if(Globals.draggedItem != null){ if(Globals.dragSourceInventory != inventory){ - if(Globals.dragSourceInventory instanceof UnrelationalInventoryState){ + if(Globals.dragSourceInventory instanceof RelationalInventoryState){ + if(ClientEquipState.hasEquipState(entity) && InventoryUtils.hasEquipInventory(entity)){ + RelationalInventoryState equipInventory = InventoryUtils.getEquipInventory(entity); + ClientEquipState equipState = ClientEquipState.getEquipState(entity); + equipState.commandAttemptUnequip(equipInventory.getItemSlot(Globals.draggedItem)); + } + } if(Globals.dragSourceInventory instanceof UnrelationalInventoryState){ //transfer item // sourceInventory.removeItem(Globals.draggedItem); // inventory.addItem(Globals.draggedItem); diff --git a/src/main/java/electrosphere/server/collision/ServerHitboxResolutionCallback.java b/src/main/java/electrosphere/server/collision/ServerHitboxResolutionCallback.java index 931645b8..dd3b8254 100644 --- a/src/main/java/electrosphere/server/collision/ServerHitboxResolutionCallback.java +++ b/src/main/java/electrosphere/server/collision/ServerHitboxResolutionCallback.java @@ -168,7 +168,7 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba // //handle attacker - this.handleAttackerCollision(impactorEntity,receiverEntity, isDamageEvent); + this.handleAttackerCollision(impactorEntity,receiverEntity, isBlockEvent); } if(isBlockEvent){ @@ -211,7 +211,9 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba if(impactorEntity != null && ServerAttackTree.getServerAttackTree(impactorEntity) != null){ ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(impactorEntity); impactorAttackTree.collideEntity(receiverEntity); - impactorAttackTree.recoilFromBlock(); + if(isBlock){ + impactorAttackTree.recoilFromBlock(); + } //if the receiver is an item that is equipped, collide with parent too if(receiverIsItem && receiverHasParent){ impactorAttackTree.collideEntity(AttachUtils.getParent(receiverEntity)); @@ -224,7 +226,9 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba } else if(impactorEntity != null && AttachUtils.hasParent(impactorEntity) && AttachUtils.getParent(impactorEntity) != null && ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorEntity)) != null){ ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorEntity)); impactorAttackTree.collideEntity(receiverEntity); - impactorAttackTree.recoilFromBlock(); + if(isBlock){ + impactorAttackTree.recoilFromBlock(); + } //if the receiver is an item that is equipped, collide with parent too if(receiverIsItem && receiverHasParent){ impactorAttackTree.collideEntity(AttachUtils.getParent(receiverEntity));