Java server development on Paper · 3 min read

Minigame states: reject stale timers and duplicate finishes

The old timer should not end the new round. To do this, the event checks two things: the current state of the arena and the launch UUID. Below is a small executable model that can be tested without Minecraft.

LonelyCraft editors · Updated

Desktop with computer and world Minecraft on screen
Illustration: desktop with a computer and world Minecraft on the screen. This is an artistic scene, not a snapshot of a specific server.

Assign an owner to each run

Example states: WAITING, RUNNING, FINISHING. Start creates a new runId; finish is accepted only from RUNNING with the same ID. Until cleaning is confirmed, the arena will not return to WAITING.

Create a folder round-demo and a file RoundDemo.java with the code below. The JDK 25 used by Paper 26.3 is suitable for the experiment. There is no game API in the model: this is a transition test, not a ready-made arena.

WAITING RUNNING FINISHING loop with stale event failure conditions
The cleanup is confirmed by the same launch owner. The old event does not move the machine to the next state.Scroll the diagram horizontally.Open full-size diagram

Run the model and compare the four responses

Execute javac -encoding UTF-8 RoundDemo.java then java RoundDemo; The compiler syntax is in javac documentation 25. Expected responses: true, false, false, true. The code intentionally does not issue rewards.

import java.util.UUID;

public final class RoundDemo {
    enum State { WAITING, RUNNING, FINISHING }
    private State state = State.WAITING;
    private UUID runId;

    UUID start() {
        if (state != State.WAITING) throw new IllegalStateException("Раунд уже занят");
        runId = UUID.randomUUID();
        state = State.RUNNING;
        return runId;
    }
    boolean finish(UUID expected) {
        if (state != State.RUNNING || !expected.equals(runId)) return false;
        state = State.FINISHING;
        return true;
    }
    void cleanup(UUID expected) {
        if (state != State.FINISHING || !expected.equals(runId))
            throw new IllegalStateException("Не тот этап очистки");
        state = State.WAITING;
        runId = null;
    }
    public static void main(String[] args) {
        RoundDemo round = new RoundDemo();
        UUID first = round.start();
        System.out.println("Первый финиш: " + round.finish(first));
        System.out.println("Повторный финиш: " + round.finish(first));
        round.cleanup(first);
        UUID second = round.start();
        System.out.println("Старый таймер: " + round.finish(first));
        System.out.println("Новый финиш: " + round.finish(second));
        round.cleanup(second);
    }
}

Add scenarios that break the expected sequence

Temporarily call a second start() immediately after the first: IllegalStateException expected. After running the second round, call cleanup(first): the cleanup of someone else's run should be rejected. These experiments test the consequences, not just the presence of the enum.

CrashSaved rule
Old runId timerNo completion of a new round
Two finishes of one runIdThe result is accepted once
Cleanup did not completeNew start is prohibited
Return from background databaseCheck the status and the same runId

Link transitions to real confirmations

In Paper, process transitions in the game thread, and return the I/O result as a separate task. For a complex entrance, add PREPARING: inventory saved, arena ready, teleport confirmed. None of these stages should subtly mean RUNNING.

FINISHING closes receiving results; the outcome and intention of the reward are then stored. Cleaning only removes the resources of its runId. Upon restart, the model's memory will disappear, so unfinished operations require a persistent log. Clearing failure means the arena is occupied, not allowing another game to start on top of it.

Failure of the snapshot should terminate the login without clearing the inventory. Save regular stuff before logging in.

Repeated finishing after a restart is checked with a unique key in the database. Save outcome and reward intent.

Frequently asked questions

Is a separate UUID needed if there is an arena number?

Yes: the same arena is used sequentially. The slot number does not distinguish the old timer from the new start.

Does canceling the timer make the ID check redundant?

No. The task may have already entered the execution queue. Owner verification remains on the processor's side.

Can FINISHING be considered equal to a successful payment?

No. The transition closes the reception of results; Confirming storage and delivery of the reward are separate steps.

Views: … · One view per browser per day

Was this useful?Total score: …

One reaction per browser. Click again to remove it, or choose the other reaction.

Sources and documentation

Minecraftplugin developmentPaper

Read on

Minigame states: reject stale timers and duplicate finishes — Lonely Craft