Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
package com.tiedup.remake.events.system;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.ItemPadlock;
|
||||
import com.tiedup.remake.items.base.ILockable;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.event.AnvilUpdateEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Event handler for anvil-based padlock attachment.
|
||||
*
|
||||
* Phase 20: Padlock via Anvil
|
||||
*
|
||||
* Allows combining a bondage item (ILockable) with a Padlock in the anvil
|
||||
* to make the item "lockable". A lockable item can then be locked with a Key.
|
||||
*
|
||||
* Flow:
|
||||
* - Place ILockable item in left slot
|
||||
* - Place Padlock in right slot
|
||||
* - Output: Same item with lockable=true
|
||||
* - Cost: 1 XP level, consumes 1 padlock
|
||||
*/
|
||||
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID)
|
||||
public class AnvilEventHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onAnvilUpdate(AnvilUpdateEvent event) {
|
||||
ItemStack left = event.getLeft(); // Bondage item
|
||||
ItemStack right = event.getRight(); // Padlock
|
||||
|
||||
// Skip if either slot is empty
|
||||
if (left.isEmpty() || right.isEmpty()) return;
|
||||
|
||||
// Right slot must be a Padlock
|
||||
if (!(right.getItem() instanceof ItemPadlock)) return;
|
||||
|
||||
// Left slot must be ILockable
|
||||
if (!(left.getItem() instanceof ILockable lockable)) return;
|
||||
|
||||
// Check if item can have a padlock attached (tape, slime, vine, web cannot)
|
||||
if (!lockable.canAttachPadlock()) {
|
||||
return; // Item type cannot have padlock
|
||||
}
|
||||
|
||||
// Item must not already have a padlock attached
|
||||
if (lockable.isLockable(left)) {
|
||||
return; // Already has padlock
|
||||
}
|
||||
|
||||
// Create result: copy of left with lockable=true
|
||||
ItemStack result = left.copy();
|
||||
lockable.setLockable(result, true);
|
||||
|
||||
// Set anvil output
|
||||
event.setOutput(result);
|
||||
event.setCost(1); // 1 XP level cost
|
||||
event.setMaterialCost(1); // Consume 1 padlock
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[AnvilEventHandler] Padlock attachment preview: {} + Padlock",
|
||||
left.getDisplayName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.tiedup.remake.events.system;
|
||||
|
||||
import com.tiedup.remake.bounty.Bounty;
|
||||
import com.tiedup.remake.bounty.BountyManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.state.PlayerCaptorManager;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import java.util.List;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Handles bounty delivery detection and player login events.
|
||||
*
|
||||
* Phase 17: Bounty System
|
||||
*
|
||||
* Detects when:
|
||||
* - A hunter brings a captive near the bounty client
|
||||
* - A player logs in (to return expired bounty rewards)
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE
|
||||
)
|
||||
public class BountyDeliveryHandler {
|
||||
|
||||
// Check every 30 ticks (1.5 seconds) - optimized for performance
|
||||
private static final int CHECK_INTERVAL = 30;
|
||||
private static int tickCounter = 0;
|
||||
|
||||
/**
|
||||
* Periodically check for bounty deliveries.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
|
||||
tickCounter++;
|
||||
if (tickCounter < CHECK_INTERVAL) return;
|
||||
tickCounter = 0;
|
||||
|
||||
// Check all players
|
||||
for (ServerPlayer player : event
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayers()) {
|
||||
checkBountyDelivery(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this player can deliver any captives to nearby bounty clients.
|
||||
*/
|
||||
private static void checkBountyDelivery(ServerPlayer hunter) {
|
||||
// Get hunter's captor manager
|
||||
PlayerBindState hunterState = PlayerBindState.getInstance(hunter);
|
||||
if (hunterState == null) return;
|
||||
|
||||
PlayerCaptorManager captorManager = hunterState.getCaptorManager();
|
||||
if (captorManager == null || !captorManager.hasCaptives()) return;
|
||||
|
||||
// Get bounty manager
|
||||
BountyManager bountyManager = BountyManager.get(hunter.serverLevel());
|
||||
List<Bounty> bounties = bountyManager.getBounties(hunter.serverLevel());
|
||||
if (bounties.isEmpty()) return;
|
||||
|
||||
// Get delivery radius
|
||||
int radius = SettingsAccessor.getBountyDeliveryRadius(
|
||||
hunter.serverLevel().getGameRules()
|
||||
);
|
||||
double radiusSq = (double) radius * radius;
|
||||
|
||||
// Find nearby players using pre-maintained player list (faster than AABB query)
|
||||
List<ServerPlayer> nearbyPlayers = new java.util.ArrayList<>();
|
||||
for (Player p : hunter.level().players()) {
|
||||
if (
|
||||
p != hunter &&
|
||||
p instanceof ServerPlayer sp &&
|
||||
hunter.distanceToSqr(p) <= radiusSq
|
||||
) {
|
||||
nearbyPlayers.add(sp);
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-filter captives: only ServerPlayers that are tied (O(m) instead of O(n*m))
|
||||
List<ServerPlayer> validCaptives = new java.util.ArrayList<>();
|
||||
for (IBondageState captive : captorManager.getCaptives()) {
|
||||
LivingEntity captiveEntity = captive.asLivingEntity();
|
||||
if (captiveEntity == null) continue;
|
||||
if (
|
||||
!(captiveEntity instanceof ServerPlayer captivePlayer)
|
||||
) continue;
|
||||
IBondageState captiveState = KidnappedHelper.getKidnappedState(
|
||||
captivePlayer
|
||||
);
|
||||
if (captiveState == null || !captiveState.isTiedUp()) continue;
|
||||
validCaptives.add(captivePlayer);
|
||||
}
|
||||
|
||||
if (validCaptives.isEmpty()) return;
|
||||
|
||||
// Check each nearby player for potential delivery
|
||||
for (ServerPlayer potentialClient : nearbyPlayers) {
|
||||
for (ServerPlayer captivePlayer : validCaptives) {
|
||||
// Captive must be near the client
|
||||
if (
|
||||
captivePlayer.distanceTo(potentialClient) > radius
|
||||
) continue;
|
||||
|
||||
// Try to deliver
|
||||
boolean delivered = bountyManager.tryDeliverCaptive(
|
||||
hunter,
|
||||
potentialClient,
|
||||
captivePlayer
|
||||
);
|
||||
|
||||
if (delivered) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[BOUNTY] Delivery detected: {} delivered {} to {}",
|
||||
hunter.getName().getString(),
|
||||
captivePlayer.getName().getString(),
|
||||
potentialClient.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle player login - return pending bounty rewards.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player) {
|
||||
BountyManager manager = BountyManager.get(player.serverLevel());
|
||||
manager.onPlayerJoin(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
package com.tiedup.remake.events.system;
|
||||
|
||||
import com.tiedup.remake.blocks.BlockCellCore;
|
||||
import com.tiedup.remake.blocks.entity.CellCoreBlockEntity;
|
||||
import com.tiedup.remake.cells.*;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
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.world.entity.player.Player;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.event.level.BlockEvent;
|
||||
import net.minecraftforge.eventbus.api.EventPriority;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
/**
|
||||
* Central event handler for Cell System V2 Phase 2.
|
||||
*
|
||||
* Handles:
|
||||
* - Selection mode (block click capture for Set Spawn/Delivery/Disguise)
|
||||
* - Door control (block prisoners/non-owners from cell doors)
|
||||
* - Breach detection (wall break → BREACHED/COMPROMISED state)
|
||||
* - Breach repair (block placed at breached position → repair)
|
||||
* - Selection timeout/cancel (player tick)
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE
|
||||
)
|
||||
public class CellV2EventHandler {
|
||||
|
||||
// ==================== RIGHT-CLICK BLOCK ====================
|
||||
|
||||
@SubscribeEvent(priority = EventPriority.HIGH)
|
||||
public static void onRightClickBlock(
|
||||
PlayerInteractEvent.RightClickBlock event
|
||||
) {
|
||||
if (event.getLevel().isClientSide()) return;
|
||||
if (!(event.getEntity() instanceof ServerPlayer player)) return;
|
||||
|
||||
UUID playerId = player.getUUID();
|
||||
BlockPos clickedPos = event.getPos();
|
||||
ServerLevel level = player.serverLevel();
|
||||
|
||||
// 1. Check selection mode first
|
||||
CellSelectionManager.SelectionContext selection =
|
||||
CellSelectionManager.getSelection(playerId);
|
||||
if (selection != null) {
|
||||
event.setCanceled(true);
|
||||
handleSelectionClick(player, level, clickedPos, selection);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Check door control
|
||||
handleDoorControl(event, player, level, clickedPos);
|
||||
}
|
||||
|
||||
// ==================== BLOCK BREAK ====================
|
||||
|
||||
@SubscribeEvent(priority = EventPriority.HIGH)
|
||||
public static void onBlockBreak(BlockEvent.BreakEvent event) {
|
||||
if (!(event.getLevel() instanceof ServerLevel level)) return;
|
||||
|
||||
BlockPos pos = event.getPos();
|
||||
Player breaker = event.getPlayer();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(level);
|
||||
|
||||
// Check if broken block is a wall of a V2 cell
|
||||
UUID cellId = registry.getCellIdAtWall(pos);
|
||||
if (cellId == null) return;
|
||||
|
||||
CellDataV2 cell = registry.getCell(cellId);
|
||||
if (cell == null) return;
|
||||
|
||||
// Prisoners cannot break the Cell Core
|
||||
if (level.getBlockState(pos).getBlock() instanceof BlockCellCore) {
|
||||
if (cell.hasPrisoner(breaker.getUUID())) {
|
||||
event.setCanceled(true);
|
||||
breaker.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.cant_break_core"
|
||||
).withStyle(ChatFormatting.RED),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Record breach
|
||||
registry.addBreach(cellId, pos);
|
||||
|
||||
// State transitions
|
||||
float breachPct = cell.getBreachPercentage();
|
||||
if (breachPct > 0.30f && cell.getState() != CellState.COMPROMISED) {
|
||||
cell.setState(CellState.COMPROMISED);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[CellV2EventHandler] Cell {} COMPROMISED ({}% breached)",
|
||||
cellId.toString().substring(0, 8),
|
||||
(int) (breachPct * 100)
|
||||
);
|
||||
} else if (cell.getState() == CellState.INTACT) {
|
||||
cell.setState(CellState.BREACHED);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[CellV2EventHandler] Cell {} BREACHED at {}",
|
||||
cellId.toString().substring(0, 8),
|
||||
pos.toShortString()
|
||||
);
|
||||
}
|
||||
|
||||
// Notify owner (skip if breaker is the owner)
|
||||
if (cell.getOwnerId() != null && !cell.isOwnedBy(breaker.getUUID())) {
|
||||
ServerPlayer owner = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(cell.getOwnerId());
|
||||
if (owner != null) {
|
||||
String cellName =
|
||||
cell.getName() != null
|
||||
? cell.getName()
|
||||
: "Cell " + cellId.toString().substring(0, 8);
|
||||
SystemMessageManager.sendToPlayer(
|
||||
owner,
|
||||
SystemMessageManager.MessageCategory.CELL_BREACH
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== BLOCK PLACE ====================
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onBlockPlace(BlockEvent.EntityPlaceEvent event) {
|
||||
if (!(event.getLevel() instanceof ServerLevel level)) return;
|
||||
|
||||
BlockPos pos = event.getPos();
|
||||
CellRegistryV2 registry = CellRegistryV2.get(level);
|
||||
|
||||
// Check if placed position is a breached wall
|
||||
UUID cellId = registry.getCellIdAtBreach(pos);
|
||||
if (cellId == null) return;
|
||||
|
||||
CellDataV2 cell = registry.getCell(cellId);
|
||||
if (cell == null) return;
|
||||
|
||||
// Only repair if the placed block is solid
|
||||
BlockState placedState = event.getPlacedBlock();
|
||||
if (!placedState.isSolid()) return;
|
||||
|
||||
registry.repairBreach(cellId, pos);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[CellV2EventHandler] Breach repaired at {} in cell {}",
|
||||
pos.toShortString(),
|
||||
cellId.toString().substring(0, 8)
|
||||
);
|
||||
|
||||
// Check if all breaches are repaired
|
||||
if (
|
||||
cell.getBreachedPositions().isEmpty() &&
|
||||
cell.getState() == CellState.BREACHED
|
||||
) {
|
||||
cell.setState(CellState.INTACT);
|
||||
|
||||
// Notify owner
|
||||
if (cell.getOwnerId() != null) {
|
||||
ServerPlayer owner = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(cell.getOwnerId());
|
||||
if (owner != null) {
|
||||
owner.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.breach_repaired"
|
||||
).withStyle(ChatFormatting.GREEN),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[CellV2EventHandler] Cell {} fully repaired → INTACT",
|
||||
cellId.toString().substring(0, 8)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== PLAYER TICK ====================
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerTick(TickEvent.PlayerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
if (!(event.player instanceof ServerPlayer player)) return;
|
||||
|
||||
UUID playerId = player.getUUID();
|
||||
if (!CellSelectionManager.isInSelectionMode(playerId)) return;
|
||||
|
||||
// Cancel on sneak
|
||||
if (player.isShiftKeyDown()) {
|
||||
CellSelectionManager.clearSelection(playerId);
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.selection.cancelled"
|
||||
).withStyle(ChatFormatting.YELLOW),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel on timeout or distance
|
||||
if (
|
||||
CellSelectionManager.shouldCancel(playerId, player.blockPosition())
|
||||
) {
|
||||
CellSelectionManager.clearSelection(playerId);
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.selection.cancelled"
|
||||
).withStyle(ChatFormatting.YELLOW),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SELECTION HANDLING ====================
|
||||
|
||||
private static void handleSelectionClick(
|
||||
ServerPlayer player,
|
||||
ServerLevel level,
|
||||
BlockPos clickedPos,
|
||||
CellSelectionManager.SelectionContext selection
|
||||
) {
|
||||
CellRegistryV2 registry = CellRegistryV2.get(level);
|
||||
CellDataV2 cell = registry.getCell(selection.cellId);
|
||||
if (cell == null) {
|
||||
CellSelectionManager.clearSelection(player.getUUID());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (selection.mode) {
|
||||
case SET_SPAWN -> handleSetSpawn(
|
||||
player,
|
||||
level,
|
||||
clickedPos,
|
||||
cell,
|
||||
selection
|
||||
);
|
||||
case SET_DELIVERY -> handleSetDelivery(
|
||||
player,
|
||||
level,
|
||||
clickedPos,
|
||||
cell,
|
||||
selection
|
||||
);
|
||||
case SET_DISGUISE -> handleSetDisguise(
|
||||
player,
|
||||
level,
|
||||
clickedPos,
|
||||
cell,
|
||||
selection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleSetSpawn(
|
||||
ServerPlayer player,
|
||||
ServerLevel level,
|
||||
BlockPos clickedPos,
|
||||
CellDataV2 cell,
|
||||
CellSelectionManager.SelectionContext selection
|
||||
) {
|
||||
// Spawn must be inside the cell or on a wall block (e.g. floor)
|
||||
boolean isInterior = cell.isContainedInCell(clickedPos);
|
||||
boolean isWall = cell.isWallBlock(clickedPos);
|
||||
|
||||
if (!isInterior && !isWall) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.not_inside_cell"
|
||||
).withStyle(ChatFormatting.RED),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If clicked on a wall (floor/ceiling/wall), spawn above it
|
||||
BlockPos actualSpawn = isWall ? clickedPos.above() : clickedPos;
|
||||
|
||||
// Verify the actual spawn position is inside the cell
|
||||
if (isWall && !cell.isContainedInCell(actualSpawn)) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.not_inside_cell"
|
||||
).withStyle(ChatFormatting.RED),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update spawn in both Core BE and CellDataV2
|
||||
BlockEntity be = level.getBlockEntity(selection.corePos);
|
||||
if (be instanceof CellCoreBlockEntity core) {
|
||||
core.setSpawnPoint(actualSpawn);
|
||||
}
|
||||
cell.setSpawnPoint(actualSpawn);
|
||||
|
||||
CellSelectionManager.clearSelection(player.getUUID());
|
||||
player.displayClientMessage(
|
||||
Component.translatable("msg.tiedup.cell_core.spawn_set").withStyle(
|
||||
ChatFormatting.GREEN
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private static void handleSetDelivery(
|
||||
ServerPlayer player,
|
||||
ServerLevel level,
|
||||
BlockPos clickedPos,
|
||||
CellDataV2 cell,
|
||||
CellSelectionManager.SelectionContext selection
|
||||
) {
|
||||
// Delivery must be outside the cell
|
||||
if (cell.isContainedInCell(clickedPos)) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.must_be_outside"
|
||||
).withStyle(ChatFormatting.RED),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
BlockEntity be = level.getBlockEntity(selection.corePos);
|
||||
if (be instanceof CellCoreBlockEntity core) {
|
||||
core.setDeliveryPoint(clickedPos);
|
||||
}
|
||||
cell.setDeliveryPoint(clickedPos);
|
||||
|
||||
CellSelectionManager.clearSelection(player.getUUID());
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.delivery_set"
|
||||
).withStyle(ChatFormatting.GREEN),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private static void handleSetDisguise(
|
||||
ServerPlayer player,
|
||||
ServerLevel level,
|
||||
BlockPos clickedPos,
|
||||
CellDataV2 cell,
|
||||
CellSelectionManager.SelectionContext selection
|
||||
) {
|
||||
BlockState clickedState = level.getBlockState(clickedPos);
|
||||
|
||||
// Must be a solid block
|
||||
if (!clickedState.isSolid()) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.must_be_solid"
|
||||
).withStyle(ChatFormatting.RED),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
BlockEntity be = level.getBlockEntity(selection.corePos);
|
||||
if (be instanceof CellCoreBlockEntity core) {
|
||||
core.setDisguiseState(clickedState);
|
||||
}
|
||||
|
||||
CellSelectionManager.clearSelection(player.getUUID());
|
||||
String blockName = clickedState.getBlock().getName().getString();
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.disguise_set",
|
||||
blockName
|
||||
).withStyle(ChatFormatting.GREEN),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== DOOR CONTROL ====================
|
||||
|
||||
private static void handleDoorControl(
|
||||
PlayerInteractEvent.RightClickBlock event,
|
||||
ServerPlayer player,
|
||||
ServerLevel level,
|
||||
BlockPos clickedPos
|
||||
) {
|
||||
CellRegistryV2 registry = CellRegistryV2.get(level);
|
||||
|
||||
// Check if the clicked position is a cell door (doors are in wall set)
|
||||
// or an interior linked redstone (buttons/levers)
|
||||
CellDataV2 cellByWall = registry.getCellByWall(clickedPos);
|
||||
CellDataV2 cellByInterior = registry.getCellContaining(clickedPos);
|
||||
|
||||
CellDataV2 cell = null;
|
||||
boolean isDoor = false;
|
||||
boolean isRedstone = false;
|
||||
|
||||
if (cellByWall != null) {
|
||||
// Check if it's in the cell's doors list
|
||||
if (cellByWall.getDoors().contains(clickedPos)) {
|
||||
cell = cellByWall;
|
||||
isDoor = true;
|
||||
}
|
||||
// Check linked redstone in walls
|
||||
if (cellByWall.getLinkedRedstone().contains(clickedPos)) {
|
||||
cell = cellByWall;
|
||||
isRedstone = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cellByInterior != null && !isDoor && !isRedstone) {
|
||||
// Check linked redstone in interior
|
||||
if (cellByInterior.getLinkedRedstone().contains(clickedPos)) {
|
||||
cell = cellByInterior;
|
||||
isRedstone = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell == null || (!isDoor && !isRedstone)) return;
|
||||
|
||||
// Owner, camp cell, or OP can always interact
|
||||
if (cell.canPlayerManage(player.getUUID(), player.hasPermissions(2))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prisoner or non-owner → block interaction
|
||||
event.setCanceled(true);
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.cell_core.door_locked"
|
||||
).withStyle(ChatFormatting.RED),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.tiedup.remake.events.system;
|
||||
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.dialogue.GagTalkManager;
|
||||
import com.tiedup.remake.items.base.ItemGag;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.GagMaterial;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.util.TiedUpUtils;
|
||||
import java.util.List;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.event.CommandEvent;
|
||||
import net.minecraftforge.event.ServerChatEvent;
|
||||
import net.minecraftforge.eventbus.api.EventPriority;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* ChatEventHandler - Intercepts chat messages to apply gag effects.
|
||||
* Evolution: Implements proximity-based chat for gagged players.
|
||||
*
|
||||
* Phase 14.1.5: Refactored to use IBondageState interface
|
||||
*
|
||||
* Security fix: Now blocks communication commands (/msg, /tell, etc.) when gagged
|
||||
* to prevent gag bypass exploit
|
||||
*/
|
||||
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID)
|
||||
public class ChatEventHandler {
|
||||
|
||||
/** List of communication commands that should be blocked when gagged */
|
||||
private static final String[] BLOCKED_COMMANDS = {
|
||||
"msg",
|
||||
"tell",
|
||||
"w",
|
||||
"whisper",
|
||||
"r",
|
||||
"reply",
|
||||
"me",
|
||||
};
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onPlayerChat(ServerChatEvent event) {
|
||||
ServerPlayer player = event.getPlayer();
|
||||
IBondageState state = KidnappedHelper.getKidnappedState(player);
|
||||
|
||||
if (state != null && state.isGagged()) {
|
||||
ItemStack gagStack = V2EquipmentHelper.getInRegion(
|
||||
player,
|
||||
BodyRegionV2.MOUTH
|
||||
);
|
||||
|
||||
if (
|
||||
!gagStack.isEmpty() &&
|
||||
gagStack.getItem() instanceof ItemGag gagItem
|
||||
) {
|
||||
String originalMessage = event.getRawText();
|
||||
GagMaterial material = gagItem.getGagMaterial();
|
||||
|
||||
// 1. Process the message through our GagTalkManager V2
|
||||
Component muffledMessage = GagTalkManager.processGagMessage(
|
||||
state,
|
||||
gagStack,
|
||||
originalMessage
|
||||
);
|
||||
|
||||
// 2. Proximity Chat Logic
|
||||
boolean useProximity = SettingsAccessor.isGagTalkProximityEnabled(
|
||||
player.level().getGameRules());
|
||||
|
||||
if (useProximity) {
|
||||
// Cancel global and send to nearby
|
||||
event.setCanceled(true);
|
||||
|
||||
Component finalChat = Component.literal("<")
|
||||
.append(player.getDisplayName())
|
||||
.append("> ")
|
||||
.append(muffledMessage);
|
||||
|
||||
double range = material.getTalkRange();
|
||||
|
||||
// Phase 14.2: Use TiedUpUtils for proximity and earplugs filtering
|
||||
List<ServerPlayer> nearbyPlayers =
|
||||
TiedUpUtils.getPlayersAround(
|
||||
player.level(),
|
||||
player.blockPosition(),
|
||||
range
|
||||
);
|
||||
|
||||
int listeners = 0;
|
||||
for (ServerPlayer other : nearbyPlayers) {
|
||||
// Check if receiver has earplugs (they can't hear)
|
||||
IBondageState receiverState =
|
||||
KidnappedHelper.getKidnappedState(other);
|
||||
if (
|
||||
receiverState != null && receiverState.hasEarplugs()
|
||||
) {
|
||||
// Can't hear - skip this player
|
||||
continue;
|
||||
}
|
||||
|
||||
other.sendSystemMessage(finalChat);
|
||||
if (other != player) listeners++;
|
||||
}
|
||||
|
||||
if (listeners == 0) {
|
||||
player.displayClientMessage(
|
||||
Component.translatable(
|
||||
"chat.tiedup.gag.no_one_heard"
|
||||
).withStyle(
|
||||
net.minecraft.ChatFormatting.ITALIC,
|
||||
net.minecraft.ChatFormatting.GRAY
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Just replace message but keep it global
|
||||
event.setMessage(muffledMessage);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[Chat] {} muffled message processed (Proximity: {})",
|
||||
player.getName().getString(),
|
||||
useProximity
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept commands to prevent gagged players from using communication commands.
|
||||
* Blocks /msg, /tell, /w, /whisper, /r, /reply, /me when player is gagged.
|
||||
*
|
||||
* Security fix: Prevents gag bypass exploit via private messages
|
||||
*/
|
||||
@SubscribeEvent(priority = EventPriority.HIGHEST)
|
||||
public static void onCommand(CommandEvent event) {
|
||||
// Only check if sender is a ServerPlayer
|
||||
if (
|
||||
!(event
|
||||
.getParseResults()
|
||||
.getContext()
|
||||
.getSource()
|
||||
.getEntity() instanceof
|
||||
ServerPlayer player)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if player is gagged
|
||||
IBondageState state = KidnappedHelper.getKidnappedState(player);
|
||||
if (state == null || !state.isGagged()) {
|
||||
return; // Not gagged, allow all commands
|
||||
}
|
||||
|
||||
// Get the command name (first part of the command string)
|
||||
String commandInput = event.getParseResults().getReader().getString();
|
||||
if (commandInput.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove leading slash if present
|
||||
String commandName = commandInput.startsWith("/")
|
||||
? commandInput.substring(1)
|
||||
: commandInput;
|
||||
// Get only the first word (command name)
|
||||
commandName = commandName.split(" ")[0].toLowerCase();
|
||||
|
||||
// Check if this is a blocked communication command
|
||||
for (String blockedCmd : BLOCKED_COMMANDS) {
|
||||
if (commandName.equals(blockedCmd)) {
|
||||
// Block the command
|
||||
event.setCanceled(true);
|
||||
|
||||
// Send muffled message to player
|
||||
SystemMessageManager.sendToPlayer(
|
||||
player,
|
||||
SystemMessageManager.MessageCategory.ERROR,
|
||||
"Mmmph! You can't use that command while gagged!"
|
||||
);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[Chat] Blocked command '{}' from gagged player {}",
|
||||
commandName,
|
||||
player.getName().getString()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.tiedup.remake.events.system;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.minigame.LockpickSessionManager;
|
||||
import com.tiedup.remake.minigame.StruggleSessionManager;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Server tick handler for mini-game session management.
|
||||
*
|
||||
* Handles:
|
||||
* - Continuous struggle session ticking (direction changes, resistance updates, shock checks)
|
||||
* - Session cleanup for expired/disconnected sessions
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE
|
||||
)
|
||||
public class MiniGameTickHandler {
|
||||
|
||||
/**
|
||||
* Tick continuous struggle sessions every server tick.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
|
||||
long currentTick = event.getServer().getTickCount();
|
||||
|
||||
// Tick continuous struggle sessions every tick
|
||||
StruggleSessionManager.getInstance().tickContinuousSessions(event.getServer(), currentTick);
|
||||
|
||||
// Cleanup expired sessions periodically
|
||||
StruggleSessionManager.getInstance().tickCleanup(currentTick);
|
||||
LockpickSessionManager.getInstance().tickCleanup(currentTick);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user