hitbox collision debounce work
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-08-13 15:51:46 -04:00
parent 4cf34d162c
commit 1ca9c60640
5 changed files with 282 additions and 68 deletions

View File

@ -553,6 +553,7 @@ Movement tweaks
Hitbox support offsets now Hitbox support offsets now
Multiple hitboxes per bone Multiple hitboxes per bone
Potential fix for client concurrency issue Potential fix for client concurrency issue
Debounce attack collisions
# TODO # TODO
@ -561,6 +562,8 @@ Potential fix for client concurrency issue
BIG BIG BIG BIG IMMEDIATE TO DO: BIG BIG BIG BIG IMMEDIATE TO DO:
always enforce opengl interface across all opengl calls jesus christ the bone uniform bug was impossible always enforce opengl interface across all opengl calls jesus christ the bone uniform bug was impossible
Rename "BehaviorTree" to be "Component" (what it actually is)
Ability to fully reload game engine state without exiting client Ability to fully reload game engine state without exiting client
- Back out to main menu and load a new level without any values persisting - Back out to main menu and load a new level without any values persisting
- Receive a teleport packet from server and flush all game state before requesting state from server again - Receive a teleport packet from server and flush all game state before requesting state from server again

View File

@ -119,7 +119,7 @@ public class HitboxAudioService {
if(ItemUtils.isWeapon(senderEntity)){ if(ItemUtils.isWeapon(senderEntity)){
if(CreatureUtils.isCreature(receiverEntity)){ if(CreatureUtils.isCreature(receiverEntity)){
if(isBlockSound){ if(isBlockSound){
return this.getWeaponOnCreature(); return this.getWeaponOnBlock();
} else if(isDamageSound){ } else if(isDamageSound){
return this.getWeaponOnCreature(); return this.getWeaponOnCreature();
} }

View File

@ -37,6 +37,7 @@ import electrosphere.renderer.actor.Actor;
import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.Realm;
import electrosphere.util.math.MathUtils; import electrosphere.util.math.MathUtils;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@ -82,6 +83,11 @@ public class ServerAttackTree implements BehaviorTree {
String projectileToFire = null; String projectileToFire = null;
String attackingPoint = 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 //The state transition util
StateTransitionUtil stateTransitionUtil; StateTransitionUtil stateTransitionUtil;
@ -190,14 +196,14 @@ public class ServerAttackTree implements BehaviorTree {
//figure out attack type we should be doing //figure out attack type we should be doing
String attackType = getAttackType(); String attackType = getAttackType();
//if we can attack, setup doing so //if we can attack, setup doing so
if(canAttack(attackType)){ if(this.canAttack(attackType)){
setAttackMoveTypeActive(attackType); this.setAttackMoveTypeActive(attackType);
currentMoveset = getMoveset(attackType); currentMoveset = this.getMoveset(attackType);
if(currentMoveset != null){ if(currentMoveset != null){
if(currentMove == null){ if(currentMove == null){
currentMove = currentMoveset.get(0); currentMove = currentMoveset.get(0);
} else { } else {
currentMove = getNextMove(currentMoveset,currentMove.getNextMoveId()); currentMove = this.getNextMove(currentMoveset,currentMove.getNextMoveId());
} }
if(currentMove != null){ if(currentMove != null){
firesProjectile = currentMove.getFiresProjectile(); firesProjectile = currentMove.getFiresProjectile();
@ -206,18 +212,20 @@ public class ServerAttackTree implements BehaviorTree {
} }
//set initial stuff (this alerts the client as well) //set initial stuff (this alerts the client as well)
setCurrentMoveId(currentMove.getAttackMoveId()); this.setCurrentMoveId(currentMove.getAttackMoveId());
//start tree //start tree
if(currentMove.getWindupState() != null){ if(currentMove.getWindupState() != null){
setState(AttackTreeState.WINDUP); this.setState(AttackTreeState.WINDUP);
} else if(currentMove.getAttackState() != null){ } else if(currentMove.getAttackState() != null){
setState(AttackTreeState.ATTACK); this.setState(AttackTreeState.ATTACK);
} else { } else {
LoggerInterface.loggerEngine.ERROR(new IllegalStateException("Trying to start attacking tree, but current move does not have windup or attack states defined!")); LoggerInterface.loggerEngine.ERROR(new IllegalStateException("Trying to start attacking tree, but current move does not have windup or attack states defined!"));
} }
//intuit can hold from presence of windup anim //intuit can hold from presence of windup anim
currentMoveCanHold = currentMove.getHoldState() != null; currentMoveCanHold = currentMove.getHoldState() != null;
//clear collided list
this.collidedEntities.clear();
//stop movement tree //stop movement tree
if(parent.containsKey(EntityDataStrings.SERVER_MOVEMENT_BT)){ if(parent.containsKey(EntityDataStrings.SERVER_MOVEMENT_BT)){
BehaviorTree movementTree = CreatureUtils.serverGetEntityMovementTree(parent); BehaviorTree movementTree = CreatureUtils.serverGetEntityMovementTree(parent);
@ -229,12 +237,15 @@ public class ServerAttackTree implements BehaviorTree {
EntityUtils.getRotation(parent).rotationTo(MathUtils.getOriginVector(), new Vector3d(movementVector.x,movementVector.y,movementVector.z)); EntityUtils.getRotation(parent).rotationTo(MathUtils.getOriginVector(), new Vector3d(movementVector.x,movementVector.y,movementVector.z));
frameCurrent = 0; frameCurrent = 0;
} else { } else {
setState(AttackTreeState.IDLE); this.setState(AttackTreeState.IDLE);
} }
} }
} }
} }
/**
* Releases the tree from holding its animation
*/
public void release(){ public void release(){
stillHold = false; stillHold = false;
} }
@ -429,10 +440,18 @@ public class ServerAttackTree implements BehaviorTree {
} }
} }
/**
* Adds a network message to the tree
* @param networkMessage The network message
*/
public void addNetworkMessage(EntityMessage networkMessage) { public void addNetworkMessage(EntityMessage networkMessage) {
networkMessageQueue.add(networkMessage); networkMessageQueue.add(networkMessage);
} }
/**
* Gets the current attack type
* @return The current attack type
*/
String getAttackType(){ String getAttackType(){
String rVal = null; String rVal = null;
if(ServerEquipState.hasEquipState(parent)){ if(ServerEquipState.hasEquipState(parent)){
@ -464,7 +483,7 @@ public class ServerAttackTree implements BehaviorTree {
* @param attackType The type of attack to perform * @param attackType The type of attack to perform
* @return true if it can attack, false otherwise * @return true if it can attack, false otherwise
*/ */
boolean canAttack(String attackType){ private boolean canAttack(String attackType){
boolean rVal = true; boolean rVal = true;
if(attackType == null){ if(attackType == null){
return false; return false;
@ -503,7 +522,13 @@ public class ServerAttackTree implements BehaviorTree {
return rVal; return rVal;
} }
AttackMove getNextMove(List<AttackMove> moveset, String nextMoveId){ /**
* Gets the next attack move
* @param moveset The moveset
* @param nextMoveId The next move's id
* @return The next move if it exists, null otherwise
*/
private AttackMove getNextMove(List<AttackMove> moveset, String nextMoveId){
AttackMove rVal = null; AttackMove rVal = null;
for(AttackMove move : moveset){ for(AttackMove move : moveset){
if(move.getAttackMoveId().equals(nextMoveId)){ if(move.getAttackMoveId().equals(nextMoveId)){
@ -539,6 +564,23 @@ public class ServerAttackTree implements BehaviorTree {
return (List<AttackMove>)parent.getData(attackType); return (List<AttackMove>)parent.getData(attackType);
} }
/**
* 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> * <p> Automatically generated </p>
* <p> * <p>

View File

@ -7,12 +7,22 @@ import electrosphere.entity.btree.StateTransitionUtil.StateTransitionUtilItem;
import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.Entity; import electrosphere.entity.Entity;
import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils; import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils;
import electrosphere.net.parser.net.message.CombatMessage;
import electrosphere.net.parser.net.message.SynchronizationMessage; import electrosphere.net.parser.net.message.SynchronizationMessage;
import electrosphere.server.datacell.utils.DataCellSearchUtils; import electrosphere.server.datacell.utils.DataCellSearchUtils;
import java.util.LinkedList;
import java.util.List;
import org.joml.Vector3d;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxState;
import electrosphere.entity.state.life.ClientLifeTree.LifeStateEnum; import electrosphere.entity.state.life.ClientLifeTree.LifeStateEnum;
import electrosphere.entity.types.item.ItemUtils;
import electrosphere.game.data.collidable.HitboxData;
import electrosphere.game.data.creature.type.HealthSystem; import electrosphere.game.data.creature.type.HealthSystem;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.synchronization.annotation.SyncedField; import electrosphere.net.synchronization.annotation.SyncedField;
import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree; import electrosphere.net.synchronization.annotation.SynchronizedBehaviorTree;
import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums; import electrosphere.net.synchronization.enums.BehaviorTreeIdEnums;
@ -48,8 +58,12 @@ public class ServerLifeTree implements BehaviorTree {
//the current iframe count //the current iframe count
int iFrameCurrent = 0; int iFrameCurrent = 0;
//accumulates collisions and determines if the parent takes damage or blocks them
List<CollisionEvent> collisionAccumulator = new LinkedList<CollisionEvent>();
@Override @Override
public void simulate(float deltaTime) { public void simulate(float deltaTime) {
this.handleAccumulatedCollisions();
switch(state){ switch(state){
case ALIVE: { case ALIVE: {
if(iFrameCurrent > 0){ if(iFrameCurrent > 0){
@ -101,6 +115,85 @@ public class ServerLifeTree implements BehaviorTree {
return this.state == LifeStateEnum.ALIVE; return this.state == LifeStateEnum.ALIVE;
} }
/**
* Handles the collisions that have been accumulated in this tree
*/
private void handleAccumulatedCollisions(){
int numCollisions = 0;
if(collisionAccumulator.size() > 0){
//get the blocked entities
List<Entity> blockedEntities = new LinkedList<Entity>();
for(CollisionEvent event : collisionAccumulator){
if(event.isBlock && !blockedEntities.contains(event.source)){
//don't allow multiple hits per collision
blockedEntities.add(event.source);
//tracking
numCollisions++;
//tell clients an impact just happened
DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage(
CombatMessage.constructserverReportHitboxCollisionMessage(
event.source.getId(),
parent.getId(),
Globals.timekeeper.getNumberOfSimFramesElapsed(),
event.sourceHitboxData.getHitboxData().getType(),
HitboxData.HITBOX_TYPE_BLOCK_CONNECTED
)
);
}
}
for(CollisionEvent event : collisionAccumulator){
if(event.isDamage && !blockedEntities.contains(event.source)){
//don't allow multiple hits per collision
blockedEntities.add(event.source);
//tracking
numCollisions++;
//tell clients an impact just happened
DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage(
CombatMessage.constructserverReportHitboxCollisionMessage(
event.source.getId(),
parent.getId(),
Globals.timekeeper.getNumberOfSimFramesElapsed(),
event.sourceHitboxData.getHitboxData().getType(),
event.parentHitboxData.getHitboxData().getType()
)
);
//do damage calculation
int damage = ItemUtils.getWeaponDataRaw(event.source).getDamage();
this.damage(damage);
if(!this.isAlive()){
throw new UnsupportedOperationException("Reviving not implemented yet!");
// Realm entityRealm = Globals.realmManager.getEntityRealm(receiverParent);
// EntityUtils.getPosition(receiverParent).set(entityRealm.getSpawnPoint());
// serverLifeTree.revive();
}
}
}
collisionAccumulator.clear();
}
if(numCollisions > 0){
LoggerInterface.loggerEngine.DEBUG("Server life tree handled: " + numCollisions + " unique collisions");
}
}
/**
* Adds a collision event to the life tree
* @param collisionSource The collision source
* @param position The position of the collision event
* @param sourceHitboxData The hitbox data for the source of the collision
* @param parentHitboxData The hitbox data for the parent of the tree
* @param isDamage True if this is a damage event
* @param isBlock True if this is a block event
*/
public void addCollisionEvent(Entity collisionSource, HitboxState sourceHitboxData, HitboxState parentHitboxData, Vector3d position, boolean isDamage, boolean isBlock){
CollisionEvent collisionEvent = new CollisionEvent(collisionSource, sourceHitboxData, parentHitboxData, isDamage, isBlock);
this.collisionAccumulator.add(collisionEvent);
}
/** /**
* <p> Automatically generated </p> * <p> Automatically generated </p>
* <p> * <p>
@ -192,4 +285,52 @@ public class ServerLifeTree implements BehaviorTree {
return (ServerLifeTree)entity.getData(EntityDataStrings.TREE_SERVERLIFETREE); return (ServerLifeTree)entity.getData(EntityDataStrings.TREE_SERVERLIFETREE);
} }
/**
* A single collision event
*/
public static class CollisionEvent {
/**
* The source of the collision event
*/
Entity source;
/**
* The hitbox data for the source of the collision
*/
HitboxState sourceHitboxData;
/**
* The hitbox data for the parent of the tree
*/
HitboxState parentHitboxData;
/**
* True if this is a damage event
*/
boolean isDamage;
/**
* True if this is a block event
*/
boolean isBlock;
/**
* Constructor
* @param source The source of the collision
* @param sourceHitboxData The hitbox data for the source of the collision
* @param parentHitboxData The hitbox data for the parent of the tree
* @param isDamage True if this is a damage event
* @param isBlock True if this is a block event
*/
public CollisionEvent(Entity source, HitboxState sourceHitboxData, HitboxState parentHitboxData, boolean isDamage, boolean isBlock){
this.source = source;
this.sourceHitboxData = sourceHitboxData;
this.parentHitboxData = parentHitboxData;
this.isDamage = isDamage;
this.isBlock = isBlock;
}
}
} }

View File

@ -6,19 +6,14 @@ import org.ode4j.ode.DGeom;
import electrosphere.collision.CollisionEngine.CollisionResolutionCallback; import electrosphere.collision.CollisionEngine.CollisionResolutionCallback;
import electrosphere.collision.collidable.Collidable; import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity; import electrosphere.entity.Entity;
import electrosphere.entity.state.attack.ServerAttackTree;
import electrosphere.entity.state.hitbox.HitboxCollectionState; import electrosphere.entity.state.hitbox.HitboxCollectionState;
import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxState; import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxState;
import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxType; import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxType;
import electrosphere.entity.state.life.ServerLifeTree; import electrosphere.entity.state.life.ServerLifeTree;
import electrosphere.entity.state.movement.ProjectileTree;
import electrosphere.entity.types.attach.AttachUtils; import electrosphere.entity.types.attach.AttachUtils;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.entity.types.item.ItemUtils; 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;
/** /**
* Callback for managing collisions on the server * Callback for managing collisions on the server
@ -43,22 +38,49 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba
boolean receiverIsHurt = receiverShapeStatus != null && receiverShapeStatus.getType() == HitboxType.HURT; boolean receiverIsHurt = receiverShapeStatus != null && receiverShapeStatus.getType() == HitboxType.HURT;
boolean receiverIsBlock = receiverShapeStatus != null && (receiverShapeStatus.getType() == HitboxType.BLOCK || (receiverShapeStatus.getType() == HitboxType.HIT && receiverShapeStatus.isBlockOverride())); boolean receiverIsBlock = receiverShapeStatus != null && (receiverShapeStatus.getType() == HitboxType.BLOCK || (receiverShapeStatus.getType() == HitboxType.HIT && receiverShapeStatus.isBlockOverride()));
boolean parentsAreDifferent = AttachUtils.getParent(impactorParent) != receiverParent; boolean parentsAreDifferent = AttachUtils.getParent(impactorParent) != receiverParent;
boolean impactorCollisionBlocked = false;
//currently, impactor needs to be an item, and the receiver must not be an item //check if the impactor thinks it can collide with the receiver
if(impactorParent != null && ServerAttackTree.getServerAttackTree(impactorParent) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(impactorParent);
//if we collide with the creature directly
if(!impactorAttackTree.canCollideEntity(receiverParent)){
impactorCollisionBlocked = true;
}
//if we collide with an item attached to the creature
if(AttachUtils.hasParent(receiverParent) && !impactorAttackTree.canCollideEntity(AttachUtils.getParent(receiverParent))){
impactorCollisionBlocked = true;
}
} else if(impactorParent != null && AttachUtils.hasParent(impactorParent) && AttachUtils.getParent(impactorParent) != null && ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent)) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent));
//if we collide with the creature directly
if(!impactorAttackTree.canCollideEntity(receiverParent)){
impactorCollisionBlocked = true;
}
//if we collide with an item attached to the creature
if(AttachUtils.hasParent(receiverParent) && !impactorAttackTree.canCollideEntity(AttachUtils.getParent(receiverParent))){
impactorCollisionBlocked = true;
}
}
//check if is damage event
boolean isDamageEvent = boolean isDamageEvent =
!impactorShapeStatusIsNull && !impactorShapeStatusIsNull &&
!receiverShapeStatusIsNull && !receiverShapeStatusIsNull &&
impactorIsHit && impactorIsHit &&
receiverIsHurt && receiverIsHurt &&
parentsAreDifferent parentsAreDifferent &&
!impactorCollisionBlocked
; ;
//check if is block event
boolean isBlockEvent = boolean isBlockEvent =
!impactorShapeStatusIsNull && !impactorShapeStatusIsNull &&
!receiverShapeStatusIsNull && !receiverShapeStatusIsNull &&
impactorIsHit && impactorIsHit &&
receiverIsBlock && receiverIsBlock &&
parentsAreDifferent parentsAreDifferent &&
!impactorCollisionBlocked
; ;
@ -75,62 +97,68 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba
boolean isItem = ItemUtils.isItem(impactorParent);//hitboxParent.containsKey(EntityDataStrings.ITEM_IS_ITEM); boolean isItem = ItemUtils.isItem(impactorParent);//hitboxParent.containsKey(EntityDataStrings.ITEM_IS_ITEM);
Entity hitboxAttachParent = AttachUtils.getParent(impactorParent); Entity hitboxAttachParent = AttachUtils.getParent(impactorParent);
//tell clients an impact just happened //
DataCellSearchUtils.getEntityDataCell(receiverParent).broadcastNetworkMessage( //handle receiver
CombatMessage.constructserverReportHitboxCollisionMessage(
impactorParent.getId(),
receiverParent.getId(),
Globals.timekeeper.getNumberOfSimFramesElapsed(),
impactorShapeStatus.getHitboxData().getType(),
receiverShapeStatus.getHitboxData().getType()
)
);
if(isItem){ if(isItem){
if(hitboxAttachParent != receiverParent){ if(hitboxAttachParent != receiverParent){
int damage = ItemUtils.getWeaponDataRaw(impactorParent).getDamage();
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverParent); ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverParent);
serverLifeTree.damage(damage); serverLifeTree.addCollisionEvent(impactorParent, impactorShapeStatus, receiverShapeStatus, localPosition, isDamageEvent, isBlockEvent);
if(!serverLifeTree.isAlive()){
throw new UnsupportedOperationException("Reviving not implemented yet!");
// Realm entityRealm = Globals.realmManager.getEntityRealm(receiverParent);
// EntityUtils.getPosition(receiverParent).set(entityRealm.getSpawnPoint());
// serverLifeTree.revive();
}
}
} else {
int damage = 0;
//for entities using attacktree
if(CreatureUtils.serverGetAttackTree(impactorParent) != null){
damage = ItemUtils.getWeaponDataRaw(impactorParent).getDamage();
} else {
//for entities using shooter tree
if(ProjectileTree.getProjectileTree(impactorParent) != null){
damage = (int)ProjectileTree.getProjectileTree(impactorParent).getDamage();
}
}
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverParent);
serverLifeTree.damage(damage);
if(!serverLifeTree.isAlive()){
throw new UnsupportedOperationException("Reviving not implemented yet!");
// Realm entityRealm = Globals.realmManager.getEntityRealm(receiverParent);
// EntityUtils.getPosition(receiverParent).set(entityRealm.getSpawnPoint());
// serverLifeTree.revive();
} }
} }
//
//handle attacker
this.handleAttackerCollision(impactorParent,receiverParent);
} }
if(isBlockEvent){ if(isBlockEvent){
//tell clients an impact just happened //
DataCellSearchUtils.getEntityDataCell(receiverParent).broadcastNetworkMessage( //handle receiver
CombatMessage.constructserverReportHitboxCollisionMessage( boolean receiverIsItem = ItemUtils.isItem(receiverParent);
impactorParent.getId(), boolean receiverHasParent = AttachUtils.hasParent(receiverParent);
receiverParent.getId(), if(receiverIsItem && receiverHasParent){
Globals.timekeeper.getNumberOfSimFramesElapsed(), //item is equipped to something
impactorShapeStatus.getHitboxData().getType(), ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(AttachUtils.getParent(receiverParent));
HitboxData.HITBOX_TYPE_BLOCK_CONNECTED //TODO: more proper block override handling if(serverLifeTree != null){
) serverLifeTree.addCollisionEvent(impactorParent, impactorShapeStatus, receiverShapeStatus, localPosition, isDamageEvent, isBlockEvent);
); }
} else {
//attacking an item that is not equipped to anything
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverParent);
if(serverLifeTree != null){
serverLifeTree.addCollisionEvent(impactorParent, impactorShapeStatus, receiverShapeStatus, localPosition, isDamageEvent, isBlockEvent);
}
}
//
//handle attacker
this.handleAttackerCollision(impactorParent,receiverParent);
}
}
/**
* Handles collision tracking from the impactor's side
* @param impactorParent The impactor hitbox's parent entity
* @param receiverParent The receiver hitbox's parent entity
*/
private void handleAttackerCollision(Entity impactorParent, Entity receiverParent){
boolean receiverIsItem = ItemUtils.isItem(receiverParent);
boolean receiverHasParent = AttachUtils.hasParent(receiverParent);
if(impactorParent != null && ServerAttackTree.getServerAttackTree(impactorParent) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(impactorParent);
impactorAttackTree.collideEntity(receiverParent);
//if the receiver is an item that is equipped, collide with parent too
if(receiverIsItem && receiverHasParent){
impactorAttackTree.collideEntity(AttachUtils.getParent(receiverParent));
}
} else if(impactorParent != null && AttachUtils.hasParent(impactorParent) && AttachUtils.getParent(impactorParent) != null && ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent)) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent));
impactorAttackTree.collideEntity(receiverParent);
//if the receiver is an item that is equipped, collide with parent too
if(receiverIsItem && receiverHasParent){
impactorAttackTree.collideEntity(AttachUtils.getParent(receiverParent));
}
} }
} }