initial hitstun implementation
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-09-17 21:58:15 -04:00
parent b8a0db5974
commit 3d6b71bf01
18 changed files with 255 additions and 103 deletions

View File

@ -469,6 +469,7 @@
"driftFrameStart" : 1,
"driftFrameEnd" : 10,
"initialMove" : true,
"hitstun" : 7,
"attackState" : {
"animation" : {
"nameFirstPerson" : "SwordR2HSlash",

View File

@ -353,6 +353,7 @@
"driftFrameStart" : 1,
"driftFrameEnd" : 10,
"initialMove" : true,
"hitstun" : 7,
"attackState" : {
"animation" : {
"nameFirstPerson" : "SwordR2HSlash1",

View File

@ -125,7 +125,7 @@
"offsetY" : 0.0,
"offsetZ" : 0.0
},
"iconPath" : "Textures/icons/itemIconWeapon.png"
"iconPath" : "Textures/icons/greatsword.png"
},
{
"id" : "bow1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -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

View File

@ -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

View File

@ -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();
// // }
// }
}
}

View File

@ -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);

View File

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

View File

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

View File

@ -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

View File

@ -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<Entity> collidedEntities = new LinkedList<Entity>();
//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);
}
/**
* <p> Automatically generated </p>
@ -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);
}

View File

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

View File

@ -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<CombatMessage> {
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;
}

View File

@ -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;
}
}
}
/**

View File

@ -23,6 +23,11 @@ public class ActorAnimationMask implements Comparable<ActorAnimationMask> {
//The mask of bones to apply this animation to
List<String> 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<ActorAnimationMask> {
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;

View File

@ -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);

View File

@ -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));