Skip to content

Commit

Permalink
Merge pull request #20 from piotrmaciejbednarski/feature/custom-tick-…
Browse files Browse the repository at this point in the history
…system

Improve documentation and add `oneTick` option
  • Loading branch information
piotrmaciejbednarski authored Feb 21, 2025
2 parents 01cc10b + f737c93 commit 8cdb7ed
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 63 deletions.
196 changes: 140 additions & 56 deletions src/main/java/pl/bednarskiwsieci/logicgatesplugin/LogicGatesPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public class LogicGatesPlugin extends JavaPlugin {
private final ConcurrentHashMap<Location, GateData> gates = new ConcurrentHashMap<>();
private final ConcurrentLinkedQueue<Location> gatesToUpdate = new ConcurrentLinkedQueue<>();
private final Set<UUID> debugPlayers = new HashSet<>();
private final Set<UUID> rotationModePlayers = new HashSet<>();
private final Set<UUID> inspectionModePlayers = new HashSet<>();
private final Set<UUID> inputToggleModePlayers = new HashSet<>();
private final Set<UUID> cooldownModePlayers = new HashSet<>();
Expand Down Expand Up @@ -75,6 +74,7 @@ public class LogicGatesPlugin extends JavaPlugin {
private String defaultLang = "en";
private boolean legacyMode = false;
private String notGateInputPosition = "default";
private boolean oneTick = false;
// endregion

// region Task Management
Expand Down Expand Up @@ -112,7 +112,7 @@ public void onEnable() {

// Initialize bStats
int BSTATS_ID = 24700;
Metrics metrics = new Metrics(this, BSTATS_ID);
new Metrics(this, BSTATS_ID);

// Automatic update check on startup
try {
Expand Down Expand Up @@ -165,6 +165,10 @@ private void initializeConfigFiles() {
// endregion

// region Component Registration

/// Checks if the WorldEdit plugin is present on the server.
///
/// @return true if WorldEdit is present, false otherwise
private boolean isWorldEditPresent() {
Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
for (Plugin plugin : plugins) {
Expand All @@ -175,6 +179,8 @@ private boolean isWorldEditPresent() {
return false;
}

/// Registers the commands for the plugin. The main command "logicgates" is set with its executor.
/// Also registers the WorldEditIntegration event listener.
private void registerCommands() {
Objects.requireNonNull(this.getCommand("logicgates"))
.setExecutor(new LogicGatesCommand(this, configManager, updateChecker));
Expand All @@ -183,6 +189,7 @@ private void registerCommands() {
this.getServer().getPluginManager().registerEvents(new WorldEditIntegration(this), this);
}

/// Registers the event listeners for the plugin. This includes the GateListener which handles gate-related events.
private void registerEventListeners() {
getServer().getPluginManager().registerEvents(new GateListener(this, configManager, updateChecker), this);
}
Expand Down Expand Up @@ -252,6 +259,8 @@ public String getMessageWithoutPrefix(String key, Object... args) {
// endregion

// region Gate management

/// Initializes the tick system to update the tick counter every server tick (50ms).
private void initializeTickSystem() {
// Update tick counter every server tick (50ms)
new BukkitRunnable() {
Expand All @@ -262,6 +271,10 @@ public void run() {
}.runTaskTimer(this, 0L, 1L);
}

/// Schedules updates for blocks that are dependent on the given output block and facing direction.
///
/// @param outputBlock the block for which dependent updates are to be scheduled.
/// @param facing the direction the block is facing.
private void scheduleDependentUpdates(Block outputBlock, BlockFace facing) {
// Check all potential connection directions
EnumSet<BlockFace> directions = EnumSet.of(
Expand Down Expand Up @@ -290,64 +303,106 @@ private void scheduleDependentUpdates(Block outputBlock, BlockFace facing) {
///
/// @param gateBlock the block representing the gate
public void updateGate(Block gateBlock) {
// Retrieve the location of the gate block.
Location loc = gateBlock.getLocation();
// Retrieve the stored data for the gate at this location.
GateData data = gates.get(loc);

// If no data is found or if the gate doesn't have the required activation
// carpet
if (data == null || !hasActivationCarpet(gateBlock))
// Validate gate data and carpet activation
if (!isValidGate(gateBlock, data)) {
return;
}

// Get the output block relative to the gate's facing direction.
// Determine primary states
final BlockFace facing = data.getFacing();
Block outputBlock = gateBlock.getRelative(facing);

Block outputBlock = findOutputBlock(gateBlock, facing);
boolean currentPhysicalState = getRedstoneState(gateBlock, facing);
boolean forceUpdate = checkForceUpdate(data, currentPhysicalState);
if (!canUpdate(loc, forceUpdate)) {
return;
}

// Bypass cooldown for initial state mismatch
boolean forceUpdate = (data.getState() != currentPhysicalState);
// Calculate input states
InputStates inputs = determineInputStates(gateBlock, data);

// Tick-based cooldown check (2 ticks minimum)
Long lastTick = lastUpdateTicks.get(loc);
if (lastTick != null && (serverTick.get() - lastTick) < 2)
return;
// Calculate final output
boolean output = GateUtils.calculateOutput(data.getType(),
inputs.leftState,
inputs.rightState,
inputs.backState,
data);

// Store current tick before processing
lastUpdateTicks.put(loc, serverTick.get());
// Apply update if needed
applyGateOutput(gateBlock, data, outputBlock, output, inputs, forceUpdate);
}

// Helper method: validate gate data and activation
private boolean isValidGate(Block gateBlock, GateData data) {
return (data != null && hasActivationCarpet(gateBlock));
}

// If a carpet is present at the standard output location, shift the output
// block one further block in the facing direction.
// Helper method: locate the output block, accounting for carpets
private Block findOutputBlock(Block gateBlock, BlockFace facing) {
Block outputBlock = gateBlock.getRelative(facing);
if (carpetTypes.containsKey(outputBlock.getType())) {
outputBlock = outputBlock.getRelative(facing, 1);
}
return outputBlock;
}

BlockFace leftFacing = GateUtils.rotateCounterClockwise(facing, ROTATION_ORDER);
BlockFace rightFacing = GateUtils.rotateClockwise(facing, ROTATION_ORDER);
BlockFace backFacing = facing.getOppositeFace();
// Helper method: decide if we need to force an update
private boolean checkForceUpdate(GateData data, boolean currentPhysicalState) {
return data.getState() != currentPhysicalState;
}

// Helper method: check the cooldown based on ticks
private boolean canUpdate(Location loc, boolean forceUpdate) {
int ticks = isOneTick() ? 1 : 2;
Long lastTick = lastUpdateTicks.get(loc);

// If not forcing an update and not enough ticks have passed, skip
if (!forceUpdate && lastTick != null && (serverTick.get() - lastTick) < ticks) {
return false;
}

// Record the current tick
lastUpdateTicks.put(loc, serverTick.get());
return true;
}

// Helper struct for holding input states
private static class InputStates {
boolean leftState;
boolean rightState;
boolean backState;

// Determine the redstone input states from the left and right sides
leftState = getRedstoneState(gateBlock, leftFacing);
rightState = getRedstoneState(gateBlock, rightFacing);
backState = false;
InputStates(boolean left, boolean right, boolean back) {
this.leftState = left;
this.rightState = right;
this.backState = back;
}
}

// Helper method: compute the three input states (left, right, back)
private InputStates determineInputStates(Block gateBlock, GateData data) {
BlockFace facing = data.getFacing();
BlockFace leftFacing = GateUtils.rotateCounterClockwise(facing, ROTATION_ORDER);
BlockFace rightFacing = GateUtils.rotateClockwise(facing, ROTATION_ORDER);
BlockFace backFacing = facing.getOppositeFace();

boolean leftState = getRedstoneState(gateBlock, leftFacing);
boolean rightState = getRedstoneState(gateBlock, rightFacing);
boolean backState = false;

// Special NOT gate handling
if (data.getType() == GateType.NOT) {
// Get the input position configuration for NOT gate
String notInputLocation = getNotGateInputPosition();
// Check if the input position is set to "opposite"
if (notInputLocation.equals("opposite")) {
// Set the left state to the opposite face of gate
BlockFace currentFacing = facing.getOppositeFace();
leftState = getRedstoneState(gateBlock, currentFacing);
leftFacing = currentFacing;
}
}

// Three-input gate handling (if needed)
if (data.isThreeInput()) {
backState = getRedstoneState(gateBlock, backFacing);
if (gateBlock.getRelative(backFacing).getType() == Material.AIR) {
Expand All @@ -356,25 +411,30 @@ public void updateGate(Block gateBlock) {
}

// Force false if input block is air
if (gateBlock.getRelative(leftFacing).getType() == Material.AIR)
if (gateBlock.getRelative(leftFacing).getType() == Material.AIR) {
leftState = false;
if (gateBlock.getRelative(rightFacing).getType() == Material.AIR)
}
if (gateBlock.getRelative(rightFacing).getType() == Material.AIR) {
rightState = false;
if (gateBlock.getRelative(backFacing).getType() == Material.AIR)
}
if (gateBlock.getRelative(backFacing).getType() == Material.AIR) {
backState = false;
}

// Calculate the output state based on the gate's type and the input states.
boolean output = GateUtils.calculateOutput(data.getType(), leftState, rightState, backState, data);

// Always update if:
// 1. Physical state differs from logical state (initialization)
// 2. Output changed
return new InputStates(leftState, rightState, backState);
}

// Helper method: apply gate output to the world
private void applyGateOutput(Block gateBlock,
GateData data,
Block outputBlock,
boolean output,
InputStates inputs,
boolean forceUpdate) {
boolean needsUpdate = true;

// Ignore checking when RS_LATCH
if (data.getType() != GateType.RS_LATCH)
if (data.getType() != GateType.RS_LATCH) {
needsUpdate = forceUpdate || (data.getState() != output);
}

if (needsUpdate) {
if (legacyMode) {
Expand All @@ -383,13 +443,11 @@ public void updateGate(Block gateBlock) {
GateUtils.setRedstonePower(outputBlock, output ? 15 : 0);
}
data.setState(output);
debugGateUpdate(gateBlock, data, leftState, rightState, backState, output);

// Play a quiet sound at the gate's location
gateBlock.getWorld().playSound(loc, Sound.BLOCK_NOTE_BLOCK_PLING, 0.1f, 1.0f);
debugGateUpdate(gateBlock, data, inputs.leftState, inputs.rightState, inputs.backState, output);
gateBlock.getWorld().playSound(gateBlock.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 0.1f, 1.0f);
}

scheduleDependentUpdates(outputBlock, facing);
scheduleDependentUpdates(outputBlock, data.getFacing());
}
// endregion

Expand Down Expand Up @@ -524,7 +582,6 @@ public void rotateGate(Block gateBlock) {
private void cleanupData() {
gates.clear();
debugPlayers.clear();
rotationModePlayers.clear();
inspectionModePlayers.clear();
}

Expand Down Expand Up @@ -761,40 +818,64 @@ public Set<UUID> getDebugPlayers() {
return debugPlayers;
}

/// Returns the set of player UUIDs who are in rotation mode.
///
/// @return the set of players in rotation mode
public Set<UUID> getRotationModePlayers() {
return rotationModePlayers;
}

/// Returns the set of player UUIDs who are in inspection mode.
///
/// @return the set of players in inspection mode
public Set<UUID> getInspectionModePlayers() {
return inspectionModePlayers;
}

/**
* Gets the players who are in input toggle mode.
*
* @return a set of UUIDs representing the players in input toggle mode.
*/
public Set<UUID> getInputToggleModePlayers() {
return inputToggleModePlayers;
}

/// Checks if the legacy mode is enabled.
///
/// @return true if legacy mode is enabled, false otherwise.
public boolean isLegacyMode() {
return legacyMode;
}

/// Sets the legacy mode.
///
/// @param legacyMode true to enable legacy mode, false to disable it.
public void setLegacyMode(boolean legacyMode) {
this.legacyMode = legacyMode;
}

/// Checks if the NotGateInputPosition mode is enabled.
///
/// @return true if mode is enabled, false otherwise.
public String getNotGateInputPosition() {
return notGateInputPosition;
}

/// Sets the NotGateInputPosition mode.
///
/// @param notGateInputPosition true to enable, false to disable it.
public void setNotGateInputPosition(String notGateInputPosition) {
this.notGateInputPosition = notGateInputPosition;
}

/// Checks if the one-tick mode is enabled.
///
/// @return true if one-tick mode is enabled, false otherwise.
public boolean isOneTick() {
return oneTick;
}

/// Sets the one-tick mode.
///
/// @param oneTick true to enable one-tick mode, false to disable it.
public void setOneTick(boolean oneTick) {
this.oneTick = oneTick;
}

/// Checks if particle effects are enabled.
///
/// @return `true` if particles are enabled, otherwise `false`
Expand Down Expand Up @@ -875,7 +956,10 @@ public Location convertStringToLocation(String str) {
return GateUtils.convertStringToLocation(str);
}

public void reloadConfiguration() {
/// Reloads the global configuration for the plugin.
/// This involves reloading the main configuration settings.
/// and reloading the gate configurations.
public void reloadGlobalConfiguration() {
configManager.reloadConfiguration();
gatesConfigManager.loadGates(gates);
}
Expand Down
Loading

0 comments on commit 8cdb7ed

Please sign in to comment.