From 867ca6f5dc8c666741256d1bf50252c4093f5158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Sp=C3=A4th?= Date: Fri, 24 Jul 2026 12:18:49 +0200 Subject: [PATCH] Migrate FightSystem modes and countdowns to GameCore - Move shared arena-mode abstractions into GameCore - Adapt fight countdowns and APIs to the shared model - Add SteamWar issue audit workflow and SQL language settings --- .claude/workflows/steamwar-issue-audit.js | 191 ++++++++++++++ .idea/IntelliLang.xml | 151 +++++++++++ FightSystem/FightSystem_Core/build.gradle.kts | 1 + .../de/steamwar/fightsystem/ArenaMode.java | 56 ---- .../src/de/steamwar/fightsystem/Config.java | 44 +--- .../de/steamwar/fightsystem/FightSystem.java | 1 + .../src/de/steamwar/fightsystem/ai/AI.java | 2 +- .../de/steamwar/fightsystem/ai/AIManager.java | 2 +- .../fightsystem/commands/AkCommand.java | 2 +- .../de/steamwar/fightsystem/commands/GUI.java | 2 +- .../fightsystem/commands/InfoCommand.java | 2 +- .../fightsystem/commands/KitCommand.java | 2 +- .../fightsystem/commands/LeaveCommand.java | 2 +- .../commands/LockschemCommand.java | 2 +- .../fightsystem/commands/ReadyCommand.java | 2 +- .../fightsystem/commands/RemoveCommand.java | 2 +- .../fightsystem/commands/RequestsCommand.java | 2 +- .../fightsystem/commands/SkipCommand.java | 2 +- .../fightsystem/commands/StateCommand.java | 2 +- .../fightsystem/commands/TBCommand.java | 2 +- .../fightsystem/commands/TPSWarpCommand.java | 2 +- .../commands/TechhiderbugCommand.java | 2 +- .../fightsystem/commands/WGCommand.java | 2 +- .../fightsystem/commands/WinCommand.java | 2 +- .../fightsystem/countdown/Countdown.java | 151 ----------- .../countdown/EnternCountdown.java | 19 +- .../countdown/EventSpectateCountdown.java | 13 +- .../countdown/FightCountdownAdapter.java | 91 +++++++ .../countdown/NoPlayersOnlineCountdown.java | 14 +- .../countdown/PostSchemCountdown.java | 14 +- .../countdown/PreRunningCountdown.java | 14 +- .../countdown/PreSchemCountdown.java | 14 +- .../countdown/SpectateOverCountdown.java | 14 +- .../countdown/TimeOverCountdown.java | 8 +- .../fightsystem/event/HellsBells.java | 11 +- .../de/steamwar/fightsystem/event/Meteor.java | 11 +- .../fightsystem/event/PersistentDamage.java | 2 +- .../de/steamwar/fightsystem/fight/Fight.java | 42 +-- .../fightsystem/fight/FightPlayer.java | 2 +- .../fightsystem/fight/FightSchematic.java | 2 +- .../steamwar/fightsystem/fight/FightTeam.java | 37 ++- .../fightsystem/fight/FightWorld.java | 2 +- .../fightsystem/fight/HotbarKitListener.java | 2 +- .../fightsystem/listener/ArenaBorder.java | 2 +- .../listener/BlockPlaceCollision.java | 2 +- .../steamwar/fightsystem/listener/Border.java | 2 +- .../steamwar/fightsystem/listener/Chat.java | 2 +- .../steamwar/fightsystem/listener/Check.java | 2 +- .../listener/DenyInventoryMovement.java | 2 +- .../listener/DenyWorldInteraction.java | 2 +- .../fightsystem/listener/EntityDamage.java | 2 +- .../fightsystem/listener/EventJoin.java | 2 +- .../fightsystem/listener/FightScoreboard.java | 2 +- .../listener/InFightInventory.java | 2 +- .../fightsystem/listener/IngameDeath.java | 2 +- .../listener/JoinRequestListener.java | 2 +- .../fightsystem/listener/NormalJoin.java | 2 +- .../fightsystem/listener/Permanent.java | 2 +- .../fightsystem/listener/PistonListener.java | 2 +- .../fightsystem/listener/PrepareSchem.java | 2 +- .../fightsystem/listener/Recording.java | 2 +- .../listener/RunningWorldInteraction.java | 2 +- .../fightsystem/listener/SetupQuit.java | 2 +- .../fightsystem/listener/Shutdown.java | 2 +- .../fightsystem/listener/Spectator.java | 2 +- .../fightsystem/listener/TeamArea.java | 2 +- .../fightsystem/listener/TestJoin.java | 2 +- .../fightsystem/listener/WaterRemover.java | 2 +- .../fightsystem/record/PacketProcessor.java | 6 +- .../steamwar/fightsystem/record/Recorder.java | 2 +- .../fightsystem/states/FightState.java | 106 ++++---- .../fightsystem/states/IStateDependent.java | 39 +-- .../states/OneShotStateDependent.java | 50 +--- .../fightsystem/states/StateDependent.java | 43 +--- .../states/StateDependentCommand.java | 50 +--- .../states/StateDependentCountdown.java | 70 ----- .../states/StateDependentListener.java | 54 +--- .../states/StateDependentTask.java | 59 +---- .../fightsystem/utils/BungeeFightInfo.java | 8 +- .../fightsystem/utils/FightStatistics.java | 4 +- .../steamwar/fightsystem/utils/FightUI.java | 8 +- .../EventTeamOffWincondition.java | 2 +- .../winconditions/Wincondition.java | 26 +- .../WinconditionComparisonTimeout.java | 9 +- .../winconditions/WinconditionPoints.java | 9 +- .../winconditions/WinconditionTimeTechKO.java | 11 +- .../WinconditionTimedDamageTechKO.java | 11 +- .../winconditions/WinconditionTimeout.java | 9 +- FightSystem/FightSystem_Core/src/plugin.yml | 3 +- FightSystem/build.gradle.kts | 5 + GameCore/README.md | 35 +++ GameCore/build.gradle.kts | 22 ++ .../src/de/steamwar/gamecore/ArenaMode.kt | 69 +++++ GameCore/src/de/steamwar/gamecore/GameCore.kt | 22 ++ .../gamecore/config/GameRuntimeProperties.kt | 63 +++++ .../steamwar/gamecore/countdown/Countdown.kt | 242 ++++++++++++++++++ .../gamecore/state/GameStateMachine.kt | 74 ++++++ .../steamwar/gamecore/state/StateDependent.kt | 194 ++++++++++++++ .../src/de/steamwar/gamecore/team/GameTeam.kt | 60 +++++ .../gamecore/wincondition/WinCondition.kt | 49 ++++ GameCore/src/plugin.yml | 7 + .../de/steamwar/gamecore/ArenaModeTest.kt | 38 +++ .../config/GameRuntimePropertiesTest.kt | 50 ++++ .../countdown/CountdownMilestonesTest.kt | 56 ++++ .../gamecore/state/GameStateMachineTest.kt | 53 ++++ .../gamecore/team/TeamRegistryTest.kt | 25 ++ .../wincondition/WinConditionsTest.kt | 28 ++ MissileWars/build.gradle.kts | 3 + .../src/de/steamwar/misslewars/Config.java | 19 +- .../de/steamwar/misslewars/FightState.java | 29 +-- .../src/de/steamwar/misslewars/MWTeam.java | 25 +- .../de/steamwar/misslewars/MissileWars.java | 34 +-- .../steamwar/misslewars/StateDependent.java | 27 +- .../misslewars/countdowns/EndCountdown.java | 24 +- .../countdowns/WaitingCountdown.java | 19 +- MissileWars/src/plugin.yml | 3 +- TNTLeague/build.gradle.kts | 3 +- .../src/de/steamwar/tntleague/TNTLeague.kt | 4 +- .../tntleague/config/TNTLeagueConfig.kt | 15 +- .../steamwar/tntleague/game/TNTLeagueGame.kt | 147 +++++++---- .../steamwar/tntleague/game/TNTLeagueTeam.kt | 26 +- TNTLeague/src/paper-plugin.yml | 5 +- TowerRun/build.gradle.kts | 1 + .../de/steamwar/towerrun/config/Config.java | 11 +- .../towerrun/countdowns/Countdown.java | 56 ++-- .../steamwar/towerrun/game/TowerRunGame.java | 9 + .../de/steamwar/towerrun/state/GameState.java | 33 +-- .../towerrun/state/GameStateListener.java | 29 --- .../state/GameStateToggleListener.java | 20 +- .../LastRemainingWincondition.java | 8 +- .../towerrun/winconditions/WinCondition.java | 13 +- TowerRun/src/plugin.yml | 2 +- settings.gradle.kts | 2 + 133 files changed, 2155 insertions(+), 1076 deletions(-) create mode 100644 .claude/workflows/steamwar-issue-audit.js create mode 100644 .idea/IntelliLang.xml delete mode 100644 FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ArenaMode.java delete mode 100644 FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/Countdown.java create mode 100644 FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/FightCountdownAdapter.java delete mode 100644 FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCountdown.java create mode 100644 GameCore/README.md create mode 100644 GameCore/build.gradle.kts create mode 100644 GameCore/src/de/steamwar/gamecore/ArenaMode.kt create mode 100644 GameCore/src/de/steamwar/gamecore/GameCore.kt create mode 100644 GameCore/src/de/steamwar/gamecore/config/GameRuntimeProperties.kt create mode 100644 GameCore/src/de/steamwar/gamecore/countdown/Countdown.kt create mode 100644 GameCore/src/de/steamwar/gamecore/state/GameStateMachine.kt create mode 100644 GameCore/src/de/steamwar/gamecore/state/StateDependent.kt create mode 100644 GameCore/src/de/steamwar/gamecore/team/GameTeam.kt create mode 100644 GameCore/src/de/steamwar/gamecore/wincondition/WinCondition.kt create mode 100644 GameCore/src/plugin.yml create mode 100644 GameCore/testsrc/de/steamwar/gamecore/ArenaModeTest.kt create mode 100644 GameCore/testsrc/de/steamwar/gamecore/config/GameRuntimePropertiesTest.kt create mode 100644 GameCore/testsrc/de/steamwar/gamecore/countdown/CountdownMilestonesTest.kt create mode 100644 GameCore/testsrc/de/steamwar/gamecore/state/GameStateMachineTest.kt create mode 100644 GameCore/testsrc/de/steamwar/gamecore/team/TeamRegistryTest.kt create mode 100644 GameCore/testsrc/de/steamwar/gamecore/wincondition/WinConditionsTest.kt delete mode 100644 TowerRun/src/de/steamwar/towerrun/state/GameStateListener.java diff --git a/.claude/workflows/steamwar-issue-audit.js b/.claude/workflows/steamwar-issue-audit.js new file mode 100644 index 00000000..912e3ebc --- /dev/null +++ b/.claude/workflows/steamwar-issue-audit.js @@ -0,0 +1,191 @@ +export const meta = { + name: 'steamwar-issue-audit', + description: 'Audit all 77 open SteamWar Gitea issues against current origin/main: already implemented?', + whenToUse: 'Re-run to re-audit open issues against the codebase', + phases: [ + { title: 'Investigate', detail: 'one agent per issue: classify + check implementation status' }, + { title: 'Verify', detail: 'adversarial refuters for implemented-claims, classification checks' }, + ], +} + +const SCRATCH = '/private/tmp/claude-501/-Users-chaos-Projects-IdeaProjects-SteamWar/1cb6f902-3cd9-4eaf-b207-85416dd40cd0/scratchpad' +const WT = SCRATCH + '/sw-main' + +const NUMBERS = [450, 449, 448, 447, 443, 442, 441, 440, 439, 438, 428, 419, 409, 407, 388, 380, 336, 334, 328, 323, 317, 313, 310, 307, 304, 302, 301, 299, 298, 294, 291, 290, 289, 287, 285, 284, 279, 276, 261, 253, 250, 247, 246, 240, 227, 223, 213, 208, 202, 200, 195, 192, 168, 161, 158, 150, 143, 141, 140, 138, 137, 132, 131, 129, 125, 123, 122, 110, 109, 107, 83, 77, 70, 60, 48, 32, 29] + +const STATUS = ['implemented', 'partially_implemented', 'not_implemented', 'not_in_this_repo', 'cannot_determine'] +const CATEGORY = ['code', 'config', 'website', 'infrastructure', 'unclear'] + +const RESULT_SCHEMA = { + type: 'object', + additionalProperties: false, + properties: { + title_en: { type: 'string', description: 'concise English translation of the issue title' }, + summary: { type: 'string', description: '1-2 English sentences: what is reported/requested' }, + category: { enum: CATEGORY }, + module: { type: 'string', description: 'main affected module, or external repo/system name' }, + status: { enum: STATUS }, + confidence: { enum: ['high', 'medium', 'low'] }, + evidence: { type: 'string', description: 'commits as `hash date subject`, code as path:line, brief explanations' }, + }, + required: ['title_en', 'summary', 'category', 'module', 'status', 'confidence', 'evidence'], +} + +const VERDICT_SCHEMA = { + type: 'object', + additionalProperties: false, + properties: { + verdict: { enum: ['confirmed', 'refuted', 'uncertain'] }, + corrected_status: { enum: STATUS }, + corrected_category: { enum: CATEGORY }, + reasoning: { type: 'string', description: '<= 80 words' }, + }, + required: ['verdict', 'reasoning'], +} + +const MODULE_MAP = `Modules in this mono-repo: BauSystem (build server: /trace, /simulator, NoClip, /laufbau, Laufsklave, SmartPlace, Pistorder, Panzern, Printer, /tick, Detonator, Rayvisualizer, hotkeys, Kanonentester, testblock, scripts), LegacyBauSystem, FightSystem (arena fights: WarShip/WarGear/MiniWarGear, AutoChecker ("Autoprüfer"/check), replay RECORDING, Techhider, Hullhider, test arena), MissileWars, TNTLeague, TowerRun, LobbySystem, SchematicSystem (schematic checks + SchematicType handling), SpigotCore (core plugin: replay PLAYBACK (RPlayer/REntity), skins, SW-WorldEdit integration), CommonCore (shared: database/SQL, GameMode + SchematicType config parsing), KotlinCore, VelocityCore (proxy: chat, /replay, /check, /dev, server orchestration incl. Bau-/arena-/"Ausfahr"-server starting, whois), Teamserver, Realtime, WebsiteBackend, CLI, CommandFramework, AccessWidener. +NOT in this repo (separate repos/systems on git.steamwar.de): the Website FRONTEND (repo "Website"), the "Paper"/"FastAsyncWorldEdit"/"Velocity" forks, deployed gamemode/arena/check-question configuration YAMLs, TexturePack, server hosting infrastructure ("Buildercloud").` + +const GROUND_RULES = `The worktree is READ-ONLY for you: NEVER modify, create, or delete any file there or anywhere else; use Read/Grep/Glob and git commands like log/show/diff/grep only. Today is 2026-07-14; worktree HEAD is 62e855ec (2026-07-05, current origin/main); full git history is available via git -C "${WT}" ...` + +function investigatePrompt(n) { + return `You are one auditor in a fleet checking open issues of the SteamWar Minecraft server project (git.steamwar.de/SteamWar/SteamWar) against the current codebase, to determine whether each issue is already implemented/fixed. + +YOUR ISSUE: #${n}. First read its JSON: ${SCRATCH}/issues/issue_${n}.json (fields: number, title, body, labels, created_at, user, comment_bodies). Text is mostly German. + +CODEBASE: git worktree of current origin/main at: ${WT} +${GROUND_RULES} +${MODULE_MAP} + +TASK: +1. Understand the issue (labels: System/* hints the module, Typ/Bug = bug, Typ/Idee = feature request, Version/* = MC version). Read comment_bodies — they often contain triage info (root cause in config, website problem, duplicate, or already-fixed notes). +2. Classify category: + - "code": concerns code living in this mono-repo. + - "config": root cause is (likely) configuration data (arena/gamemode YAMLs, check questions "Prüffragen", kits, server configs) rather than repo code. Grep the repo first to confirm the relevant config is NOT versioned here. + - "website": concerns the SteamWar website / web dashboard frontend. (WebsiteBackend IS in this repo — if backend code changes would fix it, prefer "code" with module WebsiteBackend.) + - "infrastructure": hosting/orchestration/build-tooling problems (e.g. Buildercloud versions, host resources) not fixable by code here. + - "unclear". +3. Determine implementation status in the CURRENT code (worktree HEAD): + - Grep the relevant module(s) for the feature/behavior described. + - Check git history since the issue: git -C "${WT}" log --oneline --since="" -- , plus keyword searches: git -C "${WT}" log --all --oneline -i --grep="" (try German AND English keywords, and "#${n}"). + - READ the relevant code and judge: is the described bug fixed / does the requested feature exist now? + - status: "implemented" (fixed / feature exists), "partially_implemented", "not_implemented" (bug plausibly still present / feature absent), "not_in_this_repo" (pure config/website-frontend/infrastructure issue not judgeable from this repo), "cannot_determine" (in-repo but evidence inconclusive, e.g. runtime/timing bug not decidable from source). +Be strict and evidence-based: report "implemented" ONLY with concrete evidence — a commit after the issue creation that addresses exactly the described behavior, or code clearly implementing the request. "The code looks correct" is NOT evidence that a reported bug is fixed; without a fixing change, lean to "not_implemented" or "cannot_determine". +4. evidence: cite commits as \`hash date subject\` and code as path:line (repo-relative), each with a very short explanation; <= 120 words total. + +Return via StructuredOutput. title_en = concise English title translation; summary = 1-2 English sentences; module = main affected module or external repo/system.` +} + +function evidenceVerifyPrompt(n, res) { + return `ADVERSARIAL VERIFICATION (evidence-audit lens). An auditor claims open issue #${n} of SteamWar ("${res.title_en}") has status "${res.status}" — i.e. already ${res.status === 'implemented' ? 'fully' : 'partially'} implemented/fixed in current code. + +Read the issue JSON first: ${SCRATCH}/issues/issue_${n}.json +Codebase worktree: ${WT} +${GROUND_RULES} + +Auditor's claim — category: ${res.category}, module: ${res.module}, confidence: ${res.confidence}. Evidence: +${res.evidence} + +Try to REFUTE the claim. Check every cited commit/file: does it exist, and does it genuinely address the SPECIFIC behavior in the issue (not merely the same code area)? Compare commit dates to the issue's created_at — a change that predates the issue is normally not the fix, since the issue was filed against code already containing it (unless the auditor shows the described behavior cannot occur in current code at all). Check whether the change covers the described case fully or only partially. If the evidence is about a related-but-different feature, refute. +Default to "refuted" or "uncertain" when the evidence does not clearly hold up. If refuting, set corrected_status to what it should be. reasoning <= 80 words.` +} + +function behaviorVerifyPrompt(n, res) { + return `ADVERSARIAL VERIFICATION (behavior re-check lens). An auditor claims open issue #${n} of SteamWar ("${res.title_en}") has status "${res.status}" — i.e. already ${res.status === 'implemented' ? 'fully' : 'partially'} implemented/fixed in current code. + +Read the issue JSON first: ${SCRATCH}/issues/issue_${n}.json +Codebase worktree: ${WT} +${GROUND_RULES} +${MODULE_MAP} + +Do NOT trust the auditor's citations — independently re-derive from the CURRENT code whether the described behavior is fixed / the requested feature exists. Trace the actual code path the issue describes; hunt for counter-evidence: the buggy path still present, TODO/FIXME, partial coverage, feature behind a flag, fix only in a different module than the one the issue is about. (Auditor said module: ${res.module}; their evidence, ONLY for reference at the end: ${res.evidence}) +Default to "refuted" or "uncertain" if you cannot positively confirm from code. If refuting, set corrected_status. reasoning <= 80 words.` +} + +function categoryVerifyPrompt(n, res) { + return `CLASSIFICATION CHECK. An auditor classified open issue #${n} of SteamWar ("${res.title_en}") as category "${res.category}" (root cause outside this mono-repo's plugin code), status "${res.status}", module "${res.module}". Their evidence: ${res.evidence} + +Read the issue JSON first: ${SCRATCH}/issues/issue_${n}.json +Codebase worktree: ${WT} +${GROUND_RULES} +${MODULE_MAP} + +Question: could this issue actually be addressed by code that IS in this mono-repo (making the right category "code")? Grep for the relevant feature/config keys. Also sanity-check the opposite: if it truly is a config/website-frontend/infrastructure matter, confirm the category. verdict "confirmed" = category is right; "refuted" = wrong (set corrected_category, and corrected_status if the status should change too, e.g. an issue marked not_in_this_repo that is actually in-repo code). reasoning <= 80 words.` +} + +async function processIssue(it, attempt) { + const n = it.number + const res = await agent(investigatePrompt(n), { + label: `#${n}${attempt > 1 ? ' (retry)' : ''}`, phase: 'Investigate', schema: RESULT_SCHEMA, effort: 'high', + }) + if (!res) return null + const out = { number: n, ...res, verification: 'n/a', verification_notes: [] } + + const claimsDone = res.status === 'implemented' || res.status === 'partially_implemented' + const nonCode = res.category === 'config' || res.category === 'website' || res.category === 'infrastructure' + + const checks = [] + if (claimsDone) { + checks.push(() => agent(evidenceVerifyPrompt(n, res), { label: `verify #${n} evidence`, phase: 'Verify', schema: VERDICT_SCHEMA, effort: 'high' })) + checks.push(() => agent(behaviorVerifyPrompt(n, res), { label: `verify #${n} behavior`, phase: 'Verify', schema: VERDICT_SCHEMA, effort: 'high' })) + } + if (nonCode) { + checks.push(() => agent(categoryVerifyPrompt(n, res), { label: `verify #${n} category`, phase: 'Verify', schema: VERDICT_SCHEMA, effort: 'high' })) + } + if (!checks.length) return out + + const votes = (await parallel(checks)).filter(Boolean) + const statusVotes = claimsDone ? votes.slice(0, Math.min(2, votes.length)) : [] + const catVote = nonCode ? votes[votes.length - 1] : null + // parallel() preserves order, so with both kinds present: [evidence, behavior, category] + + if (claimsDone && statusVotes.length) { + const refuters = statusVotes.filter(v => v.verdict === 'refuted') + const confirms = statusVotes.filter(v => v.verdict === 'confirmed') + if (refuters.length === statusVotes.length) { + out.status = refuters.find(v => v.corrected_status)?.corrected_status || 'not_implemented' + out.verification = 'refuted' + out.confidence = 'low' + } else if (refuters.length > 0) { + out.verification = 'disputed' + out.confidence = 'low' + } else if (confirms.length === statusVotes.length) { + out.verification = 'confirmed' + } else { + out.verification = 'unverified' + if (out.confidence === 'high') out.confidence = 'medium' + } + out.verification_notes.push(...statusVotes.map(v => `${v.verdict}: ${v.reasoning}`)) + } + + if (catVote) { + if (catVote.verdict === 'refuted') { + if (catVote.corrected_category) out.category = catVote.corrected_category + if (catVote.corrected_status && !claimsDone) out.status = catVote.corrected_status + out.verification_notes.push(`category corrected: ${catVote.reasoning}`) + } else { + out.verification_notes.push(`category ${catVote.verdict}: ${catVote.reasoning}`) + } + } + return out +} + +phase('Investigate') +log(`Auditing ${NUMBERS.length} open issues against origin/main (62e855ec)`) + +let results = await pipeline(NUMBERS.map(n => ({ number: n })), it => processIssue(it, 1)) +results = results.filter(Boolean) + +const have = new Set(results.map(r => r.number)) +const missing = NUMBERS.filter(n => !have.has(n)) +if (missing.length) { + log(`Retrying ${missing.length} failed issue(s): ${missing.join(', ')}`) + const retried = (await parallel(missing.map(n => () => processIssue({ number: n }, 2)))).filter(Boolean) + results.push(...retried) +} + +const stillMissing = NUMBERS.filter(n => !results.some(r => r.number === n)) +if (stillMissing.length) log(`Unresolved after retry: ${stillMissing.join(', ')}`) + +results.sort((a, b) => b.number - a.number) +return { audited: results.length, failed: stillMissing, results } \ No newline at end of file diff --git a/.idea/IntelliLang.xml b/.idea/IntelliLang.xml new file mode 100644 index 00000000..b223a1b2 --- /dev/null +++ b/.idea/IntelliLang.xml @@ -0,0 +1,151 @@ + + + + + AsyncQueryRunner (org.apache.commons.dbutils) + + + + + + + + + + + + + + + + + + Jodd (jodd.db) + + + + + + + + MyBatis @Select/@Delete/@Insert/@Update + + + + + + + + QueryRunner (org.apache.commons.dbutils) + + + + + + + + + + + + + + + + + + R2DBC (io.r2dbc) + + + + + + Reactiverse Postgres Client (io.reactiverse) + + + + + + + + + + + + + SmallRye Axle SqlClient (io.vertx.axle.sqlclient) + + + + + + SmallRye Mutiny SqlClient (io.vertx.mutiny.sqlclient) + + + + + + SmallRye Mutiny SqlConnection (io.vertx.mutiny.sqlclient) + + + + + + + + Vert.x SQL Extensions (io.vertx.ext.sql) + + + + + + + Vert.x SQL Reactive Extensions (io.vertx.reactivex.ext.sql) + + + + + + + + + + Vert.x SqlClient (io.vertx.sqlclient) + + + + + + + + + + + Vert.x SqlClient RxJava2 (io.vertx.reactivex.sqlclient) + + + + + + + + + + + + jOOQ (org.jooq.DSLContext) + + + + + + + + rxjava2-jdbc (org.davidmoten.rx.jdbc) + + + + + + + \ No newline at end of file diff --git a/FightSystem/FightSystem_Core/build.gradle.kts b/FightSystem/FightSystem_Core/build.gradle.kts index 9b2f746b..fad19513 100644 --- a/FightSystem/FightSystem_Core/build.gradle.kts +++ b/FightSystem/FightSystem_Core/build.gradle.kts @@ -29,6 +29,7 @@ java { dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) + compileOnly(project(":GameCore", "default")) compileOnly(project(":SpigotCore", "default")) compileOnly(libs.netty) diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ArenaMode.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ArenaMode.java deleted file mode 100644 index e65ce94c..00000000 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ArenaMode.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.fightsystem; - -import java.util.Collections; -import java.util.EnumSet; -import java.util.Set; - -public enum ArenaMode { - NORMAL, - EVENT, - TEST, - CHECK, - PREPARE, - REPLAY; - - public static final Set All = Collections.unmodifiableSet(EnumSet.allOf(ArenaMode.class)); - - public static final Set Normal = Collections.unmodifiableSet(EnumSet.of(NORMAL)); - public static final Set Check = Collections.unmodifiableSet(EnumSet.of(CHECK)); - public static final Set Event = Collections.unmodifiableSet(EnumSet.of(EVENT)); - public static final Set Test = Collections.unmodifiableSet(EnumSet.of(TEST, CHECK)); - public static final Set Prepare = Collections.unmodifiableSet(EnumSet.of(PREPARE)); - public static final Set Replay = Collections.unmodifiableSet(EnumSet.of(REPLAY, TEST)); - - public static final Set AntiReplay = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(REPLAY))); - public static final Set AntiTest = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK))); - public static final Set AntiEvent = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(EVENT))); - public static final Set AntiTestCheckPrepare = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK, PREPARE))); - public static final Set AntiPrepare = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PREPARE))); - public static final Set VariableTeams = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(EVENT, REPLAY, CHECK))); - public static final Set ManualTeams = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(EVENT, REPLAY))); - public static final Set RankedEvent = Collections.unmodifiableSet(EnumSet.of(EVENT, REPLAY)); - public static final Set Restartable = Collections.unmodifiableSet(EnumSet.of(NORMAL, TEST)); - public static final Set NotRestartable = Collections.unmodifiableSet(EnumSet.of(EVENT, REPLAY)); - public static final Set SoloLeader = Collections.unmodifiableSet(EnumSet.of(TEST, CHECK, PREPARE)); - public static final Set NotOnBau = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK, PREPARE, REPLAY))); - public static final Set SeriousFight = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK, REPLAY))); -} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java index c8a5883c..144e5260 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/Config.java @@ -22,6 +22,8 @@ package de.steamwar.fightsystem; import de.steamwar.data.GameModeConfigUtils; import de.steamwar.fightsystem.utils.Region; import de.steamwar.fightsystem.winconditions.Winconditions; +import de.steamwar.gamecore.ArenaMode; +import de.steamwar.gamecore.config.GameRuntimeProperties; import de.steamwar.sql.*; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -44,6 +46,7 @@ public class Config { } public static final World world = Bukkit.getWorlds().get(0); + public static final GameRuntimeProperties RuntimeProperties = GameRuntimeProperties.load(); public static final GameModeConfig GameModeConfig; @@ -99,11 +102,11 @@ public class Config { public static final int ReplayID; static { - CheckSchemID = Integer.parseInt(System.getProperty("checkSchemID", "0")); - PrepareSchemID = Integer.parseInt(System.getProperty("prepareSchemID", "0")); - ReplayID = Integer.parseInt(System.getProperty("replay", "0")); + CheckSchemID = RuntimeProperties.getCheckSchematicId(); + PrepareSchemID = RuntimeProperties.getPrepareSchematicId(); + ReplayID = RuntimeProperties.getReplayId(); - String configFile = System.getProperty("config", "config.yml"); + String configFile = RuntimeProperties.getConfigFile(); if (!new File(FightSystem.getPlugin().getDataFolder(), configFile).exists()) { FightSystem.getPlugin().saveDefaultConfig(); Bukkit.getLogger().log(Level.SEVERE, "Arenaconfig fehlt!"); @@ -209,7 +212,7 @@ public class Config { BlueInsetRegion = new Region(BluePasteRegion.getMinX() + GameModeConfig.Schematic.Inset.x, BluePasteRegion.getMinY() + GameModeConfig.Schematic.Inset.bottom, BluePasteRegion.getMinZ() + GameModeConfig.Schematic.Inset.z, BluePasteRegion.getMaxX() - GameModeConfig.Schematic.Inset.x, BluePasteRegion.getMaxY() - GameModeConfig.Schematic.Inset.top, BluePasteRegion.getMaxZ() - GameModeConfig.Schematic.Inset.z); - int eventKampfID = Integer.parseInt(System.getProperty("fightID", "0")); + int eventKampfID = RuntimeProperties.getFightId(); if (eventKampfID >= 1) { EventKampf = EventFight.byId(eventKampfID); if (EventKampf == null) { @@ -264,32 +267,9 @@ public class Config { SpectatePort = -ReplayID; } - String blueLeader = System.getProperty("blueLeader", null); - String redLeader = System.getProperty("redLeader", null); - if (blueLeader != null) { - BlueLeader = UUID.fromString(blueLeader); - } else { - BlueLeader = null; - } - if (redLeader != null) { - RedLeader = UUID.fromString(redLeader); - } else { - RedLeader = null; - } - - if (CheckSchemID != 0) { - mode = ArenaMode.CHECK; - } else if (PrepareSchemID != 0) { - mode = ArenaMode.PREPARE; - } else if (eventKampfID >= 1) { - mode = ArenaMode.EVENT; - } else if (eventKampfID == -1) { - mode = ArenaMode.TEST; - } else if (ReplayID != 0) { - mode = ArenaMode.REPLAY; - } else { - mode = ArenaMode.NORMAL; - } + BlueLeader = RuntimeProperties.getBlueLeader(); + RedLeader = RuntimeProperties.getRedLeader(); + mode = ArenaMode.from(RuntimeProperties); } public static boolean test() { @@ -297,7 +277,7 @@ public class Config { } public static boolean replayserver() { - return ReplayID < 0; + return RuntimeProperties.isReplayServer(); } public static boolean blueNegZ() { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java index 06ceac41..a512593e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/FightSystem.java @@ -36,6 +36,7 @@ import de.steamwar.fightsystem.utils.FightUI; import de.steamwar.fightsystem.utils.HullHider; import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.fightsystem.utils.TechHiderWrapper; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.SpigotLinker; import de.steamwar.message.Message; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java index 5faba1ef..35bc0ce3 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AI.java @@ -20,7 +20,7 @@ package de.steamwar.fightsystem.ai; import com.sk89q.worldedit.extent.clipboard.Clipboard; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AIManager.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AIManager.java index 9f8383cf..67fb8e12 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AIManager.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/ai/AIManager.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.ai; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.FightTeam; import lombok.AllArgsConstructor; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/AkCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/AkCommand.java index b1ffede2..37718a9f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/AkCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/AkCommand.java @@ -20,7 +20,7 @@ package de.steamwar.fightsystem.commands; import de.steamwar.core.Core; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Kit; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java index a65886b0..7ff85cd0 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/GUI.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.ai.AIManager; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/InfoCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/InfoCommand.java index 4fc8e96c..46cc3796 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/InfoCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/InfoCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/KitCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/KitCommand.java index bbbe07cb..d0bf3e64 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/KitCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/KitCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentCommand; import de.steamwar.linkage.Linked; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LeaveCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LeaveCommand.java index 8d63e060..c998ff91 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LeaveCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LeaveCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentCommand; import de.steamwar.linkage.Linked; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LockschemCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LockschemCommand.java index 4487b30f..08d1b336 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LockschemCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/LockschemCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/ReadyCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/ReadyCommand.java index 32386639..50ee4455 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/ReadyCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/ReadyCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentCommand; import de.steamwar.linkage.Linked; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RemoveCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RemoveCommand.java index 60c08339..03488e88 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RemoveCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RemoveCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentCommand; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RequestsCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RequestsCommand.java index 36cb8fba..a58a5b98 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RequestsCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/RequestsCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightPlayer; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/SkipCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/SkipCommand.java index 3fef2cb5..ab7d6fc7 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/SkipCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/SkipCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.record.PacketProcessor; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/StateCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/StateCommand.java index 338dcd5d..6c51bd3f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/StateCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/StateCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentCommand; import de.steamwar.linkage.Linked; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TBCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TBCommand.java index 2e627e4a..926a9e8c 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TBCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TBCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentCommand; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TPSWarpCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TPSWarpCommand.java index 6ec55acd..b16b903c 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TPSWarpCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TPSWarpCommand.java @@ -17,7 +17,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentCommand; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java index 6e89d727..58015a9e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/TechhiderbugCommand.java @@ -20,7 +20,7 @@ package de.steamwar.fightsystem.commands; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WGCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WGCommand.java index c88ae122..7512eff3 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WGCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WGCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightWorld; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WinCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WinCommand.java index e928faf4..1606d04f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WinCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/commands/WinCommand.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.commands; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/Countdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/Countdown.java deleted file mode 100644 index 7f7d5bd5..00000000 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/Countdown.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.fightsystem.countdown; - -import de.steamwar.fightsystem.FightSystem; -import de.steamwar.fightsystem.fight.Fight; -import de.steamwar.fightsystem.record.GlobalRecorder; -import de.steamwar.fightsystem.utils.FightUI; -import de.steamwar.fightsystem.utils.Message; -import de.steamwar.fightsystem.utils.SWSound; -import net.md_5.bungee.api.ChatMessageType; -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; - -import java.util.ArrayList; -import java.util.List; - -public abstract class Countdown { - - private static final List currentCountdowns = new ArrayList<>(); - - protected final Message appendix; - - private final int totalTime; - protected final Sound sound; - private final boolean level; - - protected int time; - private BukkitTask task = null; - - public abstract void countdownFinished(); - - protected Countdown(int time, Message appendix, SWSound sound, boolean level) { - this.totalTime = time; - this.time = time; - this.appendix = appendix; - this.sound = sound != null ? sound.getSound() : null; - this.level = level; - } - - public void enable() { - time = totalTime; - task = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), this::count, 20, 20); - currentCountdowns.add(this); - show(); - } - - public void disable() { - if (task != null) { - task.cancel(); - currentCountdowns.remove(this); - task = null; - } - } - - public static void skip() { - if (currentCountdowns.isEmpty()) return; - - int smallestTime = currentCountdowns.get(0).time; - for (Countdown countdown : currentCountdowns) { - if (countdown.time < smallestTime) smallestTime = countdown.time; - } - - for (Countdown countdown : new ArrayList<>(currentCountdowns)) { - countdown.time -= smallestTime; - countdown.show(); - } - - FightUI.addSubtitle("UI_SKIP"); - } - - public static void sendCountdownMessage(Player p, String message, int displaytime, Message appendix) { - FightSystem.getMessage().sendPrefixless(message, p, ChatMessageType.ACTION_BAR, displaytime, FightSystem.getMessage().parse(appendix.getMsg(), p, appendix.getParams())); - } - - protected void broadcast(String message, int divisor) { - if (this.sound != null && divisor == 1) { - Fight.playSound(this.sound, 100.0F, 1.0F); - } - - GlobalRecorder.getInstance().countdown(message, time / divisor, appendix); - Bukkit.getOnlinePlayers().forEach(p -> sendCountdownMessage(p, message, time / divisor, appendix)); - } - - public int getTimeLeft() { - return time; - } - - private void count() { - time--; - show(); - } - - private void show() { - switch (time) { - case 900: - case 600: - case 300: - case 180: - case 120: - broadcast("COUNTDOWN_MINUTES", 60); - break; - case 60: - case 30: - case 20: - case 15: - case 10: - case 5: - case 4: - case 3: - case 2: - broadcast("COUNTDOWN_SECONDS", 1); - break; - case 1: - broadcast("COUNTDOWN_SECOND", 1); - break; - case 0: - if (this.sound != null) { - Fight.playSound(this.sound, 100.0F, 2.0F); - } - - disable(); - countdownFinished(); - break; - default: - } - - if (this.level) { - Bukkit.getServer().getOnlinePlayers().forEach(player -> player.setLevel(time)); - } - } -} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java index e5a62ac6..c6b7c834 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EnternCountdown.java @@ -27,10 +27,12 @@ import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.Region; import de.steamwar.fightsystem.utils.SWSound; +import de.steamwar.gamecore.countdown.Countdown; +import de.steamwar.gamecore.countdown.StandardCountdown; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.Bukkit; -public class EnternCountdown extends Countdown { +public class EnternCountdown extends StandardCountdown { private static int calcTime(FightPlayer fp, Countdown countdown) { int time = Config.GameModeConfig.EnterStages.get(fp.getKit().getEnterStage()); @@ -46,13 +48,17 @@ public class EnternCountdown extends Countdown { private final FightPlayer fightPlayer; public EnternCountdown(FightPlayer fp, Countdown countdown) { - super(calcTime(fp, countdown), new Message("ENTERN_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false); + super( + FightSystem.getPlugin(), + calcTime(fp, countdown), + FightCountdownAdapter.playerPresentation(fp, new Message("ENTERN_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING) + ); fightPlayer = fp; enable(); } @Override - public void countdownFinished() { + protected void countdownFinished() { Bukkit.getPluginManager().callEvent(new BoardingEvent(fightPlayer)); FightSystem.getMessage().sendPrefixless("ENTERN_ALLOWED", fightPlayer.getEntity(), ChatMessageType.ACTION_BAR); fightPlayer.ifPlayer(player -> { @@ -61,11 +67,4 @@ public class EnternCountdown extends Countdown { }); } - @Override - protected void broadcast(String message, int divisor) { - fightPlayer.ifPlayer(player -> { - player.playSound(player.getLocation(), sound, 100.0f, 1.0f); - sendCountdownMessage(player, message, time / divisor, appendix); - }); - } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EventSpectateCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EventSpectateCountdown.java index 9f8d2503..83e40f67 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EventSpectateCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/EventSpectateCountdown.java @@ -19,25 +19,26 @@ package de.steamwar.fightsystem.countdown; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.linkage.Linked; @Linked -public class EventSpectateCountdown extends Countdown { +public class EventSpectateCountdown extends StandardCountdown { public EventSpectateCountdown() { - super(Config.GameModeConfig.Times.SpectatorDuration, new Message("SHUTDOWN_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false); - new StateDependentCountdown(ArenaMode.NotRestartable.contains(Config.mode) && !Config.replayserver(), FightState.Spectate, this); + super(FightSystem.getPlugin(), Config.GameModeConfig.Times.SpectatorDuration, FightCountdownAdapter.presentation(new Message("SHUTDOWN_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false)); + new StateDependentCountdown<>(FightState.lifecycle, ArenaMode.NotRestartable.contains(Config.mode) && !Config.replayserver(), FightState.Spectate, this); } @Override - public void countdownFinished() { + protected void countdownFinished() { FightSystem.shutdown(); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/FightCountdownAdapter.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/FightCountdownAdapter.java new file mode 100644 index 00000000..cf162da6 --- /dev/null +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/FightCountdownAdapter.java @@ -0,0 +1,91 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.fightsystem.countdown; + +import de.steamwar.fightsystem.FightSystem; +import de.steamwar.fightsystem.fight.Fight; +import de.steamwar.fightsystem.fight.FightPlayer; +import de.steamwar.fightsystem.record.GlobalRecorder; +import de.steamwar.fightsystem.utils.Message; +import de.steamwar.fightsystem.utils.SWSound; +import de.steamwar.gamecore.countdown.CountdownMessageSink; +import de.steamwar.gamecore.countdown.CountdownPresentation; +import net.md_5.bungee.api.ChatMessageType; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +/** + * Binds FightSystem's transports to GameCore's presentation engine. + */ +public final class FightCountdownAdapter { + private FightCountdownAdapter() { + } + + public static CountdownPresentation presentation(Message appendix, SWSound sound, boolean level) { + return presentation( + appendix, + sound, + level, + (message, displayTime) -> { + GlobalRecorder.getInstance().countdown(message.getKey(), displayTime, appendix); + Bukkit.getOnlinePlayers().forEach(player -> + sendCountdownMessage(player, message.getKey(), displayTime, appendix) + ); + } + ); + } + + public static CountdownPresentation presentation( + Message appendix, + SWSound sound, + boolean level, + CountdownMessageSink messageSink + ) { + return new CountdownPresentation( + messageSink, + sound != null + ? pitch -> Fight.playSound(sound.getSound(), 100.0F, pitch) + : null, + level + ? timeLeft -> Bukkit.getOnlinePlayers().forEach(player -> player.setLevel(timeLeft)) + : null + ); + } + + public static CountdownPresentation playerPresentation( + FightPlayer fightPlayer, + Message appendix, + SWSound sound + ) { + return new CountdownPresentation( + (message, displayTime) -> fightPlayer.ifPlayer(player -> + sendCountdownMessage(player, message.getKey(), displayTime, appendix) + ), + pitch -> fightPlayer.ifPlayer(player -> + player.playSound(player.getLocation(), sound.getSound(), 100.0F, pitch) + ), + pitch -> Fight.playSound(sound.getSound(), 100.0F, pitch), + null, + true + ); + } + + public static void sendCountdownMessage(Player player, String message, int displayTime, Message appendix) { + FightSystem.getMessage().sendPrefixless( + message, + player, + ChatMessageType.ACTION_BAR, + displayTime, + FightSystem.getMessage().parse(appendix.getMsg(), player, appendix.getParams()) + ); + } +} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/NoPlayersOnlineCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/NoPlayersOnlineCountdown.java index 280548ed..190088b9 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/NoPlayersOnlineCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/NoPlayersOnlineCountdown.java @@ -19,27 +19,29 @@ package de.steamwar.fightsystem.countdown; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.utils.Message; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.linkage.Linked; import org.bukkit.Bukkit; @Linked -public class NoPlayersOnlineCountdown extends Countdown { +public class NoPlayersOnlineCountdown extends StandardCountdown { public NoPlayersOnlineCountdown() { - super(Config.GameModeConfig.Times.NoPlayersOnlineDuration, new Message("SHUTDOWN_COUNTDOWN"), null, false); + super(FightSystem.getPlugin(), Config.GameModeConfig.Times.NoPlayersOnlineDuration, FightCountdownAdapter.presentation(new Message("SHUTDOWN_COUNTDOWN"), null, false)); if (!Config.GameModeConfig.Arena.Leaveable) { - new StateDependentCountdown(ArenaMode.AntiReplay, FightState.PreLeaderSetup, this); + new StateDependentCountdown<>(FightState.lifecycle, ArenaMode.AntiReplay.contains(Config.mode), FightState.PreLeaderSetup, this); } } @Override - public void countdownFinished() { + protected void countdownFinished() { Bukkit.getServer().shutdown(); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PostSchemCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PostSchemCountdown.java index fc9b9291..732bfc44 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PostSchemCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PostSchemCountdown.java @@ -19,27 +19,29 @@ package de.steamwar.fightsystem.countdown; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.utils.Message; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.linkage.Linked; @Linked -public class PostSchemCountdown extends Countdown { +public class PostSchemCountdown extends StandardCountdown { public PostSchemCountdown() { - super(Config.GameModeConfig.Times.SetupDuration, new Message("POST_SCHEM_COUNTDOWN"), null, false); + super(FightSystem.getPlugin(), Config.GameModeConfig.Times.SetupDuration, FightCountdownAdapter.presentation(new Message("POST_SCHEM_COUNTDOWN"), null, false)); if (Config.mode == ArenaMode.PREPARE && Config.GameModeConfig.Schematic.UnlimitedPrepare) { return; } - new StateDependentCountdown(ArenaMode.SeriousFight, FightState.PostSchemSetup, this); + new StateDependentCountdown<>(FightState.lifecycle, ArenaMode.SeriousFight.contains(Config.mode), FightState.PostSchemSetup, this); } @Override - public void countdownFinished() { + protected void countdownFinished() { FightState.setFightState(FightState.PRE_RUNNING); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreRunningCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreRunningCountdown.java index 45b8202e..25e3bf5e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreRunningCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreRunningCountdown.java @@ -19,24 +19,26 @@ package de.steamwar.fightsystem.countdown; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.SWSound; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.linkage.Linked; @Linked -public class PreRunningCountdown extends Countdown { +public class PreRunningCountdown extends StandardCountdown { public PreRunningCountdown() { - super(Config.GameModeConfig.Times.PreFightDuration, new Message("PRE_RUNNING_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, true); - new StateDependentCountdown(ArenaMode.AntiReplay, FightState.PreRunning, this); + super(FightSystem.getPlugin(), Config.GameModeConfig.Times.PreFightDuration, FightCountdownAdapter.presentation(new Message("PRE_RUNNING_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, true)); + new StateDependentCountdown<>(FightState.lifecycle, ArenaMode.AntiReplay.contains(Config.mode), FightState.PreRunning, this); } @Override - public void countdownFinished() { + protected void countdownFinished() { FightState.setFightState(FightState.RUNNING); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreSchemCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreSchemCountdown.java index 45a44296..57ccb72c 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreSchemCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/PreSchemCountdown.java @@ -19,24 +19,26 @@ package de.steamwar.fightsystem.countdown; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.SWSound; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.linkage.Linked; @Linked -public class PreSchemCountdown extends Countdown { +public class PreSchemCountdown extends StandardCountdown { public PreSchemCountdown() { - super(Config.GameModeConfig.Times.PreSchemPasteDuration, new Message("PRE_SCHEM_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false); - new StateDependentCountdown(ArenaMode.AntiReplay, FightState.PreSchemSetup, this); + super(FightSystem.getPlugin(), Config.GameModeConfig.Times.PreSchemPasteDuration, FightCountdownAdapter.presentation(new Message("PRE_SCHEM_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false)); + new StateDependentCountdown<>(FightState.lifecycle, ArenaMode.AntiReplay.contains(Config.mode), FightState.PreSchemSetup, this); } @Override - public void countdownFinished() { + protected void countdownFinished() { FightState.setFightState(FightState.POST_SCHEM_SETUP); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/SpectateOverCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/SpectateOverCountdown.java index 4844536a..a8d0fa9e 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/SpectateOverCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/SpectateOverCountdown.java @@ -19,24 +19,26 @@ package de.steamwar.fightsystem.countdown; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.SWSound; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.linkage.Linked; @Linked -public class SpectateOverCountdown extends Countdown { +public class SpectateOverCountdown extends StandardCountdown { public SpectateOverCountdown() { - super(Config.test() ? 3600 : Config.GameModeConfig.Times.SpectatorDuration, new Message("SPECTATE_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false); - new StateDependentCountdown(ArenaMode.Restartable.contains(Config.mode) || Config.replayserver(), FightState.Spectate, this); + super(FightSystem.getPlugin(), Config.test() ? 3600 : Config.GameModeConfig.Times.SpectatorDuration, FightCountdownAdapter.presentation(new Message("SPECTATE_COUNTDOWN"), SWSound.BLOCK_NOTE_PLING, false)); + new StateDependentCountdown<>(FightState.lifecycle, ArenaMode.Restartable.contains(Config.mode) || Config.replayserver(), FightState.Spectate, this); } @Override - public void countdownFinished() { + protected void countdownFinished() { FightState.setFightState(FightState.PRE_LEADER_SETUP); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/TimeOverCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/TimeOverCountdown.java index ed90d5e9..731bdd01 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/TimeOverCountdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/countdown/TimeOverCountdown.java @@ -20,20 +20,22 @@ package de.steamwar.fightsystem.countdown; import de.steamwar.fightsystem.Config; +import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.SWSound; +import de.steamwar.gamecore.countdown.StandardCountdown; -public class TimeOverCountdown extends Countdown { +public class TimeOverCountdown extends StandardCountdown { private final Runnable timeOver; public TimeOverCountdown(Runnable timeOver) { - super(Config.GameModeConfig.WinConditionParams.TimeoutTime, new Message("RUNNING_COUNTDOWN"), SWSound.BLOCK_NOTE_BASS, false); + super(FightSystem.getPlugin(), Config.GameModeConfig.WinConditionParams.TimeoutTime, FightCountdownAdapter.presentation(new Message("RUNNING_COUNTDOWN"), SWSound.BLOCK_NOTE_BASS, false)); this.timeOver = timeOver; } @Override - public void countdownFinished() { + protected void countdownFinished() { timeOver.run(); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/HellsBells.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/HellsBells.java index 7e32837a..36fc0bf7 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/HellsBells.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/HellsBells.java @@ -21,13 +21,14 @@ package de.steamwar.fightsystem.event; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.fightsystem.countdown.FightCountdownAdapter; import de.steamwar.fightsystem.record.GlobalRecorder; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependent; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.fightsystem.winconditions.Winconditions; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.linkage.Linked; import org.bukkit.Bukkit; import org.bukkit.entity.EntityType; @@ -139,14 +140,14 @@ public class HellsBells { }.register(); } - private class HellsBellsCountdown extends Countdown { + private class HellsBellsCountdown extends StandardCountdown { public HellsBellsCountdown(int time) { - super(time, new Message("HELLS_BELLS_COUNTDOWN"), SWSound.BLOCK_NOTE_BASS, true); + super(FightSystem.getPlugin(), time, FightCountdownAdapter.presentation(new Message("HELLS_BELLS_COUNTDOWN"), SWSound.BLOCK_NOTE_BASS, true)); } @Override - public void countdownFinished() { + protected void countdownFinished() { drop(); startCountdown(); } @@ -240,4 +241,4 @@ public class HellsBells { return this == NORTH || this == WEST; } } -} \ No newline at end of file +} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/Meteor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/Meteor.java index 5b40b7ce..a824d5cd 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/Meteor.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/Meteor.java @@ -21,7 +21,7 @@ package de.steamwar.fightsystem.event; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.fightsystem.countdown.FightCountdownAdapter; import de.steamwar.fightsystem.record.GlobalRecorder; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependent; @@ -29,6 +29,7 @@ import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.utils.Message; import de.steamwar.fightsystem.utils.SWSound; import de.steamwar.fightsystem.winconditions.Winconditions; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.linkage.Linked; import org.bukkit.Bukkit; import org.bukkit.entity.Fireball; @@ -154,14 +155,14 @@ public class Meteor implements Listener { }.register(); } - private class MeteorCountdown extends Countdown { + private class MeteorCountdown extends StandardCountdown { public MeteorCountdown(int time) { - super(time, new Message("METEOR_COUNTDOWN"), SWSound.BLOCK_NOTE_BASS, true); + super(FightSystem.getPlugin(), time, FightCountdownAdapter.presentation(new Message("METEOR_COUNTDOWN"), SWSound.BLOCK_NOTE_BASS, true)); } @Override - public void countdownFinished() { + protected void countdownFinished() { drop(); startCountdown(); } @@ -212,4 +213,4 @@ public class Meteor implements Listener { } } } -} \ No newline at end of file +} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/PersistentDamage.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/PersistentDamage.java index 39930db7..b53bb152 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/PersistentDamage.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/event/PersistentDamage.java @@ -20,7 +20,7 @@ package de.steamwar.fightsystem.event; import com.sk89q.worldedit.WorldEditException; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java index 8a894013..189db5cd 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/Fight.java @@ -19,9 +19,10 @@ package de.steamwar.fightsystem.fight; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.record.GlobalRecorder; +import de.steamwar.gamecore.team.TeamRegistry; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.Registry; @@ -29,7 +30,7 @@ import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; import java.util.Collection; -import java.util.HashSet; +import java.util.List; public class Fight { private Fight() { @@ -39,31 +40,14 @@ public class Fight { private static final FightTeam redTeam = new FightTeam(Config.TeamRedName, Config.TeamRedColor, Config.TeamRedSpawn, Config.RedPasteRegion, Config.RedExtendRegion, Config.RedRotate, false, Config.RedLeader); @Getter private static final FightTeam blueTeam = new FightTeam(Config.TeamBlueName, Config.TeamBlueColor, Config.TeamBlueSpawn, Config.BluePasteRegion, Config.BlueExtendRegion, Config.BlueRotate, true, Config.BlueLeader); - private static final Collection teams = new HashSet<>(); - - static { - teams.add(redTeam); - teams.add(blueTeam); - } + private static final TeamRegistry teamRegistry = new TeamRegistry<>(List.of(redTeam, blueTeam)); public static FightTeam getPlayerTeam(LivingEntity player) { - if (redTeam.isPlayerInTeam(player)) { - return redTeam; - } - if (blueTeam.isPlayerInTeam(player)) { - return blueTeam; - } - return null; + return teamRegistry.findTeam(player); } public static FightTeam getOpposite(FightTeam fightTeam) { - if (fightTeam == redTeam) { - return blueTeam; - } else if (fightTeam == blueTeam) { - return redTeam; - } - - throw new IllegalArgumentException(); + return teamRegistry.opposite(fightTeam); } public static FightPlayer getFightPlayer(LivingEntity player) { @@ -81,7 +65,11 @@ public class Fight { } public static Collection teams() { - return teams; + return teamRegistry.getTeams(); + } + + public static boolean allTeamsReady() { + return teamRegistry.allReady(); } public static FightTeam getUnrotated() { @@ -95,13 +83,7 @@ public class Fight { } public static FightTeam getTeamByName(String name) { - if (redTeam.getName().equalsIgnoreCase(name)) { - return redTeam; - } - if (blueTeam.getName().equalsIgnoreCase(name)) { - return blueTeam; - } - return null; + return teamRegistry.findById(name); } public static boolean publicOnly() { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java index 0473f751..36a18556 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightPlayer.java @@ -21,7 +21,7 @@ package de.steamwar.fightsystem.fight; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.ai.AI; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.gamecore.countdown.Countdown; import de.steamwar.fightsystem.countdown.EnternCountdown; import de.steamwar.fightsystem.events.TeamDeathEvent; import de.steamwar.sql.PersonalKit; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java index c8174349..bab8b805 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightSchematic.java @@ -22,7 +22,7 @@ package de.steamwar.fightsystem.fight; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.math.transform.AffineTransform; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.record.GlobalRecorder; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java index 0119bf40..7e55967f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightTeam.java @@ -20,12 +20,12 @@ package de.steamwar.fightsystem.fight; import com.sk89q.worldedit.extent.clipboard.Clipboard; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.ai.AIManager; import de.steamwar.fightsystem.commands.GUI; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.gamecore.countdown.Countdown; import de.steamwar.fightsystem.events.TeamLeaveEvent; import de.steamwar.fightsystem.events.TeamSpawnEvent; import de.steamwar.fightsystem.listener.FightScoreboard; @@ -57,7 +57,7 @@ import java.util.*; import java.util.function.Consumer; -public class FightTeam { +public class FightTeam implements de.steamwar.gamecore.team.GameTeam { private static void setKitButton(HotbarKit kit, boolean leader) { if (Kit.getAvailableKits(leader).size() > 1 || Config.GameModeConfig.Kits.PersonalKits) { @@ -404,6 +404,31 @@ public class FightTeam { return players.values(); } + @Override + public String teamId() { + return name; + } + + @Override + public Collection members() { + return players.values().stream().map(FightPlayer::getEntity).toList(); + } + + @Override + public LivingEntity leader() { + return leader == null ? null : leader.getEntity(); + } + + @Override + public boolean ready() { + return ready; + } + + @Override + public boolean contains(LivingEntity member) { + return isPlayerInTeam(member); + } + public void pasteSchem() { testPasteAction(); } @@ -455,7 +480,7 @@ public class FightTeam { if (ready) { broadcast("TEAM_READY"); leader.ifPlayer(readyKit::loadToPlayer); - if (Fight.getOpposite(this).isReady() || ArenaMode.SoloLeader.contains(Config.mode)) { + if (Fight.allTeamsReady() || ArenaMode.SoloLeader.contains(Config.mode)) { FightState.setFightState(FightState.PRE_RUNNING); } } else { @@ -471,7 +496,9 @@ public class FightTeam { if (Fight.getOpposite(this).skip || Config.test()) { skip = false; Fight.getOpposite(this).skip = false; - Countdown.skip(); + if (Countdown.skipActive()) { + FightUI.addSubtitle("UI_SKIP"); + } } } else { broadcast("SKIP_NOT_READY"); diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java index 83c15170..eb42549c 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/FightWorld.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.fight; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.listener.Recording; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/HotbarKitListener.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/HotbarKitListener.java index 0f0e9fcd..02c6f09a 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/HotbarKitListener.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/fight/HotbarKitListener.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.fight; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.listener.PersonalKitCreator; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArenaBorder.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArenaBorder.java index c7972f64..6a424e15 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArenaBorder.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/ArenaBorder.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.events.TeamDeathEvent; import de.steamwar.fightsystem.events.TeamLeaveEvent; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/BlockPlaceCollision.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/BlockPlaceCollision.java index 97b49283..5c5ba36f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/BlockPlaceCollision.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/BlockPlaceCollision.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.linkage.Linked; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Border.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Border.java index 55864abd..23e7bced 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Border.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Border.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Chat.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Chat.java index 617684d8..1bd9f0a3 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Chat.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Chat.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Check.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Check.java index ac338bfb..a672fb26 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Check.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Check.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyInventoryMovement.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyInventoryMovement.java index 0452691c..4b74eb54 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyInventoryMovement.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyInventoryMovement.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyWorldInteraction.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyWorldInteraction.java index 42b5909e..733f3c28 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyWorldInteraction.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/DenyWorldInteraction.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java index dcc29026..fa70ee92 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EntityDamage.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EventJoin.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EventJoin.java index a28f8b6c..f87af682 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EventJoin.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/EventJoin.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/FightScoreboard.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/FightScoreboard.java index d173fa76..794ebc4b 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/FightScoreboard.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/FightScoreboard.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.scoreboard.SWScoreboard; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java index c49b46c6..e6ce133d 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/InFightInventory.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/IngameDeath.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/IngameDeath.java index 00536147..6a4b3aec 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/IngameDeath.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/IngameDeath.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/JoinRequestListener.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/JoinRequestListener.java index feda65a2..b63a7412 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/JoinRequestListener.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/JoinRequestListener.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/NormalJoin.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/NormalJoin.java index e5aa7a98..fac361e4 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/NormalJoin.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/NormalJoin.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Permanent.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Permanent.java index e5c55436..57af9443 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Permanent.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Permanent.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PistonListener.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PistonListener.java index 0b7f0de2..7a3ed07b 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PistonListener.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PistonListener.java @@ -19,10 +19,10 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; +import de.steamwar.gamecore.ArenaMode; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.PistonMoveReaction; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PrepareSchem.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PrepareSchem.java index f83c4c21..bc760eb1 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PrepareSchem.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/PrepareSchem.java @@ -20,7 +20,7 @@ package de.steamwar.fightsystem.listener; import com.sk89q.worldedit.WorldEditException; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java index ecd333ec..fb6bc0ef 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Recording.java @@ -20,7 +20,7 @@ package de.steamwar.fightsystem.listener; import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.events.TeamDeathEvent; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/RunningWorldInteraction.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/RunningWorldInteraction.java index b62d814d..177f2b3f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/RunningWorldInteraction.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/RunningWorldInteraction.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightPlayer; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/SetupQuit.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/SetupQuit.java index 2d502d2a..817c11b2 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/SetupQuit.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/SetupQuit.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Shutdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Shutdown.java index a4acd27f..2aef208c 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Shutdown.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Shutdown.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.StateDependentListener; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Spectator.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Spectator.java index d070ce98..65dfe3c0 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Spectator.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/Spectator.java @@ -22,7 +22,7 @@ package de.steamwar.fightsystem.listener; import com.comphenix.tinyprotocol.TinyProtocol; import com.mojang.authlib.GameProfile; import de.steamwar.core.ProtocolWrapper; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightPlayer; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TeamArea.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TeamArea.java index 0c8ce1a2..e3329b2f 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TeamArea.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TeamArea.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.events.BoardingEvent; import de.steamwar.fightsystem.events.TeamDeathEvent; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TestJoin.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TestJoin.java index d41a3eeb..63b37ba6 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TestJoin.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/TestJoin.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java index bded9613..7a27dfdd 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/listener/WaterRemover.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.listener; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java index 80fcf091..badb430d 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/PacketProcessor.java @@ -24,10 +24,10 @@ import de.steamwar.core.WorldEditWrapper; import de.steamwar.entity.REntity; import de.steamwar.entity.REntityServer; import de.steamwar.entity.RPlayer; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.fightsystem.countdown.FightCountdownAdapter; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.fight.FightWorld; @@ -393,7 +393,7 @@ public class PacketProcessor implements Listener { int displaytime = source.readInt(); Message appendix = readMessage(); - Bukkit.getOnlinePlayers().forEach(p -> Countdown.sendCountdownMessage(p, message, displaytime, appendix)); + Bukkit.getOnlinePlayers().forEach(p -> FightCountdownAdapter.sendCountdownMessage(p, message, displaytime, appendix)); } private void oldArenaInfo() throws IOException { diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java index 2d42c473..88a7a342 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/record/Recorder.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.record; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightPlayer; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/FightState.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/FightState.java index c2fe5790..1147e688 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/FightState.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/FightState.java @@ -1,27 +1,12 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.fightsystem.states; -import lombok.Getter; +import de.steamwar.gamecore.state.GameStateMachine; +import de.steamwar.gamecore.state.IStateDependent; -import java.util.*; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; public enum FightState { PRE_LEADER_SETUP, @@ -31,63 +16,58 @@ public enum FightState { RUNNING, SPECTATE; - public static final Set All = Collections.unmodifiableSet(EnumSet.allOf(FightState.class)); + public static final Set All = immutable(EnumSet.allOf(FightState.class)); + public static final Set PreLeaderSetup = immutable(EnumSet.of(PRE_LEADER_SETUP)); + public static final Set PreSchemSetup = immutable(EnumSet.of(PRE_SCHEM_SETUP)); + public static final Set PostSchemSetup = immutable(EnumSet.of(POST_SCHEM_SETUP)); + public static final Set PreRunning = immutable(EnumSet.of(PRE_RUNNING)); + public static final Set Running = immutable(EnumSet.of(RUNNING)); + public static final Set Spectate = immutable(EnumSet.of(SPECTATE)); + public static final Set Setup = immutable(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, POST_SCHEM_SETUP)); + public static final Set Ingame = immutable(EnumSet.of(PRE_RUNNING, RUNNING)); + public static final Set TeamFix = immutable(EnumSet.of(PRE_RUNNING, RUNNING, SPECTATE)); + public static final Set Schem = complement(PRE_LEADER_SETUP, PRE_SCHEM_SETUP); + public static final Set Recording = complement(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, SPECTATE); + public static final Set AntiRunning = complement(RUNNING); + public static final Set AntiIngame = complement(PRE_RUNNING, RUNNING); + public static final Set AntiSpectate = complement(SPECTATE); - public static final Set PreLeaderSetup = Collections.unmodifiableSet(EnumSet.of(PRE_LEADER_SETUP)); - public static final Set PreSchemSetup = Collections.unmodifiableSet(EnumSet.of(PRE_SCHEM_SETUP)); - public static final Set PostSchemSetup = Collections.unmodifiableSet(EnumSet.of(POST_SCHEM_SETUP)); - public static final Set PreRunning = Collections.unmodifiableSet(EnumSet.of(PRE_RUNNING)); - public static final Set Running = Collections.unmodifiableSet(EnumSet.of(RUNNING)); - public static final Set Spectate = Collections.unmodifiableSet(EnumSet.of(SPECTATE)); + public static final GameStateMachine lifecycle = new GameStateMachine<>(PRE_LEADER_SETUP); - public static final Set Setup = Collections.unmodifiableSet(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, POST_SCHEM_SETUP)); - public static final Set Ingame = Collections.unmodifiableSet(EnumSet.of(PRE_RUNNING, RUNNING)); - public static final Set TeamFix = Collections.unmodifiableSet(EnumSet.of(PRE_RUNNING, RUNNING, SPECTATE)); - public static final Set Schem = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP))); - public static final Set Recording = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, SPECTATE))); - public static final Set AntiRunning = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(RUNNING))); - public static final Set AntiIngame = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PRE_RUNNING, RUNNING))); - public static final Set AntiSpectate = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(SPECTATE))); - - @Getter - private static final Map stateDependentFeatures = new HashMap<>(); - @Getter - private static FightState fightState = PRE_LEADER_SETUP; - - public static void registerStateDependent(IStateDependent stateDependent) { - if (stateDependent.enabled().isEmpty()) return; - boolean enabled = stateDependent.enabled().contains(fightState); - stateDependentFeatures.put(stateDependent, enabled); - if (enabled) stateDependent.enable(); + public static void registerStateDependent(IStateDependent stateDependent) { + lifecycle.registerStateDependent(stateDependent); } public static void setFightState(FightState state) { - fightState = state; + lifecycle.transitionTo(state); + } - for (Map.Entry feature : stateDependentFeatures.entrySet()) { - //Enable feature if should be enabled and currently disabled - if (feature.getKey().enabled().contains(fightState) && !feature.getValue()) { - feature.getKey().enable(); - feature.setValue(true); - } + public static FightState getFightState() { + return lifecycle.getCurrentState(); + } - //Disable feature if should be disabled and currently enabled - if (!feature.getKey().enabled().contains(fightState) && feature.getValue()) { - feature.getKey().disable(); - feature.setValue(false); - } - } + public static Map, Boolean> getStateDependentFeatures() { + return lifecycle.getStateDependentFeatures(); } public static boolean setup() { - return Setup.contains(fightState); + return Setup.contains(getFightState()); } public static boolean ingame() { - return Ingame.contains(fightState); + return Ingame.contains(getFightState()); } public static boolean infight() { - return fightState == RUNNING; + return getFightState() == RUNNING; + } + + private static Set complement(FightState first, FightState... remaining) { + EnumSet excluded = EnumSet.of(first, remaining); + return immutable(EnumSet.complementOf(excluded)); + } + + private static Set immutable(EnumSet states) { + return Collections.unmodifiableSet(states); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/IStateDependent.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/IStateDependent.java index e699a519..8f11a011 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/IStateDependent.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/IStateDependent.java @@ -1,40 +1,5 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.fightsystem.states; -import java.util.Set; - -public interface IStateDependent { - - /** - * @return returns a set containing - */ - Set enabled(); - - /** - * Enables the state dependent object - */ - void enable(); - - /** - * Disables the state dependent object - */ - void disable(); +@Deprecated +public interface IStateDependent extends de.steamwar.gamecore.state.IStateDependent { } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/OneShotStateDependent.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/OneShotStateDependent.java index c3741ba2..d7acd724 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/OneShotStateDependent.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/OneShotStateDependent.java @@ -1,59 +1,21 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.fightsystem.states; -import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.winconditions.Winconditions; import java.util.Set; -public class OneShotStateDependent extends StateDependent { - - private final Runnable runnable; - - public OneShotStateDependent(Set mode, Set states, Runnable runnable) { - super(mode, states); - this.runnable = runnable; - register(); +@Deprecated +public class OneShotStateDependent extends de.steamwar.gamecore.state.OneShotStateDependent implements IStateDependent { + public OneShotStateDependent(Set modes, Set states, Runnable runnable) { + super(FightState.lifecycle, modes, Config.mode, states, runnable); } public OneShotStateDependent(Winconditions wincondition, Set states, Runnable runnable) { - super(Config.GameModeConfig.WinConditions.contains(wincondition), states); - this.runnable = runnable; - register(); + this(Config.GameModeConfig.WinConditions.contains(wincondition), states, runnable); } public OneShotStateDependent(boolean active, Set states, Runnable runnable) { - super(active, states); - this.runnable = runnable; - register(); - } - - @Override - public void enable() { - runnable.run(); - } - - @Override - public void disable() { - //Do nothing, oneshot + super(FightState.lifecycle, active, states, runnable); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependent.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependent.java index 9382a490..7b4c2e0a 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependent.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependent.java @@ -1,54 +1,21 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.fightsystem.states; -import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.winconditions.Winconditions; import java.util.Set; -public abstract class StateDependent implements IStateDependent { - - private final Set enabled; - private final boolean register; - +@Deprecated +public abstract class StateDependent extends de.steamwar.gamecore.state.StateDependent implements IStateDependent { protected StateDependent(Winconditions wincondition, Set states) { this(Config.GameModeConfig.WinConditions.contains(wincondition), states); } - protected StateDependent(Set mode, Set states) { - this(mode.contains(Config.mode), states); + protected StateDependent(Set modes, Set states) { + super(FightState.lifecycle, modes, Config.mode, states); } protected StateDependent(boolean enabled, Set states) { - this.enabled = states; - this.register = enabled; - } - - public void register() { - if (register) FightState.registerStateDependent(this); - } - - @Override - public Set enabled() { - return enabled; + super(FightState.lifecycle, enabled, states); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCommand.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCommand.java index 53ea244a..bd5a1da9 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCommand.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCommand.java @@ -1,58 +1,20 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.fightsystem.states; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import net.md_5.bungee.api.ChatMessageType; import org.bukkit.command.CommandExecutor; -import org.bukkit.command.PluginCommand; import java.util.Set; -public class StateDependentCommand extends StateDependent { - - private static final CommandExecutor unavailable = (sender, cmd, s, strings) -> { +@Deprecated +public class StateDependentCommand extends de.steamwar.gamecore.state.StateDependentCommand implements IStateDependent { + private static final CommandExecutor unavailable = (sender, cmd, label, args) -> { FightSystem.getMessage().sendPrefixless("COMMAND_CURRENTLY_UNAVAILABLE", sender, ChatMessageType.ACTION_BAR); return false; }; - private final PluginCommand command; - private final CommandExecutor executor; - - public StateDependentCommand(Set mode, Set states, String name, CommandExecutor executor) { - super(mode, states); - this.executor = executor; - this.command = FightSystem.getPlugin().getCommand(name); - assert command != null; - disable(); - register(); - } - - @Override - public void enable() { - command.setExecutor(executor); - } - - @Override - public void disable() { - command.setExecutor(unavailable); + public StateDependentCommand(Set modes, Set states, String name, CommandExecutor executor) { + super(FightState.lifecycle, modes.contains(Config.mode), states, FightSystem.getPlugin(), name, executor, unavailable); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCountdown.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCountdown.java deleted file mode 100644 index f9f5c744..00000000 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentCountdown.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.fightsystem.states; - -import de.steamwar.fightsystem.ArenaMode; -import de.steamwar.fightsystem.Config; -import de.steamwar.fightsystem.countdown.Countdown; -import de.steamwar.fightsystem.winconditions.Winconditions; - -import java.util.Set; - -public class StateDependentCountdown extends StateDependent { - - private static Countdown mainCountdown = null; - - public static Countdown getMainCountdown() { - return mainCountdown; - } - - private final Countdown countdown; - - public StateDependentCountdown(Set mode, Set states, Countdown countdown) { - super(mode, states); - this.countdown = countdown; - register(); - } - - public StateDependentCountdown(Winconditions wincondition, Set states, Countdown countdown) { - this(Config.GameModeConfig.WinConditions.contains(wincondition), states, countdown); - } - - public StateDependentCountdown(boolean active, Set states, Countdown countdown) { - super(active, states); - this.countdown = countdown; - register(); - } - - public Countdown getCountdown() { - return countdown; - } - - @Override - public void enable() { - mainCountdown = countdown; - countdown.enable(); - } - - @Override - public void disable() { - countdown.disable(); - if (mainCountdown == countdown) mainCountdown = null; - } -} diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentListener.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentListener.java index 9fb831de..7f951ebf 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentListener.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentListener.java @@ -1,63 +1,23 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.fightsystem.states; -import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.winconditions.Winconditions; -import org.bukkit.Bukkit; -import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import java.util.Set; -public class StateDependentListener extends StateDependent { - - private final Listener listener; - +@Deprecated +public class StateDependentListener extends de.steamwar.gamecore.state.StateDependentListener implements IStateDependent { public StateDependentListener(Winconditions wincondition, Set states, Listener listener) { - super(Config.GameModeConfig.WinConditions.contains(wincondition), states); - this.listener = listener; - register(); + this(Config.GameModeConfig.WinConditions.contains(wincondition), states, listener); } - public StateDependentListener(boolean enabled, Set states, Listener listener) { - super(enabled, states); - this.listener = listener; - register(); + public StateDependentListener(boolean active, Set states, Listener listener) { + super(FightState.lifecycle, active, states, FightSystem.getPlugin(), listener); } - public StateDependentListener(Set mode, Set states, Listener listener) { - super(mode, states); - this.listener = listener; - register(); - } - - @Override - public void enable() { - Bukkit.getPluginManager().registerEvents(listener, FightSystem.getPlugin()); - } - - @Override - public void disable() { - HandlerList.unregisterAll(listener); + public StateDependentListener(Set modes, Set states, Listener listener) { + super(FightState.lifecycle, modes, Config.mode, states, FightSystem.getPlugin(), listener); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentTask.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentTask.java index 4478ca92..abb7e578 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentTask.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/states/StateDependentTask.java @@ -1,69 +1,22 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.fightsystem.states; -import de.steamwar.fightsystem.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.winconditions.Winconditions; -import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitTask; import java.util.Set; -public class StateDependentTask extends StateDependent { - - private final Runnable runnable; - private final long delay; - private final long period; - - private BukkitTask task = null; - +@Deprecated +public class StateDependentTask extends de.steamwar.gamecore.state.StateDependentTask implements IStateDependent { public StateDependentTask(Winconditions wincondition, Set states, Runnable runnable, long delay, long period) { this(Config.GameModeConfig.WinConditions.contains(wincondition), states, runnable, delay, period); } - public StateDependentTask(boolean enabled, Set states, Runnable runnable, long delay, long period) { - super(enabled, states); - this.runnable = runnable; - this.delay = delay; - this.period = period; - register(); + public StateDependentTask(boolean active, Set states, Runnable runnable, long delay, long period) { + super(FightState.lifecycle, active, states, FightSystem.getPlugin(), runnable, delay, period); } - public StateDependentTask(Set mode, Set states, Runnable runnable, long delay, long period) { - super(mode, states); - this.runnable = runnable; - this.delay = delay; - this.period = period; - register(); + public StateDependentTask(Set modes, Set states, Runnable runnable, long delay, long period) { + super(FightState.lifecycle, modes, Config.mode, states, FightSystem.getPlugin(), runnable, delay, period); } - - @Override - public void enable() { - task = Bukkit.getScheduler().runTaskTimer(FightSystem.getPlugin(), runnable, delay, period); - } - - @Override - public void disable() { - task.cancel(); - } - } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/BungeeFightInfo.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/BungeeFightInfo.java index 7d3b31f8..47a5c910 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/BungeeFightInfo.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/BungeeFightInfo.java @@ -19,11 +19,12 @@ package de.steamwar.fightsystem.utils; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; +import de.steamwar.gamecore.countdown.Countdown; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.linkage.Linked; import de.steamwar.network.NetworkSender; @@ -41,6 +42,7 @@ public class BungeeFightInfo { } private void send() { + Countdown mainCountdown = StateDependentCountdown.getMainCountdown(FightState.lifecycle); NetworkSender.send(new FightInfoPacket( Config.world.getName(), Config.GameModeConfig.Schematic.Type.toDB(), @@ -48,7 +50,7 @@ public class BungeeFightInfo { Fight.getBlueTeam().getColoredName(), Fight.getRedTeam().getColoredName(), FightState.getFightState().name(), - StateDependentCountdown.getMainCountdown() != null ? StateDependentCountdown.getMainCountdown().getTimeLeft() : 0, + mainCountdown != null ? mainCountdown.getTimeLeft() : 0, Fight.getBlueTeam().getLeader() != null ? Fight.getBlueTeam().getLeader().getUser().getId() : 0, Fight.getRedTeam().getLeader() != null ? Fight.getRedTeam().getLeader().getUser().getId() : 0, Fight.getBlueTeam().getSchematic(), diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java index 6f613608..0253bbf3 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightStatistics.java @@ -19,10 +19,10 @@ package de.steamwar.fightsystem.utils; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.gamecore.countdown.Countdown; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightPlayer; import de.steamwar.fightsystem.fight.FightTeam; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightUI.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightUI.java index 68103b76..35999136 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightUI.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/utils/FightUI.java @@ -20,7 +20,8 @@ package de.steamwar.fightsystem.utils; import de.steamwar.core.TPSWatcher; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; +import de.steamwar.gamecore.countdown.Countdown; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.FightSystem; import de.steamwar.fightsystem.fight.Fight; @@ -28,7 +29,7 @@ import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.record.GlobalRecorder; import de.steamwar.fightsystem.states.FightState; import de.steamwar.fightsystem.states.OneShotStateDependent; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.states.StateDependentTask; import de.steamwar.fightsystem.winconditions.Wincondition; import de.steamwar.linkage.Linked; @@ -71,7 +72,8 @@ public class FightUI { totalHearts = 1.0; } - int timeLeft = StateDependentCountdown.getMainCountdown() != null ? StateDependentCountdown.getMainCountdown().getTimeLeft() : 0; + Countdown mainCountdown = StateDependentCountdown.getMainCountdown(FightState.lifecycle); + int timeLeft = mainCountdown != null ? mainCountdown.getTimeLeft() : 0; String time = "§7" + (timeLeft / 60) + "§8:§7" + (timeLeft % 60 < 10 ? "0" : "") + (timeLeft % 60); String bluePlayers = blue.getPrefix() + blue.getAlivePlayers() + "§8/§7" + blue.getPlayerCount(); String redPlayers = red.getPrefix() + red.getAlivePlayers() + "§8/§7" + red.getPlayerCount(); diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/EventTeamOffWincondition.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/EventTeamOffWincondition.java index c2f203ff..78fab9d7 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/EventTeamOffWincondition.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/EventTeamOffWincondition.java @@ -19,7 +19,7 @@ package de.steamwar.fightsystem.winconditions; -import de.steamwar.fightsystem.ArenaMode; +import de.steamwar.gamecore.ArenaMode; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/Wincondition.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/Wincondition.java index b91ba2d2..22063317 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/Wincondition.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/Wincondition.java @@ -20,10 +20,12 @@ package de.steamwar.fightsystem.winconditions; import de.steamwar.fightsystem.FightSystem; -import de.steamwar.fightsystem.countdown.Countdown; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.fightsystem.states.FightState; +import de.steamwar.gamecore.state.StateDependentCountdown; +import de.steamwar.gamecore.countdown.Countdown; +import de.steamwar.gamecore.wincondition.GameResult; import lombok.Getter; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; @@ -31,13 +33,12 @@ import org.bukkit.entity.LivingEntity; import java.util.ArrayList; import java.util.List; import java.util.function.ToDoubleFunction; -import java.util.stream.Collectors; -public abstract class Wincondition { +public abstract class Wincondition implements de.steamwar.gamecore.wincondition.WinCondition { @Getter protected static WinconditionBasePercent percentWincondition = null; - protected static StateDependentCountdown timeOverCountdown = null; + protected static StateDependentCountdown timeOverCountdown = null; @Getter protected static final List printableWinconditions = new ArrayList<>(); @@ -47,6 +48,11 @@ public abstract class Wincondition { this.windescription = windescription; } + @Override + public String id() { + return windescription; + } + public static Countdown getTimeOverCountdown() { if (timeOverCountdown == null) return null; return timeOverCountdown.getCountdown(); @@ -63,12 +69,12 @@ public abstract class Wincondition { } protected void comparisonWin(ToDoubleFunction evaluate, String winMessage, String tieMessage) { - double max = Fight.teams().stream().mapToDouble(evaluate).max().orElseThrow(() -> new SecurityException("No teams present")); - List teams = Fight.teams().stream().filter(team -> evaluate.applyAsDouble(team) == max).collect(Collectors.toList()); - if (teams.size() > 1) { - win(null, tieMessage); + GameResult result = de.steamwar.gamecore.wincondition.WinConditions.winnerByScore(Fight.teams(), evaluate); + if (result instanceof GameResult.Winner winner) { + FightTeam team = winner.getWinner(); + win(team, winMessage, team.getColoredName()); } else { - win(teams.get(0), winMessage, teams.get(0).getColoredName()); + win(null, tieMessage); } } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionComparisonTimeout.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionComparisonTimeout.java index f0ef5303..7c7be2dc 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionComparisonTimeout.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionComparisonTimeout.java @@ -23,7 +23,7 @@ import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.countdown.TimeOverCountdown; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import java.util.function.ToDoubleFunction; @@ -32,7 +32,12 @@ public abstract class WinconditionComparisonTimeout extends Wincondition { public WinconditionComparisonTimeout(Winconditions wincondition, String windescription, String winMessage, ToDoubleFunction evaluate) { super(windescription); if (Config.GameModeConfig.WinConditions.contains(wincondition)) { - timeOverCountdown = new StateDependentCountdown(wincondition, FightState.Running, new TimeOverCountdown(() -> comparisonWin(evaluate, winMessage, "WIN_TIME_OVER"))); + timeOverCountdown = new StateDependentCountdown<>( + FightState.lifecycle, + true, + FightState.Running, + new TimeOverCountdown(() -> comparisonWin(evaluate, winMessage, "WIN_TIME_OVER")) + ); } } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionPoints.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionPoints.java index e70b155e..0ad482f6 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionPoints.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionPoints.java @@ -24,7 +24,7 @@ import de.steamwar.fightsystem.countdown.TimeOverCountdown; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.fightsystem.states.StateDependentListener; import de.steamwar.fightsystem.utils.Message; import de.steamwar.linkage.Linked; @@ -54,7 +54,12 @@ public class WinconditionPoints extends WinconditionBasePercent implements Liste new StateDependentListener(Winconditions.POINTS, FightState.Ingame, this); if (Config.GameModeConfig.WinConditions.contains(Winconditions.POINTS)) { - timeOverCountdown = new StateDependentCountdown(Winconditions.POINTS, FightState.Running, new TimeOverCountdown(this::timeOver)); + timeOverCountdown = new StateDependentCountdown<>( + FightState.lifecycle, + true, + FightState.Running, + new TimeOverCountdown(this::timeOver) + ); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeTechKO.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeTechKO.java index c75bb918..506ab73b 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeTechKO.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeTechKO.java @@ -20,7 +20,10 @@ package de.steamwar.fightsystem.winconditions; import de.steamwar.fightsystem.Config; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.fightsystem.FightSystem; +import de.steamwar.fightsystem.countdown.FightCountdownAdapter; +import de.steamwar.gamecore.countdown.Countdown; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; @@ -109,17 +112,17 @@ public class WinconditionTimeTechKO extends Wincondition implements Listener { }); } - private class TechKOCountdown extends Countdown { + private class TechKOCountdown extends StandardCountdown { private final FightTeam team; public TechKOCountdown(FightTeam team, int countdownTime) { - super(countdownTime, new Message("TECHKO_COUNTDOWN", team.getColoredName()), SWSound.BLOCK_NOTE_PLING, false); + super(FightSystem.getPlugin(), countdownTime, FightCountdownAdapter.presentation(new Message("TECHKO_COUNTDOWN", team.getColoredName()), SWSound.BLOCK_NOTE_PLING, false)); this.team = team; super.enable(); } @Override - public void countdownFinished() { + protected void countdownFinished() { win(Fight.getOpposite(team), "WIN_TECHKO", team.getColoredName()); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimedDamageTechKO.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimedDamageTechKO.java index cefe292a..6f1e7a54 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimedDamageTechKO.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimedDamageTechKO.java @@ -20,7 +20,10 @@ package de.steamwar.fightsystem.winconditions; import de.steamwar.fightsystem.Config; -import de.steamwar.fightsystem.countdown.Countdown; +import de.steamwar.fightsystem.FightSystem; +import de.steamwar.fightsystem.countdown.FightCountdownAdapter; +import de.steamwar.gamecore.countdown.Countdown; +import de.steamwar.gamecore.countdown.StandardCountdown; import de.steamwar.fightsystem.fight.Fight; import de.steamwar.fightsystem.fight.FightTeam; import de.steamwar.fightsystem.states.FightState; @@ -100,16 +103,16 @@ public class WinconditionTimedDamageTechKO extends Wincondition implements Print }); } - private class TechKOCountdown extends Countdown { + private class TechKOCountdown extends StandardCountdown { private final FightTeam team; public TechKOCountdown(FightTeam team, int countdownTime) { - super(countdownTime, new Message("TECHKO_COUNTDOWN", team.getColoredName()), SWSound.BLOCK_NOTE_PLING, false); + super(FightSystem.getPlugin(), countdownTime, FightCountdownAdapter.presentation(new Message("TECHKO_COUNTDOWN", team.getColoredName()), SWSound.BLOCK_NOTE_PLING, false)); this.team = team; } @Override - public void countdownFinished() { + protected void countdownFinished() { win(Fight.getOpposite(team), "WIN_TECHKO", team.getColoredName()); } } diff --git a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeout.java b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeout.java index 0778d5be..11043755 100644 --- a/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeout.java +++ b/FightSystem/FightSystem_Core/src/de/steamwar/fightsystem/winconditions/WinconditionTimeout.java @@ -22,7 +22,7 @@ package de.steamwar.fightsystem.winconditions; import de.steamwar.fightsystem.Config; import de.steamwar.fightsystem.countdown.TimeOverCountdown; import de.steamwar.fightsystem.states.FightState; -import de.steamwar.fightsystem.states.StateDependentCountdown; +import de.steamwar.gamecore.state.StateDependentCountdown; import de.steamwar.linkage.Linked; @Linked @@ -31,7 +31,12 @@ public class WinconditionTimeout extends Wincondition { public WinconditionTimeout() { super("Timeout"); if (Config.GameModeConfig.WinConditions.contains(Winconditions.TIMEOUT)) { - timeOverCountdown = new StateDependentCountdown(Winconditions.TIMEOUT, FightState.Running, new TimeOverCountdown(this::timeOver)); + timeOverCountdown = new StateDependentCountdown<>( + FightState.lifecycle, + true, + FightState.Running, + new TimeOverCountdown(this::timeOver) + ); } } diff --git a/FightSystem/FightSystem_Core/src/plugin.yml b/FightSystem/FightSystem_Core/src/plugin.yml index c2a8393b..a0ad4876 100644 --- a/FightSystem/FightSystem_Core/src/plugin.yml +++ b/FightSystem/FightSystem_Core/src/plugin.yml @@ -7,6 +7,7 @@ main: de.steamwar.fightsystem.FightSystem softdepend: - SpigotCore depend: + - GameCore - WorldEdit api-version: "1.13" @@ -28,4 +29,4 @@ commands: tpslimit: tpswarp: techhiderbug: - unrank: \ No newline at end of file + unrank: diff --git a/FightSystem/build.gradle.kts b/FightSystem/build.gradle.kts index 7079e0f1..7f43f450 100644 --- a/FightSystem/build.gradle.kts +++ b/FightSystem/build.gradle.kts @@ -30,6 +30,11 @@ dependencies { implementation(project(":FightSystem:FightSystem_Core")) } +tasks.withType().configureEach { + dependsOn(":KotlinCore:shadowJar") + dependsOn(":GameCore:jar") +} + tasks.register("HalloweenWS") { group = "run" description = "Run a Halloween 1.21 Fight Replay Server" diff --git a/GameCore/README.md b/GameCore/README.md new file mode 100644 index 00000000..87a19b49 --- /dev/null +++ b/GameCore/README.md @@ -0,0 +1,35 @@ +# GameCore + +GameCore is the Kotlin runtime library shared by SteamWar game-mode plugins. +Its implementation is based on FightSystem, the most exhaustive game mode, and +contains the mechanics that do not depend on a specific arena: + +- restartable Bukkit countdown scheduling, FightSystem milestone behavior, + message/sound/level presentation policy, and shared skip behavior; +- generic team contracts, team lookup, readiness, and opposite-team handling; +- reusable winner/draw results and score/last-remaining evaluation; +- the complete state-dependent lifecycle (one-shots, tasks, listeners, commands, + and countdowns); +- the FightSystem arena modes and typed JVM runtime properties; +- observable, generic game-state transitions. + +GameCore intentionally does not define a game-state enum. Each consumer supplies +its own enum to `GameStateMachine` and the generic state-dependent classes, so +each game mode controls its valid states while sharing the lifecycle behavior. + +```kotlin +enum class State { WAITING, RUNNING, END } + +val lifecycle = GameStateMachine(State.WAITING) +StateDependentListener( + lifecycle, + true, + setOf(State.RUNNING), + plugin, + ingameListener, +) +``` + +FightSystem, MissileWars, TNTLeague, and TowerRun retain their own messages, +inventories, listeners, schematics, and concrete win rules. They depend on the +GameCore plugin at runtime; GameCore itself depends on KotlinCore. diff --git a/GameCore/build.gradle.kts b/GameCore/build.gradle.kts new file mode 100644 index 00000000..a53ef64b --- /dev/null +++ b/GameCore/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +plugins { + steamwar.kotlin +} + +dependencies { + compileOnly(libs.paperapi) + compileOnly(project(":KotlinCore", "default")) + + testImplementation(libs.junit) + testImplementation(libs.hamcrest) +} diff --git a/GameCore/src/de/steamwar/gamecore/ArenaMode.kt b/GameCore/src/de/steamwar/gamecore/ArenaMode.kt new file mode 100644 index 00000000..da01fa1d --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/ArenaMode.kt @@ -0,0 +1,69 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore + +import de.steamwar.gamecore.config.GameRuntimeProperties +import java.util.Collections +import java.util.EnumSet + +/** + * Canonical arena mode model, ported from FightSystem. + */ +enum class ArenaMode { + NORMAL, + EVENT, + TEST, + CHECK, + PREPARE, + REPLAY; + + companion object { + @JvmField val All: Set = immutable(EnumSet.allOf(ArenaMode::class.java)) + + @JvmField val Normal: Set = immutable(EnumSet.of(NORMAL)) + @JvmField val Check: Set = immutable(EnumSet.of(CHECK)) + @JvmField val Event: Set = immutable(EnumSet.of(EVENT)) + @JvmField val Test: Set = immutable(EnumSet.of(TEST, CHECK)) + @JvmField val Prepare: Set = immutable(EnumSet.of(PREPARE)) + @JvmField val Replay: Set = immutable(EnumSet.of(REPLAY, TEST)) + + @JvmField val AntiReplay: Set = complement(REPLAY) + @JvmField val AntiTest: Set = complement(TEST, CHECK) + @JvmField val AntiEvent: Set = complement(EVENT) + @JvmField val AntiTestCheckPrepare: Set = complement(TEST, CHECK, PREPARE) + @JvmField val AntiPrepare: Set = complement(PREPARE) + @JvmField val VariableTeams: Set = complement(EVENT, REPLAY, CHECK) + @JvmField val ManualTeams: Set = complement(EVENT, REPLAY) + @JvmField val RankedEvent: Set = immutable(EnumSet.of(EVENT, REPLAY)) + @JvmField val Restartable: Set = immutable(EnumSet.of(NORMAL, TEST)) + @JvmField val NotRestartable: Set = immutable(EnumSet.of(EVENT, REPLAY)) + @JvmField val SoloLeader: Set = immutable(EnumSet.of(TEST, CHECK, PREPARE)) + @JvmField val NotOnBau: Set = complement(TEST, CHECK, PREPARE, REPLAY) + @JvmField val SeriousFight: Set = complement(TEST, CHECK, REPLAY) + + @JvmStatic + fun from(properties: GameRuntimeProperties): ArenaMode = when { + properties.checkSchematicId != 0 -> CHECK + properties.prepareSchematicId != 0 -> PREPARE + properties.fightId >= 1 -> EVENT + properties.fightId == -1 -> TEST + properties.replayId != 0 -> REPLAY + else -> NORMAL + } + + private fun complement(vararg modes: ArenaMode): Set = + immutable(EnumSet.complementOf(EnumSet.copyOf(modes.toSet()))) + + private fun immutable(modes: EnumSet): Set = + Collections.unmodifiableSet(modes) + } +} diff --git a/GameCore/src/de/steamwar/gamecore/GameCore.kt b/GameCore/src/de/steamwar/gamecore/GameCore.kt new file mode 100644 index 00000000..44eb6454 --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/GameCore.kt @@ -0,0 +1,22 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore + +import org.bukkit.plugin.java.JavaPlugin + +/** + * Runtime boundary for code shared by the SteamWar game modes. + * + * GameCore intentionally has no game lifecycle of its own. Depending plugins + * provide the concrete teams, states, messages and win effects. + */ +class GameCore : JavaPlugin() diff --git a/GameCore/src/de/steamwar/gamecore/config/GameRuntimeProperties.kt b/GameCore/src/de/steamwar/gamecore/config/GameRuntimeProperties.kt new file mode 100644 index 00000000..56cac0e5 --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/config/GameRuntimeProperties.kt @@ -0,0 +1,63 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore.config + +import java.util.Properties +import java.util.UUID + +/** + * Typed view of every JVM system property historically consumed by FightSystem. + * + * Other game modes use the same names and parser, so launch behavior is + * consistent across the complete GameCore family. + */ +data class GameRuntimeProperties( + val checkSchematicId: Int, + val prepareSchematicId: Int, + val replayId: Int, + val configFile: String, + val fightId: Int, + val blueLeader: UUID?, + val redLeader: UUID?, +) { + val isReplayServer: Boolean + get() = replayId < 0 + + companion object { + const val CHECK_SCHEMATIC_ID = "checkSchemID" + const val PREPARE_SCHEMATIC_ID = "prepareSchemID" + const val REPLAY_ID = "replay" + const val CONFIG_FILE = "config" + const val FIGHT_ID = "fightID" + const val BLUE_LEADER = "blueLeader" + const val RED_LEADER = "redLeader" + + @JvmStatic + @JvmOverloads + fun load(properties: Properties = System.getProperties()): GameRuntimeProperties = + GameRuntimeProperties( + checkSchematicId = properties.int(CHECK_SCHEMATIC_ID), + prepareSchematicId = properties.int(PREPARE_SCHEMATIC_ID), + replayId = properties.int(REPLAY_ID), + configFile = properties.getProperty(CONFIG_FILE, "config.yml"), + fightId = properties.int(FIGHT_ID), + blueLeader = properties.uuid(BLUE_LEADER), + redLeader = properties.uuid(RED_LEADER), + ) + + private fun Properties.int(name: String): Int = + getProperty(name, "0").toInt() + + private fun Properties.uuid(name: String): UUID? = + getProperty(name)?.let(UUID::fromString) + } +} diff --git a/GameCore/src/de/steamwar/gamecore/countdown/Countdown.kt b/GameCore/src/de/steamwar/gamecore/countdown/Countdown.kt new file mode 100644 index 00000000..0db0d754 --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/countdown/Countdown.kt @@ -0,0 +1,242 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore.countdown + +import org.bukkit.plugin.Plugin +import org.bukkit.scheduler.BukkitTask +import java.util.concurrent.CopyOnWriteArraySet +import java.util.function.IntConsumer + +/** + * Common, restartable countdown implementation. + * + * The engine owns scheduling and cancellation. Game modes only implement + * presentation in [onTick] and their transition in [onFinished]. + */ +abstract class Countdown @JvmOverloads constructor( + private val plugin: Plugin, + val totalTime: Int, + private val initialDelayTicks: Long = 20, + private val periodTicks: Long = 20, + private val showInitialValue: Boolean = true, +) { + init { + require(totalTime >= 0) { "totalTime must not be negative" } + require(initialDelayTicks >= 0) { "initialDelayTicks must not be negative" } + require(periodTicks > 0) { "periodTicks must be positive" } + } + + @Volatile + var timeLeft: Int = totalTime + private set + + @Volatile + private var enabled = false + + private var task: BukkitTask? = null + + val isRunning: Boolean + get() = enabled + + fun enable() { + disable() + timeLeft = totalTime + enabled = true + activeCountdowns.add(this) + onStarted() + + if (!enabled) return + + if (showInitialValue) { + onTick(timeLeft) + } + + if (!enabled) return + + if (totalTime == 0) { + finish() + return + } + + task = plugin.server.scheduler.runTaskTimer( + plugin, + Runnable { advance() }, + initialDelayTicks, + periodTicks, + ) + } + + fun disable() { + val wasEnabled = enabled + enabled = false + val currentTask = task + task = null + currentTask?.cancel() + activeCountdowns.remove(this) + if (wasEnabled) { + onStopped() + } + } + + fun reset() { + timeLeft = totalTime + } + + fun setRemainingTime(time: Int) { + timeLeft = time.coerceIn(0, totalTime) + } + + fun decreaseBy(time: Int) { + require(time >= 0) { "time must not be negative" } + if (!enabled) return + timeLeft = (timeLeft - time).coerceAtLeast(0) + onTick(timeLeft) + if (timeLeft == 0 && isRunning) { + finish() + } + } + + private fun advance() { + if (!enabled) return + timeLeft = (timeLeft - 1).coerceAtLeast(0) + onTick(timeLeft) + if (timeLeft == 0) { + finish() + } + } + + private fun finish() { + if (!enabled) return + disable() + onFinished() + } + + protected open fun onStarted() = Unit + + protected open fun onStopped() = Unit + + protected open fun onTick(timeLeft: Int) = Unit + + protected abstract fun onFinished() + + companion object { + private val activeCountdowns = CopyOnWriteArraySet() + + /** + * Advances every running countdown to the next countdown that finishes. + */ + @JvmStatic + fun skipActive(): Boolean { + val advanceBy = activeCountdowns.minOfOrNull { it.timeLeft } ?: return false + activeCountdowns.toList().forEach { it.decreaseBy(advanceBy) } + return true + } + } +} + +enum class CountdownMessage(val key: String) { + MINUTES("COUNTDOWN_MINUTES"), + SECONDS("COUNTDOWN_SECONDS"), + SECOND("COUNTDOWN_SECOND"), +} + +data class CountdownMilestone( + val message: CountdownMessage, + val displayTime: Int, +) + +object CountdownMilestones { + @JvmStatic + fun at(timeLeft: Int): CountdownMilestone? = when (timeLeft) { + 900, 600, 300, 180, 120 -> + CountdownMilestone(CountdownMessage.MINUTES, timeLeft / 60) + + 60, 30, 20, 15, 10, 5, 4, 3, 2 -> + CountdownMilestone(CountdownMessage.SECONDS, timeLeft) + + 1 -> + CountdownMilestone(CountdownMessage.SECOND, timeLeft) + + else -> null + } +} + +/** + * FightSystem's complete countdown behavior. Consumers bind their game-specific + * transports while GameCore owns lifecycle, milestones, and presentation. + */ +abstract class StandardCountdown @JvmOverloads constructor( + plugin: Plugin, + totalTime: Int, + private val presentation: CountdownPresentation, + initialDelayTicks: Long = 20, + periodTicks: Long = 20, + showInitialValue: Boolean = true, +) : Countdown( + plugin, + totalTime, + initialDelayTicks, + periodTicks, + showInitialValue, +) { + final override fun onTick(timeLeft: Int) { + presentation.update(timeLeft) + } + + final override fun onFinished() { + presentation.finished() + countdownFinished() + } + + protected abstract fun countdownFinished() +} + +fun interface CountdownMessageSink { + fun display(message: CountdownMessage, displayTime: Int) +} + +fun interface CountdownSoundSink { + fun play(pitch: Float) +} + +/** + * Complete standard presentation policy. Game modes only bind their actual + * message, sound, and level transports. + */ +class CountdownPresentation constructor( + private val messageSink: CountdownMessageSink, + private val tickSoundSink: CountdownSoundSink?, + private val finishedSoundSink: CountdownSoundSink?, + private val levelSink: IntConsumer?, + private val tickSoundOnMinutes: Boolean, +) { + @JvmOverloads + constructor( + messageSink: CountdownMessageSink, + soundSink: CountdownSoundSink? = null, + levelSink: IntConsumer? = null, + ) : this(messageSink, soundSink, soundSink, levelSink, false) + + fun update(timeLeft: Int) { + CountdownMilestones.at(timeLeft)?.let { + if (tickSoundOnMinutes || it.message != CountdownMessage.MINUTES) { + tickSoundSink?.play(1.0f) + } + messageSink.display(it.message, it.displayTime) + } + levelSink?.accept(timeLeft) + } + + fun finished() { + finishedSoundSink?.play(2.0f) + } +} diff --git a/GameCore/src/de/steamwar/gamecore/state/GameStateMachine.kt b/GameCore/src/de/steamwar/gamecore/state/GameStateMachine.kt new file mode 100644 index 00000000..91db16d7 --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/state/GameStateMachine.kt @@ -0,0 +1,74 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore.state + +import java.util.Collections +import java.util.LinkedHashMap +import java.util.concurrent.CopyOnWriteArrayList + +fun interface GameStateListener> { + fun onStateChange(oldState: S, newState: S) +} + +/** + * Small state holder used by game modes instead of duplicating listener lists + * and transition notification code. + */ +class GameStateMachine>(initialState: S) { + private val listeners = CopyOnWriteArrayList>() + private val stateDependents = LinkedHashMap, Boolean>() + + @Volatile + var currentState: S = initialState + private set + + val stateDependentFeatures: Map, Boolean> + get() = Collections.unmodifiableMap(LinkedHashMap(stateDependents)) + + fun registerStateDependent(stateDependent: IStateDependent) { + if (stateDependent.enabled().isEmpty()) return + val enabled = currentState in stateDependent.enabled() + stateDependents[stateDependent] = enabled + if (enabled) stateDependent.enable() + } + + fun transitionTo(newState: S) { + val oldState = currentState + if (oldState == newState) return + currentState = newState + + stateDependents.entries.toList().forEach { (feature, wasEnabled) -> + val shouldBeEnabled = newState in feature.enabled() + if (shouldBeEnabled && !wasEnabled) { + feature.enable() + stateDependents[feature] = true + } else if (!shouldBeEnabled && wasEnabled) { + feature.disable() + stateDependents[feature] = false + } + } + + listeners.forEach { it.onStateChange(oldState, newState) } + } + + fun reset(initialState: S) { + transitionTo(initialState) + } + + fun addListener(listener: GameStateListener) { + listeners.add(listener) + } + + fun removeListener(listener: GameStateListener) { + listeners.remove(listener) + } +} diff --git a/GameCore/src/de/steamwar/gamecore/state/StateDependent.kt b/GameCore/src/de/steamwar/gamecore/state/StateDependent.kt new file mode 100644 index 00000000..73c360a3 --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/state/StateDependent.kt @@ -0,0 +1,194 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore.state + +import de.steamwar.gamecore.ArenaMode +import de.steamwar.gamecore.countdown.Countdown +import org.bukkit.command.CommandExecutor +import org.bukkit.event.HandlerList +import org.bukkit.event.Listener +import org.bukkit.plugin.Plugin +import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.scheduler.BukkitTask +import java.util.concurrent.ConcurrentHashMap + +interface IStateDependent> { + fun enabled(): Set + + fun enable() + + fun disable() +} + +abstract class StateDependent> protected constructor( + protected val lifecycle: GameStateMachine, + private val active: Boolean, + private val enabledStates: Set, +) : IStateDependent { + protected constructor( + lifecycle: GameStateMachine, + modes: Set, + arenaMode: ArenaMode, + enabledStates: Set, + ) : this(lifecycle, arenaMode in modes, enabledStates) + + final override fun enabled(): Set = enabledStates + + fun register() { + if (active) lifecycle.registerStateDependent(this) + } +} + +open class OneShotStateDependent>( + lifecycle: GameStateMachine, + active: Boolean, + enabledStates: Set, + private val runnable: Runnable, +) : StateDependent(lifecycle, active, enabledStates) { + constructor( + lifecycle: GameStateMachine, + modes: Set, + arenaMode: ArenaMode, + enabledStates: Set, + runnable: Runnable, + ) : this(lifecycle, arenaMode in modes, enabledStates, runnable) + + init { + register() + } + + override fun enable() = runnable.run() + + override fun disable() = Unit +} + +open class StateDependentTask>( + lifecycle: GameStateMachine, + active: Boolean, + enabledStates: Set, + private val plugin: Plugin, + private val runnable: Runnable, + private val delay: Long, + private val period: Long, +) : StateDependent(lifecycle, active, enabledStates) { + constructor( + lifecycle: GameStateMachine, + modes: Set, + arenaMode: ArenaMode, + enabledStates: Set, + plugin: Plugin, + runnable: Runnable, + delay: Long, + period: Long, + ) : this(lifecycle, arenaMode in modes, enabledStates, plugin, runnable, delay, period) + + private var task: BukkitTask? = null + + init { + register() + } + + override fun enable() { + task?.cancel() + task = plugin.server.scheduler.runTaskTimer(plugin, runnable, delay, period) + } + + override fun disable() { + task?.cancel() + task = null + } +} + +open class StateDependentListener>( + lifecycle: GameStateMachine, + active: Boolean, + enabledStates: Set, + private val plugin: Plugin, + private val listener: Listener, +) : StateDependent(lifecycle, active, enabledStates) { + constructor( + lifecycle: GameStateMachine, + modes: Set, + arenaMode: ArenaMode, + enabledStates: Set, + plugin: Plugin, + listener: Listener, + ) : this(lifecycle, arenaMode in modes, enabledStates, plugin, listener) + + init { + register() + } + + override fun enable() { + plugin.server.pluginManager.registerEvents(listener, plugin) + } + + override fun disable() { + HandlerList.unregisterAll(listener) + } +} + +open class StateDependentCommand>( + lifecycle: GameStateMachine, + active: Boolean, + enabledStates: Set, + plugin: JavaPlugin, + name: String, + private val executor: CommandExecutor, + private val unavailable: CommandExecutor, +) : StateDependent(lifecycle, active, enabledStates) { + private val command = requireNotNull(plugin.getCommand(name)) { + "Command '$name' is not declared by ${plugin.name}" + } + + init { + disable() + register() + } + + override fun enable() { + command.setExecutor(executor) + } + + override fun disable() { + command.setExecutor(unavailable) + } +} + +open class StateDependentCountdown>( + lifecycle: GameStateMachine, + active: Boolean, + enabledStates: Set, + open val countdown: Countdown, +) : StateDependent(lifecycle, active, enabledStates) { + init { + register() + } + + override fun enable() { + mainCountdowns[lifecycle] = countdown + countdown.enable() + } + + override fun disable() { + countdown.disable() + mainCountdowns.remove(lifecycle, countdown) + } + + companion object { + private val mainCountdowns = ConcurrentHashMap, Countdown>() + + @JvmStatic + fun getMainCountdown(lifecycle: GameStateMachine<*>): Countdown? = + mainCountdowns[lifecycle] + } +} diff --git a/GameCore/src/de/steamwar/gamecore/team/GameTeam.kt b/GameCore/src/de/steamwar/gamecore/team/GameTeam.kt new file mode 100644 index 00000000..bf925c9b --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/team/GameTeam.kt @@ -0,0 +1,60 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore.team + +import java.util.Locale + +/** + * Minimal team contract shared by all game modes. + * + * Membership side effects (teleports, inventories, scoreboards) deliberately + * stay in the game mode. This contract centralizes lookup and team relations. + */ +interface GameTeam { + fun teamId(): String + + fun members(): Collection + + fun leader(): M? = null + + fun ready(): Boolean = false + + fun contains(member: M): Boolean = members().contains(member) +} + +class TeamRegistry, M>(teams: Collection) { + private val teamsById = LinkedHashMap() + + init { + teams.forEach { team -> + require(teamsById.put(team.teamId().lowercase(Locale.ROOT), team) == null) { + "Duplicate team id: ${team.teamId()}" + } + } + } + + val teams: Collection + get() = teamsById.values.toList() + + fun findTeam(member: M): T? = teamsById.values.firstOrNull { it.contains(member) } + + fun findById(id: String): T? = teamsById[id.lowercase(Locale.ROOT)] + + fun allReady(): Boolean = + teamsById.isNotEmpty() && teamsById.values.all { it.ready() } + + fun opposite(team: T): T { + require(teamsById.size == 2) { "opposite() requires exactly two teams" } + require(teamsById.values.any { it === team }) { "Unknown team: ${team.teamId()}" } + return teamsById.values.first { it !== team } + } +} diff --git a/GameCore/src/de/steamwar/gamecore/wincondition/WinCondition.kt b/GameCore/src/de/steamwar/gamecore/wincondition/WinCondition.kt new file mode 100644 index 00000000..b68c84fd --- /dev/null +++ b/GameCore/src/de/steamwar/gamecore/wincondition/WinCondition.kt @@ -0,0 +1,49 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2026 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +package de.steamwar.gamecore.wincondition + +import java.util.function.ToDoubleFunction + +interface WinCondition { + fun id(): String + + fun active(): Boolean = true +} + +sealed interface GameResult { + data class Winner(val winner: T) : GameResult + + data object Draw : GameResult +} + +object WinConditions { + /** + * Selects the unique highest scoring entry or returns a draw. + */ + @JvmStatic + fun winnerByScore( + entries: Collection, + score: ToDoubleFunction, + ): GameResult { + require(entries.isNotEmpty()) { "At least one entry is required" } + val highestScore = entries.maxOf(score::applyAsDouble) + val winners = entries.filter { score.applyAsDouble(it) == highestScore } + return if (winners.size == 1) GameResult.Winner(winners.single()) else GameResult.Draw + } + + /** + * Selects the only remaining entry or returns a draw. + */ + @JvmStatic + fun lastRemaining(entries: Collection): GameResult = + if (entries.size == 1) GameResult.Winner(entries.single()) else GameResult.Draw +} diff --git a/GameCore/src/plugin.yml b/GameCore/src/plugin.yml new file mode 100644 index 00000000..07fde934 --- /dev/null +++ b/GameCore/src/plugin.yml @@ -0,0 +1,7 @@ +name: GameCore +version: '1.0.0' +main: de.steamwar.gamecore.GameCore +load: POSTWORLD +api-version: '1.13' +depend: + - KotlinCore diff --git a/GameCore/testsrc/de/steamwar/gamecore/ArenaModeTest.kt b/GameCore/testsrc/de/steamwar/gamecore/ArenaModeTest.kt new file mode 100644 index 00000000..735f0ddb --- /dev/null +++ b/GameCore/testsrc/de/steamwar/gamecore/ArenaModeTest.kt @@ -0,0 +1,38 @@ +package de.steamwar.gamecore + +import de.steamwar.gamecore.config.GameRuntimeProperties +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import java.util.Properties + +class ArenaModeTest { + @Test + fun resolvesEveryFightSystemArenaMode() { + assertThat(mode(), equalTo(ArenaMode.NORMAL)) + assertThat(mode(GameRuntimeProperties.FIGHT_ID to "42"), equalTo(ArenaMode.EVENT)) + assertThat(mode(GameRuntimeProperties.FIGHT_ID to "-1"), equalTo(ArenaMode.TEST)) + assertThat(mode(GameRuntimeProperties.REPLAY_ID to "42"), equalTo(ArenaMode.REPLAY)) + assertThat(mode(GameRuntimeProperties.PREPARE_SCHEMATIC_ID to "42"), equalTo(ArenaMode.PREPARE)) + assertThat(mode(GameRuntimeProperties.CHECK_SCHEMATIC_ID to "42"), equalTo(ArenaMode.CHECK)) + } + + @Test + fun keepsFightSystemResolutionPrecedence() { + val resolved = mode( + GameRuntimeProperties.CHECK_SCHEMATIC_ID to "1", + GameRuntimeProperties.PREPARE_SCHEMATIC_ID to "2", + GameRuntimeProperties.FIGHT_ID to "3", + GameRuntimeProperties.REPLAY_ID to "4", + ) + + assertThat(resolved, equalTo(ArenaMode.CHECK)) + } + + private fun mode(vararg values: Pair): ArenaMode { + val properties = Properties().apply { + values.forEach { (name, value) -> setProperty(name, value) } + } + return ArenaMode.from(GameRuntimeProperties.load(properties)) + } +} diff --git a/GameCore/testsrc/de/steamwar/gamecore/config/GameRuntimePropertiesTest.kt b/GameCore/testsrc/de/steamwar/gamecore/config/GameRuntimePropertiesTest.kt new file mode 100644 index 00000000..7c0c972c --- /dev/null +++ b/GameCore/testsrc/de/steamwar/gamecore/config/GameRuntimePropertiesTest.kt @@ -0,0 +1,50 @@ +package de.steamwar.gamecore.config + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue +import org.junit.Test +import java.util.Properties +import java.util.UUID + +class GameRuntimePropertiesTest { + @Test + fun loadsEveryFightSystemRuntimeProperty() { + val blueLeader = UUID.randomUUID() + val redLeader = UUID.randomUUID() + val properties = Properties().apply { + setProperty(GameRuntimeProperties.CHECK_SCHEMATIC_ID, "11") + setProperty(GameRuntimeProperties.PREPARE_SCHEMATIC_ID, "12") + setProperty(GameRuntimeProperties.REPLAY_ID, "-13") + setProperty(GameRuntimeProperties.CONFIG_FILE, "event.yml") + setProperty(GameRuntimeProperties.FIGHT_ID, "14") + setProperty(GameRuntimeProperties.BLUE_LEADER, blueLeader.toString()) + setProperty(GameRuntimeProperties.RED_LEADER, redLeader.toString()) + } + + val runtime = GameRuntimeProperties.load(properties) + + assertThat(runtime.checkSchematicId, equalTo(11)) + assertThat(runtime.prepareSchematicId, equalTo(12)) + assertThat(runtime.replayId, equalTo(-13)) + assertThat(runtime.configFile, equalTo("event.yml")) + assertThat(runtime.fightId, equalTo(14)) + assertThat(runtime.blueLeader, equalTo(blueLeader)) + assertThat(runtime.redLeader, equalTo(redLeader)) + assertThat(runtime.isReplayServer, equalTo(true)) + } + + @Test + fun usesFightSystemDefaults() { + val runtime = GameRuntimeProperties.load(Properties()) + + assertThat(runtime.checkSchematicId, equalTo(0)) + assertThat(runtime.prepareSchematicId, equalTo(0)) + assertThat(runtime.replayId, equalTo(0)) + assertThat(runtime.configFile, equalTo("config.yml")) + assertThat(runtime.fightId, equalTo(0)) + assertThat(runtime.blueLeader, nullValue()) + assertThat(runtime.redLeader, nullValue()) + assertThat(runtime.isReplayServer, equalTo(false)) + } +} diff --git a/GameCore/testsrc/de/steamwar/gamecore/countdown/CountdownMilestonesTest.kt b/GameCore/testsrc/de/steamwar/gamecore/countdown/CountdownMilestonesTest.kt new file mode 100644 index 00000000..86ef287d --- /dev/null +++ b/GameCore/testsrc/de/steamwar/gamecore/countdown/CountdownMilestonesTest.kt @@ -0,0 +1,56 @@ +package de.steamwar.gamecore.countdown + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.nullValue +import org.junit.Test +import java.util.function.IntConsumer + +class CountdownMilestonesTest { + @Test + fun preservesFightSystemMilestones() { + assertThat( + CountdownMilestones.at(900), + equalTo(CountdownMilestone(CountdownMessage.MINUTES, 15)), + ) + assertThat( + CountdownMilestones.at(60), + equalTo(CountdownMilestone(CountdownMessage.SECONDS, 60)), + ) + assertThat( + CountdownMilestones.at(1), + equalTo(CountdownMilestone(CountdownMessage.SECOND, 1)), + ) + assertThat(CountdownMilestones.at(0), nullValue()) + assertThat(CountdownMilestones.at(59), nullValue()) + } + + @Test + fun presentationOwnsMessagesSoundsAndLevelUpdates() { + val messages = mutableListOf() + val pitches = mutableListOf() + val levels = mutableListOf() + val presentation = CountdownPresentation( + CountdownMessageSink { message, displayTime -> + messages += CountdownMilestone(message, displayTime) + }, + CountdownSoundSink(pitches::add), + IntConsumer(levels::add), + ) + + presentation.update(120) + presentation.update(10) + presentation.finished() + + assertThat( + messages, + contains( + CountdownMilestone(CountdownMessage.MINUTES, 2), + CountdownMilestone(CountdownMessage.SECONDS, 10), + ), + ) + assertThat(pitches, contains(1.0f, 2.0f)) + assertThat(levels, contains(120, 10)) + } +} diff --git a/GameCore/testsrc/de/steamwar/gamecore/state/GameStateMachineTest.kt b/GameCore/testsrc/de/steamwar/gamecore/state/GameStateMachineTest.kt new file mode 100644 index 00000000..d31cc1ca --- /dev/null +++ b/GameCore/testsrc/de/steamwar/gamecore/state/GameStateMachineTest.kt @@ -0,0 +1,53 @@ +package de.steamwar.gamecore.state + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.junit.Test + +class GameStateMachineTest { + private enum class ConsumerState { + WAITING, + RUNNING, + END, + } + + @Test + fun transitionsNotifyListenersOnce() { + val state = GameStateMachine(ConsumerState.WAITING) + val transitions = mutableListOf>() + state.addListener { oldState, newState -> transitions.add(oldState to newState) } + + state.transitionTo(ConsumerState.RUNNING) + state.transitionTo(ConsumerState.RUNNING) + + assertThat(state.currentState, equalTo(ConsumerState.RUNNING)) + assertThat(transitions, contains(ConsumerState.WAITING to ConsumerState.RUNNING)) + } + + @Test + fun consumerEnumDrivesRegisteredStateDependents() { + val lifecycle = GameStateMachine(ConsumerState.WAITING) + var enables = 0 + var disables = 0 + val feature = object : IStateDependent { + override fun enabled() = setOf(ConsumerState.RUNNING) + + override fun enable() { + enables++ + } + + override fun disable() { + disables++ + } + } + + lifecycle.registerStateDependent(feature) + lifecycle.transitionTo(ConsumerState.RUNNING) + lifecycle.transitionTo(ConsumerState.END) + + assertThat(enables, equalTo(1)) + assertThat(disables, equalTo(1)) + assertThat(lifecycle.currentState, equalTo(ConsumerState.END)) + } +} diff --git a/GameCore/testsrc/de/steamwar/gamecore/team/TeamRegistryTest.kt b/GameCore/testsrc/de/steamwar/gamecore/team/TeamRegistryTest.kt new file mode 100644 index 00000000..2144d494 --- /dev/null +++ b/GameCore/testsrc/de/steamwar/gamecore/team/TeamRegistryTest.kt @@ -0,0 +1,25 @@ +package de.steamwar.gamecore.team + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test + +class TeamRegistryTest { + private data class Team( + private val id: String, + private val players: Collection, + ) : GameTeam { + override fun teamId() = id + override fun members() = players + } + + @Test + fun findsMembersAndOppositeTeam() { + val red = Team("red", listOf("Ada")) + val blue = Team("blue", listOf("Linus")) + val registry = TeamRegistry(listOf(red, blue)) + + assertThat(registry.findTeam("Ada"), equalTo(red)) + assertThat(registry.opposite(red), equalTo(blue)) + } +} diff --git a/GameCore/testsrc/de/steamwar/gamecore/wincondition/WinConditionsTest.kt b/GameCore/testsrc/de/steamwar/gamecore/wincondition/WinConditionsTest.kt new file mode 100644 index 00000000..2271e118 --- /dev/null +++ b/GameCore/testsrc/de/steamwar/gamecore/wincondition/WinConditionsTest.kt @@ -0,0 +1,28 @@ +package de.steamwar.gamecore.wincondition + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import java.util.function.ToDoubleFunction + +class WinConditionsTest { + @Test + fun selectsUniqueHighestScore() { + val result = WinConditions.winnerByScore( + listOf("red" to 4.0, "blue" to 7.0), + ToDoubleFunction { it.second }, + ) + + assertThat(result, equalTo(GameResult.Winner("blue" to 7.0))) + } + + @Test + fun equalScoresAreADraw() { + val result = WinConditions.winnerByScore( + listOf("red" to 7.0, "blue" to 7.0), + ToDoubleFunction { it.second }, + ) + + assertThat(result, equalTo(GameResult.Draw)) + } +} diff --git a/MissileWars/build.gradle.kts b/MissileWars/build.gradle.kts index f347a5ec..1946183b 100644 --- a/MissileWars/build.gradle.kts +++ b/MissileWars/build.gradle.kts @@ -24,6 +24,7 @@ plugins { dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) + compileOnly(project(":GameCore", "default")) compileOnly(project(":SpigotCore", "default")) compileOnly(libs.paperapi) @@ -36,6 +37,8 @@ tasks.register("MissileWars21") { group = "run" description = "Run a 1.21 Dev MissileWars" dependsOn(":SpigotCore:shadowJar") + dependsOn(":KotlinCore:shadowJar") + dependsOn(":GameCore:jar") dependsOn(":MissileWars:jar") template = "MissileWars" worldName = "Great_Wall" diff --git a/MissileWars/src/de/steamwar/misslewars/Config.java b/MissileWars/src/de/steamwar/misslewars/Config.java index 4fa354fe..8fa5d7bb 100644 --- a/MissileWars/src/de/steamwar/misslewars/Config.java +++ b/MissileWars/src/de/steamwar/misslewars/Config.java @@ -19,6 +19,8 @@ package de.steamwar.misslewars; +import de.steamwar.gamecore.ArenaMode; +import de.steamwar.gamecore.config.GameRuntimeProperties; import de.steamwar.sql.Event; import de.steamwar.sql.EventFight; import de.steamwar.sql.Team; @@ -36,6 +38,9 @@ public class Config { private Config() { } + public static final GameRuntimeProperties RuntimeProperties = GameRuntimeProperties.load(); + public static final ArenaMode mode = ArenaMode.from(RuntimeProperties); + public static final int ArenaMinX; public static final int ArenaMaxX; public static final int ArenaMinY; @@ -108,17 +113,13 @@ public class Config { BluePortalZ = blue.getInt("PortalZ"); BlueSpawn = new Location(Bukkit.getWorlds().get(0), blue.getDouble("SpawnX"), blue.getDouble("SpawnY"), blue.getDouble("SpawnZ"), (float) blue.getDouble("SpawnYaw"), (float) blue.getDouble("SpawnPitch")); - String blueLeader = System.getProperty("blueLeader", null); - String redLeader = System.getProperty("redLeader", null); - if (blueLeader != null) BlueLeader = UUID.fromString(blueLeader); - else BlueLeader = null; - if (redLeader != null) RedLeader = UUID.fromString(redLeader); - else RedLeader = null; + BlueLeader = RuntimeProperties.getBlueLeader(); + RedLeader = RuntimeProperties.getRedLeader(); Barrier = config.getBoolean("Barrier", false); Space = config.getBoolean("Space", false); - EventKampfID = Integer.parseInt(System.getProperty("fightID", "0")); + EventKampfID = RuntimeProperties.getFightId(); if (EventKampfID >= 1) { EventKampf = EventFight.byId(EventKampfID); if (EventKampf == null) { @@ -163,7 +164,7 @@ public class Config { } public static boolean isEvent() { - return EventKampfID >= 1; + return mode == ArenaMode.EVENT; } public static boolean isChallenge() { @@ -171,6 +172,6 @@ public class Config { } public static boolean test() { - return EventKampfID == -1; + return mode == ArenaMode.TEST; } } diff --git a/MissileWars/src/de/steamwar/misslewars/FightState.java b/MissileWars/src/de/steamwar/misslewars/FightState.java index ef1cf72b..153cfd10 100644 --- a/MissileWars/src/de/steamwar/misslewars/FightState.java +++ b/MissileWars/src/de/steamwar/misslewars/FightState.java @@ -1,26 +1,11 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package de.steamwar.misslewars; +import de.steamwar.gamecore.state.GameStateMachine; + public enum FightState { - WAITING, // waiting for at least 2 players - FIGHTING, // no one has won - END // fight ended for some reason + WAITING, + FIGHTING, + END; + + public static final GameStateMachine lifecycle = new GameStateMachine<>(WAITING); } diff --git a/MissileWars/src/de/steamwar/misslewars/MWTeam.java b/MissileWars/src/de/steamwar/misslewars/MWTeam.java index 76c87213..31145727 100644 --- a/MissileWars/src/de/steamwar/misslewars/MWTeam.java +++ b/MissileWars/src/de/steamwar/misslewars/MWTeam.java @@ -34,7 +34,7 @@ import org.bukkit.scoreboard.Team; import java.util.*; -public class MWTeam { +public class MWTeam implements de.steamwar.gamecore.team.GameTeam { public static final ItemStack bow = new ItemStack(Material.BOW); static { @@ -226,8 +226,7 @@ public class MWTeam { } private MWTeam enemy() { - if (this == MissileWars.getRedTeam()) return MissileWars.getBlueTeam(); - return MissileWars.getRedTeam(); + return MissileWars.getOpposite(this); } public String getColorCode() { @@ -246,6 +245,26 @@ public class MWTeam { return color + teamName; } + @Override + public String teamId() { + return teamName; + } + + @Override + public Collection members() { + return Collections.unmodifiableList(players); + } + + @Override + public Player leader() { + return players.peekFirst(); + } + + @Override + public boolean contains(Player member) { + return hasPlayer(member); + } + public static void removeInvitations(Player p) { MissileWars.getRedTeam().openInvitations.remove(p); MissileWars.getBlueTeam().openInvitations.remove(p); diff --git a/MissileWars/src/de/steamwar/misslewars/MissileWars.java b/MissileWars/src/de/steamwar/misslewars/MissileWars.java index 7734cc74..a7d0baed 100644 --- a/MissileWars/src/de/steamwar/misslewars/MissileWars.java +++ b/MissileWars/src/de/steamwar/misslewars/MissileWars.java @@ -21,6 +21,7 @@ package de.steamwar.misslewars; import de.steamwar.linkage.AbstractLinker; import de.steamwar.linkage.SpigotLinker; +import de.steamwar.gamecore.team.TeamRegistry; import de.steamwar.misslewars.items.CustomItem; import de.steamwar.misslewars.items.Missile; import de.steamwar.misslewars.slowmo.SlowMoRunner; @@ -33,6 +34,7 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import java.util.Set; +import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -40,7 +42,7 @@ public class MissileWars extends JavaPlugin { private static MissileWars plugin; private static MWTeam redTeam; //red has South side -> spawns missles towards north private static MWTeam blueTeam; - private static FightState fightState; + private static TeamRegistry teams; /** * create teams, ect @@ -48,13 +50,14 @@ public class MissileWars extends JavaPlugin { @Override public void onLoad() { plugin = this; - fightState = FightState.WAITING; + FightState.lifecycle.reset(FightState.WAITING); } @Override public void onEnable() { redTeam = new MWTeam(Config.TeamRedColor, Config.RedSpawn, Config.TeamRedName, Config.RedPortalZ); blueTeam = new MWTeam(Config.TeamBlueColor, Config.BlueSpawn, Config.TeamBlueName, Config.BluePortalZ); + teams = new TeamRegistry<>(List.of(redTeam, blueTeam)); SpigotLinker spigotLinker = new SpigotLinker(this, null) { @Override @@ -73,8 +76,6 @@ public class MissileWars extends JavaPlugin { Missile.init(); CustomItem.init(); - StateDependent.setupState(fightState); - Bukkit.getScheduler().runTaskTimer(this, new FightInfoPacketSender(), 20, 20); Bukkit.getWorlds().forEach(world -> { @@ -87,9 +88,8 @@ public class MissileWars extends JavaPlugin { } public static void waiting() { - if (fightState == FightState.END) { - fightState = FightState.WAITING; - StateDependent.setupState(fightState); + if (FightState.lifecycle.getCurrentState() == FightState.END) { + StateDependent.setupState(FightState.WAITING); SlowMoRunner.resetSlowMoTime(); @@ -108,10 +108,9 @@ public class MissileWars extends JavaPlugin { * call to change fightstate from WAITING to INGAME */ static void startRound() { - if (fightState != FightState.WAITING) // anti dual-call + if (FightState.lifecycle.getCurrentState() != FightState.WAITING) // anti dual-call return; - fightState = FightState.FIGHTING; - StateDependent.setupState(fightState); + StateDependent.setupState(FightState.FIGHTING); } /** @@ -121,10 +120,9 @@ public class MissileWars extends JavaPlugin { * @param winner Gewinner (oder null) */ public static void end(WinReasons reason, MWTeam winner) { - if (fightState == FightState.END) //verhindern dass es mehrfach gecallt wird + if (FightState.lifecycle.getCurrentState() == FightState.END) //verhindern dass es mehrfach gecallt wird return; - fightState = FightState.END; - StateDependent.setupState(fightState); + StateDependent.setupState(FightState.END); for (Player player : Bukkit.getOnlinePlayers()) { player.playSound(player.getLocation(), Sound.ENTITY_ENDER_DRAGON_DEATH, 100f, 1f); @@ -156,7 +154,7 @@ public class MissileWars extends JavaPlugin { } public static FightState getFightState() { - return fightState; + return FightState.lifecycle.getCurrentState(); } public static MWTeam getRedTeam() { @@ -168,9 +166,11 @@ public class MissileWars extends JavaPlugin { } public static MWTeam getTeam(Player p) { - if (blueTeam.hasPlayer(p)) return blueTeam; - if (redTeam.hasPlayer(p)) return redTeam; - return null; + return teams.findTeam(p); + } + + public static MWTeam getOpposite(MWTeam team) { + return teams.opposite(team); } public static MWTeam getInvitation(Player p) { diff --git a/MissileWars/src/de/steamwar/misslewars/StateDependent.java b/MissileWars/src/de/steamwar/misslewars/StateDependent.java index c8fd8fcf..f08244ca 100644 --- a/MissileWars/src/de/steamwar/misslewars/StateDependent.java +++ b/MissileWars/src/de/steamwar/misslewars/StateDependent.java @@ -19,37 +19,18 @@ package de.steamwar.misslewars; -import java.util.HashSet; import java.util.Set; -public abstract class StateDependent { - private static Set listeners = new HashSet<>(); - - private final Set active; - private boolean running; - +public abstract class StateDependent extends de.steamwar.gamecore.state.StateDependent { public StateDependent(final Set active) { - this.active = active; - running = false; - listeners.add(this); + super(FightState.lifecycle, true, active); + register(); } - public abstract void enable(); - - public abstract void disable(); - static void setupState(FightState state) { if (state == FightState.FIGHTING) { FightScoreboard.startTime(); } - for (StateDependent listener : listeners) { - if (listener.running && !listener.active.contains(state)) { - listener.disable(); - listener.running = false; - } else if (!listener.running && listener.active.contains(state)) { - listener.enable(); - listener.running = true; - } - } + FightState.lifecycle.transitionTo(state); } } diff --git a/MissileWars/src/de/steamwar/misslewars/countdowns/EndCountdown.java b/MissileWars/src/de/steamwar/misslewars/countdowns/EndCountdown.java index 8a824637..7c164728 100644 --- a/MissileWars/src/de/steamwar/misslewars/countdowns/EndCountdown.java +++ b/MissileWars/src/de/steamwar/misslewars/countdowns/EndCountdown.java @@ -22,14 +22,13 @@ package de.steamwar.misslewars.countdowns; import de.steamwar.linkage.Linked; import de.steamwar.misslewars.*; import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitTask; import java.util.EnumSet; @Linked public class EndCountdown extends StateDependent { - private BukkitTask task; + private de.steamwar.gamecore.countdown.Countdown countdown; public EndCountdown() { super(EnumSet.of(FightState.END)); @@ -37,11 +36,20 @@ public class EndCountdown extends StateDependent { @Override public void enable() { - if (Config.isEvent()) { - task = Bukkit.getScheduler().runTaskLater(MissileWars.getPlugin(), this::stop, 200); - } else { - task = Bukkit.getScheduler().runTaskLater(MissileWars.getPlugin(), this::restart, Config.EndTime); - } + long delay = Config.isEvent() ? 200 : Config.EndTime; + countdown = new de.steamwar.gamecore.countdown.Countdown( + MissileWars.getPlugin(), 1, delay, 1, false + ) { + @Override + protected void onFinished() { + if (Config.isEvent()) { + stop(); + } else { + restart(); + } + } + }; + countdown.enable(); } private void restart() { @@ -62,6 +70,6 @@ public class EndCountdown extends StateDependent { @Override public void disable() { - if (task != null && !task.isCancelled()) task.cancel(); + if (countdown != null) countdown.disable(); } } diff --git a/MissileWars/src/de/steamwar/misslewars/countdowns/WaitingCountdown.java b/MissileWars/src/de/steamwar/misslewars/countdowns/WaitingCountdown.java index 050aa7aa..7cf25425 100644 --- a/MissileWars/src/de/steamwar/misslewars/countdowns/WaitingCountdown.java +++ b/MissileWars/src/de/steamwar/misslewars/countdowns/WaitingCountdown.java @@ -25,14 +25,13 @@ import de.steamwar.misslewars.FightState; import de.steamwar.misslewars.MissileWars; import de.steamwar.misslewars.StateDependent; import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitTask; import java.util.EnumSet; @Linked public class WaitingCountdown extends StateDependent { - private BukkitTask task; + private de.steamwar.gamecore.countdown.Countdown countdown; public WaitingCountdown() { super(EnumSet.of(FightState.WAITING)); @@ -40,15 +39,21 @@ public class WaitingCountdown extends StateDependent { @Override public void enable() { - task = Bukkit.getScheduler().runTaskLater(MissileWars.getPlugin(), () -> { - if (Bukkit.getOnlinePlayers().isEmpty()) { - Bukkit.shutdown(); + countdown = new de.steamwar.gamecore.countdown.Countdown( + MissileWars.getPlugin(), 1, Config.WaitingTime, 1, false + ) { + @Override + protected void onFinished() { + if (Bukkit.getOnlinePlayers().isEmpty()) { + Bukkit.shutdown(); + } } - }, Config.WaitingTime); + }; + countdown.enable(); } @Override public void disable() { - if (task != null && !task.isCancelled()) task.cancel(); + if (countdown != null) countdown.disable(); } } diff --git a/MissileWars/src/plugin.yml b/MissileWars/src/plugin.yml index 7450c97f..ad629122 100644 --- a/MissileWars/src/plugin.yml +++ b/MissileWars/src/plugin.yml @@ -8,10 +8,11 @@ authors: version: "1.0" api-version: "1.13" depend: + - GameCore - WorldEdit - SpigotCore commands: spectate: invite: accept: - decline: \ No newline at end of file + decline: diff --git a/TNTLeague/build.gradle.kts b/TNTLeague/build.gradle.kts index f3b78d69..5d471804 100644 --- a/TNTLeague/build.gradle.kts +++ b/TNTLeague/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { dependencies { compileOnly(libs.paperapi) + compileOnly(project(":GameCore", "default")) compileOnly(project(":SpigotCore", "default")) compileOnly(project(":KotlinCore", "default")) -} \ No newline at end of file +} diff --git a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt index 588b6864..62573727 100644 --- a/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt +++ b/TNTLeague/src/de/steamwar/tntleague/TNTLeague.kt @@ -22,7 +22,7 @@ package de.steamwar.tntleague import de.steamwar.message.Message import de.steamwar.tntleague.command.* import de.steamwar.tntleague.events.GlobalListener -import de.steamwar.tntleague.events.LobbyListener +import de.steamwar.tntleague.game.TNTLeagueGame import de.steamwar.tntleague.game.TNTLeagueTeam import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin @@ -38,8 +38,8 @@ class TNTLeague : JavaPlugin() { override fun onEnable() { message = Message("de.steamwar.tntleague.TNTLeague", classLoader) - server.pluginManager.registerEvents(LobbyListener, this) server.pluginManager.registerEvents(GlobalListener, this) + TNTLeagueGame.initialize() logger.info("TNTLeague enabled") diff --git a/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt index f575fdf1..b4fcb643 100644 --- a/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt +++ b/TNTLeague/src/de/steamwar/tntleague/config/TNTLeagueConfig.kt @@ -19,6 +19,8 @@ package de.steamwar.tntleague.config +import de.steamwar.gamecore.ArenaMode +import de.steamwar.gamecore.config.GameRuntimeProperties import de.steamwar.message.SubMessage import de.steamwar.sql.Event import de.steamwar.sql.EventFight @@ -32,16 +34,19 @@ import org.bukkit.inventory.meta.Damageable import org.bukkit.inventory.meta.ItemMeta import java.util.* +private val runtimeProperties = GameRuntimeProperties.load() + data class TNTLeagueConfig( val startDelay: Int = 10, val gameTime: Int = 60 * 20, val prices: Map, - val blueLeader: UUID? = System.getProperty("blueLeader")?.let { UUID.fromString(it) }, - val redLeader: UUID? = System.getProperty("redLeader")?.let { UUID.fromString(it) }, + val blueLeader: UUID? = runtimeProperties.blueLeader, + val redLeader: UUID? = runtimeProperties.redLeader, - val eventFightId: Int? = System.getProperty("fightID")?.toInt() + val eventFightId: Int? = runtimeProperties.fightId.takeIf { it != 0 }, + val arenaMode: ArenaMode = ArenaMode.from(runtimeProperties), ) { lateinit var eventFight: EventFight lateinit var event: Event @@ -92,9 +97,7 @@ data class TNTLeagueConfig( } } - fun isEvent() = - (config.eventFightId - ?: 0) > 0 + fun isEvent() = config.arenaMode == ArenaMode.EVENT } data class Price( diff --git a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt index 500cbe03..54d87e60 100644 --- a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt +++ b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueGame.kt @@ -19,6 +19,12 @@ package de.steamwar.tntleague.game +import de.steamwar.gamecore.countdown.Countdown +import de.steamwar.gamecore.state.GameStateMachine +import de.steamwar.gamecore.state.StateDependentListener +import de.steamwar.gamecore.team.TeamRegistry +import de.steamwar.gamecore.wincondition.GameResult +import de.steamwar.gamecore.wincondition.WinConditions import de.steamwar.kotlin.util.Area import de.steamwar.network.NetworkSender import de.steamwar.network.packets.common.FightInfoPacket @@ -41,38 +47,49 @@ import org.bukkit.Sound import org.bukkit.entity.Item import org.bukkit.entity.Player import org.bukkit.entity.TNTPrimed -import org.bukkit.event.HandlerList -import org.bukkit.event.Listener import org.bukkit.inventory.ItemStack import org.bukkit.scheduler.BukkitTask import java.sql.Timestamp import java.time.Instant object TNTLeagueGame { - var state: GameState = GameState.LOBBY + private val stateMachine = GameStateMachine(GameState.LOBBY) + private val stateDependencies by lazy { + listOf( + StateDependentListener(stateMachine, true, setOf(GameState.LOBBY, GameState.STARTING), plugin, LobbyListener), + StateDependentListener(stateMachine, true, setOf(GameState.RUNNING), plugin, IngameListener), + StateDependentListener(stateMachine, true, setOf(GameState.END), plugin, DummyListener), + ) + } + + var state: GameState + get() = stateMachine.currentState set(value) { - if (field.listener != value.listener) { - HandlerList.unregisterAll(field.listener) - plugin.server.pluginManager.registerEvents(value.listener, plugin) - } - field = value + stateMachine.transitionTo(value) } + fun initialize() { + stateDependencies + } + var gameTimeRemaining: Int = TNTLeagueConfig.config.gameTime val blueTeam = TNTLeagueTeam(TNTLeagueConfig.config.blueTeam) val redTeam = TNTLeagueTeam(TNTLeagueConfig.config.redTeam) + private val teams = TeamRegistry(listOf(blueTeam, redTeam)) private lateinit var start: Timestamp - private var task: Int? = null + private var startCountdown: Countdown? = null + private var gameCountdown: Countdown? = null + private var endCountdown: Countdown? = null private lateinit var spawnerTask: BukkitTask - private lateinit var timerTask: BukkitTask private fun setup() { assert(state == GameState.STARTING) { "Game is already running" } state = GameState.RUNNING + gameTimeRemaining = TNTLeagueConfig.config.gameTime plugin.server.onlinePlayers.forEach { SWScoreboard.impl.createScoreboard(it, TNTLeagueScoreboard(it)) } @@ -104,24 +121,33 @@ object TNTLeagueGame { } }, 5, 10) - timerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit { - gameTimeRemaining-- - if (gameTimeRemaining == 0) { - if (blueTeam.damagedBlocks > redTeam.damagedBlocks) { - win(blueTeam, WinReason.TIMEOUT) - } else if (redTeam.damagedBlocks > blueTeam.damagedBlocks) { - win(redTeam, WinReason.TIMEOUT) - } else { - draw(WinReason.TIMEOUT) + gameCountdown = object : Countdown( + plugin, + TNTLeagueConfig.config.gameTime, + 20, + 20, + false, + ) { + override fun onTick(timeLeft: Int) { + gameTimeRemaining = timeLeft + if (timeLeft > 0 && timeLeft % 300 == 0) { + message.broadcast("TIME_REMAINING", (timeLeft / 60)) + plugin.server.onlinePlayers.forEach { + it.playSound(it.location, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1f) + } } - return@bukkit } - if (gameTimeRemaining % 300 == 0) { - message.broadcast("TIME_REMAINING", (gameTimeRemaining / 60)) - plugin.server.onlinePlayers.forEach { it.playSound(it.location, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 1f) } + override fun onFinished() { + when (val result = WinConditions.winnerByScore( + teams.teams, + { team -> team.damagedBlocks.toDouble() }, + )) { + is GameResult.Winner -> win(result.winner, WinReason.TIMEOUT) + GameResult.Draw -> draw(WinReason.TIMEOUT) + } } - }, 20, 20) + }.also { it.enable() } } private fun bukkit(f: () -> Unit): () -> Unit = @@ -140,18 +166,19 @@ object TNTLeagueGame { message.broadcast("GAME_ENDED") spawnerTask.cancel() + gameCountdown?.disable() - var shutdown = 10 - - plugin.server.scheduler.runTaskTimer(plugin, bukkit { - if (shutdown == 0) { - plugin.server.shutdown() + endCountdown = object : Countdown(plugin, 11, 20, 20, false) { + override fun onTick(timeLeft: Int) { + if (timeLeft > 0) { + message.broadcast("SHUTDOWN", timeLeft) + } } - message.broadcast("SHUTDOWN", shutdown) - - shutdown-- - }, 20, 20) + override fun onFinished() { + plugin.server.shutdown() + } + }.also { it.enable() } updateFightinfo() } @@ -160,7 +187,10 @@ object TNTLeagueGame { plugin.server.worlds.first().dropItem(loc, item) fun getTeam(player: Player) = - if (player in blueTeam.members) blueTeam else if (player in redTeam.members) redTeam else null + teams.findTeam(player) + + fun opposite(team: TNTLeagueTeam) = + teams.opposite(team) fun getFreeTeam(player: Player) = if (TNTLeagueConfig.isEvent()) getEventFreeTeam(player) else getNormalFreeTeam(player) @@ -178,27 +208,31 @@ object TNTLeagueGame { } fun checkStart() { - if (blueTeam.isReady && redTeam.isReady) { + if (teams.allReady()) { blueTeam.leader?.inventory?.clear() redTeam.leader?.inventory?.clear() state = GameState.STARTING - var countdown = TNTLeagueConfig.config.startDelay - message.broadcast("GAME_STARTING", countdown.toString()) - task = plugin.server.scheduler.scheduleSyncRepeatingTask(plugin, { - plugin.server.onlinePlayers.forEach { it.playSound(it.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f) } - if (--countdown == 0) { - plugin.server.onlinePlayers.forEach { it.level = 0 } - task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null } - setup() - } else { - plugin.server.onlinePlayers.forEach { it.level = countdown } + message.broadcast("GAME_STARTING", TNTLeagueConfig.config.startDelay.toString()) + startCountdown = object : Countdown( + plugin, + TNTLeagueConfig.config.startDelay, + 20, + 20, + false, + ) { + override fun onTick(timeLeft: Int) { + plugin.server.onlinePlayers.forEach { + it.playSound(it.location, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f) + it.level = timeLeft + } } - }, 20, 20) - if (task == -1) { - error("Failed to start countdown task") - } + override fun onFinished() { + plugin.server.onlinePlayers.forEach { it.level = 0 } + setup() + } + }.also { it.enable() } updateFightinfo() } @@ -216,7 +250,8 @@ object TNTLeagueGame { assert(state == GameState.LOBBY || state == GameState.STARTING) { "Game is not in lobby or starting state" } if (state == GameState.STARTING) { - task = task?.also { plugin.server.scheduler.cancelTask(it) }.let { null } + startCountdown?.disable() + startCountdown = null state = GameState.LOBBY } @@ -277,11 +312,11 @@ object TNTLeagueGame { )) } - enum class GameState(val listener: Listener, val lobbyName: String) { - LOBBY(LobbyListener, "PRE_LEADER_SETUP"), - STARTING(LobbyListener, "POST_SCHEM_SETUP"), - RUNNING(IngameListener, "RUNNING"), - END(DummyListener, "SPECTATE"); + enum class GameState(val lobbyName: String) { + LOBBY("PRE_LEADER_SETUP"), + STARTING("POST_SCHEM_SETUP"), + RUNNING("RUNNING"), + END("SPECTATE"); } enum class WinReason { @@ -289,4 +324,4 @@ object TNTLeagueGame { DESTROYED, LEAVE } -} \ No newline at end of file +} diff --git a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt index 2cf5e9fd..8f528917 100644 --- a/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt +++ b/TNTLeague/src/de/steamwar/tntleague/game/TNTLeagueTeam.kt @@ -20,6 +20,7 @@ package de.steamwar.tntleague.game import de.steamwar.message.SubMessage +import de.steamwar.gamecore.team.GameTeam import de.steamwar.tntleague.colorByTeam import de.steamwar.tntleague.config.TNTLeagueWorldConfig import de.steamwar.tntleague.config.TeamConfig @@ -36,7 +37,7 @@ import org.bukkit.Sound import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack -data class TNTLeagueTeam(val config: TeamConfig) { +data class TNTLeagueTeam(val config: TeamConfig) : GameTeam { var leader: Player? = null set(player) { @@ -82,11 +83,7 @@ data class TNTLeagueTeam(val config: TeamConfig) { var coins: Int = 0 val opposite: TNTLeagueTeam - get() = when (this) { - TNTLeagueGame.redTeam -> TNTLeagueGame.blueTeam - TNTLeagueGame.blueTeam -> TNTLeagueGame.redTeam - else -> error("Invalid Team") - } + get() = TNTLeagueGame.opposite(this) val ergebnisInt: Int get() = when (this) { @@ -95,6 +92,21 @@ data class TNTLeagueTeam(val config: TeamConfig) { else -> error("Invalid Team") } + override fun teamId() = + name.message + + override fun members(): Collection = + members + + override fun leader(): Player? = + leader + + override fun ready() = + isReady + + override fun contains(member: Player) = + member in members + fun join(player: Player): Boolean { members.add(player) @@ -168,4 +180,4 @@ data class TNTLeagueTeam(val config: TeamConfig) { } updateFightinfo() } -} \ No newline at end of file +} diff --git a/TNTLeague/src/paper-plugin.yml b/TNTLeague/src/paper-plugin.yml index 4e740ea9..1cfa62d3 100644 --- a/TNTLeague/src/paper-plugin.yml +++ b/TNTLeague/src/paper-plugin.yml @@ -5,9 +5,12 @@ load: POSTWORLD api-version: '1.21' dependencies: server: + GameCore: + required: true + load: BEFORE SpigotCore: required: true load: BEFORE KotlinCore: required: true - load: BEFORE \ No newline at end of file + load: BEFORE diff --git a/TowerRun/build.gradle.kts b/TowerRun/build.gradle.kts index e1b3b5fb..96d34684 100644 --- a/TowerRun/build.gradle.kts +++ b/TowerRun/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { compileOnly(libs.classindex) annotationProcessor(libs.classindex) + compileOnly(project(":GameCore", "default")) compileOnly(project(":SpigotCore", "default")) compileOnly(libs.nms) diff --git a/TowerRun/src/de/steamwar/towerrun/config/Config.java b/TowerRun/src/de/steamwar/towerrun/config/Config.java index 6e4d4f5f..0fc4b88f 100644 --- a/TowerRun/src/de/steamwar/towerrun/config/Config.java +++ b/TowerRun/src/de/steamwar/towerrun/config/Config.java @@ -19,6 +19,8 @@ package de.steamwar.towerrun.config; +import de.steamwar.gamecore.ArenaMode; +import de.steamwar.gamecore.config.GameRuntimeProperties; import de.steamwar.sql.Event; import de.steamwar.sql.EventFight; import de.steamwar.towerrun.TowerRun; @@ -35,6 +37,9 @@ import java.util.stream.Collectors; @UtilityClass public class Config { + public static final GameRuntimeProperties RUNTIME_PROPERTIES = GameRuntimeProperties.load(); + public static final ArenaMode MODE = ArenaMode.from(RUNTIME_PROPERTIES); + public static final int MIN_PLAYERS; public static final int LOBBY_TIMER; public static final Set DESTROYABLE_BLOCKS; @@ -64,7 +69,7 @@ public class Config { GAME_ESCAPE_TIMER = config.getInt("gameEscapeTimer", 60); DESTROYABLE_BLOCKS = EnumSet.copyOf(config.getStringList("destroyable").stream().map(Material::valueOf).collect(Collectors.toSet())); - EVENT_KAMPF_ID = Integer.parseInt(System.getProperty("fightID", "0")); + EVENT_KAMPF_ID = RUNTIME_PROPERTIES.getFightId(); if (event()) { EVENT_FIGHT = EventFight.byId(EVENT_KAMPF_ID); @@ -89,10 +94,10 @@ public class Config { } public static boolean test() { - return EVENT_KAMPF_ID == -1; + return MODE == ArenaMode.TEST; } public static boolean event() { - return EVENT_KAMPF_ID > 0; + return MODE == ArenaMode.EVENT; } } diff --git a/TowerRun/src/de/steamwar/towerrun/countdowns/Countdown.java b/TowerRun/src/de/steamwar/towerrun/countdowns/Countdown.java index b010cbf3..b76c8493 100644 --- a/TowerRun/src/de/steamwar/towerrun/countdowns/Countdown.java +++ b/TowerRun/src/de/steamwar/towerrun/countdowns/Countdown.java @@ -23,18 +23,14 @@ import de.steamwar.towerrun.TowerRun; import de.steamwar.towerrun.state.GameStateToggleListener; import de.steamwar.towerrun.state.GameStates; import lombok.Getter; -import lombok.Setter; -import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitTask; import java.util.EnumSet; public abstract class Countdown extends GameStateToggleListener { - @Setter @Getter protected int time = defaultTime(); - protected BukkitTask task; + private de.steamwar.gamecore.countdown.Countdown countdown; protected Countdown(EnumSet enabledStates) { super(enabledStates); @@ -60,30 +56,48 @@ public abstract class Countdown extends GameStateToggleListener { void timerReset() { } - private void timer() { - if (timerShouldCancel()) { - time = defaultTime(); - timerReset(); - } else { - if (time == defaultTime()) { - timerStart(); - } - if (time == 0) { - timerEnd(); - return; - } - run(); - time--; + public void setTime(int time) { + this.time = time; + if (countdown != null) { + countdown.setRemainingTime(time + 1); } } @Override public void enable() { - task = Bukkit.getScheduler().runTaskTimer(TowerRun.getInstance(), this::timer, 0, 20); + countdown = new de.steamwar.gamecore.countdown.Countdown( + TowerRun.getInstance(), defaultTime() + 1, 0, 20, false + ) { + @Override + protected void onTick(int remaining) { + time = remaining; + if (timerShouldCancel()) { + reset(); + time = defaultTime(); + timerReset(); + return; + } + if (time == defaultTime()) { + timerStart(); + } + if (time > 0) { + run(); + } + } + + @Override + protected void onFinished() { + timerEnd(); + } + }; + countdown.enable(); } @Override public void disable() { - task.cancel(); + if (countdown != null) { + countdown.disable(); + countdown = null; + } } } diff --git a/TowerRun/src/de/steamwar/towerrun/game/TowerRunGame.java b/TowerRun/src/de/steamwar/towerrun/game/TowerRunGame.java index 0dd7d7fe..f6f68adb 100644 --- a/TowerRun/src/de/steamwar/towerrun/game/TowerRunGame.java +++ b/TowerRun/src/de/steamwar/towerrun/game/TowerRunGame.java @@ -20,6 +20,7 @@ package de.steamwar.towerrun.game; import de.steamwar.core.CraftbukkitWrapper; +import de.steamwar.gamecore.wincondition.GameResult; import de.steamwar.sql.SteamwarUser; import de.steamwar.towerrun.TowerRun; import de.steamwar.towerrun.config.Config; @@ -119,6 +120,14 @@ public class TowerRunGame { } } + public static void complete(GameResult result) { + if (result instanceof GameResult.Winner winner) { + win(winner.getWinner()); + } else { + tie(); + } + } + public static void win(TowerRunPlayer tPlayer) { Bukkit.getOnlinePlayers().forEach(player -> player.setGameMode(GameMode.SPECTATOR)); PLAYERS_ALIVE.clear(); diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameState.java b/TowerRun/src/de/steamwar/towerrun/state/GameState.java index 5f8097fe..a9bcf2c8 100644 --- a/TowerRun/src/de/steamwar/towerrun/state/GameState.java +++ b/TowerRun/src/de/steamwar/towerrun/state/GameState.java @@ -19,41 +19,30 @@ package de.steamwar.towerrun.state; -import lombok.Getter; +import de.steamwar.gamecore.state.GameStateMachine; import lombok.experimental.UtilityClass; -import java.util.ArrayList; -import java.util.List; - @UtilityClass public class GameState { - @Getter - private static GameStates currentState = GameStates.WAITING; - private static final List gameStateListeners = new ArrayList<>(); + private static final GameStateMachine state = new GameStateMachine<>(GameStates.WAITING); - public static void addGameStateListener(GameStateListener gameStateListener) { - gameStateListeners.add(gameStateListener); + public static GameStateMachine getLifecycle() { + return state; + } + + public static GameStates getCurrentState() { + return state.getCurrentState(); } public static void nextState() { - final GameStates oldState = currentState; - currentState = currentState.getNextState(); - gameStateChanges(oldState, currentState); + state.transitionTo(state.getCurrentState().getNextState()); } static void setState(final GameStates newState) { - final GameStates oldState = currentState; - currentState = newState; - gameStateChanges(oldState, currentState); + state.transitionTo(newState); } public static void reset() { - final GameStates oldState = currentState; - currentState = GameStates.WAITING; - gameStateChanges(oldState, currentState); - } - - private static void gameStateChanges(GameStates oldState, GameStates newState) { - gameStateListeners.forEach(gameStateListener -> gameStateListener.onGameStateChange(oldState, newState)); + state.transitionTo(GameStates.WAITING); } } diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameStateListener.java b/TowerRun/src/de/steamwar/towerrun/state/GameStateListener.java deleted file mode 100644 index dbd9f70e..00000000 --- a/TowerRun/src/de/steamwar/towerrun/state/GameStateListener.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2025 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.towerrun.state; - -public abstract class GameStateListener { - - protected GameStateListener() { - GameState.addGameStateListener(this); - } - - public abstract void onGameStateChange(GameStates oldState, GameStates newState); -} diff --git a/TowerRun/src/de/steamwar/towerrun/state/GameStateToggleListener.java b/TowerRun/src/de/steamwar/towerrun/state/GameStateToggleListener.java index baf983d6..df7e9fe9 100644 --- a/TowerRun/src/de/steamwar/towerrun/state/GameStateToggleListener.java +++ b/TowerRun/src/de/steamwar/towerrun/state/GameStateToggleListener.java @@ -21,28 +21,14 @@ package de.steamwar.towerrun.state; import java.util.EnumSet; -public abstract class GameStateToggleListener extends GameStateListener { - - private final EnumSet enabledStates; +public abstract class GameStateToggleListener extends de.steamwar.gamecore.state.StateDependent { protected GameStateToggleListener(EnumSet enabledStates) { - super(); - this.enabledStates = enabledStates; - if (enabledStates.contains(GameState.getCurrentState())) { - enable(); - } + super(GameState.getLifecycle(), true, enabledStates); + register(); } public abstract void enable(); public abstract void disable(); - - @Override - public void onGameStateChange(GameStates oldState, GameStates newState) { - if (enabledStates.contains(newState) && !enabledStates.contains(oldState)) { - enable(); - } else if (!enabledStates.contains(newState) && enabledStates.contains(oldState)) { - disable(); - } - } } diff --git a/TowerRun/src/de/steamwar/towerrun/winconditions/LastRemainingWincondition.java b/TowerRun/src/de/steamwar/towerrun/winconditions/LastRemainingWincondition.java index 7cdbe3b4..8b59f8fc 100644 --- a/TowerRun/src/de/steamwar/towerrun/winconditions/LastRemainingWincondition.java +++ b/TowerRun/src/de/steamwar/towerrun/winconditions/LastRemainingWincondition.java @@ -39,8 +39,8 @@ public class LastRemainingWincondition extends WinCondition { TowerRunGame.PLAYERS_ALIVE.remove(tPlayer); TowerRunGame.PLAYERS_ESCAPED.remove(tPlayer); tPlayer.player().setGameMode(GameMode.SPECTATOR); - if (TowerRunGame.PLAYERS_ALIVE.size() == 1 && TowerRunGame.PLAYERS_ESCAPED.isEmpty()) { - TowerRunGame.win(TowerRunGame.PLAYERS_ALIVE.get(0)); + if (TowerRunGame.PLAYERS_ESCAPED.isEmpty() && TowerRunGame.PLAYERS_ALIVE.size() == 1) { + TowerRunGame.complete(de.steamwar.gamecore.wincondition.WinConditions.lastRemaining(TowerRunGame.PLAYERS_ALIVE)); } } @@ -50,8 +50,8 @@ public class LastRemainingWincondition extends WinCondition { if (TowerRunGame.isAlive(tPlayer)) { TowerRunGame.PLAYERS_ALIVE.remove(tPlayer); TowerRunGame.PLAYERS_ESCAPED.remove(tPlayer); - if (TowerRunGame.PLAYERS_ALIVE.size() == 1 && TowerRunGame.PLAYERS_ESCAPED.isEmpty()) { - TowerRunGame.win(TowerRunGame.PLAYERS_ALIVE.get(0)); + if (TowerRunGame.PLAYERS_ESCAPED.isEmpty() && TowerRunGame.PLAYERS_ALIVE.size() == 1) { + TowerRunGame.complete(de.steamwar.gamecore.wincondition.WinConditions.lastRemaining(TowerRunGame.PLAYERS_ALIVE)); } } } diff --git a/TowerRun/src/de/steamwar/towerrun/winconditions/WinCondition.java b/TowerRun/src/de/steamwar/towerrun/winconditions/WinCondition.java index dc0a39b3..cadb9975 100644 --- a/TowerRun/src/de/steamwar/towerrun/winconditions/WinCondition.java +++ b/TowerRun/src/de/steamwar/towerrun/winconditions/WinCondition.java @@ -19,6 +19,7 @@ package de.steamwar.towerrun.winconditions; +import de.steamwar.towerrun.game.TowerRunPlayer; import de.steamwar.towerrun.state.GameStateBukkitListener; import de.steamwar.towerrun.state.GameStates; import lombok.Getter; @@ -27,7 +28,7 @@ import lombok.Setter; import java.util.EnumSet; @Getter -public abstract class WinCondition extends GameStateBukkitListener { +public abstract class WinCondition extends GameStateBukkitListener implements de.steamwar.gamecore.wincondition.WinCondition { private final String name; @Setter @@ -38,6 +39,16 @@ public abstract class WinCondition extends GameStateBukkitListener { this.name = name; } + @Override + public String id() { + return name; + } + + @Override + public boolean active() { + return active; + } + @Override public void enable() { if (active) { diff --git a/TowerRun/src/plugin.yml b/TowerRun/src/plugin.yml index f3560c30..a5eac8ce 100644 --- a/TowerRun/src/plugin.yml +++ b/TowerRun/src/plugin.yml @@ -2,4 +2,4 @@ name: TowerRun version: 1.0.0 main: de.steamwar.towerrun.TowerRun api-version: 1.21 -depend: [ SpigotCore ] +depend: [ GameCore, SpigotCore ] diff --git a/settings.gradle.kts b/settings.gradle.kts index 14419caa..5248006a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -189,6 +189,8 @@ include( "FightSystem:FightSystem_Standalone" ) +include("GameCore") + include("KotlinCore") include("LobbySystem")