diff --git a/src/main/java/com/atsuishio/superbwarfare/capability/CapabilityHandler.java b/src/main/java/com/atsuishio/superbwarfare/capability/CapabilityHandler.java new file mode 100644 index 000000000..659d4c9c8 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/capability/CapabilityHandler.java @@ -0,0 +1,19 @@ +package com.atsuishio.superbwarfare.capability; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; + +@Mod.EventBusSubscriber() +public class CapabilityHandler { + + @SubscribeEvent + public static void registerCapabilities(AttachCapabilitiesEvent event) { + if (event.getObject() instanceof Player) { + event.addCapability(LaserCapability.ID, new LaserCapability.LaserCapabilityProvider()); + } + } + +} diff --git a/src/main/java/com/atsuishio/superbwarfare/capability/LaserCapability.java b/src/main/java/com/atsuishio/superbwarfare/capability/LaserCapability.java new file mode 100644 index 000000000..060457b4b --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/capability/LaserCapability.java @@ -0,0 +1,92 @@ +package com.atsuishio.superbwarfare.capability; + +import com.atsuishio.superbwarfare.ModUtils; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.capabilities.ICapabilitySerializable; +import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.common.util.LazyOptional; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class LaserCapability { + + public static ResourceLocation ID = ModUtils.loc("laser_capability"); + + public interface ILaserCapability extends INBTSerializable { + + void init(LaserHandler handler); + + void start(); + + void tick(); + + void stop(); + + } + + public static class LaserCapabilityImpl implements ILaserCapability { + + public LaserHandler laserHandler; + + @Override + public void init(LaserHandler handler) { + this.laserHandler = handler; + } + + @Override + public void start() { + this.laserHandler.start(); + } + + @Override + public void tick() { + } + + @Override + public void stop() { + if (this.laserHandler != null) { + this.laserHandler.stop(); + } + } + + @Override + public CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + if (this.laserHandler != null) { + tag.put("Laser", this.laserHandler.writeNBT()); + } + return tag; + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + if (nbt.contains("Laser") && this.laserHandler != null) { + this.laserHandler.readNBT(nbt.getCompound("Laser")); + } + } + } + + public static class LaserCapabilityProvider implements ICapabilityProvider, ICapabilitySerializable { + + private final LazyOptional instance = LazyOptional.of(LaserCapabilityImpl::new); + + @Override + public @NotNull LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + return ModCapabilities.LASER_CAPABILITY.orEmpty(cap, instance.cast()); + } + + @Override + public CompoundTag serializeNBT() { + return instance.orElseThrow(NullPointerException::new).serializeNBT(); + } + + @Override + public void deserializeNBT(CompoundTag nbt) { + instance.orElseThrow(NullPointerException::new).deserializeNBT(nbt); + } + } +} diff --git a/src/main/java/com/atsuishio/superbwarfare/capability/LaserHandler.java b/src/main/java/com/atsuishio/superbwarfare/capability/LaserHandler.java new file mode 100644 index 000000000..cc7ad83b4 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/capability/LaserHandler.java @@ -0,0 +1,78 @@ +package com.atsuishio.superbwarfare.capability; + +import com.atsuishio.superbwarfare.entity.projectile.AbstractLaserEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.LivingEntity; + +public class LaserHandler { + + public final int coolingTick; + public boolean isUsing; + public final LivingEntity entity; + public final AbstractLaserEntity laserEntity; + private int tick; + + public LaserHandler(LivingEntity entity, AbstractLaserEntity laserEntity, int coolingTick) { + this.coolingTick = coolingTick; + this.entity = entity; + this.laserEntity = laserEntity; + } + + public void start() { + this.tick = 0; + this.isUsing = true; + + if (this.entity.level() instanceof ServerLevel level) { + level.addFreshEntity(this.laserEntity); + } + this.entity.swing(InteractionHand.MAIN_HAND, true); + } + + public void tick() { + if (this.isUsing) { + this.tick++; + + if (this.tick > laserEntity.getDuration()) { + this.stop(); + } + } + } + + public void stop() { + if (!this.isUsing) return; + + this.isUsing = false; + this.tick = 0; + if (this.laserEntity != null) { + this.laserEntity.discard(); + } + } + + public int getTick() { + return this.tick; + } + + public CompoundTag writeNBT() { + CompoundTag compoundTag = new CompoundTag(); + if (this.isUsing) { + compoundTag.putInt("Tick", this.tick); + } + return compoundTag; + } + + public void readNBT(Tag nbt) { + CompoundTag compoundTag = (CompoundTag) nbt; + this.isUsing = compoundTag.contains("Tick"); + if (this.isUsing) { + this.tick = compoundTag.getInt("Tick"); + } + } + + public boolean isUsable() { + return !this.isUsing; + } + +} diff --git a/src/main/java/com/atsuishio/superbwarfare/capability/ModCapabilities.java b/src/main/java/com/atsuishio/superbwarfare/capability/ModCapabilities.java new file mode 100644 index 000000000..16bbd35a3 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/capability/ModCapabilities.java @@ -0,0 +1,12 @@ +package com.atsuishio.superbwarfare.capability; + +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.capabilities.CapabilityToken; + +public class ModCapabilities { + + public static final Capability LASER_CAPABILITY = CapabilityManager.get(new CapabilityToken<>() { + }); + +} diff --git a/src/main/java/com/atsuishio/superbwarfare/client/AnimationTicker.java b/src/main/java/com/atsuishio/superbwarfare/client/AnimationTicker.java new file mode 100644 index 000000000..54c8311a8 --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/client/AnimationTicker.java @@ -0,0 +1,82 @@ +package com.atsuishio.superbwarfare.client; + +/** + * Code based on @EEEAB's EEEABsMobs and @Mercurows's DreamaticVoyage + */ +public class AnimationTicker { + + private int tick; + private int prevTick; + private int duration; + + public AnimationTicker(int duration) { + this.tick = 0; + this.prevTick = 0; + this.duration = duration; + } + + public void setDuration(int duration) { + this.tick = 0; + this.prevTick = 0; + this.duration = duration; + } + + public int getTick() { + return this.tick; + } + + public boolean isStopped() { + return this.tick == 0 && this.prevTick == 0; + } + + public boolean isEnded() { + return this.tick == this.duration || this.prevTick == this.duration; + } + + public int getPrevTick() { + return this.prevTick; + } + + public int getDuration() { + return this.duration; + } + + public void changeTimer(boolean flag) { + changeTimer(flag, 1); + } + + public void changeTimer(boolean add, int time) { + if (add) { + increaseTimer(time); + } else { + decreaseTimer(time); + } + } + + public void increaseTimer(int time) { + int newTime = this.tick + time; + if (newTime <= duration && newTime >= 0) { + this.tick = newTime; + } else { + this.tick = newTime < 0 ? 0 : duration; + } + } + + public void decreaseTimer(int time) { + if (this.tick - time > 0.0D) { + this.tick -= time; + } else { + this.tick = 0; + } + } + + public void updatePrevTimer() { + this.prevTick = this.tick; + } + + public void resetTimer() { + this.tick = 0; + this.prevTick = 0; + } + +} diff --git a/src/main/java/com/atsuishio/superbwarfare/entity/projectile/AbstractLaserEntity.java b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/AbstractLaserEntity.java new file mode 100644 index 000000000..053e2ae9f --- /dev/null +++ b/src/main/java/com/atsuishio/superbwarfare/entity/projectile/AbstractLaserEntity.java @@ -0,0 +1,274 @@ +package com.atsuishio.superbwarfare.entity.projectile; + +import com.atsuishio.superbwarfare.client.AnimationTicker; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.TraceableEntity; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.*; +import net.minecraftforge.network.NetworkHooks; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Code based on @BobMowzie's MowziesMobs, @EEEAB's EEEABsMobs and @Mercurows's DreamaticVoyage + */ +public abstract class AbstractLaserEntity extends Entity implements TraceableEntity { + + public LivingEntity caster; + public float yaw, pitch; + public float preYaw, prePitch; + public double endPosX, endPosY, endPosZ; + public double collidePosX, collidePosY, collidePosZ; + public double prevCollidePosX, prevCollidePosY, prevCollidePosZ; + public Direction blockSide = null; + public boolean on = true; + public AnimationTicker ticker = new AnimationTicker(3); + + private static final EntityDataAccessor DATA_CASTER_ID = SynchedEntityData.defineId(AbstractLaserEntity.class, EntityDataSerializers.INT); + private static final EntityDataAccessor DATA_YAW = SynchedEntityData.defineId(AbstractLaserEntity.class, EntityDataSerializers.FLOAT); + private static final EntityDataAccessor DATA_PITCH = SynchedEntityData.defineId(AbstractLaserEntity.class, EntityDataSerializers.FLOAT); + private static final EntityDataAccessor DATA_DURATION = SynchedEntityData.defineId(AbstractLaserEntity.class, EntityDataSerializers.INT); + private static final EntityDataAccessor DATA_COUNT_DOWN = SynchedEntityData.defineId(AbstractLaserEntity.class, EntityDataSerializers.INT); + + public AbstractLaserEntity(EntityType type, Level level, int countDown) { + super(type, level); + this.setCountDown(countDown); + noCulling = true; + } + + @Override + public void tick() { + super.tick(); + this.prevCollidePosX = this.collidePosX; + this.prevCollidePosY = this.collidePosY; + this.prevCollidePosZ = this.collidePosZ; + this.preYaw = this.yaw; + this.prePitch = this.pitch; + this.yaw = this.getYaw(); + this.pitch = this.getPitch(); + this.xo = this.getX(); + this.yo = this.getY(); + this.zo = this.getZ(); + if (this.tickCount == 1 && this.level().isClientSide) { + this.caster = (LivingEntity) this.level().getEntity(getCasterId()); + } + + this.beamTick(); + + if ((!this.on && this.ticker.isStopped()) || (this.caster != null && !caster.isAlive())) { + this.discard(); + } + this.ticker.changeTimer(this.on && this.isAccumulating()); + + if (this.tickCount - this.getCountDown() > this.getDuration()) { + this.on = false; + } + } + + @Override + public Packet getAddEntityPacket() { + return NetworkHooks.getEntitySpawningPacket(this); + } + + @Override + protected void readAdditionalSaveData(CompoundTag pCompound) { + + } + + @Override + protected void addAdditionalSaveData(CompoundTag pCompound) { + + } + + protected void beamTick() { + } + + @Nullable + public LivingEntity getOwner() { + return caster; + } + + @Override + public boolean isPickable() { + return super.isPickable(); + } + + @Override + public void push(Entity entityIn) { + } + + @Override + public PushReaction getPistonPushReaction() { + return PushReaction.IGNORE; + } + + @Override + protected void defineSynchedData() { + this.entityData.define(DATA_CASTER_ID, -1); + this.entityData.define(DATA_YAW, 0F); + this.entityData.define(DATA_PITCH, 0F); + this.entityData.define(DATA_DURATION, 0); + this.entityData.define(DATA_COUNT_DOWN, 0); + } + + public void setCasterId(int id) { + this.entityData.set(DATA_CASTER_ID, id); + } + + public int getCasterId() { + return this.entityData.get(DATA_CASTER_ID); + } + + public boolean isAccumulating() { + return this.tickCount > this.getCountDown(); + } + + public float getYaw() { + return getEntityData().get(DATA_YAW); + } + + public void setYaw(float rotAngle) { + getEntityData().set(DATA_YAW, rotAngle); + } + + public float getPitch() { + return getEntityData().get(DATA_PITCH); + } + + public void setPitch(float rotAngle) { + getEntityData().set(DATA_PITCH, rotAngle); + } + + public int getDuration() { + return getEntityData().get(DATA_DURATION); + } + + public void setDuration(int duration) { + getEntityData().set(DATA_DURATION, duration); + } + + public int getCountDown() { + return getEntityData().get(DATA_COUNT_DOWN); + } + + public void setCountDown(int countDown) { + getEntityData().set(DATA_COUNT_DOWN, countDown); + } + + protected void calculateEndPos(double radius) { + if (level().isClientSide()) { + endPosX = getX() + radius * Math.cos(yaw) * Math.cos(pitch); + endPosZ = getZ() + radius * Math.sin(yaw) * Math.cos(pitch); + endPosY = getY() + radius * Math.sin(pitch); + } else { + endPosX = getX() + radius * Math.cos(getYaw()) * Math.cos(getPitch()); + endPosZ = getZ() + radius * Math.sin(getYaw()) * Math.cos(getPitch()); + endPosY = getY() + radius * Math.sin(getPitch()); + } + } + + public CustomHitResult raytraceEntities(Level world, Vec3 from, Vec3 to) { + CustomHitResult result = new CustomHitResult(); + result.setBlockHit(world.clip(new ClipContext(from, to, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this))); + if (result.getBlockHit() != null) { + Vec3 hitVec = result.getBlockHit().getLocation(); + collidePosX = hitVec.x; + collidePosY = hitVec.y; + collidePosZ = hitVec.z; + blockSide = result.getBlockHit().getDirection(); + } else { + collidePosX = endPosX; + collidePosY = endPosY; + collidePosZ = endPosZ; + blockSide = null; + } + List entities = world.getEntitiesOfClass(LivingEntity.class, new AABB(Math.min(getX(), collidePosX), Math.min(getY(), collidePosY), Math.min(getZ(), collidePosZ), Math.max(getX(), collidePosX), Math.max(getY(), collidePosY), Math.max(getZ(), collidePosZ)).inflate(1, 1, 1)); + for (LivingEntity entity : entities) { + if (entity == this.caster) { + continue; + } + float pad = entity.getPickRadius() + getBaseScale(); + AABB aabb = entity.getBoundingBox().inflate(pad, pad, pad); + Optional hit = aabb.clip(from, to); + if (aabb.contains(from)) { + result.addEntityHit(entity); + } else if (hit.isPresent()) { + result.addEntityHit(entity); + } + } + return result; + } + + @Override + public boolean isAttackable() { + return false; + } + + @Override + public boolean displayFireAnimation() { + return false; + } + + protected void onHit(HitResult hitResult) { + HitResult.Type hitresult$type = hitResult.getType(); + if (hitresult$type == HitResult.Type.ENTITY) { + this.onHitEntity((EntityHitResult) hitResult); + this.level().gameEvent(GameEvent.PROJECTILE_LAND, hitResult.getLocation(), GameEvent.Context.of(this, null)); + } else if (hitresult$type == HitResult.Type.BLOCK) { + BlockHitResult blockhitresult = (BlockHitResult) hitResult; + this.onHitBlock(blockhitresult); + BlockPos blockpos = blockhitresult.getBlockPos(); + this.level().gameEvent(GameEvent.PROJECTILE_LAND, blockpos, GameEvent.Context.of(this, this.level().getBlockState(blockpos))); + } + } + + protected void onHitEntity(EntityHitResult result) { + } + + protected void onHitBlock(BlockHitResult result) { + } + + protected float getBaseScale() { + return 0.5F; + } + + public static class CustomHitResult { + + private BlockHitResult blockHit; + private final List entities = new ArrayList<>(); + + public BlockHitResult getBlockHit() { + return blockHit; + } + + public List getEntities() { + return entities; + } + + public void setBlockHit(HitResult rayTraceResult) { + if (rayTraceResult.getType() == HitResult.Type.BLOCK) + this.blockHit = (BlockHitResult) rayTraceResult; + } + + public void addEntityHit(LivingEntity entity) { + entities.add(entity); + } + } + +}