From 924c1a1bf815a2a2fc51239bb04a30e656723bab Mon Sep 17 00:00:00 2001 From: TGdoesCode Date: Tue, 17 Mar 2026 13:28:17 +0100 Subject: [PATCH] attack each other and backroom block --- gradle.properties | 2 +- .../tggamesyt/szar/AttackEnemyTeamGoal.java | 18 ++ .../dev/tggamesyt/szar/CommunistEntity.java | 46 +++- .../java/dev/tggamesyt/szar/HitterEntity.java | 8 +- .../java/dev/tggamesyt/szar/NaziEntity.java | 46 +++- .../java/dev/tggamesyt/szar/PortalBlock.java | 252 ++++++++++++++++++ .../dev/tggamesyt/szar/PortalDataState.java | 46 ++++ .../java/dev/tggamesyt/szar/StalinEntity.java | 7 +- src/main/java/dev/tggamesyt/szar/Szar.java | 32 +++ .../java/dev/tggamesyt/szar/TeamMember.java | 5 + .../java/dev/tggamesyt/szar/TrackerBlock.java | 52 ++++ .../tggamesyt/szar/TrackerBlockEntity.java | 91 +++++++ .../dev/tggamesyt/szar/mixin/NoClipMixin.java | 39 +++ src/main/resources/szar.mixins.json | 1 + 14 files changed, 639 insertions(+), 6 deletions(-) create mode 100644 src/main/java/dev/tggamesyt/szar/AttackEnemyTeamGoal.java create mode 100644 src/main/java/dev/tggamesyt/szar/PortalBlock.java create mode 100644 src/main/java/dev/tggamesyt/szar/PortalDataState.java create mode 100644 src/main/java/dev/tggamesyt/szar/TeamMember.java create mode 100644 src/main/java/dev/tggamesyt/szar/TrackerBlock.java create mode 100644 src/main/java/dev/tggamesyt/szar/TrackerBlockEntity.java create mode 100644 src/main/java/dev/tggamesyt/szar/mixin/NoClipMixin.java diff --git a/gradle.properties b/gradle.properties index b7fd245..cd99f33 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.20.1 yarn_mappings=1.20.1+build.10 loader_version=0.18.3 # Mod Properties -mod_version=26.3.16.1 +mod_version=26.3.17 maven_group=dev.tggamesyt archives_base_name=szar # Dependencies diff --git a/src/main/java/dev/tggamesyt/szar/AttackEnemyTeamGoal.java b/src/main/java/dev/tggamesyt/szar/AttackEnemyTeamGoal.java new file mode 100644 index 0000000..cf7b2a4 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/AttackEnemyTeamGoal.java @@ -0,0 +1,18 @@ +// AttackEnemyTeamGoal.java +package dev.tggamesyt.szar; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.ActiveTargetGoal; +import net.minecraft.entity.mob.PathAwareEntity; + +public class AttackEnemyTeamGoal extends ActiveTargetGoal { + + public AttackEnemyTeamGoal(PathAwareEntity mob, String myTeam) { + super(mob, LivingEntity.class, true, target -> { + if (target instanceof TeamMember other) { + return !other.getTeam().equals(myTeam); + } + return false; + }); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/CommunistEntity.java b/src/main/java/dev/tggamesyt/szar/CommunistEntity.java index 66ca6e1..2c6c714 100644 --- a/src/main/java/dev/tggamesyt/szar/CommunistEntity.java +++ b/src/main/java/dev/tggamesyt/szar/CommunistEntity.java @@ -1,7 +1,9 @@ package dev.tggamesyt.szar; +import net.minecraft.entity.EntityData; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.goal.WanderAroundFarGoal; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; @@ -13,10 +15,13 @@ import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtList; import net.minecraft.nbt.NbtString; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.LocalDifficulty; +import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; -public class CommunistEntity extends PathAwareEntity implements Arrestable{ +public class CommunistEntity extends PathAwareEntity implements Arrestable, TeamMember { public static boolean arrestable = false; @Nullable @@ -31,6 +36,9 @@ public class CommunistEntity extends PathAwareEntity implements Arrestable{ this.goalSelector.add(2, new FollowLeaderWanderGoal(this, 1.0D, 6.0F)); this.goalSelector.add(3, new WanderAroundFarGoal(this, 0.8D)); this.goalSelector.add(1, new AK47AttackGoal(this, 16.0F, 2)); + + this.targetSelector.add(1, new AggroOnHitRevengeGoal(this)); + this.targetSelector.add(2, new AttackEnemyTeamGoal(this, "communist")); } @@ -85,5 +93,41 @@ public class CommunistEntity extends PathAwareEntity implements Arrestable{ public StalinEntity getLeader() { return this.leader; } + @Override + public String getTeam() { + return "communist"; + } + @Override + @Nullable + public EntityData initialize( + ServerWorldAccess world, + LocalDifficulty difficulty, + SpawnReason spawnReason, + @Nullable EntityData entityData, + @Nullable NbtCompound entityNbt + ) { + EntityData data = super.initialize(world, difficulty, spawnReason, entityData, entityNbt); + + // Only auto-assign if NOT spawned as part of Stalin's group + // (Stalin's group sets the leader manually after this call) + if (this.leader == null && world instanceof ServerWorld serverWorld) { + StalinEntity nearest = serverWorld.getEntitiesByClass( + StalinEntity.class, + this.getBoundingBox().expand(24), + s -> s.isAlive() + ).stream() + .min((a, b) -> Double.compare( + a.squaredDistanceTo(this), + b.squaredDistanceTo(this) + )) + .orElse(null); + + if (nearest != null) { + this.setLeader(nearest); + } + } + + return data; + } } diff --git a/src/main/java/dev/tggamesyt/szar/HitterEntity.java b/src/main/java/dev/tggamesyt/szar/HitterEntity.java index b280bad..d601f2e 100644 --- a/src/main/java/dev/tggamesyt/szar/HitterEntity.java +++ b/src/main/java/dev/tggamesyt/szar/HitterEntity.java @@ -25,7 +25,7 @@ import java.util.List; import static dev.tggamesyt.szar.Szar.NaziEntityType; -public class HitterEntity extends PathAwareEntity implements Arrestable{ +public class HitterEntity extends PathAwareEntity implements Arrestable, TeamMember { public static boolean arrestable = true; @@ -40,6 +40,7 @@ public class HitterEntity extends PathAwareEntity implements Arrestable{ this.goalSelector.add(3, new LookAroundGoal(this)); this.targetSelector.add(1, new AggroOnHitRevengeGoal(this)); + this.targetSelector.add(2, new AttackEnemyTeamGoal(this, "nazi")); } @@ -146,5 +147,8 @@ public class HitterEntity extends PathAwareEntity implements Arrestable{ } } - + @Override + public String getTeam() { + return "nazi"; + } } diff --git a/src/main/java/dev/tggamesyt/szar/NaziEntity.java b/src/main/java/dev/tggamesyt/szar/NaziEntity.java index 71071b6..d8aaf94 100644 --- a/src/main/java/dev/tggamesyt/szar/NaziEntity.java +++ b/src/main/java/dev/tggamesyt/szar/NaziEntity.java @@ -1,7 +1,9 @@ package dev.tggamesyt.szar; +import net.minecraft.entity.EntityData; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.goal.WanderAroundFarGoal; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; @@ -13,10 +15,13 @@ import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtList; import net.minecraft.nbt.NbtString; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.LocalDifficulty; +import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; -public class NaziEntity extends PathAwareEntity implements Arrestable{ +public class NaziEntity extends PathAwareEntity implements Arrestable, TeamMember { private boolean hithandPlaying = false; private int hithandTimer = 0; // ticks remaining @@ -84,6 +89,9 @@ public class NaziEntity extends PathAwareEntity implements Arrestable{ this.goalSelector.add(2, new FollowLeaderWanderGoal(this, 1.0D, 6.0F)); this.goalSelector.add(3, new WanderAroundFarGoal(this, 0.8D)); this.goalSelector.add(1, new AK47AttackGoal(this, 16.0F, 2)); + + this.targetSelector.add(1, new AggroOnHitRevengeGoal(this)); + this.targetSelector.add(2, new AttackEnemyTeamGoal(this, "nazi")); } @@ -138,5 +146,41 @@ public class NaziEntity extends PathAwareEntity implements Arrestable{ public HitterEntity getLeader() { return this.leader; } + @Override + public String getTeam() { + return "nazi"; + } + @Override + @Nullable + public EntityData initialize( + ServerWorldAccess world, + LocalDifficulty difficulty, + SpawnReason spawnReason, + @Nullable EntityData entityData, + @Nullable NbtCompound entityNbt + ) { + EntityData data = super.initialize(world, difficulty, spawnReason, entityData, entityNbt); + + // Only auto-assign if NOT spawned as part of Stalin's group + // (Stalin's group sets the leader manually after this call) + if (this.leader == null && world instanceof ServerWorld serverWorld) { + HitterEntity nearest = serverWorld.getEntitiesByClass( + HitterEntity.class, + this.getBoundingBox().expand(24), + s -> s.isAlive() + ).stream() + .min((a, b) -> Double.compare( + a.squaredDistanceTo(this), + b.squaredDistanceTo(this) + )) + .orElse(null); + + if (nearest != null) { + this.setLeader(nearest); + } + } + + return data; + } } diff --git a/src/main/java/dev/tggamesyt/szar/PortalBlock.java b/src/main/java/dev/tggamesyt/szar/PortalBlock.java new file mode 100644 index 0000000..1812e02 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/PortalBlock.java @@ -0,0 +1,252 @@ +package dev.tggamesyt.szar; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.World; + +import java.util.ArrayList; +import java.util.List; + +public class PortalBlock extends Block { + + // Cooldown tracker so players don't teleport 20x per second + private static final java.util.Map cooldowns = new java.util.HashMap<>(); + + public PortalBlock(Settings settings) { + super(settings); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, net.minecraft.world.BlockView world, + BlockPos pos, ShapeContext ctx) { + return VoxelShapes.empty(); + } + + @Override + public void onEntityCollision(BlockState state, World world, BlockPos pos, + Entity entity) { + if (world.isClient) return; + if (!(entity instanceof ServerPlayerEntity player)) return; + + // Cooldown check — 3 seconds + long now = world.getTime(); + Long last = cooldowns.get(player.getUuid()); + if (last != null && now - last < 60) return; + cooldowns.put(player.getUuid(), now); + + // Find the TrackerBlock above (within 5 blocks) + TrackerBlockEntity tracker = findTrackerAbove(world, pos); + if (tracker == null) return; + + MinecraftServer server = world.getServer(); + if (server == null) return; + + if (!tracker.isNetherSide) { + // --- OVERWORLD → NETHER --- + teleportToNether(player, tracker, server, pos); + } else { + // --- NETHER → OVERWORLD --- + teleportToOverworld(player, tracker, server); + } + } + + private void teleportToNether(ServerPlayerEntity player, TrackerBlockEntity tracker, + MinecraftServer server, BlockPos portalPos) { + // Save return position (a few blocks above entry) + tracker.returnX = player.getX(); + tracker.returnY = player.getY() + 3; + tracker.returnZ = player.getZ(); + tracker.markDirty(); + + // Save inventory + NbtList savedInventory = saveInventory(player); + + // Clear inventory + player.getInventory().clear(); + + // Register player as inside + tracker.addPlayer(player.getUuid()); + + // Teleport to nether + ServerWorld nether = server.getWorld(World.NETHER); + if (nether == null) return; + + double netherX = player.getX(); + double netherZ = player.getZ(); + double netherY = findSafeY(nether, (int) netherX, (int) netherZ); + + // Store saved inventory in player's persistent data + NbtCompound persistent = player.writeNbt(new NbtCompound()); + // We use a custom data attachment via the player's nbt + saveInventoryToPlayer(player, savedInventory); + + // Store which overworld tracker owns this player + NbtCompound tag = getOrCreateCustomData(player); + tag.putInt("OwnerTrackerX", tracker.getPos().getX()); + tag.putInt("OwnerTrackerY", tracker.getPos().getY()); + tag.putInt("OwnerTrackerZ", tracker.getPos().getZ()); + + // Generate nether-side portal structure + BlockPos netherPortalPos = new BlockPos((int) netherX, (int) netherY, (int) netherZ); + generateNetherPortal(nether, netherPortalPos, tracker); + + // Teleport + player.teleport(nether, netherX, netherY + 1, netherZ, + player.getYaw(), player.getPitch()); + } + + private void teleportToOverworld(ServerPlayerEntity player, TrackerBlockEntity tracker, + MinecraftServer server) { + // Restore inventory + restoreInventoryToPlayer(player); + + // Remove from nether tracker + tracker.removePlayer(player.getUuid()); + + // Find overworld paired tracker and remove from that too + ServerWorld overworld = server.getWorld(World.OVERWORLD); + if (overworld != null && tracker.pairedTrackerPos != null) { + if (overworld.getBlockEntity(tracker.pairedTrackerPos) + instanceof TrackerBlockEntity owTracker) { + owTracker.removePlayer(player.getUuid()); + + // If no players left, remove both trackers and their portal blocks + if (!owTracker.hasPlayers()) { + removePortalStructure(overworld, tracker.pairedTrackerPos); + removePortalStructure((ServerWorld) player.getWorld(), tracker.getPos()); + } + } + } + + // Teleport back (a few blocks above entry) + player.teleport(overworld, + tracker.returnX, tracker.returnY, tracker.returnZ, + player.getYaw(), player.getPitch()); + } + + // --- Helpers --- + + private TrackerBlockEntity findTrackerAbove(World world, BlockPos portalPos) { + for (int i = 1; i <= 5; i++) { + BlockPos check = portalPos.up(i); + if (world.getBlockState(check).getBlock() instanceof TrackerBlock) { + if (world.getBlockEntity(check) instanceof TrackerBlockEntity te) { + return te; + } + } + } + return null; + } + + private double findSafeY(ServerWorld world, int x, int z) { + // Search from y=100 downward for solid ground with 2 air blocks above + for (int y = 100; y > 10; y--) { + BlockPos feet = new BlockPos(x, y, z); + BlockPos head = feet.up(); + BlockPos ground = feet.down(); + if (!world.getBlockState(feet).isSolidBlock(world, feet) + && !world.getBlockState(head).isSolidBlock(world, head) + && world.getBlockState(ground).isSolidBlock(world, ground)) { + return y; + } + } + return 64; // fallback + } + + private void generateNetherPortal(ServerWorld nether, BlockPos portalPos, + TrackerBlockEntity overworldTracker) { + // Place TrackerBlock 4 blocks above portal + BlockPos trackerPos = portalPos.up(4); + nether.setBlockState(trackerPos, Szar.TRACKER_BLOCK.getDefaultState()); + + if (nether.getBlockEntity(trackerPos) instanceof TrackerBlockEntity netherTracker) { + netherTracker.isNetherSide = true; + netherTracker.returnX = overworldTracker.returnX; + netherTracker.returnY = overworldTracker.returnY; + netherTracker.returnZ = overworldTracker.returnZ; + netherTracker.pairedTrackerPos = overworldTracker.getPos(); + overworldTracker.pairedTrackerPos = trackerPos; + overworldTracker.markDirty(); + netherTracker.markDirty(); + } + + // Place portal block at bottom + nether.setBlockState(portalPos, Szar.PORTAL_BLOCK.getDefaultState()); + } + + private void removePortalStructure(ServerWorld world, BlockPos trackerPos) { + // Remove tracker + world.removeBlock(trackerPos, false); + // Remove portal block (4 below) + world.removeBlock(trackerPos.down(4), false); + } + + // Inventory persistence via player NBT custom data + private static final String INV_KEY = "SzarSavedInventory"; + + private NbtList saveInventory(ServerPlayerEntity player) { + NbtList list = new NbtList(); + PlayerInventory inv = player.getInventory(); + for (int i = 0; i < inv.size(); i++) { + ItemStack stack = inv.getStack(i); + if (!stack.isEmpty()) { + NbtCompound entry = new NbtCompound(); + entry.putInt("Slot", i); + stack.writeNbt(entry); + list.add(entry); + } + } + return list; + } + + private void saveInventoryToPlayer(ServerPlayerEntity player, NbtList inventory) { + NbtCompound tag = getOrCreateCustomData(player); + tag.put(INV_KEY, inventory); + } + + private void restoreInventoryToPlayer(ServerPlayerEntity player) { + NbtCompound tag = getOrCreateCustomData(player); + if (!tag.contains(INV_KEY)) return; + + NbtList list = tag.getList(INV_KEY, 10); // 10 = NbtCompound type + player.getInventory().clear(); + + for (int i = 0; i < list.size(); i++) { + NbtCompound entry = list.getCompound(i); + int slot = entry.getInt("Slot"); + ItemStack stack = ItemStack.fromNbt(entry); + player.getInventory().setStack(slot, stack); + } + + tag.remove(INV_KEY); + } + + // Fabric doesn't have a built-in persistent custom data on players in 1.20.1 + // without a mod like FAPI's PersistentStateManager trick. + // The cleanest approach in vanilla Fabric is to store it in a custom + // ServerState attached to the overworld. + private NbtCompound getOrCreateCustomData(ServerPlayerEntity player) { + // We'll use the player's existing nbt stack — store in a sub-tag + // This works for the session but won't survive a crash mid-dimension. + // For a robust solution, use a PersistentState (shown below in PortalDataState.java) + return PortalDataState.getOrCreate( + player.getServer().getWorld(World.OVERWORLD) + ).getOrCreatePlayerData(player.getUuid()); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/PortalDataState.java b/src/main/java/dev/tggamesyt/szar/PortalDataState.java new file mode 100644 index 0000000..4783b65 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/PortalDataState.java @@ -0,0 +1,46 @@ +package dev.tggamesyt.szar; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.PersistentState; + +import java.util.UUID; + +public class PortalDataState extends PersistentState { + + private static final String KEY = "szar_portal_data"; + private final NbtCompound data = new NbtCompound(); + + public NbtCompound getOrCreatePlayerData(UUID uuid) { + String key = uuid.toString(); + if (!data.contains(key)) { + data.put(key, new NbtCompound()); + } + return data.getCompound(key); + } + + public void removePlayerData(UUID uuid) { + data.remove(uuid.toString()); + markDirty(); + } + + @Override + public NbtCompound writeNbt(NbtCompound nbt) { + nbt.put("PlayerData", data.copy()); + return nbt; + } + + public static PortalDataState fromNbt(NbtCompound nbt) { + PortalDataState state = new PortalDataState(); + NbtCompound saved = nbt.getCompound("PlayerData"); + for (String key : saved.getKeys()) { + state.data.put(key, saved.getCompound(key)); + } + return state; + } + + public static PortalDataState getOrCreate(ServerWorld overworld) { + return overworld.getPersistentStateManager() + .getOrCreate(PortalDataState::fromNbt, PortalDataState::new, KEY); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/StalinEntity.java b/src/main/java/dev/tggamesyt/szar/StalinEntity.java index 3ec2396..2b98a59 100644 --- a/src/main/java/dev/tggamesyt/szar/StalinEntity.java +++ b/src/main/java/dev/tggamesyt/szar/StalinEntity.java @@ -26,7 +26,7 @@ import java.util.List; import static dev.tggamesyt.szar.Szar.CommunistEntityType; -public class StalinEntity extends PathAwareEntity implements Arrestable{ +public class StalinEntity extends PathAwareEntity implements Arrestable, TeamMember { public static boolean arrestable = true; @@ -41,6 +41,7 @@ public class StalinEntity extends PathAwareEntity implements Arrestable{ this.goalSelector.add(3, new LookAroundGoal(this)); this.targetSelector.add(1, new AggroOnHitRevengeGoal(this)); + this.targetSelector.add(2, new AttackEnemyTeamGoal(this, "communist")); } @@ -148,4 +149,8 @@ public class StalinEntity extends PathAwareEntity implements Arrestable{ } + @Override + public String getTeam() { + return "communist"; + } } diff --git a/src/main/java/dev/tggamesyt/szar/Szar.java b/src/main/java/dev/tggamesyt/szar/Szar.java index 19c4195..6f80458 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -1065,6 +1065,38 @@ public class Szar implements ModInitializer { }); }); } + // Blocks + public static final TrackerBlock TRACKER_BLOCK = Registry.register( + Registries.BLOCK, new Identifier(MOD_ID, "tracker"), + new TrackerBlock(FabricBlockSettings.create().noCollision().air()) + // .air() makes it not render and not block light + ); + + public static final PortalBlock PORTAL_BLOCK = Registry.register( + Registries.BLOCK, new Identifier(MOD_ID, "portal"), + new PortalBlock(FabricBlockSettings.create().noCollision() + .strength(-1.0f) // indestructible by default, change if needed + .luminance(state -> 11)) // slight glow so you can see it + ); + + // Block items (so you can place them) + public static final BlockItem TRACKER_BLOCK_ITEM = Registry.register( + Registries.ITEM, new Identifier(MOD_ID, "tracker"), + new BlockItem(TRACKER_BLOCK, new FabricItemSettings()) + ); + + public static final BlockItem PORTAL_BLOCK_ITEM = Registry.register( + Registries.ITEM, new Identifier(MOD_ID, "portal"), + new BlockItem(PORTAL_BLOCK, new FabricItemSettings()) + ); + + // Block entity + public static final BlockEntityType TRACKER_BLOCK_ENTITY = + Registry.register( + Registries.BLOCK_ENTITY_TYPE, + new Identifier(MOD_ID, "tracker"), + FabricBlockEntityTypeBuilder.create(TrackerBlockEntity::new, TRACKER_BLOCK).build() + ); // In your ModItems or wherever you register items public static final Item REVOLVER = Registry.register( diff --git a/src/main/java/dev/tggamesyt/szar/TeamMember.java b/src/main/java/dev/tggamesyt/szar/TeamMember.java new file mode 100644 index 0000000..a93daf1 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/TeamMember.java @@ -0,0 +1,5 @@ +package dev.tggamesyt.szar; + +public interface TeamMember { + String getTeam(); // returns "communist" or "tsarist" (or whatever your 2nd team is) +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/TrackerBlock.java b/src/main/java/dev/tggamesyt/szar/TrackerBlock.java new file mode 100644 index 0000000..9079aba --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/TrackerBlock.java @@ -0,0 +1,52 @@ +package dev.tggamesyt.szar; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class TrackerBlock extends Block implements BlockEntityProvider { + + public TrackerBlock(Settings settings) { + super(settings); + } + + // No hitbox + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, + BlockPos pos, ShapeContext ctx) { + return VoxelShapes.empty(); + } + + // No collision + @Override + public VoxelShape getCollisionShape(BlockState state, BlockView world, + BlockPos pos, ShapeContext ctx) { + return VoxelShapes.empty(); + } + + @Nullable + @Override + public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return new TrackerBlockEntity(pos, state); + } + + @Override + public void onPlaced(World world, BlockPos pos, BlockState state, + @Nullable LivingEntity placer, ItemStack itemStack) { + if (world.isClient) return; + + BlockPos portalPos = pos.down(4); + world.setBlockState(portalPos, Szar.PORTAL_BLOCK.getDefaultState()); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/TrackerBlockEntity.java b/src/main/java/dev/tggamesyt/szar/TrackerBlockEntity.java new file mode 100644 index 0000000..2401517 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/TrackerBlockEntity.java @@ -0,0 +1,91 @@ +package dev.tggamesyt.szar; + +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.nbt.NbtString; +import net.minecraft.util.math.BlockPos; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class TrackerBlockEntity extends BlockEntity { + + // The overworld entry coords (used by nether-side tracker to know where to send players back) + public double returnX, returnY, returnZ; + // Whether this tracker is in the nether or overworld + public boolean isNetherSide = false; + // BlockPos of the paired tracker in the other dimension + public BlockPos pairedTrackerPos = null; + + // UUIDs of players currently inside the dimension via this portal + private final Set playersInside = new HashSet<>(); + + public TrackerBlockEntity(BlockPos pos, BlockState state) { + super(Szar.TRACKER_BLOCK_ENTITY, pos, state); + } + + public void addPlayer(UUID uuid) { + playersInside.add(uuid); + markDirty(); + } + + public void removePlayer(UUID uuid) { + playersInside.remove(uuid); + markDirty(); + } + + public boolean hasPlayers() { + return !playersInside.isEmpty(); + } + + public Set getPlayersInside() { + return playersInside; + } + + @Override + public void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + nbt.putDouble("ReturnX", returnX); + nbt.putDouble("ReturnY", returnY); + nbt.putDouble("ReturnZ", returnZ); + nbt.putBoolean("IsNetherSide", isNetherSide); + + if (pairedTrackerPos != null) { + nbt.putInt("PairedX", pairedTrackerPos.getX()); + nbt.putInt("PairedY", pairedTrackerPos.getY()); + nbt.putInt("PairedZ", pairedTrackerPos.getZ()); + } + + NbtList list = new NbtList(); + for (UUID uuid : playersInside) { + list.add(NbtString.of(uuid.toString())); + } + nbt.put("PlayersInside", list); + } + + @Override + public void readNbt(NbtCompound nbt) { + super.readNbt(nbt); + returnX = nbt.getDouble("ReturnX"); + returnY = nbt.getDouble("ReturnY"); + returnZ = nbt.getDouble("ReturnZ"); + isNetherSide = nbt.getBoolean("IsNetherSide"); + + if (nbt.contains("PairedX")) { + pairedTrackerPos = new BlockPos( + nbt.getInt("PairedX"), + nbt.getInt("PairedY"), + nbt.getInt("PairedZ") + ); + } + + NbtList list = nbt.getList("PlayersInside", 8); + playersInside.clear(); + for (int i = 0; i < list.size(); i++) { + playersInside.add(UUID.fromString(list.getString(i))); + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/mixin/NoClipMixin.java b/src/main/java/dev/tggamesyt/szar/mixin/NoClipMixin.java new file mode 100644 index 0000000..61eb858 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/mixin/NoClipMixin.java @@ -0,0 +1,39 @@ +package dev.tggamesyt.szar.mixin; + +import dev.tggamesyt.szar.TrackerBlock; +import net.minecraft.block.AbstractBlock; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.block.EntityShapeContext; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +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(AbstractBlock.AbstractBlockState.class) +public class NoClipMixin { + + @Inject(method = "getCollisionShape*", at = @At("HEAD"), cancellable = true) + private void szar_noClipBelowTracker(BlockView world, BlockPos pos, + ShapeContext ctx, CallbackInfoReturnable cir) { + // Only applies to players + if (!(ctx instanceof EntityShapeContext esc)) return; + Entity entity = esc.getEntity(); + if (!(entity instanceof PlayerEntity)) return; + + // Check 1–5 blocks above this position for a TrackerBlock + for (int i = 1; i <= 5; i++) { + BlockPos above = pos.up(i); + if (world.getBlockState(above).getBlock() instanceof TrackerBlock) { + cir.setReturnValue(VoxelShapes.empty()); + return; + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/szar.mixins.json b/src/main/resources/szar.mixins.json index ab2704e..12cd7b0 100644 --- a/src/main/resources/szar.mixins.json +++ b/src/main/resources/szar.mixins.json @@ -7,6 +7,7 @@ "CraftingScreenHandlerMixin", "CraftingScreenHandlerMixin2", "LivingEntityFallDamageMixin", + "NoClipMixin", "PlaneBlockInteractionMixin", "PlayerEntityMixin", "PlayerInteractionMixin",