添加ProjectileEntity
This commit is contained in:
parent
0e1a31f3a4
commit
03276fb3d8
8 changed files with 1209 additions and 29 deletions
|
@ -0,0 +1,19 @@
|
|||
package com.atsuishio.superbwarfare.entity;
|
||||
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
|
||||
/**
|
||||
* Codes Based On @TACZ
|
||||
*/
|
||||
public interface ICustomKnockback {
|
||||
|
||||
static ICustomKnockback getInstance(LivingEntity entity) {
|
||||
return (ICustomKnockback) entity;
|
||||
}
|
||||
|
||||
void superbWarfare$setKnockbackStrength(double strength);
|
||||
|
||||
void superbWarfare$resetKnockbackStrength();
|
||||
|
||||
double superbWarfare$getKnockbackStrength();
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package com.atsuishio.superbwarfare.entity.projectile;
|
||||
|
||||
public interface CustomSyncMotionEntity {
|
||||
void syncMotion();
|
||||
}
|
|
@ -0,0 +1,910 @@
|
|||
package com.atsuishio.superbwarfare.entity.projectile;
|
||||
|
||||
import com.atsuishio.superbwarfare.block.BarbedWireBlock;
|
||||
import com.atsuishio.superbwarfare.config.server.MiscConfig;
|
||||
import com.atsuishio.superbwarfare.config.server.ProjectileConfig;
|
||||
import com.atsuishio.superbwarfare.entity.ICustomKnockback;
|
||||
import com.atsuishio.superbwarfare.init.*;
|
||||
import com.atsuishio.superbwarfare.network.message.ClientMotionSyncMessage;
|
||||
import com.atsuishio.superbwarfare.tools.*;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.tags.EntityTypeTags;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.projectile.Projectile;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.*;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.gameevent.GameEvent;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.phys.*;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import net.neoforged.neoforge.common.Tags;
|
||||
import net.neoforged.neoforge.entity.IEntityWithComplexSpawn;
|
||||
import net.neoforged.neoforge.entity.PartEntity;
|
||||
import net.neoforged.neoforge.event.EventHooks;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import software.bernie.geckolib.animatable.GeoEntity;
|
||||
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
|
||||
import software.bernie.geckolib.animation.AnimatableManager;
|
||||
import software.bernie.geckolib.util.GeckoLibUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@SuppressWarnings({"unused", "UnusedReturnValue", "SuspiciousNameCombination"})
|
||||
public class ProjectileEntity extends Projectile implements IEntityWithComplexSpawn, GeoEntity, CustomSyncMotionEntity {
|
||||
|
||||
public static final EntityDataAccessor<Float> COLOR_R = SynchedEntityData.defineId(ProjectileEntity.class, EntityDataSerializers.FLOAT);
|
||||
public static final EntityDataAccessor<Float> COLOR_G = SynchedEntityData.defineId(ProjectileEntity.class, EntityDataSerializers.FLOAT);
|
||||
public static final EntityDataAccessor<Float> COLOR_B = SynchedEntityData.defineId(ProjectileEntity.class, EntityDataSerializers.FLOAT);
|
||||
private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
|
||||
|
||||
private static final Predicate<Entity> PROJECTILE_TARGETS = input -> input != null && input.isPickable() && !input.isSpectator() && input.isAlive();
|
||||
private static final Predicate<BlockState> IGNORE_LIST = input -> input != null && (input.getBlock() instanceof LeavesBlock
|
||||
|| input.getBlock() instanceof FenceBlock
|
||||
|| input.is(Blocks.IRON_BARS)
|
||||
|| input.getBlock() instanceof DoorBlock
|
||||
|| input.getBlock() instanceof TrapDoorBlock
|
||||
|| input.getBlock() instanceof BarbedWireBlock);
|
||||
|
||||
@Nullable
|
||||
protected LivingEntity shooter;
|
||||
protected int shooterId;
|
||||
private float damage = 1f;
|
||||
private float headShot = 1f;
|
||||
private float monsterMultiple = 0.0f;
|
||||
private float legShot = 0.5f;
|
||||
private boolean beast = false;
|
||||
private boolean zoom = false;
|
||||
private float bypassArmorRate = 0.0f;
|
||||
private float undeadMultiple = 1.0f;
|
||||
private int jhpLevel = 0;
|
||||
private int heLevel = 0;
|
||||
private int fireLevel = 0;
|
||||
private boolean dragonBreath = false;
|
||||
private float knockback = 0.05f;
|
||||
private boolean forceKnockback = false;
|
||||
private final ArrayList<MobEffectInstance> mobEffects = new ArrayList<>();
|
||||
|
||||
public ProjectileEntity(EntityType<? extends ProjectileEntity> entityType, Level level) {
|
||||
super(entityType, level);
|
||||
this.noCulling = true;
|
||||
}
|
||||
|
||||
public ProjectileEntity(Level level) {
|
||||
super(ModEntities.PROJECTILE.get(), level);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected EntityResult findEntityOnPath(Vec3 startVec, Vec3 endVec) {
|
||||
if (this.shooter == null) return null;
|
||||
|
||||
Vec3 hitVec = null;
|
||||
Entity hitEntity = null;
|
||||
boolean headshot = false;
|
||||
boolean legShot = false;
|
||||
List<Entity> entities = this.level()
|
||||
.getEntities(this,
|
||||
this.getBoundingBox()
|
||||
.expandTowards(this.getDeltaMovement())
|
||||
.inflate(this.beast ? 3 : 1),
|
||||
PROJECTILE_TARGETS
|
||||
);
|
||||
double closestDistance = Double.MAX_VALUE;
|
||||
|
||||
for (Entity entity : entities) {
|
||||
if (entity.equals(this.shooter)) continue;
|
||||
if (entity.equals(this.shooter.getVehicle())) continue;
|
||||
|
||||
// TODO target entity
|
||||
// if (entity instanceof TargetEntity && entity.getEntityData().get(TargetEntity.DOWN_TIME) > 0) continue;
|
||||
|
||||
EntityResult result = this.getHitResult(entity, startVec, endVec);
|
||||
if (result == null) continue;
|
||||
|
||||
Vec3 hitPos = result.getHitPos();
|
||||
if (hitPos == null) continue;
|
||||
|
||||
double distanceToHit = startVec.distanceTo(hitPos);
|
||||
if (distanceToHit < closestDistance) {
|
||||
hitVec = hitPos;
|
||||
hitEntity = entity;
|
||||
closestDistance = distanceToHit;
|
||||
headshot = result.isHeadshot();
|
||||
legShot = result.isLegShot();
|
||||
}
|
||||
}
|
||||
return hitEntity != null ? new EntityResult(hitEntity, hitVec, headshot, legShot) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected List<EntityResult> findEntitiesOnPath(Vec3 startVec, Vec3 endVec) {
|
||||
List<EntityResult> hitEntities = new ArrayList<>();
|
||||
List<Entity> entities = this.level().getEntities(
|
||||
this,
|
||||
this.getBoundingBox()
|
||||
.expandTowards(this.getDeltaMovement())
|
||||
.inflate(this.beast ? 3 : 1),
|
||||
PROJECTILE_TARGETS
|
||||
);
|
||||
for (Entity entity : entities) {
|
||||
if (!entity.equals(this.shooter)) {
|
||||
EntityResult result = this.getHitResult(entity, startVec, endVec);
|
||||
if (result == null) continue;
|
||||
hitEntities.add(result);
|
||||
}
|
||||
}
|
||||
return hitEntities;
|
||||
}
|
||||
|
||||
/**
|
||||
* From TaC-Z
|
||||
*/
|
||||
@Nullable
|
||||
private EntityResult getHitResult(Entity entity, Vec3 startVec, Vec3 endVec) {
|
||||
double expandHeight = entity instanceof Player && !entity.isCrouching() ? 0.0625 : 0.0;
|
||||
AABB boundingBox = entity.getBoundingBox();
|
||||
Vec3 velocity = new Vec3(entity.getX() - entity.xOld, entity.getY() - entity.yOld, entity.getZ() - entity.zOld);
|
||||
|
||||
if (entity instanceof ServerPlayer player && this.shooter instanceof ServerPlayer serverPlayerOwner) {
|
||||
int ping = Mth.floor((serverPlayerOwner.connection.latency() / 1000.0) * 20.0 + 0.5);
|
||||
boundingBox = HitboxHelper.getBoundingBox(player, ping);
|
||||
velocity = HitboxHelper.getVelocity(player, ping);
|
||||
}
|
||||
boundingBox = boundingBox.expandTowards(0, expandHeight, 0);
|
||||
|
||||
boundingBox = boundingBox.expandTowards(velocity.x, velocity.y, velocity.z);
|
||||
|
||||
double playerHitboxOffset = 3;
|
||||
if (entity instanceof ServerPlayer) {
|
||||
if (entity.getVehicle() != null) {
|
||||
boundingBox = boundingBox.move(velocity.multiply(playerHitboxOffset / 2, playerHitboxOffset / 2, playerHitboxOffset / 2));
|
||||
}
|
||||
boundingBox = boundingBox.move(velocity.multiply(playerHitboxOffset, playerHitboxOffset, playerHitboxOffset));
|
||||
}
|
||||
|
||||
if (entity.getVehicle() != null) {
|
||||
boundingBox = boundingBox.move(velocity.multiply(-2.5, -2.5, -2.5));
|
||||
}
|
||||
boundingBox = boundingBox.move(velocity.multiply(-5, -5, -5));
|
||||
|
||||
if (this.beast) {
|
||||
boundingBox = boundingBox.inflate(3);
|
||||
}
|
||||
|
||||
Vec3 hitPos = boundingBox.clip(startVec, endVec).orElse(null);
|
||||
|
||||
if (hitPos == null) {
|
||||
return null;
|
||||
}
|
||||
Vec3 hitBoxPos = hitPos.subtract(entity.position());
|
||||
boolean headshot = false;
|
||||
boolean legShot = false;
|
||||
float eyeHeight = entity.getEyeHeight();
|
||||
float bodyHeight = entity.getBbHeight();
|
||||
if ((eyeHeight - 0.35) < hitBoxPos.y && hitBoxPos.y < (eyeHeight + 0.4) && entity instanceof LivingEntity) {
|
||||
headshot = true;
|
||||
}
|
||||
if (hitBoxPos.y < (0.33 * bodyHeight) && entity instanceof LivingEntity) {
|
||||
legShot = true;
|
||||
}
|
||||
|
||||
return new EntityResult(entity, hitPos, headshot, legShot);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void defineSynchedData(SynchedEntityData.Builder builder) {
|
||||
builder.define(COLOR_R, 1.0f)
|
||||
.define(COLOR_G, 222 / 255f)
|
||||
.define(COLOR_B, 39 / 255f);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
this.updateHeading();
|
||||
|
||||
Vec3 vec = this.getDeltaMovement();
|
||||
|
||||
if (!this.level().isClientSide() && this.shooter != null) {
|
||||
Vec3 startVec = this.position();
|
||||
Vec3 endVec = startVec.add(this.getDeltaMovement());
|
||||
HitResult result = rayTraceBlocks(this.level(), new ClipContext(startVec, endVec, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, this),
|
||||
ProjectileConfig.ALLOW_PROJECTILE_DESTROY_GLASS.get() ? IGNORE_LIST : IGNORE_LIST.or(input -> input.is(Tags.Blocks.GLASS_PANES)));
|
||||
if (result.getType() != HitResult.Type.MISS) {
|
||||
endVec = result.getLocation();
|
||||
}
|
||||
|
||||
List<EntityResult> entityResults = new ArrayList<>();
|
||||
var temp = findEntitiesOnPath(startVec, endVec);
|
||||
if (temp != null) entityResults.addAll(temp);
|
||||
entityResults.sort(Comparator.comparingDouble(e -> e.getHitPos().distanceTo(this.shooter.position())));
|
||||
|
||||
for (EntityResult entityResult : entityResults) {
|
||||
result = new ExtendedEntityRayTraceResult(entityResult);
|
||||
if (((EntityHitResult) result).getEntity() instanceof Player player) {
|
||||
if (this.shooter instanceof Player p && !p.canHarmPlayer(player)) {
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
if (result != null) {
|
||||
this.onHit(result);
|
||||
}
|
||||
|
||||
if (!this.beast) {
|
||||
this.bypassArmorRate -= 0.2F;
|
||||
if (this.bypassArmorRate < 0.8F) {
|
||||
// TODO target
|
||||
// if (result != null && !(((EntityHitResult) result).getEntity() instanceof TargetEntity target && target.getEntityData().get(TargetEntity.DOWN_TIME) > 0)) {
|
||||
// break;
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (entityResults.isEmpty()) {
|
||||
this.onHit(result);
|
||||
}
|
||||
|
||||
this.setPos(this.getX() + vec.x, this.getY() + vec.y, this.getZ() + vec.z);
|
||||
} else {
|
||||
this.setPosRaw(this.getX() + vec.x, this.getY() + vec.y, this.getZ() + vec.z);
|
||||
}
|
||||
|
||||
this.setDeltaMovement(vec.x, vec.y - 0.02, vec.z);
|
||||
|
||||
if (this.tickCount > (fireLevel > 0 ? 10 : 40)) {
|
||||
this.discard();
|
||||
}
|
||||
|
||||
if (fireLevel > 0 && dragonBreath && this.level() instanceof ServerLevel serverLevel) {
|
||||
double randomPos = this.tickCount * 0.08 * (Math.random() - 0.5);
|
||||
ParticleTool.sendParticle(serverLevel, ParticleTypes.FLAME,
|
||||
(this.xo + this.getX()) / 2 + randomPos, (this.yo + this.getY()) / 2 + randomPos, (this.zo + this.getZ()) / 2 + randomPos,
|
||||
0,
|
||||
this.getDeltaMovement().x, this.getDeltaMovement().y, this.getDeltaMovement().z,
|
||||
Math.max(this.getDeltaMovement().length() - 1.1 * this.tickCount, 0.2), true
|
||||
);
|
||||
}
|
||||
|
||||
this.syncMotion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncMotion() {
|
||||
if (!this.level().isClientSide) {
|
||||
PacketDistributor.sendToAllPlayers(new ClientMotionSyncMessage(this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readAdditionalSaveData(CompoundTag tag) {
|
||||
this.damage = tag.getFloat("Damage");
|
||||
this.headShot = tag.getFloat("HeadShot");
|
||||
this.monsterMultiple = tag.getFloat("MonsterMultiple");
|
||||
this.legShot = tag.getFloat("LegShot");
|
||||
this.bypassArmorRate = tag.getFloat("BypassArmorRate");
|
||||
this.undeadMultiple = tag.getFloat("UndeadMultiple");
|
||||
this.knockback = tag.getFloat("Knockback");
|
||||
|
||||
this.beast = tag.getBoolean("Beast");
|
||||
this.forceKnockback = tag.getBoolean("ForceKnockback");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addAdditionalSaveData(CompoundTag tag) {
|
||||
tag.putFloat("Damage", this.damage);
|
||||
tag.putFloat("HeadShot", this.headShot);
|
||||
tag.putFloat("MonsterMultiple", this.monsterMultiple);
|
||||
tag.putFloat("LegShot", this.legShot);
|
||||
tag.putFloat("BypassArmorRate", this.bypassArmorRate);
|
||||
tag.putFloat("UndeadMultiple", this.undeadMultiple);
|
||||
tag.putFloat("Knockback", this.knockback);
|
||||
|
||||
tag.putBoolean("Beast", this.beast);
|
||||
tag.putBoolean("ForceKnockback", this.forceKnockback);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHit(@NotNull HitResult result) {
|
||||
if (result instanceof BlockHitResult blockHitResult) {
|
||||
if (blockHitResult.getType() == HitResult.Type.MISS) {
|
||||
return;
|
||||
}
|
||||
BlockPos resultPos = blockHitResult.getBlockPos();
|
||||
BlockState state = this.level().getBlockState(resultPos);
|
||||
SoundEvent event = state.getBlock().getSoundType(state, this.level(), resultPos, this).getBreakSound();
|
||||
this.level().playSound(null, result.getLocation().x, result.getLocation().y, result.getLocation().z, event, SoundSource.AMBIENT, 1.0F, 1.0F);
|
||||
Vec3 hitVec = result.getLocation();
|
||||
|
||||
if (state.getBlock() instanceof BellBlock bell) {
|
||||
bell.attemptToRing(this.level(), resultPos, blockHitResult.getDirection());
|
||||
}
|
||||
|
||||
if (ProjectileConfig.ALLOW_PROJECTILE_DESTROY_GLASS.get()) {
|
||||
if (state.is(Tags.Blocks.GLASS_BLOCKS) || state.is(Tags.Blocks.GLASS_PANES)) {
|
||||
this.level().destroyBlock(resultPos, false, this.getShooter());
|
||||
}
|
||||
}
|
||||
|
||||
if (state.getBlock() instanceof TargetBlock) {
|
||||
if (this.shooter == null) return;
|
||||
|
||||
int rings = getRings(blockHitResult, hitVec);
|
||||
double dis = this.shooter.position().distanceTo(hitVec);
|
||||
recordHitScore(rings, dis);
|
||||
}
|
||||
|
||||
this.onHitBlock(hitVec);
|
||||
if (heLevel > 0) {
|
||||
explosionBulletBlock(this, this.damage, heLevel, monsterMultiple + 1, hitVec);
|
||||
}
|
||||
if (fireLevel > 0 && this.level() instanceof ServerLevel serverLevel) {
|
||||
ParticleTool.sendParticle(serverLevel, ParticleTypes.LAVA, hitVec.x, hitVec.y, hitVec.z,
|
||||
3, 0, 0, 0, 0.5, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (result instanceof ExtendedEntityRayTraceResult entityHitResult) {
|
||||
Entity entity = entityHitResult.getEntity();
|
||||
if (entity.getId() == this.shooterId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shooter instanceof Player player) {
|
||||
if (entity.hasIndirectPassenger(player)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.onHitEntity(entity, entityHitResult.isHeadshot(), entityHitResult.isLegShot());
|
||||
entity.invulnerableTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getRings(@NotNull BlockHitResult blockHitResult, @NotNull Vec3 hitVec) {
|
||||
Direction direction = blockHitResult.getDirection();
|
||||
double x = Math.abs(Mth.frac(hitVec.x) - 0.5);
|
||||
double y = Math.abs(Mth.frac(hitVec.y) - 0.5);
|
||||
double z = Math.abs(Mth.frac(hitVec.z) - 0.5);
|
||||
Direction.Axis axis = direction.getAxis();
|
||||
double v;
|
||||
if (axis == Direction.Axis.Y) {
|
||||
v = Math.max(x, z);
|
||||
} else if (axis == Direction.Axis.Z) {
|
||||
v = Math.max(x, y);
|
||||
} else {
|
||||
v = Math.max(y, z);
|
||||
}
|
||||
|
||||
return Math.max(1, Mth.ceil(10.0 * Mth.clamp((0.5 - v) / 0.5, 0.0, 1.0)));
|
||||
}
|
||||
|
||||
private void recordHitScore(int score, double distance) {
|
||||
if (!(shooter instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.displayClientMessage(Component.literal(String.valueOf(score))
|
||||
.append(Component.translatable("tips.superbwarfare.shoot.rings"))
|
||||
.append(Component.literal(" " + FormatTool.format1D(distance, "m"))), false);
|
||||
|
||||
if (!this.shooter.level().isClientSide() && this.shooter instanceof ServerPlayer serverPlayer) {
|
||||
var holder = score == 10 ? Holder.direct(ModSounds.HEADSHOT.get()) : Holder.direct(ModSounds.INDICATION.get());
|
||||
serverPlayer.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong()));
|
||||
// TODO indicator message
|
||||
// ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new ClientIndicatorMessage(score == 10 ? 1 : 0, 5));
|
||||
}
|
||||
|
||||
ItemStack stack = player.getOffhandItem();
|
||||
|
||||
// TODO transcript
|
||||
// if (stack.is(ModItems.TRANSCRIPT.get())) {
|
||||
// final int size = 10;
|
||||
//
|
||||
// ListTag tags = stack.getOrCreateTag().getList(Transcript.TAG_SCORES, Tag.TAG_COMPOUND);
|
||||
//
|
||||
// Queue<CompoundTag> queue = new ArrayDeque<>();
|
||||
// for (int i = 0; i < tags.size(); i++) {
|
||||
// queue.add(tags.getCompound(i));
|
||||
// }
|
||||
//
|
||||
// CompoundTag tag = new CompoundTag();
|
||||
// tag.putInt("Score", score);
|
||||
// tag.putDouble("Distance", distance);
|
||||
// queue.offer(tag);
|
||||
//
|
||||
// while (queue.size() > size) {
|
||||
// queue.poll();
|
||||
// }
|
||||
//
|
||||
// ListTag newTags = new ListTag();
|
||||
// newTags.addAll(queue);
|
||||
//
|
||||
// stack.getOrCreateTag().put(Transcript.TAG_SCORES, newTags);
|
||||
// }
|
||||
}
|
||||
|
||||
protected void onHitBlock(Vec3 location) {
|
||||
if (this.level() instanceof ServerLevel serverLevel) {
|
||||
if (this.beast) {
|
||||
ParticleTool.sendParticle(serverLevel, ParticleTypes.END_ROD, location.x, location.y, location.z, 15, 0.1, 0.1, 0.1, 0.05, true);
|
||||
} else {
|
||||
ParticleTool.sendParticle(serverLevel, ModParticleTypes.BULLET_HOLE.get(), location.x, location.y, location.z, 1, 0, 0, 0, 0, true);
|
||||
ParticleTool.sendParticle(serverLevel, ParticleTypes.SMOKE, location.x, location.y, location.z, 3, 0, 0.1, 0, 0.01, true);
|
||||
this.discard();
|
||||
}
|
||||
serverLevel.playSound(null, new BlockPos((int) location.x, (int) location.y, (int) location.z), ModSounds.LAND.get(), SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onHitEntity(Entity entity, boolean headshot, boolean legShot) {
|
||||
if (this.shooter == null) return;
|
||||
|
||||
float mMultiple = 1 + this.monsterMultiple;
|
||||
|
||||
if (entity == null) return;
|
||||
|
||||
if (entity instanceof PartEntity<?> part) {
|
||||
entity = part.getParent();
|
||||
}
|
||||
|
||||
if (beast && entity instanceof LivingEntity living) {
|
||||
if (living.isDeadOrDying()) return;
|
||||
|
||||
// TODO target entity
|
||||
// if (living instanceof TargetEntity) return;
|
||||
|
||||
if (this.shooter instanceof ServerPlayer player) {
|
||||
// TODO indicator msg
|
||||
// ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> player), new ClientIndicatorMessage(0, 5));
|
||||
var holder = Holder.direct(ModSounds.INDICATION.get());
|
||||
player.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong()));
|
||||
((ServerLevel) this.level()).sendParticles(ParticleTypes.DAMAGE_INDICATOR, living.getX(), living.getY() + .5, living.getZ(), 1000, .4, .7, .4, 0);
|
||||
|
||||
if (MiscConfig.SEND_KILL_FEEDBACK.get()) {
|
||||
// TODO kill feedback
|
||||
// ModUtils.PACKET_HANDLER.send(PacketDistributor.ALL.noArg(), new PlayerGunKillMessage(player.getId(), living.getId(), false, ModDamageTypes.BEAST));
|
||||
}
|
||||
}
|
||||
|
||||
if (living instanceof ServerPlayer victim) {
|
||||
living.setHealth(0);
|
||||
living.level().players().forEach(
|
||||
p -> p.sendSystemMessage(
|
||||
Component.translatable("death.attack.beast_gun",
|
||||
victim.getDisplayName(),
|
||||
shooter == null ? "" : shooter.getDisplayName()
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
living.setHealth(0);
|
||||
living.level().broadcastEntityEvent(living, (byte) 60);
|
||||
living.remove(RemovalReason.KILLED);
|
||||
living.gameEvent(GameEvent.ENTITY_DIE);
|
||||
}
|
||||
|
||||
level().playSound(living, new BlockPos((int) living.getX(), (int) living.getY(), (int) living.getZ()), ModSounds.OUCH.get(), SoundSource.PLAYERS, 2.0F, 1.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity instanceof Monster) {
|
||||
this.damage *= mMultiple;
|
||||
}
|
||||
|
||||
if (entity instanceof LivingEntity living && living.getType().is(EntityTypeTags.UNDEAD)) {
|
||||
this.damage *= this.undeadMultiple;
|
||||
}
|
||||
|
||||
if (entity instanceof LivingEntity living && jhpLevel > 0) {
|
||||
this.damage *= (1.0f + 0.12f * jhpLevel) * ((float) (10 / (living.getAttributeValue(Attributes.ARMOR) + 10)) + 0.25f);
|
||||
}
|
||||
|
||||
if (heLevel > 0) {
|
||||
explosionBulletEntity(this, entity, this.damage, heLevel, mMultiple);
|
||||
}
|
||||
|
||||
if (fireLevel > 0) {
|
||||
if (!entity.level().isClientSide() && entity instanceof LivingEntity living) {
|
||||
living.addEffect(new MobEffectInstance(ModMobEffects.BURN, 60 + fireLevel * 20, fireLevel, false, false), this.shooter);
|
||||
}
|
||||
}
|
||||
|
||||
if (headshot) {
|
||||
if (!this.shooter.level().isClientSide() && this.shooter instanceof ServerPlayer player) {
|
||||
var holder = Holder.direct(ModSounds.HEADSHOT.get());
|
||||
player.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong()));
|
||||
|
||||
// TODO indicator msg
|
||||
// ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> player), new ClientIndicatorMessage(1, 5));
|
||||
}
|
||||
performOnHit(entity, this.damage, true, this.knockback);
|
||||
} else {
|
||||
if (!this.shooter.level().isClientSide() && this.shooter instanceof ServerPlayer player) {
|
||||
var holder = Holder.direct(ModSounds.INDICATION.get());
|
||||
player.connection.send(new ClientboundSoundPacket(holder, SoundSource.PLAYERS, player.getX(), player.getY(), player.getZ(), 1f, 1f, player.level().random.nextLong()));
|
||||
// TODO indicator msg
|
||||
// ModUtils.PACKET_HANDLER.send(PacketDistributor.PLAYER.with(() -> player), new ClientIndicatorMessage(0, 5));
|
||||
}
|
||||
|
||||
if (legShot) {
|
||||
if (entity instanceof LivingEntity living) {
|
||||
if (living instanceof Player player && player.isCreative()) {
|
||||
return;
|
||||
}
|
||||
if (!living.level().isClientSide()) {
|
||||
living.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 2, false, false));
|
||||
}
|
||||
}
|
||||
this.damage *= this.legShot;
|
||||
}
|
||||
|
||||
performOnHit(entity, this.damage, false, this.knockback);
|
||||
}
|
||||
|
||||
if (!this.mobEffects.isEmpty() && entity instanceof LivingEntity living) {
|
||||
for (MobEffectInstance instance : this.mobEffects) {
|
||||
living.addEffect(instance, this.shooter);
|
||||
}
|
||||
}
|
||||
|
||||
this.discard();
|
||||
}
|
||||
|
||||
public void performOnHit(Entity entity, float damage, boolean headshot, double knockback) {
|
||||
if (entity instanceof LivingEntity living) {
|
||||
if (this.forceKnockback) {
|
||||
Vec3 vec3 = this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D).normalize();
|
||||
living.addDeltaMovement(vec3.scale(knockback));
|
||||
performDamage(entity, damage, headshot);
|
||||
} else {
|
||||
ICustomKnockback iCustomKnockback = ICustomKnockback.getInstance(living);
|
||||
iCustomKnockback.superbWarfare$setKnockbackStrength(knockback);
|
||||
performDamage(entity, damage, headshot);
|
||||
iCustomKnockback.superbWarfare$resetKnockbackStrength();
|
||||
}
|
||||
} else {
|
||||
performDamage(entity, damage, headshot);
|
||||
}
|
||||
}
|
||||
|
||||
protected void explosionBulletBlock(Entity projectile, float damage, int heLevel, float monsterMultiple, Vec3 hitVec) {
|
||||
CustomExplosion explosion = new CustomExplosion(projectile.level(), projectile,
|
||||
ModDamageTypes.causeProjectileBoomDamage(projectile.level().registryAccess(), projectile, this.getShooter()), (float) ((0.9 * damage) * (1 + 0.1 * heLevel)),
|
||||
hitVec.x, hitVec.y, hitVec.z, (float) ((1.5 + 0.02 * damage) * (1 + 0.05 * heLevel))).setDamageMultiplier(monsterMultiple).bulletExplode();
|
||||
explosion.explode();
|
||||
EventHooks.onExplosionStart(projectile.level(), explosion);
|
||||
explosion.finalizeExplosion(false);
|
||||
ParticleTool.spawnMiniExplosionParticles(this.level(), hitVec);
|
||||
}
|
||||
|
||||
protected void explosionBulletEntity(Entity projectile, Entity target, float damage, int heLevel, float monsterMultiple) {
|
||||
CustomExplosion explosion = new CustomExplosion(projectile.level(), projectile,
|
||||
ModDamageTypes.causeProjectileBoomDamage(projectile.level().registryAccess(), projectile, this.getShooter()), (float) ((0.8 * damage) * (1 + 0.1 * heLevel)),
|
||||
target.getX(), target.getY(), target.getZ(), (float) ((1.5 + 0.02 * damage) * (1 + 0.05 * heLevel))).setDamageMultiplier(monsterMultiple).bulletExplode();
|
||||
explosion.explode();
|
||||
EventHooks.onExplosionStart(projectile.level(), explosion);
|
||||
explosion.finalizeExplosion(false);
|
||||
ParticleTool.spawnMiniExplosionParticles(target.level(), target.position());
|
||||
}
|
||||
|
||||
public void setDamage(float damage) {
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
public float getDamage() {
|
||||
return this.damage;
|
||||
}
|
||||
|
||||
public void shoot(Player player, double vecX, double vecY, double vecZ, float velocity, float spread) {
|
||||
Vec3 vec3 = (new Vec3(vecX, vecY, vecZ)).normalize().
|
||||
add(this.random.triangle(0.0D, 0.0172275D * (double) spread), this.random.triangle(0.0D, 0.0172275D * (double) spread), this.random.triangle(0.0D, 0.0172275D * (double) spread)).
|
||||
scale(velocity);
|
||||
this.setDeltaMovement(vec3);
|
||||
double d0 = vec3.horizontalDistance();
|
||||
this.setYRot((float) (Mth.atan2(vec3.x, vec3.z) * (double) (180F / (float) Math.PI)));
|
||||
this.setXRot((float) (Mth.atan2(vec3.y, d0) * (double) (180F / (float) Math.PI)));
|
||||
this.yRotO = this.getYRot();
|
||||
this.xRotO = this.getXRot();
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static BlockHitResult rayTraceBlocks(Level world, ClipContext context, Predicate<BlockState> ignorePredicate) {
|
||||
return performRayTrace(context, (rayTraceContext, blockPos) -> {
|
||||
BlockState blockState = world.getBlockState(blockPos);
|
||||
if (ignorePredicate.test(blockState)) return null;
|
||||
FluidState fluidState = world.getFluidState(blockPos);
|
||||
Vec3 startVec = rayTraceContext.getFrom();
|
||||
Vec3 endVec = rayTraceContext.getTo();
|
||||
VoxelShape blockShape = rayTraceContext.getBlockShape(blockState, world, blockPos);
|
||||
BlockHitResult blockResult = world.clipWithInteractionOverride(startVec, endVec, blockPos, blockShape, blockState);
|
||||
VoxelShape fluidShape = rayTraceContext.getFluidShape(fluidState, world, blockPos);
|
||||
BlockHitResult fluidResult = fluidShape.clip(startVec, endVec, blockPos);
|
||||
double blockDistance = blockResult == null ? Double.MAX_VALUE : rayTraceContext.getFrom().distanceToSqr(blockResult.getLocation());
|
||||
double fluidDistance = fluidResult == null ? Double.MAX_VALUE : rayTraceContext.getFrom().distanceToSqr(fluidResult.getLocation());
|
||||
return blockDistance <= fluidDistance ? blockResult : fluidResult;
|
||||
}, (rayTraceContext) -> {
|
||||
Vec3 Vector3d = rayTraceContext.getFrom().subtract(rayTraceContext.getTo());
|
||||
return BlockHitResult.miss(rayTraceContext.getTo(), Direction.getNearest(Vector3d.x, Vector3d.y, Vector3d.z), BlockPos.containing(rayTraceContext.getTo()));
|
||||
});
|
||||
}
|
||||
|
||||
private static <T> T performRayTrace(ClipContext context, BiFunction<ClipContext, BlockPos, T> hitFunction, Function<ClipContext, T> p_217300_2_) {
|
||||
Vec3 startVec = context.getFrom();
|
||||
Vec3 endVec = context.getTo();
|
||||
if (!startVec.equals(endVec)) {
|
||||
double startX = Mth.lerp(-0.0000001, endVec.x, startVec.x);
|
||||
double startY = Mth.lerp(-0.0000001, endVec.y, startVec.y);
|
||||
double startZ = Mth.lerp(-0.0000001, endVec.z, startVec.z);
|
||||
double endX = Mth.lerp(-0.0000001, startVec.x, endVec.x);
|
||||
double endY = Mth.lerp(-0.0000001, startVec.y, endVec.y);
|
||||
double endZ = Mth.lerp(-0.0000001, startVec.z, endVec.z);
|
||||
int blockX = Mth.floor(endX);
|
||||
int blockY = Mth.floor(endY);
|
||||
int blockZ = Mth.floor(endZ);
|
||||
BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(blockX, blockY, blockZ);
|
||||
T t = hitFunction.apply(context, mutablePos);
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
|
||||
double deltaX = startX - endX;
|
||||
double deltaY = startY - endY;
|
||||
double deltaZ = startZ - endZ;
|
||||
int signX = Mth.sign(deltaX);
|
||||
int signY = Mth.sign(deltaY);
|
||||
int signZ = Mth.sign(deltaZ);
|
||||
double d9 = signX == 0 ? Double.MAX_VALUE : (double) signX / deltaX;
|
||||
double d10 = signY == 0 ? Double.MAX_VALUE : (double) signY / deltaY;
|
||||
double d11 = signZ == 0 ? Double.MAX_VALUE : (double) signZ / deltaZ;
|
||||
double d12 = d9 * (signX > 0 ? 1.0D - Mth.frac(endX) : Mth.frac(endX));
|
||||
double d13 = d10 * (signY > 0 ? 1.0D - Mth.frac(endY) : Mth.frac(endY));
|
||||
double d14 = d11 * (signZ > 0 ? 1.0D - Mth.frac(endZ) : Mth.frac(endZ));
|
||||
|
||||
while (d12 <= 1.0D || d13 <= 1.0D || d14 <= 1.0D) {
|
||||
if (d12 < d13) {
|
||||
if (d12 < d14) {
|
||||
blockX += signX;
|
||||
d12 += d9;
|
||||
} else {
|
||||
blockZ += signZ;
|
||||
d14 += d11;
|
||||
}
|
||||
} else if (d13 < d14) {
|
||||
blockY += signY;
|
||||
d13 += d10;
|
||||
} else {
|
||||
blockZ += signZ;
|
||||
d14 += d11;
|
||||
}
|
||||
|
||||
T t1 = hitFunction.apply(context, mutablePos.set(blockX, blockY, blockZ));
|
||||
if (t1 != null) {
|
||||
return t1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return p_217300_2_.apply(context);
|
||||
}
|
||||
|
||||
public @Nullable LivingEntity getShooter() {
|
||||
return this.shooter;
|
||||
}
|
||||
|
||||
public int getShooterId() {
|
||||
return this.shooterId;
|
||||
}
|
||||
|
||||
public float getBypassArmorRate() {
|
||||
return this.bypassArmorRate;
|
||||
}
|
||||
|
||||
public void updateHeading() {
|
||||
double horizontalDistance = this.getDeltaMovement().horizontalDistance();
|
||||
this.setYRot((float) (Mth.atan2(this.getDeltaMovement().x(), this.getDeltaMovement().z()) * (180D / Math.PI)));
|
||||
this.setXRot((float) (Mth.atan2(this.getDeltaMovement().y(), horizontalDistance) * (180D / Math.PI)));
|
||||
this.yRotO = this.getYRot();
|
||||
this.xRotO = this.getXRot();
|
||||
}
|
||||
|
||||
private void performDamage(Entity entity, float damage, boolean isHeadshot) {
|
||||
float rate = Mth.clamp(this.bypassArmorRate, 0, 1);
|
||||
|
||||
float normalDamage = damage * Mth.clamp(1 - rate, 0, 1);
|
||||
float absoluteDamage = damage * Mth.clamp(rate, 0, 1);
|
||||
|
||||
entity.invulnerableTime = 0;
|
||||
|
||||
float headShotModifier = isHeadshot ? this.headShot : 1;
|
||||
if (normalDamage > 0) {
|
||||
entity.hurt(ModDamageTypes.causeGunFireHeadshotDamage(this.level().registryAccess(), this, this.shooter), normalDamage * headShotModifier);
|
||||
entity.invulnerableTime = 0;
|
||||
}
|
||||
if (absoluteDamage > 0) {
|
||||
entity.hurt(ModDamageTypes.causeGunFireHeadshotAbsoluteDamage(this.level().registryAccess(), this, this.shooter), absoluteDamage * headShotModifier);
|
||||
entity.invulnerableTime = 0;
|
||||
|
||||
// TODO 大于1的穿甲对载具造成额外伤害
|
||||
// if (entity instanceof VehicleEntity vehicle && this.bypassArmorRate > 1) {
|
||||
// vehicle.hurt(ModDamageTypes.causeGunFireAbsoluteDamage(this.level().registryAccess(), this, this.shooter), absoluteDamage * (this.bypassArmorRate - 1) * 0.5f);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void readSpawnData(@NotNull RegistryFriendlyByteBuf additionalData) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSpawnData(@NotNull RegistryFriendlyByteBuf buffer) {
|
||||
}
|
||||
|
||||
public static class EntityResult {
|
||||
private final Entity entity;
|
||||
private final Vec3 hitVec;
|
||||
private final boolean headshot;
|
||||
private final boolean legShot;
|
||||
|
||||
public EntityResult(Entity entity, Vec3 hitVec, boolean headshot, boolean legShot) {
|
||||
this.entity = entity;
|
||||
this.hitVec = hitVec;
|
||||
this.headshot = headshot;
|
||||
this.legShot = legShot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity that was hit by the projectile
|
||||
*/
|
||||
public Entity getEntity() {
|
||||
return this.entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the position the projectile hit
|
||||
*/
|
||||
public Vec3 getHitPos() {
|
||||
return this.hitVec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if this was a headshot
|
||||
*/
|
||||
public boolean isHeadshot() {
|
||||
return this.headshot;
|
||||
}
|
||||
|
||||
public boolean isLegShot() {
|
||||
return this.legShot;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerControllers(AnimatableManager.ControllerRegistrar data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnimatableInstanceCache getAnimatableInstanceCache() {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
public boolean isZoom() {
|
||||
return this.zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builders
|
||||
*/
|
||||
public ProjectileEntity shooter(LivingEntity shooter) {
|
||||
this.shooter = shooter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity damage(float damage) {
|
||||
this.damage = damage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity headShot(float headShot) {
|
||||
this.headShot = headShot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity monsterMultiple(float monsterMultiple) {
|
||||
this.monsterMultiple = monsterMultiple;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity legShot(float legShot) {
|
||||
this.legShot = legShot;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity beast() {
|
||||
this.beast = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity jhpBullet(int jhpLevel) {
|
||||
this.jhpLevel = jhpLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity heBullet(int heLevel) {
|
||||
this.heLevel = heLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity fireBullet(int fireLevel, boolean dragonBreath) {
|
||||
this.fireLevel = fireLevel;
|
||||
this.dragonBreath = dragonBreath;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity zoom(boolean zoom) {
|
||||
this.zoom = zoom;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity bypassArmorRate(float bypassArmorRate) {
|
||||
this.bypassArmorRate = bypassArmorRate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity undeadMultiple(float undeadMultiple) {
|
||||
this.undeadMultiple = undeadMultiple;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity effect(ArrayList<MobEffectInstance> mobEffectInstances) {
|
||||
this.mobEffects.addAll(mobEffectInstances);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setRGB(float[] rgb) {
|
||||
this.entityData.set(COLOR_R, rgb[0]);
|
||||
this.entityData.set(COLOR_G, rgb[1]);
|
||||
this.entityData.set(COLOR_B, rgb[2]);
|
||||
}
|
||||
|
||||
public ProjectileEntity knockback(float knockback) {
|
||||
this.knockback = knockback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProjectileEntity forceKnockback() {
|
||||
this.forceKnockback = true;
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package com.atsuishio.superbwarfare.init;
|
|||
|
||||
import com.atsuishio.superbwarfare.ModUtils;
|
||||
import com.atsuishio.superbwarfare.entity.projectile.LaserEntity;
|
||||
import com.atsuishio.superbwarfare.entity.projectile.ProjectileEntity;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
|
@ -48,8 +49,8 @@ public class ModEntities {
|
|||
// EntityType.Builder.<RpgRocketEntity>of(RpgRocketEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).setUpdateInterval(1).setCustomClientFactory(RpgRocketEntity::new).sized(0.5f, 0.5f));
|
||||
// public static final DeferredHolder<EntityType<?>, EntityType<MortarShellEntity>> MORTAR_SHELL = register("mortar_shell",
|
||||
// EntityType.Builder.<MortarShellEntity>of(MortarShellEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).setUpdateInterval(1).setCustomClientFactory(MortarShellEntity::new).sized(0.5f, 0.5f));
|
||||
// public static final DeferredHolder<EntityType<?>, EntityType<ProjectileEntity>> PROJECTILE = register("projectile",
|
||||
// EntityType.Builder.<ProjectileEntity>of(ProjectileEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setCustomClientFactory(ProjectileEntity::new).setTrackingRange(64).noSave().noSummon().sized(0.25f, 0.25f));
|
||||
public static final DeferredHolder<EntityType<?>, EntityType<ProjectileEntity>> PROJECTILE = register("projectile",
|
||||
EntityType.Builder.<ProjectileEntity>of(ProjectileEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).noSave().noSummon().sized(0.25f, 0.25f));
|
||||
// public static final DeferredHolder<EntityType<?>, EntityType<CannonShellEntity>> CANNON_SHELL = register("cannon_shell",
|
||||
// EntityType.Builder.<CannonShellEntity>of(CannonShellEntity::new, MobCategory.MISC).setShouldReceiveVelocityUpdates(false).setTrackingRange(64).setUpdateInterval(1).setCustomClientFactory(CannonShellEntity::new).sized(0.5f, 0.5f));
|
||||
// public static final DeferredHolder<EntityType<?>, EntityType<GunGrenadeEntity>> GUN_GRENADE = register("gun_grenade",
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package com.atsuishio.superbwarfare.network;
|
||||
|
||||
import com.atsuishio.superbwarfare.network.message.BreathMessage;
|
||||
import com.atsuishio.superbwarfare.network.message.LaserShootMessage;
|
||||
import com.atsuishio.superbwarfare.network.message.PlayerVariablesSyncMessage;
|
||||
import com.atsuishio.superbwarfare.network.message.ShakeClientMessage;
|
||||
import com.atsuishio.superbwarfare.network.message.*;
|
||||
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||
|
||||
|
@ -11,29 +8,11 @@ public class NetworkRegistry {
|
|||
public static void register(final RegisterPayloadHandlersEvent event) {
|
||||
final PayloadRegistrar registrar = event.registrar("1");
|
||||
|
||||
registrar.playToClient(
|
||||
PlayerVariablesSyncMessage.TYPE,
|
||||
PlayerVariablesSyncMessage.STREAM_CODEC,
|
||||
PlayerVariablesSyncMessage::handler
|
||||
);
|
||||
|
||||
registrar.playToClient(
|
||||
ShakeClientMessage.TYPE,
|
||||
ShakeClientMessage.STREAM_CODEC,
|
||||
ShakeClientMessage::handler
|
||||
);
|
||||
|
||||
registrar.playToServer(
|
||||
LaserShootMessage.TYPE,
|
||||
LaserShootMessage.STREAM_CODEC,
|
||||
LaserShootMessage::handler
|
||||
);
|
||||
|
||||
registrar.playToServer(
|
||||
BreathMessage.TYPE,
|
||||
BreathMessage.STREAM_CODEC,
|
||||
BreathMessage::handler
|
||||
);
|
||||
registrar.playToClient(PlayerVariablesSyncMessage.TYPE, PlayerVariablesSyncMessage.STREAM_CODEC, PlayerVariablesSyncMessage::handler);
|
||||
registrar.playToClient(ShakeClientMessage.TYPE, ShakeClientMessage.STREAM_CODEC, ShakeClientMessage::handler);
|
||||
registrar.playToClient(ClientMotionSyncMessage.TYPE, ClientMotionSyncMessage.STREAM_CODEC, ClientMotionSyncMessage::handler);
|
||||
registrar.playToServer(LaserShootMessage.TYPE, LaserShootMessage.STREAM_CODEC, LaserShootMessage::handler);
|
||||
registrar.playToServer(BreathMessage.TYPE, BreathMessage.STREAM_CODEC, BreathMessage::handler);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.atsuishio.superbwarfare.network.message;
|
||||
|
||||
import com.atsuishio.superbwarfare.ModUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record ClientMotionSyncMessage(int id, float x, float y, float z) implements CustomPacketPayload {
|
||||
|
||||
public static final CustomPacketPayload.Type<ClientMotionSyncMessage> TYPE = new CustomPacketPayload.Type<>(ModUtils.loc("breath"));
|
||||
|
||||
public static final StreamCodec<ByteBuf, ClientMotionSyncMessage> STREAM_CODEC = StreamCodec.composite(
|
||||
ByteBufCodecs.INT,
|
||||
ClientMotionSyncMessage::id,
|
||||
ByteBufCodecs.FLOAT,
|
||||
ClientMotionSyncMessage::x,
|
||||
ByteBufCodecs.FLOAT,
|
||||
ClientMotionSyncMessage::y,
|
||||
ByteBufCodecs.FLOAT,
|
||||
ClientMotionSyncMessage::z,
|
||||
ClientMotionSyncMessage::new
|
||||
);
|
||||
|
||||
public ClientMotionSyncMessage(Entity entity) {
|
||||
this(entity.getId(), (float) entity.getDeltaMovement().x, (float) entity.getDeltaMovement().y, (float) entity.getDeltaMovement().z);
|
||||
}
|
||||
|
||||
|
||||
public static void handler(final ClientMotionSyncMessage message, final IPayloadContext context) {
|
||||
var level = Minecraft.getInstance().level;
|
||||
if (level == null) return;
|
||||
Entity entity = level.getEntity(message.id);
|
||||
if (entity != null) {
|
||||
entity.lerpMotion(message.x, message.y, message.z);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
package com.atsuishio.superbwarfare.tools;
|
||||
|
||||
import com.atsuishio.superbwarfare.config.server.ExplosionConfig;
|
||||
import com.atsuishio.superbwarfare.network.message.ShakeClientMessage;
|
||||
import com.google.common.collect.Sets;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.item.PrimedTnt;
|
||||
import net.minecraft.world.entity.monster.Monster;
|
||||
import net.minecraft.world.level.Explosion;
|
||||
import net.minecraft.world.level.ExplosionDamageCalculator;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.gameevent.GameEvent;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.neoforge.event.EventHooks;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class CustomExplosion extends Explosion {
|
||||
|
||||
private final Level level;
|
||||
private final double x;
|
||||
private final double y;
|
||||
private final double z;
|
||||
@Nullable
|
||||
private final Entity source;
|
||||
private final float radius;
|
||||
private final DamageSource damageSource;
|
||||
private final ExplosionDamageCalculator damageCalculator;
|
||||
private final float damage;
|
||||
private int fireTime;
|
||||
private float damageMultiplier;
|
||||
|
||||
public CustomExplosion(Level pLevel, @Nullable Entity pSource, @Nullable DamageSource source,
|
||||
@Nullable ExplosionDamageCalculator pDamageCalculator,
|
||||
float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius,
|
||||
BlockInteraction pBlockInteraction) {
|
||||
|
||||
// TODO what are these options?
|
||||
super(pLevel, pSource, source, null, pToBlowX, pToBlowY, pToBlowZ, pRadius, false, pBlockInteraction, null, null, null);
|
||||
// super(pLevel, pSource, source, null, pToBlowX, pToBlowY, pToBlowZ, pRadius, false, pBlockInteraction
|
||||
// , ?,?,? );
|
||||
|
||||
this.level = pLevel;
|
||||
this.source = pSource;
|
||||
this.radius = pRadius;
|
||||
this.damageSource = source == null ? pLevel.damageSources().explosion(this) : source;
|
||||
this.damageCalculator = pDamageCalculator == null ? new ExplosionDamageCalculator() : pDamageCalculator;
|
||||
this.x = pToBlowX;
|
||||
this.y = pToBlowY;
|
||||
this.z = pToBlowZ;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
public CustomExplosion(Level pLevel, @Nullable Entity pSource, float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius, BlockInteraction pBlockInteraction) {
|
||||
this(pLevel, pSource, null, null, damage, pToBlowX, pToBlowY, pToBlowZ, pRadius, pBlockInteraction);
|
||||
}
|
||||
|
||||
public CustomExplosion(Level pLevel, @Nullable Entity pSource, @Nullable DamageSource source, float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius, BlockInteraction pBlockInteraction) {
|
||||
this(pLevel, pSource, source, null, damage, pToBlowX, pToBlowY, pToBlowZ, pRadius, pBlockInteraction);
|
||||
final Vec3 center = new Vec3(pToBlowX, pToBlowY, pToBlowZ);
|
||||
for (Entity target : level.getEntitiesOfClass(Entity.class, new AABB(center, center).inflate(4 * radius), e -> true).stream().sorted(Comparator.comparingDouble(e -> e.distanceToSqr(center))).toList()) {
|
||||
if (target instanceof ServerPlayer serverPlayer) {
|
||||
PacketDistributor.sendToPlayer(serverPlayer, new ShakeClientMessage(20 + 0.02 * damage, 3 * pRadius, 50 + 0.05 * damage, pToBlowX, pToBlowY, pToBlowZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CustomExplosion(Level pLevel, @Nullable Entity pSource, @Nullable DamageSource source, float damage, double pToBlowX, double pToBlowY, double pToBlowZ, float pRadius) {
|
||||
this(pLevel, pSource, source, null, damage, pToBlowX, pToBlowY, pToBlowZ, pRadius, BlockInteraction.KEEP);
|
||||
|
||||
final Vec3 center = new Vec3(pToBlowX, pToBlowY, pToBlowZ);
|
||||
for (Entity target : level.getEntitiesOfClass(Entity.class, new AABB(center, center).inflate(radius), e -> true).stream().sorted(Comparator.comparingDouble(e -> e.distanceToSqr(center))).toList()) {
|
||||
if (target instanceof ServerPlayer serverPlayer && !(target == pSource && pSource.getVehicle() != null)) {
|
||||
PacketDistributor.sendToPlayer(serverPlayer, new ShakeClientMessage(20 + 0.02 * damage, pRadius, 10 + 0.03 * damage, pToBlowX, pToBlowY, pToBlowZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CustomExplosion setFireTime(int fireTime) {
|
||||
this.fireTime = fireTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CustomExplosion setDamageMultiplier(float damageMultiplier) {
|
||||
this.damageMultiplier = damageMultiplier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CustomExplosion bulletExplode() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void explode() {
|
||||
this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z));
|
||||
Set<BlockPos> set = Sets.newHashSet();
|
||||
|
||||
for (int j = 0; j < 16; ++j) {
|
||||
for (int k = 0; k < 16; ++k) {
|
||||
for (int l = 0; l < 16; ++l) {
|
||||
if (j == 0 || j == 15 || k == 0 || k == 15 || l == 0 || l == 15) {
|
||||
double d0 = (float) j / 15.0F * 2.0F - 1.0F;
|
||||
double d1 = (float) k / 15.0F * 2.0F - 1.0F;
|
||||
double d2 = (float) l / 15.0F * 2.0F - 1.0F;
|
||||
double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
|
||||
d0 /= d3;
|
||||
d1 /= d3;
|
||||
d2 /= d3;
|
||||
float f = this.radius * (0.4F + this.level.random.nextFloat() * 0.3F);
|
||||
double d4 = this.x;
|
||||
double d6 = this.y;
|
||||
double d8 = this.z;
|
||||
|
||||
for (; f > 0.0F; f -= 0.22500001F) {
|
||||
BlockPos blockpos = BlockPos.containing(d4, d6, d8);
|
||||
BlockState blockstate = this.level.getBlockState(blockpos);
|
||||
FluidState fluidstate = this.level.getFluidState(blockpos);
|
||||
if (!this.level.isInWorldBounds(blockpos)) {
|
||||
break;
|
||||
}
|
||||
|
||||
Optional<Float> optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockpos, blockstate, fluidstate);
|
||||
if (optional.isPresent()) {
|
||||
f -= (optional.get() + 1F) * 0.3F;
|
||||
}
|
||||
|
||||
if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockpos, blockstate, f)) {
|
||||
set.add(blockpos);
|
||||
}
|
||||
|
||||
d4 += d0 * (double) 0.3F;
|
||||
d6 += d1 * (double) 0.3F;
|
||||
d8 += d2 * (double) 0.3F;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.getToBlow().addAll(set);
|
||||
|
||||
float diameter = this.radius * 2.0F;
|
||||
int x0 = Mth.floor(this.x - (double) diameter - 1.0D);
|
||||
int x1 = Mth.floor(this.x + (double) diameter + 1.0D);
|
||||
int y0 = Mth.floor(this.y - (double) diameter - 1.0D);
|
||||
int y1 = Mth.floor(this.y + (double) diameter + 1.0D);
|
||||
int z0 = Mth.floor(this.z - (double) diameter - 1.0D);
|
||||
int z1 = Mth.floor(this.z + (double) diameter + 1.0D);
|
||||
List<Entity> list = this.level.getEntities(this.source, new AABB(x0, y0, z0, x1, y1, z1));
|
||||
EventHooks.onExplosionDetonate(this.level, this, list, diameter);
|
||||
Vec3 position = new Vec3(this.x, this.y, this.z);
|
||||
|
||||
for (Entity entity : list) {
|
||||
if (!entity.ignoreExplosion(this)) {
|
||||
double distanceRate = Math.sqrt(entity.distanceToSqr(position)) / (double) diameter;
|
||||
if (distanceRate <= 1.0D) {
|
||||
double xDistance = entity.getX() - this.x;
|
||||
double yDistance = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - this.y;
|
||||
double zDistance = entity.getZ() - this.z;
|
||||
double distance = Math.sqrt(xDistance * xDistance + yDistance * yDistance + zDistance * zDistance);
|
||||
|
||||
if (distance != 0.0D) {
|
||||
double seenPercent = Mth.clamp(getSeenPercent(position, entity), 0.01 * ExplosionConfig.EXPLOSION_PENETRATION_RATIO.get(), Double.POSITIVE_INFINITY);
|
||||
double damagePercent = (1.0D - distanceRate) * seenPercent;
|
||||
double damageFinal = (damagePercent * damagePercent + damagePercent) / 2.0D * damage;
|
||||
|
||||
if (entity instanceof Monster monster) {
|
||||
monster.hurt(this.damageSource, (float) damageFinal * (1 + 0.2f * this.damageMultiplier));
|
||||
} else {
|
||||
entity.hurt(this.damageSource, (float) damageFinal);
|
||||
}
|
||||
|
||||
if (fireTime > 0) {
|
||||
entity.setRemainingFireTicks(fireTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package com.atsuishio.superbwarfare.tools;
|
||||
|
||||
import com.atsuishio.superbwarfare.entity.projectile.ProjectileEntity;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
|
||||
/**
|
||||
* Author: MrCrayFish
|
||||
*/
|
||||
public class ExtendedEntityRayTraceResult extends EntityHitResult {
|
||||
private final boolean headshot;
|
||||
private final boolean legShot;
|
||||
|
||||
public ExtendedEntityRayTraceResult(ProjectileEntity.EntityResult result) {
|
||||
super(result.getEntity(), result.getHitPos());
|
||||
this.headshot = result.isHeadshot();
|
||||
this.legShot = result.isLegShot();
|
||||
}
|
||||
|
||||
public boolean isHeadshot() {
|
||||
return this.headshot;
|
||||
}
|
||||
|
||||
public boolean isLegShot() {
|
||||
return this.legShot;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue