diff --git a/assets/Audio/weapons/collisions/Sword Hit A.wav b/assets/Audio/weapons/collisions/Sword Hit A.wav new file mode 100644 index 00000000..9342f975 Binary files /dev/null and b/assets/Audio/weapons/collisions/Sword Hit A.wav differ diff --git a/assets/Audio/weapons/collisions/Sword Hit B.wav b/assets/Audio/weapons/collisions/Sword Hit B.wav new file mode 100644 index 00000000..cf36bb83 Binary files /dev/null and b/assets/Audio/weapons/collisions/Sword Hit B.wav differ diff --git a/assets/Audio/weapons/collisions/Sword Hit C.wav b/assets/Audio/weapons/collisions/Sword Hit C.wav new file mode 100644 index 00000000..796ceecf Binary files /dev/null and b/assets/Audio/weapons/collisions/Sword Hit C.wav differ diff --git a/assets/Audio/weapons/collisions/Sword Hit D.wav b/assets/Audio/weapons/collisions/Sword Hit D.wav new file mode 100644 index 00000000..13460307 Binary files /dev/null and b/assets/Audio/weapons/collisions/Sword Hit D.wav differ diff --git a/assets/Audio/weapons/collisions/Sword Hit E.wav b/assets/Audio/weapons/collisions/Sword Hit E.wav new file mode 100644 index 00000000..2d1856d6 Binary files /dev/null and b/assets/Audio/weapons/collisions/Sword Hit E.wav differ diff --git a/docs/src/progress/currenttarget.md b/docs/src/progress/currenttarget.md index 72444f75..c8445579 100644 --- a/docs/src/progress/currenttarget.md +++ b/docs/src/progress/currenttarget.md @@ -9,7 +9,6 @@ review combat code (lifestate, damage calculation, etc) - Allow block hotboxxes to block damage audio fx for everything - - Block SFX + rearchitecture @@ -18,6 +17,4 @@ Hitboxes between server and client feel wayyyy off + bug fixes - On attack, the server weapon does not swing (it instead stops aligning) - The server does not interrupt attack on animation end diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 202e5f97..def9e6d2 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -539,6 +539,8 @@ Pass at client-server physics synchronization (08/12/2024) Fix server animation playing at reduced timescale +Block override concept for hitboxes +Block sfx # TODO diff --git a/src/main/java/electrosphere/audio/collision/HitboxAudioService.java b/src/main/java/electrosphere/audio/collision/HitboxAudioService.java index 3834a80a..6681b157 100644 --- a/src/main/java/electrosphere/audio/collision/HitboxAudioService.java +++ b/src/main/java/electrosphere/audio/collision/HitboxAudioService.java @@ -9,6 +9,8 @@ import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.types.item.ItemUtils; +import electrosphere.entity.types.object.ObjectUtils; +import electrosphere.game.data.collidable.HitboxData; import electrosphere.logger.LoggerInterface; /** @@ -25,6 +27,17 @@ public class HitboxAudioService { "Audio/weapons/collisions/Massive Punch C.wav", }; + /** + * Default audio files to play. Eventually should probably refactor into service that determines audio based on materials + */ + static String[] defaultBlockboxAudio = new String[]{ + "Audio/weapons/collisions/Sword Hit A.wav", + "Audio/weapons/collisions/Sword Hit B.wav", + "Audio/weapons/collisions/Sword Hit C.wav", + "Audio/weapons/collisions/Sword Hit D.wav", + "Audio/weapons/collisions/Sword Hit E.wav", + }; + /** * The random for selecting which file to play */ @@ -64,11 +77,66 @@ public class HitboxAudioService { * @return The audio file to play */ private String getAudioPath(Entity senderEntity, Entity receiverEntity, String hitboxType, String hurtboxType){ + boolean isBlockSound = false; + boolean isDamageSound = false; + switch(hitboxType){ + case HitboxData.HITBOX_TYPE_HIT: + case HitboxData.HITBOX_TYPE_HIT_CONNECTED: { + switch(hurtboxType){ + case HitboxData.HITBOX_TYPE_HIT: + case HitboxData.HITBOX_TYPE_HIT_CONNECTED: { + isBlockSound = true; + } break; + case HitboxData.HITBOX_TYPE_BLOCK_CONNECTED: { + isBlockSound = true; + } break; + case HitboxData.HITBOX_TYPE_HURT: + case HitboxData.HITBOX_TYPE_HURT_CONNECTED: { + isDamageSound = true; + } break; + } + } break; + case HitboxData.HITBOX_TYPE_BLOCK_CONNECTED: { + switch(hurtboxType){ + case HitboxData.HITBOX_TYPE_HIT: + case HitboxData.HITBOX_TYPE_HIT_CONNECTED: { + isBlockSound = true; + } break; + case HitboxData.HITBOX_TYPE_BLOCK_CONNECTED: { + isBlockSound = true; + } break; + case HitboxData.HITBOX_TYPE_HURT: + case HitboxData.HITBOX_TYPE_HURT_CONNECTED: { + + } break; + } + } break; + case HitboxData.HITBOX_TYPE_HURT: + case HitboxData.HITBOX_TYPE_HURT_CONNECTED: { + + } break; + } if(ItemUtils.isWeapon(senderEntity)){ if(CreatureUtils.isCreature(receiverEntity)){ - return this.getWeaponOnCreature(); + if(isBlockSound){ + return this.getWeaponOnCreature(); + } else if(isDamageSound){ + return this.getWeaponOnCreature(); + } + } else if(ItemUtils.isWeapon(receiverEntity)){ + if(isBlockSound){ + return this.getWeaponOnBlock(); + } } else { - LoggerInterface.loggerEngine.WARNING("Getting audio for unhandled hurtbox collision type!"); + String message = "Getting audio for unhandled hurtbox collision type!\n" + + "Is creature: " + CreatureUtils.isCreature(receiverEntity) + "\n" + + "Is item: " + ItemUtils.isItem(receiverEntity) + "\n" + + "Is weapon: " + ItemUtils.isWeapon(receiverEntity) + "\n" + + "Is object: " + ObjectUtils.isObject(receiverEntity); + if(ItemUtils.isItem(receiverEntity)){ + message = message + "\nItem Type: " + ItemUtils.getType(receiverEntity); + } + LoggerInterface.loggerEngine.WARNING(message); } } else { LoggerInterface.loggerEngine.WARNING("Getting audio for unhandled hitbox collision type!"); @@ -86,4 +154,13 @@ public class HitboxAudioService { return defaultHitboxAudio[roll]; } + /** + * Gets the audio to play when a weapon collides with a block box + * @return The audio file to play + */ + private String getWeaponOnBlock(){ + int roll = random.nextInt(defaultBlockboxAudio.length); + return defaultBlockboxAudio[roll]; + } + } diff --git a/src/main/java/electrosphere/client/collision/ClientHitboxCollision.java b/src/main/java/electrosphere/client/collision/ClientHitboxCollision.java index 690c379c..a97c9bb7 100644 --- a/src/main/java/electrosphere/client/collision/ClientHitboxCollision.java +++ b/src/main/java/electrosphere/client/collision/ClientHitboxCollision.java @@ -25,7 +25,10 @@ public class ClientHitboxCollision { switch(hurtboxType){ case HitboxData.HITBOX_TYPE_HURT: case HitboxData.HITBOX_TYPE_HURT_CONNECTED: { - Globals.hitboxAudioService.playAudio(senderEntity, receiverEntity, hitboxType, hurtboxType); + Globals.hitboxAudioService.playAudioPositional(senderEntity, receiverEntity, hitboxType, hurtboxType, position); + } break; + case HitboxData.HITBOX_TYPE_BLOCK_CONNECTED: { + Globals.hitboxAudioService.playAudioPositional(senderEntity, receiverEntity, hitboxType, hurtboxType, position); } break; default: { LoggerInterface.loggerEngine.WARNING("Client handling undefined hurtbox type: " + hurtboxType); diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 1a8dc875..aa6e5469 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -478,6 +478,11 @@ public class Globals { "/Audio/weapons/collisions/Massive Punch A.wav", "/Audio/weapons/collisions/Massive Punch B.wav", "/Audio/weapons/collisions/Massive Punch C.wav", + "Audio/weapons/collisions/Sword Hit A.wav", + "Audio/weapons/collisions/Sword Hit B.wav", + "Audio/weapons/collisions/Sword Hit C.wav", + "Audio/weapons/collisions/Sword Hit D.wav", + "Audio/weapons/collisions/Sword Hit E.wav", }; LoggerInterface.loggerStartup.INFO("Loading default audio resources"); for(String path : audioToInit){ diff --git a/src/main/java/electrosphere/entity/ClientEntityUtils.java b/src/main/java/electrosphere/entity/ClientEntityUtils.java index 168923f6..abdd1dd6 100644 --- a/src/main/java/electrosphere/entity/ClientEntityUtils.java +++ b/src/main/java/electrosphere/entity/ClientEntityUtils.java @@ -20,6 +20,15 @@ public class ClientEntityUtils { //reposition entity CollisionObjUtils.clientPositionCharacter(entity, position); } + + /** + * Destroys an entity on the client + * @param entity the entity to destroy + */ + public static void destroyEntity(Entity entity){ + //deregister all behavior trees + EntityUtils.cleanUpEntity(entity); + } } diff --git a/src/main/java/electrosphere/entity/state/attack/ServerAttackTree.java b/src/main/java/electrosphere/entity/state/attack/ServerAttackTree.java index b1555ad0..b86fe703 100644 --- a/src/main/java/electrosphere/entity/state/attack/ServerAttackTree.java +++ b/src/main/java/electrosphere/entity/state/attack/ServerAttackTree.java @@ -239,12 +239,19 @@ public class ServerAttackTree implements BehaviorTree { stillHold = false; } + /** + * Interrupts the tree + */ public void interrupt(){ setState(AttackTreeState.IDLE); - } - - public void slowdown(){ - setState(AttackTreeState.COOLDOWN); + //activate hitboxes + List attachedEntities = AttachUtils.getChildrenList(parent); + for(Entity currentAttached : attachedEntities){ + if(HitboxCollectionState.hasHitboxState(currentAttached)){ + HitboxCollectionState currentState = HitboxCollectionState.getHitboxState(currentAttached); + currentState.setActive(false); + } + } } @Override diff --git a/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java b/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java index 802343a9..ab3d80b4 100644 --- a/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java +++ b/src/main/java/electrosphere/entity/state/block/ServerBlockTree.java @@ -1,6 +1,8 @@ package electrosphere.entity.state.block; +import java.util.List; + import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; @@ -12,6 +14,8 @@ import electrosphere.net.parser.net.message.SynchronizationMessage; import electrosphere.server.datacell.utils.DataCellSearchUtils; import electrosphere.entity.state.block.ClientBlockTree.BlockState; +import electrosphere.entity.state.hitbox.HitboxCollectionState; +import electrosphere.entity.types.attach.AttachUtils; import electrosphere.game.data.creature.type.block.BlockSystem; import electrosphere.game.data.creature.type.block.BlockVariant; import electrosphere.net.synchronization.annotation.SyncedField; @@ -96,6 +100,15 @@ public class ServerBlockTree implements BehaviorTree { public void stop(){ this.stateTransitionUtil.reset(); setState(BlockState.COOLDOWN); + //activate hitboxes + List attachedEntities = AttachUtils.getChildrenList(parent); + for(Entity currentAttached : attachedEntities){ + if(HitboxCollectionState.hasHitboxState(currentAttached)){ + HitboxCollectionState currentState = HitboxCollectionState.getHitboxState(currentAttached); + currentState.setActive(false); + currentState.setBlockOverride(false); + } + } } @Override @@ -105,9 +118,27 @@ public class ServerBlockTree implements BehaviorTree { this.stateTransitionUtil.simulate(BlockState.WIND_UP); } break; case BLOCKING: { + //activate hitboxes + List attachedEntities = AttachUtils.getChildrenList(parent); + for(Entity currentAttached : attachedEntities){ + if(HitboxCollectionState.hasHitboxState(currentAttached)){ + HitboxCollectionState currentState = HitboxCollectionState.getHitboxState(currentAttached); + currentState.setActive(true); + currentState.setBlockOverride(true); + } + } this.stateTransitionUtil.simulate(BlockState.BLOCKING); } break; case COOLDOWN: { + //activate hitboxes + List attachedEntities = AttachUtils.getChildrenList(parent); + for(Entity currentAttached : attachedEntities){ + if(HitboxCollectionState.hasHitboxState(currentAttached)){ + HitboxCollectionState currentState = HitboxCollectionState.getHitboxState(currentAttached); + currentState.setActive(false); + currentState.setBlockOverride(false); + } + } this.stateTransitionUtil.simulate(BlockState.COOLDOWN); } break; case NOT_BLOCKING: { diff --git a/src/main/java/electrosphere/entity/state/hitbox/HitboxCollectionState.java b/src/main/java/electrosphere/entity/state/hitbox/HitboxCollectionState.java index 59a52777..4a6a6e5e 100644 --- a/src/main/java/electrosphere/entity/state/hitbox/HitboxCollectionState.java +++ b/src/main/java/electrosphere/entity/state/hitbox/HitboxCollectionState.java @@ -73,6 +73,9 @@ public class HitboxCollectionState { //controls whether the hitbox state is active or not boolean active = true; + //controls whether active hitboxes should be overwritten with block boxes + boolean blockOverride = false; + //the associated manager HitboxManager manager; @@ -153,7 +156,7 @@ public class HitboxCollectionState { rVal.hitboxGeomMap.put(hitboxDataRaw.getBone(),geom); } rVal.geoms.add(geom); - rVal.geomStateMap.put(geom,new HitboxState(hitboxDataRaw.getBone(), hitboxDataRaw, type, subType, shapeType, true)); + rVal.geomStateMap.put(geom,new HitboxState(hitboxDataRaw.getBone(), hitboxDataRaw, type, subType, shapeType, false)); } //create body with all the shapes @@ -467,6 +470,26 @@ public class HitboxCollectionState { } } + /** + * Gets the block override status + * @return true if should override hitboxes with blockboxes, false otherwise + */ + public boolean isBlockOverride(){ + return this.blockOverride; + } + + /** + * Sets the block override of the hitbox + * @param state true to override attack hitboxes with block boxes, false otherwise + */ + public void setBlockOverride(boolean state){ + this.blockOverride = state; + for(DGeom geom : this.geoms){ + HitboxState shapeState = this.getShapeStatus(geom); + shapeState.setBlockOverride(state); + } + } + /** * Gets the list of all DGeoms in the data * @return the list of all DGeoms @@ -526,6 +549,10 @@ public class HitboxCollectionState { //controls whether the hitbox is active boolean isActive; + //controls whether the block override is active or not + //if it is active, hitboxes should behave like block boxes + boolean blockOverride = false; + //the previous position of this hitbox shape Vector3d previousWorldPos = null; @@ -633,6 +660,22 @@ public class HitboxCollectionState { this.isActive = active; } + /** + * Gets whether the block override is active or not + * @return true if the block override is active, false otherwise + */ + public boolean isBlockOverride(){ + return blockOverride; + } + + /** + * Sets whether the block override is active or not + * @param blockOverride true if the block override is active, false otherwise + */ + public void setBlockOverride(boolean blockOverride){ + this.blockOverride = blockOverride; + } + /** * Gets the previous world position of this hitbox * @return The previous world position diff --git a/src/main/java/electrosphere/entity/types/item/ItemUtils.java b/src/main/java/electrosphere/entity/types/item/ItemUtils.java index c096207d..98d443f4 100644 --- a/src/main/java/electrosphere/entity/types/item/ItemUtils.java +++ b/src/main/java/electrosphere/entity/types/item/ItemUtils.java @@ -376,10 +376,17 @@ public class ItemUtils { */ public static Entity clientRecreateContainerItem(Entity item, Entity containingParent){ if(isItem(item)){ + Item itemData = Globals.gameConfigCurrent.getItemMap().getItem(ItemUtils.getType(item)); Entity rVal = EntityCreationUtils.createClientNonSpatialEntity(); if(getEquipWhitelist(item) != null){ rVal.putData(EntityDataStrings.ITEM_EQUIP_WHITELIST, getEquipWhitelist(item)); } + if(itemData.getWeaponData() != null){ + rVal.putData(EntityDataStrings.ITEM_IS_WEAPON, true); + WeaponData weaponData = itemData.getWeaponData(); + rVal.putData(EntityDataStrings.ITEM_WEAPON_CLASS,weaponData.getWeaponClass()); + rVal.putData(EntityDataStrings.ITEM_WEAPON_DATA_RAW,weaponData); + } rVal.putData(EntityDataStrings.ITEM_ICON,ItemUtils.getItemIcon(item)); rVal.putData(EntityDataStrings.ITEM_EQUIP_CLASS, item.getData(EntityDataStrings.ITEM_EQUIP_CLASS)); EntityUtils.setEntityType(rVal, ENTITY_TYPE_ITEM); diff --git a/src/main/java/electrosphere/game/data/collidable/HitboxData.java b/src/main/java/electrosphere/game/data/collidable/HitboxData.java index dfa32e3f..06623c1d 100644 --- a/src/main/java/electrosphere/game/data/collidable/HitboxData.java +++ b/src/main/java/electrosphere/game/data/collidable/HitboxData.java @@ -50,6 +50,9 @@ public class HitboxData { //controls whether the hitbox is active or not boolean active = false; + //override when block state is active. Used to make hitboxes function as blockboxes while blocking + boolean blockOverride = false; + //used for more advanced hitbox spawning to find hitbox position on frame update HitboxPositionCallback positionCallback; @@ -112,6 +115,22 @@ public class HitboxData { this.active = active; } + /** + * Gets the block override status + * @return true if should override hitboxes with block, false otherwise + */ + public boolean isBlockOverride(){ + return blockOverride; + } + + /** + * Sets the status of the block override + * @param blockOverride true if should override hitboxes with block, false otherwise + */ + public void setBlockOverride(boolean blockOverride){ + this.blockOverride = blockOverride; + } + /** * Sets the bone this hitbox is attached to * @param bone the bone to attach the hitbox to diff --git a/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java b/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java index 38ec501b..5953fec8 100644 --- a/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java +++ b/src/main/java/electrosphere/renderer/pipelines/debug/DebugContentPipeline.java @@ -358,6 +358,9 @@ public class DebugContentPipeline implements RenderPipeline { return "Textures/color/transparent_yellow.png"; } if(shapeStatus.isActive()){ + if(shapeStatus.isBlockOverride()){ + return "Textures/transparent_blue.png"; + } return "Textures/transparent_red.png"; } return "Textures/transparent_grey.png"; diff --git a/src/main/java/electrosphere/server/datacell/physics/ServerHitboxResolutionCallback.java b/src/main/java/electrosphere/server/datacell/physics/ServerHitboxResolutionCallback.java index 87909862..98d2b86d 100644 --- a/src/main/java/electrosphere/server/datacell/physics/ServerHitboxResolutionCallback.java +++ b/src/main/java/electrosphere/server/datacell/physics/ServerHitboxResolutionCallback.java @@ -16,6 +16,7 @@ import electrosphere.entity.state.movement.ProjectileTree; import electrosphere.entity.types.attach.AttachUtils; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.types.item.ItemUtils; +import electrosphere.game.data.collidable.HitboxData; import electrosphere.net.parser.net.message.CombatMessage; import electrosphere.server.datacell.utils.DataCellSearchUtils; @@ -44,6 +45,14 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba receiverShapeStatus.getType() == HitboxType.HURT && AttachUtils.getParent(impactorParent) != receiverParent ; + + boolean isBlockEvent = + impactorShapeStatus != null && + receiverShapeStatus != null && + impactorShapeStatus.getType() == HitboxType.HIT && + (receiverShapeStatus.getType() == HitboxType.BLOCK || (receiverShapeStatus.getType() == HitboxType.HIT && receiverShapeStatus.isBlockOverride())) && + AttachUtils.getParent(impactorParent) != receiverParent + ; if(impactorShapeStatus != null){ @@ -103,6 +112,19 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba } } } + + if(isBlockEvent){ + //tell clients an impact just happened + DataCellSearchUtils.getEntityDataCell(receiverParent).broadcastNetworkMessage( + CombatMessage.constructserverReportHitboxCollisionMessage( + impactorParent.getId(), + receiverParent.getId(), + Globals.timekeeper.getNumberOfSimFramesElapsed(), + impactorShapeStatus.getHitboxData().getType(), + HitboxData.HITBOX_TYPE_BLOCK_CONNECTED //TODO: more proper block override handling + ) + ); + } } }