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:
NotEvil
2026-04-12 00:51:22 +02:00
parent 2e7a1d403b
commit f6466360b6
1947 changed files with 238025 additions and 1 deletions

View File

@@ -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()
);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
);
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}