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
This commit is contained in:
2026-07-24 12:18:49 +02:00
parent badff1c70f
commit 867ca6f5dc
133 changed files with 2155 additions and 1076 deletions
+191
View File
@@ -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="<created_at>" -- <module path>, plus keyword searches: git -C "${WT}" log --all --oneline -i --grep="<keyword>" (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 }
+151
View File
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="LanguageInjectionConfiguration">
<injection language="SQL" injector-id="java">
<display-name>AsyncQueryRunner (org.apache.commons.dbutils)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("batch").withParameterCount(2).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("insertBatch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("batch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("insertBatch").withParameterCount(4).definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.AsyncQueryRunner"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Jodd (jodd.db)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query").withParameterCount(1).definedInClass("jodd.db.DbQuery"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("DbQuery").withParameterCount(2).definedInClass("jodd.db.DbQuery"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query").withParameterCount(2).definedInClass("jodd.db.DbQuery"))]]></place>
<place><![CDATA[psiParameter().ofMethod(2, psiMethod().withName("DbQuery").withParameterCount(3).definedInClass("jodd.db.DbQuery"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>MyBatis @Select/@Delete/@Insert/@Update</display-name>
<single-file value="true" />
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.apache.ibatis.annotations.Delete")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.apache.ibatis.annotations.Insert")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.apache.ibatis.annotations.Select")]]></place>
<place><![CDATA[psiMethod().withName("value").withParameters().definedInClass("org.apache.ibatis.annotations.Update")]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>QueryRunner (org.apache.commons.dbutils)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("batch").withParameterCount(2).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("insertBatch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "insert", "execute").withParameters("java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update").withParameters("java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("update", "execute").withParameters("java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("batch").withParameterCount(3).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("insertBatch").withParameterCount(4).definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("query", "insert", "execute").withParameters("java.sql.Connection", "java.lang.String", "org.apache.commons.dbutils.ResultSetHandler").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
<place><![CDATA[psiParameter().ofMethod(1, psiMethod().withName("update", "execute").withParameters("java.sql.Connection", "java.lang.String", "java.lang.Object...").definedInClass("org.apache.commons.dbutils.QueryRunner"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>R2DBC (io.r2dbc)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("add").definedInClass("io.r2dbc.spi.Batch"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("createStatement").definedInClass("io.r2dbc.spi.Connection"))]]></place>
</injection>
<injection language="PostgreSQL" injector-id="java">
<display-name>Reactiverse Postgres Client (io.reactiverse)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgTransaction"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgPool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.reactiverse.reactivex.pgclient.PgTransaction"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.axle.pgclient.PgClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.reactiverse.pgclient.PgPool"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>SmallRye Axle SqlClient (io.vertx.axle.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.axle.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.axle.sqlclient.SqlClient"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>SmallRye Mutiny SqlClient (io.vertx.mutiny.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mutiny.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mutiny.sqlclient.SqlClient"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>SmallRye Mutiny SqlConnection (io.vertx.mutiny.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.db2client.DB2Connection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.mssqlclient.MSSQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.mysqlclient.MySQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("prepare", "prepareAndAwait").definedInClass("io.vertx.mutiny.pgclient.PgConnection"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SQL Extensions (io.vertx.ext.sql)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams").definedInClass("io.vertx.ext.sql.SQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams").definedInClass("io.vertx.ext.sql.SQLOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams", "execute", "batchWithParams", "batchCallableWithParams").definedInClass("io.vertx.ext.sql.SQLConnection"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SQL Reactive Extensions (io.vertx.reactivex.ext.sql)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams").definedInClass("io.vertx.reactivex.ext.sql.SQLOperations"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams", "execute", "batchWithParams", "batchCallableWithParams", "rxQuerySingle", "rxQuerySingleWithParams", "rxQuery", "rxQueryWithParams", "rxQueryStream", "rxQueryStreamWithParams", "rxUpdate", "rxUpdateWithParams", "rxCall", "rxCallWithParams", "rxExecute", "rxBatchWithParams", "rxBatchCallableWithParams").definedInClass("io.vertx.reactivex.ext.sql.SQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "queryWithParams", "queryStream", "queryStreamWithParams", "querySingle", "querySingleWithParams", "update", "updateWithParams", "call", "callWithParams", "execute", "batchWithParams", "batchCallableWithParams", "rxQuerySingle", "rxQuerySingleWithParams", "rxQuery", "rxQueryWithParams", "rxQueryStream", "rxQueryStreamWithParams", "rxUpdate", "rxUpdateWithParams", "rxCall", "rxCallWithParams", "rxExecute", "rxBatchWithParams", "rxBatchCallableWithParams").definedInClass("io.vertx.reactivex.ext.sql.SQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("querySingle", "rxQuerySingle", "querySingleWithParams", "rxQuerySingleWithParams").definedInClass("io.vertx.reactivex.ext.asyncsql.AsyncSQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("querySingle", "rxQuerySingle", "querySingleWithParams", "rxQuerySingleWithParams").definedInClass("io.vertx.reactivex.ext.asyncsql.MySQLClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("querySingle", "rxQuerySingle", "querySingleWithParams", "rxQuerySingleWithParams").definedInClass("io.vertx.reactivex.ext.asyncsql.PostgreSQLClient"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SqlClient (io.vertx.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mssqlclient.MSSQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.mysqlclient.MySQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.SqlClient"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.SqlConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch").definedInClass("io.vertx.sqlclient.Transaction"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>Vert.x SqlClient RxJava2 (io.vertx.reactivex.sqlclient)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.mysqlclient.MySQLConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.pgclient.PgConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.SqlConnection"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPrepare", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.Transaction"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.mysqlclient.MySQLPool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.pgclient.PgPool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.Pool"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "prepare", "preparedQuery", "preparedBatch", "rxQuery", "rxPreparedQuery", "rxPreparedBatch").definedInClass("io.vertx.reactivex.sqlclient.SqlClient"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>jOOQ (org.jooq.DSLContext)</display-name>
<single-file value="true" />
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("batch").withParameters("java.lang.String", "java.lang.Object[]...").definedInClass("org.jooq.DSLContext"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "fetch", "fetchLazy", "fetchAsync", "fetchStream", "fetchMany", "fetchOne", "fetchSingle", "fetchOptional", "fetchValue", "fetchOptionalValue", "fetchValues", "execute", "resultQuery").withParameters("java.lang.String", "java.lang.Object...").definedInClass("org.jooq.DSLContext"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("query", "fetch", "fetchLazy", "fetchAsync", "fetchStream", "fetchMany", "fetchOne", "fetchSingle", "fetchOptional", "fetchValue", "fetchOptionalValue", "fetchValues", "execute", "resultQuery", "batch").withParameters("java.lang.String").definedInClass("org.jooq.DSLContext"))]]></place>
<place><![CDATA[psiParameter().ofMethod(psiMethod().withName("batch").withParameters("java.lang.String...").definedInClass("org.jooq.DSLContext"))]]></place>
</injection>
<injection language="SQL" injector-id="java">
<display-name>rxjava2-jdbc (org.davidmoten.rx.jdbc)</display-name>
<single-file value="true" />
<place><![CDATA[psiMethod().withName("value").definedInClass("org.davidmoten.rx.jdbc.annotations.Query")]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("call", "select", "update").definedInClass("org.davidmoten.rx.jdbc.Database"))]]></place>
<place><![CDATA[psiParameter().ofMethod(0, psiMethod().withName("call", "select", "update").definedInClass("org.davidmoten.rx.jdbc.TransactedBuilder"))]]></place>
</injection>
</component>
</project>
@@ -29,6 +29,7 @@ java {
dependencies {
compileOnly(libs.classindex)
annotationProcessor(libs.classindex)
compileOnly(project(":GameCore", "default"))
compileOnly(project(":SpigotCore", "default"))
compileOnly(libs.netty)
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<ArenaMode> All = Collections.unmodifiableSet(EnumSet.allOf(ArenaMode.class));
public static final Set<ArenaMode> Normal = Collections.unmodifiableSet(EnumSet.of(NORMAL));
public static final Set<ArenaMode> Check = Collections.unmodifiableSet(EnumSet.of(CHECK));
public static final Set<ArenaMode> Event = Collections.unmodifiableSet(EnumSet.of(EVENT));
public static final Set<ArenaMode> Test = Collections.unmodifiableSet(EnumSet.of(TEST, CHECK));
public static final Set<ArenaMode> Prepare = Collections.unmodifiableSet(EnumSet.of(PREPARE));
public static final Set<ArenaMode> Replay = Collections.unmodifiableSet(EnumSet.of(REPLAY, TEST));
public static final Set<ArenaMode> AntiReplay = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(REPLAY)));
public static final Set<ArenaMode> AntiTest = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK)));
public static final Set<ArenaMode> AntiEvent = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(EVENT)));
public static final Set<ArenaMode> AntiTestCheckPrepare = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK, PREPARE)));
public static final Set<ArenaMode> AntiPrepare = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PREPARE)));
public static final Set<ArenaMode> VariableTeams = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(EVENT, REPLAY, CHECK)));
public static final Set<ArenaMode> ManualTeams = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(EVENT, REPLAY)));
public static final Set<ArenaMode> RankedEvent = Collections.unmodifiableSet(EnumSet.of(EVENT, REPLAY));
public static final Set<ArenaMode> Restartable = Collections.unmodifiableSet(EnumSet.of(NORMAL, TEST));
public static final Set<ArenaMode> NotRestartable = Collections.unmodifiableSet(EnumSet.of(EVENT, REPLAY));
public static final Set<ArenaMode> SoloLeader = Collections.unmodifiableSet(EnumSet.of(TEST, CHECK, PREPARE));
public static final Set<ArenaMode> NotOnBau = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK, PREPARE, REPLAY)));
public static final Set<ArenaMode> SeriousFight = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(TEST, CHECK, REPLAY)));
}
@@ -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<Material, Winconditions> 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() {
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Countdown> 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));
}
}
}
@@ -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);
});
}
}
@@ -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();
}
}
@@ -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())
);
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -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;
}
}
}
}
@@ -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 {
}
}
}
}
}
@@ -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;
@@ -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<FightTeam> teams = new HashSet<>();
static {
teams.add(redTeam);
teams.add(blueTeam);
}
private static final TeamRegistry<FightTeam, LivingEntity> 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<FightTeam> 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() {
@@ -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;
@@ -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;
@@ -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<LivingEntity> {
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<LivingEntity> 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");
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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 {
@@ -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;
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<FightState> All = Collections.unmodifiableSet(EnumSet.allOf(FightState.class));
public static final Set<FightState> All = immutable(EnumSet.allOf(FightState.class));
public static final Set<FightState> PreLeaderSetup = immutable(EnumSet.of(PRE_LEADER_SETUP));
public static final Set<FightState> PreSchemSetup = immutable(EnumSet.of(PRE_SCHEM_SETUP));
public static final Set<FightState> PostSchemSetup = immutable(EnumSet.of(POST_SCHEM_SETUP));
public static final Set<FightState> PreRunning = immutable(EnumSet.of(PRE_RUNNING));
public static final Set<FightState> Running = immutable(EnumSet.of(RUNNING));
public static final Set<FightState> Spectate = immutable(EnumSet.of(SPECTATE));
public static final Set<FightState> Setup = immutable(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, POST_SCHEM_SETUP));
public static final Set<FightState> Ingame = immutable(EnumSet.of(PRE_RUNNING, RUNNING));
public static final Set<FightState> TeamFix = immutable(EnumSet.of(PRE_RUNNING, RUNNING, SPECTATE));
public static final Set<FightState> Schem = complement(PRE_LEADER_SETUP, PRE_SCHEM_SETUP);
public static final Set<FightState> Recording = complement(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, SPECTATE);
public static final Set<FightState> AntiRunning = complement(RUNNING);
public static final Set<FightState> AntiIngame = complement(PRE_RUNNING, RUNNING);
public static final Set<FightState> AntiSpectate = complement(SPECTATE);
public static final Set<FightState> PreLeaderSetup = Collections.unmodifiableSet(EnumSet.of(PRE_LEADER_SETUP));
public static final Set<FightState> PreSchemSetup = Collections.unmodifiableSet(EnumSet.of(PRE_SCHEM_SETUP));
public static final Set<FightState> PostSchemSetup = Collections.unmodifiableSet(EnumSet.of(POST_SCHEM_SETUP));
public static final Set<FightState> PreRunning = Collections.unmodifiableSet(EnumSet.of(PRE_RUNNING));
public static final Set<FightState> Running = Collections.unmodifiableSet(EnumSet.of(RUNNING));
public static final Set<FightState> Spectate = Collections.unmodifiableSet(EnumSet.of(SPECTATE));
public static final GameStateMachine<FightState> lifecycle = new GameStateMachine<>(PRE_LEADER_SETUP);
public static final Set<FightState> Setup = Collections.unmodifiableSet(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, POST_SCHEM_SETUP));
public static final Set<FightState> Ingame = Collections.unmodifiableSet(EnumSet.of(PRE_RUNNING, RUNNING));
public static final Set<FightState> TeamFix = Collections.unmodifiableSet(EnumSet.of(PRE_RUNNING, RUNNING, SPECTATE));
public static final Set<FightState> Schem = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP)));
public static final Set<FightState> Recording = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PRE_LEADER_SETUP, PRE_SCHEM_SETUP, SPECTATE)));
public static final Set<FightState> AntiRunning = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(RUNNING)));
public static final Set<FightState> AntiIngame = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(PRE_RUNNING, RUNNING)));
public static final Set<FightState> AntiSpectate = Collections.unmodifiableSet(EnumSet.complementOf(EnumSet.of(SPECTATE)));
@Getter
private static final Map<IStateDependent, Boolean> 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<FightState> stateDependent) {
lifecycle.registerStateDependent(stateDependent);
}
public static void setFightState(FightState state) {
fightState = state;
lifecycle.transitionTo(state);
}
for (Map.Entry<IStateDependent, Boolean> 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<IStateDependent<FightState>, 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<FightState> complement(FightState first, FightState... remaining) {
EnumSet<FightState> excluded = EnumSet.of(first, remaining);
return immutable(EnumSet.complementOf(excluded));
}
private static Set<FightState> immutable(EnumSet<FightState> states) {
return Collections.unmodifiableSet(states);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
package de.steamwar.fightsystem.states;
import java.util.Set;
public interface IStateDependent {
/**
* @return returns a set containing
*/
Set<FightState> 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<FightState> {
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<ArenaMode> mode, Set<FightState> states, Runnable runnable) {
super(mode, states);
this.runnable = runnable;
register();
@Deprecated
public class OneShotStateDependent extends de.steamwar.gamecore.state.OneShotStateDependent<FightState> implements IStateDependent {
public OneShotStateDependent(Set<de.steamwar.gamecore.ArenaMode> modes, Set<FightState> states, Runnable runnable) {
super(FightState.lifecycle, modes, Config.mode, states, runnable);
}
public OneShotStateDependent(Winconditions wincondition, Set<FightState> 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<FightState> 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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<FightState> enabled;
private final boolean register;
@Deprecated
public abstract class StateDependent extends de.steamwar.gamecore.state.StateDependent<FightState> implements IStateDependent {
protected StateDependent(Winconditions wincondition, Set<FightState> states) {
this(Config.GameModeConfig.WinConditions.contains(wincondition), states);
}
protected StateDependent(Set<ArenaMode> mode, Set<FightState> states) {
this(mode.contains(Config.mode), states);
protected StateDependent(Set<de.steamwar.gamecore.ArenaMode> modes, Set<FightState> states) {
super(FightState.lifecycle, modes, Config.mode, states);
}
protected StateDependent(boolean enabled, Set<FightState> states) {
this.enabled = states;
this.register = enabled;
}
public void register() {
if (register) FightState.registerStateDependent(this);
}
@Override
public Set<FightState> enabled() {
return enabled;
super(FightState.lifecycle, enabled, states);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<FightState> 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<ArenaMode> mode, Set<FightState> 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<de.steamwar.gamecore.ArenaMode> modes, Set<FightState> states, String name, CommandExecutor executor) {
super(FightState.lifecycle, modes.contains(Config.mode), states, FightSystem.getPlugin(), name, executor, unavailable);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<ArenaMode> mode, Set<FightState> states, Countdown countdown) {
super(mode, states);
this.countdown = countdown;
register();
}
public StateDependentCountdown(Winconditions wincondition, Set<FightState> states, Countdown countdown) {
this(Config.GameModeConfig.WinConditions.contains(wincondition), states, countdown);
}
public StateDependentCountdown(boolean active, Set<FightState> 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;
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<FightState> implements IStateDependent {
public StateDependentListener(Winconditions wincondition, Set<FightState> 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<FightState> states, Listener listener) {
super(enabled, states);
this.listener = listener;
register();
public StateDependentListener(boolean active, Set<FightState> states, Listener listener) {
super(FightState.lifecycle, active, states, FightSystem.getPlugin(), listener);
}
public StateDependentListener(Set<ArenaMode> mode, Set<FightState> 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<de.steamwar.gamecore.ArenaMode> modes, Set<FightState> states, Listener listener) {
super(FightState.lifecycle, modes, Config.mode, states, FightSystem.getPlugin(), listener);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<FightState> implements IStateDependent {
public StateDependentTask(Winconditions wincondition, Set<FightState> states, Runnable runnable, long delay, long period) {
this(Config.GameModeConfig.WinConditions.contains(wincondition), states, runnable, delay, period);
}
public StateDependentTask(boolean enabled, Set<FightState> 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<FightState> states, Runnable runnable, long delay, long period) {
super(FightState.lifecycle, active, states, FightSystem.getPlugin(), runnable, delay, period);
}
public StateDependentTask(Set<ArenaMode> mode, Set<FightState> states, Runnable runnable, long delay, long period) {
super(mode, states);
this.runnable = runnable;
this.delay = delay;
this.period = period;
register();
public StateDependentTask(Set<de.steamwar.gamecore.ArenaMode> modes, Set<FightState> 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();
}
}
@@ -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(),
@@ -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;
@@ -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();
@@ -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;
@@ -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<FightTeam> {
@Getter
protected static WinconditionBasePercent percentWincondition = null;
protected static StateDependentCountdown timeOverCountdown = null;
protected static StateDependentCountdown<FightState> timeOverCountdown = null;
@Getter
protected static final List<PrintableWincondition> 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<FightTeam> evaluate, String winMessage, String tieMessage) {
double max = Fight.teams().stream().mapToDouble(evaluate).max().orElseThrow(() -> new SecurityException("No teams present"));
List<FightTeam> teams = Fight.teams().stream().filter(team -> evaluate.applyAsDouble(team) == max).collect(Collectors.toList());
if (teams.size() > 1) {
win(null, tieMessage);
GameResult<FightTeam> result = de.steamwar.gamecore.wincondition.WinConditions.winnerByScore(Fight.teams(), evaluate);
if (result instanceof GameResult.Winner<FightTeam> winner) {
FightTeam team = winner.getWinner();
win(team, winMessage, team.getColoredName());
} else {
win(teams.get(0), winMessage, teams.get(0).getColoredName());
win(null, tieMessage);
}
}
}
@@ -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<FightTeam> 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"))
);
}
}
}
@@ -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)
);
}
}
@@ -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());
}
}
@@ -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());
}
}
@@ -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)
);
}
}
+2 -1
View File
@@ -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:
unrank:
+5
View File
@@ -30,6 +30,11 @@ dependencies {
implementation(project(":FightSystem:FightSystem_Core"))
}
tasks.withType<FightServer>().configureEach {
dependsOn(":KotlinCore:shadowJar")
dependsOn(":GameCore:jar")
}
tasks.register<FightServer>("HalloweenWS") {
group = "run"
description = "Run a Halloween 1.21 Fight Replay Server"
+35
View File
@@ -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<S>` 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.
+22
View File
@@ -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)
}
@@ -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<ArenaMode> = immutable(EnumSet.allOf(ArenaMode::class.java))
@JvmField val Normal: Set<ArenaMode> = immutable(EnumSet.of(NORMAL))
@JvmField val Check: Set<ArenaMode> = immutable(EnumSet.of(CHECK))
@JvmField val Event: Set<ArenaMode> = immutable(EnumSet.of(EVENT))
@JvmField val Test: Set<ArenaMode> = immutable(EnumSet.of(TEST, CHECK))
@JvmField val Prepare: Set<ArenaMode> = immutable(EnumSet.of(PREPARE))
@JvmField val Replay: Set<ArenaMode> = immutable(EnumSet.of(REPLAY, TEST))
@JvmField val AntiReplay: Set<ArenaMode> = complement(REPLAY)
@JvmField val AntiTest: Set<ArenaMode> = complement(TEST, CHECK)
@JvmField val AntiEvent: Set<ArenaMode> = complement(EVENT)
@JvmField val AntiTestCheckPrepare: Set<ArenaMode> = complement(TEST, CHECK, PREPARE)
@JvmField val AntiPrepare: Set<ArenaMode> = complement(PREPARE)
@JvmField val VariableTeams: Set<ArenaMode> = complement(EVENT, REPLAY, CHECK)
@JvmField val ManualTeams: Set<ArenaMode> = complement(EVENT, REPLAY)
@JvmField val RankedEvent: Set<ArenaMode> = immutable(EnumSet.of(EVENT, REPLAY))
@JvmField val Restartable: Set<ArenaMode> = immutable(EnumSet.of(NORMAL, TEST))
@JvmField val NotRestartable: Set<ArenaMode> = immutable(EnumSet.of(EVENT, REPLAY))
@JvmField val SoloLeader: Set<ArenaMode> = immutable(EnumSet.of(TEST, CHECK, PREPARE))
@JvmField val NotOnBau: Set<ArenaMode> = complement(TEST, CHECK, PREPARE, REPLAY)
@JvmField val SeriousFight: Set<ArenaMode> = 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<ArenaMode> =
immutable(EnumSet.complementOf(EnumSet.copyOf(modes.toSet())))
private fun immutable(modes: EnumSet<ArenaMode>): Set<ArenaMode> =
Collections.unmodifiableSet(modes)
}
}
@@ -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()
@@ -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)
}
}
@@ -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<Countdown>()
/**
* 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)
}
}
@@ -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<S : Enum<S>> {
fun onStateChange(oldState: S, newState: S)
}
/**
* Small state holder used by game modes instead of duplicating listener lists
* and transition notification code.
*/
class GameStateMachine<S : Enum<S>>(initialState: S) {
private val listeners = CopyOnWriteArrayList<GameStateListener<S>>()
private val stateDependents = LinkedHashMap<IStateDependent<S>, Boolean>()
@Volatile
var currentState: S = initialState
private set
val stateDependentFeatures: Map<IStateDependent<S>, Boolean>
get() = Collections.unmodifiableMap(LinkedHashMap(stateDependents))
fun registerStateDependent(stateDependent: IStateDependent<S>) {
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<S>) {
listeners.add(listener)
}
fun removeListener(listener: GameStateListener<S>) {
listeners.remove(listener)
}
}
@@ -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<S : Enum<S>> {
fun enabled(): Set<S>
fun enable()
fun disable()
}
abstract class StateDependent<S : Enum<S>> protected constructor(
protected val lifecycle: GameStateMachine<S>,
private val active: Boolean,
private val enabledStates: Set<S>,
) : IStateDependent<S> {
protected constructor(
lifecycle: GameStateMachine<S>,
modes: Set<ArenaMode>,
arenaMode: ArenaMode,
enabledStates: Set<S>,
) : this(lifecycle, arenaMode in modes, enabledStates)
final override fun enabled(): Set<S> = enabledStates
fun register() {
if (active) lifecycle.registerStateDependent(this)
}
}
open class OneShotStateDependent<S : Enum<S>>(
lifecycle: GameStateMachine<S>,
active: Boolean,
enabledStates: Set<S>,
private val runnable: Runnable,
) : StateDependent<S>(lifecycle, active, enabledStates) {
constructor(
lifecycle: GameStateMachine<S>,
modes: Set<ArenaMode>,
arenaMode: ArenaMode,
enabledStates: Set<S>,
runnable: Runnable,
) : this(lifecycle, arenaMode in modes, enabledStates, runnable)
init {
register()
}
override fun enable() = runnable.run()
override fun disable() = Unit
}
open class StateDependentTask<S : Enum<S>>(
lifecycle: GameStateMachine<S>,
active: Boolean,
enabledStates: Set<S>,
private val plugin: Plugin,
private val runnable: Runnable,
private val delay: Long,
private val period: Long,
) : StateDependent<S>(lifecycle, active, enabledStates) {
constructor(
lifecycle: GameStateMachine<S>,
modes: Set<ArenaMode>,
arenaMode: ArenaMode,
enabledStates: Set<S>,
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<S : Enum<S>>(
lifecycle: GameStateMachine<S>,
active: Boolean,
enabledStates: Set<S>,
private val plugin: Plugin,
private val listener: Listener,
) : StateDependent<S>(lifecycle, active, enabledStates) {
constructor(
lifecycle: GameStateMachine<S>,
modes: Set<ArenaMode>,
arenaMode: ArenaMode,
enabledStates: Set<S>,
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<S : Enum<S>>(
lifecycle: GameStateMachine<S>,
active: Boolean,
enabledStates: Set<S>,
plugin: JavaPlugin,
name: String,
private val executor: CommandExecutor,
private val unavailable: CommandExecutor,
) : StateDependent<S>(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<S : Enum<S>>(
lifecycle: GameStateMachine<S>,
active: Boolean,
enabledStates: Set<S>,
open val countdown: Countdown,
) : StateDependent<S>(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<GameStateMachine<*>, Countdown>()
@JvmStatic
fun getMainCountdown(lifecycle: GameStateMachine<*>): Countdown? =
mainCountdowns[lifecycle]
}
}
@@ -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<M> {
fun teamId(): String
fun members(): Collection<M>
fun leader(): M? = null
fun ready(): Boolean = false
fun contains(member: M): Boolean = members().contains(member)
}
class TeamRegistry<T : GameTeam<M>, M>(teams: Collection<T>) {
private val teamsById = LinkedHashMap<String, T>()
init {
teams.forEach { team ->
require(teamsById.put(team.teamId().lowercase(Locale.ROOT), team) == null) {
"Duplicate team id: ${team.teamId()}"
}
}
}
val teams: Collection<T>
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 }
}
}
@@ -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<T> {
fun id(): String
fun active(): Boolean = true
}
sealed interface GameResult<out T> {
data class Winner<T>(val winner: T) : GameResult<T>
data object Draw : GameResult<Nothing>
}
object WinConditions {
/**
* Selects the unique highest scoring entry or returns a draw.
*/
@JvmStatic
fun <T> winnerByScore(
entries: Collection<T>,
score: ToDoubleFunction<T>,
): GameResult<T> {
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 <T> lastRemaining(entries: Collection<T>): GameResult<T> =
if (entries.size == 1) GameResult.Winner(entries.single()) else GameResult.Draw
}

Some files were not shown because too many files have changed in this diff Show More