Round 5b: SIDE A campaign — mission 01 FIRST PRESSING with scripted objectives, briefing, timed Stream probe; shared campaign.lua, campaign rules, objectives panel, missions registry

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-25 19:12:35 +10:00
parent 5255aeeb1f
commit 93ad888c32
12 changed files with 295 additions and 0 deletions

View File

@ -0,0 +1,7 @@
new-objective = New objective
objective-completed = Objective completed
objective-failed = Objective failed
press-first-records = Build a Pressing Plant and bank 2000 records
survive-the-probe = Survive the Stream's survey probe
keep-the-van-alive = Do not lose the Pop-Up Shop

View File

@ -0,0 +1,46 @@
-- 01: FIRST PRESSING
TARGET_RECORDS = 2000
PROBE_DELAY = DateTime.Minutes(4)
WorldLoaded = function()
-- do NOT shadow the `Player` global: Player.GetPlayer is still needed below
Mrp = Player.GetPlayer("Player")
Stream = Player.GetPlayer("Enemy")
InitObjectives(Mrp)
PressObjective = Mrp.AddPrimaryObjective("press-first-records")
VanObjective = Mrp.AddSecondaryObjective("keep-the-van-alive")
-- lose the HQ, lose the secondary
local hq = Utils.Where(Map.ActorsInWorld, function(a)
return a.Owner == Mrp and (a.Type == "popupvan" or a.Type == "popupshop")
end)
if #hq > 0 then
Trigger.OnAllKilled(hq, function()
Mrp.MarkFailedObjective(VanObjective)
end)
end
-- the probe
Trigger.AfterDelay(PROBE_DELAY, function()
Media.PlaySpeechNotification(Mrp, "BaseAttack")
Utils.Do(Map.ActorsInWorld, function(a)
if a.Owner == Stream and a.HasProperty("Hunt") then
a.Hunt()
end
end)
end)
end
Tick = function()
if PressObjective ~= nil and not Mrp.IsObjectiveCompleted(PressObjective) then
local banked = Mrp.Cash + Mrp.Resources
local plants = Utils.Where(Map.ActorsInWorld, function(a)
return a.Owner == Mrp and a.Type == "pressplant"
end)
if #plants > 0 and banked >= TARGET_RECORDS then
Mrp.MarkCompletedObjective(PressObjective)
end
end
end

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

View File

@ -0,0 +1,94 @@
MapFormat: 12
RequiresMod: vinylgod
Title: 01 - First Pressing
Author: Monster Robot Party
Tileset: VINYLGOD
MapSize: 50,50
Bounds: 1,1,48,48
Visibility: MissionSelector
Categories: Campaign
LockPreview: True
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: vinylgod
PlayerReference@Player:
Name: Player
Playable: True
Required: True
AllowBots: False
LockFaction: True
Faction: vinylgod
LockSpawn: True
LockTeam: True
Enemies: Enemy
PlayerReference@Enemy:
Name: Enemy
Faction: stream
Enemies: Player
Actors:
spawn0: mpspawn
Owner: Neutral
Location: 9,38
van: popupvan
Owner: Player
Location: 9,38
dig: digger
Owner: Player
Location: 12,38
grunt1: djgrunt
Owner: Player
Location: 11,41
grunt2: djgrunt
Owner: Player
Location: 12,41
outpost: transcoder
Owner: Enemy
Location: 38,9
pods: podfarm
Owner: Enemy
Location: 41,12
node: datacentre
Owner: Enemy
Location: 36,12
probe1: bufferbot
Owner: Enemy
Location: 38,14
probe2: bufferbot
Owner: Enemy
Location: 39,14
probe3: bufferbot
Owner: Enemy
Location: 40,14
probe4: crawler
Owner: Enemy
Location: 37,15
waxseed0: waxseed
Owner: Neutral
Location: 9,43
waxseed1: waxseed
Owner: Neutral
Location: 14,34
waxseed2: waxseed
Owner: Neutral
Location: 25,25
waxseed3: waxseed
Owner: Neutral
Location: 40,14
Rules: vinylgod|rules/campaign.yaml, rules.yaml
FluentMessages: vinylgod|fluent/lua.ftl

View File

@ -0,0 +1,5 @@
world:
LuaScript:
Scripts: campaign.lua, m01.lua
MissionData:
Briefing: The Party wakes up in the landfill with a van, a digger and a wax field.\n\nGet a Pressing Plant running and bank 2000 records before the Stream finishes counting what is down here.\n\nThey will send a probe. They always send a probe.

View File

@ -0,0 +1,2 @@
SIDE A - Monster Robot Party:
m01

View File

@ -8,6 +8,7 @@ FileSystem: DefaultFileSystem
^EngineDir
$vinylgod: vinylgod
^EngineDir|mods/common: common
vinylgod|scripts
MapFolders:
vinylgod|maps: System

View File

@ -0,0 +1,10 @@
# Applied by campaign maps on top of the standard rules.
player:
-ConquestVictoryConditions:
MissionObjectives:
EarlyGameOver: true
GameOverDelay: 3000
world:
ObjectivesPanel:
PanelName: MISSION_OBJECTIVES

View File

@ -46,6 +46,8 @@ world:
PathFinder:
MapOptions:
ScriptTriggers:
ObjectivesPanel:
PanelName: SKIRMISH_STATS
Locomotor@WHEELED:
Name: wheeled
TerrainSpeeds:

View File

@ -0,0 +1,26 @@
-- Shared campaign helpers for SIDE A. Mirrors the engine's campaign.lua shape.
InitObjectives = function(player)
Trigger.OnObjectiveAdded(player, function(p, id)
Media.DisplayMessage(p.GetObjectiveDescription(id),
UserInterface.GetFluentMessage("new-objective"))
end)
Trigger.OnObjectiveCompleted(player, function(p, id)
Media.DisplayMessage(p.GetObjectiveDescription(id),
UserInterface.GetFluentMessage("objective-completed"))
end)
Trigger.OnObjectiveFailed(player, function(p, id)
Media.DisplayMessage(p.GetObjectiveDescription(id),
UserInterface.GetFluentMessage("objective-failed"))
end)
Trigger.OnPlayerWon(player, function()
Trigger.AfterDelay(DateTime.Seconds(1), function()
Media.PlaySpeechNotification(player, "Win")
end)
end)
Trigger.OnPlayerLost(player, function()
Trigger.AfterDelay(DateTime.Seconds(1), function()
Media.PlaySpeechNotification(player, "Lose")
end)
end)
end

102
tools/gen_mission.py Normal file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Generate campaign mission 01 — FIRST PRESSING (map.bin + map.yaml + preview)."""
import os, struct
from PIL import Image
SIZE = 48
W = H = SIZE + 2
MAP = os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'maps', 'm01')
os.makedirs(MAP, exist_ok=True)
WAX = [(14, 34, 5), (9, 43, 4), (25, 25, 5), (40, 14, 4)]
data = bytearray()
data += struct.pack('<BHH', 2, W, H)
data += struct.pack('<III', 17, 0, 3 * W * H + 17)
data += b'\xff\x00\x00' * (W * H)
res = bytearray(2 * W * H)
for cx, cy, r in WAX:
for x in range(cx - r, cx + r + 1):
for y in range(cy - r, cy + r + 1):
d2 = (x - cx) ** 2 + (y - cy) ** 2
if d2 <= r * r and 0 < x < W - 1 and 0 < y < H - 1:
o = 2 * (x * H + y)
res[o], res[o + 1] = 1, max(2, round(12 * (1 - (d2 ** 0.5) / (r + 1))))
data += res
open(os.path.join(MAP, 'map.bin'), 'wb').write(data)
ACTORS = [
('spawn0', 'mpspawn', 'Neutral', 9, 38),
('van', 'popupvan', 'Player', 9, 38),
('dig', 'digger', 'Player', 12, 38),
('grunt1', 'djgrunt', 'Player', 11, 41),
('grunt2', 'djgrunt', 'Player', 12, 41),
('outpost', 'transcoder', 'Enemy', 38, 9),
('pods', 'podfarm', 'Enemy', 41, 12),
('node', 'datacentre', 'Enemy', 36, 12),
('probe1', 'bufferbot', 'Enemy', 38, 14),
('probe2', 'bufferbot', 'Enemy', 39, 14),
('probe3', 'bufferbot', 'Enemy', 40, 14),
('probe4', 'crawler', 'Enemy', 37, 15),
]
actors = '\n'.join(f'\t{n}: {t}\n\t\tOwner: {o}\n\t\tLocation: {x},{y}' for n, t, o, x, y in ACTORS)
open(os.path.join(MAP, 'map.yaml'), 'w').write(f"""MapFormat: 12
RequiresMod: vinylgod
Title: 01 - First Pressing
Author: Monster Robot Party
Tileset: VINYLGOD
MapSize: {W},{H}
Bounds: 1,1,{SIZE},{SIZE}
Visibility: MissionSelector
Categories: Campaign
LockPreview: True
Players:
PlayerReference@Neutral:
Name: Neutral
OwnsWorld: True
NonCombatant: True
Faction: vinylgod
PlayerReference@Player:
Name: Player
Playable: True
Required: True
AllowBots: False
LockFaction: True
Faction: vinylgod
LockSpawn: True
LockTeam: True
Enemies: Enemy
PlayerReference@Enemy:
Name: Enemy
Faction: stream
Enemies: Player
Actors:
{actors}
Rules: vinylgod|rules/campaign.yaml, rules.yaml
FluentMessages: vinylgod|fluent/lua.ftl
""")
im = Image.new('RGB', (W, H), (55, 50, 52))
for cx, cy, r in WAX:
for x in range(cx - r, cx + r + 1):
for y in range(cy - r, cy + r + 1):
if (x - cx) ** 2 + (y - cy) ** 2 <= r * r and 0 <= x < W and 0 <= y < H:
im.putpixel((x, y), (255, 45, 150))
for _, _, o, x, y in ACTORS:
im.putpixel((x, y), (255, 255, 255) if o == 'Player' else (80, 150, 255))
im.resize((W * 3, H * 3), Image.NEAREST).save(os.path.join(MAP, 'map.png'))
print('m01 written')