moved miners dream to seperate mod

new portal gun draft
fixed scorched guns mixin
sodium compat layer
This commit is contained in:
craisinlord committed 2026-09-29 23:37:11 -05:00
1 parent 23599c4c59
commit c7cb48edfb
189 files changed
+6450 -2743

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ https://www.curseforge.com/minecraft/mc-mods/antarchy
ANTS! ANTS! ANTS!
Antarchy requires Architectury API, AntOS, Integrated API, GeckoLib, and Sizeable Folia.
Antarchy requires Architectury API, AntOS, Integrated API, GeckoLib, and Sizeable Foliage.
## Credits
@@ -110,7 +110,7 @@ public final class AntarchyConfigCatalog {
|| name.startsWith("nightmareSword") || name.startsWith("basiliskDagger")
|| name.startsWith("squidzooka") || name.equals("invertProjectilesFromInvertedPlayers")
|| name.startsWith("gravityGun") || name.startsWith("critterCage")
|| name.startsWith("minersDream") || name.equals("potentNyxiteInvertedDurationSeconds")
|| name.equals("potentNyxiteInvertedDurationSeconds")
|| name.equals("corneaEarNightVisionSeconds") || name.startsWith("american")
|| name.startsWith("mogglesVision") || name.equals("ductTapeRepairPercentPerUse")
|| name.startsWith("fallenKingCrown");
@@ -163,11 +163,6 @@ public final class AntarchySettings {
private static double gravityGunMaxHoldDistance = 10.0D;
private static double critterCageMaxCapturableWidth = 1.65D;
private static double critterCageMaxCapturableHeight = 1.65D;
private static boolean minersDreamEnabled = true;
private static int minersDreamMinimumRange = 48;
private static int minersDreamMaximumRange = 96;
private static int minersDreamTorchSpacing = 10;
private static int minersDreamBlocksPerTick = 150;
private static double lucidAttackRange = 9.0D;
private static double lucidPearlInvertedDurationSeconds = 6.0D;
private static int corneaEarNightVisionSeconds = 15;
@@ -1189,26 +1184,6 @@ public final class AntarchySettings {
return critterCageMaxCapturableHeight;
}
public static boolean minersDreamEnabled() {
return minersDreamEnabled;
}
public static int minersDreamMinimumRange() {
return minersDreamMinimumRange;
}
public static int minersDreamMaximumRange() {
return minersDreamMaximumRange;
}
public static int minersDreamTorchSpacing() {
return minersDreamTorchSpacing;
}
public static int minersDreamBlocksPerTick() {
return minersDreamBlocksPerTick;
}
public static void setDisableInfinityBookPortalCreation(boolean value) {
disableInfinityBookPortalCreation = value;
}
@@ -1731,26 +1706,6 @@ public final class AntarchySettings {
critterCageMaxCapturableHeight = value;
}
public static void setMinersDreamEnabled(boolean value) {
minersDreamEnabled = value;
}
public static void setMinersDreamMinimumRange(int value) {
minersDreamMinimumRange = value;
}
public static void setMinersDreamMaximumRange(int value) {
minersDreamMaximumRange = value;
}
public static void setMinersDreamTorchSpacing(int value) {
minersDreamTorchSpacing = value;
}
public static void setMinersDreamBlocksPerTick(int value) {
minersDreamBlocksPerTick = value;
}
public static double lucidAttackRange() {
return lucidAttackRange;
}
@@ -40,7 +40,6 @@ public final class AntarchyTags {
public static final TagKey<Block> CAVARYN_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("cavaryn_portal_frames"));
public static final TagKey<Block> RAINBOW_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("rainbow_portal_frames"));
public static final TagKey<Block> ROLLY_CAVERN_CARVEABLE = TagKey.create(Registries.BLOCK, id("rolly_cavern_carveable"));
public static final TagKey<Block> MINERS_DREAM_CARVABLE = TagKey.create(Registries.BLOCK, id("miners_dream_carvable"));
public static final TagKey<Block> BLUESTONE_COMPONENTS = TagKey.create(Registries.BLOCK, id("bluestone_components"));
public static final TagKey<Block> BLUESTONE_CONNECTABLE = TagKey.create(Registries.BLOCK, id("bluestone_connectable"));
public static final TagKey<Block> ANTIMETAL_INVERSION_BLOCKS = TagKey.create(Registries.BLOCK, id("antimetal_inversion_blocks"));
@@ -19,6 +19,7 @@ import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.HopperMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
@@ -118,6 +119,10 @@ public class UpperBlockEntity extends RandomizableContainerBlockEntity {
}
private static Container getContainerAt(ServerLevel level, BlockPos pos) {
BlockState state = level.getBlockState(pos);
if (state.getBlock() instanceof ChestBlock chestBlock) {
return ChestBlock.getContainer(chestBlock, state, level, pos, false);
}
BlockEntity blockEntity = level.getBlockEntity(pos);
return blockEntity instanceof Container container ? container : null;
}
@@ -1,173 +0,0 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.AntarchyTags;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
public final class MinersDreamCaveGenerator {
private static final double MIN_HORIZONTAL_RADIUS = 2.0D;
private static final double MAX_HORIZONTAL_RADIUS = 5.0D;
private static final double MIN_VERTICAL_RADIUS = 1.5D;
private static final double MAX_VERTICAL_RADIUS = 4.0D;
private static final double MAX_VERTICAL_DEVIATION = 8.0D;
private static final double SOFT_VERTICAL_DEVIATION = 4.0D;
private static final int WORLD_MARGIN = 3;
private MinersDreamCaveGenerator() {
}
public record CaveNode(BlockPos center, double horizontalRadius, double verticalRadius) {
}
public record CaveGenerationResult(List<CaveNode> nodes, Set<Integer> torchCheckpoints) {
}
public static CaveGenerationResult generate(BlockPos start, Direction initialDirection, int length,
int torchSpacing, int minBuildHeight, int maxBuildHeight, RandomSource random) {
List<CaveNode> nodes = new ArrayList<>(length);
Set<Integer> torchCheckpoints = new HashSet<>();
double x = start.getX() + 0.5D;
double y = start.getY() + 0.5D;
double z = start.getZ() + 0.5D;
double startY = y;
double headingX = initialDirection.getStepX();
double headingZ = initialDirection.getStepZ();
if (headingX == 0.0D && headingZ == 0.0D) {
headingX = 1.0D;
}
double verticalVelocity = 0.0D;
double horizontalRadius = 3.0D;
double verticalRadius = 2.0D;
int minY = minBuildHeight + WORLD_MARGIN;
int maxY = maxBuildHeight - WORLD_MARGIN;
int spacing = Math.max(4, torchSpacing);
int traveledSinceTorch = 0;
int nextTorchDistance = spacing + random.nextInt(5) - 2;
for (int i = 0; i < length; i++) {
BlockPos center = BlockPos.containing(x, Mth.clamp(y, minY, maxY), z);
nodes.add(new CaveNode(center, horizontalRadius, verticalRadius));
traveledSinceTorch++;
if (traveledSinceTorch >= Math.max(1, nextTorchDistance)) {
torchCheckpoints.add(i);
traveledSinceTorch = 0;
nextTorchDistance = spacing + random.nextInt(5) - 2;
}
double turnDegrees = random.nextGaussian() * 8.0D;
if (random.nextFloat() < 0.1F) {
turnDegrees = random.nextGaussian() * 15.0D;
}
turnDegrees = Mth.clamp(turnDegrees, -25.0D, 25.0D);
double turnRadians = Math.toRadians(turnDegrees);
double cos = Math.cos(turnRadians);
double sin = Math.sin(turnRadians);
double newHeadingX = headingX * cos - headingZ * sin;
double newHeadingZ = headingX * sin + headingZ * cos;
double headingLength = Math.sqrt(newHeadingX * newHeadingX + newHeadingZ * newHeadingZ);
headingX = newHeadingX / headingLength;
headingZ = newHeadingZ / headingLength;
verticalVelocity += random.nextGaussian() * 0.15D;
double deviation = y - startY;
verticalVelocity -= deviation * 0.06D;
if (deviation > SOFT_VERTICAL_DEVIATION) {
verticalVelocity -= 0.08D;
} else if (deviation < -SOFT_VERTICAL_DEVIATION) {
verticalVelocity += 0.08D;
}
verticalVelocity = Mth.clamp(verticalVelocity, -0.6D, 0.6D);
x += headingX;
z += headingZ;
y = Mth.clamp(y + verticalVelocity, startY - MAX_VERTICAL_DEVIATION, startY + MAX_VERTICAL_DEVIATION);
horizontalRadius = Mth.clamp(horizontalRadius + random.nextGaussian() * 0.15D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS);
verticalRadius = Mth.clamp(verticalRadius + random.nextGaussian() * 0.12D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
if (random.nextFloat() < 0.05F) {
horizontalRadius = Mth.clamp(horizontalRadius + 0.6D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS);
verticalRadius = Mth.clamp(verticalRadius + 0.5D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
}
}
return new CaveGenerationResult(nodes, torchCheckpoints);
}
public static void collectNodeCandidates(ServerLevel level, CaveNode node, LongOpenHashSet visited, RandomSource random, List<BlockPos> out) {
BlockPos center = node.center();
int hr = Mth.ceil(node.horizontalRadius());
double upExtent = node.verticalRadius() * 1.25D;
double downExtent = node.verticalRadius() * 0.65D;
int upR = Mth.ceil(upExtent);
int downR = Mth.ceil(downExtent);
int minBuildHeight = level.getMinBuildHeight();
int maxBuildHeight = level.getMaxBuildHeight();
for (int dy = -downR; dy <= upR; dy++) {
double vExtent = dy >= 0 ? upExtent : downExtent;
if (vExtent <= 0.0D) {
continue;
}
double vTerm = (dy * dy) / (vExtent * vExtent);
if (vTerm > 1.05D) {
continue;
}
for (int dx = -hr; dx <= hr; dx++) {
double hxTerm = (dx * dx) / (node.horizontalRadius() * node.horizontalRadius());
for (int dz = -hr; dz <= hr; dz++) {
double hzTerm = (dz * dz) / (node.horizontalRadius() * node.horizontalRadius());
double distance = hxTerm + vTerm + hzTerm;
if (distance > 1.0D) {
continue;
}
if (distance > 0.7D) {
double edgeChance = (1.0D - distance) / 0.3D * 0.85D;
if (random.nextFloat() > edgeChance) {
continue;
}
}
BlockPos pos = center.offset(dx, dy, dz);
if (pos.getY() < minBuildHeight || pos.getY() >= maxBuildHeight) {
continue;
}
long packed = pos.asLong();
if (visited.contains(packed)) {
continue;
}
BlockState state = level.getBlockState(pos);
if (!state.is(AntarchyTags.Blocks.MINERS_DREAM_CARVABLE)) {
continue;
}
if (level.getBlockEntity(pos) != null) {
continue;
}
visited.add(packed);
out.add(pos);
}
}
}
}
public static boolean isChunkLoaded(ServerLevel level, BlockPos pos) {
return level.isLoaded(pos);
}
}
@@ -1,152 +0,0 @@
package com.craisinlord.antarchy.content.item;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.joml.Vector3f;
public final class MinersDreamExcavation {
private static final Vector3f TRIFFID_GOO_COLOR = new Vector3f(0.35F, 0.85F, 0.4F);
private static final int SLICES_BETWEEN_RUMBLE = 4;
private final UUID owner;
private final ResourceKey<Level> dimension;
private final List<MinersDreamCaveGenerator.CaveNode> nodes;
private final Set<Integer> torchCheckpoints;
private final LongOpenHashSet visited = new LongOpenHashSet();
private int nodeIndex = 0;
private List<BlockPos> currentNodePositions = null;
private int positionCursor = 0;
private int slicesSinceRumble = 0;
private boolean finished = false;
public MinersDreamExcavation(UUID owner, ResourceKey<Level> dimension, List<MinersDreamCaveGenerator.CaveNode> nodes, Set<Integer> torchCheckpoints) {
this.owner = owner;
this.dimension = dimension;
this.nodes = nodes;
this.torchCheckpoints = torchCheckpoints;
}
public UUID owner() {
return owner;
}
public ResourceKey<Level> dimension() {
return dimension;
}
public boolean isFinished() {
return finished;
}
public void tick(ServerLevel level, int blockBudget) {
RandomSource random = level.getRandom();
int remaining = blockBudget;
while (remaining > 0 && !finished) {
if (currentNodePositions == null) {
if (nodeIndex >= nodes.size()) {
finished = true;
playCompletionSound(level);
return;
}
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
if (!MinersDreamCaveGenerator.isChunkLoaded(level, node.center())) {
advanceNode(level, node);
continue;
}
currentNodePositions = new ArrayList<>();
MinersDreamCaveGenerator.collectNodeCandidates(level, node, visited, random, currentNodePositions);
positionCursor = 0;
}
if (currentNodePositions.isEmpty()) {
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
advanceNode(level, node);
continue;
}
int endIndex = Math.min(currentNodePositions.size(), positionCursor + remaining);
for (; positionCursor < endIndex; positionCursor++) {
BlockPos pos = currentNodePositions.get(positionCursor);
BlockState removedState = level.getBlockState(pos);
if (removedState.isAir()) {
continue;
}
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
remaining--;
if (random.nextFloat() < 0.12F) {
spawnBreakParticles(level, pos, removedState, random);
}
}
if (positionCursor >= currentNodePositions.size()) {
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
advanceNode(level, node);
}
}
}
private void advanceNode(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) {
RandomSource random = level.getRandom();
spawnFrontDust(level, node, random);
slicesSinceRumble++;
if (slicesSinceRumble >= SLICES_BETWEEN_RUMBLE) {
slicesSinceRumble = 0;
playRumble(level, node);
}
if (torchCheckpoints.contains(nodeIndex)) {
MinersDreamTorchPlacer.tryPlaceTorch(level, node.center(), random);
}
currentNodePositions = null;
positionCursor = 0;
nodeIndex++;
}
private void spawnBreakParticles(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
level.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state),
pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 2, 0.25D, 0.25D, 0.25D, 0.02D);
if (random.nextFloat() < 0.2F) {
level.sendParticles(new DustParticleOptions(TRIFFID_GOO_COLOR, 1.0F),
pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 1, 0.2D, 0.2D, 0.2D, 0.0D);
}
}
private void spawnFrontDust(ServerLevel level, MinersDreamCaveGenerator.CaveNode node, RandomSource random) {
BlockPos center = node.center();
level.sendParticles(ParticleTypes.CLOUD, center.getX() + 0.5D, center.getY() + 0.5D, center.getZ() + 0.5D,
3, node.horizontalRadius() * 0.4D, node.verticalRadius() * 0.3D, node.horizontalRadius() * 0.4D, 0.01D);
}
private void playRumble(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) {
BlockPos center = node.center();
level.playSound(null, center, SoundEvents.STONE_BREAK, SoundSource.BLOCKS, 0.5F, 0.7F);
}
private void playCompletionSound(ServerLevel level) {
if (nodes.isEmpty()) {
return;
}
BlockPos center = nodes.get(nodes.size() - 1).center();
level.playSound(null, center, SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 0.4F, 1.4F);
}
}
@@ -1,81 +0,0 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
public final class MinersDreamExcavationManager {
private static final int MAX_ACTIVE_TASKS = 8;
private static final int SAFE_MIN_RANGE = 8;
private static final int SAFE_MAX_RANGE = 256;
private static final Map<UUID, MinersDreamExcavation> ACTIVE = new ConcurrentHashMap<>();
private MinersDreamExcavationManager() {
}
public static boolean hasActiveExcavation(UUID owner) {
return ACTIVE.containsKey(owner);
}
public static MinersDreamExcavation tryStart(ServerLevel level, UUID owner, BlockPos startPos, Direction direction) {
if (ACTIVE.containsKey(owner) || ACTIVE.size() >= MAX_ACTIVE_TASKS) {
return null;
}
int minRange = Mth.clamp(AntarchySettings.minersDreamMinimumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE);
int maxRange = Mth.clamp(AntarchySettings.minersDreamMaximumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE);
if (minRange > maxRange) {
int swap = minRange;
minRange = maxRange;
maxRange = swap;
}
RandomSource random = level.getRandom();
int length = minRange == maxRange ? minRange : minRange + random.nextInt(maxRange - minRange + 1);
MinersDreamCaveGenerator.CaveGenerationResult result = MinersDreamCaveGenerator.generate(
startPos,
direction,
length,
AntarchySettings.minersDreamTorchSpacing(),
level.getMinBuildHeight(),
level.getMaxBuildHeight(),
random
);
if (result.nodes().isEmpty()) {
return null;
}
MinersDreamExcavation excavation = new MinersDreamExcavation(owner, level.dimension(), result.nodes(), result.torchCheckpoints());
ACTIVE.put(owner, excavation);
return excavation;
}
public static void tick(ServerLevel level) {
if (ACTIVE.isEmpty()) {
return;
}
int budget = Math.max(1, AntarchySettings.minersDreamBlocksPerTick());
Iterator<Map.Entry<UUID, MinersDreamExcavation>> iterator = ACTIVE.entrySet().iterator();
while (iterator.hasNext()) {
MinersDreamExcavation excavation = iterator.next().getValue();
if (!excavation.dimension().equals(level.dimension())) {
continue;
}
excavation.tick(level, budget);
if (excavation.isFinished()) {
iterator.remove();
}
}
}
}
@@ -1,79 +0,0 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
public final class MinersDreamItem extends Item {
public MinersDreamItem(Properties properties) {
super(properties);
}
@Override
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
Player player = context.getPlayer();
if (player == null) {
return InteractionResult.PASS;
}
if (!AntarchySettings.minersDreamEnabled()) {
if (!level.isClientSide()) {
player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_disabled"), true);
}
return InteractionResult.FAIL;
}
if (level.isClientSide()) {
return InteractionResult.SUCCESS;
}
ServerLevel serverLevel = (ServerLevel) level;
BlockPos clickedPos = context.getClickedPos();
Direction clickedFace = context.getClickedFace();
Direction travelDirection = clickedFace.getAxis().isHorizontal() ? clickedFace : player.getDirection();
BlockPos startPos = clickedPos.relative(travelDirection);
if (!serverLevel.isInWorldBounds(startPos) || !MinersDreamCaveGenerator.isChunkLoaded(serverLevel, startPos)) {
return InteractionResult.FAIL;
}
if (MinersDreamExcavationManager.hasActiveExcavation(player.getUUID())) {
player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_busy"), true);
return InteractionResult.FAIL;
}
MinersDreamExcavation excavation = MinersDreamExcavationManager.tryStart(serverLevel, player.getUUID(), startPos, travelDirection);
if (excavation == null) {
return InteractionResult.FAIL;
}
serverLevel.playSound(null, clickedPos, SoundEvents.TNT_PRIMED, SoundSource.PLAYERS, 0.8F, 1.1F);
if (!player.getAbilities().instabuild) {
context.getItemInHand().shrink(1);
}
return InteractionResult.CONSUME;
}
@Override
public void appendHoverText(ItemStack stack, Level level, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line1").withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line2").withStyle(ChatFormatting.GRAY));
super.appendHoverText(stack, level, tooltipComponents, tooltipFlag);
}
}
@@ -1,89 +0,0 @@
package com.craisinlord.antarchy.content.item;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
public final class MinersDreamTorchPlacer {
private MinersDreamTorchPlacer() {
}
public static void tryPlaceTorch(ServerLevel level, BlockPos center, RandomSource random) {
BlockPos floorSpot = findFloorSpot(level, center);
boolean floorFirst = random.nextBoolean();
if (floorFirst) {
if (floorSpot != null && placeFloorTorch(level, floorSpot)) {
return;
}
placeWallTorch(level, center, random);
return;
}
if (placeWallTorch(level, center, random)) {
return;
}
if (floorSpot != null) {
placeFloorTorch(level, floorSpot);
}
}
private static BlockPos findFloorSpot(ServerLevel level, BlockPos center) {
for (int dy = -2; dy <= 1; dy++) {
BlockPos candidate = center.offset(0, dy, 0);
if (!isOpenForTorch(level, candidate)) {
continue;
}
BlockState below = level.getBlockState(candidate.below());
if (below.isFaceSturdy(level, candidate.below(), Direction.UP)) {
return candidate;
}
}
return null;
}
private static boolean placeFloorTorch(ServerLevel level, BlockPos pos) {
if (!isOpenForTorch(level, pos)) {
return false;
}
BlockState state = Blocks.TORCH.defaultBlockState();
if (!state.canSurvive(level, pos)) {
return false;
}
level.setBlock(pos, state, 3);
level.playSound(null, pos, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
return true;
}
private static boolean placeWallTorch(ServerLevel level, BlockPos center, RandomSource random) {
List<Direction> order = Direction.Plane.HORIZONTAL.shuffledCopy(random);
for (int dy = -1; dy <= 1; dy++) {
BlockPos candidate = center.offset(0, dy, 0);
if (!isOpenForTorch(level, candidate)) {
continue;
}
for (Direction wallSide : order) {
Direction facing = wallSide.getOpposite();
BlockState state = Blocks.WALL_TORCH.defaultBlockState().setValue(BlockStateProperties.HORIZONTAL_FACING, facing);
if (!state.canSurvive(level, candidate)) {
continue;
}
level.setBlock(candidate, state, 3);
level.playSound(null, candidate, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
return true;
}
}
return false;
}
private static boolean isOpenForTorch(ServerLevel level, BlockPos pos) {
BlockState state = level.getBlockState(pos);
return (state.isAir() || state.canBeReplaced()) && level.getFluidState(pos).isEmpty();
}
}
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "Rechtsklicke mit einer Glasflasche auf eine Reverie, um sie einzufangen.",
"tooltip.antarchy.reverie_bottle.release": "Rechtsklick, um die Reverie freizulassen.",
"item.antarchy.gravity_gun": "Gravitationskanone",
"item.antarchy.miners_dream": "Traum des Bergmanns",
"tooltip.antarchy.miners_dream.line1": "Rechtsklick, um eine gewundene Höhle zu graben.",
"tooltip.antarchy.miners_dream.line2": "Lässt wertvolle Blöcke unberührt.",
"message.antarchy.miners_dream_disabled": "Der Traum des Bergmanns ist deaktiviert.",
"message.antarchy.miners_dream_busy": "Dein vorheriger Traum entfaltet sich noch.",
"item.antarchy.diamond_minecart": "Die Diamantlore",
"item.antarchy.cogwheel_pet": "Zahnrad-Haustier",
"item.antarchy.fire_dragon_pet": "Feuerdrachen-Haustier",
@@ -632,11 +632,6 @@
"tooltip.antarchy.reverie_bottle.release": "Right click to release the Reverie.",
"item.antarchy.gravity_gun": "Gravity Gun",
"item.antarchy.portal_gun": "Portal Gun",
"item.antarchy.miners_dream": "Miner's Dream",
"tooltip.antarchy.miners_dream.line1": "Use to carve a winding cave.",
"tooltip.antarchy.miners_dream.line2": "Leaves valuable blocks untouched.",
"message.antarchy.miners_dream_disabled": "The Miner's Dream is disabled.",
"message.antarchy.miners_dream_busy": "Your previous dream is still unfolding.",
"item.antarchy.diamond_minecart": "The Diamond Minecart",
"item.antarchy.cogwheel_pet": "Cogwheel Pet",
"item.antarchy.fire_dragon_pet": "Fire Dragon Pet",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "Haz clic derecho en una Reverie con una botella de cristal para guardarla.",
"tooltip.antarchy.reverie_bottle.release": "Clic derecho para liberar a la Reverie.",
"item.antarchy.gravity_gun": "Pistola gravitatoria",
"item.antarchy.miners_dream": "Sueño del minero",
"tooltip.antarchy.miners_dream.line1": "Clic derecho para excavar una cueva sinuosa.",
"tooltip.antarchy.miners_dream.line2": "Deja intactos los bloques valiosos.",
"message.antarchy.miners_dream_disabled": "El Sueño del minero está desactivado.",
"message.antarchy.miners_dream_busy": "Tu sueño anterior todavía se está desarrollando.",
"item.antarchy.diamond_minecart": "La vagoneta de diamante",
"item.antarchy.cogwheel_pet": "Mascota engranaje",
"item.antarchy.fire_dragon_pet": "Mascota de dragón de fuego",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "Faites un clic droit sur une Reverie avec une fiole en verre pour la capturer.",
"tooltip.antarchy.reverie_bottle.release": "Clic droit pour libérer la Reverie.",
"item.antarchy.gravity_gun": "Canon à gravité",
"item.antarchy.miners_dream": "Rêve du mineur",
"tooltip.antarchy.miners_dream.line1": "Clic droit pour creuser une grotte sinueuse.",
"tooltip.antarchy.miners_dream.line2": "Laisse les blocs précieux intacts.",
"message.antarchy.miners_dream_disabled": "Le Rêve du mineur est désactivé.",
"message.antarchy.miners_dream_busy": "Votre rêve précédent est encore en train de se déployer.",
"item.antarchy.diamond_minecart": "Le wagonnet de diamant",
"item.antarchy.cogwheel_pet": "Familier engrenage",
"item.antarchy.fire_dragon_pet": "Familier dragon de feu",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "Fai clic destro su una Reverie con una bottiglia di vetro per catturarla.",
"tooltip.antarchy.reverie_bottle.release": "Fai clic destro per liberare la Reverie.",
"item.antarchy.gravity_gun": "Cannone gravitazionale",
"item.antarchy.miners_dream": "Sogno del minatore",
"tooltip.antarchy.miners_dream.line1": "Fai clic destro per scavare una grotta tortuosa.",
"tooltip.antarchy.miners_dream.line2": "Lascia intatti i blocchi preziosi.",
"message.antarchy.miners_dream_disabled": "Il Sogno del minatore è disattivato.",
"message.antarchy.miners_dream_busy": "Il tuo sogno precedente si sta ancora dispiegando.",
"item.antarchy.diamond_minecart": "Il carrello di diamante",
"item.antarchy.cogwheel_pet": "Famiglio ingranaggio",
"item.antarchy.fire_dragon_pet": "Famiglio drago di fuoco",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "ガラス瓶を持ってレヴェリーを右クリックすると収納できます。",
"tooltip.antarchy.reverie_bottle.release": "右クリックでレヴェリーを解放します。",
"item.antarchy.gravity_gun": "重力銃",
"item.antarchy.miners_dream": "採掘者の夢",
"tooltip.antarchy.miners_dream.line1": "右クリックで曲がりくねった洞窟を掘ります。",
"tooltip.antarchy.miners_dream.line2": "貴重なブロックはそのまま残します。",
"message.antarchy.miners_dream_disabled": "採掘者の夢は無効になっています。",
"message.antarchy.miners_dream_busy": "前の夢はまだ展開中です。",
"item.antarchy.diamond_minecart": "ダイヤモンドのトロッコ",
"item.antarchy.cogwheel_pet": "歯車ペット",
"item.antarchy.fire_dragon_pet": "ファイアドラゴンのペット",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "유리병으로 레버리를 우클릭하여 담습니다.",
"tooltip.antarchy.reverie_bottle.release": "우클릭하여 레버리를 풀어줍니다.",
"item.antarchy.gravity_gun": "중력총",
"item.antarchy.miners_dream": "광부의 꿈",
"tooltip.antarchy.miners_dream.line1": "우클릭하여 구불구불한 동굴을 뚫습니다.",
"tooltip.antarchy.miners_dream.line2": "귀중한 블록은 건드리지 않습니다.",
"message.antarchy.miners_dream_disabled": "광부의 꿈이 비활성화되어 있습니다.",
"message.antarchy.miners_dream_busy": "이전 꿈이 아직 펼쳐지고 있습니다.",
"item.antarchy.diamond_minecart": "다이아몬드 광산 수레",
"item.antarchy.cogwheel_pet": "톱니바퀴 펫",
"item.antarchy.fire_dragon_pet": "화염 드래곤 펫",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "Clique com o botão direito em uma Reverie com um frasco de vidro para guardá-la.",
"tooltip.antarchy.reverie_bottle.release": "Clique com o botão direito para soltar a Reverie.",
"item.antarchy.gravity_gun": "Arma Gravitacional",
"item.antarchy.miners_dream": "Sonho do Minerador",
"tooltip.antarchy.miners_dream.line1": "Clique com o botão direito para escavar uma caverna sinuosa.",
"tooltip.antarchy.miners_dream.line2": "Deixa blocos valiosos intactos.",
"message.antarchy.miners_dream_disabled": "O Sonho do Minerador está desativado.",
"message.antarchy.miners_dream_busy": "Seu sonho anterior ainda está se desenrolando.",
"item.antarchy.diamond_minecart": "O Carrinho de Diamante",
"item.antarchy.cogwheel_pet": "Mascote de Engrenagem",
"item.antarchy.fire_dragon_pet": "Mascote de Dragão de Fogo",
@@ -387,11 +387,6 @@
"tooltip.antarchy.reverie_bottle.capture": "Нажмите ПКМ стеклянной бутылочкой по грёзе, чтобы поймать её.",
"tooltip.antarchy.reverie_bottle.release": "Нажмите ПКМ, чтобы выпустить грёзу.",
"item.antarchy.gravity_gun": "Гравитационная пушка",
"item.antarchy.miners_dream": "Мечта шахтёра",
"tooltip.antarchy.miners_dream.line1": "Нажмите ПКМ, чтобы вырезать извилистую пещеру.",
"tooltip.antarchy.miners_dream.line2": "Оставляет ценные блоки нетронутыми.",
"message.antarchy.miners_dream_disabled": "Мечта шахтёра отключена.",
"message.antarchy.miners_dream_busy": "Ваша предыдущая мечта всё ещё воплощается.",
"item.antarchy.diamond_minecart": "Алмазная вагонетка",
"item.antarchy.cogwheel_pet": "Шестерёнка-питомец",
"item.antarchy.fire_dragon_pet": "Огненный дракон-питомец",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "手持玻璃瓶右键雷弗里即可将其收纳。",
"tooltip.antarchy.reverie_bottle.release": "右键释放雷弗里。",
"item.antarchy.gravity_gun": "重力枪",
"item.antarchy.miners_dream": "矿工之梦",
"tooltip.antarchy.miners_dream.line1": "右键挖出一条蜿蜒洞穴。",
"tooltip.antarchy.miners_dream.line2": "不会破坏贵重方块。",
"message.antarchy.miners_dream_disabled": "矿工之梦已禁用。",
"message.antarchy.miners_dream_busy": "你之前的梦仍在展开。",
"item.antarchy.diamond_minecart": "钻石矿车",
"item.antarchy.cogwheel_pet": "齿轮宠物",
"item.antarchy.fire_dragon_pet": "火龙宠物",
@@ -411,11 +411,6 @@
"tooltip.antarchy.reverie_bottle.capture": "手持玻璃瓶右鍵雷弗裡即可將其收納。",
"tooltip.antarchy.reverie_bottle.release": "右鍵釋放雷弗裡。",
"item.antarchy.gravity_gun": "重力槍",
"item.antarchy.miners_dream": "礦工之夢",
"tooltip.antarchy.miners_dream.line1": "右鍵挖出一條蜿蜒洞穴。",
"tooltip.antarchy.miners_dream.line2": "不會破壞貴重方塊。",
"message.antarchy.miners_dream_disabled": "礦工之夢已禁用。",
"message.antarchy.miners_dream_busy": "你之前的夢仍在展開。",
"item.antarchy.diamond_minecart": "鑽石礦車",
"item.antarchy.cogwheel_pet": "齒輪寵物",
"item.antarchy.fire_dragon_pet": "火龍寵物",
@@ -1,6 +0,0 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "antarchy:item/miners_dream"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 B

@@ -1,19 +0,0 @@
{
"type": "minecraft:crafting_shaped",
"category": "misc",
"pattern": [
"GGG",
"RDR",
"TTT"
],
"key": {
"G": { "item": "antarchy:triffid_goo" },
"R": { "item": "minecraft:redstone_block" },
"D": { "item": "minecraft:diamond_pickaxe" },
"T": { "item": "minecraft:tnt" }
},
"result": {
"id": "antarchy:miners_dream",
"count": 1
}
}
@@ -1,19 +0,0 @@
{
"type": "minecraft:crafting_shaped",
"category": "misc",
"pattern": [
"GGG",
"RDR",
"TTT"
],
"key": {
"G": { "item": "antarchy:triffid_goo" },
"R": { "item": "minecraft:redstone_block" },
"D": { "item": "minecraft:diamond_pickaxe" },
"T": { "item": "minecraft:tnt" }
},
"result": {
"item": "antarchy:miners_dream",
"count": 1
}
}
@@ -1,36 +0,0 @@
{
"replace": false,
"values": [
"#minecraft:base_stone_overworld",
"#minecraft:base_stone_nether",
"minecraft:stone",
"minecraft:deepslate",
"minecraft:granite",
"minecraft:diorite",
"minecraft:andesite",
"minecraft:tuff",
"minecraft:calcite",
"minecraft:netherrack",
"minecraft:blackstone",
"minecraft:basalt",
"minecraft:smooth_basalt",
"minecraft:end_stone",
"minecraft:dirt",
"minecraft:coarse_dirt",
"minecraft:rooted_dirt",
"minecraft:podzol",
"minecraft:mycelium",
"minecraft:gravel",
"minecraft:sand",
"minecraft:red_sand",
"minecraft:sandstone",
"minecraft:red_sandstone",
"minecraft:clay",
"minecraft:soul_sand",
"minecraft:soul_soil",
"antarchy:nyxite",
"antarchy:pale_nyxite",
"antarchy:dream_sand",
"antarchy:dream_sandstone"
]
}
@@ -1,36 +0,0 @@
{
"replace": false,
"values": [
"#minecraft:base_stone_overworld",
"#minecraft:base_stone_nether",
"minecraft:stone",
"minecraft:deepslate",
"minecraft:granite",
"minecraft:diorite",
"minecraft:andesite",
"minecraft:tuff",
"minecraft:calcite",
"minecraft:netherrack",
"minecraft:blackstone",
"minecraft:basalt",
"minecraft:smooth_basalt",
"minecraft:end_stone",
"minecraft:dirt",
"minecraft:coarse_dirt",
"minecraft:rooted_dirt",
"minecraft:podzol",
"minecraft:mycelium",
"minecraft:gravel",
"minecraft:sand",
"minecraft:red_sand",
"minecraft:sandstone",
"minecraft:red_sandstone",
"minecraft:clay",
"minecraft:soul_sand",
"minecraft:soul_soil",
"antarchy:nyxite",
"antarchy:pale_nyxite",
"antarchy:dream_sand",
"antarchy:dream_sandstone"
]
}
@@ -137,6 +137,14 @@ public final class AntarchyConfigCatalog {
|| name.equals("elythiaPortalEnabled")
|| name.equals("thoraxisPortalEnabled")
|| name.equals("cavarynPortalEnabled")
|| name.startsWith("portalGunCanFireThrough")
|| name.startsWith("portalGunSeeThrough")
|| name.startsWith("portalGunFancy")
|| name.equals("portalGunCanPortalsResizeWhenCreated")
|| name.equals("portalGunCanPortalProjectilesChunkload")
|| name.equals("portalGunMaxShootDistance")
|| name.equals("portalGunEntityGrabWeightBase")
|| name.equals("portalGunIndicatorSize")
|| name.equals("hushweedSporeLifetimeSeconds")
|| name.equals("elythiaFireflyParticlesEnabled")
|| name.equals("duplicatorTreeEnabled")
@@ -177,7 +185,6 @@ public final class AntarchyConfigCatalog {
|| name.equals("invertProjectilesFromInvertedPlayers")
|| name.startsWith("gravityGun")
|| name.startsWith("critterCage")
|| name.startsWith("minersDream")
|| name.equals("potentNyxiteInvertedDurationSeconds")
|| name.equals("corneaEarNightVisionSeconds")
|| name.startsWith("american")
@@ -167,11 +167,6 @@ public final class AntarchySettings {
private static double gravityGunMaxHoldDistance = 10.0D;
private static double critterCageMaxCapturableWidth = 1.65D;
private static double critterCageMaxCapturableHeight = 1.65D;
private static boolean minersDreamEnabled = true;
private static int minersDreamMinimumRange = 48;
private static int minersDreamMaximumRange = 96;
private static int minersDreamTorchSpacing = 10;
private static int minersDreamBlocksPerTick = 150;
private static double lucidAttackRange = 9.0D;
private static double lucidPearlInvertedDurationSeconds = 6.0D;
private static int corneaEarNightVisionSeconds = 15;
@@ -554,6 +549,15 @@ public final class AntarchySettings {
private static boolean elythiaPortalEnabled = true;
private static boolean thoraxisPortalEnabled = true;
private static boolean cavarynPortalEnabled = true;
private static boolean portalGunCanFireThroughGlass = false;
private static boolean portalGunCanFireThroughLiquid = false;
private static boolean portalGunCanPortalsResizeWhenCreated = true;
private static boolean portalGunCanPortalProjectilesChunkload = true;
private static boolean portalGunSeeThroughPortals = true;
private static boolean portalGunFancyPortals = true;
private static int portalGunIndicatorSize = 30;
private static int portalGunMaxShootDistance = 10000;
private static int portalGunEntityGrabWeightBase = 100;
private static boolean elythiaFireflyParticlesEnabled = true;
private static int bloodCrystalArmorShieldRechargeTicks = 600;
private static int bloodCrystalAppleShieldCount = 2;
@@ -1251,26 +1255,6 @@ public final class AntarchySettings {
return critterCageMaxCapturableHeight;
}
public static boolean minersDreamEnabled() {
return minersDreamEnabled;
}
public static int minersDreamMinimumRange() {
return minersDreamMinimumRange;
}
public static int minersDreamMaximumRange() {
return minersDreamMaximumRange;
}
public static int minersDreamTorchSpacing() {
return minersDreamTorchSpacing;
}
public static int minersDreamBlocksPerTick() {
return minersDreamBlocksPerTick;
}
public static void setDisableInfinityBookPortalCreation(boolean value) {
disableInfinityBookPortalCreation = value;
}
@@ -1327,11 +1311,56 @@ public final class AntarchySettings {
cavarynPortalEnabled = value;
}
public static void setPortalGunCanFireThroughGlass(boolean value) {
portalGunCanFireThroughGlass = value;
}
public static void setPortalGunCanFireThroughLiquid(boolean value) {
portalGunCanFireThroughLiquid = value;
}
public static void setPortalGunCanPortalsResizeWhenCreated(boolean value) {
portalGunCanPortalsResizeWhenCreated = value;
}
public static void setPortalGunCanPortalProjectilesChunkload(boolean value) {
portalGunCanPortalProjectilesChunkload = value;
}
public static void setPortalGunSeeThroughPortals(boolean value) {
portalGunSeeThroughPortals = value;
}
public static void setPortalGunFancyPortals(boolean value) {
portalGunFancyPortals = value;
}
public static void setPortalGunIndicatorSize(int value) {
portalGunIndicatorSize = Math.max(0, Math.min(100, value));
}
public static void setPortalGunMaxShootDistance(int value) {
portalGunMaxShootDistance = Math.max(1, value);
}
public static void setPortalGunEntityGrabWeightBase(int value) {
portalGunEntityGrabWeightBase = Math.max(0, value);
}
public static boolean permanentPortalsEnabled() { return permanentPortalsEnabled; }
public static boolean permanentPortalsFlintAndSteelEnabled() { return permanentPortalsFlintAndSteelEnabled; }
public static boolean elythiaPortalEnabled() { return elythiaPortalEnabled; }
public static boolean thoraxisPortalEnabled() { return thoraxisPortalEnabled; }
public static boolean cavarynPortalEnabled() { return cavarynPortalEnabled; }
public static boolean portalGunCanFireThroughGlass() { return portalGunCanFireThroughGlass; }
public static boolean portalGunCanFireThroughLiquid() { return portalGunCanFireThroughLiquid; }
public static boolean portalGunCanPortalsResizeWhenCreated() { return portalGunCanPortalsResizeWhenCreated; }
public static boolean portalGunCanPortalProjectilesChunkload() { return portalGunCanPortalProjectilesChunkload; }
public static boolean portalGunSeeThroughPortals() { return portalGunSeeThroughPortals; }
public static boolean portalGunFancyPortals() { return portalGunFancyPortals; }
public static int portalGunIndicatorSize() { return portalGunIndicatorSize; }
public static int portalGunMaxShootDistance() { return portalGunMaxShootDistance; }
public static int portalGunEntityGrabWeightBase() { return portalGunEntityGrabWeightBase; }
public static void setBrownAntRequiresReagent(boolean value) {
brownAntRequiresReagent = value;
@@ -1809,26 +1838,6 @@ public final class AntarchySettings {
critterCageMaxCapturableHeight = value;
}
public static void setMinersDreamEnabled(boolean value) {
minersDreamEnabled = value;
}
public static void setMinersDreamMinimumRange(int value) {
minersDreamMinimumRange = value;
}
public static void setMinersDreamMaximumRange(int value) {
minersDreamMaximumRange = value;
}
public static void setMinersDreamTorchSpacing(int value) {
minersDreamTorchSpacing = value;
}
public static void setMinersDreamBlocksPerTick(int value) {
minersDreamBlocksPerTick = value;
}
public static double lucidAttackRange() {
return lucidAttackRange;
}
@@ -41,7 +41,6 @@ public final class AntarchyTags {
public static final TagKey<Block> CAVARYN_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("cavaryn_portal_frames"));
public static final TagKey<Block> RAINBOW_PORTAL_FRAMES = TagKey.create(Registries.BLOCK, id("rainbow_portal_frames"));
public static final TagKey<Block> ROLLY_CAVERN_CARVEABLE = TagKey.create(Registries.BLOCK, id("rolly_cavern_carveable"));
public static final TagKey<Block> MINERS_DREAM_CARVABLE = TagKey.create(Registries.BLOCK, id("miners_dream_carvable"));
public static final TagKey<Block> BLUESTONE_COMPONENTS = TagKey.create(Registries.BLOCK, id("bluestone_components"));
public static final TagKey<Block> BLUESTONE_CONNECTABLE = TagKey.create(Registries.BLOCK, id("bluestone_connectable"));
public static final TagKey<Block> ANTIMETAL_INVERSION_BLOCKS = TagKey.create(Registries.BLOCK, id("antimetal_inversion_blocks"));
@@ -1,6 +1,8 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import java.util.EnumMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
@@ -14,7 +16,8 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
public class PortalGunPortalBaseBlockEntity extends BlockEntity {
public class PortalGunPortalBaseBlockEntity extends BlockEntity implements PortalGunPortalCellAccess {
private final Map<net.minecraft.core.Direction, PortalGunPortalFaceRecord> faceRecords = new EnumMap<>(net.minecraft.core.Direction.class);
private UUID ownerId;
private UUID portalId;
private PortalGunPortalEntity.PortalSide side = PortalGunPortalEntity.PortalSide.BLUE;
@@ -26,6 +29,11 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity {
super(typeSupplier.get(), pos, state);
}
@Override
public Map<net.minecraft.core.Direction, PortalGunPortalFaceRecord> portalGun$getFaceRecords() {
return this.faceRecords;
}
public void configure(UUID ownerId, UUID portalId, PortalGunPortalEntity.PortalSide side, BlockPos masterPos) {
this.ownerId = ownerId;
this.portalId = portalId;
@@ -61,6 +69,11 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity {
public void updatePair(UUID linkedPortalId, int pairTime) {
this.linkedPortalId = linkedPortalId;
this.pairTime = pairTime;
for (PortalGunPortalFaceRecord record : java.util.List.copyOf(this.faceRecords.values())) {
if (record.portalId().equals(this.portalId)) {
this.portalGun$putFaceRecord(record.withPair(linkedPortalId, pairTime));
}
}
this.setChanged();
}
@@ -69,27 +82,49 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity {
}
public void onBroken() {
if (!(this.level instanceof ServerLevel serverLevel) || this.portalId == null) {
if (!(this.level instanceof ServerLevel serverLevel)) {
return;
}
if (serverLevel.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
java.util.Set<UUID> portalIds = new java.util.HashSet<>();
if (this.portalId != null) {
portalIds.add(this.portalId);
}
for (PortalGunPortalFaceRecord record : this.faceRecords.values()) {
portalIds.add(record.portalId());
}
for (UUID id : portalIds) {
if (serverLevel.getEntity(id) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
portal.discard();
}
}
}
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, PortalGunPortalBaseBlockEntity blockEntity) {
if (((level.getGameTime() + pos.asLong()) & 15L) != 0L) {
return;
}
if (level.getBlockEntity(blockEntity.masterPos) instanceof PortalGunPortalMasterBlockEntity master
&& master.matches(blockEntity.ownerId, blockEntity.portalId, blockEntity.side)) {
PortalGunPortalFaceRecord masterRecord = master.portalGun$getFaceRecord(master.getFacing(), blockEntity.portalId);
if (masterRecord != null && blockEntity.portalGun$getFaceRecord(masterRecord.face(), masterRecord.portalId()) == null) {
blockEntity.portalGun$putFaceRecord(masterRecord.withMaster(false));
}
}
blockEntity.portalGun$restoreMasterFaces(level, pos);
if (blockEntity.portalId != null
&& !(level.getEntity(blockEntity.portalId) instanceof PortalGunPortalEntity)
&& blockEntity.faceRecords.values().stream().anyMatch(record -> !record.portalId().equals(blockEntity.portalId))) {
return;
}
if (!(level.getBlockEntity(blockEntity.masterPos) instanceof PortalGunPortalMasterBlockEntity master)
|| !master.matches(blockEntity.ownerId, blockEntity.portalId, blockEntity.side)
|| !pos.equals(master.getBasePos())) {
|| !master.containsPortalSpot(pos)) {
level.removeBlock(pos, false);
return;
}
if (!(level.getEntity(blockEntity.portalId) instanceof PortalGunPortalEntity portal)
|| portal.isRemoved()
|| !pos.equals(portal.getBasePos())
|| !portal.containsPortalSpot(pos)
|| blockEntity.ownerId == null
|| !blockEntity.ownerId.equals(portal.getOwnerId())
|| blockEntity.side != portal.getPortalSide()) {
@@ -123,6 +158,7 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity {
tag.putInt("MasterY", this.masterPos.getY());
tag.putInt("MasterZ", this.masterPos.getZ());
tag.putInt("PairTime", this.pairTime);
this.portalGun$saveFaceRecords(tag);
}
@Override
@@ -134,6 +170,7 @@ public class PortalGunPortalBaseBlockEntity extends BlockEntity {
this.side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE;
this.masterPos = new BlockPos(tag.getInt("MasterX"), tag.getInt("MasterY"), tag.getInt("MasterZ"));
this.pairTime = tag.getInt("PairTime");
this.portalGun$loadFaceRecords(tag);
}
@Override
@@ -0,0 +1,118 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement;
import com.craisinlord.antarchy.content.portalgun.PortalGunSavedData;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
public interface PortalGunPortalCellAccess {
Map<Direction, PortalGunPortalFaceRecord> portalGun$getFaceRecords();
default PortalGunPortalFaceRecord portalGun$getFaceRecord(Direction face) {
return this.portalGun$getFaceRecords().get(face);
}
default PortalGunPortalFaceRecord portalGun$getFaceRecord(Direction face, UUID portalId) {
PortalGunPortalFaceRecord record = this.portalGun$getFaceRecord(face);
return record != null && record.portalId().equals(portalId) ? record : null;
}
default boolean portalGun$putFaceRecord(PortalGunPortalFaceRecord record) {
PortalGunPortalFaceRecord existing = this.portalGun$getFaceRecord(record.face());
if (existing != null
&& (!existing.ownerId().equals(record.ownerId())
|| !java.util.Objects.equals(existing.channelName(), record.channelName())
|| existing.side() != record.side())) {
return false;
}
this.portalGun$getFaceRecords().put(record.face(), record);
if (this instanceof net.minecraft.world.level.block.entity.BlockEntity blockEntity) {
blockEntity.setChanged();
}
return true;
}
default PortalGunPortalFaceRecord portalGun$removeFaceRecord(Direction face, UUID portalId) {
PortalGunPortalFaceRecord existing = this.portalGun$getFaceRecord(face, portalId);
if (existing == null) {
return null;
}
PortalGunPortalFaceRecord removed = this.portalGun$getFaceRecords().remove(face);
if (this instanceof net.minecraft.world.level.block.entity.BlockEntity blockEntity) {
blockEntity.setChanged();
}
return removed;
}
default void portalGun$updateFacePair(UUID portalId, UUID linkedPortalId, int pairTime) {
for (PortalGunPortalFaceRecord record : java.util.List.copyOf(this.portalGun$getFaceRecords().values())) {
if (record.portalId().equals(portalId)) {
this.portalGun$putFaceRecord(record.withPair(linkedPortalId, pairTime));
}
}
}
default void portalGun$restoreMasterFaces(ServerLevel level, BlockPos pos) {
for (PortalGunPortalFaceRecord record : java.util.List.copyOf(this.portalGun$getFaceRecords().values())) {
if (record.ownerId() == null || record.portalId() == null) {
continue;
}
if (!PortalGunSavedData.isRegistered(level, record.ownerId(), record.gunId(), record.channelName(), record.side(), record.portalId())) {
this.portalGun$removeFaceRecord(record.face(), record.portalId());
continue;
}
if (!record.master() || !record.masterPos().equals(pos)) {
continue;
}
boolean complete = !record.portalSpots().isEmpty() && record.portalSpots().getFirst().equals(record.masterPos());
for (int i = 0; complete && i < record.portalSpots().size(); i++) {
BlockPos spot = record.portalSpots().get(i);
BlockPos support = spot.relative(record.face().getOpposite());
if (!level.hasChunkAt(spot) || !level.hasChunkAt(support)
|| !level.getBlockState(support).isFaceSturdy(level, support, record.face())
|| !(level.getBlockEntity(spot) instanceof PortalGunPortalCellAccess cell)) {
complete = false;
continue;
}
PortalGunPortalFaceRecord footprintRecord = cell.portalGun$getFaceRecord(record.face(), record.portalId());
complete = footprintRecord != null && footprintRecord.master() == (i == 0);
}
complete = complete && PortalGunPlacement.isRectangularFootprint(
record.portalSpots().toArray(BlockPos[]::new),
record.face(),
record.upAxis(),
record.portalWidth(),
record.portalHeight()
);
if (complete) {
PortalGunPortalMasterBlockEntity.restorePortalEntity(level, record);
}
}
}
default void portalGun$saveFaceRecords(CompoundTag tag) {
ListTag faces = new ListTag();
for (PortalGunPortalFaceRecord record : this.portalGun$getFaceRecords().values()) {
faces.add(record.save());
}
tag.put("PortalFaces", faces);
}
default void portalGun$loadFaceRecords(CompoundTag tag) {
Map<Direction, PortalGunPortalFaceRecord> records = this.portalGun$getFaceRecords();
records.clear();
ListTag faces = tag.getList("PortalFaces", Tag.TAG_COMPOUND);
for (int i = 0; i < faces.size(); i++) {
PortalGunPortalFaceRecord record = PortalGunPortalFaceRecord.load(faces.getCompound(i));
if (record != null) {
records.put(record.face(), record);
}
}
}
}
@@ -0,0 +1,151 @@
package com.craisinlord.antarchy.content.block.entity;
import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.Tag;
public record PortalGunPortalFaceRecord(
UUID ownerId,
String ownerIdentity,
UUID gunId,
String channelName,
UUID portalId,
UUID linkedPortalId,
PortalGunPortalEntity.PortalSide side,
Direction face,
Direction upAxis,
BlockPos masterPos,
BlockPos basePos,
List<BlockPos> portalSpots,
Set<BlockPos> compensatedSpots,
int pairTime,
int portalWidth,
int portalHeight,
boolean master
) {
public PortalGunPortalFaceRecord {
masterPos = masterPos.immutable();
basePos = basePos.immutable();
portalSpots = portalSpots.stream().map(BlockPos::immutable).toList();
compensatedSpots = Set.copyOf(compensatedSpots);
}
public PortalGunPlacement placement() {
return PortalGunPlacement.fromStored(this.face, this.upAxis, this.masterPos, this.basePos, this.portalSpots.toArray(BlockPos[]::new), this.compensatedSpots, this.portalWidth, this.portalHeight);
}
public PortalGunPortalFaceRecord withPair(UUID linkedPortalId, int pairTime) {
return new PortalGunPortalFaceRecord(this.ownerId, this.ownerIdentity, this.gunId, this.channelName, this.portalId, linkedPortalId, this.side, this.face, this.upAxis, this.masterPos, this.basePos, this.portalSpots, this.compensatedSpots, pairTime, this.portalWidth, this.portalHeight, this.master);
}
public PortalGunPortalFaceRecord withMaster(boolean master) {
return new PortalGunPortalFaceRecord(this.ownerId, this.ownerIdentity, this.gunId, this.channelName, this.portalId, this.linkedPortalId, this.side, this.face, this.upAxis, this.masterPos, this.basePos, this.portalSpots, this.compensatedSpots, this.pairTime, this.portalWidth, this.portalHeight, master);
}
public CompoundTag save() {
CompoundTag tag = new CompoundTag();
tag.putUUID("OwnerId", this.ownerId);
tag.putString("OwnerIdentity", this.ownerIdentity == null ? "" : this.ownerIdentity);
if (this.gunId != null) {
tag.putUUID("GunId", this.gunId);
}
tag.putString("ChannelName", this.channelName == null ? "" : this.channelName);
tag.putUUID("PortalId", this.portalId);
if (this.linkedPortalId != null) {
tag.putUUID("LinkedPortalId", this.linkedPortalId);
}
tag.putInt("Side", this.side.ordinal());
tag.putInt("Face", this.face.get3DDataValue());
tag.putInt("UpAxis", this.upAxis.get3DDataValue());
putPos(tag, "Master", this.masterPos);
putPos(tag, "Base", this.basePos);
tag.putInt("PairTime", this.pairTime);
tag.putInt("PortalWidth", this.portalWidth);
tag.putInt("PortalHeight", this.portalHeight);
tag.putBoolean("MasterRole", this.master);
ListTag spots = new ListTag();
for (BlockPos pos : this.portalSpots) {
spots.add(savePos(pos));
}
tag.put("PortalSpots", spots);
ListTag compensated = new ListTag();
for (BlockPos pos : this.compensatedSpots) {
compensated.add(savePos(pos));
}
tag.put("CompensatedSpots", compensated);
return tag;
}
public static PortalGunPortalFaceRecord load(CompoundTag tag) {
if (!tag.hasUUID("OwnerId") || !tag.hasUUID("PortalId")) {
return null;
}
ListTag spotsTag = tag.getList("PortalSpots", Tag.TAG_COMPOUND);
List<BlockPos> spots = new java.util.ArrayList<>(spotsTag.size());
for (int i = 0; i < spotsTag.size(); i++) {
spots.add(loadPos(spotsTag.getCompound(i)));
}
ListTag compensatedTag = tag.getList("CompensatedSpots", Tag.TAG_COMPOUND);
Set<BlockPos> compensated = new HashSet<>();
for (int i = 0; i < compensatedTag.size(); i++) {
compensated.add(loadPos(compensatedTag.getCompound(i)));
}
PortalGunPortalEntity.PortalSide side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal()
? PortalGunPortalEntity.PortalSide.ORANGE
: PortalGunPortalEntity.PortalSide.BLUE;
int width = net.minecraft.util.Mth.clamp(tag.getInt("PortalWidth"), 1, 16);
int height = net.minecraft.util.Mth.clamp(tag.getInt("PortalHeight"), 2, 16);
return new PortalGunPortalFaceRecord(
tag.getUUID("OwnerId"),
tag.getString("OwnerIdentity"),
tag.hasUUID("GunId") ? tag.getUUID("GunId") : null,
tag.getString("ChannelName"),
tag.getUUID("PortalId"),
tag.hasUUID("LinkedPortalId") ? tag.getUUID("LinkedPortalId") : null,
side,
Direction.from3DDataValue(tag.getInt("Face")),
Direction.from3DDataValue(tag.getInt("UpAxis")),
getPos(tag, "Master"),
getPos(tag, "Base"),
spots,
compensated,
tag.getInt("PairTime"),
width,
height,
tag.getBoolean("MasterRole")
);
}
private static void putPos(CompoundTag tag, String key, BlockPos pos) {
CompoundTag value = new CompoundTag();
value.putInt("X", pos.getX());
value.putInt("Y", pos.getY());
value.putInt("Z", pos.getZ());
tag.put(key, value);
}
private static BlockPos getPos(CompoundTag tag, String key) {
CompoundTag value = tag.getCompound(key);
return new BlockPos(value.getInt("X"), value.getInt("Y"), value.getInt("Z"));
}
private static CompoundTag savePos(BlockPos pos) {
CompoundTag tag = new CompoundTag();
tag.putInt("X", pos.getX());
tag.putInt("Y", pos.getY());
tag.putInt("Z", pos.getZ());
return tag;
}
private static BlockPos loadPos(CompoundTag tag) {
return new BlockPos(tag.getInt("X"), tag.getInt("Y"), tag.getInt("Z"));
}
}
@@ -5,8 +5,10 @@ import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement;
import com.craisinlord.antarchy.content.portalgun.PortalGunSavedData;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
@@ -30,14 +32,20 @@ import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
public class PortalGunPortalMasterBlockEntity extends BlockEntity {
public class PortalGunPortalMasterBlockEntity extends BlockEntity implements PortalGunPortalCellAccess {
private final Map<Direction, PortalGunPortalFaceRecord> faceRecords = new EnumMap<>(Direction.class);
private UUID ownerId;
private String ownerIdentity;
private UUID gunId;
private String channelName;
private UUID portalId;
private PortalGunPortalEntity.PortalSide side = PortalGunPortalEntity.PortalSide.BLUE;
private Direction facing = Direction.NORTH;
private Direction upAxis = Direction.UP;
private BlockPos basePos = BlockPos.ZERO;
private BlockPos[] portalSpots = new BlockPos[] {BlockPos.ZERO, BlockPos.ZERO};
private int portalWidth = 1;
private int portalHeight = 2;
private Set<BlockPos> compensatedSpots = Set.of();
private UUID linkedPortalId;
private int pairTime;
@@ -48,6 +56,9 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
public void configure(
UUID ownerId,
UUID gunId,
String ownerIdentity,
String channelName,
UUID portalId,
PortalGunPortalEntity.PortalSide side,
Direction facing,
@@ -55,20 +66,33 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
BlockPos basePos,
BlockPos[] portalSpots,
Set<BlockPos> compensatedSpots,
int pairTime
int pairTime,
int portalWidth,
int portalHeight
) {
this.ownerId = ownerId;
this.ownerIdentity = ownerIdentity;
this.gunId = gunId;
this.channelName = channelName;
this.portalId = portalId;
this.side = side;
this.facing = facing;
this.upAxis = upAxis;
this.basePos = basePos.immutable();
this.portalSpots = new BlockPos[] {portalSpots[0].immutable(), portalSpots[1].immutable()};
this.portalSpots = java.util.Arrays.stream(portalSpots).map(BlockPos::immutable).toArray(BlockPos[]::new);
this.portalWidth = portalWidth;
this.portalHeight = portalHeight;
this.compensatedSpots = new HashSet<>(compensatedSpots);
this.pairTime = pairTime;
this.portalGun$putFaceRecord(new PortalGunPortalFaceRecord(ownerId, ownerIdentity, gunId, channelName, portalId, this.linkedPortalId, side, facing, upAxis, this.worldPosition, this.basePos, List.of(this.portalSpots), this.compensatedSpots, pairTime, portalWidth, portalHeight, true));
this.setChanged();
}
@Override
public Map<Direction, PortalGunPortalFaceRecord> portalGun$getFaceRecords() {
return this.faceRecords;
}
public UUID getPortalId() {
return this.portalId;
}
@@ -77,6 +101,36 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
return this.ownerId;
}
public UUID getGunId() {
return this.gunId;
}
public String getChannelName() {
return this.channelName;
}
public void adoptGunId(UUID gunId) {
if (this.gunId == null && gunId != null) {
this.gunId = gunId;
this.setChanged();
}
}
public void adoptChannelIdentity(UUID gunId, String channelName) {
boolean changed = false;
if (this.gunId == null && gunId != null) {
this.gunId = gunId;
changed = true;
}
if (channelName != null && !channelName.isEmpty() && !channelName.equals(this.channelName)) {
this.channelName = channelName;
changed = true;
}
if (changed) {
this.setChanged();
}
}
public PortalGunPortalEntity.PortalSide getSide() {
return this.side;
}
@@ -94,7 +148,16 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
}
public BlockPos[] getPortalSpots() {
return new BlockPos[] {this.portalSpots[0], this.portalSpots[1]};
return this.portalSpots.clone();
}
public boolean containsPortalSpot(BlockPos pos) {
for (BlockPos portalSpot : this.portalSpots) {
if (portalSpot.equals(pos)) {
return true;
}
}
return false;
}
public Set<BlockPos> getCompensatedSpots() {
@@ -112,6 +175,10 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
public void updatePair(UUID linkedPortalId, int pairTime) {
this.linkedPortalId = linkedPortalId;
this.pairTime = pairTime;
PortalGunPortalFaceRecord record = this.portalGun$getFaceRecord(this.facing, this.portalId);
if (record != null) {
this.portalGun$putFaceRecord(record.withPair(linkedPortalId, pairTime));
}
this.setChanged();
}
@@ -120,18 +187,33 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
}
public void onBroken() {
if (!(this.level instanceof ServerLevel serverLevel) || this.portalId == null) {
if (!(this.level instanceof ServerLevel serverLevel)) {
return;
}
if (serverLevel.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
Set<UUID> portalIds = new HashSet<>();
if (this.portalId != null) {
portalIds.add(this.portalId);
}
for (PortalGunPortalFaceRecord record : this.faceRecords.values()) {
portalIds.add(record.portalId());
}
for (UUID id : portalIds) {
if (serverLevel.getEntity(id) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
portal.discard();
}
}
}
public static void serverTick(ServerLevel level, BlockPos pos, BlockState state, PortalGunPortalMasterBlockEntity blockEntity) {
if (((level.getGameTime() + pos.asLong()) & 15L) != 0L) {
return;
}
blockEntity.portalGun$restoreMasterFaces(level, pos);
if (blockEntity.portalId != null
&& !(level.getEntity(blockEntity.portalId) instanceof PortalGunPortalEntity)
&& blockEntity.faceRecords.values().stream().anyMatch(record -> !record.portalId().equals(blockEntity.portalId))) {
return;
}
if (!blockEntity.hasValidFootprint(level, pos)) {
level.removeBlock(pos, false);
return;
@@ -146,6 +228,12 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
level.removeBlock(pos, false);
return;
}
if (blockEntity.gunId == null && portal.getGunId() != null) {
blockEntity.adoptGunId(portal.getGunId());
} else if (portal.getGunId() == null && blockEntity.gunId != null) {
portal.adoptGunId(blockEntity.gunId);
}
blockEntity.adoptChannelIdentity(portal.getGunId(), portal.getChannelName());
blockEntity.ensureLinkedPortal(level, portal);
UUID linkedPortalId = portal.getLinkedPortalId();
if ((linkedPortalId == null && blockEntity.linkedPortalId != null)
@@ -161,16 +249,24 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
if (this.ownerId == null || this.portalId == null || !pos.equals(this.worldPosition)) {
return false;
}
if (!(level.getBlockEntity(this.basePos) instanceof PortalGunPortalBaseBlockEntity base)
|| !base.matches(this.ownerId, this.portalId, this.side)
|| !pos.equals(base.getMasterPos())) {
if (!PortalGunPlacement.isRectangularFootprint(this.portalSpots, this.facing, this.upAxis, this.portalWidth, this.portalHeight)) {
return false;
}
List<BlockPos> supportPositions = new ArrayList<>(2);
for (BlockPos portalSpot : this.portalSpots) {
List<BlockPos> supportPositions = new ArrayList<>(this.portalSpots.length);
for (int i = 0; i < this.portalSpots.length; i++) {
BlockPos portalSpot = this.portalSpots[i];
if (portalSpot == null || BlockPos.ZERO.equals(portalSpot)) {
return false;
}
if (i == 0) {
if (!portalSpot.equals(pos)) {
return false;
}
} else if (!(level.getBlockEntity(portalSpot) instanceof PortalGunPortalBaseBlockEntity base)
|| !base.matches(this.ownerId, this.portalId, this.side)
|| !pos.equals(base.getMasterPos())) {
return false;
}
supportPositions.add(portalSpot.relative(this.facing.getOpposite()));
}
for (BlockPos supportPos : supportPositions) {
@@ -186,23 +282,60 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
if (this.portalId == null || this.ownerId == null) {
return null;
}
if (!PortalGunSavedData.isRegistered(level, this.ownerId, this.gunId, this.channelName, this.side, this.portalId)) {
return null;
}
if (level.getEntity(this.portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
return portal;
}
PortalGunPortalFaceRecord record = this.portalGun$getFaceRecord(this.facing, this.portalId);
if (record != null) {
return restorePortalEntity(level, record);
}
EntityType<?> rawType = BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_portal"));
if (!(rawType instanceof EntityType<?>)) {
return null;
}
PortalGunPlacement placement = PortalGunPlacement.fromStored(this.facing, this.upAxis, this.worldPosition, this.basePos, this.compensatedSpots);
PortalGunPlacement placement = PortalGunPlacement.fromStored(this.facing, this.upAxis, this.worldPosition, this.basePos, this.portalSpots, this.compensatedSpots, this.portalWidth, this.portalHeight);
@SuppressWarnings("unchecked")
PortalGunPortalEntity restored = new PortalGunPortalEntity((EntityType<? extends PortalGunPortalEntity>) rawType, level);
restored.setUUID(this.portalId);
restored.configure(this.ownerId, this.side, placement);
restored.configure(this.ownerId, this.gunId, this.ownerIdentity == null ? String.valueOf(this.ownerId) : this.ownerIdentity, this.channelName == null ? String.valueOf(this.gunId) : this.channelName, this.side, placement);
restored.restorePair(this.linkedPortalId, this.pairTime);
Vec3 center = placement.center();
restored.moveTo(center.x, center.y, center.z, placement.yaw(), 0.0F);
level.addFreshEntity(restored);
PortalGunSavedData.setPortal(level.getServer(), this.ownerId, this.side, restored.getUUID());
PortalGunSavedData.setPortal(level, this.ownerId, this.gunId, this.channelName, this.side, restored.getUUID());
return restored;
}
public static PortalGunPortalEntity restorePortalEntity(ServerLevel level, PortalGunPortalFaceRecord record) {
if (record.ownerId() == null || record.portalId() == null
|| !PortalGunSavedData.isRegistered(level, record.ownerId(), record.gunId(), record.channelName(), record.side(), record.portalId())) {
return null;
}
if (level.getEntity(record.portalId()) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
return portal;
}
EntityType<?> rawType = BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_portal"));
if (rawType == null) {
return null;
}
PortalGunPlacement placement = record.placement();
@SuppressWarnings("unchecked")
PortalGunPortalEntity restored = new PortalGunPortalEntity((EntityType<? extends PortalGunPortalEntity>) rawType, level);
restored.setUUID(record.portalId());
restored.configure(record.ownerId(), record.gunId(), record.ownerIdentity(), record.channelName(), record.side(), placement);
restored.restorePair(record.linkedPortalId(), record.pairTime());
Vec3 center = placement.center();
restored.moveTo(center.x, center.y, center.z, placement.yaw(), 0.0F);
level.addFreshEntity(restored);
PortalGunSavedData.setPortal(level, record.ownerId(), record.gunId(), record.channelName(), record.side(), restored.getUUID());
PortalGunPortalEntity linked = PortalGunSavedData.findLoadedPortal(level, record.ownerId(), record.gunId(), record.channelName(), record.side() == PortalGunPortalEntity.PortalSide.BLUE ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE);
if (linked != null && linked != restored && !linked.isRemoved()) {
restored.linkTo(linked);
linked.linkTo(restored);
}
return restored;
}
@@ -211,8 +344,10 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
return;
}
PortalGunPortalEntity counterpart = PortalGunSavedData.findLoadedPortal(
level.getServer(),
level,
this.ownerId,
this.gunId,
this.channelName,
this.side == PortalGunPortalEntity.PortalSide.BLUE ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE
);
if (counterpart == null || counterpart == portal || counterpart.isRemoved()) {
@@ -230,6 +365,15 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
if (this.ownerId != null) {
tag.putUUID("OwnerId", this.ownerId);
}
if (this.ownerIdentity != null) {
tag.putString("OwnerIdentity", this.ownerIdentity);
}
if (this.gunId != null) {
tag.putUUID("GunId", this.gunId);
}
if (this.channelName != null) {
tag.putString("ChannelName", this.channelName);
}
if (this.portalId != null) {
tag.putUUID("PortalId", this.portalId);
}
@@ -243,6 +387,8 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
tag.putInt("BaseY", this.basePos.getY());
tag.putInt("BaseZ", this.basePos.getZ());
tag.putInt("PairTime", this.pairTime);
tag.putInt("PortalWidth", this.portalWidth);
tag.putInt("PortalHeight", this.portalHeight);
ListTag spots = new ListTag();
for (BlockPos portalSpot : this.portalSpots) {
CompoundTag spot = new CompoundTag();
@@ -261,12 +407,16 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
compensated.add(spot);
}
tag.put("CompensatedSpots", compensated);
this.portalGun$saveFaceRecords(tag);
}
@Override
protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
super.loadAdditional(tag, registries);
this.ownerId = tag.hasUUID("OwnerId") ? tag.getUUID("OwnerId") : null;
this.ownerIdentity = tag.contains("OwnerIdentity") ? tag.getString("OwnerIdentity") : String.valueOf(this.ownerId);
this.gunId = tag.hasUUID("GunId") ? tag.getUUID("GunId") : null;
this.channelName = tag.contains("ChannelName") ? tag.getString("ChannelName") : this.gunId == null ? "" : "Random Channel #" + this.gunId.hashCode();
this.portalId = tag.hasUUID("PortalId") ? tag.getUUID("PortalId") : null;
this.linkedPortalId = tag.hasUUID("LinkedPortalId") ? tag.getUUID("LinkedPortalId") : null;
this.side = tag.getInt("Side") == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE;
@@ -275,10 +425,15 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
this.basePos = new BlockPos(tag.getInt("BaseX"), tag.getInt("BaseY"), tag.getInt("BaseZ"));
this.pairTime = tag.getInt("PairTime");
ListTag spots = tag.getList("PortalSpots", Tag.TAG_COMPOUND);
for (int i = 0; i < Math.min(2, spots.size()); i++) {
if (!spots.isEmpty()) {
this.portalSpots = new BlockPos[spots.size()];
}
for (int i = 0; i < spots.size(); i++) {
CompoundTag spot = spots.getCompound(i);
this.portalSpots[i] = new BlockPos(spot.getInt("X"), spot.getInt("Y"), spot.getInt("Z"));
}
this.portalWidth = net.minecraft.util.Mth.clamp(tag.getInt("PortalWidth"), 1, 16);
this.portalHeight = net.minecraft.util.Mth.clamp(tag.getInt("PortalHeight"), 2, 16);
ListTag compensated = tag.getList("CompensatedSpots", Tag.TAG_COMPOUND);
Set<BlockPos> compensatedSpots = new HashSet<>();
for (int i = 0; i < compensated.size(); i++) {
@@ -286,6 +441,10 @@ public class PortalGunPortalMasterBlockEntity extends BlockEntity {
compensatedSpots.add(new BlockPos(spot.getInt("X"), spot.getInt("Y"), spot.getInt("Z")));
}
this.compensatedSpots = compensatedSpots;
this.portalGun$loadFaceRecords(tag);
if (this.faceRecords.isEmpty() && this.ownerId != null && this.portalId != null && this.portalSpots.length > 0) {
this.portalGun$putFaceRecord(new PortalGunPortalFaceRecord(this.ownerId, this.ownerIdentity, this.gunId, this.channelName, this.portalId, this.linkedPortalId, this.side, this.facing, this.upAxis, this.worldPosition, this.basePos, List.of(this.portalSpots), this.compensatedSpots, this.pairTime, this.portalWidth, this.portalHeight, true));
}
}
@Override
@@ -20,6 +20,7 @@ import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.HopperMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
@@ -119,6 +120,10 @@ public class UpperBlockEntity extends RandomizableContainerBlockEntity {
}
private static Container getContainerAt(ServerLevel level, BlockPos pos) {
BlockState state = level.getBlockState(pos);
if (state.getBlock() instanceof ChestBlock chestBlock) {
return ChestBlock.getContainer(chestBlock, state, level, pos, false);
}
BlockEntity blockEntity = level.getBlockEntity(pos);
return blockEntity instanceof Container container ? container : null;
}
@@ -0,0 +1,116 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.craisinlord.antarchy.content.client.PortalGunIndicatorClientState;
import com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB;
public final class PortalGunCrosshairRenderer {
private static final ResourceLocation CROSSHAIR = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/gui/portal_gun_crosshair.png");
private static final int WIDTH = 34;
private static final int HEIGHT = 33;
private static ClientLevel cachedLevel;
private static long cachedGameTime = Long.MIN_VALUE;
private static UUID cachedOwnerId;
private static Set<PortalKey> cachedPortals = Set.of();
private PortalGunCrosshairRenderer() {
}
public static void render(GuiGraphics guiGraphics) {
Minecraft minecraft = Minecraft.getInstance();
if (PortalGunGrabClientState.isActive()) {
return;
}
if (minecraft.player == null || minecraft.screen != null || minecraft.options.hideGui || !minecraft.options.getCameraType().isFirstPerson()) {
return;
}
ItemStack held = minecraft.player.getMainHandItem();
if (!(held.getItem() instanceof PortalGunItem)) {
held = minecraft.player.getOffhandItem();
if (!(held.getItem() instanceof PortalGunItem)) {
return;
}
}
UUID gunId = PortalGunItem.getGunId(held);
UUID ownerId = PortalGunItem.getPortalOwnerId(held, minecraft.player.getUUID());
String channelName = PortalGunItem.getChannelName(held);
PortalGunIndicatorClientState.requestIfNeeded(minecraft, gunId);
Set<PortalKey> portals = portalsFor(minecraft, ownerId, channelName);
PortalGunIndicatorPayload serverState = PortalGunIndicatorClientState.get(gunId);
boolean blueAvailable = serverState == null
? portals.contains(new PortalKey(channelName, PortalGunPortalEntity.PortalSide.BLUE))
: serverState.blueAvailable();
boolean orangeAvailable = serverState == null
? portals.contains(new PortalKey(channelName, PortalGunPortalEntity.PortalSide.ORANGE))
: serverState.orangeAvailable();
int left = (minecraft.getWindow().getGuiScaledWidth() - WIDTH) / 2;
int top = (minecraft.getWindow().getGuiScaledHeight() - HEIGHT) / 2;
guiGraphics.blit(CROSSHAIR, left + 6, top, 0.0F, 0.0F, 17, HEIGHT, WIDTH, HEIGHT);
guiGraphics.blit(CROSSHAIR, left + 11, top, 17.0F, 0.0F, 17, HEIGHT, WIDTH, HEIGHT);
if (!blueAvailable) {
guiGraphics.fill(left + 6, top, left + 23, top + HEIGHT, 0x99000000);
}
if (!orangeAvailable) {
guiGraphics.fill(left + 11, top, left + 28, top + HEIGHT, 0x99000000);
}
int indicatorSize = AntarchySettings.portalGunIndicatorSize();
if (indicatorSize > 0) {
int dot = Math.max(1, Math.round(HEIGHT * indicatorSize / 100.0F));
int gap = Math.max(1, dot / 3);
int indicatorLeft = left + (WIDTH - dot * 2 - gap) / 2;
int indicatorTop = top + HEIGHT + 2;
guiGraphics.fill(indicatorLeft, indicatorTop, indicatorLeft + dot, indicatorTop + dot,
blueAvailable ? 0xFF21A8FF : 0xFF33414A);
int orangeLeft = indicatorLeft + dot + gap;
guiGraphics.fill(orangeLeft, indicatorTop, orangeLeft + dot, indicatorTop + dot,
orangeAvailable ? 0xFFFF8A24 : 0xFF4A4035);
}
}
private static Set<PortalKey> portalsFor(Minecraft minecraft, UUID ownerId, String channelName) {
ClientLevel level = minecraft.level;
long gameTime = level.getGameTime();
if (cachedLevel != level || cachedGameTime != gameTime || !ownerId.equals(cachedOwnerId)) {
Set<PortalKey> portals = new HashSet<>();
if (minecraft.player != null) {
AABB bounds = minecraft.player.getBoundingBox().inflate(128.0D);
for (PortalGunPortalEntity portal : level.getEntitiesOfClass(PortalGunPortalEntity.class, bounds, Entity::isAlive)) {
UUID portalOwnerId = portal.getOwnerId();
String portalChannelName = portal.getChannelName();
if (ownerId.equals(portalOwnerId) && portalChannelName != null && !portalChannelName.isEmpty()) {
portals.add(new PortalKey(portalChannelName, portal.getPortalSide()));
if (portal.getLinkedPortalId() != null) {
portals.add(new PortalKey(portalChannelName, PortalGunPortalEntity.PortalSide.BLUE));
portals.add(new PortalKey(portalChannelName, PortalGunPortalEntity.PortalSide.ORANGE));
}
}
}
}
cachedLevel = level;
cachedGameTime = gameTime;
cachedOwnerId = ownerId;
cachedPortals = Set.copyOf(portals);
}
if (channelName == null || channelName.isEmpty()) {
return Set.of();
}
return cachedPortals;
}
private record PortalKey(String channelName, PortalGunPortalEntity.PortalSide side) {
}
}
@@ -27,30 +27,43 @@ public final class PortalGunEntityTransformationStack {
}
public Vec3 moveEntity(PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, float partialTick) {
Vec3 interpolated = new Vec3(
double eyeHeight = this.entity.getEyeHeight();
Vec3 interpolatedEye = new Vec3(
net.minecraft.util.Mth.lerp(partialTick, this.entity.xo, this.entity.getX()),
net.minecraft.util.Mth.lerp(partialTick, this.entity.yo, this.entity.getY()),
net.minecraft.util.Mth.lerp(partialTick, this.entity.yo, this.entity.getY()) + eyeHeight,
net.minecraft.util.Mth.lerp(partialTick, this.entity.zo, this.entity.getZ())
);
Vec3 previous = new Vec3(this.entity.xo, this.entity.yo, this.entity.zo);
Vec3 transformed = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, interpolated.subtract(sourcePortal.position())));
Vec3 transformedPrevious = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, previous.subtract(sourcePortal.position())));
Vec3 previousEye = new Vec3(this.entity.xo, this.entity.yo + eyeHeight, this.entity.zo);
Vec3 transformedEye = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, interpolatedEye.subtract(sourcePortal.position())));
Vec3 transformedPreviousEye = destinationPortal.position().add(PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, previousEye.subtract(sourcePortal.position())));
Vec3 transformed = transformedEye.subtract(0.0D, eyeHeight, 0.0D);
Vec3 transformedPrevious = transformedPreviousEye.subtract(0.0D, eyeHeight, 0.0D);
Vec3 look = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, this.entity.getLookAngle()).normalize();
Vec3 previousLook = PortalGunTransformUtil.transformVector(
sourcePortal,
destinationPortal,
Vec3.directionFromRotation(this.entity.xRotO, this.entity.yRotO)
).normalize();
Vec3 velocity = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, this.entity.getDeltaMovement());
float yaw = PortalGunTransformUtil.yawFromLook(look);
float pitch = PortalGunTransformUtil.pitchFromLook(look);
float previousYaw = PortalGunTransformUtil.yawFromLook(previousLook);
float previousPitch = PortalGunTransformUtil.pitchFromLook(previousLook);
float yawDelta = net.minecraft.util.Mth.wrapDegrees(yaw - this.entity.getYRot());
this.entity.setPos(transformed.x, transformed.y, transformed.z);
this.entity.xo = transformedPrevious.x;
this.entity.yo = transformedPrevious.y;
this.entity.zo = transformedPrevious.z;
this.entity.setYRot(yaw);
this.entity.setXRot(pitch);
this.entity.yRotO = yaw;
this.entity.xRotO = pitch;
this.entity.yRotO = previousYaw;
this.entity.xRotO = previousPitch;
this.entity.setDeltaMovement(velocity);
if (this.entity instanceof LivingEntity living) {
living.setYHeadRot(yaw);
living.setYBodyRot(yaw);
living.setYHeadRot(living.getYHeadRot() + yawDelta);
living.setYBodyRot(living.yBodyRot + yawDelta);
living.yHeadRotO += yawDelta;
living.yBodyRotO += yawDelta;
}
return transformed;
}
@@ -73,13 +86,17 @@ public final class PortalGunEntityTransformationStack {
float yRotO,
float xRotO,
float yHeadRot,
float yHeadRotO,
float yBodyRot,
float yBodyRotO,
Vec3 deltaMovement
) {
private static EntityTransformation capture(Entity entity) {
float yHeadRot = entity instanceof LivingEntity living ? living.getYHeadRot() : entity.getYRot();
float yHeadRotO = entity instanceof LivingEntity living ? living.yHeadRotO : entity.yRotO;
float yBodyRot = entity instanceof LivingEntity living ? living.yBodyRot : entity.getYRot();
return new EntityTransformation(entity.getX(), entity.getY(), entity.getZ(), entity.xo, entity.yo, entity.zo, entity.getYRot(), entity.getXRot(), entity.yRotO, entity.xRotO, yHeadRot, yBodyRot, entity.getDeltaMovement());
float yBodyRotO = entity instanceof LivingEntity living ? living.yBodyRotO : entity.yRotO;
return new EntityTransformation(entity.getX(), entity.getY(), entity.getZ(), entity.xo, entity.yo, entity.zo, entity.getYRot(), entity.getXRot(), entity.yRotO, entity.xRotO, yHeadRot, yHeadRotO, yBodyRot, yBodyRotO, entity.getDeltaMovement());
}
private void restore(Entity entity) {
@@ -94,7 +111,9 @@ public final class PortalGunEntityTransformationStack {
entity.setDeltaMovement(this.deltaMovement);
if (entity instanceof LivingEntity living) {
living.setYHeadRot(this.yHeadRot);
living.yHeadRotO = this.yHeadRotO;
living.setYBodyRot(this.yBodyRot);
living.yBodyRotO = this.yBodyRotO;
}
}
}
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.client;
public final class PortalGunGrabClientState {
private static boolean active;
private PortalGunGrabClientState() {
}
public static void update(boolean grabActive) {
active = grabActive;
}
public static boolean isActive() {
return active;
}
public static void clear() {
active = false;
}
}
@@ -0,0 +1,59 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload;
import com.craisinlord.antarchy.content.network.PortalGunIndicatorRequestPayload;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
public final class PortalGunIndicatorClientState {
private static final Map<UUID, PortalGunIndicatorPayload> STATES = new HashMap<>();
private static ClientLevel level;
private static long lastRequestTick = Long.MIN_VALUE;
private static UUID lastRequestedGun;
private static Consumer<PortalGunIndicatorRequestPayload> requestSender = payload -> {};
private PortalGunIndicatorClientState() {
}
public static void setRequestSender(Consumer<PortalGunIndicatorRequestPayload> sender) {
requestSender = sender;
}
public static void requestIfNeeded(Minecraft minecraft, UUID gunId) {
ClientLevel currentLevel = minecraft.level;
if (currentLevel == null || gunId == null || minecraft.player == null) {
return;
}
if (level != currentLevel) {
level = currentLevel;
STATES.clear();
lastRequestedGun = null;
lastRequestTick = Long.MIN_VALUE;
}
long currentTick = minecraft.player.tickCount;
if (!gunId.equals(lastRequestedGun) || currentTick - lastRequestTick >= 20L) {
lastRequestedGun = gunId;
lastRequestTick = currentTick;
requestSender.accept(new PortalGunIndicatorRequestPayload(gunId));
}
}
public static void update(PortalGunIndicatorPayload payload) {
STATES.put(payload.gunId(), payload);
}
public static PortalGunIndicatorPayload get(UUID gunId) {
return gunId == null ? null : STATES.get(gunId);
}
public static void clear() {
STATES.clear();
level = null;
lastRequestedGun = null;
lastRequestTick = Long.MIN_VALUE;
}
}
@@ -3,6 +3,7 @@ package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.UUID;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix4f;
@@ -133,7 +134,8 @@ public final class PortalGunPortalRenderState {
Matrix4f viewMatrix,
Matrix4f projectionMatrix,
int renderLevel,
boolean renderAll
boolean renderAll,
UUID destinationId
) {
}
}
@@ -0,0 +1,51 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.network.PortalGunResetPayload;
import java.util.function.Consumer;
import net.minecraft.client.Minecraft;
public final class PortalGunResetClientState {
private static boolean resetDown;
private static boolean attackDown;
private static boolean useDown;
private static boolean selectedSide;
private PortalGunResetClientState() {
}
public static boolean tick(Minecraft minecraft, boolean enabled, boolean resetPressed, Consumer<PortalGunResetPayload> sender) {
boolean canInput = enabled && minecraft.player != null && minecraft.level != null && minecraft.screen == null;
boolean active = canInput && resetPressed;
boolean attack = canInput && minecraft.options.keyAttack.isDown();
boolean use = canInput && minecraft.options.keyUse.isDown();
if (active && !resetDown) {
sender.accept(new PortalGunResetPayload(com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.ARM));
selectedSide = false;
}
if (active && attack && !attackDown) {
sender.accept(new PortalGunResetPayload(com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.RESET_BLUE));
selectedSide = true;
}
if (active && use && !useDown) {
selectedSide = true;
}
if (!active && resetDown) {
int action = !canInput || selectedSide
? com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.RELEASE
: com.craisinlord.antarchy.content.portalgun.PortalGunResetManager.RESET_ALL;
sender.accept(new PortalGunResetPayload(action));
selectedSide = false;
}
resetDown = active;
attackDown = attack;
useDown = use;
return active;
}
public static void clear() {
resetDown = false;
attackDown = false;
useDown = false;
selectedSide = false;
}
}
@@ -7,10 +7,12 @@ import java.util.Map;
import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
public final class PortalGunRollClientState {
private static final int TELEPORT_COOLDOWN_TICKS = 40;
private static final double PORTAL_QUERY_RADIUS = 12.0D;
private static final int TELEPORT_COOLDOWN_TICKS = 3;
private static final float ROLL_DECAY = 0.85F;
private static final float ROLL_EPSILON = 0.05F;
private static final Map<UUID, Long> TELEPORT_COOLDOWNS = new HashMap<>();
@@ -32,8 +34,11 @@ public final class PortalGunRollClientState {
if (minecraft.level == null || entity == null) {
return false;
}
for (Entity renderEntity : minecraft.level.entitiesForRendering()) {
if (renderEntity instanceof PortalGunPortalEntity portal && portal.isAlive() && portal.intersectsEntityBounds(entity)) {
for (PortalGunPortalEntity portal : minecraft.level.getEntitiesOfClass(
PortalGunPortalEntity.class,
entity.getBoundingBox().inflate(PORTAL_QUERY_RADIUS),
Entity::isAlive)) {
if (portal.intersectsEntityBounds(entity)) {
return true;
}
}
@@ -67,10 +72,10 @@ public final class PortalGunRollClientState {
}
long gameTime = minecraft.level.getGameTime();
TELEPORT_COOLDOWNS.entrySet().removeIf(entry -> entry.getValue() <= gameTime);
for (Entity entity : minecraft.level.entitiesForRendering()) {
if (!(entity instanceof PortalGunPortalEntity portal) || !portal.isAlive()) {
continue;
}
for (PortalGunPortalEntity portal : minecraft.level.getEntitiesOfClass(
PortalGunPortalEntity.class,
cameraEntity.getBoundingBox().inflate(PORTAL_QUERY_RADIUS),
Entity::isAlive)) {
PortalGunPortalEntity linkedPortal = portal.getLinkedPortal();
if (linkedPortal == null || !linkedPortal.isAlive()) {
continue;
@@ -81,11 +86,13 @@ public final class PortalGunRollClientState {
if (!canTeleport(cameraEntity, gameTime)) {
continue;
}
Vec3 crossingProbe = portal.resolveCrossingProbe(cameraEntity);
if (crossingProbe == null) {
AABB currentBox = cameraEntity.getBoundingBox();
AABB previousBox = currentBox.move(cameraEntity.xo - cameraEntity.getX(), cameraEntity.yo - cameraEntity.getY(), cameraEntity.zo - cameraEntity.getZ());
PortalGunPortalEntity.PortalCrossing crossing = portal.resolveCrossing(cameraEntity, previousBox, currentBox);
if (crossing == null) {
continue;
}
applyPredictedTeleport(cameraEntity, portal, linkedPortal, crossingProbe, gameTime);
applyPredictedTeleport(cameraEntity, portal, linkedPortal, crossing, gameTime);
break;
}
}
@@ -98,26 +105,30 @@ public final class PortalGunRollClientState {
return cooldownUntil <= gameTime;
}
private static void applyPredictedTeleport(Entity entity, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, Vec3 currentProbe, long gameTime) {
private static void applyPredictedTeleport(Entity entity, PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, PortalGunPortalEntity.PortalCrossing crossing, long gameTime) {
Vec3 transformedLook = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.getLookAngle()).normalize();
Vec3 transformedUp = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, PortalGunTransformUtil.upVectorFromLookAndRoll(entity.getLookAngle(), rollDegrees)).normalize();
Vec3 relativeProbe = currentProbe.subtract(sourcePortal.position());
Vec3 transformedProbe = PortalGunTransformUtil.transformPosition(sourcePortal, destinationPortal, relativeProbe);
Vec3 transformedEntityOffset = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.position().subtract(currentProbe));
Vec3 destinationNormal = destinationPortal.getNormalVec().normalize();
double exitClearance = Math.max(0.35D, entity.getBbWidth() * 0.5D + 0.16D);
Vec3 exitProbe = destinationPortal.position().add(transformedProbe).add(destinationNormal.scale(exitClearance - transformedProbe.dot(destinationNormal)));
Vec3 exitPos = exitProbe.add(transformedEntityOffset);
Vec3 transformedMotion = PortalGunTransformUtil.transformVector(sourcePortal, destinationPortal, entity.getDeltaMovement());
double outward = transformedMotion.dot(destinationNormal);
if (outward < 0.42D) {
transformedMotion = transformedMotion.add(destinationNormal.scale(0.42D - outward));
}
Vec3 resolvedMovement = entity.position().subtract(entity.xo, entity.yo, entity.zo);
PortalGunPortalEntity.PortalTransitTransform transit = sourcePortal.previewTransit(entity, destinationPortal, crossing, resolvedMovement);
Vec3 transformedMotion = transit.movement();
transformedMotion = clampPortalMotion(transformedMotion);
rollDegrees = PortalGunTransformUtil.rollFromOrientation(transformedLook, transformedUp);
Vec3 exitPos = transit.exitPosition();
entity.teleportTo(exitPos.x, exitPos.y, exitPos.z);
entity.setDeltaMovement(transformedMotion);
float verticalMotion = (float) transformedMotion.y;
entity.fallDistance = 0.1F * (verticalMotion / -0.1F * verticalMotion / -0.1F);
entity.setYRot(PortalGunTransformUtil.yawFromLook(transformedLook));
entity.setXRot(PortalGunTransformUtil.pitchFromLook(transformedLook));
entity.setPortalCooldown(TELEPORT_COOLDOWN_TICKS);
TELEPORT_COOLDOWNS.put(entity.getUUID(), gameTime + TELEPORT_COOLDOWN_TICKS);
}
private static Vec3 clampPortalMotion(Vec3 motion) {
return new Vec3(clampPortalMotionComponent(motion.x), clampPortalMotionComponent(motion.y), clampPortalMotionComponent(motion.z));
}
private static double clampPortalMotionComponent(double motion) {
return Math.abs(motion) > 0.99D ? motion / (Math.abs(motion) + 0.001D) : motion;
}
}
@@ -0,0 +1,72 @@
package com.craisinlord.antarchy.content.client;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Options;
import net.minecraft.world.item.ItemStack;
public final class PortalGunZoomClientState {
private static int progress;
private static boolean zooming;
private static Double originalSensitivity;
private PortalGunZoomClientState() {
}
public static void tick(Minecraft minecraft, boolean held, boolean togglePressed) {
if (minecraft.player == null || minecraft.level == null) {
zooming = false;
progress = 0;
restoreSensitivity(minecraft.options);
return;
}
ItemStack main = minecraft.player.getMainHandItem();
ItemStack off = minecraft.player.getOffhandItem();
boolean holdingPortalGun = held && (main.getItem() instanceof PortalGunItem || off.getItem() instanceof PortalGunItem);
if (togglePressed && holdingPortalGun) {
zooming = !zooming;
if (zooming && originalSensitivity == null) {
originalSensitivity = minecraft.options.sensitivity().get();
}
}
if (!holdingPortalGun) {
zooming = false;
}
progress = Math.max(0, Math.min(5, progress + (zooming ? 1 : -1)));
if (originalSensitivity != null) {
if (progress == 0) {
restoreSensitivity(minecraft.options);
}
}
}
public static double applyFov(double fov, float partialTick, Options options) {
if (progress == 0 && !zooming) {
restoreSensitivity(options);
return fov;
}
double easedProgress = zooming ? progress - 1.0D + partialTick : progress + 1.0D - partialTick;
double zoomFactor = zoomFactor(easedProgress);
if (originalSensitivity != null) {
options.sensitivity().set(originalSensitivity * zoomFactor);
}
return fov * zoomFactor;
}
private static double zoomFactor(double progress) {
return 0.1D + 0.9D * (1.0D - Math.sin(Math.toRadians(90.0D * Math.max(0.0D, Math.min(1.0D, progress / 5.0D)))));
}
private static double zoomFactor(int progress) {
return zoomFactor((double) progress);
}
private static void restoreSensitivity(Options options) {
if (originalSensitivity != null) {
options.sensitivity().set(originalSensitivity);
originalSensitivity = null;
}
}
}
@@ -2,12 +2,8 @@ package com.craisinlord.antarchy.content.client.game;
import com.craisinlord.antos.api.client.game.ComputerGame;
import com.craisinlord.antos.api.client.game.ComputerGameRegistry;
import com.craisinlord.antarchy.content.network.AntarchyGameNetworking;
import com.craisinlord.antarchy.content.network.AntarchyGamePayload;
import com.craisinlord.antarchy.content.client.AntarchyGameClientState;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
@@ -17,11 +13,11 @@ public final class AntarchyComputerGames {
private AntarchyComputerGames() {}
public static void register() {
registerIfMissing(game("basilisk", "BASILISK", pos -> {
BasiliskProgram program = new BasiliskProgram(true, 0, 0, score -> AntarchyGameNetworking.saveBasiliskScore(pos, score));
var loaded = new java.util.concurrent.atomic.AtomicBoolean();
AntarchyGameNetworking.requestBasiliskState(pos);
return new GameSession(() -> { if (!loaded.get()) loaded.set(loadScores(pos, AntarchyGamePayload.BASILISK_STATE, program::setStoredScores)); program.tick(); }, program::render, key -> loaded.get() && program.keyPressed(key), ignored -> false);
registerIfMissing(game("basilisk", "BASILISK", () -> {
// AntOS 1.2.1 creates game sessions without passing the computer position.
// Keep Basilisk playable without a position-bound score callback.
BasiliskProgram program = new BasiliskProgram(true, 0, 0, score -> {});
return new GameSession(program::tick, program::render, program::keyPressed, ignored -> false);
}));
}
@@ -29,18 +25,6 @@ public final class AntarchyComputerGames {
if (ComputerGameRegistry.get(game.id()) == null) ComputerGameRegistry.register(game);
}
private static boolean loadScores(BlockPos pos, int action, java.util.function.BiConsumer<Integer, Integer> setter) {
String response = AntarchyGameClientState.getGameState(pos, action);
if (response == null) return false;
if (response.isBlank()) { AntarchyGameClientState.clearGameState(pos, action); return true; }
String[] scores = response.split("\0", 2);
if (scores.length != 2) return false;
try { setter.accept(Integer.parseInt(scores[0]), Integer.parseInt(scores[1])); }
catch (NumberFormatException ignored) { return false; }
AntarchyGameClientState.clearGameState(pos, action);
return true;
}
private static ComputerGame game(String path, String title, ProgramFactory factory) {
ResourceLocation id = ResourceLocation.fromNamespaceAndPath("antarchy", path);
ResourceLocation disk = ResourceLocation.fromNamespaceAndPath("antarchy", path + "_game");
@@ -48,12 +32,12 @@ public final class AntarchyComputerGames {
@Override public ResourceLocation id() { return id; }
@Override public ResourceLocation diskId() { return disk; }
@Override public String title() { return title; }
@Override public Session create(BlockPos position) { return factory.create(position); }
@Override public Session create() { return factory.create(); }
};
}
@FunctionalInterface
private interface ProgramFactory { ComputerGame.Session create(BlockPos position); }
private interface ProgramFactory { ComputerGame.Session create(); }
@FunctionalInterface private interface GameRenderer { void render(GuiGraphics graphics, Font font, int x, int y, int width, int height); }
private static final class GameSession implements ComputerGame.Session {
@@ -1,11 +1,15 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings;
import com.craisinlord.antarchy.content.client.model.PortalGunPortalModel;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.GraphicsStatus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.OverlayTexture;
@@ -24,23 +28,61 @@ public class PortalGunPortalRenderer extends GeoEntityRenderer<PortalGunPortalEn
@Override
public void render(PortalGunPortalEntity entity, float entityYaw, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight) {
poseStack.pushPose();
VertexConsumer consumer = bufferSource.getBuffer(RenderType.entityTranslucent(this.getTextureLocation(entity)));
Matrix4f pose = poseStack.last().pose();
putPortalVertex(consumer, pose, entity, -0.5F, -1.0F, 0.0F, 1.0F, false);
putPortalVertex(consumer, pose, entity, -0.5F, 1.0F, 0.0F, 0.0F, false);
putPortalVertex(consumer, pose, entity, 0.5F, 1.0F, 1.0F, 0.0F, false);
putPortalVertex(consumer, pose, entity, 0.5F, -1.0F, 1.0F, 1.0F, false);
float portalScale = entity.getPortalVisualScale(partialTick);
float halfWidth = entity.getPortalWidth() * 0.5F;
float halfHeight = entity.getPortalHeight() * 0.5F;
ResourceLocation texture = portalTexture(entity);
VertexConsumer consumer = bufferSource.getBuffer(RenderType.entityTranslucent(texture));
if (isFancyPortalsEnabled()) {
float frameV = Math.floorMod((int) ((entity.level().getGameTime() + partialTick) / 5.0F), 4) * 0.25F;
putAnimatedPortalVertex(consumer, pose, entity, portalScale, frameV, -halfWidth, -halfHeight, 0.0F, 1.0F);
putAnimatedPortalVertex(consumer, pose, entity, portalScale, frameV, -halfWidth, halfHeight, 0.0F, 0.0F);
putAnimatedPortalVertex(consumer, pose, entity, portalScale, frameV, halfWidth, halfHeight, 1.0F, 0.0F);
putAnimatedPortalVertex(consumer, pose, entity, portalScale, frameV, halfWidth, -halfHeight, 1.0F, 1.0F);
} else {
putPortalVertex(consumer, pose, entity, portalScale, -halfWidth, -halfHeight, 0.0F, 1.0F, false);
putPortalVertex(consumer, pose, entity, portalScale, -halfWidth, halfHeight, 0.0F, 0.0F, false);
putPortalVertex(consumer, pose, entity, portalScale, halfWidth, halfHeight, 1.0F, 0.0F, false);
putPortalVertex(consumer, pose, entity, portalScale, halfWidth, -halfHeight, 1.0F, 1.0F, false);
}
VertexConsumer edge = bufferSource.getBuffer(RenderType.entityTranslucent(this.getTextureLocation(entity)));
putPortalVertex(edge, pose, entity, -0.56F, -1.07F, 0.0F, 1.0F, true);
putPortalVertex(edge, pose, entity, -0.56F, 1.07F, 0.0F, 0.0F, true);
putPortalVertex(edge, pose, entity, 0.56F, 1.07F, 1.0F, 0.0F, true);
putPortalVertex(edge, pose, entity, 0.56F, -1.07F, 1.0F, 1.0F, true);
putPortalVertex(edge, pose, entity, portalScale, -halfWidth - 0.06F, -halfHeight - 0.07F, 0.0F, 1.0F, true);
putPortalVertex(edge, pose, entity, portalScale, -halfWidth - 0.06F, halfHeight + 0.07F, 0.0F, 0.0F, true);
putPortalVertex(edge, pose, entity, portalScale, halfWidth + 0.06F, halfHeight + 0.07F, 1.0F, 0.0F, true);
putPortalVertex(edge, pose, entity, portalScale, halfWidth + 0.06F, -halfHeight - 0.07F, 1.0F, 1.0F, true);
poseStack.popPose();
}
private static void putPortalVertex(VertexConsumer consumer, Matrix4f pose, PortalGunPortalEntity entity, float horizontal, float vertical, float u, float v, boolean channelTint) {
Vec3 position = entity.getWidthVec().normalize().scale(horizontal)
.add(entity.getUpVec().normalize().scale(vertical))
private static ResourceLocation portalTexture(PortalGunPortalEntity entity) {
String color = entity.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE ? "blue" : "orange";
String path = isFancyPortalsEnabled()
? "textures/entity/portal_gun/portal_" + color + "_vortex.png"
: "textures/vfx/portal_gun_portal_" + color + ".png";
return ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, path);
}
private static boolean isFancyPortalsEnabled() {
Minecraft minecraft = Minecraft.getInstance();
return AntarchySettings.portalGunFancyPortals()
&& minecraft.options.graphicsMode().get() == GraphicsStatus.FANCY;
}
private static void putAnimatedPortalVertex(VertexConsumer consumer, Matrix4f pose, PortalGunPortalEntity entity, float portalScale, float frameV, float horizontal, float vertical, float u, float v) {
Vec3 position = entity.getWidthVec().normalize().scale(horizontal * portalScale)
.add(entity.getUpVec().normalize().scale(vertical * portalScale))
.add(entity.getNormalVec().normalize().scale(0.03125D));
consumer.addVertex(pose, (float) position.x, (float) position.y, (float) position.z)
.setColor(255, 255, 255, 210)
.setUv(u, frameV + v * 0.25F)
.setOverlay(OverlayTexture.NO_OVERLAY)
.setLight(LightTexture.FULL_BRIGHT)
.setNormal((float) entity.getNormalVec().x, (float) entity.getNormalVec().y, (float) entity.getNormalVec().z);
}
private static void putPortalVertex(VertexConsumer consumer, Matrix4f pose, PortalGunPortalEntity entity, float portalScale, float horizontal, float vertical, float u, float v, boolean channelTint) {
Vec3 position = entity.getWidthVec().normalize().scale(horizontal * portalScale)
.add(entity.getUpVec().normalize().scale(vertical * portalScale))
.add(entity.getNormalVec().normalize().scale(0.03125D));
int red = channelTint ? entity.getChannelRed() : 255;
int green = channelTint ? entity.getChannelGreen() : 255;
@@ -60,6 +102,6 @@ public class PortalGunPortalRenderer extends GeoEntityRenderer<PortalGunPortalEn
@Override
public @Nullable RenderType getRenderType(PortalGunPortalEntity animatable, ResourceLocation texture, @Nullable MultiBufferSource bufferSource, float partialTick) {
return RenderType.entityTranslucent(this.getTextureLocation(animatable));
return RenderType.entityTranslucent(portalTexture(animatable));
}
}
@@ -0,0 +1,148 @@
package com.craisinlord.antarchy.content.client.renderer;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.chunk.RenderRegionCache;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
public final class PortalGunPortalRendererPool {
private static final int MAX_RENDERERS = 8;
private static final long PORTAL_SECTION_BUDGET_NANOS = 4_166_666L;
private static final LinkedHashMap<UUID, PortalRenderer> RENDERERS = new LinkedHashMap<>(8, 0.75F, true);
private static final Set<LevelRenderer> PROXY_RENDERERS = java.util.Collections.newSetFromMap(new IdentityHashMap<>());
private static final ThreadLocal<Long> SECTION_BUDGET_DEADLINE = ThreadLocal.withInitial(() -> 0L);
private static final ThreadLocal<Integer> SCHEDULED_SECTION_REBUILDS = ThreadLocal.withInitial(() -> 0);
private static final ThreadLocal<Boolean> SKIP_SET_NOT_DIRTY = ThreadLocal.withInitial(() -> false);
private static ClientLevel ownerLevel;
private PortalGunPortalRendererPool() {
}
public static LevelRenderer acquire(Minecraft minecraft, UUID destinationId, int width, int height) {
if (ownerLevel != minecraft.level) {
clear();
ownerLevel = minecraft.level;
}
PortalRenderer state = RENDERERS.get(destinationId);
if (state == null) {
LevelRenderer renderer = new LevelRenderer(
minecraft,
minecraft.getEntityRenderDispatcher(),
minecraft.getBlockEntityRenderDispatcher(),
minecraft.renderBuffers()
);
PROXY_RENDERERS.add(renderer);
renderer.setLevel(minecraft.level);
renderer.resize(width, height);
state = new PortalRenderer(renderer, width, height);
RENDERERS.put(destinationId, state);
trim();
} else if (state.width() != width || state.height() != height) {
state.renderer().resize(width, height);
RENDERERS.put(destinationId, new PortalRenderer(state.renderer(), width, height));
}
return state.renderer();
}
public static boolean isProxy(LevelRenderer renderer) {
return PROXY_RENDERERS.contains(renderer);
}
public static void beginFrame() {
SECTION_BUDGET_DEADLINE.set(System.nanoTime() + PORTAL_SECTION_BUDGET_NANOS);
SCHEDULED_SECTION_REBUILDS.set(0);
SKIP_SET_NOT_DIRTY.set(false);
}
public static void beginSceneBudget() {
SECTION_BUDGET_DEADLINE.set(System.nanoTime() + PORTAL_SECTION_BUDGET_NANOS);
SCHEDULED_SECTION_REBUILDS.set(0);
SKIP_SET_NOT_DIRTY.set(false);
}
public static int scheduledSectionRebuilds() {
return SCHEDULED_SECTION_REBUILDS.get();
}
public static long remainingSectionBudgetNanos() {
return Math.max(0L, SECTION_BUDGET_DEADLINE.get() - System.nanoTime());
}
public static void rebuildSection(LevelRenderer renderer, SectionRenderDispatcher dispatcher, SectionRenderDispatcher.RenderSection section, RenderRegionCache cache) {
if (!isProxy(renderer)) {
dispatcher.rebuildSectionSync(section, cache);
SKIP_SET_NOT_DIRTY.set(false);
return;
}
if (remainingSectionBudgetNanos() <= 0L) {
SKIP_SET_NOT_DIRTY.set(true);
return;
}
section.rebuildSectionAsync(dispatcher, cache);
SCHEDULED_SECTION_REBUILDS.set(SCHEDULED_SECTION_REBUILDS.get() + 1);
SKIP_SET_NOT_DIRTY.set(false);
}
public static void rebuildSectionAsync(LevelRenderer renderer, SectionRenderDispatcher dispatcher, SectionRenderDispatcher.RenderSection section, RenderRegionCache cache) {
if (!isProxy(renderer)) {
section.rebuildSectionAsync(dispatcher, cache);
SKIP_SET_NOT_DIRTY.set(false);
return;
}
if (remainingSectionBudgetNanos() <= 0L) {
SKIP_SET_NOT_DIRTY.set(true);
return;
}
section.rebuildSectionAsync(dispatcher, cache);
SCHEDULED_SECTION_REBUILDS.set(SCHEDULED_SECTION_REBUILDS.get() + 1);
SKIP_SET_NOT_DIRTY.set(false);
}
public static void setNotDirty(SectionRenderDispatcher.RenderSection section) {
boolean leaveDirty = SKIP_SET_NOT_DIRTY.get();
SKIP_SET_NOT_DIRTY.set(false);
if (!leaveDirty) {
section.setNotDirty();
}
}
public static void clear() {
for (PortalRenderer state : new ArrayList<>(RENDERERS.values())) {
LevelRenderer renderer = state.renderer();
PortalGunPortalViewAreaManager.releaseRenderer(renderer);
try {
renderer.close();
} finally {
PROXY_RENDERERS.remove(renderer);
}
}
RENDERERS.clear();
ownerLevel = null;
}
private static void trim() {
Iterator<Map.Entry<UUID, PortalRenderer>> iterator = RENDERERS.entrySet().iterator();
while (RENDERERS.size() > MAX_RENDERERS && iterator.hasNext()) {
PortalRenderer state = iterator.next().getValue();
iterator.remove();
LevelRenderer renderer = state.renderer();
PortalGunPortalViewAreaManager.releaseRenderer(renderer);
try {
renderer.close();
} finally {
PROXY_RENDERERS.remove(renderer);
}
}
}
private record PortalRenderer(LevelRenderer renderer, int width, int height) {
}
}
@@ -0,0 +1,403 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.mixins.client.LevelRendererPortalViewAreaAccessor;
import com.mojang.blaze3d.vertex.VertexBuffer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.SectionOcclusionGraph;
import net.minecraft.client.renderer.ViewArea;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public final class PortalGunPortalViewAreaManager {
private static final int MAX_VIEW_AREAS = 8;
private static final LinkedHashMap<ViewKey, ViewState> VIEW_AREAS = new LinkedHashMap<>(8, 0.75F, true);
private static final Map<SectionRenderDispatcher.RenderSection, ViewState> SECTION_OWNERS = Collections.synchronizedMap(new IdentityHashMap<>());
private static ClientLevel ownerLevel;
private static int ownerViewDistance = -1;
private PortalGunPortalViewAreaManager() {
}
public static Scope enter(Minecraft minecraft, LevelRenderer renderer, UUID destinationId, Vec3 cameraPos, net.minecraft.client.Camera portalCamera) {
LevelRendererPortalViewAreaAccessor accessor = (LevelRendererPortalViewAreaAccessor) renderer;
int viewDistance = minecraft.options.getEffectiveRenderDistance();
if (ownerLevel != minecraft.level || ownerViewDistance != viewDistance) {
clear();
ownerLevel = minecraft.level;
ownerViewDistance = viewDistance;
}
Scope sodiumScope = SodiumCompat.isLoaded() ? new SodiumScope(SodiumCompat.beginPortalView(portalCamera)) : null;
ViewKey viewKey = new ViewKey(renderer, destinationId);
ViewState state = VIEW_AREAS.get(viewKey);
if (state == null) {
ViewArea area = accessor.antarchy$getViewArea();
SectionOcclusionGraph graph = accessor.antarchy$getSectionOcclusionGraph();
ObjectArrayList<SectionRenderDispatcher.RenderSection> sections = accessor.antarchy$getVisibleSections();
state = new ViewState(area, graph, sections, accessor.antarchy$getGlobalBlockEntities());
state.cloudBuffer = accessor.antarchy$getCloudBuffer();
state.generateClouds = accessor.antarchy$getGenerateClouds();
state.prevCloudX = accessor.antarchy$getPrevCloudX();
state.prevCloudY = accessor.antarchy$getPrevCloudY();
state.prevCloudZ = accessor.antarchy$getPrevCloudZ();
state.prevCloudColor = accessor.antarchy$getPrevCloudColor();
state.prevCloudsType = accessor.antarchy$getPrevCloudsType();
for (SectionRenderDispatcher.RenderSection section : area.sections) {
((PortalViewSectionMarker) section).antarchy$markPortalViewSection();
SECTION_OWNERS.put(section, state);
}
VIEW_AREAS.put(viewKey, state);
trimOldViews();
}
state.area.repositionCamera(cameraPos.x, cameraPos.z);
int sectionX = SectionPos.posToSectionCoord(cameraPos.x);
int sectionY = SectionPos.posToSectionCoord(cameraPos.y);
int sectionZ = SectionPos.posToSectionCoord(cameraPos.z);
state.cameraPos = cameraPos;
state.lastSectionX = sectionX;
state.lastSectionY = sectionY;
state.lastSectionZ = sectionZ;
Scope scope = new VanillaScope(renderer, accessor, state, minecraft.getBlockEntityRenderDispatcher());
accessor.antarchy$setViewArea(state.area);
accessor.antarchy$setSectionOcclusionGraph(state.graph);
accessor.antarchy$setVisibleSections(state.visibleSections);
accessor.antarchy$setGlobalBlockEntities(state.globalBlockEntities);
accessor.antarchy$setLastCameraSectionX(sectionX);
accessor.antarchy$setLastCameraSectionY(sectionY);
accessor.antarchy$setLastCameraSectionZ(sectionZ);
return sodiumScope == null ? scope : new CombinedScope(scope, sodiumScope);
}
public static boolean routeSectionCompiled(SectionRenderDispatcher.RenderSection section) {
ViewState state = SECTION_OWNERS.get(section);
if (state == null) {
return ((PortalViewSectionMarker) section).antarchy$isPortalViewSection();
}
state.graph.onSectionCompiled(section);
return true;
}
public static Vec3 cameraForSection(SectionRenderDispatcher.RenderSection section, Vec3 fallback) {
ViewState state = SECTION_OWNERS.get(section);
return state == null || state.cameraPos == null ? fallback : state.cameraPos;
}
public static boolean updateGlobalBlockEntities(SectionRenderDispatcher.RenderSection section, Iterable<BlockEntity> removed, Iterable<BlockEntity> added) {
ViewState state = SECTION_OWNERS.get(section);
if (state == null) {
return ((PortalViewSectionMarker) section).antarchy$isPortalViewSection();
}
synchronized (state.globalBlockEntities) {
for (BlockEntity blockEntity : removed) {
state.globalBlockEntities.remove(blockEntity);
}
for (BlockEntity blockEntity : added) {
state.globalBlockEntities.add(blockEntity);
}
}
return true;
}
public static void onSectionDirty(int sectionX, int sectionY, int sectionZ, boolean playerChanged) {
for (ViewState state : VIEW_AREAS.values()) {
int sectionRange = state.area.getViewDistance() + 1;
if (Math.abs((long) sectionX - state.lastSectionX) > sectionRange
|| Math.abs((long) sectionZ - state.lastSectionZ) > sectionRange
|| sectionY < ownerLevel.getMinSection()
|| sectionY > ownerLevel.getMaxSection()) {
continue;
}
state.area.setDirty(sectionX, sectionY, sectionZ, playerChanged);
}
}
public static void onChunkLoaded(net.minecraft.world.level.ChunkPos pos) {
for (ViewState state : VIEW_AREAS.values()) {
state.graph.onChunkLoaded(pos);
markChunkDirty(state, pos);
}
}
public static void onChunkDropped(net.minecraft.world.level.ChunkPos pos) {
for (ViewState state : VIEW_AREAS.values()) {
markChunkDirty(state, pos);
state.graph.invalidate();
}
}
private static void markChunkDirty(ViewState state, net.minecraft.world.level.ChunkPos pos) {
int sectionRange = state.area.getViewDistance() + 1;
if (Math.abs((long) pos.x - state.lastSectionX) <= sectionRange
&& Math.abs((long) pos.z - state.lastSectionZ) <= sectionRange) {
for (int sectionY = ownerLevel.getMinSection(); sectionY <= ownerLevel.getMaxSection(); sectionY++) {
state.area.setDirty(pos.x, sectionY, pos.z, true);
}
}
}
public static void clear() {
SodiumCompat.clear();
for (ViewState state : VIEW_AREAS.values()) {
state.graph.waitAndReset(state.area);
}
VIEW_AREAS.clear();
SECTION_OWNERS.clear();
ownerLevel = null;
ownerViewDistance = -1;
}
public static void releaseRenderer(LevelRenderer renderer) {
Iterator<Map.Entry<ViewKey, ViewState>> iterator = VIEW_AREAS.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<ViewKey, ViewState> entry = iterator.next();
if (entry.getKey().renderer() == renderer) {
ViewState state = entry.getValue();
state.graph.waitAndReset(state.area);
removeOwners(state);
iterator.remove();
}
}
}
private static void trimOldViews() {
Iterator<Map.Entry<ViewKey, ViewState>> iterator = VIEW_AREAS.entrySet().iterator();
while (VIEW_AREAS.size() > MAX_VIEW_AREAS && iterator.hasNext()) {
ViewState state = iterator.next().getValue();
iterator.remove();
state.graph.waitAndReset(state.area);
removeOwners(state);
}
}
private static void removeOwners(ViewState state) {
for (SectionRenderDispatcher.RenderSection section : state.area.sections) {
SECTION_OWNERS.remove(section);
}
}
private static final class ViewState {
private final ViewArea area;
private final SectionOcclusionGraph graph;
private final ObjectArrayList<SectionRenderDispatcher.RenderSection> visibleSections;
private final Set<BlockEntity> globalBlockEntities;
private VertexBuffer cloudBuffer;
private boolean generateClouds = true;
private int prevCloudX = Integer.MIN_VALUE;
private int prevCloudY = Integer.MIN_VALUE;
private int prevCloudZ = Integer.MIN_VALUE;
private Vec3 prevCloudColor = Vec3.ZERO;
private CloudStatus prevCloudsType;
private int lastSectionX;
private int lastSectionY;
private int lastSectionZ;
private volatile Vec3 cameraPos;
private double transparentSortX = Double.NaN;
private double transparentSortY = Double.NaN;
private double transparentSortZ = Double.NaN;
private double prevCamX = Double.NaN;
private double prevCamY = Double.NaN;
private double prevCamZ = Double.NaN;
private double prevCamRotX = Double.NaN;
private double prevCamRotY = Double.NaN;
private ViewState(ViewArea area, SectionOcclusionGraph graph, ObjectArrayList<SectionRenderDispatcher.RenderSection> visibleSections, Set<BlockEntity> globalBlockEntities) {
this.area = area;
this.graph = graph;
this.visibleSections = visibleSections;
this.globalBlockEntities = globalBlockEntities;
}
}
private record ViewKey(LevelRenderer renderer, UUID destinationId) {
}
public interface Scope extends AutoCloseable {
@Override
void close();
}
private static final class CombinedScope implements Scope {
private final Scope viewAreaScope;
private final Scope sodiumScope;
private CombinedScope(Scope viewAreaScope, Scope sodiumScope) {
this.viewAreaScope = viewAreaScope;
this.sodiumScope = sodiumScope;
}
@Override
public void close() {
try {
viewAreaScope.close();
} finally {
sodiumScope.close();
}
}
}
private static final class SodiumScope implements Scope {
private final SodiumCompat.TerrainScope terrainScope;
private SodiumScope(SodiumCompat.TerrainScope terrainScope) {
this.terrainScope = terrainScope;
}
@Override
public void close() {
terrainScope.close();
}
}
private static final class VanillaScope implements Scope {
private final LevelRendererPortalViewAreaAccessor accessor;
private final ViewArea previousArea;
private final SectionOcclusionGraph previousGraph;
private final ObjectArrayList<SectionRenderDispatcher.RenderSection> previousVisibleSections;
private final Set<BlockEntity> previousGlobalBlockEntities;
private final VertexBuffer previousCloudBuffer;
private final boolean previousGenerateClouds;
private final int previousCloudX;
private final int previousCloudY;
private final int previousCloudZ;
private final Vec3 previousCloudColor;
private final CloudStatus previousCloudsType;
private final int previousRainSoundTime;
private final int previousSectionX;
private final int previousSectionY;
private final int previousSectionZ;
private final double previousPrevCamX;
private final double previousPrevCamY;
private final double previousPrevCamZ;
private final double previousPrevCamRotX;
private final double previousPrevCamRotY;
private final double previousTransparentSortX;
private final double previousTransparentSortY;
private final double previousTransparentSortZ;
private final Vec3 previousDispatcherCamera;
private final SectionRenderDispatcher dispatcher;
private final BlockEntityRenderDispatcher blockEntityDispatcher;
private final Level previousBlockEntityLevel;
private final net.minecraft.client.Camera previousBlockEntityCamera;
private final HitResult previousBlockEntityHitResult;
private final ViewState state;
private boolean closed;
private VanillaScope(LevelRenderer renderer, LevelRendererPortalViewAreaAccessor accessor, ViewState state, BlockEntityRenderDispatcher blockEntityDispatcher) {
this.accessor = accessor;
this.state = state;
this.blockEntityDispatcher = blockEntityDispatcher;
this.previousBlockEntityLevel = blockEntityDispatcher.level;
this.previousBlockEntityCamera = blockEntityDispatcher.camera;
this.previousBlockEntityHitResult = blockEntityDispatcher.cameraHitResult;
this.previousArea = accessor.antarchy$getViewArea();
this.previousGraph = accessor.antarchy$getSectionOcclusionGraph();
this.previousVisibleSections = accessor.antarchy$getVisibleSections();
this.previousGlobalBlockEntities = accessor.antarchy$getGlobalBlockEntities();
this.previousCloudBuffer = accessor.antarchy$getCloudBuffer();
this.previousGenerateClouds = accessor.antarchy$getGenerateClouds();
this.previousCloudX = accessor.antarchy$getPrevCloudX();
this.previousCloudY = accessor.antarchy$getPrevCloudY();
this.previousCloudZ = accessor.antarchy$getPrevCloudZ();
this.previousCloudColor = accessor.antarchy$getPrevCloudColor();
this.previousCloudsType = accessor.antarchy$getPrevCloudsType();
this.previousRainSoundTime = accessor.antarchy$getRainSoundTime();
this.previousSectionX = accessor.antarchy$getLastCameraSectionX();
this.previousSectionY = accessor.antarchy$getLastCameraSectionY();
this.previousSectionZ = accessor.antarchy$getLastCameraSectionZ();
this.previousPrevCamX = accessor.antarchy$getPrevCamX();
this.previousPrevCamY = accessor.antarchy$getPrevCamY();
this.previousPrevCamZ = accessor.antarchy$getPrevCamZ();
this.previousPrevCamRotX = accessor.antarchy$getPrevCamRotX();
this.previousPrevCamRotY = accessor.antarchy$getPrevCamRotY();
this.previousTransparentSortX = accessor.antarchy$getTransparentSortX();
this.previousTransparentSortY = accessor.antarchy$getTransparentSortY();
this.previousTransparentSortZ = accessor.antarchy$getTransparentSortZ();
this.dispatcher = renderer.getSectionRenderDispatcher();
this.previousDispatcherCamera = dispatcher.getCameraPosition();
accessor.antarchy$setLastCameraSectionX(state.lastSectionX);
accessor.antarchy$setLastCameraSectionY(state.lastSectionY);
accessor.antarchy$setLastCameraSectionZ(state.lastSectionZ);
accessor.antarchy$setPrevCamX(state.prevCamX);
accessor.antarchy$setPrevCamY(state.prevCamY);
accessor.antarchy$setPrevCamZ(state.prevCamZ);
accessor.antarchy$setPrevCamRotX(state.prevCamRotX);
accessor.antarchy$setPrevCamRotY(state.prevCamRotY);
accessor.antarchy$setTransparentSortX(state.transparentSortX);
accessor.antarchy$setTransparentSortY(state.transparentSortY);
accessor.antarchy$setTransparentSortZ(state.transparentSortZ);
accessor.antarchy$setCloudBuffer(state.cloudBuffer);
accessor.antarchy$setGenerateClouds(state.generateClouds);
accessor.antarchy$setPrevCloudX(state.prevCloudX);
accessor.antarchy$setPrevCloudY(state.prevCloudY);
accessor.antarchy$setPrevCloudZ(state.prevCloudZ);
accessor.antarchy$setPrevCloudColor(state.prevCloudColor);
accessor.antarchy$setPrevCloudsType(state.prevCloudsType);
}
@Override
public void close() {
if (closed) {
return;
}
closed = true;
state.lastSectionX = accessor.antarchy$getLastCameraSectionX();
state.lastSectionY = accessor.antarchy$getLastCameraSectionY();
state.lastSectionZ = accessor.antarchy$getLastCameraSectionZ();
state.prevCamX = accessor.antarchy$getPrevCamX();
state.prevCamY = accessor.antarchy$getPrevCamY();
state.prevCamZ = accessor.antarchy$getPrevCamZ();
state.prevCamRotX = accessor.antarchy$getPrevCamRotX();
state.prevCamRotY = accessor.antarchy$getPrevCamRotY();
state.transparentSortX = accessor.antarchy$getTransparentSortX();
state.transparentSortY = accessor.antarchy$getTransparentSortY();
state.transparentSortZ = accessor.antarchy$getTransparentSortZ();
state.cloudBuffer = accessor.antarchy$getCloudBuffer();
state.generateClouds = accessor.antarchy$getGenerateClouds();
state.prevCloudX = accessor.antarchy$getPrevCloudX();
state.prevCloudY = accessor.antarchy$getPrevCloudY();
state.prevCloudZ = accessor.antarchy$getPrevCloudZ();
state.prevCloudColor = accessor.antarchy$getPrevCloudColor();
state.prevCloudsType = accessor.antarchy$getPrevCloudsType();
accessor.antarchy$setViewArea(previousArea);
accessor.antarchy$setSectionOcclusionGraph(previousGraph);
accessor.antarchy$setVisibleSections(previousVisibleSections);
accessor.antarchy$setGlobalBlockEntities(previousGlobalBlockEntities);
accessor.antarchy$setCloudBuffer(previousCloudBuffer);
accessor.antarchy$setGenerateClouds(previousGenerateClouds);
accessor.antarchy$setPrevCloudX(previousCloudX);
accessor.antarchy$setPrevCloudY(previousCloudY);
accessor.antarchy$setPrevCloudZ(previousCloudZ);
accessor.antarchy$setPrevCloudColor(previousCloudColor);
accessor.antarchy$setPrevCloudsType(previousCloudsType);
accessor.antarchy$setRainSoundTime(previousRainSoundTime);
accessor.antarchy$setLastCameraSectionX(previousSectionX);
accessor.antarchy$setLastCameraSectionY(previousSectionY);
accessor.antarchy$setLastCameraSectionZ(previousSectionZ);
accessor.antarchy$setPrevCamX(previousPrevCamX);
accessor.antarchy$setPrevCamY(previousPrevCamY);
accessor.antarchy$setPrevCamZ(previousPrevCamZ);
accessor.antarchy$setPrevCamRotX(previousPrevCamRotX);
accessor.antarchy$setPrevCamRotY(previousPrevCamRotY);
accessor.antarchy$setTransparentSortX(previousTransparentSortX);
accessor.antarchy$setTransparentSortY(previousTransparentSortY);
accessor.antarchy$setTransparentSortZ(previousTransparentSortZ);
dispatcher.setCamera(previousDispatcherCamera);
blockEntityDispatcher.prepare(previousBlockEntityLevel, previousBlockEntityCamera, previousBlockEntityHitResult);
}
}
}
@@ -0,0 +1,193 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.mixins.client.LevelRendererPortalSceneInvoker;
import com.craisinlord.antarchy.mixins.client.LevelRendererPortalViewAreaAccessor;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.platform.Lighting;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Camera;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.FogRenderer;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.particle.ParticleRenderType;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.AABB;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
public final class PortalGunProxySceneRenderer {
private PortalGunProxySceneRenderer() {
}
public static void render(Minecraft minecraft, LevelRenderer renderer, Camera camera, float partialTick, Matrix4f modelViewMatrix, Matrix4f projectionMatrix, String tracePair) {
Vec3 cameraPosition = camera.getPosition();
double cameraX = cameraPosition.x;
double cameraY = cameraPosition.y;
double cameraZ = cameraPosition.z;
Frustum frustum = new Frustum(modelViewMatrix, projectionMatrix);
frustum.prepare(cameraX, cameraY, cameraZ);
LevelRendererPortalSceneInvoker scene = (LevelRendererPortalSceneInvoker) renderer;
float renderDistance = minecraft.gameRenderer.getRenderDistance();
BlockPos cameraBlockPos = BlockPos.containing(cameraX, cameraY, cameraZ);
boolean foggy = minecraft.level.effects().isFoggyAt(cameraBlockPos.getX(), cameraBlockPos.getY())
|| minecraft.gui.getBossOverlay().shouldCreateWorldFog();
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "sky", "camera=" + cameraPosition)) {
RenderSystem.setShaderGameTime(minecraft.level.getGameTime(), partialTick);
minecraft.getBlockEntityRenderDispatcher().prepare(minecraft.level, camera, minecraft.hitResult);
minecraft.getEntityRenderDispatcher().prepare(minecraft.level, camera, minecraft.crosshairPickEntity);
FogRenderer.setupColor(camera, partialTick, minecraft.level, minecraft.options.getEffectiveRenderDistance(), minecraft.gameRenderer.getDarkenWorldAmount(partialTick));
FogRenderer.levelFogColor();
FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_SKY, renderDistance, foggy, partialTick);
RenderSystem.setShader(net.minecraft.client.renderer.GameRenderer::getPositionShader);
renderer.renderSky(modelViewMatrix, projectionMatrix, partialTick, camera, foggy,
() -> FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_SKY, renderDistance, foggy, partialTick));
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "frustum-and-visibility", "renderDistance=" + rendererAccessDistance(renderer))) {
FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, Math.max(renderDistance, 32.0F), foggy, partialTick);
scene.antarchy$setupPortalScene(camera, frustum, false, minecraft.player != null && minecraft.player.isSpectator());
}
LevelRendererPortalViewAreaAccessor viewAreaAccessor = (LevelRendererPortalViewAreaAccessor) renderer;
ObjectArrayList<SectionRenderDispatcher.RenderSection> fullVisibleSections = viewAreaAccessor.antarchy$getVisibleSections();
ObjectArrayList<SectionRenderDispatcher.RenderSection> budgetedVisibleSections = new ObjectArrayList<>();
PortalGunPortalRendererPool.beginSceneBudget();
int scannedVisibleSections = 0;
while (scannedVisibleSections < fullVisibleSections.size() && PortalGunPortalRendererPool.remainingSectionBudgetNanos() > 0L) {
budgetedVisibleSections.add(fullVisibleSections.get(scannedVisibleSections++));
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "compile-visible-sections", "visibleTotal=" + fullVisibleSections.size() + " visibleScanned=" + scannedVisibleSections + " rebuildBudgetNanos=" + PortalGunPortalRendererPool.remainingSectionBudgetNanos())) {
viewAreaAccessor.antarchy$setVisibleSections(budgetedVisibleSections);
try {
scene.antarchy$compilePortalSections(camera);
} finally {
viewAreaAccessor.antarchy$setVisibleSections(fullVisibleSections);
}
}
PortalSceneRenderTrace.event(tracePair, "visible-sections-ready", "count=" + viewAreaAccessor.antarchy$getVisibleSections().size() + " scheduledRebuilds=" + PortalGunPortalRendererPool.scheduledSectionRebuilds());
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "opaque-terrain-layers", "sections=" + viewAreaAccessor.antarchy$getVisibleSections().size())) {
RenderSystem.enableDepthTest();
RenderSystem.depthMask(true);
scene.antarchy$renderPortalSectionLayer(RenderType.solid(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
scene.antarchy$renderPortalSectionLayer(RenderType.cutoutMipped(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
scene.antarchy$renderPortalSectionLayer(RenderType.cutout(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
}
if (minecraft.level.effects().constantAmbientLight()) {
Lighting.setupNetherLevel();
} else {
Lighting.setupLevel();
}
Matrix4fStack modelViewStack = RenderSystem.getModelViewStack();
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "entity-scene-state", "enter-model-view")) {
modelViewStack.pushMatrix();
modelViewStack.mul(modelViewMatrix);
RenderSystem.applyModelViewMatrix();
}
PoseStack poseStack = new PoseStack();
MultiBufferSource.BufferSource buffers = minecraft.renderBuffers().bufferSource();
try {
int entityCount = 0;
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "entities", "dispatch-start")) {
for (Entity entity : minecraft.level.entitiesForRendering()) {
if (!minecraft.getEntityRenderDispatcher().shouldRender(entity, frustum, cameraX, cameraY, cameraZ)
&& !entity.hasIndirectPassenger(minecraft.player)) {
continue;
}
if (entity == camera.getEntity() && !camera.isDetached()
&& (!(entity instanceof LivingEntity living) || !living.isSleeping())) {
continue;
}
if (entity instanceof Player player && player == minecraft.player && !player.isSpectator() && entity != camera.getEntity()) {
continue;
}
scene.antarchy$renderPortalEntity(entity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers);
entityCount++;
}
}
buffers.endBatch();
PortalSceneRenderTrace.event(tracePair, "entities-dispatched", "count=" + entityCount);
int blockEntityCount = 0;
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "block-entities", "visibleSections=" + viewAreaAccessor.antarchy$getVisibleSections().size())) {
for (var section : viewAreaAccessor.antarchy$getVisibleSections()) {
for (BlockEntity blockEntity : section.getCompiled().getRenderableBlockEntities()) {
renderBlockEntity(minecraft, blockEntity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers);
blockEntityCount++;
}
}
synchronized (viewAreaAccessor.antarchy$getGlobalBlockEntities()) {
for (BlockEntity blockEntity : viewAreaAccessor.antarchy$getGlobalBlockEntities()) {
if (frustum.isVisible(new AABB(blockEntity.getBlockPos()).inflate(1.0D))) {
renderBlockEntity(minecraft, blockEntity, cameraX, cameraY, cameraZ, partialTick, poseStack, buffers);
blockEntityCount++;
}
}
}
}
buffers.endBatch();
PortalSceneRenderTrace.event(tracePair, "block-entities-dispatched", "count=" + blockEntityCount);
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "translucent-and-particles", "rendering")) {
minecraft.particleEngine.render(minecraft.gameRenderer.lightTexture(), camera, partialTick, frustum, type -> !type.isTranslucent());
scene.antarchy$renderPortalSectionLayer(RenderType.translucent(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
buffers.endBatch(RenderType.lines());
buffers.endBatch();
scene.antarchy$renderPortalSectionLayer(RenderType.tripwire(), cameraX, cameraY, cameraZ, modelViewMatrix, projectionMatrix);
minecraft.particleEngine.render(minecraft.gameRenderer.lightTexture(), camera, partialTick, frustum, ParticleRenderType::isTranslucent);
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "clouds", minecraft.options.getCloudsType().toString())) {
if (minecraft.options.getCloudsType() != CloudStatus.OFF) {
renderer.renderClouds(poseStack, modelViewMatrix, projectionMatrix, partialTick, cameraX, cameraY, cameraZ);
}
}
try (PortalSceneRenderTrace.Phase ignored = PortalSceneRenderTrace.begin(tracePair, "weather-and-border", "rendering")) {
RenderSystem.depthMask(false);
scene.antarchy$renderPortalWeather(minecraft.gameRenderer.lightTexture(), partialTick, cameraX, cameraY, cameraZ);
scene.antarchy$renderPortalWorldBorder(camera);
}
} finally {
try {
buffers.endBatch();
} finally {
modelViewStack.popMatrix();
RenderSystem.applyModelViewMatrix();
RenderSystem.depthMask(true);
RenderSystem.enableDepthTest();
RenderSystem.enableCull();
RenderSystem.disableBlend();
}
}
}
private static int rendererAccessDistance(LevelRenderer renderer) {
return ((LevelRendererPortalViewAreaAccessor) renderer).antarchy$getViewArea().getViewDistance();
}
private static void renderBlockEntity(Minecraft minecraft, BlockEntity blockEntity, double cameraX, double cameraY, double cameraZ,
float partialTick, PoseStack poseStack, MultiBufferSource.BufferSource buffers) {
if (!blockEntity.hasLevel() || !blockEntity.getType().isValid(blockEntity.getBlockState())) {
return;
}
BlockPos pos = blockEntity.getBlockPos();
poseStack.pushPose();
poseStack.translate(pos.getX() - cameraX, pos.getY() - cameraY, pos.getZ() - cameraZ);
minecraft.getBlockEntityRenderDispatcher().render(blockEntity, partialTick, poseStack, buffers);
poseStack.popPose();
}
}
@@ -0,0 +1,82 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.Antarchy;
import java.util.HashSet;
import java.util.Set;
public final class PortalSceneRenderTrace {
private static final Set<String> LOGGED_PHASES = new HashSet<>();
private static long generation;
private static String armedKey = "";
private PortalSceneRenderTrace() {
}
public static synchronized void arm(String key) {
if (!armedKey.equals(key)) {
armedKey = key;
generation++;
LOGGED_PHASES.clear();
}
}
public static synchronized void reset() {
armedKey = "";
LOGGED_PHASES.clear();
generation++;
}
public static Phase begin(String pair, String phase, String detail) {
long currentGeneration;
boolean shouldLog;
synchronized (PortalSceneRenderTrace.class) {
currentGeneration = generation;
shouldLog = LOGGED_PHASES.add(currentGeneration + ":" + pair + ":" + phase);
}
if (shouldLog) {
Antarchy.LOGGER.info("Portal scene trace begin generation={} pair={} phase={} detail={}", currentGeneration, pair, phase, detail);
}
return new Phase(currentGeneration, pair, phase, shouldLog, System.nanoTime());
}
public static void event(String pair, String event, String detail) {
long currentGeneration;
boolean shouldLog;
synchronized (PortalSceneRenderTrace.class) {
currentGeneration = generation;
shouldLog = LOGGED_PHASES.add(currentGeneration + ":" + pair + ":event:" + event);
}
if (shouldLog) {
Antarchy.LOGGER.info("Portal scene trace event generation={} pair={} event={} detail={}", currentGeneration, pair, event, detail);
}
}
public static final class Phase implements AutoCloseable {
private final long generation;
private final String pair;
private final String phase;
private final boolean logCompletion;
private final long startNanos;
private boolean closed;
private Phase(long generation, String pair, String phase, boolean logCompletion, long startNanos) {
this.generation = generation;
this.pair = pair;
this.phase = phase;
this.logCompletion = logCompletion;
this.startNanos = startNanos;
}
@Override
public void close() {
if (this.closed) {
return;
}
this.closed = true;
if (this.logCompletion) {
Antarchy.LOGGER.info("Portal scene trace complete generation={} pair={} phase={} durationMs={}",
this.generation, this.pair, this.phase, (System.nanoTime() - this.startNanos) / 1_000_000.0D);
}
}
}
}
@@ -0,0 +1,196 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.systems.RenderSystem;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import com.mojang.blaze3d.pipeline.TextureTarget;
public final class PortalStencilTarget {
private static final Map<RenderTarget, Attachment> ATTACHMENTS = Collections.synchronizedMap(new IdentityHashMap<>());
private static final Set<RenderTarget> DIRECT_TARGETS = Collections.newSetFromMap(new IdentityHashMap<>());
private PortalStencilTarget() {
}
public static void prepareMainTarget(RenderTarget target) {
try {
java.lang.reflect.Method enabled = target.getClass().getMethod("isStencilEnabled");
if (!Boolean.TRUE.equals(enabled.invoke(target))) {
target.getClass().getMethod("enableStencil").invoke(target);
}
} catch (ReflectiveOperationException ignored) {
}
target.bindWrite(true);
}
public static Scope beginDirect(RenderTarget target, TextureTarget depthBackup, Runnable drawApertureMask) {
target.bindWrite(true);
if (GL11.glGetInteger(GL11.GL_STENCIL_BITS) <= 0) {
throw new IllegalStateException("Main render target has no stencil attachment");
}
depthBackup.copyDepthFrom(target);
DIRECT_TARGETS.add(target);
target.bindWrite(true);
RenderSystem.disableScissor();
RenderSystem.stencilMask(0xFF);
RenderSystem.clearStencil(0);
GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
RenderSystem.stencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
RenderSystem.colorMask(false, false, false, false);
RenderSystem.depthMask(false);
RenderSystem.disableDepthTest();
GL11.glEnable(GL11.GL_STENCIL_TEST);
try {
Objects.requireNonNull(drawApertureMask).run();
} catch (RuntimeException | Error exception) {
resetState();
target.copyDepthFrom(depthBackup);
DIRECT_TARGETS.remove(target);
throw exception;
}
RenderSystem.colorMask(true, true, true, true);
RenderSystem.depthMask(true);
RenderSystem.stencilMask(0x00);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
RenderSystem.enableDepthTest();
return new Scope(target, depthBackup);
}
public static Scope begin(RenderTarget target, Runnable drawApertureMask) {
ensureAttachment(target);
target.bindWrite(true);
RenderSystem.disableScissor();
RenderSystem.stencilMask(0xFF);
RenderSystem.clearStencil(0);
GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
RenderSystem.stencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
RenderSystem.colorMask(false, false, false, false);
RenderSystem.depthMask(false);
RenderSystem.disableDepthTest();
GL11.glEnable(GL11.GL_STENCIL_TEST);
try {
Objects.requireNonNull(drawApertureMask).run();
} catch (RuntimeException | Error exception) {
resetState();
throw exception;
}
RenderSystem.colorMask(true, true, true, true);
RenderSystem.depthMask(true);
RenderSystem.stencilMask(0x00);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
RenderSystem.enableDepthTest();
return new Scope(target);
}
public static void activate(RenderTarget target) {
if (!ATTACHMENTS.containsKey(target) && !DIRECT_TARGETS.contains(target)) {
return;
}
target.bindWrite(true);
GL11.glEnable(GL11.GL_STENCIL_TEST);
RenderSystem.stencilMask(0x00);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
}
public static void release(RenderTarget target) {
DIRECT_TARGETS.remove(target);
Attachment attachment = ATTACHMENTS.remove(target);
if (attachment != null) {
GL30.glDeleteRenderbuffers(attachment.renderbuffer());
}
}
public static void clear() {
for (Attachment attachment : ATTACHMENTS.values()) {
GL30.glDeleteRenderbuffers(attachment.renderbuffer());
}
ATTACHMENTS.clear();
DIRECT_TARGETS.clear();
}
private static void ensureAttachment(RenderTarget target) {
Attachment attachment = ATTACHMENTS.get(target);
if (attachment != null
&& attachment.framebuffer() == target.frameBufferId
&& attachment.width() == target.width
&& attachment.height() == target.height) {
return;
}
if (attachment != null) {
GL30.glDeleteRenderbuffers(attachment.renderbuffer());
}
int previousFramebuffer = GL11.glGetInteger(GL30.GL_FRAMEBUFFER_BINDING);
int previousRenderbuffer = GL11.glGetInteger(GL30.GL_RENDERBUFFER_BINDING);
int renderbuffer = GL30.glGenRenderbuffers();
try {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, target.frameBufferId);
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, renderbuffer);
GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, target.width, target.height);
GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, renderbuffer);
int status = GL30.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER);
if (status != GL30.GL_FRAMEBUFFER_COMPLETE) {
GL30.glDeleteRenderbuffers(renderbuffer);
throw new IllegalStateException("Portal view framebuffer is incomplete with packed depth/stencil attachment: 0x" + Integer.toHexString(status));
}
ATTACHMENTS.put(target, new Attachment(target.frameBufferId, renderbuffer, target.width, target.height));
} finally {
GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, previousRenderbuffer);
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, previousFramebuffer);
}
}
private static void resetState() {
GL11.glDisable(GL11.GL_STENCIL_TEST);
RenderSystem.stencilMask(0xFF);
RenderSystem.stencilFunc(GL11.GL_ALWAYS, 0, 0xFF);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.depthMask(true);
RenderSystem.enableDepthTest();
}
private record Attachment(int framebuffer, int renderbuffer, int width, int height) {
}
public static final class Scope implements AutoCloseable {
private boolean closed;
private final RenderTarget target;
private final TextureTarget depthBackup;
private Scope(RenderTarget target) {
this(target, null);
}
private Scope(RenderTarget target, TextureTarget depthBackup) {
this.target = Objects.requireNonNull(target);
this.depthBackup = depthBackup;
}
@Override
public void close() {
if (closed) {
return;
}
closed = true;
if (this.depthBackup != null) {
this.target.copyDepthFrom(this.depthBackup);
this.target.bindWrite(true);
DIRECT_TARGETS.remove(this.target);
}
resetState();
}
}
}
@@ -0,0 +1,7 @@
package com.craisinlord.antarchy.content.client.renderer;
public interface PortalViewSectionMarker {
void antarchy$markPortalViewSection();
boolean antarchy$isPortalViewSection();
}
@@ -0,0 +1,117 @@
package com.craisinlord.antarchy.content.client.renderer;
import com.craisinlord.antarchy.Antarchy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.Deque;
import net.minecraft.client.Camera;
public final class SodiumCompat {
private static final String SODIUM_RENDERER_CLASS = "net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer";
private static final ThreadLocal<RenderState> RENDER_STATE = ThreadLocal.withInitial(RenderState::new);
private static boolean restoreWarningLogged;
private SodiumCompat() {
}
public static boolean isLoaded() {
String resource = SODIUM_RENDERER_CLASS.replace('.', '/') + ".class";
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
if (contextLoader != null && contextLoader.getResource(resource) != null) {
return true;
}
ClassLoader ownLoader = SodiumCompat.class.getClassLoader();
return ownLoader != null && ownLoader.getResource(resource) != null;
}
public static void captureTerrainSetup(Camera camera, Object viewport, boolean spectator, boolean updateChunks) {
RENDER_STATE.get().activeSetup = new TerrainSetup(camera, viewport, spectator, updateChunks);
}
public static TerrainScope beginPortalView(Camera portalCamera) {
RenderState state = RENDER_STATE.get();
TerrainSetup previousSetup = state.activeSetup;
state.previousSetups.push(new PreviousSetup(previousSetup));
if (previousSetup != null) {
restoreTerrainSetup(previousSetup.withCamera(portalCamera), state);
}
return () -> {
PreviousSetup previous = state.previousSetups.pop();
if (previous.setup() == null) {
state.activeSetup = null;
return;
}
restoreTerrainSetup(previous.setup(), state);
};
}
public static void clear() {
RENDER_STATE.remove();
}
private static void restoreTerrainSetup(TerrainSetup setup, RenderState state) {
try {
ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
Class<?> rendererClass = Class.forName(SODIUM_RENDERER_CLASS, false,
contextLoader == null ? SodiumCompat.class.getClassLoader() : contextLoader);
Object renderer = rendererClass.getMethod("instanceNullable").invoke(null);
if (renderer == null) {
return;
}
Method terrainSetup = null;
for (Method method : rendererClass.getMethods()) {
Class<?>[] parameters = method.getParameterTypes();
if (method.getName().equals("setupTerrain")
&& parameters.length == 4
&& parameters[0].isInstance(setup.camera())
&& parameters[1].isInstance(setup.viewport())
&& parameters[2] == boolean.class
&& parameters[3] == boolean.class) {
terrainSetup = method;
break;
}
}
if (terrainSetup == null) {
logRestoreFailure("Sodium terrain setup method was not found", null);
state.activeSetup = setup;
return;
}
terrainSetup.invoke(renderer, setup.camera(), setup.viewport(), setup.spectator(), setup.updateChunks());
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | LinkageError exception) {
logRestoreFailure("Could not restore Sodium terrain after a portal view", exception);
state.activeSetup = setup;
}
}
private static void logRestoreFailure(String message, Throwable exception) {
if (!restoreWarningLogged) {
restoreWarningLogged = true;
if (exception == null) {
Antarchy.LOGGER.warn(message);
} else {
Antarchy.LOGGER.warn(message, exception);
}
}
}
@FunctionalInterface
public interface TerrainScope extends AutoCloseable {
@Override
void close();
}
private static final class RenderState {
private final Deque<PreviousSetup> previousSetups = new ArrayDeque<>();
private TerrainSetup activeSetup;
}
private record PreviousSetup(TerrainSetup setup) {
}
private record TerrainSetup(Camera camera, Object viewport, boolean spectator, boolean updateChunks) {
private TerrainSetup withCamera(Camera camera) {
return new TerrainSetup(camera, this.viewport, this.spectator, this.updateChunks);
}
}
}
@@ -2,11 +2,13 @@ package com.craisinlord.antarchy.content.computer;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.command.QueenLocateCommand;
import com.craisinlord.antarchy.content.entity.brutalfly.BrutalflyEntity;
import com.craisinlord.antarchy.content.worldgen.elythia.KingsTreeGrid;
import com.craisinlord.antos.api.archive.ArchiveLocatorRegistry;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB;
/** Archive coordinate lookups for royals that live on grids rather than in vanilla structures. */
public final class AntarchyArchiveLocators {
@@ -24,6 +26,14 @@ public final class AntarchyArchiveLocators {
Heightmap.Types.WORLD_SURFACE_WG, level, level.getChunkSource().randomState());
return new BlockPos(center.getX(), surfaceY, center.getZ());
});
ArchiveLocatorRegistry.register(id("brutalfly"), (level, origin) -> {
AABB searchArea = AABB.ofSize(origin.getCenter(), 1_024.0D, 1_024.0D, 1_024.0D);
return level.getEntitiesOfClass(BrutalflyEntity.class, searchArea, BrutalflyEntity::isAlive)
.stream()
.min(java.util.Comparator.comparingDouble(entity -> entity.distanceToSqr(origin.getCenter())))
.map(entity -> entity.blockPosition())
.orElse(null);
});
}
private static ResourceLocation id(String path) {
@@ -4,6 +4,7 @@ import com.craisinlord.antarchy.content.AntarchyObjects;
import com.craisinlord.antarchy.content.entity.glimmer.GlimmerVariant;
import com.craisinlord.antarchy.content.item.GiantFryingPanToolHelper;
import com.craisinlord.antarchy.content.item.GlimmerBottleItem;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import com.craisinlord.antarchy.content.item.royal.RoyalGearHelper;
import com.craisinlord.antarchy.content.item.ultimate.UltimateGearHelper;
import java.util.HashMap;
@@ -425,7 +426,7 @@ public final class CreativeTabContents {
entry("shrink_ray", "combat"),
entry("growth_ray", "combat"),
entry("gravity_gun", "combat"),
// entry("portal_gun", "combat"), // deferred — out of scope for 2.0
entry("portal_gun", "combat"),
entry("eye_of_the_storm", "combat"),
entry("water_cannon", "combat"),
entry("attitude_adjuster", "combat"),
@@ -640,6 +641,14 @@ public final class CreativeTabContents {
}
continue;
}
if ("portal_gun".equals(entry.path())) {
Item portalGun = item(entry.path());
output.accept(portalGun);
output.accept(PortalGunItem.createGlobalVariant(portalGun, "Chell"));
output.accept(PortalGunItem.createGlobalVariant(portalGun, "Atlas"));
output.accept(PortalGunItem.createGlobalVariant(portalGun, "P-body"));
continue;
}
output.accept(item(entry.path()));
}
for (String potionPath : ANTARCHY_POTION_FAMILIES) {
@@ -669,6 +678,14 @@ public final class CreativeTabContents {
output.insertAfter(vanillaItem(entry.insertAfterPath()), item(entry.path()));
continue;
}
if ("portal_gun".equals(entry.path())) {
Item portalGun = item(entry.path());
output.accept(portalGun);
output.accept(PortalGunItem.createGlobalVariant(portalGun, "Chell"));
output.accept(PortalGunItem.createGlobalVariant(portalGun, "Atlas"));
output.accept(PortalGunItem.createGlobalVariant(portalGun, "P-body"));
continue;
}
if ("combat".equals(tabPath)) {
if (isUltimateArmor(entry.path())) {
output.accept(UltimateGearHelper.createUltimateArmorStack(item(entry.path()), holders));
@@ -0,0 +1,10 @@
package com.craisinlord.antarchy.content.gravity;
import net.minecraft.world.entity.LivingEntity;
import org.jetbrains.annotations.Nullable;
/** Exposes Scorched Guns' base ProjectileEntity fields to mixins targeting its subclasses. */
public interface ScgunsProjectileAccess {
@Nullable LivingEntity antarchy$getScgunsShooter();
double antarchy$getScgunsModifiedGravity();
}
@@ -59,6 +59,8 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "textures/item/big_bertha/big_bertha.png");
private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath("antarchy", "animations/static_item.animation.json");
private static final String MODE_TAG = "antarchy.big_bertha_mode";
public static final int MODE_TREMORSAURUS = 8;
public static final int MODE_FORSAKEN = 9;
private static final String SPIN_TICKS_TAG = "antarchy.big_bertha_spin_ticks";
private static final String SPIN_DIRECTION_X_TAG = "antarchy.big_bertha_spin_direction_x";
private static final String SPIN_DIRECTION_Z_TAG = "antarchy.big_bertha_spin_direction_z";
@@ -161,7 +163,7 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
case NIGHTMARE -> applyNightmareHit(player, target, serverLevel);
case KRAKEN -> applyKrakenHit(player, target, serverLevel);
case HERCULES -> applyHerculesHit(player, target, serverLevel);
case MOLEVORE, TORETERROR -> {
case MOLEVORE, TORETERROR, TREMORSAURUS, FORSAKEN -> {
}
}
@@ -389,6 +391,10 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
}
}
public static int getModeId(ItemStack stack) {
return getMode(stack).id;
}
public boolean tryCycleModeFromInput(Level level, Player player, ItemStack stack) {
if (player.getMainHandItem() != stack) {
return false;
@@ -650,6 +656,8 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
MOLEVORE(3, "tooltip.antarchy.big_bertha.mode.molevore", "message.antarchy.big_bertha.mode.molevore"),
LUCID(4, "tooltip.antarchy.big_bertha.mode.lucid", "message.antarchy.big_bertha.mode.lucid"),
TORETERROR(5, "tooltip.antarchy.big_bertha.mode.toreterror", "message.antarchy.big_bertha.mode.toreterror"),
TREMORSAURUS(MODE_TREMORSAURUS, "tooltip.antarchy.big_bertha.mode.tremorsaurus", "message.antarchy.big_bertha.mode.tremorsaurus"),
FORSAKEN(MODE_FORSAKEN, "tooltip.antarchy.big_bertha.mode.forsaken", "message.antarchy.big_bertha.mode.forsaken"),
HERCULES(6, "tooltip.antarchy.big_bertha.mode.hercules", "message.antarchy.big_bertha.mode.hercules");
private final int id;
@@ -671,7 +679,9 @@ public class BigBerthaItem extends SwordItem implements GeoItem {
case KRAKEN -> HERCULES;
case HERCULES -> MOLEVORE;
case MOLEVORE -> TORETERROR;
case TORETERROR -> NONE;
case TORETERROR -> TREMORSAURUS;
case TREMORSAURUS -> FORSAKEN;
case FORSAKEN -> NONE;
};
}
@@ -1,173 +0,0 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.content.AntarchyTags;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.state.BlockState;
public final class MinersDreamCaveGenerator {
private static final double MIN_HORIZONTAL_RADIUS = 2.0D;
private static final double MAX_HORIZONTAL_RADIUS = 5.0D;
private static final double MIN_VERTICAL_RADIUS = 1.5D;
private static final double MAX_VERTICAL_RADIUS = 4.0D;
private static final double MAX_VERTICAL_DEVIATION = 8.0D;
private static final double SOFT_VERTICAL_DEVIATION = 4.0D;
private static final int WORLD_MARGIN = 3;
private MinersDreamCaveGenerator() {
}
public record CaveNode(BlockPos center, double horizontalRadius, double verticalRadius) {
}
public record CaveGenerationResult(List<CaveNode> nodes, Set<Integer> torchCheckpoints) {
}
public static CaveGenerationResult generate(BlockPos start, Direction initialDirection, int length,
int torchSpacing, int minBuildHeight, int maxBuildHeight, RandomSource random) {
List<CaveNode> nodes = new ArrayList<>(length);
Set<Integer> torchCheckpoints = new HashSet<>();
double x = start.getX() + 0.5D;
double y = start.getY() + 0.5D;
double z = start.getZ() + 0.5D;
double startY = y;
double headingX = initialDirection.getStepX();
double headingZ = initialDirection.getStepZ();
if (headingX == 0.0D && headingZ == 0.0D) {
headingX = 1.0D;
}
double verticalVelocity = 0.0D;
double horizontalRadius = 3.0D;
double verticalRadius = 2.0D;
int minY = minBuildHeight + WORLD_MARGIN;
int maxY = maxBuildHeight - WORLD_MARGIN;
int spacing = Math.max(4, torchSpacing);
int traveledSinceTorch = 0;
int nextTorchDistance = spacing + random.nextInt(5) - 2;
for (int i = 0; i < length; i++) {
BlockPos center = BlockPos.containing(x, Mth.clamp(y, minY, maxY), z);
nodes.add(new CaveNode(center, horizontalRadius, verticalRadius));
traveledSinceTorch++;
if (traveledSinceTorch >= Math.max(1, nextTorchDistance)) {
torchCheckpoints.add(i);
traveledSinceTorch = 0;
nextTorchDistance = spacing + random.nextInt(5) - 2;
}
double turnDegrees = random.nextGaussian() * 8.0D;
if (random.nextFloat() < 0.1F) {
turnDegrees = random.nextGaussian() * 15.0D;
}
turnDegrees = Mth.clamp(turnDegrees, -25.0D, 25.0D);
double turnRadians = Math.toRadians(turnDegrees);
double cos = Math.cos(turnRadians);
double sin = Math.sin(turnRadians);
double newHeadingX = headingX * cos - headingZ * sin;
double newHeadingZ = headingX * sin + headingZ * cos;
double headingLength = Math.sqrt(newHeadingX * newHeadingX + newHeadingZ * newHeadingZ);
headingX = newHeadingX / headingLength;
headingZ = newHeadingZ / headingLength;
verticalVelocity += random.nextGaussian() * 0.15D;
double deviation = y - startY;
verticalVelocity -= deviation * 0.06D;
if (deviation > SOFT_VERTICAL_DEVIATION) {
verticalVelocity -= 0.08D;
} else if (deviation < -SOFT_VERTICAL_DEVIATION) {
verticalVelocity += 0.08D;
}
verticalVelocity = Mth.clamp(verticalVelocity, -0.6D, 0.6D);
x += headingX;
z += headingZ;
y = Mth.clamp(y + verticalVelocity, startY - MAX_VERTICAL_DEVIATION, startY + MAX_VERTICAL_DEVIATION);
horizontalRadius = Mth.clamp(horizontalRadius + random.nextGaussian() * 0.15D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS);
verticalRadius = Mth.clamp(verticalRadius + random.nextGaussian() * 0.12D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
if (random.nextFloat() < 0.05F) {
horizontalRadius = Mth.clamp(horizontalRadius + 0.6D, MIN_HORIZONTAL_RADIUS, MAX_HORIZONTAL_RADIUS);
verticalRadius = Mth.clamp(verticalRadius + 0.5D, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
}
}
return new CaveGenerationResult(nodes, torchCheckpoints);
}
public static void collectNodeCandidates(ServerLevel level, CaveNode node, LongOpenHashSet visited, RandomSource random, List<BlockPos> out) {
BlockPos center = node.center();
int hr = Mth.ceil(node.horizontalRadius());
double upExtent = node.verticalRadius() * 1.25D;
double downExtent = node.verticalRadius() * 0.65D;
int upR = Mth.ceil(upExtent);
int downR = Mth.ceil(downExtent);
int minBuildHeight = level.getMinBuildHeight();
int maxBuildHeight = level.getMaxBuildHeight();
for (int dy = -downR; dy <= upR; dy++) {
double vExtent = dy >= 0 ? upExtent : downExtent;
if (vExtent <= 0.0D) {
continue;
}
double vTerm = (dy * dy) / (vExtent * vExtent);
if (vTerm > 1.05D) {
continue;
}
for (int dx = -hr; dx <= hr; dx++) {
double hxTerm = (dx * dx) / (node.horizontalRadius() * node.horizontalRadius());
for (int dz = -hr; dz <= hr; dz++) {
double hzTerm = (dz * dz) / (node.horizontalRadius() * node.horizontalRadius());
double distance = hxTerm + vTerm + hzTerm;
if (distance > 1.0D) {
continue;
}
if (distance > 0.7D) {
double edgeChance = (1.0D - distance) / 0.3D * 0.85D;
if (random.nextFloat() > edgeChance) {
continue;
}
}
BlockPos pos = center.offset(dx, dy, dz);
if (pos.getY() < minBuildHeight || pos.getY() >= maxBuildHeight) {
continue;
}
long packed = pos.asLong();
if (visited.contains(packed)) {
continue;
}
BlockState state = level.getBlockState(pos);
if (!state.is(AntarchyTags.Blocks.MINERS_DREAM_CARVABLE)) {
continue;
}
if (level.getBlockEntity(pos) != null) {
continue;
}
visited.add(packed);
out.add(pos);
}
}
}
}
public static boolean isChunkLoaded(ServerLevel level, BlockPos pos) {
return level.isLoaded(pos);
}
}
@@ -1,152 +0,0 @@
package com.craisinlord.antarchy.content.item;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.BlockParticleOption;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import org.joml.Vector3f;
public final class MinersDreamExcavation {
private static final Vector3f FLYTRAP_GOO_COLOR = new Vector3f(0.35F, 0.85F, 0.4F);
private static final int SLICES_BETWEEN_RUMBLE = 4;
private final UUID owner;
private final ResourceKey<Level> dimension;
private final List<MinersDreamCaveGenerator.CaveNode> nodes;
private final Set<Integer> torchCheckpoints;
private final LongOpenHashSet visited = new LongOpenHashSet();
private int nodeIndex = 0;
private List<BlockPos> currentNodePositions = null;
private int positionCursor = 0;
private int slicesSinceRumble = 0;
private boolean finished = false;
public MinersDreamExcavation(UUID owner, ResourceKey<Level> dimension, List<MinersDreamCaveGenerator.CaveNode> nodes, Set<Integer> torchCheckpoints) {
this.owner = owner;
this.dimension = dimension;
this.nodes = nodes;
this.torchCheckpoints = torchCheckpoints;
}
public UUID owner() {
return owner;
}
public ResourceKey<Level> dimension() {
return dimension;
}
public boolean isFinished() {
return finished;
}
public void tick(ServerLevel level, int blockBudget) {
RandomSource random = level.getRandom();
int remaining = blockBudget;
while (remaining > 0 && !finished) {
if (currentNodePositions == null) {
if (nodeIndex >= nodes.size()) {
finished = true;
playCompletionSound(level);
return;
}
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
if (!MinersDreamCaveGenerator.isChunkLoaded(level, node.center())) {
advanceNode(level, node);
continue;
}
currentNodePositions = new ArrayList<>();
MinersDreamCaveGenerator.collectNodeCandidates(level, node, visited, random, currentNodePositions);
positionCursor = 0;
}
if (currentNodePositions.isEmpty()) {
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
advanceNode(level, node);
continue;
}
int endIndex = Math.min(currentNodePositions.size(), positionCursor + remaining);
for (; positionCursor < endIndex; positionCursor++) {
BlockPos pos = currentNodePositions.get(positionCursor);
BlockState removedState = level.getBlockState(pos);
if (removedState.isAir()) {
continue;
}
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
remaining--;
if (random.nextFloat() < 0.12F) {
spawnBreakParticles(level, pos, removedState, random);
}
}
if (positionCursor >= currentNodePositions.size()) {
MinersDreamCaveGenerator.CaveNode node = nodes.get(nodeIndex);
advanceNode(level, node);
}
}
}
private void advanceNode(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) {
RandomSource random = level.getRandom();
spawnFrontDust(level, node, random);
slicesSinceRumble++;
if (slicesSinceRumble >= SLICES_BETWEEN_RUMBLE) {
slicesSinceRumble = 0;
playRumble(level, node);
}
if (torchCheckpoints.contains(nodeIndex)) {
MinersDreamTorchPlacer.tryPlaceTorch(level, node.center(), random);
}
currentNodePositions = null;
positionCursor = 0;
nodeIndex++;
}
private void spawnBreakParticles(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) {
level.sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state),
pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 2, 0.25D, 0.25D, 0.25D, 0.02D);
if (random.nextFloat() < 0.2F) {
level.sendParticles(new DustParticleOptions(FLYTRAP_GOO_COLOR, 1.0F),
pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, 1, 0.2D, 0.2D, 0.2D, 0.0D);
}
}
private void spawnFrontDust(ServerLevel level, MinersDreamCaveGenerator.CaveNode node, RandomSource random) {
BlockPos center = node.center();
level.sendParticles(ParticleTypes.CLOUD, center.getX() + 0.5D, center.getY() + 0.5D, center.getZ() + 0.5D,
3, node.horizontalRadius() * 0.4D, node.verticalRadius() * 0.3D, node.horizontalRadius() * 0.4D, 0.01D);
}
private void playRumble(ServerLevel level, MinersDreamCaveGenerator.CaveNode node) {
BlockPos center = node.center();
level.playSound(null, center, SoundEvents.STONE_BREAK, SoundSource.BLOCKS, 0.5F, 0.7F);
}
private void playCompletionSound(ServerLevel level) {
if (nodes.isEmpty()) {
return;
}
BlockPos center = nodes.get(nodes.size() - 1).center();
level.playSound(null, center, SoundEvents.BEACON_ACTIVATE, SoundSource.PLAYERS, 0.4F, 1.4F);
}
}
@@ -1,81 +0,0 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
public final class MinersDreamExcavationManager {
private static final int MAX_ACTIVE_TASKS = 8;
private static final int SAFE_MIN_RANGE = 8;
private static final int SAFE_MAX_RANGE = 256;
private static final Map<UUID, MinersDreamExcavation> ACTIVE = new ConcurrentHashMap<>();
private MinersDreamExcavationManager() {
}
public static boolean hasActiveExcavation(UUID owner) {
return ACTIVE.containsKey(owner);
}
public static MinersDreamExcavation tryStart(ServerLevel level, UUID owner, BlockPos startPos, Direction direction) {
if (ACTIVE.containsKey(owner) || ACTIVE.size() >= MAX_ACTIVE_TASKS) {
return null;
}
int minRange = Mth.clamp(AntarchySettings.minersDreamMinimumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE);
int maxRange = Mth.clamp(AntarchySettings.minersDreamMaximumRange(), SAFE_MIN_RANGE, SAFE_MAX_RANGE);
if (minRange > maxRange) {
int swap = minRange;
minRange = maxRange;
maxRange = swap;
}
RandomSource random = level.getRandom();
int length = minRange == maxRange ? minRange : minRange + random.nextInt(maxRange - minRange + 1);
MinersDreamCaveGenerator.CaveGenerationResult result = MinersDreamCaveGenerator.generate(
startPos,
direction,
length,
AntarchySettings.minersDreamTorchSpacing(),
level.getMinBuildHeight(),
level.getMaxBuildHeight(),
random
);
if (result.nodes().isEmpty()) {
return null;
}
MinersDreamExcavation excavation = new MinersDreamExcavation(owner, level.dimension(), result.nodes(), result.torchCheckpoints());
ACTIVE.put(owner, excavation);
return excavation;
}
public static void tick(ServerLevel level) {
if (ACTIVE.isEmpty()) {
return;
}
int budget = Math.max(1, AntarchySettings.minersDreamBlocksPerTick());
Iterator<Map.Entry<UUID, MinersDreamExcavation>> iterator = ACTIVE.entrySet().iterator();
while (iterator.hasNext()) {
MinersDreamExcavation excavation = iterator.next().getValue();
if (!excavation.dimension().equals(level.dimension())) {
continue;
}
excavation.tick(level, budget);
if (excavation.isFinished()) {
iterator.remove();
}
}
}
}
@@ -1,79 +0,0 @@
package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
public final class MinersDreamItem extends Item {
public MinersDreamItem(Properties properties) {
super(properties);
}
@Override
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
Player player = context.getPlayer();
if (player == null) {
return InteractionResult.PASS;
}
if (!AntarchySettings.minersDreamEnabled()) {
if (!level.isClientSide()) {
player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_disabled"), true);
}
return InteractionResult.FAIL;
}
if (level.isClientSide()) {
return InteractionResult.SUCCESS;
}
ServerLevel serverLevel = (ServerLevel) level;
BlockPos clickedPos = context.getClickedPos();
Direction clickedFace = context.getClickedFace();
Direction travelDirection = clickedFace.getAxis().isHorizontal() ? clickedFace : player.getDirection();
BlockPos startPos = clickedPos.relative(travelDirection);
if (!serverLevel.isInWorldBounds(startPos) || !MinersDreamCaveGenerator.isChunkLoaded(serverLevel, startPos)) {
return InteractionResult.FAIL;
}
if (MinersDreamExcavationManager.hasActiveExcavation(player.getUUID())) {
player.displayClientMessage(Component.translatable("message.antarchy.miners_dream_busy"), true);
return InteractionResult.FAIL;
}
MinersDreamExcavation excavation = MinersDreamExcavationManager.tryStart(serverLevel, player.getUUID(), startPos, travelDirection);
if (excavation == null) {
return InteractionResult.FAIL;
}
serverLevel.playSound(null, clickedPos, SoundEvents.TNT_PRIMED, SoundSource.PLAYERS, 0.8F, 1.1F);
if (!player.getAbilities().instabuild) {
context.getItemInHand().shrink(1);
}
return InteractionResult.CONSUME;
}
@Override
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line1").withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.miners_dream.line2").withStyle(ChatFormatting.GRAY));
super.appendHoverText(stack, context, tooltipComponents, tooltipFlag);
}
}
@@ -1,89 +0,0 @@
package com.craisinlord.antarchy.content.item;
import java.util.List;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
public final class MinersDreamTorchPlacer {
private MinersDreamTorchPlacer() {
}
public static void tryPlaceTorch(ServerLevel level, BlockPos center, RandomSource random) {
BlockPos floorSpot = findFloorSpot(level, center);
boolean floorFirst = random.nextBoolean();
if (floorFirst) {
if (floorSpot != null && placeFloorTorch(level, floorSpot)) {
return;
}
placeWallTorch(level, center, random);
return;
}
if (placeWallTorch(level, center, random)) {
return;
}
if (floorSpot != null) {
placeFloorTorch(level, floorSpot);
}
}
private static BlockPos findFloorSpot(ServerLevel level, BlockPos center) {
for (int dy = -2; dy <= 1; dy++) {
BlockPos candidate = center.offset(0, dy, 0);
if (!isOpenForTorch(level, candidate)) {
continue;
}
BlockState below = level.getBlockState(candidate.below());
if (below.isFaceSturdy(level, candidate.below(), Direction.UP)) {
return candidate;
}
}
return null;
}
private static boolean placeFloorTorch(ServerLevel level, BlockPos pos) {
if (!isOpenForTorch(level, pos)) {
return false;
}
BlockState state = Blocks.TORCH.defaultBlockState();
if (!state.canSurvive(level, pos)) {
return false;
}
level.setBlock(pos, state, 3);
level.playSound(null, pos, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
return true;
}
private static boolean placeWallTorch(ServerLevel level, BlockPos center, RandomSource random) {
List<Direction> order = Direction.Plane.HORIZONTAL.shuffledCopy(random);
for (int dy = -1; dy <= 1; dy++) {
BlockPos candidate = center.offset(0, dy, 0);
if (!isOpenForTorch(level, candidate)) {
continue;
}
for (Direction wallSide : order) {
Direction facing = wallSide.getOpposite();
BlockState state = Blocks.WALL_TORCH.defaultBlockState().setValue(BlockStateProperties.HORIZONTAL_FACING, facing);
if (!state.canSurvive(level, candidate)) {
continue;
}
level.setBlock(candidate, state, 3);
level.playSound(null, candidate, state.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
return true;
}
}
return false;
}
private static boolean isOpenForTorch(ServerLevel level, BlockPos pos) {
BlockState state = level.getBlockState(pos);
return (state.isAir() || state.canBeReplaced()) && level.getFluidState(pos).isEmpty();
}
}
@@ -3,6 +3,8 @@ package com.craisinlord.antarchy.content.item;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.content.block.entity.PortalGunPortalBaseBlockEntity;
import com.craisinlord.antarchy.content.block.entity.PortalGunPortalMasterBlockEntity;
import com.craisinlord.antarchy.content.block.entity.PortalGunPortalFaceRecord;
import com.craisinlord.antarchy.content.block.entity.PortalGunPortalCellAccess;
import com.craisinlord.antarchy.content.client.model.ResourceBackedGeoItemModel;
import com.craisinlord.antarchy.content.client.renderer.AnimatedHeldItemRenderer;
import com.craisinlord.antarchy.content.network.PortalGunPrimaryPayload;
@@ -11,11 +13,14 @@ import com.craisinlord.antarchy.content.portalgun.PortalGunPlacement;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.craisinlord.antarchy.content.portalgun.PortalGunProjectileEntity;
import com.craisinlord.antarchy.content.portalgun.PortalGunSavedData;
import com.craisinlord.antarchy.content.portalgun.PortalGunResetManager;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import java.util.function.Supplier;
import net.minecraft.core.BlockPos;
@@ -27,8 +32,6 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
@@ -52,6 +55,7 @@ import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.util.Mth;
import software.bernie.geckolib.animatable.GeoItem;
import software.bernie.geckolib.animatable.client.GeoRenderProvider;
import software.bernie.geckolib.animatable.instance.AnimatableInstanceCache;
@@ -69,6 +73,15 @@ public class PortalGunItem extends Item implements GeoItem {
private static final String LAST_SIDE_TAG = "antarchy.portal_gun_last_side";
private static final String MOON_SIDE_TAG = "antarchy.portal_gun_moon_side";
public static final String GUN_ID_TAG = "antarchy.portal_gun_id";
public static final String CHANNEL_NAME_TAG = "antarchy.portal_gun_channel";
public static final String OWNER_ID_TAG = "antarchy.portal_gun_owner";
public static final String OWNER_NAME_TAG = "antarchy.portal_gun_owner_name";
public static final UUID GLOBAL_OWNER_ID = UUID.nameUUIDFromBytes("Global".getBytes(StandardCharsets.UTF_8));
public static final String PORTAL_WIDTH_TAG = "width";
public static final String PORTAL_HEIGHT_TAG = "height";
public static final String GRAB_STRENGTH_TAG = "grabStrength";
private static final int MAX_PORTAL_WIDTH = 16;
private static final int MAX_PORTAL_HEIGHT = 16;
private static final ResourceLocation MODEL_LOCATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "geo/portal_gun.geo.json");
private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "textures/models/item/portal_gun.png");
private static final ResourceLocation ANIMATION_LOCATION = ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "animations/portal_gun.animation.json");
@@ -104,81 +117,126 @@ public class PortalGunItem extends Item implements GeoItem {
}
ServerLevel serverLevel = (ServerLevel) level;
if (player.isShiftKeyDown()) {
PortalGunSavedData.clearAllPortals(serverLevel.getServer(), player.getUUID());
this.setLastSide(stack, null);
this.setMoonSide(stack, null);
level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_fizzle", SoundEvents.ENDER_CHEST_CLOSE), SoundSource.PLAYERS, 0.6F, 1.0F);
if (player instanceof ServerPlayer serverPlayer) {
this.resetPortals(serverPlayer, stack);
}
return InteractionResultHolder.consume(stack);
}
boolean fired = this.firePortal(serverLevel, player, stack, PortalGunPortalEntity.PortalSide.ORANGE);
if (player instanceof ServerPlayer serverPlayer && PortalGunResetManager.handleOrangeReset(serverPlayer, stack)) {
return InteractionResultHolder.consume(stack);
}
boolean fired = this.firePortal(serverLevel, player, stack, PortalGunPortalEntity.PortalSide.ORANGE, usedHand == InteractionHand.OFF_HAND);
return fired ? InteractionResultHolder.consume(stack) : InteractionResultHolder.fail(stack);
}
public void firePrimary(ServerLevel level, ServerPlayer player, ItemStack stack) {
this.firePortal(level, player, stack, PortalGunPortalEntity.PortalSide.BLUE);
public void firePrimary(ServerLevel level, ServerPlayer player, ItemStack stack, boolean offhand) {
this.firePortal(level, player, stack, PortalGunPortalEntity.PortalSide.BLUE, offhand);
}
private boolean firePortal(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side) {
public void resetPortals(ServerPlayer player, ItemStack stack) {
UUID gunId = this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName());
UUID ownerId = getPortalOwnerId(stack, player.getUUID());
PortalGunSavedData.clearAllPortals(player.serverLevel(), ownerId, gunId, getChannelName(stack));
this.setLastSide(stack, null);
this.setMoonSide(stack, null);
player.serverLevel().playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_fizzle"), SoundSource.PLAYERS, 0.6F, 1.0F);
}
public void resetPortalSide(ServerPlayer player, ItemStack stack, PortalGunPortalEntity.PortalSide side) {
UUID gunId = this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName());
UUID ownerId = getPortalOwnerId(stack, player.getUUID());
PortalGunSavedData.clearPortalSide(player.serverLevel(), ownerId, gunId, getChannelName(stack), side);
player.serverLevel().playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_fizzle"), SoundSource.PLAYERS, 0.6F, 1.0F);
}
public void fireMobPortal(ServerLevel level, LivingEntity shooter, ItemStack stack, PortalGunPortalEntity.PortalSide side) {
this.ensureGunId(stack, shooter.getUUID(), shooter.getName().getString());
String firePath = side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange";
level.playSound(null, shooter.getX(), shooter.getY(), shooter.getZ(), PortalGunPortalEntity.sound(firePath), shooter.getSoundSource(), 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F);
this.spawnProjectile(level, shooter, stack, side);
}
private boolean firePortal(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side, boolean offhand) {
if (this.raycast(level, player).getType() == HitResult.Type.MISS) {
if (this.tryArmMoonShot(level, player, stack, side)) {
if (this.tryArmMoonShot(level, player, stack, side, offhand)) {
return true;
}
level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound("portal_gun_invalid_surface", SoundEvents.DISPENSER_FAIL), SoundSource.PLAYERS, 0.55F, 1.0F);
return false;
}
this.triggerFireAnimation(level, player, stack);
this.setLastSide(stack, side);
stack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND);
this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName());
stack.hurtAndBreak(1, player, offhand ? EquipmentSlot.OFFHAND : EquipmentSlot.MAINHAND);
player.awardStat(Stats.ITEM_USED.get(this));
player.getCooldowns().addCooldown(this, 6);
String firePath = side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange";
level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(firePath, SoundEvents.BEACON_ACTIVATE), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F);
level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(firePath), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F);
this.spawnProjectile(level, player, stack, side);
return true;
}
public void handlePortalImpact(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, Vec3 impactPos) {
PortalGunPortalEntity existing = this.findPlacedPortal(level, player.getUUID(), side);
PortalGunPlacement placement = this.findPlacement(level, player, side, hitResult, existing);
public void handlePortalImpact(ServerLevel level, LivingEntity shooter, ItemStack stack, UUID gunId, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, Vec3 impactPos, int portalWidth, int portalHeight) {
UUID portalOwnerId = getPortalOwnerId(stack, shooter.getUUID());
String portalOwnerIdentity = getPortalOwnerIdentity(stack, shooter.getUUID());
String channelName = getChannelName(stack);
PortalGunPortalEntity existing = this.findPlacedPortal(level, portalOwnerId, gunId, channelName, side);
PortalGunPlacement placement = this.findPlacement(level, shooter, portalOwnerId, channelName, side, hitResult, existing, portalWidth, portalHeight);
if (placement == null) {
Antarchy.LOGGER.info("Portal gun placement failed owner={} side={} hitBlock={} hitFace={} hitLocation={}", player.getUUID(), side, hitResult.getBlockPos(), hitResult.getDirection(), hitResult.getLocation());
level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface", SoundEvents.DISPENSER_FAIL), SoundSource.PLAYERS, 0.55F, 1.0F);
Antarchy.LOGGER.debug("Portal gun placement failed owner={} side={} hitBlock={} hitFace={} hitLocation={}", shooter.getUUID(), side, hitResult.getBlockPos(), hitResult.getDirection(), hitResult.getLocation());
level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface"), SoundSource.PLAYERS, 0.55F, 1.0F);
return;
}
if (existing != null && this.isSamePortalPlacement(existing, placement)) {
existing.restartOpeningAnimation();
if (stack.getItem() == this) {
this.setLastSide(stack, side);
}
level.playSound(null, existing.getX(), existing.getY(), existing.getZ(), PortalGunPortalEntity.sound("portal_fizzle"), SoundSource.PLAYERS, 0.6F, 1.0F);
return;
}
if (existing != null) {
Antarchy.LOGGER.info("Portal gun replacing existing portal owner={} side={} oldPortal={}", player.getUUID(), side, existing.getUUID());
Antarchy.LOGGER.debug("Portal gun replacing existing portal owner={} side={} oldPortal={}", shooter.getUUID(), side, existing.getUUID());
existing.discard();
}
if (this.isMoonSideArmed(stack, side)) {
this.setMoonSide(stack, null);
}
if (this.isMoonSideArmed(stack, this.otherSide(side))) {
if (shooter instanceof Player player && this.isMoonSideArmed(stack, this.otherSide(side))) {
this.spawnBlackHole(level, player, stack, side, placement.center());
return;
}
PortalGunPortalEntity portal = new PortalGunPortalEntity(this.portalType.get(), level);
portal.configure(player.getUUID(), side, placement);
portal.configure(portalOwnerId, gunId, portalOwnerIdentity, channelName, side, placement);
portal.moveTo(placement.center().x, placement.center().y, placement.center().z, placement.yaw(), 0.0F);
if (!this.placePortalBlocks(level, player.getUUID(), side, portal, placement)) {
Antarchy.LOGGER.info("Portal gun block placement failed owner={} side={} portal={} center={} facing={} up={} master={} base={}", player.getUUID(), side, portal.getUUID(), placement.center(), placement.facing(), placement.upAxis(), placement.masterPos(), placement.basePos());
level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface", SoundEvents.DISPENSER_FAIL), SoundSource.PLAYERS, 0.55F, 1.0F);
if (!this.placePortalBlocks(level, portalOwnerId, gunId, portalOwnerIdentity, channelName, side, portal, placement)) {
Antarchy.LOGGER.debug("Portal gun block placement failed owner={} side={} portal={} center={} facing={} up={} master={} base={}", shooter.getUUID(), side, portal.getUUID(), placement.center(), placement.facing(), placement.upAxis(), placement.masterPos(), placement.basePos());
level.playSound(null, impactPos.x, impactPos.y, impactPos.z, PortalGunPortalEntity.sound("portal_gun_invalid_surface"), SoundSource.PLAYERS, 0.55F, 1.0F);
return;
}
level.addFreshEntity(portal);
PortalGunSavedData.setPortal(level.getServer(), player.getUUID(), side, portal.getUUID());
Antarchy.LOGGER.info("Portal gun portal placed owner={} side={} portal={} center={} facing={} up={} width={} master={} base={}", player.getUUID(), side, portal.getUUID(), placement.center(), placement.facing(), placement.upAxis(), placement.widthAxis(), placement.masterPos(), placement.basePos());
PortalGunPortalEntity other = this.findCounterpart(level, player.getUUID(), side);
PortalGunSavedData.setPortal(level, portalOwnerId, gunId, channelName, side, portal.getUUID());
PortalGunPortalEntity other = this.findCounterpart(level, portalOwnerId, gunId, channelName, side);
if (other != null) {
portal.linkTo(other);
other.linkTo(portal);
this.syncPortalBlocks(level, portal);
this.syncPortalBlocks(level, other);
Antarchy.LOGGER.info("Portal gun portals linked owner={} placedSide={} placedPortal={} otherPortal={} otherSide={}", player.getUUID(), side, portal.getUUID(), other.getUUID(), other.getPortalSide());
} else {
Antarchy.LOGGER.info("Portal gun portal waiting for counterpart owner={} side={} portal={}", player.getUUID(), side, portal.getUUID());
}
if (stack.getItem() == this) {
this.setLastSide(stack, side);
}
String openPath = side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_open_blue" : "portal_open_orange";
level.playSound(null, portal.getX(), portal.getY(), portal.getZ(), PortalGunPortalEntity.sound(openPath, SoundEvents.END_PORTAL_SPAWN), SoundSource.PLAYERS, 0.45F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.05F : 0.92F);
level.playSound(null, portal.getX(), portal.getY(), portal.getZ(), PortalGunPortalEntity.sound(openPath), SoundSource.PLAYERS, 0.45F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.05F : 0.92F);
}
private boolean isSamePortalPlacement(PortalGunPortalEntity portal, PortalGunPlacement placement) {
if (portal.getPortalWidth() != placement.width()
|| portal.getPortalHeight() != placement.height()
|| portal.getFacingDirection() != placement.facing()
|| portal.getUpAxis() != placement.upAxis()
|| !portal.getMasterPos().equals(placement.masterPos())) {
return false;
}
return new HashSet<>(java.util.Arrays.asList(portal.getPortalSpots()))
.equals(new HashSet<>(java.util.Arrays.asList(placement.portalSpots())));
}
public ItemStack findMatchingGunStack(Player player, UUID gunId) {
@@ -199,21 +257,22 @@ public class PortalGunItem extends Item implements GeoItem {
return ItemStack.EMPTY;
}
private boolean tryArmMoonShot(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side) {
private boolean tryArmMoonShot(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side, boolean offhand) {
if (!level.dimensionType().hasSkyLight() || !level.isNight() || player.getXRot() > -40.0F) {
return false;
}
PortalGunPortalEntity existing = this.findPlacedPortal(level, player.getUUID(), side);
UUID ownerId = getPortalOwnerId(stack, player.getUUID());
PortalGunPortalEntity existing = this.findPlacedPortal(level, ownerId, this.ensureGunId(stack, player.getUUID(), player.getGameProfile().getName()), getChannelName(stack), side);
if (existing != null) {
existing.discard();
}
this.setMoonSide(stack, side);
this.setLastSide(stack, side);
this.triggerFireAnimation(level, player, stack);
stack.hurtAndBreak(1, player, EquipmentSlot.MAINHAND);
stack.hurtAndBreak(1, player, offhand ? EquipmentSlot.OFFHAND : EquipmentSlot.MAINHAND);
player.awardStat(Stats.ITEM_USED.get(this));
player.getCooldowns().addCooldown(this, 6);
level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange", SoundEvents.BEACON_ACTIVATE), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F);
level.playSound(null, player.getX(), player.getY(), player.getZ(), PortalGunPortalEntity.sound(side == PortalGunPortalEntity.PortalSide.BLUE ? "portal_gun_fire_blue" : "portal_gun_fire_orange"), SoundSource.PLAYERS, 0.65F, side == PortalGunPortalEntity.PortalSide.BLUE ? 1.15F : 0.88F);
this.spawnShotTrail(level, player, player.getEyePosition().add(player.getLookAngle().scale(64.0D)), side);
return true;
}
@@ -224,7 +283,7 @@ public class PortalGunItem extends Item implements GeoItem {
blackHole.configure(player.getUUID());
blackHole.moveTo(spawnPos.x, spawnPos.y, spawnPos.z, 0.0F, 0.0F);
level.addFreshEntity(blackHole);
level.playSound(null, spawnPos.x, spawnPos.y, spawnPos.z, PortalGunPortalEntity.sound("portal_gun_black_hole", SoundEvents.WARDEN_SONIC_BOOM), SoundSource.PLAYERS, 0.6F, 0.6F);
level.playSound(null, spawnPos.x, spawnPos.y, spawnPos.z, PortalGunPortalEntity.sound("portal_open_orange"), SoundSource.PLAYERS, 0.6F, 0.6F);
return true;
}
@@ -241,20 +300,38 @@ public class PortalGunItem extends Item implements GeoItem {
}
}
private void spawnProjectile(ServerLevel level, Player player, ItemStack stack, PortalGunPortalEntity.PortalSide side) {
UUID gunId = this.ensureGunId(stack);
PortalGunProjectileEntity projectile = new PortalGunProjectileEntity(this.projectileType.get(), player, level);
private void spawnProjectile(ServerLevel level, LivingEntity shooter, ItemStack stack, PortalGunPortalEntity.PortalSide side) {
UUID gunId = this.ensureGunId(stack, shooter.getUUID(), shooter.getName().getString());
PortalGunProjectileEntity projectile = new PortalGunProjectileEntity(this.projectileType.get(), shooter, level);
projectile.configure(side, gunId, stack);
Vec3 look = player.getLookAngle();
Vec3 start = player.getEyePosition().add(look.scale(0.4D));
Vec3 look = shooter.getLookAngle();
Vec3 start = shooter.getEyePosition();
double launchSpeed = PortalGunProjectileEntity.MIN_LAUNCH_SPEED + level.getRandom().nextDouble() * PortalGunProjectileEntity.LAUNCH_SPEED_VARIANCE;
projectile.setProjectileSpeed(launchSpeed);
projectile.setPos(start.x, start.y, start.z);
projectile.setDeltaMovement(look.scale(2.5D));
projectile.setDeltaMovement(look.scale(projectile.getProjectileSpeed()));
projectile.syncLaunchState();
level.addFreshEntity(projectile);
}
private PortalGunPortalEntity findCounterpart(ServerLevel level, UUID owner, PortalGunPortalEntity.PortalSide side) {
public static int getPortalWidth(ItemStack stack) {
net.minecraft.world.item.component.CustomData data = stack.get(DataComponents.CUSTOM_DATA);
return Mth.clamp(data == null ? 1 : data.copyTag().getInt(PORTAL_WIDTH_TAG), 1, MAX_PORTAL_WIDTH);
}
public static int getPortalHeight(ItemStack stack) {
net.minecraft.world.item.component.CustomData data = stack.get(DataComponents.CUSTOM_DATA);
return Mth.clamp(data == null || !data.copyTag().contains(PORTAL_HEIGHT_TAG) ? 2 : data.copyTag().getInt(PORTAL_HEIGHT_TAG), 2, MAX_PORTAL_HEIGHT);
}
public static int getGrabStrength(ItemStack stack) {
net.minecraft.world.item.component.CustomData data = stack.get(DataComponents.CUSTOM_DATA);
return Mth.clamp(data == null || !data.copyTag().contains(GRAB_STRENGTH_TAG) ? 4 : data.copyTag().getInt(GRAB_STRENGTH_TAG), 1, 4);
}
private PortalGunPortalEntity findCounterpart(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) {
PortalGunPortalEntity.PortalSide otherSide = side == PortalGunPortalEntity.PortalSide.BLUE ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE;
Optional<UUID> otherId = PortalGunSavedData.getPortalId(level.getServer(), owner, otherSide);
Optional<UUID> otherId = PortalGunSavedData.getPortalId(level.getServer(), owner, gunId, channelName, otherSide, level.dimension().location());
if (otherId.isEmpty()) {
return null;
}
@@ -262,8 +339,8 @@ public class PortalGunItem extends Item implements GeoItem {
return entity instanceof PortalGunPortalEntity portal ? portal : null;
}
private PortalGunPortalEntity findPlacedPortal(ServerLevel level, UUID owner, PortalGunPortalEntity.PortalSide side) {
Optional<UUID> portalId = PortalGunSavedData.getPortalId(level.getServer(), owner, side);
private PortalGunPortalEntity findPlacedPortal(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) {
Optional<UUID> portalId = PortalGunSavedData.getPortalId(level.getServer(), owner, gunId, channelName, side, level.dimension().location());
if (portalId.isEmpty()) {
return null;
}
@@ -281,136 +358,247 @@ public class PortalGunItem extends Item implements GeoItem {
return level.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player));
}
private PortalGunPlacement findPlacement(Level level, Player player, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, PortalGunPortalEntity replacingPortal) {
private PortalGunPlacement findPlacement(Level level, LivingEntity shooter, UUID ownerId, String channelName, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, PortalGunPortalEntity replacingPortal, int requestedWidth, int requestedHeight) {
int maxWidth = Mth.clamp(requestedWidth, 1, 16);
int maxHeight = Mth.clamp(requestedHeight, 2, 16);
boolean canResize = AntarchySettings.portalGunCanPortalsResizeWhenCreated();
for (int height = maxHeight; height >= 2; height--) {
for (int width = maxWidth; width >= 1; width--) {
if (!canResize && (width != maxWidth || height != maxHeight)) {
continue;
}
PortalGunPlacement placement = this.findPlacementForSize(level, shooter, ownerId, channelName, side, hitResult, replacingPortal, width, height);
if (placement != null) {
return placement;
}
}
}
return null;
}
private PortalGunPlacement findPlacementForSize(Level level, LivingEntity shooter, UUID ownerId, String channelName, PortalGunPortalEntity.PortalSide side, BlockHitResult hitResult, PortalGunPortalEntity replacingPortal, int width, int height) {
Direction facing = hitResult.getDirection();
Direction heightAxis = this.resolveUpAxis(player, facing);
Direction heightAxis = this.resolveUpAxis(shooter, facing);
Direction widthAxis = PortalGunPlacement.widthAxis(facing, heightAxis);
float yaw = PortalGunPlacement.yawFor(facing, heightAxis);
BlockPos hitPos = hitResult.getBlockPos();
Vec3 hitLocation = hitResult.getLocation();
UUID replacingPortalId = replacingPortal == null ? null : replacingPortal.getUUID();
PortalGunPlacement bestPlacement = null;
double bestScore = Double.MAX_VALUE;
for (int widthOffset = -3; widthOffset <= 3; widthOffset++) {
for (int heightOffset = -4; heightOffset <= 3; heightOffset++) {
BlockPos supportOrigin = hitPos.relative(widthAxis, widthOffset).relative(heightAxis, heightOffset);
if (!this.canPlaceAt(level, player.getUUID(), side, supportOrigin, facing, heightAxis, replacingPortalId)) {
int maxRadius = Math.max(width, height);
List<Integer> radiusOrder = new java.util.ArrayList<>(maxRadius);
for (int radius = 0; radius < maxRadius; radius++) {
radiusOrder.add(radius);
}
int preferredRadius = maxRadius / 2;
radiusOrder.remove(Integer.valueOf(preferredRadius));
radiusOrder.add(0, preferredRadius);
for (int radius : radiusOrder) {
for (int horizontalOffset = radius; horizontalOffset >= 0; horizontalOffset--) {
for (int verticalOffset = radius; verticalOffset >= 0; verticalOffset--) {
List<BlockPos> supports = new java.util.ArrayList<>(width * height);
for (int widthCell = 1 - width; widthCell <= 0; widthCell++) {
for (int heightCell = 1 - height; heightCell <= 0; heightCell++) {
supports.add(this.referenceSupportPosition(hitPos, facing, heightAxis, horizontalOffset, verticalOffset, widthCell, heightCell));
}
}
if (!supports.contains(hitPos)) {
continue;
}
BlockPos masterPos = supportOrigin.relative(facing);
BlockPos basePos = masterPos.relative(heightAxis);
BlockPos[] portalSpots = new BlockPos[] {masterPos, basePos};
Vec3 firstCellCenter = masterPos.getCenter();
Vec3 center = firstCellCenter
.add(Vec3.atLowerCornerOf(heightAxis.getNormal()).scale(0.5D))
.subtract(Vec3.atLowerCornerOf(facing.getNormal()).scale(0.5D));
Vec3 local = hitLocation.subtract(center);
double widthLocal = local.dot(Vec3.atLowerCornerOf(widthAxis.getNormal()));
double heightLocal = local.dot(Vec3.atLowerCornerOf(heightAxis.getNormal()));
double depthLocal = local.dot(Vec3.atLowerCornerOf(facing.getNormal()));
double widthDistance = Math.max(0.0D, Math.abs(widthLocal) - 0.5D);
double heightDistance = Math.max(0.0D, Math.abs(heightLocal) - 1.0D);
double score = widthDistance * widthDistance
+ heightDistance * heightDistance
+ Math.abs(depthLocal) * 0.25D
+ supportOrigin.distSqr(hitPos) * 0.12D
+ Math.abs(widthOffset) * 0.04D
+ Math.abs(heightOffset) * 0.025D
+ (widthOffset != 0 || heightOffset != 0 ? 0.02D : 0.0D);
if (score < bestScore) {
bestScore = score;
BlockPos[] portalSpots = new BlockPos[supports.size()];
boolean valid = true;
int spotIndex = 0;
for (BlockPos supportPos : supports) {
BlockPos portalPos = supportPos.relative(facing);
BlockState supportState = level.getBlockState(supportPos);
BlockState portalState = level.getBlockState(portalPos);
if (!supportState.isFaceSturdy(level, supportPos, facing)
|| !this.isPortalSpaceAvailable(level, portalPos, portalState, ownerId, channelName, side, facing)) {
valid = false;
break;
}
portalSpots[spotIndex++] = portalPos;
}
if (!valid) {
continue;
}
BlockPos masterPos = null;
for (BlockPos portalSpot : portalSpots) {
BlockState state = level.getBlockState(portalSpot);
boolean belongsToReplacingPortal = replacingPortal != null && replacingPortal.containsPortalSpot(portalSpot);
boolean existingPortalCell = level.getBlockEntity(portalSpot) instanceof PortalGunPortalCellAccess;
if ((state.canBeReplaced() || belongsToReplacingPortal || existingPortalCell) && level.getFluidState(portalSpot).isEmpty()) {
masterPos = portalSpot;
break;
}
}
if (masterPos == null) {
return null;
}
BlockPos[] orderedPortalSpots = new BlockPos[portalSpots.length];
orderedPortalSpots[0] = masterPos;
int orderedSpotIndex = 1;
for (BlockPos portalSpot : portalSpots) {
if (!portalSpot.equals(masterPos)) {
orderedPortalSpots[orderedSpotIndex++] = portalSpot;
}
}
portalSpots = orderedPortalSpots;
Vec3 center = Vec3.ZERO;
Set<BlockPos> compensatedSpots = new HashSet<>();
if (widthOffset != 0 || heightOffset != 0) {
compensatedSpots.add(hitPos.relative(facing));
compensatedSpots.add(masterPos);
compensatedSpots.add(basePos);
compensatedSpots.add(supportOrigin);
compensatedSpots.add(basePos.relative(facing.getOpposite()));
for (BlockPos portalSpot : portalSpots) {
center = center.add(portalSpot.getCenter());
if (horizontalOffset != 0 || verticalOffset != 0) {
compensatedSpots.add(portalSpot);
compensatedSpots.add(portalSpot.relative(facing.getOpposite()));
}
bestPlacement = new PortalGunPlacement(center, facing, heightAxis, widthAxis, yaw, supportOrigin, masterPos, basePos, portalSpots, compensatedSpots);
}
center = center.scale(1.0D / portalSpots.length)
.subtract(Vec3.atLowerCornerOf(facing.getNormal()).scale(0.5D));
BlockPos supportOrigin = masterPos.relative(facing.getOpposite());
BlockPos basePos = portalSpots.length > 1 ? portalSpots[1] : portalSpots[0];
return new PortalGunPlacement(center, facing, heightAxis, widthAxis, yaw, supportOrigin, masterPos, basePos, portalSpots, compensatedSpots, width, height);
}
}
}
return bestPlacement;
return null;
}
private Direction resolveUpAxis(Player player, Direction facing) {
private BlockPos referenceSupportPosition(BlockPos origin, Direction facing, Direction upAxis, int horizontalOffset, int verticalOffset, int widthCell, int heightCell) {
int x;
int y;
int z;
if (facing.getAxis() != Direction.Axis.Y) {
if (facing.getAxis() == Direction.Axis.Z) {
x = horizontalOffset + widthCell;
z = 0;
} else {
x = 0;
z = horizontalOffset + widthCell;
}
y = -verticalOffset - heightCell;
} else if (upAxis == Direction.NORTH) {
x = horizontalOffset + widthCell;
y = 0;
z = verticalOffset + heightCell;
} else if (upAxis == Direction.SOUTH) {
x = horizontalOffset + widthCell;
y = 0;
z = -verticalOffset - heightCell;
} else if (upAxis == Direction.WEST) {
x = -verticalOffset - heightCell;
y = 0;
z = horizontalOffset + widthCell;
} else {
x = verticalOffset + heightCell;
y = 0;
z = horizontalOffset + widthCell;
}
return origin.offset(x, y, z);
}
private Direction resolveUpAxis(LivingEntity shooter, Direction facing) {
if (facing.getAxis() != Direction.Axis.Y) {
return Direction.UP;
}
Direction horizontal = player.getDirection();
Direction horizontal = shooter.getDirection();
if (horizontal.getAxis() == Direction.Axis.Y) {
return Direction.NORTH;
}
return horizontal.getOpposite();
return horizontal;
}
private boolean canPlaceAt(Level level, UUID ownerId, PortalGunPortalEntity.PortalSide side, BlockPos supportOrigin, Direction facing, Direction heightAxis, UUID replacingPortalId) {
for (int h = 0; h < 2; h++) {
BlockPos supportPos = supportOrigin.relative(heightAxis, h);
BlockPos airPos = supportPos.relative(facing);
BlockState supportState = level.getBlockState(supportPos);
BlockState airState = level.getBlockState(airPos);
FluidState fluidState = level.getFluidState(airPos);
if (!supportState.isFaceSturdy(level, supportPos, facing)) {
return false;
}
if (!this.isPortalSpaceAvailable(level, airPos, airState, ownerId, side, replacingPortalId)) {
return false;
}
if (!fluidState.isEmpty()) {
return false;
}
}
return true;
}
private boolean isPortalSpaceAvailable(Level level, BlockPos pos, BlockState state, UUID ownerId, PortalGunPortalEntity.PortalSide side, UUID replacingPortalId) {
if (state.isAir() || state.canBeReplaced()) {
return true;
}
private boolean isPortalSpaceAvailable(Level level, BlockPos pos, BlockState state, UUID ownerId, String channelName, PortalGunPortalEntity.PortalSide side, Direction face) {
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof PortalGunPortalMasterBlockEntity master) {
return ownerId != null
&& ownerId.equals(master.getOwnerId())
&& master.getSide() == side
&& (replacingPortalId == null || replacingPortalId.equals(master.getPortalId()));
}
if (blockEntity instanceof PortalGunPortalBaseBlockEntity base) {
return ownerId != null
&& ownerId.equals(base.getOwnerId())
&& base.getSide() == side
&& (replacingPortalId == null || replacingPortalId.equals(base.getPortalId()));
if (blockEntity instanceof PortalGunPortalCellAccess portalCell) {
PortalGunPortalFaceRecord record = portalCell.portalGun$getFaceRecord(face);
if (record == null) {
if (blockEntity instanceof PortalGunPortalMasterBlockEntity master && master.getPortalId() != null && master.getFacing() == face) {
return ownerId != null && ownerId.equals(master.getOwnerId()) && channelName.equals(master.getChannelName()) && master.getSide() == side;
}
if (blockEntity instanceof PortalGunPortalBaseBlockEntity base && base.getPortalId() != null) {
BlockEntity masterEntity = level.getBlockEntity(base.getMasterPos());
if (!(masterEntity instanceof PortalGunPortalMasterBlockEntity master)) {
return false;
}
return master.getFacing() != face
|| (ownerId != null
&& ownerId.equals(base.getOwnerId())
&& channelName.equals(master.getChannelName())
&& base.getSide() == side
&& master.getSide() == side);
}
return true;
}
return ownerId != null
&& ownerId.equals(record.ownerId())
&& channelName.equals(record.channelName())
&& record.side() == side;
}
return state.getCollisionShape(level, pos).isEmpty();
}
private boolean placePortalBlocks(ServerLevel level, UUID ownerId, PortalGunPortalEntity.PortalSide side, PortalGunPortalEntity portal, PortalGunPlacement placement) {
if (!level.setBlock(placement.masterPos(), this.portalMasterBlock.get().defaultBlockState(), 3)) {
private boolean placePortalBlocks(ServerLevel level, UUID ownerId, UUID gunId, String ownerIdentity, String channelName, PortalGunPortalEntity.PortalSide side, PortalGunPortalEntity portal, PortalGunPlacement placement) {
int pairTime = level.getServer().getTickCount();
for (int i = 0; i < placement.portalSpots().length; i++) {
BlockPos spot = placement.portalSpots()[i];
BlockEntity existing = level.getBlockEntity(spot);
boolean masterRole = i == 0;
if (!(existing instanceof PortalGunPortalCellAccess)) {
BlockState block = masterRole ? this.portalMasterBlock.get().defaultBlockState() : this.portalBaseBlock.get().defaultBlockState();
if (!level.setBlock(spot, block, 3)) {
for (BlockPos placed : placement.portalSpots()) {
BlockEntity cell = level.getBlockEntity(placed);
if (cell instanceof PortalGunPortalCellAccess portalCell) {
portalCell.portalGun$removeFaceRecord(placement.facing(), portal.getUUID());
}
}
return false;
}
if (!level.setBlock(placement.basePos(), this.portalBaseBlock.get().defaultBlockState(), 3)) {
level.setBlock(placement.masterPos(), Blocks.AIR.defaultBlockState(), 3);
existing = level.getBlockEntity(spot);
}
if (!(existing instanceof PortalGunPortalCellAccess cell)
|| !cell.portalGun$putFaceRecord(new PortalGunPortalFaceRecord(
ownerId,
ownerIdentity,
gunId,
channelName,
portal.getUUID(),
portal.getLinkedPortalId(),
side,
placement.facing(),
placement.upAxis(),
placement.masterPos(),
placement.basePos(),
java.util.Arrays.asList(placement.portalSpots()),
placement.compensatedSpots(),
pairTime,
placement.width(),
placement.height(),
masterRole
))) {
return false;
}
if (!(level.getBlockEntity(placement.masterPos()) instanceof PortalGunPortalMasterBlockEntity master) || !(level.getBlockEntity(placement.basePos()) instanceof PortalGunPortalBaseBlockEntity base)) {
level.setBlock(placement.masterPos(), Blocks.AIR.defaultBlockState(), 3);
level.setBlock(placement.basePos(), Blocks.AIR.defaultBlockState(), 3);
return false;
}
master.configure(ownerId, portal.getUUID(), side, placement.facing(), placement.upAxis(), placement.basePos(), placement.portalSpots(), placement.compensatedSpots(), level.getServer().getTickCount());
if (masterRole && existing instanceof PortalGunPortalMasterBlockEntity master
&& (master.getPortalId() == null || master.getPortalId().equals(portal.getUUID()))) {
master.configure(ownerId, gunId, ownerIdentity, channelName, portal.getUUID(), side, placement.facing(), placement.upAxis(), placement.basePos(), placement.portalSpots(), placement.compensatedSpots(), pairTime, placement.width(), placement.height());
} else if (!masterRole && existing instanceof PortalGunPortalBaseBlockEntity base
&& (base.getPortalId() == null || base.getPortalId().equals(portal.getUUID()))) {
base.configure(ownerId, portal.getUUID(), side, placement.masterPos());
}
}
return true;
}
private void syncPortalBlocks(ServerLevel level, PortalGunPortalEntity portal) {
if (level.getBlockEntity(portal.getMasterPos()) instanceof PortalGunPortalMasterBlockEntity master) {
master.updatePair(portal.getLinkedPortalId(), portal.getPairTime());
for (BlockPos portalSpot : portal.getPortalSpots()) {
if (level.getBlockEntity(portalSpot) instanceof PortalGunPortalCellAccess cell) {
cell.portalGun$updateFacePair(portal.getUUID(), portal.getLinkedPortalId(), portal.getPairTime());
}
if (level.getBlockEntity(portal.getBasePos()) instanceof PortalGunPortalBaseBlockEntity base) {
if (portalSpot.equals(portal.getMasterPos()) && level.getBlockEntity(portalSpot) instanceof PortalGunPortalMasterBlockEntity master && portal.getUUID().equals(master.getPortalId())) {
master.updatePair(portal.getLinkedPortalId(), portal.getPairTime());
} else if (!portalSpot.equals(portal.getMasterPos()) && level.getBlockEntity(portalSpot) instanceof PortalGunPortalBaseBlockEntity base && portal.getUUID().equals(base.getPortalId())) {
base.updatePair(portal.getLinkedPortalId(), portal.getPairTime());
}
}
}
private void setLastSide(ItemStack stack, PortalGunPortalEntity.PortalSide side) {
if (side == null) {
@@ -452,16 +640,115 @@ public class PortalGunItem extends Item implements GeoItem {
return side.name().equals(sideName);
}
private UUID ensureGunId(ItemStack stack) {
public static UUID getGunId(ItemStack stack) {
net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData != null && customData.copyTag().hasUUID(GUN_ID_TAG)) {
if (customData == null || !customData.copyTag().hasUUID(GUN_ID_TAG)) {
return null;
}
return customData.copyTag().getUUID(GUN_ID_TAG);
}
UUID gunId = UUID.randomUUID();
net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putUUID(GUN_ID_TAG, gunId));
private UUID ensureGunId(ItemStack stack) {
net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
UUID gunId;
if (customData != null && customData.copyTag().hasUUID(GUN_ID_TAG)) {
gunId = customData.copyTag().getUUID(GUN_ID_TAG);
} else {
gunId = UUID.randomUUID();
}
String channelName = "Random Channel #" + gunId.hashCode();
net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
tag.putUUID(GUN_ID_TAG, gunId);
if (!tag.contains(CHANNEL_NAME_TAG)) {
tag.putString(CHANNEL_NAME_TAG, channelName);
}
if (!tag.contains(LAST_SIDE_TAG)) {
tag.putString(LAST_SIDE_TAG, PortalGunPortalEntity.PortalSide.BLUE.name());
}
});
return gunId;
}
private UUID ensureGunId(ItemStack stack, UUID ownerId, String ownerName) {
UUID gunId = this.ensureGunId(stack);
net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
if (!tag.contains(OWNER_ID_TAG)) {
tag.putString(OWNER_ID_TAG, ownerId.toString());
tag.putString(OWNER_NAME_TAG, ownerName);
}
});
return gunId;
}
public static String getChannelName(ItemStack stack) {
net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData == null) {
return "";
}
return customData.copyTag().getString(CHANNEL_NAME_TAG);
}
public static String getPortalOwnerName(ItemStack stack) {
net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
return customData == null ? "" : customData.copyTag().getString(OWNER_NAME_TAG);
}
public static UUID ensureGunIdentity(ItemStack stack) {
if (stack.isEmpty() || !(stack.getItem() instanceof PortalGunItem item)) {
return null;
}
return item.ensureGunId(stack);
}
public static UUID ensureGunIdentity(ItemStack stack, Player owner) {
if (stack.isEmpty() || !(stack.getItem() instanceof PortalGunItem item)) {
return null;
}
return item.ensureGunId(stack, owner.getUUID(), owner.getGameProfile().getName());
}
public static UUID getPortalOwnerId(ItemStack stack, UUID fallback) {
net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData == null) {
return fallback;
}
String owner = customData.copyTag().getString(OWNER_ID_TAG);
if ("Global".equals(owner)) {
return GLOBAL_OWNER_ID;
}
try {
return owner.isEmpty() ? fallback : UUID.fromString(owner);
} catch (IllegalArgumentException ignored) {
return fallback;
}
}
public static String getPortalOwnerIdentity(ItemStack stack, UUID fallback) {
net.minecraft.world.item.component.CustomData customData = stack.get(DataComponents.CUSTOM_DATA);
if (customData == null) {
return String.valueOf(fallback);
}
String owner = customData.copyTag().getString(OWNER_ID_TAG);
return owner.isEmpty() ? String.valueOf(fallback) : owner;
}
public static ItemStack createGlobalVariant(Item item, String channelName) {
ItemStack stack = new ItemStack(item);
UUID gunId = UUID.nameUUIDFromBytes(("Global\u0000" + channelName).getBytes(StandardCharsets.UTF_8));
net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> {
tag.putUUID(GUN_ID_TAG, gunId);
tag.putString(OWNER_ID_TAG, "Global");
tag.putString(OWNER_NAME_TAG, "Global");
tag.putString(CHANNEL_NAME_TAG, channelName);
tag.putInt(PORTAL_WIDTH_TAG, 1);
tag.putInt(PORTAL_HEIGHT_TAG, 2);
tag.putInt(GRAB_STRENGTH_TAG, 4);
tag.putString(LAST_SIDE_TAG, PortalGunPortalEntity.PortalSide.BLUE.name());
tag.putBoolean("lastFired", true);
});
return stack;
}
private boolean matchesGunId(ItemStack stack, UUID gunId) {
if (stack.isEmpty() || stack.getItem() != this) {
return false;
@@ -484,7 +771,17 @@ public class PortalGunItem extends Item implements GeoItem {
public void appendHoverText(ItemStack stack, Item.TooltipContext context, List<Component> tooltipComponents, TooltipFlag tooltipFlag) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.primary").withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.secondary").withStyle(ChatFormatting.GRAY));
String ownerName = getPortalOwnerName(stack);
if (!ownerName.isEmpty()) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.owner", ownerName).withStyle(ChatFormatting.GRAY));
}
String channelName = getChannelName(stack);
if (!channelName.isEmpty()) {
tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.channel", channelName).withStyle(ChatFormatting.GRAY));
}
tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.reset").withStyle(ChatFormatting.DARK_GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.size", getPortalWidth(stack), getPortalHeight(stack)).withStyle(ChatFormatting.GRAY));
tooltipComponents.add(Component.translatable("tooltip.antarchy.portal_gun.grab_strength", getGrabStrength(stack)).withStyle(ChatFormatting.GRAY));
}
@Override
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record PortalGunGrabPayload() implements CustomPacketPayload {
public static final Type<PortalGunGrabPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_grab"));
public static final StreamCodec<ByteBuf, PortalGunGrabPayload> STREAM_CODEC = StreamCodec.unit(new PortalGunGrabPayload());
@Override
public Type<PortalGunGrabPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record PortalGunGrabStatePayload(boolean active) implements CustomPacketPayload {
public static final Type<PortalGunGrabStatePayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_grab_state"));
public static final StreamCodec<ByteBuf, PortalGunGrabStatePayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, PortalGunGrabStatePayload::active, PortalGunGrabStatePayload::new
);
@Override
public Type<PortalGunGrabStatePayload> type() {
return TYPE;
}
}
@@ -0,0 +1,26 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record PortalGunIndicatorPayload(UUID gunId, boolean blueAvailable, boolean orangeAvailable) implements CustomPacketPayload {
public static final Type<PortalGunIndicatorPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_indicator"));
public static final StreamCodec<ByteBuf, PortalGunIndicatorPayload> STREAM_CODEC = StreamCodec.of(
(buffer, payload) -> {
buffer.writeLong(payload.gunId().getMostSignificantBits());
buffer.writeLong(payload.gunId().getLeastSignificantBits());
buffer.writeBoolean(payload.blueAvailable());
buffer.writeBoolean(payload.orangeAvailable());
},
buffer -> new PortalGunIndicatorPayload(new UUID(buffer.readLong(), buffer.readLong()), buffer.readBoolean(), buffer.readBoolean())
);
@Override
public Type<PortalGunIndicatorPayload> type() {
return TYPE;
}
}
@@ -0,0 +1,24 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record PortalGunIndicatorRequestPayload(UUID gunId) implements CustomPacketPayload {
public static final Type<PortalGunIndicatorRequestPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_indicator_request"));
public static final StreamCodec<ByteBuf, PortalGunIndicatorRequestPayload> STREAM_CODEC = StreamCodec.of(
(buffer, payload) -> {
buffer.writeLong(payload.gunId().getMostSignificantBits());
buffer.writeLong(payload.gunId().getLeastSignificantBits());
},
buffer -> new PortalGunIndicatorRequestPayload(new UUID(buffer.readLong(), buffer.readLong()))
);
@Override
public Type<PortalGunIndicatorRequestPayload> type() {
return TYPE;
}
}
@@ -2,13 +2,17 @@ package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record PortalGunPrimaryPayload() implements CustomPacketPayload {
public record PortalGunPrimaryPayload(boolean offhand) implements CustomPacketPayload {
public static final Type<PortalGunPrimaryPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_primary"));
public static final StreamCodec<ByteBuf, PortalGunPrimaryPayload> STREAM_CODEC = StreamCodec.unit(new PortalGunPrimaryPayload());
public static final StreamCodec<ByteBuf, PortalGunPrimaryPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, PortalGunPrimaryPayload::offhand,
PortalGunPrimaryPayload::new
);
@Override
public Type<PortalGunPrimaryPayload> type() {
@@ -0,0 +1,20 @@
package com.craisinlord.antarchy.content.network;
import com.craisinlord.antarchy.Antarchy;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
public record PortalGunResetPayload(int action) implements CustomPacketPayload {
public static final Type<PortalGunResetPayload> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(Antarchy.MODID, "portal_gun_reset"));
public static final StreamCodec<ByteBuf, PortalGunResetPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT, PortalGunResetPayload::action, PortalGunResetPayload::new
);
@Override
public Type<PortalGunResetPayload> type() {
return TYPE;
}
}
@@ -4,7 +4,6 @@ import java.util.List;
import java.util.UUID;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
@@ -73,7 +72,7 @@ public class PortalGunBlackHoleEntity extends Entity implements GeoEntity {
}
this.pullNearby(serverLevel);
if (this.ageTicks % 10 == 0) {
serverLevel.playSound(null, this.blockPosition(), SoundEvents.PORTAL_AMBIENT, SoundSource.HOSTILE, 0.5F, 0.6F);
serverLevel.playSound(null, this.blockPosition(), PortalGunPortalEntity.sound("portal_ambient"), SoundSource.HOSTILE, 0.5F, 0.6F);
}
}
@@ -23,13 +23,12 @@ public final class PortalGunCollisionHelper {
return collided;
}
AABB pathBox = startBox.expandTowards(movement).inflate(SEARCH_PADDING);
List<Entity> nearby = entity.level().getEntities(entity, pathBox, candidate -> candidate instanceof PortalGunPortalEntity && candidate.isAlive());
List<PortalGunPortalEntity> nearby = PortalGunPortalEntity.findPortalsNearBounds(entity.level(), pathBox);
if (nearby.isEmpty()) {
return collided;
}
Vec3 adjusted = collided;
for (Entity candidate : nearby) {
PortalGunPortalEntity portal = (PortalGunPortalEntity) candidate;
for (PortalGunPortalEntity portal : nearby) {
Vec3 candidateAdjusted = resolveCollisionAgainstPortal(entity, startBox, movement, adjusted, portal);
if (candidateAdjusted.distanceToSqr(movement) < adjusted.distanceToSqr(movement)) {
adjusted = candidateAdjusted;
@@ -39,6 +38,10 @@ public final class PortalGunCollisionHelper {
}
private static Vec3 resolveCollisionAgainstPortal(Entity entity, AABB startBox, Vec3 movement, Vec3 collided, PortalGunPortalEntity portal) {
PortalGunPortalEntity linked = portal.getLinkedPortal();
if (linked == null || !linked.isAlive() || portal.isTeleportCoolingDown(entity)) {
return collided;
}
PortalGunWorldPortalShape shape = portal.getWorldPortalShape();
Vec3 normal = portal.getNormalVec().normalize();
double desiredNormal = movement.dot(normal);
@@ -60,21 +63,21 @@ public final class PortalGunCollisionHelper {
&& !portal.getPortalInsides(entity).intersects(endBox)
&& !intersectsPortalWindow(startBox, shape)
&& !intersectsPortalWindow(endBox, shape)
&& !segmentIntersectsPortalWindow(shape, startBox, endBox)) {
&& !segmentIntersectsPortalWindow(entity, portal, startBox, endBox)) {
return false;
}
if (crossesPortal(shape, entity, startBox, movement)) {
Vec3 startProbe = portal.teleportProbePosition(entity, startBox);
Vec3 endProbe = portal.teleportProbePosition(entity, endBox);
if (portal.crossesPortal(startProbe, endProbe)) {
return true;
}
Vec3 startCenter = startBox.getCenter();
Vec3 endCenter = endBox.getCenter();
PortalGunWorldPortalShape.PortalLocalCoords startCoords = shape.localCoords(startCenter);
PortalGunWorldPortalShape.PortalLocalCoords endCoords = shape.localCoords(endCenter);
PortalGunWorldPortalShape.PortalLocalCoords startCoords = shape.localCoords(startProbe);
PortalGunWorldPortalShape.PortalLocalCoords endCoords = shape.localCoords(endProbe);
return startCoords.depth() > 0.0D
&& endCoords.depth() >= -PLANE_TOLERANCE
&& Math.abs(endCoords.horizontal()) <= shape.halfWidth() + EDGE_PADDING
&& Math.abs(endCoords.vertical()) <= shape.halfHeight() + EDGE_PADDING
&& projectedWindowOverlap(shape, startBox, endBox);
&& projectedWindowOverlap(shape, startProbe, endProbe);
}
private static boolean intersectsPortalWindow(AABB box, PortalGunWorldPortalShape shape) {
@@ -90,39 +93,17 @@ public final class PortalGunCollisionHelper {
return false;
}
private static boolean segmentIntersectsPortalWindow(PortalGunWorldPortalShape shape, AABB startBox, AABB endBox) {
for (Vec3 startCorner : corners(startBox)) {
Vec3 endCorner = matchSample(startCorner, startBox, endBox);
if (shape.crosses(startCorner, endCorner)) {
return true;
}
}
return false;
private static boolean segmentIntersectsPortalWindow(Entity entity, PortalGunPortalEntity portal, AABB startBox, AABB endBox) {
return portal.crossesPortal(portal.teleportProbePosition(entity, startBox), portal.teleportProbePosition(entity, endBox));
}
private static boolean projectedWindowOverlap(PortalGunWorldPortalShape shape, AABB startBox, AABB endBox) {
double minHorizontal = Double.POSITIVE_INFINITY;
double maxHorizontal = Double.NEGATIVE_INFINITY;
double minVertical = Double.POSITIVE_INFINITY;
double maxVertical = Double.NEGATIVE_INFINITY;
for (Vec3 corner : corners(startBox)) {
PortalGunWorldPortalShape.PortalLocalCoords coords = shape.localCoords(corner);
minHorizontal = Math.min(minHorizontal, coords.horizontal());
maxHorizontal = Math.max(maxHorizontal, coords.horizontal());
minVertical = Math.min(minVertical, coords.vertical());
maxVertical = Math.max(maxVertical, coords.vertical());
}
for (Vec3 corner : corners(endBox)) {
PortalGunWorldPortalShape.PortalLocalCoords coords = shape.localCoords(corner);
minHorizontal = Math.min(minHorizontal, coords.horizontal());
maxHorizontal = Math.max(maxHorizontal, coords.horizontal());
minVertical = Math.min(minVertical, coords.vertical());
maxVertical = Math.max(maxVertical, coords.vertical());
}
return maxHorizontal >= -shape.halfWidth() - BORDER_TOLERANCE
&& minHorizontal <= shape.halfWidth() + BORDER_TOLERANCE
&& maxVertical >= -shape.halfHeight() - BORDER_TOLERANCE
&& minVertical <= shape.halfHeight() + BORDER_TOLERANCE;
private static boolean projectedWindowOverlap(PortalGunWorldPortalShape shape, Vec3 startProbe, Vec3 endProbe) {
PortalGunWorldPortalShape.PortalLocalCoords start = shape.localCoords(startProbe);
PortalGunWorldPortalShape.PortalLocalCoords end = shape.localCoords(endProbe);
return Math.abs(start.horizontal()) <= shape.halfWidth() + BORDER_TOLERANCE
&& Math.abs(start.vertical()) <= shape.halfHeight() + BORDER_TOLERANCE
&& Math.abs(end.horizontal()) <= shape.halfWidth() + BORDER_TOLERANCE
&& Math.abs(end.vertical()) <= shape.halfHeight() + BORDER_TOLERANCE;
}
private static Vec3 applyBorderCollision(Entity entity, AABB startBox, Vec3 movement, PortalGunPortalEntity portal) {
@@ -143,51 +124,6 @@ public final class PortalGunCollisionHelper {
return collided.distanceToSqr(movement) < 1.0E-7D ? movement : collided;
}
private static boolean crossesPortal(PortalGunWorldPortalShape shape, Entity entity, AABB startBox, Vec3 movement) {
AABB endBox = startBox.move(movement);
for (Vec3 startSample : portalSamples(entity, startBox, true)) {
Vec3 endSample = matchSample(startSample, startBox, endBox);
if (shape.crosses(startSample, endSample)) {
return true;
}
}
return false;
}
private static List<Vec3> portalSamples(Entity entity, AABB box, boolean includeEye) {
Vec3 center = new Vec3((box.minX + box.maxX) * 0.5D, (box.minY + box.maxY) * 0.5D, (box.minZ + box.maxZ) * 0.5D);
Vec3 topCenter = new Vec3(center.x, box.maxY, center.z);
Vec3 bottomCenter = new Vec3(center.x, box.minY, center.z);
if (includeEye) {
Vec3 eye = new Vec3(entity.getX(), entity.getY() + entity.getEyeHeight(), entity.getZ());
return List.of(center, eye, topCenter, bottomCenter);
}
return List.of(center, topCenter, bottomCenter);
}
private static Vec3 matchSample(Vec3 startSample, AABB startBox, AABB endBox) {
double xLerp = axisRatio(startSample.x, startBox.minX, startBox.maxX);
double yLerp = axisRatio(startSample.y, startBox.minY, startBox.maxY);
double zLerp = axisRatio(startSample.z, startBox.minZ, startBox.maxZ);
return new Vec3(
lerp(endBox.minX, endBox.maxX, xLerp),
lerp(endBox.minY, endBox.maxY, yLerp),
lerp(endBox.minZ, endBox.maxZ, zLerp)
);
}
private static double axisRatio(double value, double min, double max) {
double size = max - min;
if (Math.abs(size) < 1.0E-6D) {
return 0.5D;
}
return (value - min) / size;
}
private static double lerp(double min, double max, double delta) {
return min + (max - min) * delta;
}
private static Vec3[] corners(AABB box) {
return new Vec3[] {
new Vec3(box.minX, box.minY, box.minZ),
@@ -0,0 +1,280 @@
package com.craisinlord.antarchy.content.portalgun;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import com.craisinlord.antarchy.content.AntarchyTags;
import com.craisinlord.antarchy.Antarchy;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.projectile.AbstractHurtingProjectile;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import java.util.function.BiConsumer;
import com.craisinlord.antarchy.content.network.PortalGunGrabStatePayload;
public final class PortalGunGrabManager {
private static final double REACH = 5.0D;
private static final double BASE_HOLD_DISTANCE = 3.5D;
private static final double MAX_HOLD_DISTANCE = 6.0D;
private static final Map<UUID, GrabState> GRABS = new HashMap<>();
private static BiConsumer<ServerPlayer, PortalGunGrabStatePayload> stateSender = (player, payload) -> {};
private PortalGunGrabManager() {
}
public static void setStateSender(BiConsumer<ServerPlayer, PortalGunGrabStatePayload> sender) {
stateSender = sender;
}
public static void toggle(ServerPlayer player) {
GrabState active = GRABS.remove(player.getUUID());
if (active != null) {
release(active);
stateSender.accept(player, new PortalGunGrabStatePayload(false));
return;
}
ItemStack gunStack = getHeldPortalGun(player);
if (gunStack.isEmpty()) {
return;
}
int grabStrength = PortalGunItem.getGrabStrength(gunStack);
Entity target = findTarget(player, grabStrength);
if (target == null) {
target = findBlockTarget(player, grabStrength);
if (target == null) {
return;
}
}
for (Map.Entry<UUID, GrabState> entry : new HashMap<>(GRABS).entrySet()) {
if (entry.getValue().target() == target) {
GRABS.remove(entry.getKey());
release(entry.getValue());
ServerPlayer previousHolder = player.serverLevel().getServer().getPlayerList().getPlayer(entry.getKey());
if (previousHolder != null) {
stateSender.accept(previousHolder, new PortalGunGrabStatePayload(false));
}
}
}
double holdDistance = BASE_HOLD_DISTANCE;
if (target.getBbWidth() > 2.0F) {
holdDistance = Math.min(MAX_HOLD_DISTANCE, holdDistance + target.getBbWidth() - 2.0D);
}
GrabState state = new GrabState(player.getUUID(), player.serverLevel(), target, holdDistance, target.isNoGravity());
GRABS.put(player.getUUID(), state);
target.setNoGravity(true);
stateSender.accept(player, new PortalGunGrabStatePayload(true));
}
public static void tick(MinecraftServer server) {
for (Map.Entry<UUID, GrabState> entry : new HashMap<>(GRABS).entrySet()) {
GrabState state = entry.getValue();
ServerPlayer player = server.getPlayerList().getPlayer(state.playerId());
Entity target = state.target();
if (player == null || !player.isAlive() || player.serverLevel() != state.level() || !isHoldingPortalGun(player)
|| !target.isAlive() || target.isRemoved() || target.level() != state.level()
|| target.distanceTo(player) > state.holdDistance() + 6.25D) {
GRABS.remove(entry.getKey());
release(state);
if (player != null) {
stateSender.accept(player, new PortalGunGrabStatePayload(false));
}
continue;
}
Vec3 holdPosition = player.getEyePosition(1.0F).add(player.getLookAngle().scale(state.holdDistance()));
if (target.position().distanceTo(holdPosition) > 5.0D) {
target.teleportTo(player.getX(), player.getY(), player.getZ());
}
Vec3 velocity = holdPosition.subtract(target.getX(), target.getBoundingBox().getCenter().y, target.getZ());
target.setDeltaMovement(velocity);
target.move(MoverType.SELF, velocity);
velocity = holdPosition.subtract(target.getX(), target.getBoundingBox().getCenter().y, target.getZ());
target.setDeltaMovement(velocity);
target.setNoGravity(true);
if (target instanceof FallingBlockEntity fallingBlock) {
fallingBlock.time = 2;
}
if (target instanceof AbstractHurtingProjectile projectile) {
projectile.accelerationPower = 0.0D;
}
target.fallDistance = (float) -(velocity.y * velocity.y);
target.setOnGround(false);
target.hasImpulse = true;
target.hurtMarked = true;
}
}
public static void clear(MinecraftServer server) {
for (GrabState state : GRABS.values()) {
release(state);
ServerPlayer player = server.getPlayerList().getPlayer(state.playerId());
if (player != null) {
stateSender.accept(player, new PortalGunGrabStatePayload(false));
}
}
GRABS.clear();
}
private static Entity findTarget(ServerPlayer player, int grabStrength) {
Vec3 start = player.getEyePosition();
Vec3 look = player.getLookAngle();
Vec3 end = start.add(look.scale(REACH));
BlockHitResult blockHit = player.serverLevel().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player));
double maxDistance = blockHit.getType() == HitResult.Type.BLOCK ? start.distanceTo(blockHit.getLocation()) : REACH;
AABB searchBounds = player.getBoundingBox().expandTowards(look.scale(maxDistance)).inflate(1.0D);
EntityHitResult closest = null;
double closestDistance = maxDistance * maxDistance;
for (Entity candidate : player.serverLevel().getEntities(player, searchBounds, entity -> canGrab(entity, grabStrength))) {
var hit = candidate.getBoundingBox().inflate(candidate.getPickRadius()).clip(start, end);
if (hit.isEmpty()) {
continue;
}
double distance = start.distanceToSqr(hit.get());
if (distance < closestDistance) {
closestDistance = distance;
closest = new EntityHitResult(candidate, hit.get());
}
}
return closest == null ? null : closest.getEntity();
}
private static Entity findBlockTarget(ServerPlayer player, int grabStrength) {
if (grabStrength < 2) {
return null;
}
Vec3 start = player.getEyePosition();
Vec3 end = start.add(player.getLookAngle().scale(REACH));
BlockHitResult hit = player.serverLevel().clip(new ClipContext(start, end, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, player));
if (hit.getType() != HitResult.Type.BLOCK) {
return null;
}
BlockPos pos = hit.getBlockPos();
ServerLevel level = player.serverLevel();
BlockState state = level.getBlockState(pos);
if (state.isAir() || state.is(AntarchyTags.Blocks.GRAVITY_GUN_BLACKLIST)) {
return null;
}
BlockPos basePos = pos;
BlockPos upperPos = null;
if (state.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)) {
basePos = state.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.UPPER ? pos.below() : pos;
BlockState baseState = level.getBlockState(basePos);
BlockPos candidateUpper = basePos.above();
BlockState upperState = level.getBlockState(candidateUpper);
if (!baseState.is(state.getBlock()) || !baseState.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)
|| baseState.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) != DoubleBlockHalf.LOWER
|| !upperState.is(state.getBlock()) || !upperState.hasProperty(BlockStateProperties.DOUBLE_BLOCK_HALF)
|| upperState.getValue(BlockStateProperties.DOUBLE_BLOCK_HALF) != DoubleBlockHalf.UPPER) {
return null;
}
state = baseState;
upperPos = candidateUpper;
}
if (state.getDestroySpeed(level, basePos) < 0.0F) {
return null;
}
if (AntarchySettings.portalGunEntityGrabWeightBase() == 0
&& !Antarchy.MODID.equals(BuiltInRegistries.BLOCK.getKey(state.getBlock()).getNamespace())) {
return null;
}
BlockEntity blockEntity = level.getBlockEntity(basePos);
if (blockEntity != null && grabStrength < 3) {
return null;
}
if (!state.isSolidRender(level, basePos)
&& state.getRenderShape() != RenderShape.ENTITYBLOCK_ANIMATED
&& !state.is(Blocks.SNOW)
&& !state.is(Blocks.GLOWSTONE)
&& !state.is(Blocks.CAKE)
&& !state.is(Blocks.TNT)
&& !state.is(Blocks.ICE)
&& !state.is(BlockTags.SLABS)
&& !state.is(Blocks.ANVIL)
&& !state.is(BlockTags.STAIRS)) {
return null;
}
CompoundTag blockEntityData = blockEntity == null ? null : blockEntity.saveWithId(level.registryAccess());
if (blockEntity != null) {
level.removeBlockEntity(basePos);
}
if (upperPos != null) {
level.removeBlock(upperPos, false);
}
FallingBlockEntity fallingBlock = FallingBlockEntity.fall(level, basePos, state);
if (fallingBlock == null) {
return null;
}
if (blockEntityData != null) {
fallingBlock.blockData = blockEntityData;
}
fallingBlock.setDeltaMovement(Vec3.ZERO);
fallingBlock.setNoGravity(true);
fallingBlock.hurtMarked = true;
return fallingBlock;
}
private static boolean canGrab(Entity entity, int grabStrength) {
if (!entity.isAlive() || entity instanceof Player || entity instanceof PortalGunPortalEntity || entity.isPassenger()) {
return false;
}
double volume = entity.getBbWidth() * entity.getBbHeight() * entity.getBbWidth();
double strengthScale = switch (grabStrength) {
case 1 -> 1.0D;
case 2 -> 1.5D;
case 3 -> 2.25D;
default -> 3.0D;
};
int grabWeightBase = AntarchySettings.portalGunEntityGrabWeightBase();
boolean modEntityExempt = grabWeightBase == 0
&& Antarchy.MODID.equals(BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).getNamespace());
return (modEntityExempt || Math.cbrt(volume) < grabWeightBase * strengthScale / 100.0D) && entity.isPickable();
}
private static ItemStack getHeldPortalGun(ServerPlayer player) {
if (player.getMainHandItem().getItem() instanceof PortalGunItem) {
return player.getMainHandItem();
}
if (player.getOffhandItem().getItem() instanceof PortalGunItem) {
return player.getOffhandItem();
}
return ItemStack.EMPTY;
}
private static boolean isHoldingPortalGun(ServerPlayer player) {
return !getHeldPortalGun(player).isEmpty();
}
private static void release(GrabState state) {
Entity target = state.target();
if (target.isAlive() && !target.isRemoved()) {
target.setNoGravity(state.hadNoGravity());
target.hasImpulse = true;
target.hurtMarked = true;
}
}
private record GrabState(UUID playerId, ServerLevel level, Entity target, double holdDistance, boolean hadNoGravity) {
}
}
@@ -0,0 +1,49 @@
package com.craisinlord.antarchy.content.portalgun;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import com.craisinlord.antarchy.content.network.PortalGunIndicatorPayload;
import com.craisinlord.antarchy.content.network.PortalGunIndicatorRequestPayload;
import java.util.UUID;
import java.util.function.BiConsumer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
public final class PortalGunIndicatorSync {
private static BiConsumer<ServerPlayer, PortalGunIndicatorPayload> sender = (player, payload) -> {};
private PortalGunIndicatorSync() {
}
public static void setSender(BiConsumer<ServerPlayer, PortalGunIndicatorPayload> payloadSender) {
sender = payloadSender;
}
public static void handleRequest(ServerPlayer player, PortalGunIndicatorRequestPayload request) {
ItemStack main = player.getMainHandItem();
ItemStack off = player.getOffhandItem();
ItemStack stack = main.getItem() instanceof PortalGunItem && request.gunId().equals(PortalGunItem.getGunId(main))
? main
: off.getItem() instanceof PortalGunItem && request.gunId().equals(PortalGunItem.getGunId(off)) ? off : ItemStack.EMPTY;
if (stack.isEmpty()) {
return;
}
UUID ownerId = PortalGunItem.getPortalOwnerId(stack, player.getUUID());
boolean blue = PortalGunSavedData.getPortalId(
player.serverLevel().getServer(),
ownerId,
request.gunId(),
PortalGunItem.getChannelName(stack),
PortalGunPortalEntity.PortalSide.BLUE,
player.serverLevel().dimension().location()
).isPresent();
boolean orange = PortalGunSavedData.getPortalId(
player.serverLevel().getServer(),
ownerId,
request.gunId(),
PortalGunItem.getChannelName(stack),
PortalGunPortalEntity.PortalSide.ORANGE,
player.serverLevel().dimension().location()
).isPresent();
sender.accept(player, new PortalGunIndicatorPayload(request.gunId(), blue, orange));
}
}
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.content.portalgun;
import java.util.Set;
import java.util.HashSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.Vec3;
@@ -15,13 +16,18 @@ public record PortalGunPlacement(
BlockPos masterPos,
BlockPos basePos,
BlockPos[] portalSpots,
Set<BlockPos> compensatedSpots
Set<BlockPos> compensatedSpots,
int width,
int height
) {
public static PortalGunPlacement fromStored(Direction facing, Direction upAxis, BlockPos masterPos, BlockPos basePos, Set<BlockPos> compensatedSpots) {
public static PortalGunPlacement fromStored(Direction facing, Direction upAxis, BlockPos masterPos, BlockPos basePos, BlockPos[] portalSpots, Set<BlockPos> compensatedSpots, int width, int height) {
Direction widthAxis = widthAxis(facing, upAxis);
BlockPos supportOrigin = masterPos.relative(facing.getOpposite());
Vec3 center = masterPos.getCenter()
.add(Vec3.atLowerCornerOf(upAxis.getNormal()).scale(0.5D))
Vec3 center = Vec3.ZERO;
for (BlockPos portalSpot : portalSpots) {
center = center.add(portalSpot.getCenter());
}
center = center.scale(1.0D / portalSpots.length)
.subtract(Vec3.atLowerCornerOf(facing.getNormal()).scale(0.5D));
return new PortalGunPlacement(
center,
@@ -32,8 +38,10 @@ public record PortalGunPlacement(
supportOrigin,
masterPos,
basePos,
new BlockPos[] {masterPos, basePos},
compensatedSpots
portalSpots,
compensatedSpots,
width,
height
);
}
@@ -47,4 +55,47 @@ public record PortalGunPlacement(
public static float yawFor(Direction facing, Direction upAxis) {
return facing.getAxis() == Direction.Axis.Y ? upAxis.toYRot() : facing.toYRot();
}
public static boolean isRectangularFootprint(BlockPos[] portalSpots, Direction facing, Direction upAxis, int width, int height) {
if (portalSpots == null || portalSpots.length != width * height || portalSpots.length == 0) {
return false;
}
Direction widthAxis = widthAxis(facing, upAxis);
BlockPos origin = portalSpots[0];
Set<Long> cells = new HashSet<>(portalSpots.length);
int minWidth = Integer.MAX_VALUE;
int maxWidth = Integer.MIN_VALUE;
int minHeight = Integer.MAX_VALUE;
int maxHeight = Integer.MIN_VALUE;
for (BlockPos spot : portalSpots) {
if (spot == null) {
return false;
}
int dx = spot.getX() - origin.getX();
int dy = spot.getY() - origin.getY();
int dz = spot.getZ() - origin.getZ();
int depth = dx * facing.getStepX() + dy * facing.getStepY() + dz * facing.getStepZ();
int widthCell = dx * widthAxis.getStepX() + dy * widthAxis.getStepY() + dz * widthAxis.getStepZ();
int heightCell = dx * upAxis.getStepX() + dy * upAxis.getStepY() + dz * upAxis.getStepZ();
if (depth != 0 || !cells.add(((long) widthCell << 32) ^ (heightCell & 0xffffffffL))) {
return false;
}
minWidth = Math.min(minWidth, widthCell);
maxWidth = Math.max(maxWidth, widthCell);
minHeight = Math.min(minHeight, heightCell);
maxHeight = Math.max(maxHeight, heightCell);
}
if (maxWidth - minWidth != width - 1 || maxHeight - minHeight != height - 1
|| (minWidth != 0 && maxWidth != 0) || (minHeight != 0 && maxHeight != 0)) {
return false;
}
for (int widthCell = minWidth; widthCell <= maxWidth; widthCell++) {
for (int heightCell = minHeight; heightCell <= maxHeight; heightCell++) {
if (!cells.contains(((long) widthCell << 32) ^ (heightCell & 0xffffffffL))) {
return false;
}
}
}
return true;
}
}
@@ -1,40 +1,72 @@
package com.craisinlord.antarchy.content.portalgun;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import com.craisinlord.antarchy.config.AntarchySettings;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ThrowableItemProjectile;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.StainedGlassBlock;
import net.minecraft.world.level.block.StainedGlassPaneBlock;
import net.minecraft.world.level.block.TintedGlassBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class PortalGunProjectileEntity extends ThrowableItemProjectile {
private static final TagKey<Block> GLASS_BLOCKS = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath("c", "glass_blocks"));
private static final TagKey<Block> GLASS_PANES = TagKey.create(Registries.BLOCK, ResourceLocation.fromNamespaceAndPath("c", "glass_panes"));
private static final int MAX_TICKETED_PROJECTILES = 20;
private static final int PROJECTILE_TICKET_LEVEL = 31;
private static final TicketType<UUID> PROJECTILE_TICKET = TicketType.create("antarchy_portal_projectile", Comparator.comparing(UUID::toString));
private static final LinkedHashMap<UUID, ProjectileTicket> PROJECTILE_TICKETS = new LinkedHashMap<>();
private static final EntityDataAccessor<Integer> SIDE = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> SHOOTER_ID = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> DISTANCE = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> SPAWN_X = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> SPAWN_Y = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Integer> SPAWN_Z = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Long> SPAWN_X = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.LONG);
private static final EntityDataAccessor<Long> SPAWN_Y = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.LONG);
private static final EntityDataAccessor<Long> SPAWN_Z = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.LONG);
private static final EntityDataAccessor<Integer> MAX_TRAVEL_DISTANCE_DATA = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.INT);
private static final EntityDataAccessor<Float> VELOCITY_X = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> VELOCITY_Y = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Float> VELOCITY_Z = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT);
private static final int MAX_FLIGHT_TICKS = 80;
private static final EntityDataAccessor<Float> PROJECTILE_SPEED = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.FLOAT);
private static final EntityDataAccessor<Boolean> PASS_THROUGH_GLASS = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.BOOLEAN);
private static final EntityDataAccessor<Boolean> PASS_THROUGH_LIQUID = SynchedEntityData.defineId(PortalGunProjectileEntity.class, EntityDataSerializers.BOOLEAN);
public static final double MIN_LAUNCH_SPEED = 4.98D;
public static final double LAUNCH_SPEED_VARIANCE = 0.02D;
public static final double MAX_TRAVEL_DISTANCE = 10000.0D;
private UUID gunId;
private boolean spawnSynced;
private boolean ticketRegistered;
public int portalWidth = 1;
public int portalHeight = 2;
@@ -53,30 +85,55 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
this.noCulling = true;
}
@Override
public EntityDimensions getDimensions(Pose pose) {
return EntityDimensions.fixed(0.3F, 0.3F);
}
@Override
protected void defineSynchedData(SynchedEntityData.Builder builder) {
super.defineSynchedData(builder);
builder.define(SIDE, PortalGunPortalEntity.PortalSide.BLUE.ordinal());
builder.define(SHOOTER_ID, -1);
builder.define(DISTANCE, 0);
builder.define(SPAWN_X, 0);
builder.define(SPAWN_Y, 0);
builder.define(SPAWN_Z, 0);
builder.define(SPAWN_X, Double.doubleToRawLongBits(0.0D));
builder.define(SPAWN_Y, Double.doubleToRawLongBits(0.0D));
builder.define(SPAWN_Z, Double.doubleToRawLongBits(0.0D));
builder.define(MAX_TRAVEL_DISTANCE_DATA, (int) MAX_TRAVEL_DISTANCE);
builder.define(VELOCITY_X, 0.0F);
builder.define(VELOCITY_Y, 0.0F);
builder.define(VELOCITY_Z, 0.0F);
builder.define(PROJECTILE_SPEED, 4.99F);
builder.define(PASS_THROUGH_GLASS, false);
builder.define(PASS_THROUGH_LIQUID, false);
}
public void configure(PortalGunPortalEntity.PortalSide side, UUID gunId, ItemStack gunStack) {
this.entityData.set(SIDE, side.ordinal());
this.gunId = gunId;
this.setItem(gunStack.copyWithCount(1));
this.portalWidth = PortalGunItem.getPortalWidth(gunStack);
this.portalHeight = PortalGunItem.getPortalHeight(gunStack);
this.entityData.set(MAX_TRAVEL_DISTANCE_DATA, AntarchySettings.portalGunMaxShootDistance());
this.entityData.set(PASS_THROUGH_GLASS, AntarchySettings.portalGunCanFireThroughGlass());
this.entityData.set(PASS_THROUGH_LIQUID, AntarchySettings.portalGunCanFireThroughLiquid());
Entity owner = this.getOwner();
this.entityData.set(SHOOTER_ID, owner == null ? -1 : owner.getId());
this.syncSpawnPosition(this.blockPosition());
}
public void syncLaunchState() {
this.syncSpawnPosition(this.position());
this.syncVelocity();
}
public void setProjectileSpeed(double speed) {
this.entityData.set(PROJECTILE_SPEED, (float) speed);
}
public double getProjectileSpeed() {
return this.entityData.get(PROJECTILE_SPEED);
}
public PortalGunPortalEntity.PortalSide getPortalSide() {
return this.entityData.get(SIDE) == PortalGunPortalEntity.PortalSide.ORANGE.ordinal() ? PortalGunPortalEntity.PortalSide.ORANGE : PortalGunPortalEntity.PortalSide.BLUE;
}
@@ -89,8 +146,16 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
return this.entityData.get(DISTANCE);
}
public BlockPos getSyncedSpawnPos() {
return new BlockPos(this.entityData.get(SPAWN_X), this.entityData.get(SPAWN_Y), this.entityData.get(SPAWN_Z));
public int getMaxTravelDistance() {
return this.entityData.get(MAX_TRAVEL_DISTANCE_DATA);
}
public Vec3 getSyncedSpawnPosition() {
return new Vec3(
Double.longBitsToDouble(this.entityData.get(SPAWN_X)),
Double.longBitsToDouble(this.entityData.get(SPAWN_Y)),
Double.longBitsToDouble(this.entityData.get(SPAWN_Z))
);
}
public Vec3 getSyncedVelocity() {
@@ -124,22 +189,82 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
@Override
public void tick() {
super.tick();
if (!this.spawnSynced) {
this.syncSpawnPosition(this.blockPosition());
if (this.level().isClientSide && !this.spawnSynced) {
Vec3 spawnPosition = this.getSyncedSpawnPosition();
this.setPos(spawnPosition.x, spawnPosition.y, spawnPosition.z);
this.setDeltaMovement(this.getSyncedVelocity());
this.spawnSynced = true;
}
if (!this.level().isClientSide && !this.isRemoved()) {
this.updateProjectileTicket();
}
this.baseTick();
if (this.isRemoved()) {
return;
}
if (!this.spawnSynced && !this.level().isClientSide) {
this.syncSpawnPosition(this.position());
this.syncVelocity();
this.spawnSynced = true;
}
this.entityData.set(DISTANCE, this.tickCount);
if (this.tickCount > MAX_FLIGHT_TICKS) {
int maxTravelDistance = this.getMaxTravelDistance();
Vec3 spawnPosition = this.getSyncedSpawnPosition();
if (this.tickCount > maxTravelDistance / 5.0D + 10.0D
|| this.position().distanceToSqr(spawnPosition) > (maxTravelDistance + 5.0D) * (maxTravelDistance + 5.0D)) {
this.discard();
return;
}
Vec3 motion = this.getDeltaMovement();
Vec3 start = this.position();
Vec3 end = start.add(motion);
ClipContext.Fluid fluidMode = this.entityData.get(PASS_THROUGH_LIQUID) ? ClipContext.Fluid.NONE : ClipContext.Fluid.ANY;
HitResult hit = this.level().clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, fluidMode, this));
if (hit instanceof BlockHitResult blockHit) {
BlockPos hitBlockPos = blockHit.getBlockPos();
net.minecraft.world.level.block.state.BlockState hitBlockState = this.level().getBlockState(hitBlockPos);
boolean passThroughGlass = this.shouldPassThroughGlass(blockHit);
if (!hitBlockState.isAir()) {
hitBlockState.entityInside(this.level(), hitBlockPos, this);
if (this.isRemoved()) {
return;
}
}
if (!passThroughGlass) {
this.onHit(blockHit);
if (this.isRemoved()) {
return;
}
}
this.setPos(blockHit.getLocation().subtract(motion.scale(0.98D)));
}
if (motion.lengthSqr() > 1.0E-6D) {
double horizontal = Math.sqrt(motion.x * motion.x + motion.z * motion.z);
this.setYRot((float) (net.minecraft.util.Mth.atan2(motion.z, motion.x) * (180.0D / Math.PI)) - 90.0F);
this.setXRot((float) (-(net.minecraft.util.Mth.atan2(motion.y, horizontal) * (180.0D / Math.PI))));
}
this.setPos(this.position().add(motion));
this.checkInsideBlocks();
if (this.isRemoved()) {
return;
}
if (!this.level().isClientSide && !this.isRemoved()) {
this.updateProjectileTicket();
}
if (this.isInWater()) {
for (int i = 0; i < 4; i++) {
float backtrack = 0.25F;
this.level().addParticle(
net.minecraft.core.particles.ParticleTypes.BUBBLE,
this.getX() - motion.x * backtrack,
this.getY() - motion.y * backtrack,
this.getZ() - motion.z * backtrack,
motion.x,
motion.y,
motion.z
);
}
}
if (this.level().isClientSide) {
Vec3 point = this.position();
if (this.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE) {
@@ -152,6 +277,63 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
}
}
@Override
public void remove(Entity.RemovalReason reason) {
this.releaseProjectileTicket();
super.remove(reason);
}
private void updateProjectileTicket() {
if (!(this.level() instanceof ServerLevel serverLevel)) {
return;
}
if (!AntarchySettings.portalGunCanPortalProjectilesChunkload()) {
this.releaseProjectileTicket();
return;
}
ChunkPos chunkPos = this.chunkPosition();
UUID ticketId = this.getUUID();
ProjectileTicket current = PROJECTILE_TICKETS.get(ticketId);
if (current != null && current.level == serverLevel && current.chunkPos.equals(chunkPos)) {
return;
}
if (current != null) {
current.level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, current.chunkPos, PROJECTILE_TICKET_LEVEL, ticketId);
PROJECTILE_TICKETS.remove(ticketId);
}
while (PROJECTILE_TICKETS.size() >= MAX_TICKETED_PROJECTILES) {
Map.Entry<UUID, ProjectileTicket> oldest = PROJECTILE_TICKETS.entrySet().iterator().next();
oldest.getValue().level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, oldest.getValue().chunkPos, PROJECTILE_TICKET_LEVEL, oldest.getKey());
PROJECTILE_TICKETS.remove(oldest.getKey());
}
serverLevel.getChunkSource().addRegionTicket(PROJECTILE_TICKET, chunkPos, PROJECTILE_TICKET_LEVEL, ticketId);
PROJECTILE_TICKETS.put(ticketId, new ProjectileTicket(serverLevel, chunkPos));
this.ticketRegistered = true;
}
private void releaseProjectileTicket() {
if (!this.ticketRegistered) {
return;
}
UUID ticketId = this.getUUID();
ProjectileTicket ticket = PROJECTILE_TICKETS.remove(ticketId);
if (ticket != null) {
ticket.level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, ticket.chunkPos, PROJECTILE_TICKET_LEVEL, ticketId);
}
this.ticketRegistered = false;
}
public static void releaseAllProjectileTickets() {
for (Map.Entry<UUID, ProjectileTicket> entry : PROJECTILE_TICKETS.entrySet()) {
ProjectileTicket ticket = entry.getValue();
ticket.level.getChunkSource().removeRegionTicket(PROJECTILE_TICKET, ticket.chunkPos, PROJECTILE_TICKET_LEVEL, entry.getKey());
}
PROJECTILE_TICKETS.clear();
}
private record ProjectileTicket(ServerLevel level, ChunkPos chunkPos) {
}
@Override
protected void onHitEntity(EntityHitResult result) {
}
@@ -161,13 +343,30 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
if (!this.level().isClientSide && this.level() instanceof ServerLevel serverLevel) {
this.spawnImpactParticles(serverLevel, result.getLocation());
ItemStack sourceStack = this.resolveSourceStack();
if (this.getOwner() instanceof Player player && sourceStack.getItem() instanceof PortalGunItem portalGunItem) {
portalGunItem.handlePortalImpact(serverLevel, player, sourceStack, this.getPortalSide(), result, this.position());
if (this.getOwner() instanceof LivingEntity shooter && sourceStack.getItem() instanceof PortalGunItem portalGunItem) {
portalGunItem.handlePortalImpact(serverLevel, shooter, sourceStack, this.gunId, this.getPortalSide(), result, result.getLocation(), this.portalWidth, this.portalHeight);
}
}
this.discard();
}
private boolean shouldPassThroughGlass(BlockHitResult hit) {
if (!this.entityData.get(PASS_THROUGH_GLASS)) {
return false;
}
BlockPos blockPos = hit.getBlockPos();
BlockState state = this.level().getBlockState(blockPos);
Block block = state.getBlock();
return state.is(GLASS_BLOCKS)
|| state.is(GLASS_PANES)
|| block == Blocks.GLASS
|| block == Blocks.GLASS_PANE
|| block == Blocks.TINTED_GLASS
|| block instanceof StainedGlassBlock
|| block instanceof StainedGlassPaneBlock
|| block instanceof TintedGlassBlock;
}
private void spawnImpactParticles(ServerLevel level, Vec3 impactPos) {
if (this.getPortalSide() == PortalGunPortalEntity.PortalSide.BLUE) {
level.sendParticles(net.minecraft.core.particles.ParticleTypes.SOUL_FIRE_FLAME, impactPos.x, impactPos.y, impactPos.z, 10, 0.08D, 0.08D, 0.08D, 0.01D);
@@ -178,10 +377,10 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
level.sendParticles(net.minecraft.core.particles.ParticleTypes.CRIT, impactPos.x, impactPos.y, impactPos.z, 6, 0.04D, 0.04D, 0.04D, 0.01D);
}
private void syncSpawnPosition(BlockPos pos) {
this.entityData.set(SPAWN_X, pos.getX());
this.entityData.set(SPAWN_Y, pos.getY());
this.entityData.set(SPAWN_Z, pos.getZ());
private void syncSpawnPosition(Vec3 pos) {
this.entityData.set(SPAWN_X, Double.doubleToRawLongBits(pos.x));
this.entityData.set(SPAWN_Y, Double.doubleToRawLongBits(pos.y));
this.entityData.set(SPAWN_Z, Double.doubleToRawLongBits(pos.z));
this.spawnSynced = true;
}
@@ -207,12 +406,20 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
tag.putInt("Side", this.entityData.get(SIDE));
tag.putInt("ShooterId", this.entityData.get(SHOOTER_ID));
tag.putInt("Distance", this.entityData.get(DISTANCE));
tag.putInt("SpawnX", this.entityData.get(SPAWN_X));
tag.putInt("SpawnY", this.entityData.get(SPAWN_Y));
tag.putInt("SpawnZ", this.entityData.get(SPAWN_Z));
Vec3 spawnPosition = this.getSyncedSpawnPosition();
tag.putInt("SpawnX", net.minecraft.util.Mth.floor(spawnPosition.x));
tag.putInt("SpawnY", net.minecraft.util.Mth.floor(spawnPosition.y));
tag.putInt("SpawnZ", net.minecraft.util.Mth.floor(spawnPosition.z));
tag.putDouble("SpawnPosX", spawnPosition.x);
tag.putDouble("SpawnPosY", spawnPosition.y);
tag.putDouble("SpawnPosZ", spawnPosition.z);
tag.putInt("MaxTravelDistance", this.entityData.get(MAX_TRAVEL_DISTANCE_DATA));
tag.putFloat("VelocityX", this.entityData.get(VELOCITY_X));
tag.putFloat("VelocityY", this.entityData.get(VELOCITY_Y));
tag.putFloat("VelocityZ", this.entityData.get(VELOCITY_Z));
tag.putFloat("ProjectileSpeed", this.entityData.get(PROJECTILE_SPEED));
tag.putBoolean("PassThroughGlass", this.entityData.get(PASS_THROUGH_GLASS));
tag.putBoolean("PassThroughLiquid", this.entityData.get(PASS_THROUGH_LIQUID));
tag.putInt("PortalWidth", this.portalWidth);
tag.putInt("PortalHeight", this.portalHeight);
if (this.gunId != null) {
@@ -226,14 +433,20 @@ public class PortalGunProjectileEntity extends ThrowableItemProjectile {
this.entityData.set(SIDE, tag.getInt("Side"));
this.entityData.set(SHOOTER_ID, tag.getInt("ShooterId"));
this.entityData.set(DISTANCE, tag.getInt("Distance"));
this.entityData.set(SPAWN_X, tag.getInt("SpawnX"));
this.entityData.set(SPAWN_Y, tag.getInt("SpawnY"));
this.entityData.set(SPAWN_Z, tag.getInt("SpawnZ"));
this.entityData.set(SPAWN_X, Double.doubleToRawLongBits(tag.contains("SpawnPosX") ? tag.getDouble("SpawnPosX") : tag.getInt("SpawnX")));
this.entityData.set(SPAWN_Y, Double.doubleToRawLongBits(tag.contains("SpawnPosY") ? tag.getDouble("SpawnPosY") : tag.getInt("SpawnY")));
this.entityData.set(SPAWN_Z, Double.doubleToRawLongBits(tag.contains("SpawnPosZ") ? tag.getDouble("SpawnPosZ") : tag.getInt("SpawnZ")));
this.entityData.set(MAX_TRAVEL_DISTANCE_DATA, tag.contains("MaxTravelDistance")
? Math.max(1, tag.getInt("MaxTravelDistance"))
: AntarchySettings.portalGunMaxShootDistance());
this.entityData.set(VELOCITY_X, tag.getFloat("VelocityX"));
this.entityData.set(VELOCITY_Y, tag.getFloat("VelocityY"));
this.entityData.set(VELOCITY_Z, tag.getFloat("VelocityZ"));
this.portalWidth = Math.max(1, tag.getInt("PortalWidth"));
this.portalHeight = Math.max(2, tag.getInt("PortalHeight"));
this.entityData.set(PROJECTILE_SPEED, tag.contains("ProjectileSpeed") ? tag.getFloat("ProjectileSpeed") : 4.99F);
this.entityData.set(PASS_THROUGH_GLASS, tag.contains("PassThroughGlass") ? tag.getBoolean("PassThroughGlass") : AntarchySettings.portalGunCanFireThroughGlass());
this.entityData.set(PASS_THROUGH_LIQUID, tag.contains("PassThroughLiquid") ? tag.getBoolean("PassThroughLiquid") : AntarchySettings.portalGunCanFireThroughLiquid());
this.portalWidth = net.minecraft.util.Mth.clamp(tag.getInt("PortalWidth"), 1, 16);
this.portalHeight = net.minecraft.util.Mth.clamp(tag.getInt("PortalHeight"), 2, 16);
this.spawnSynced = true;
if (tag.hasUUID("GunId")) {
this.gunId = tag.getUUID("GunId");
@@ -0,0 +1,86 @@
package com.craisinlord.antarchy.content.portalgun;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
public final class PortalGunResetManager {
public static final int ARM = 0;
public static final int RELEASE = 1;
public static final int RESET_ALL = 2;
public static final int RESET_BLUE = 3;
private static final Map<UUID, ResetState> STATES = new HashMap<>();
private PortalGunResetManager() {
}
public static void handleInput(ServerPlayer player, int action) {
ItemStack stack = heldPortalGun(player);
if (stack.isEmpty()) {
STATES.remove(player.getUUID());
return;
}
UUID gunId = PortalGunItem.ensureGunIdentity(stack, player);
if (action == ARM) {
STATES.put(player.getUUID(), new ResetState(gunId, player.serverLevel().getGameTime(), false));
return;
}
ResetState state = activeState(player, gunId);
if (action == RESET_ALL) {
PortalGunItem item = (PortalGunItem) stack.getItem();
item.resetPortals(player, stack);
STATES.remove(player.getUUID());
} else if (action == RESET_BLUE && state != null && !state.sideReset()) {
((PortalGunItem) stack.getItem()).resetPortalSide(player, stack, PortalGunPortalEntity.PortalSide.BLUE);
STATES.put(player.getUUID(), new ResetState(gunId, state.startedAt(), true));
} else if (action == RELEASE) {
STATES.remove(player.getUUID());
}
}
public static boolean handleOrangeReset(ServerPlayer player, ItemStack stack) {
if (!(stack.getItem() instanceof PortalGunItem)) {
return false;
}
UUID gunId = PortalGunItem.getGunId(stack);
ResetState state = activeState(player, gunId);
if (state == null) {
return false;
}
if (!state.sideReset()) {
((PortalGunItem) stack.getItem()).resetPortalSide(player, stack, PortalGunPortalEntity.PortalSide.ORANGE);
STATES.put(player.getUUID(), new ResetState(gunId, state.startedAt(), true));
}
return true;
}
public static void clear(MinecraftServer server) {
STATES.clear();
}
private static ResetState activeState(ServerPlayer player, UUID gunId) {
ResetState state = STATES.get(player.getUUID());
if (state == null || gunId == null || !state.gunId().equals(gunId)
|| player.serverLevel().getGameTime() - state.startedAt() > 100L) {
STATES.remove(player.getUUID());
return null;
}
return state;
}
private static ItemStack heldPortalGun(ServerPlayer player) {
ItemStack main = player.getMainHandItem();
if (main.getItem() instanceof PortalGunItem) {
return main;
}
ItemStack offhand = player.getOffhandItem();
return offhand.getItem() instanceof PortalGunItem ? offhand : ItemStack.EMPTY;
}
private record ResetState(UUID gunId, long startedAt, boolean sideReset) {
}
}
@@ -1,12 +1,17 @@
package com.craisinlord.antarchy.content.portalgun;
import java.util.HashMap;
import java.util.HashSet;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
@@ -16,9 +21,14 @@ public final class PortalGunSavedData extends SavedData {
private static final String ID = "antarchy_portal_gun_pairs";
private static final String ENTRIES_KEY = "Entries";
private static final String OWNER_KEY = "Owner";
private static final String DIMENSION_KEY = "Dimension";
private static final String GUN_KEY = "Gun";
private static final String BLUE_KEY = "Blue";
private static final String ORANGE_KEY = "Orange";
private final Map<UUID, PairRecord> pairs = new HashMap<>();
private static final String CHANNELS_KEY = "Channels";
private final Map<PairKey, PairRecord> pairs = new HashMap<>();
private final Map<UUID, PairRecord> legacyPairs = new HashMap<>();
private final Map<OwnerChannelKey, UUID> channelGunIds = new HashMap<>();
public static PortalGunSavedData create() {
return new PortalGunSavedData();
@@ -31,7 +41,20 @@ public final class PortalGunSavedData extends SavedData {
UUID owner = entryTag.getUUID(OWNER_KEY);
UUID blue = entryTag.hasUUID(BLUE_KEY) ? entryTag.getUUID(BLUE_KEY) : null;
UUID orange = entryTag.hasUUID(ORANGE_KEY) ? entryTag.getUUID(ORANGE_KEY) : null;
data.pairs.put(owner, new PairRecord(blue, orange));
PairRecord record = new PairRecord(blue, orange);
ResourceLocation dimension = entryTag.contains(DIMENSION_KEY) ? ResourceLocation.tryParse(entryTag.getString(DIMENSION_KEY)) : null;
if (dimension == null) {
data.legacyPairs.put(owner, record);
} else {
UUID gunId = entryTag.hasUUID(GUN_KEY) ? entryTag.getUUID(GUN_KEY) : null;
data.pairs.put(new PairKey(owner, dimension, gunId), record);
}
}
for (net.minecraft.nbt.Tag rawChannel : tag.getList(CHANNELS_KEY, net.minecraft.nbt.Tag.TAG_COMPOUND)) {
CompoundTag channelTag = (CompoundTag) rawChannel;
if (channelTag.hasUUID(OWNER_KEY) && channelTag.hasUUID(GUN_KEY) && channelTag.contains("Channel")) {
data.channelGunIds.put(new OwnerChannelKey(channelTag.getUUID(OWNER_KEY), channelTag.getString("Channel")), channelTag.getUUID(GUN_KEY));
}
}
return data;
}
@@ -39,21 +62,44 @@ public final class PortalGunSavedData extends SavedData {
@Override
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
ListTag entries = new ListTag();
for (Map.Entry<UUID, PairRecord> entry : this.pairs.entrySet()) {
for (Map.Entry<PairKey, PairRecord> entry : this.pairs.entrySet()) {
CompoundTag entryTag = new CompoundTag();
entryTag.putUUID(OWNER_KEY, entry.getKey());
if (entry.getValue().bluePortalId() != null) {
entryTag.putUUID(BLUE_KEY, entry.getValue().bluePortalId());
}
if (entry.getValue().orangePortalId() != null) {
entryTag.putUUID(ORANGE_KEY, entry.getValue().orangePortalId());
entryTag.putUUID(OWNER_KEY, entry.getKey().owner());
entryTag.putString(DIMENSION_KEY, entry.getKey().dimension().toString());
if (entry.getKey().gunId() != null) {
entryTag.putUUID(GUN_KEY, entry.getKey().gunId());
}
writePair(entryTag, entry.getValue());
entries.add(entryTag);
}
for (Map.Entry<UUID, PairRecord> entry : this.legacyPairs.entrySet()) {
CompoundTag entryTag = new CompoundTag();
entryTag.putUUID(OWNER_KEY, entry.getKey());
writePair(entryTag, entry.getValue());
entries.add(entryTag);
}
ListTag channels = new ListTag();
for (Map.Entry<OwnerChannelKey, UUID> entry : this.channelGunIds.entrySet()) {
CompoundTag channelTag = new CompoundTag();
channelTag.putUUID(OWNER_KEY, entry.getKey().owner());
channelTag.putString("Channel", entry.getKey().channelName());
channelTag.putUUID(GUN_KEY, entry.getValue());
channels.add(channelTag);
}
tag.put(ENTRIES_KEY, entries);
tag.put(CHANNELS_KEY, channels);
return tag;
}
private static void writePair(CompoundTag tag, PairRecord record) {
if (record.bluePortalId() != null) {
tag.putUUID(BLUE_KEY, record.bluePortalId());
}
if (record.orangePortalId() != null) {
tag.putUUID(ORANGE_KEY, record.orangePortalId());
}
}
private static PortalGunSavedData get(MinecraftServer server) {
return server.overworld().getDataStorage().computeIfAbsent(
new SavedData.Factory<>(PortalGunSavedData::create, PortalGunSavedData::load, null),
@@ -61,35 +107,95 @@ public final class PortalGunSavedData extends SavedData {
);
}
public static Optional<UUID> getPortalId(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side) {
PairRecord record = get(server).pairs.get(owner);
public static Optional<UUID> getPortalId(MinecraftServer server, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, ResourceLocation dimension) {
PortalGunSavedData data = get(server);
UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId);
data.migrateLegacyPair(server, owner);
data.adoptLegacyPairs(server, owner, resolvedGunId, channelName);
PairRecord record = data.pairs.get(new PairKey(owner, dimension, resolvedGunId));
if (record == null) {
return Optional.empty();
}
data.synchronizeLoadedPortal(server, owner, dimension, record.bluePortalId(), resolvedGunId, channelName);
data.synchronizeLoadedPortal(server, owner, dimension, record.orangePortalId(), resolvedGunId, channelName);
return Optional.ofNullable(side == PortalGunPortalEntity.PortalSide.BLUE ? record.bluePortalId() : record.orangePortalId());
}
public static boolean isRegistered(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side, UUID portalId) {
return getPortalId(server, owner, side).map(portalId::equals).orElse(false);
public static Optional<UUID> getPortalId(MinecraftServer server, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, ResourceLocation dimension) {
return getPortalId(server, owner, gunId, String.valueOf(gunId), side, dimension);
}
public static void setPortal(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side, UUID portalId) {
public static Optional<UUID> getGunIdForPortal(MinecraftServer server, UUID owner, ResourceLocation dimension, UUID portalId) {
PortalGunSavedData data = get(server);
PairRecord current = data.pairs.getOrDefault(owner, new PairRecord(null, null));
data.migrateLegacyPair(server, owner);
for (Map.Entry<PairKey, PairRecord> entry : data.pairs.entrySet()) {
PairKey key = entry.getKey();
if (key.owner().equals(owner) && key.dimension().equals(dimension) && key.gunId() != null
&& (portalId.equals(entry.getValue().bluePortalId()) || portalId.equals(entry.getValue().orangePortalId()))) {
return Optional.of(key.gunId());
}
}
return Optional.empty();
}
public static Optional<String> getChannelNameForPortal(MinecraftServer server, UUID owner, ResourceLocation dimension, UUID portalId) {
PortalGunSavedData data = get(server);
for (Map.Entry<PairKey, PairRecord> entry : data.pairs.entrySet()) {
PairKey key = entry.getKey();
if (!key.owner().equals(owner) || !key.dimension().equals(dimension)
|| (!portalId.equals(entry.getValue().bluePortalId()) && !portalId.equals(entry.getValue().orangePortalId()))) {
continue;
}
return data.channelGunIds.entrySet().stream()
.filter(channel -> channel.getKey().owner().equals(owner) && channel.getValue().equals(key.gunId()))
.map(channel -> channel.getKey().channelName())
.findFirst();
}
return Optional.empty();
}
public static boolean isRegistered(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, UUID portalId) {
return isRegistered(level, owner, gunId, String.valueOf(gunId), side, portalId);
}
public static boolean isRegistered(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, UUID portalId) {
return getPortalId(level.getServer(), owner, gunId, channelName, side, level.dimension().location()).map(portalId::equals).orElse(false);
}
public static void setPortal(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, UUID portalId) {
setPortal(level, owner, gunId, String.valueOf(gunId), side, portalId);
}
public static void setPortal(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, UUID portalId) {
PortalGunSavedData data = get(level.getServer());
ResourceLocation dimension = level.dimension().location();
UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId);
data.migrateLegacyPair(level.getServer(), owner);
data.adoptLegacyPairs(level.getServer(), owner, resolvedGunId, channelName);
PairKey key = new PairKey(owner, dimension, resolvedGunId);
PairRecord current = data.pairs.getOrDefault(key, new PairRecord(null, null));
PairRecord updated = side == PortalGunPortalEntity.PortalSide.BLUE
? new PairRecord(portalId, current.orangePortalId())
: new PairRecord(current.bluePortalId(), portalId);
if (updated.bluePortalId() == null && updated.orangePortalId() == null) {
data.pairs.remove(owner);
data.pairs.remove(key);
} else {
data.pairs.put(owner, updated);
data.pairs.put(key, updated);
}
data.setDirty();
}
public static void clearPortal(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side, UUID portalId) {
public static void clearPortal(MinecraftServer server, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side, UUID portalId, ResourceLocation dimension) {
clearPortal(server, owner, gunId, String.valueOf(gunId), side, portalId, dimension);
}
public static void clearPortal(MinecraftServer server, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side, UUID portalId, ResourceLocation dimension) {
PortalGunSavedData data = get(server);
PairRecord current = data.pairs.get(owner);
UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId);
data.migrateLegacyPair(server, owner);
data.adoptLegacyPairs(server, owner, resolvedGunId, channelName);
PairKey key = new PairKey(owner, dimension, resolvedGunId);
PairRecord current = data.pairs.get(key);
if (current == null) {
return;
}
@@ -102,48 +208,247 @@ public final class PortalGunSavedData extends SavedData {
orange = null;
}
if (blue == null && orange == null) {
data.pairs.remove(owner);
data.pairs.remove(key);
} else {
data.pairs.put(owner, new PairRecord(blue, orange));
data.pairs.put(key, new PairRecord(blue, orange));
}
data.setDirty();
}
public static void clearAllPortals(MinecraftServer server, UUID owner) {
public static void clearAllPortals(ServerLevel level, UUID owner, UUID gunId) {
clearAllPortals(level, owner, gunId, String.valueOf(gunId));
}
public static void clearAllPortals(ServerLevel level, UUID owner, UUID gunId, String channelName) {
MinecraftServer server = level.getServer();
PortalGunSavedData data = get(server);
PairRecord current = data.pairs.remove(owner);
data.setDirty();
if (current == null) {
return;
UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId);
data.migrateLegacyPair(server, owner);
data.adoptLegacyPairs(server, owner, resolvedGunId, channelName);
Set<UUID> portalIds = new HashSet<>();
PairRecord legacy = gunId == null ? data.legacyPairs.remove(owner) : null;
if (legacy != null) {
addPortalIds(portalIds, legacy);
}
data.pairs.entrySet().removeIf(entry -> {
if (!entry.getKey().owner().equals(owner) || !entry.getKey().dimension().equals(level.dimension().location())
|| !Objects.equals(entry.getKey().gunId(), resolvedGunId)) {
return false;
}
addPortalIds(portalIds, entry.getValue());
return true;
});
data.setDirty();
for (UUID portalId : portalIds) {
discardIfLoaded(server, portalId);
}
discardIfLoaded(server, current.bluePortalId());
discardIfLoaded(server, current.orangePortalId());
}
public static PortalGunPortalEntity findLoadedPortal(MinecraftServer server, UUID owner, PortalGunPortalEntity.PortalSide side) {
Optional<UUID> portalId = getPortalId(server, owner, side);
public static void clearPortalSide(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side) {
clearPortalSide(level, owner, gunId, String.valueOf(gunId), side);
}
public static void clearPortalSide(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) {
MinecraftServer server = level.getServer();
PortalGunSavedData data = get(server);
UUID resolvedGunId = data.resolveChannelGunId(owner, channelName, gunId);
data.migrateLegacyPair(server, owner);
data.adoptLegacyPairs(server, owner, resolvedGunId, channelName);
Set<UUID> portalIds = new HashSet<>();
for (Map.Entry<PairKey, PairRecord> entry : new HashMap<>(data.pairs).entrySet()) {
PairKey key = entry.getKey();
if (!key.owner().equals(owner) || !key.dimension().equals(level.dimension().location()) || !Objects.equals(key.gunId(), resolvedGunId)) {
continue;
}
PairRecord pair = entry.getValue();
UUID removed = side == PortalGunPortalEntity.PortalSide.BLUE ? pair.bluePortalId() : pair.orangePortalId();
if (removed == null) {
continue;
}
portalIds.add(removed);
PairRecord remaining = side == PortalGunPortalEntity.PortalSide.BLUE
? new PairRecord(null, pair.orangePortalId())
: new PairRecord(pair.bluePortalId(), null);
if (remaining.bluePortalId() == null && remaining.orangePortalId() == null) {
data.pairs.remove(key);
} else {
data.pairs.put(key, remaining);
}
}
data.setDirty();
for (UUID portalId : portalIds) {
discardIfLoaded(server, portalId);
}
}
public static PortalGunPortalEntity findLoadedPortal(ServerLevel level, UUID owner, UUID gunId, PortalGunPortalEntity.PortalSide side) {
Optional<UUID> portalId = getPortalId(level.getServer(), owner, gunId, side, level.dimension().location());
if (portalId.isEmpty()) {
return null;
}
for (ServerLevel level : server.getAllLevels()) {
Entity entity = level.getEntity(portalId.get());
if (entity instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
return portal;
return entity instanceof PortalGunPortalEntity portal && !portal.isRemoved() ? portal : null;
}
public static PortalGunPortalEntity findLoadedPortal(ServerLevel level, UUID owner, UUID gunId, String channelName, PortalGunPortalEntity.PortalSide side) {
Optional<UUID> portalId = getPortalId(level.getServer(), owner, gunId, channelName, side, level.dimension().location());
if (portalId.isEmpty()) {
return null;
}
Entity entity = level.getEntity(portalId.get());
return entity instanceof PortalGunPortalEntity portal && !portal.isRemoved() ? portal : null;
}
private UUID resolveChannelGunId(UUID owner, String channelName, UUID gunId) {
if (channelName == null || channelName.isEmpty()) {
return gunId;
}
OwnerChannelKey key = new OwnerChannelKey(owner, channelName);
UUID existing = this.channelGunIds.get(key);
if (existing != null) {
return existing;
}
UUID channelGunId = UUID.nameUUIDFromBytes((owner + "\u0000" + channelName).getBytes(StandardCharsets.UTF_8));
this.channelGunIds.put(key, channelGunId);
if (gunId != null && !gunId.equals(channelGunId)) {
this.migrateGunPairs(owner, gunId, channelGunId);
}
this.setDirty();
return channelGunId;
}
private void migrateGunPairs(UUID owner, UUID previousGunId, UUID channelGunId) {
for (Map.Entry<PairKey, PairRecord> entry : new HashMap<>(this.pairs).entrySet()) {
PairKey previousKey = entry.getKey();
if (!previousKey.owner().equals(owner) || !previousGunId.equals(previousKey.gunId())) {
continue;
}
PairKey channelKey = new PairKey(owner, previousKey.dimension(), channelGunId);
PairRecord existing = this.pairs.get(channelKey);
PairRecord migrated = mergePairs(existing, entry.getValue());
this.pairs.remove(previousKey);
this.pairs.put(channelKey, migrated);
}
}
private static PairRecord mergePairs(PairRecord preferred, PairRecord fallback) {
if (preferred == null) {
return fallback;
}
return new PairRecord(
preferred.bluePortalId() == null ? fallback.bluePortalId() : preferred.bluePortalId(),
preferred.orangePortalId() == null ? fallback.orangePortalId() : preferred.orangePortalId()
);
}
private void migrateLegacyPair(MinecraftServer server, UUID owner) {
PairRecord legacy = this.legacyPairs.get(owner);
if (legacy == null) {
return;
}
ServerLevel blueLevel = findPortalLevel(server, legacy.bluePortalId());
ServerLevel orangeLevel = findPortalLevel(server, legacy.orangePortalId());
UUID legacyBlue = legacy.bluePortalId();
UUID legacyOrange = legacy.orangePortalId();
if (blueLevel != null) {
Entity blueEntity = blueLevel.getEntity(legacyBlue);
UUID blueGunId = blueEntity instanceof PortalGunPortalEntity portal ? portal.getGunId() : null;
PairKey key = new PairKey(owner, blueLevel.dimension().location(), blueGunId);
this.pairs.put(key, withPortal(this.pairs.get(key), PortalGunPortalEntity.PortalSide.BLUE, legacyBlue));
legacyBlue = null;
}
if (orangeLevel != null) {
Entity orangeEntity = orangeLevel.getEntity(legacyOrange);
UUID orangeGunId = orangeEntity instanceof PortalGunPortalEntity portal ? portal.getGunId() : null;
PairKey key = new PairKey(owner, orangeLevel.dimension().location(), orangeGunId);
this.pairs.put(key, withPortal(this.pairs.get(key), PortalGunPortalEntity.PortalSide.ORANGE, legacyOrange));
legacyOrange = null;
}
if (!Objects.equals(legacy.bluePortalId(), legacyBlue) || !Objects.equals(legacy.orangePortalId(), legacyOrange)) {
if (legacyBlue == null && legacyOrange == null) {
this.legacyPairs.remove(owner);
} else {
this.legacyPairs.put(owner, new PairRecord(legacyBlue, legacyOrange));
}
this.setDirty();
}
}
private void adoptLegacyPairs(MinecraftServer server, UUID owner, UUID gunId, String channelName) {
if (gunId == null) {
return;
}
boolean changed = false;
for (Map.Entry<PairKey, PairRecord> entry : new HashMap<>(this.pairs).entrySet()) {
PairKey legacyKey = entry.getKey();
if (!legacyKey.owner().equals(owner) || legacyKey.gunId() != null) {
continue;
}
PairKey ownedKey = new PairKey(owner, legacyKey.dimension(), gunId);
if (this.pairs.containsKey(ownedKey)) {
continue;
}
this.pairs.remove(legacyKey);
this.pairs.put(ownedKey, entry.getValue());
synchronizeLoadedPortal(server, owner, legacyKey.dimension(), entry.getValue().bluePortalId(), gunId, channelName);
synchronizeLoadedPortal(server, owner, legacyKey.dimension(), entry.getValue().orangePortalId(), gunId, channelName);
changed = true;
}
if (changed) {
this.setDirty();
}
}
private static void synchronizeLoadedPortal(MinecraftServer server, UUID owner, ResourceLocation dimension, UUID portalId, UUID gunId, String channelName) {
if (portalId == null) {
return;
}
ServerLevel level = server.getLevel(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, dimension));
if (level == null || !(level.getEntity(portalId) instanceof PortalGunPortalEntity portal) || !owner.equals(portal.getOwnerId())) {
return;
}
portal.adoptChannelIdentity(gunId, channelName);
}
private static PairRecord withPortal(PairRecord current, PortalGunPortalEntity.PortalSide side, UUID portalId) {
PairRecord pair = current == null ? new PairRecord(null, null) : current;
return side == PortalGunPortalEntity.PortalSide.BLUE
? new PairRecord(pair.bluePortalId() == null ? portalId : pair.bluePortalId(), pair.orangePortalId())
: new PairRecord(pair.bluePortalId(), pair.orangePortalId() == null ? portalId : pair.orangePortalId());
}
private static ServerLevel findPortalLevel(MinecraftServer server, UUID portalId) {
if (portalId == null) {
return null;
}
for (ServerLevel level : server.getAllLevels()) {
if (level.getEntity(portalId) instanceof PortalGunPortalEntity portal && !portal.isRemoved()) {
return level;
}
}
return null;
}
private static void addPortalIds(Set<UUID> portalIds, PairRecord pair) {
if (pair.bluePortalId() != null) {
portalIds.add(pair.bluePortalId());
}
if (pair.orangePortalId() != null) {
portalIds.add(pair.orangePortalId());
}
}
private static void discardIfLoaded(MinecraftServer server, UUID portalId) {
if (portalId == null) {
return;
}
for (ServerLevel level : server.getAllLevels()) {
if (level.getEntity(portalId) instanceof PortalGunPortalEntity portal) {
ServerLevel level = findPortalLevel(server, portalId);
if (level != null && level.getEntity(portalId) instanceof PortalGunPortalEntity portal) {
portal.discard();
return;
}
}
private record PairKey(UUID owner, ResourceLocation dimension, UUID gunId) {
}
private record OwnerChannelKey(UUID owner, String channelName) {
}
private record PairRecord(UUID bluePortalId, UUID orangePortalId) {
@@ -14,19 +14,19 @@ public final class PortalGunTransformUtil {
}
public static Quaternionf createTransform(PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal) {
return createTransform(sourcePortal.getWidthVec(), sourcePortal.getUpVec(), sourcePortal.getNormalVec().scale(-1.0D), destinationPortal.getWidthVec(), destinationPortal.getUpVec(), destinationPortal.getNormalVec());
return createTransform(sourcePortal.getWidthVec(), sourcePortal.getUpVec(), sourcePortal.getNormalVec().scale(-1.0D), destinationPortal.getWidthVec().scale(-1.0D), destinationPortal.getUpVec(), destinationPortal.getNormalVec());
}
public static Quaternionf createTransform(Vec3 sourceWidth, Vec3 sourceUp, Vec3 sourceForward, Vec3 destinationWidth, Vec3 destinationUp, Vec3 destinationForward) {
Matrix3f sourceBasis = new Matrix3f(
(float) sourceWidth.x, (float) sourceUp.x, (float) sourceForward.x,
(float) sourceWidth.y, (float) sourceUp.y, (float) sourceForward.y,
(float) sourceWidth.z, (float) sourceUp.z, (float) sourceForward.z
(float) sourceWidth.x, (float) sourceWidth.y, (float) sourceWidth.z,
(float) sourceUp.x, (float) sourceUp.y, (float) sourceUp.z,
(float) sourceForward.x, (float) sourceForward.y, (float) sourceForward.z
);
Matrix3f destinationBasis = new Matrix3f(
(float) destinationWidth.x, (float) destinationUp.x, (float) destinationForward.x,
(float) destinationWidth.y, (float) destinationUp.y, (float) destinationForward.y,
(float) destinationWidth.z, (float) destinationUp.z, (float) destinationForward.z
(float) destinationWidth.x, (float) destinationWidth.y, (float) destinationWidth.z,
(float) destinationUp.x, (float) destinationUp.y, (float) destinationUp.z,
(float) destinationForward.x, (float) destinationForward.y, (float) destinationForward.z
);
Matrix3f transform = destinationBasis.mul(sourceBasis.transpose(new Matrix3f()), new Matrix3f());
return transform.getNormalizedRotation(new Quaternionf());
@@ -48,9 +48,9 @@ public final class PortalGunTransformUtil {
Vec3 destinationWidth = destinationPortal.getWidthVec().normalize();
Vec3 destinationUp = destinationPortal.getUpVec().normalize();
Vec3 destinationNormal = destinationPortal.getNormalVec().normalize();
return destinationWidth.scale(relativePosition.dot(sourceWidth))
return destinationWidth.scale(-relativePosition.dot(sourceWidth))
.add(destinationUp.scale(relativePosition.dot(sourceUp)))
.add(destinationNormal.scale(relativePosition.dot(sourceNormal)));
.add(destinationNormal.scale(-relativePosition.dot(sourceNormal)));
}
public static Vec3 transformVector(PortalGunPortalEntity sourcePortal, PortalGunPortalEntity destinationPortal, Vec3 vector) {
@@ -60,7 +60,7 @@ public final class PortalGunTransformUtil {
Vec3 destinationWidth = destinationPortal.getWidthVec().normalize();
Vec3 destinationUp = destinationPortal.getUpVec().normalize();
Vec3 destinationOut = destinationPortal.getNormalVec().normalize();
return destinationWidth.scale(vector.dot(sourceWidth))
return destinationWidth.scale(-vector.dot(sourceWidth))
.add(destinationUp.scale(vector.dot(sourceUp)))
.add(destinationOut.scale(vector.dot(sourceInto)));
}
@@ -102,11 +102,11 @@ public final class PortalGunTransformUtil {
public static Quaternionf orientationQuaternion(Vec3 look, Vec3 up) {
Vec3 forward = look.normalize();
Vec3 correctedUp = up.subtract(forward.scale(up.dot(forward))).normalize();
Vec3 right = correctedUp.cross(forward).normalize();
Vec3 right = forward.cross(correctedUp).normalize();
Matrix3f basis = new Matrix3f(
(float) right.x, (float) correctedUp.x, (float) (-forward.x),
(float) right.y, (float) correctedUp.y, (float) (-forward.y),
(float) right.z, (float) correctedUp.z, (float) (-forward.z)
(float) right.x, (float) right.y, (float) right.z,
(float) correctedUp.x, (float) correctedUp.y, (float) correctedUp.z,
(float) -forward.x, (float) -forward.y, (float) -forward.z
);
return basis.getNormalizedRotation(new Quaternionf());
}
@@ -35,6 +35,11 @@ public record PortalGunWorldPortalShape(
return this.createPlaneAround(offset, this.halfDepth);
}
public PortalGunWorldPortalShape scaleAperture(double scale) {
double safeScale = Math.max(0.0D, scale);
return new PortalGunWorldPortalShape(this.center, this.normal, this.up, this.right, this.halfWidth * safeScale, this.halfHeight * safeScale, this.halfDepth);
}
public Vec3[] getCorners(double normalOffset) {
Vec3 center = this.center.add(this.normal.scale(normalOffset));
Vec3 width = this.right.scale(this.halfWidth);
@@ -76,13 +81,7 @@ public record PortalGunWorldPortalShape(
}
public boolean shouldRenderFront(Vec3 cameraPos) {
PortalLocalCoords coords = this.localCoords(cameraPos);
if (coords.depth() <= -0.25D) {
return false;
}
double depthScale = Math.min(DEFAULT_SCAN_DISTANCE, Math.max(0.0D, coords.depth()));
return Math.abs(coords.horizontal()) <= this.halfWidth + depthScale
&& Math.abs(coords.vertical()) <= this.halfHeight + depthScale;
return this.localCoords(cameraPos).depth() > 0.0D;
}
public boolean intersectsFront(AABB box, double tolerance) {
@@ -128,8 +127,8 @@ public record PortalGunWorldPortalShape(
double horizontalOuter = this.halfWidth + borderThickness;
double verticalOuter = this.halfHeight + borderThickness;
return List.of(
this.createLocalBox(-horizontalOuter, this.halfWidth, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth),
this.createLocalBox(-this.halfWidth, horizontalOuter, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth),
this.createLocalBox(-horizontalOuter, -this.halfWidth, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth),
this.createLocalBox(this.halfWidth, horizontalOuter, -verticalOuter, verticalOuter, -this.halfDepth, this.halfDepth),
this.createLocalBox(-this.halfWidth, this.halfWidth, -verticalOuter, -this.halfHeight, -this.halfDepth, this.halfDepth),
this.createLocalBox(-this.halfWidth, this.halfWidth, this.halfHeight, verticalOuter, -this.halfDepth, this.halfDepth)
);
@@ -95,6 +95,12 @@ public class TimeDilationFieldEntity extends Entity {
|| this.anchorId != null && this.anchorId.equals(entity.getUUID())) {
return false;
}
if (this.ownerId != null && this.level() instanceof ServerLevel level
&& level.getEntity(this.ownerId) instanceof QueenEntity
&& entity instanceof LivingEntity living
&& !com.craisinlord.antarchy.content.effect.RoyalEffectEligibility.canApplyDilated(living)) {
return false;
}
if (this.chronosphere && !(entity instanceof LivingEntity || entity instanceof Projectile || entity instanceof ItemEntity)) {
return false;
}
@@ -146,8 +146,7 @@ public final class TimeDilationManager {
if (!(entity instanceof TimeDilationEntityAccess access) || entity instanceof TimeDilationFieldEntity) {
continue;
}
if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE)
|| (entity instanceof net.minecraft.world.entity.LivingEntity living && !RoyalEffectEligibility.canApplyDilated(living))) {
if (entity.getType().is(com.craisinlord.antarchy.content.AntarchyTags.Entities.TIME_DILATION_IMMUNE)) {
if (RoyalEffectHooks.dilatedHolder() != null && entity instanceof net.minecraft.world.entity.LivingEntity living) {
living.removeEffect(RoyalEffectHooks.dilatedHolder());
}
@@ -2,9 +2,11 @@ package com.craisinlord.antarchy.content.worldgen.thoraxis;
import com.craisinlord.antarchy.Antarchy;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import it.unimi.dsi.fastutil.longs.Long2DoubleOpenHashMap;
import java.util.List;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
@@ -56,7 +58,8 @@ public final class ThoraxisBiomeSource extends BiomeSource {
Codec.INT.optionalFieldOf("dream_dunes_max_y", 160).forGetter(ThoraxisBiomeSource::dreamDunesMaxY),
Codec.INT.optionalFieldOf("umbral_hills_max_y", 160).forGetter(ThoraxisBiomeSource::umbralHillsMaxY),
Codec.INT.optionalFieldOf("lucid_pools_min_y", 116).forGetter(ThoraxisBiomeSource::lucidPoolsMinY),
Codec.INT.optionalFieldOf("nadir_forest_min_y", 128).forGetter(ThoraxisBiomeSource::nadirForestMinY)
Codec.INT.optionalFieldOf("nadir_forest_min_y", 128).forGetter(ThoraxisBiomeSource::nadirForestMinY),
RegionalBiomeOverlay.CODEC.listOf().optionalFieldOf("regional_overlays", List.of()).forGetter(ThoraxisBiomeSource::regionalOverlays)
).apply(instance, ThoraxisBiomeSource::new));
private final Climate.ParameterList<Holder<Biome>> parameters;
@@ -65,6 +68,7 @@ public final class ThoraxisBiomeSource extends BiomeSource {
private final int umbralHillsMaxY;
private final int lucidPoolsMinY;
private final int nadirForestMinY;
private final List<RegionalBiomeOverlay> regionalOverlays;
private final int dreamDunesMaxQuartY;
private final int umbralHillsMaxQuartY;
private final int lucidPoolsMinQuartY;
@@ -76,19 +80,21 @@ public final class ThoraxisBiomeSource extends BiomeSource {
private Holder<Biome> umbralHillsBiome;
private Holder<Biome> lucidPoolsBiome;
private Holder<Biome> nadirForestBiome;
private List<ResolvedRegionalBiomeOverlay> resolvedRegionalOverlays = List.of();
private static final ThreadLocal<Long2DoubleOpenHashMap> REGION_CACHE =
ThreadLocal.withInitial(() -> new Long2DoubleOpenHashMap(512));
private static final ThreadLocal<Long2DoubleOpenHashMap> VERTICAL_CACHE =
ThreadLocal.withInitial(() -> new Long2DoubleOpenHashMap(64));
public ThoraxisBiomeSource(Climate.ParameterList<Holder<Biome>> parameters, int dreamDunesMaxY, int umbralHillsMaxY, int lucidPoolsMinY, int nadirForestMinY) {
public ThoraxisBiomeSource(Climate.ParameterList<Holder<Biome>> parameters, int dreamDunesMaxY, int umbralHillsMaxY, int lucidPoolsMinY, int nadirForestMinY, List<RegionalBiomeOverlay> regionalOverlays) {
this.parameters = parameters;
this.delegate = MultiNoiseBiomeSource.createFromList(parameters);
this.dreamDunesMaxY = dreamDunesMaxY;
this.umbralHillsMaxY = umbralHillsMaxY;
this.lucidPoolsMinY = lucidPoolsMinY;
this.nadirForestMinY = nadirForestMinY;
this.regionalOverlays = regionalOverlays;
this.dreamDunesMaxQuartY = QuartPos.fromBlock(dreamDunesMaxY);
this.umbralHillsMaxQuartY = QuartPos.fromBlock(umbralHillsMaxY);
this.lucidPoolsMinQuartY = QuartPos.fromBlock(lucidPoolsMinY);
@@ -109,6 +115,7 @@ public final class ThoraxisBiomeSource extends BiomeSource {
this.umbralHillsBiome = this.findBiome(UMBRAL_HILLS);
this.lucidPoolsBiome = this.findBiome(LUCID_POOLS);
this.nadirForestBiome = this.findBiome(NADIR_FOREST);
this.resolvedRegionalOverlays = this.resolveRegionalOverlays();
this.biomesResolved = true;
}
}
@@ -133,6 +140,10 @@ public final class ThoraxisBiomeSource extends BiomeSource {
return this.umbralHillsMaxY;
}
private List<RegionalBiomeOverlay> regionalOverlays() {
return this.regionalOverlays;
}
@Override
protected MapCodec<? extends BiomeSource> codec() {
return CODEC;
@@ -179,6 +190,12 @@ public final class ThoraxisBiomeSource extends BiomeSource {
return this.umbralHillsBiome;
}
for (ResolvedRegionalBiomeOverlay overlay : this.resolvedRegionalOverlays) {
if (region >= overlay.minNoise() && region < overlay.maxNoise()) {
return overlay.biome();
}
}
Holder<Biome> delegateBiome = this.delegate.getNoiseBiome(x, y, z, sampler);
return this.nightmareWastesBiome != null ? this.nightmareWastesBiome : delegateBiome;
}
@@ -198,6 +215,37 @@ public final class ThoraxisBiomeSource extends BiomeSource {
.orElse(null);
}
private Holder<Biome> findBiome(ResourceLocation location) {
ResourceKey<Biome> key = ResourceKey.create(Registries.BIOME, location);
return this.findBiome(key);
}
private List<ResolvedRegionalBiomeOverlay> resolveRegionalOverlays() {
return this.regionalOverlays.stream().map(overlay -> {
Holder<Biome> biome = this.findBiome(overlay.biome());
if (biome == null) {
throw new IllegalStateException("Thoraxis regional overlay biome " + overlay.biome() + " must be listed in the biome source parameters");
}
return new ResolvedRegionalBiomeOverlay(biome, overlay.minNoise(), overlay.maxNoise());
}).toList();
}
private record RegionalBiomeOverlay(ResourceLocation biome, double minNoise, double maxNoise) {
private static final Codec<RegionalBiomeOverlay> CODEC = RecordCodecBuilder.<RegionalBiomeOverlay>create(instance -> instance.group(
ResourceLocation.CODEC.fieldOf("biome").forGetter(RegionalBiomeOverlay::biome),
Codec.DOUBLE.fieldOf("min_noise").forGetter(RegionalBiomeOverlay::minNoise),
Codec.DOUBLE.fieldOf("max_noise").forGetter(RegionalBiomeOverlay::maxNoise)
).apply(instance, RegionalBiomeOverlay::new)).flatXmap(
overlay -> overlay.minNoise() >= 0.0D && overlay.maxNoise() <= 1.0D && overlay.minNoise() < overlay.maxNoise()
? DataResult.success(overlay)
: DataResult.error(() -> "Regional biome overlay noise bounds must satisfy 0 <= min_noise < max_noise <= 1"),
DataResult::success
);
}
private record ResolvedRegionalBiomeOverlay(Holder<Biome> biome, double minNoise, double maxNoise) {
}
private double cachedRegionNoise(int quartX, int quartZ) {
long key = net.minecraft.world.level.ChunkPos.asLong(quartX, quartZ);
Long2DoubleOpenHashMap cache = REGION_CACHE.get();
@@ -1,5 +1,6 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.client.renderer.SodiumCompat;
import java.util.List;
import java.util.Set;
import org.objectweb.asm.tree.ClassNode;
@@ -28,6 +29,17 @@ public final class AntarchyMixinPlugin implements IMixinConfigPlugin {
if (!classPresent(targetClassName)) {
return false;
}
if (SodiumCompat.isLoaded()
&& (mixinClassName.endsWith(".LevelRendererPortalViewAreaAccessor")
|| mixinClassName.endsWith(".LevelRendererPortalViewAreaMixin")
|| mixinClassName.endsWith(".LevelRendererPortalChunkBudgetMixin")
|| mixinClassName.endsWith(".PortalViewChunkCacheMixin")
|| mixinClassName.endsWith(".PortalViewSectionSortingMixin"))) {
return false;
}
if (mixinClassName.endsWith(".SodiumWorldRendererMixin")) {
return SodiumCompat.isLoaded();
}
if (mixinClassName.startsWith("com.craisinlord.antarchy.mixins.compat.scguns.")) {
return classPresent("top.ribs.scguns.entity.projectile.ProjectileEntity");
}
@@ -0,0 +1,11 @@
package com.craisinlord.antarchy.mixins;
import net.minecraft.world.entity.item.FallingBlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(FallingBlockEntity.class)
public interface FallingBlockEntityAccessor {
@Accessor("time")
void antarchy$setTime(int time);
}
@@ -1,6 +1,7 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.item.InnateEnchantmentHelper;
import com.craisinlord.antarchy.content.item.GiantFryingPanItem;
import net.minecraft.world.inventory.GrindstoneMenu;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
@@ -19,6 +20,11 @@ public abstract class GrindstoneMenuMixin {
@Inject(method = "computeResult", at = @At("RETURN"), cancellable = true)
private void antarchy$blockInnateOnlyNoOp(ItemStack firstInput, ItemStack secondInput, CallbackInfoReturnable<ItemStack> cir) {
if (firstInput.getItem() instanceof GiantFryingPanItem || secondInput.getItem() instanceof GiantFryingPanItem) {
cir.setReturnValue(ItemStack.EMPTY);
return;
}
if (!firstInput.isEmpty() && secondInput.isEmpty()
&& InnateEnchantmentHelper.hasInnateNonCurseEnchantments(firstInput)
&& !InnateEnchantmentHelper.hasRemovableNonCurseEnchantments(firstInput)) {
@@ -1,7 +1,7 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.gravity.AntarchyGravityApi;
import com.craisinlord.antarchy.content.portalgun.PortalGunCollisionHelper;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@@ -10,11 +10,37 @@ import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Entity.class)
public abstract class PortalGunEntityCollisionMixin {
@Unique
private AABB antarchy$portalGunStartBox;
@Unique
private AABB antarchy$portalGunMoveStartBox;
@Unique
private Vec3 antarchy$portalGunMoveStartPosition;
@Inject(method = "move", at = @At("HEAD"))
private void antarchy$capturePortalGunMovementStart(net.minecraft.world.entity.MoverType type, Vec3 movement, CallbackInfo ci) {
Entity entity = (Entity) (Object) this;
this.antarchy$portalGunMoveStartBox = entity.getBoundingBox();
this.antarchy$portalGunMoveStartPosition = entity.position();
}
@Inject(method = "move", at = @At("RETURN"))
private void antarchy$teleportMobOnPortalCrossing(net.minecraft.world.entity.MoverType type, Vec3 movement, CallbackInfo ci) {
Entity entity = (Entity) (Object) this;
if (this.antarchy$portalGunMoveStartBox != null && this.antarchy$portalGunMoveStartPosition != null) {
PortalGunPortalEntity.teleportEntityAfterMovement(
entity,
this.antarchy$portalGunMoveStartBox,
entity.position().subtract(this.antarchy$portalGunMoveStartPosition)
);
}
this.antarchy$portalGunMoveStartBox = null;
this.antarchy$portalGunMoveStartPosition = null;
}
@Inject(method = "collide", at = @At("HEAD"))
private void antarchy$capturePortalGunStartBox(Vec3 movement, CallbackInfoReturnable<Vec3> cir) {
@@ -24,10 +50,11 @@ public abstract class PortalGunEntityCollisionMixin {
@Inject(method = "collide", at = @At("RETURN"), cancellable = true)
private void antarchy$restorePortalCrossingMotion(Vec3 movement, CallbackInfoReturnable<Vec3> cir) {
Entity entity = (Entity) (Object) this;
if (this.antarchy$portalGunStartBox == null || AntarchyGravityApi.isGravityInverted(entity)) {
if (this.antarchy$portalGunStartBox == null) {
return;
}
Vec3 adjusted = PortalGunCollisionHelper.resolveCollision(entity, this.antarchy$portalGunStartBox, movement, cir.getReturnValue());
cir.setReturnValue(adjusted);
}
}
@@ -0,0 +1,29 @@
package com.craisinlord.antarchy.mixins;
import com.craisinlord.antarchy.content.item.PortalGunItem;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Zombie.class)
public abstract class PortalGunZombieMixin {
@Inject(method = "tick", at = @At("TAIL"))
private void antarchy$firePortalGun(CallbackInfo ci) {
Zombie zombie = (Zombie) (Object) this;
if (!(zombie.level() instanceof ServerLevel serverLevel)) {
return;
}
ItemStack stack = zombie.getMainHandItem();
if (stack.getItem() instanceof PortalGunItem portalGun && zombie.getRandom().nextFloat() < 0.008F) {
PortalGunPortalEntity.PortalSide side = zombie.getRandom().nextBoolean()
? PortalGunPortalEntity.PortalSide.BLUE
: PortalGunPortalEntity.PortalSide.ORANGE;
portalGun.fireMobPortal(serverLevel, zombie, stack, side);
}
}
}
@@ -0,0 +1,18 @@
package com.craisinlord.antarchy.mixins.client;
import net.minecraft.client.Camera;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Camera.class)
public interface CameraBasisAccessor {
@Accessor("forwards")
Vector3f antarchy$getForwards();
@Accessor("up")
Vector3f antarchy$getUp();
@Accessor("left")
Vector3f antarchy$getLeft();
}
@@ -0,0 +1,65 @@
package com.craisinlord.antarchy.mixins.client;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalRendererPool;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.chunk.RenderRegionCache;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
import net.minecraft.world.level.lighting.LevelLightEngine;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(LevelRenderer.class)
public abstract class LevelRendererPortalChunkBudgetMixin {
@Redirect(
method = "renderLevel",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;pollLightUpdates()V")
)
private void antarchy$skipProxyLightQueuePoll(ClientLevel level) {
if (!PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) {
level.pollLightUpdates();
}
}
@Redirect(
method = "renderLevel",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/lighting/LevelLightEngine;runLightUpdates()I")
)
private int antarchy$skipProxyLightQueueDrain(LevelLightEngine lightEngine) {
return PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this) ? 0 : lightEngine.runLightUpdates();
}
@Redirect(
method = "compileSections",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher;rebuildSectionSync(Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;Lnet/minecraft/client/renderer/chunk/RenderRegionCache;)V"
)
)
private void antarchy$queuePortalSyncRebuilds(SectionRenderDispatcher dispatcher, SectionRenderDispatcher.RenderSection section, RenderRegionCache cache) {
PortalGunPortalRendererPool.rebuildSection((LevelRenderer) (Object) this, dispatcher, section, cache);
}
@Redirect(
method = "compileSections",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;rebuildSectionAsync(Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher;Lnet/minecraft/client/renderer/chunk/RenderRegionCache;)V"
)
)
private void antarchy$budgetPortalAsyncRebuilds(SectionRenderDispatcher.RenderSection section, SectionRenderDispatcher dispatcher, RenderRegionCache cache) {
PortalGunPortalRendererPool.rebuildSectionAsync((LevelRenderer) (Object) this, dispatcher, section, cache);
}
@Redirect(
method = "compileSections",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/client/renderer/chunk/SectionRenderDispatcher$RenderSection;setNotDirty()V"
)
)
private void antarchy$keepUnscheduledPortalSectionsDirty(SectionRenderDispatcher.RenderSection section) {
PortalGunPortalRendererPool.setNotDirty(section);
}
}
@@ -0,0 +1,55 @@
package com.craisinlord.antarchy.mixins.client;
import com.craisinlord.antarchy.content.client.PortalGunPortalRenderState;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalRendererPool;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewRenderer;
import com.craisinlord.antarchy.content.client.renderer.PortalStencilTarget;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LevelRenderer.class)
public abstract class LevelRendererPortalLifecycleMixin {
@Inject(method = "renderLevel", at = @At("HEAD"))
private void antarchy$preparePortalStencil(CallbackInfo ci) {
if (!PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) {
PortalStencilTarget.prepareMainTarget(Minecraft.getInstance().getMainRenderTarget());
}
}
@Inject(method = "setLevel", at = @At("HEAD"))
private void antarchy$clearPortalViewsOnLevelChange(ClientLevel level, CallbackInfo ci) {
if (PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) {
return;
}
PortalGunPortalRendererPool.clear();
PortalGunPortalViewAreaManager.clear();
}
@Inject(method = "close", at = @At("HEAD"))
private void antarchy$clearPortalViewsOnClose(CallbackInfo ci) {
if (PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) {
return;
}
PortalGunPortalRendererPool.clear();
PortalGunPortalViewAreaManager.clear();
PortalGunPortalViewRenderer.releaseTargets();
}
@Inject(method = "allChanged", at = @At("TAIL"))
private void antarchy$clearPortalViewsOnRendererRebuild(CallbackInfo ci) {
if (PortalGunPortalRendererPool.isProxy((LevelRenderer) (Object) this)) {
return;
}
if (PortalGunPortalRenderState.getContext() == null) {
PortalGunPortalRendererPool.clear();
PortalGunPortalViewAreaManager.clear();
PortalGunPortalViewRenderer.releaseTargets();
}
}
}
@@ -0,0 +1,34 @@
package com.craisinlord.antarchy.mixins.client;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.culling.Frustum;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.world.entity.Entity;
import org.joml.Matrix4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(LevelRenderer.class)
public interface LevelRendererPortalSceneInvoker {
@Invoker("setupRender")
void antarchy$setupPortalScene(Camera camera, Frustum frustum, boolean capturedFrustum, boolean spectator);
@Invoker("compileSections")
void antarchy$compilePortalSections(Camera camera);
@Invoker("renderSectionLayer")
void antarchy$renderPortalSectionLayer(RenderType renderType, double cameraX, double cameraY, double cameraZ, Matrix4f modelViewMatrix, Matrix4f projectionMatrix);
@Invoker("renderEntity")
void antarchy$renderPortalEntity(Entity entity, double cameraX, double cameraY, double cameraZ, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource);
@Invoker("renderSnowAndRain")
void antarchy$renderPortalWeather(LightTexture lightTexture, float partialTick, double cameraX, double cameraY, double cameraZ);
@Invoker("renderWorldBorder")
void antarchy$renderPortalWorldBorder(Camera camera);
}
@@ -0,0 +1,15 @@
package com.craisinlord.antarchy.mixins.client;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.PostChain;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(LevelRenderer.class)
public interface LevelRendererPortalTargetsAccessor {
@Accessor("entityEffect")
PostChain antarchy$getEntityEffect();
@Accessor("transparencyChain")
PostChain antarchy$getTransparencyChain();
}
@@ -0,0 +1,160 @@
package com.craisinlord.antarchy.mixins.client;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.mojang.blaze3d.vertex.VertexBuffer;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.SectionOcclusionGraph;
import net.minecraft.client.renderer.ViewArea;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.Vec3;
import java.util.Set;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(LevelRenderer.class)
public interface LevelRendererPortalViewAreaAccessor {
@Accessor("viewArea")
ViewArea antarchy$getViewArea();
@Accessor("viewArea")
void antarchy$setViewArea(ViewArea viewArea);
@Accessor("sectionOcclusionGraph")
SectionOcclusionGraph antarchy$getSectionOcclusionGraph();
@Mutable
@Accessor("sectionOcclusionGraph")
void antarchy$setSectionOcclusionGraph(SectionOcclusionGraph graph);
@Accessor("visibleSections")
ObjectArrayList<SectionRenderDispatcher.RenderSection> antarchy$getVisibleSections();
@Mutable
@Accessor("visibleSections")
void antarchy$setVisibleSections(ObjectArrayList<SectionRenderDispatcher.RenderSection> sections);
@Accessor("globalBlockEntities")
Set<BlockEntity> antarchy$getGlobalBlockEntities();
@Mutable
@Accessor("globalBlockEntities")
void antarchy$setGlobalBlockEntities(Set<BlockEntity> blockEntities);
@Accessor("lastCameraSectionX")
int antarchy$getLastCameraSectionX();
@Accessor("lastCameraSectionX")
void antarchy$setLastCameraSectionX(int value);
@Accessor("lastCameraSectionY")
int antarchy$getLastCameraSectionY();
@Accessor("lastCameraSectionY")
void antarchy$setLastCameraSectionY(int value);
@Accessor("lastCameraSectionZ")
int antarchy$getLastCameraSectionZ();
@Accessor("lastCameraSectionZ")
void antarchy$setLastCameraSectionZ(int value);
@Accessor("prevCamX")
double antarchy$getPrevCamX();
@Accessor("prevCamX")
void antarchy$setPrevCamX(double value);
@Accessor("prevCamY")
double antarchy$getPrevCamY();
@Accessor("prevCamY")
void antarchy$setPrevCamY(double value);
@Accessor("prevCamZ")
double antarchy$getPrevCamZ();
@Accessor("prevCamZ")
void antarchy$setPrevCamZ(double value);
@Accessor("prevCamRotX")
double antarchy$getPrevCamRotX();
@Accessor("prevCamRotX")
void antarchy$setPrevCamRotX(double value);
@Accessor("prevCamRotY")
double antarchy$getPrevCamRotY();
@Accessor("prevCamRotY")
void antarchy$setPrevCamRotY(double value);
@Accessor("xTransparentOld")
double antarchy$getTransparentSortX();
@Accessor("xTransparentOld")
void antarchy$setTransparentSortX(double value);
@Accessor("yTransparentOld")
double antarchy$getTransparentSortY();
@Accessor("yTransparentOld")
void antarchy$setTransparentSortY(double value);
@Accessor("zTransparentOld")
double antarchy$getTransparentSortZ();
@Accessor("zTransparentOld")
void antarchy$setTransparentSortZ(double value);
@Accessor("cloudBuffer")
VertexBuffer antarchy$getCloudBuffer();
@Accessor("cloudBuffer")
void antarchy$setCloudBuffer(VertexBuffer cloudBuffer);
@Accessor("generateClouds")
boolean antarchy$getGenerateClouds();
@Accessor("generateClouds")
void antarchy$setGenerateClouds(boolean generateClouds);
@Accessor("prevCloudX")
int antarchy$getPrevCloudX();
@Accessor("prevCloudX")
void antarchy$setPrevCloudX(int value);
@Accessor("prevCloudY")
int antarchy$getPrevCloudY();
@Accessor("prevCloudY")
void antarchy$setPrevCloudY(int value);
@Accessor("prevCloudZ")
int antarchy$getPrevCloudZ();
@Accessor("prevCloudZ")
void antarchy$setPrevCloudZ(int value);
@Accessor("prevCloudColor")
Vec3 antarchy$getPrevCloudColor();
@Accessor("prevCloudColor")
void antarchy$setPrevCloudColor(Vec3 value);
@Accessor("prevCloudsType")
CloudStatus antarchy$getPrevCloudsType();
@Accessor("prevCloudsType")
void antarchy$setPrevCloudsType(CloudStatus value);
@Accessor("rainSoundTime")
int antarchy$getRainSoundTime();
@Accessor("rainSoundTime")
void antarchy$setRainSoundTime(int value);
}
@@ -0,0 +1,63 @@
package com.craisinlord.antarchy.mixins.client;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager;
import com.craisinlord.antarchy.content.client.PortalGunPortalRenderState;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.chunk.SectionRenderDispatcher;
import net.minecraft.core.BlockPos;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.ChunkPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LevelRenderer.class)
public abstract class LevelRendererPortalViewAreaMixin {
@Redirect(method = "renderSnowAndRain", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientLevel;playLocalSound(Lnet/minecraft/core/BlockPos;Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FFZ)V"), require = 0)
private void antarchy$suppressPortalWeatherSound(ClientLevel level, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch, boolean distanceDelay) {
if (PortalGunPortalRenderState.getContext() == null) {
level.playLocalSound(pos, sound, source, volume, pitch, distanceDelay);
}
}
@ModifyExpressionValue(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getX()D"), require = 0)
private double antarchy$usePortalCameraX(double original) {
PortalGunPortalRenderState.PortalRenderContext context = PortalGunPortalRenderState.getContext();
return context == null ? original : context.cameraPos().x;
}
@ModifyExpressionValue(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getY()D"), require = 0)
private double antarchy$usePortalCameraY(double original) {
PortalGunPortalRenderState.PortalRenderContext context = PortalGunPortalRenderState.getContext();
return context == null ? original : context.cameraPos().y;
}
@ModifyExpressionValue(method = "setupRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;getZ()D"), require = 0)
private double antarchy$usePortalCameraZ(double original) {
PortalGunPortalRenderState.PortalRenderContext context = PortalGunPortalRenderState.getContext();
return context == null ? original : context.cameraPos().z;
}
@Inject(method = "addRecentlyCompiledSection", at = @At("HEAD"), cancellable = true)
private void antarchy$routePortalSectionCompile(SectionRenderDispatcher.RenderSection section, CallbackInfo ci) {
if (PortalGunPortalViewAreaManager.routeSectionCompiled(section)) {
ci.cancel();
}
}
@Inject(method = "setSectionDirty(IIIZ)V", at = @At("TAIL"))
private void antarchy$dirtyPortalSection(int sectionX, int sectionY, int sectionZ, boolean playerChanged, CallbackInfo ci) {
PortalGunPortalViewAreaManager.onSectionDirty(sectionX, sectionY, sectionZ, playerChanged);
}
@Inject(method = "onChunkLoaded", at = @At("TAIL"))
private void antarchy$updatePortalChunkGraph(ChunkPos pos, CallbackInfo ci) {
PortalGunPortalViewAreaManager.onChunkLoaded(pos);
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.mixins.client;
import net.minecraft.world.entity.LivingEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(LivingEntity.class)
public interface LivingEntityPortalLerpAccessor {
@Mutable
@Accessor("lerpSteps")
void antarchy$setLerpSteps(int steps);
@Mutable
@Accessor("lerpHeadSteps")
void antarchy$setLerpHeadSteps(int steps);
}
@@ -0,0 +1,14 @@
package com.craisinlord.antarchy.mixins.client;
import com.mojang.blaze3d.pipeline.RenderTarget;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Minecraft.class)
public interface MinecraftMainRenderTargetAccessor {
@Mutable
@Accessor("mainRenderTarget")
void antarchy$setMainRenderTarget(RenderTarget renderTarget);
}
@@ -0,0 +1,68 @@
package com.craisinlord.antarchy.mixins.client;
import com.craisinlord.antarchy.content.portalgun.PortalGunPortalEntity;
import com.craisinlord.antarchy.content.portalgun.PortalGunWorldPortalShape;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPacketListener.class)
public abstract class PortalGunEntityTeleportPacketMixin {
@Inject(method = "handleTeleportEntity", at = @At("TAIL"))
private void antarchy$snapPortalTransitEntity(ClientboundTeleportEntityPacket packet, CallbackInfo ci) {
Minecraft minecraft = Minecraft.getInstance();
if (minecraft.level == null) {
return;
}
Entity entity = minecraft.level.getEntity(packet.getId());
Vec3 destination = new Vec3(packet.getX(), packet.getY(), packet.getZ());
if (entity == null) {
return;
}
antarchy$snapToPortalExit(entity, destination);
}
private static void antarchy$snapToPortalExit(Entity entity, Vec3 destination) {
if (!isNearPortalExit(entity, destination)) {
return;
}
entity.lerpTo(destination.x, destination.y, destination.z, entity.getYRot(), entity.getXRot(), 0);
entity.setPortalCooldown(PortalGunPortalEntity.TELEPORT_COOLDOWN_TICKS);
entity.setPos(destination.x, destination.y, destination.z);
entity.xo = destination.x;
entity.yo = destination.y;
entity.zo = destination.z;
entity.yRotO = entity.getYRot();
entity.xRotO = entity.getXRot();
if (entity instanceof LivingEntity livingEntity) {
LivingEntityPortalLerpAccessor accessor = (LivingEntityPortalLerpAccessor) livingEntity;
accessor.antarchy$setLerpHeadSteps(0);
}
}
private static boolean isNearPortalExit(Entity entity, Vec3 destination) {
Vec3 offset = destination.subtract(entity.position());
double entityExtent = Math.max(entity.getBbWidth(), entity.getBbHeight());
double edgeAllowance = entityExtent * 0.5D;
AABB search = entity.getBoundingBox().move(offset).inflate(entityExtent + PortalGunWorldPortalShape.DEFAULT_SCAN_DISTANCE);
Vec3 probe = destination.add(0.0D, entity.getEyeHeight(), 0.0D);
for (PortalGunPortalEntity portal : entity.level().getEntitiesOfClass(PortalGunPortalEntity.class, search, Entity::isAlive)) {
PortalGunWorldPortalShape.PortalLocalCoords coords = portal.getWorldPortalShape().localCoords(probe);
if (coords.depth() >= -entityExtent - 0.5D
&& coords.depth() <= PortalGunWorldPortalShape.DEFAULT_SCAN_DISTANCE + 0.5D
&& Math.abs(coords.horizontal()) <= portal.getWorldPortalShape().halfWidth() + edgeAllowance
&& Math.abs(coords.vertical()) <= portal.getWorldPortalShape().halfHeight() + edgeAllowance) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,18 @@
package com.craisinlord.antarchy.mixins.client;
import com.craisinlord.antarchy.content.client.PortalGunZoomClientState;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(GameRenderer.class)
public abstract class PortalGunZoomFovMixin {
@Inject(method = "getFov", at = @At("RETURN"), cancellable = true)
private void antarchy$applyPortalGunZoom(Camera camera, float partialTick, boolean useFovSetting, CallbackInfoReturnable<Double> cir) {
cir.setReturnValue(PortalGunZoomClientState.applyFov(cir.getReturnValue(), partialTick, Minecraft.getInstance().options));
}
}
@@ -0,0 +1,17 @@
package com.craisinlord.antarchy.mixins.client;
import com.craisinlord.antarchy.content.client.renderer.PortalGunPortalViewAreaManager;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.world.level.ChunkPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientChunkCache.class)
public abstract class PortalViewChunkCacheMixin {
@Inject(method = "drop", at = @At("TAIL"))
private void antarchy$invalidatePortalChunk(ChunkPos pos, CallbackInfo ci) {
PortalGunPortalViewAreaManager.onChunkDropped(pos);
}
}
Loaded 100 of 189 files, more files were not shown because too many files have changed in this diff. Show more