forked from beyond-all-reason/Beyond-All-Reason
-
Notifications
You must be signed in to change notification settings - Fork 0
campaign api: hello pawn #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ac56a66
modules: borrow the loader bootstrap from the modules branch
keithharvey cac9a3d
missions: trigger engine + dot-only authoring DSL, with specs
keithharvey dac82fc
missions: matchflow verdict path, mission loader, and the hello_pawns…
keithharvey 1e35de6
tests: hello_pawns integration test
keithharvey 9ee7f18
missions: Has() counts completed units only, not nanoframes
keithharvey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| ---@diagnostic disable: lowercase-global, undefined-field | ||
|
|
||
| -- Plays the hello_pawns mission end to end: load it via the chat command, | ||
| -- reach 3 Pawns, expect scripted victory through the matchflow verdict path. | ||
| -- Fails if DSL registration breaks, the loader env is missing a verb, the | ||
| -- condition never fires, or the verdict path is disconnected. | ||
| -- | ||
| -- NOTE: this test really ends the game (Spring.GameOver). If it disturbs | ||
| -- later tests in a full suite run, scope the run: `runtestsheadless hello_pawns`. | ||
|
|
||
| local PAWN = "armpw" | ||
| local PAWN_COUNT = 3 | ||
|
|
||
| function skip() | ||
| return Spring.GetGameFrame() <= 0 | ||
| end | ||
|
|
||
| function setup() | ||
| Test.clearMap() | ||
| end | ||
|
|
||
| function cleanup() | ||
| Test.clearMap() | ||
| end | ||
|
|
||
| function test() | ||
| -- SyncedRun ships the caller's stack locals (not upvalues), so copy the | ||
| -- file-level config into locals for the spawn block below. | ||
| local pawnName = PAWN | ||
| local pawnCount = PAWN_COUNT | ||
| local teamID = Spring.GetLocalTeamID() | ||
| local _, _, _, _, _, allyTeamID = Spring.GetTeamInfo(teamID, false) | ||
| local pawnDefID = UnitDefNames[PAWN].id | ||
|
|
||
| -- Arm the mission via the same chat command a player would use. | ||
| Spring.SendCommands("luarules mission hello_pawns") | ||
| Test.waitUntil(function() | ||
| return Spring.GetGameRulesParam("mission_active") == 1 | ||
| end) | ||
|
|
||
| -- Buffer GameOver before anything can win. | ||
| Test.expectCallin("GameOver") | ||
|
|
||
| -- Cheat-spawn toward the objective instead of really building: two finished | ||
| -- Pawns plus one nanoframe. Has() means COMPLETED units — the nanoframe | ||
| -- must not win the mission (the factory-blueprint bug). | ||
| local x = Game.mapSizeX / 2 | ||
| local z = Game.mapSizeZ / 2 | ||
| local frameUnitID = SyncedRun(function(locals) | ||
| for i = 1, locals.pawnCount - 1 do | ||
| Spring.CreateUnit(locals.pawnName, locals.x + i * 32, Spring.GetGroundHeight(locals.x + i * 32, locals.z), locals.z, 0, locals.teamID) | ||
| end | ||
| -- The nanoframe needs a live builder: unit_prevent_lab_hax2 destroys | ||
| -- fresh under-construction units whose builder is dead/absent. | ||
| local builderID = Spring.CreateUnit("armck", locals.x - 64, Spring.GetGroundHeight(locals.x - 64, locals.z), locals.z, 0, locals.teamID) | ||
| local unitID = Spring.CreateUnit(locals.pawnName, locals.x, Spring.GetGroundHeight(locals.x, locals.z), locals.z, 0, locals.teamID, true, true, nil, builderID) | ||
| if unitID ~= nil then | ||
| -- Give it real progress so abandoned-nanoframe decay stays far away. | ||
| Spring.SetUnitHealth(unitID, { build = 0.8 }) | ||
| end | ||
| return unitID | ||
| end) | ||
| assert(frameUnitID ~= nil, "failed to spawn the nanoframe Pawn") | ||
|
|
||
| Test.waitUntil(function() | ||
| return Spring.GetTeamUnitDefCount(teamID, pawnDefID) >= PAWN_COUNT | ||
| end) | ||
|
|
||
| -- Two evaluation cadences with the third Pawn still a nanoframe: no verdict. | ||
| Test.waitFrames(2 * 15 + 2) | ||
| assert(Spring.GetGameRulesParam("objective_build_pawns") ~= 1, "mission must not complete while the third Pawn is a nanoframe") | ||
|
|
||
| -- Finish the build; NOW the mission should be won. | ||
| SyncedRun(function(locals) | ||
| Spring.SetUnitHealth(locals.frameUnitID, { build = 1 }) | ||
| end) | ||
|
|
||
| -- The mission's effect chain: objective completes, then scripted victory | ||
| -- for the player's ally team. | ||
| Test.waitUntilCallin("GameOver", function(winningAllyTeams) | ||
| if type(winningAllyTeams) ~= "table" then | ||
| return false | ||
| end | ||
| for _, winner in ipairs(winningAllyTeams) do | ||
| if winner == allyTeamID then | ||
| return true | ||
| end | ||
| end | ||
| return false | ||
| end, 10 * 30) | ||
|
|
||
| assert(Spring.GetGameRulesParam("objective_build_pawns") == 1, "objective build_pawns should be complete before victory") | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| --- Synced contract of the matchflow module — DEMO-MINIMAL: the scripted-verdict | ||
| --- path only, not the game_end extraction (that is matchflow_module_plan.md, | ||
| --- later). The point of this file is the call shape: `MatchFlow.Victory(...)` | ||
| --- survives unchanged when the real module lands underneath it. | ||
| --- | ||
| --- The pending verdict lives in the verdict gadget (one owner); contract | ||
| --- includes are per-consumer, so this file holds no state and forwards | ||
| --- through the gadget's GG surface. | ||
|
|
||
| ---@param name string | ||
| ---@return table | ||
| local function gadgetSurface(name) | ||
| local surface = GG.MatchFlow | ||
| assert(surface ~= nil, "MatchFlow." .. name .. " called before the matchflow_verdict gadget initialized") | ||
| return surface | ||
| end | ||
|
|
||
| return { | ||
| ---Scripted victory: the given ally team wins on the next verdict tick. | ||
| ---@param allyTeamID integer | ||
| Victory = function(allyTeamID) | ||
| gadgetSurface("Victory").Victory(allyTeamID) | ||
| end, | ||
|
|
||
| ---Scripted defeat: every other ally team (bar Gaia) wins. | ||
| ---@param allyTeamIDs integer[] | ||
| Defeat = function(allyTeamIDs) | ||
| gadgetSurface("Defeat").Defeat(allyTeamIDs) | ||
| end, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| local gadget = gadget ---@type Gadget | ||
|
|
||
| function gadget:GetInfo() | ||
| return { | ||
| name = "MatchFlow Verdict", | ||
| desc = "Scripted-verdict path of the matchflow module: applies pending Victory/Defeat verdicts (demo-minimal; the game_end extraction comes later)", | ||
| author = "Beyond All Reason", | ||
| date = "July 2026", | ||
| license = "GNU GPL, v2 or later", | ||
| layer = 0, | ||
| enabled = true, | ||
| } | ||
| end | ||
|
|
||
| if not gadgetHandler:IsSyncedCode() then | ||
| return | ||
| end | ||
|
|
||
| -- DEMO-ONLY coexistence: game_end.lua still runs its own end conditions, the | ||
| -- same way missions coexist with it today (the scenariooptions path). This | ||
| -- gadget only ever ends the game when a mission scripted a verdict. | ||
|
|
||
| ---@type integer[]|nil winning ally team ids; applied on the next GameFrame | ||
| local pendingWinners = nil | ||
|
|
||
| ---@param allyTeamID integer | ||
| local function Victory(allyTeamID) | ||
| pendingWinners = { allyTeamID } | ||
| end | ||
|
|
||
| ---@param losers integer[] | ||
| local function Defeat(losers) | ||
| local losing = {} | ||
| for _, allyTeamID in ipairs(losers) do | ||
| losing[allyTeamID] = true | ||
| end | ||
| local gaiaAllyTeam = select(6, Spring.GetTeamInfo(Spring.GetGaiaTeamID(), false)) | ||
| local winners = {} | ||
| for _, allyTeamID in ipairs(Spring.GetAllyTeamList()) do | ||
| if not losing[allyTeamID] and allyTeamID ~= gaiaAllyTeam then | ||
| winners[#winners + 1] = allyTeamID | ||
| end | ||
| end | ||
| pendingWinners = winners | ||
| end | ||
|
|
||
| function gadget:Initialize() | ||
| GG.MatchFlow = { | ||
| Victory = Victory, | ||
| Defeat = Defeat, | ||
| } | ||
| end | ||
|
|
||
| function gadget:Shutdown() | ||
| GG.MatchFlow = nil | ||
| end | ||
|
|
||
| function gadget:GameFrame() | ||
| if pendingWinners ~= nil then | ||
| local winners = pendingWinners | ||
| pendingWinners = nil | ||
| Spring.GameOver(winners) | ||
| end | ||
| end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these busted globals are unfortunately correct because transforms hasn't merged yet.