Mission 03: THE DROP — campaign finale
Full base-building map against a live Stream bot holding The Algorithm behind a defended base. Building the Mastering Studio triggers a scripted all-in counterattack; silencing The Algorithm wins. Tick scan throttled to 1Hz. SIDE A is now three missions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c09da56dbe
commit
ef432e4182
BIN
assets_src/m03_algorithm.png
Normal file
BIN
assets_src/m03_algorithm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 KiB |
BIN
assets_src/map_m03.png
Normal file
BIN
assets_src/map_m03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@ -9,3 +9,8 @@ keep-the-van-alive = Do not lose the Pop-Up Shop
|
||||
destroy-the-transcoder = Destroy the Transcoder before the master is cut
|
||||
keep-the-selector-alive = The Selector must survive
|
||||
wipe-the-outpost = Level the whole outpost
|
||||
|
||||
build-the-studio = Build a Mastering Studio
|
||||
silence-the-algorithm = Silence The Algorithm
|
||||
keep-the-shop-standing = Keep the Pop-Up Shop standing
|
||||
it-heard-you = THE ALGORITHM HAS NOTICED YOU
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
59
mods/vinylgod/maps/m03/m03.lua
Normal file
59
mods/vinylgod/maps/m03/m03.lua
Normal file
@ -0,0 +1,59 @@
|
||||
-- 03: THE DROP — finale.
|
||||
|
||||
CounterattackDone = false
|
||||
Ticks = 0
|
||||
|
||||
WorldLoaded = function()
|
||||
Mrp = Player.GetPlayer("Player")
|
||||
Stream = Player.GetPlayer("Enemy")
|
||||
InitObjectives(Mrp)
|
||||
|
||||
StudioObjective = Mrp.AddPrimaryObjective("build-the-studio")
|
||||
AlgorithmObjective = Mrp.AddPrimaryObjective("silence-the-algorithm")
|
||||
ShopObjective = Mrp.AddSecondaryObjective("keep-the-shop-standing")
|
||||
|
||||
Trigger.OnKilled(TheAlgorithm, function()
|
||||
Mrp.MarkCompletedObjective(AlgorithmObjective)
|
||||
end)
|
||||
|
||||
-- losing the HQ costs the secondary, not the mission
|
||||
Trigger.OnKilled(TheVan, function()
|
||||
Trigger.AfterDelay(DateTime.Seconds(2), function()
|
||||
local hq = Utils.Where(Map.ActorsInWorld, function(a)
|
||||
return a.Owner == Mrp and a.Type == "popupshop"
|
||||
end)
|
||||
if #hq == 0 then
|
||||
Mrp.MarkFailedObjective(ShopObjective)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
Tick = function()
|
||||
Ticks = Ticks + 1
|
||||
-- once a second is plenty; scanning every actor every frame is wasteful
|
||||
if Ticks % 25 ~= 0 then
|
||||
return
|
||||
end
|
||||
if StudioObjective == nil or Mrp.IsObjectiveCompleted(StudioObjective) then
|
||||
return
|
||||
end
|
||||
|
||||
local studios = Utils.Where(Map.ActorsInWorld, function(a)
|
||||
return a.Owner == Mrp and a.Type == "mastering"
|
||||
end)
|
||||
|
||||
if #studios > 0 then
|
||||
Mrp.MarkCompletedObjective(StudioObjective)
|
||||
if not CounterattackDone then
|
||||
CounterattackDone = true
|
||||
Media.DisplayMessage(UserInterface.GetFluentMessage("it-heard-you"))
|
||||
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
|
||||
end
|
||||
BIN
mods/vinylgod/maps/m03/map.bin
Normal file
BIN
mods/vinylgod/maps/m03/map.bin
Normal file
Binary file not shown.
BIN
mods/vinylgod/maps/m03/map.png
Normal file
BIN
mods/vinylgod/maps/m03/map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
152
mods/vinylgod/maps/m03/map.yaml
Normal file
152
mods/vinylgod/maps/m03/map.yaml
Normal file
@ -0,0 +1,152 @@
|
||||
MapFormat: 12
|
||||
|
||||
RequiresMod: vinylgod
|
||||
|
||||
Title: 03 - The Drop
|
||||
|
||||
Author: Monster Robot Party
|
||||
|
||||
Tileset: VINYLGOD
|
||||
|
||||
MapSize: 66,66
|
||||
|
||||
Bounds: 1,1,64,64
|
||||
|
||||
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
|
||||
Bot: wax
|
||||
Enemies: Player
|
||||
|
||||
Actors:
|
||||
spawn0: mpspawn
|
||||
Owner: Neutral
|
||||
Location: 10,54
|
||||
TheVan: popupvan
|
||||
Owner: Player
|
||||
Location: 10,54
|
||||
dig1: digger
|
||||
Owner: Player
|
||||
Location: 13,54
|
||||
dig2: digger
|
||||
Owner: Player
|
||||
Location: 12,56
|
||||
guard1: stacktank
|
||||
Owner: Player
|
||||
Location: 15,52
|
||||
guard2: spinner
|
||||
Owner: Player
|
||||
Location: 16,55
|
||||
guard3: djgrunt
|
||||
Owner: Player
|
||||
Location: 14,57
|
||||
guard4: djgrunt
|
||||
Owner: Player
|
||||
Location: 15,57
|
||||
TheAlgorithm: algorithm
|
||||
Owner: Enemy
|
||||
Location: 52,8
|
||||
ehub: streamhub
|
||||
Owner: Enemy
|
||||
Location: 48,10
|
||||
eproc1: transcoder
|
||||
Owner: Enemy
|
||||
Location: 44,8
|
||||
eproc2: transcoder
|
||||
Owner: Enemy
|
||||
Location: 55,14
|
||||
enode1: datacentre
|
||||
Owner: Enemy
|
||||
Location: 50,13
|
||||
enode2: datacentre
|
||||
Owner: Enemy
|
||||
Location: 54,11
|
||||
epods: podfarm
|
||||
Owner: Enemy
|
||||
Location: 45,13
|
||||
eprint: printfarm
|
||||
Owner: Enemy
|
||||
Location: 49,16
|
||||
eport: droneport
|
||||
Owner: Enemy
|
||||
Location: 56,18
|
||||
etur1: adblocker
|
||||
Owner: Enemy
|
||||
Location: 46,17
|
||||
etur2: adblocker
|
||||
Owner: Enemy
|
||||
Location: 52,18
|
||||
etur3: adblocker
|
||||
Owner: Enemy
|
||||
Location: 43,11
|
||||
escrap1: scraper
|
||||
Owner: Enemy
|
||||
Location: 47,20
|
||||
escrap2: scraper
|
||||
Owner: Enemy
|
||||
Location: 50,20
|
||||
emob1: drmwalker
|
||||
Owner: Enemy
|
||||
Location: 44,20
|
||||
emob2: drmwalker
|
||||
Owner: Enemy
|
||||
Location: 53,22
|
||||
emob3: bufferbot
|
||||
Owner: Enemy
|
||||
Location: 46,22
|
||||
emob4: bufferbot
|
||||
Owner: Enemy
|
||||
Location: 47,22
|
||||
emob5: bufferbot
|
||||
Owner: Enemy
|
||||
Location: 48,22
|
||||
mid1: adblocker
|
||||
Owner: Enemy
|
||||
Location: 34,28
|
||||
mid2: bufferbot
|
||||
Owner: Enemy
|
||||
Location: 33,30
|
||||
mid3: bufferbot
|
||||
Owner: Enemy
|
||||
Location: 35,30
|
||||
waxseed0: waxseed
|
||||
Owner: Neutral
|
||||
Location: 12,30
|
||||
waxseed1: waxseed
|
||||
Owner: Neutral
|
||||
Location: 14,50
|
||||
waxseed2: waxseed
|
||||
Owner: Neutral
|
||||
Location: 32,32
|
||||
waxseed3: waxseed
|
||||
Owner: Neutral
|
||||
Location: 50,14
|
||||
waxseed4: waxseed
|
||||
Owner: Neutral
|
||||
Location: 52,34
|
||||
|
||||
Rules: vinylgod|rules/campaign.yaml, rules.yaml
|
||||
|
||||
FluentMessages: vinylgod|fluent/lua.ftl
|
||||
5
mods/vinylgod/maps/m03/rules.yaml
Normal file
5
mods/vinylgod/maps/m03/rules.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
world:
|
||||
LuaScript:
|
||||
Scripts: campaign.lua, m03.lua
|
||||
MissionData:
|
||||
Briefing: The Algorithm has surfaced. It is sitting in the north-east behind everything it owns, scoring us.\n\nBuild a base. Build a Mastering Studio. Charge the orbital tonearm.\n\nThen put the needle through it.\n\nIt will hear the studio go up, and it will come for you. Let it.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +1,4 @@
|
||||
SIDE A - Monster Robot Party:
|
||||
m01
|
||||
m02
|
||||
m03
|
||||
|
||||
124
tools/gen_mission3.py
Normal file
124
tools/gen_mission3.py
Normal file
@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate campaign mission 03 — THE DROP (finale: full base vs a live Stream base)."""
|
||||
import os, struct
|
||||
from PIL import Image
|
||||
|
||||
SIZE = 64
|
||||
W = H = SIZE + 2
|
||||
MAP = os.path.join(os.path.dirname(__file__), '..', 'mods', 'vinylgod', 'maps', 'm03')
|
||||
os.makedirs(MAP, exist_ok=True)
|
||||
|
||||
WAX = [(14, 50, 6), (24, 40, 5), (32, 32, 6), (40, 24, 5), (50, 14, 6), (12, 30, 4), (52, 34, 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', 10, 54),
|
||||
# the Party
|
||||
('TheVan', 'popupvan', 'Player', 10, 54),
|
||||
('dig1', 'digger', 'Player', 13, 54),
|
||||
('dig2', 'digger', 'Player', 12, 56),
|
||||
('guard1', 'stacktank', 'Player', 15, 52),
|
||||
('guard2', 'spinner', 'Player', 16, 55),
|
||||
('guard3', 'djgrunt', 'Player', 14, 57),
|
||||
('guard4', 'djgrunt', 'Player', 15, 57),
|
||||
# the Algorithm's seat
|
||||
('TheAlgorithm', 'algorithm', 'Enemy', 52, 8),
|
||||
('ehub', 'streamhub', 'Enemy', 48, 10),
|
||||
('eproc1', 'transcoder', 'Enemy', 44, 8),
|
||||
('eproc2', 'transcoder', 'Enemy', 55, 14),
|
||||
('enode1', 'datacentre', 'Enemy', 50, 13),
|
||||
('enode2', 'datacentre', 'Enemy', 54, 11),
|
||||
('epods', 'podfarm', 'Enemy', 45, 13),
|
||||
('eprint', 'printfarm', 'Enemy', 49, 16),
|
||||
('eport', 'droneport', 'Enemy', 56, 18),
|
||||
('etur1', 'adblocker', 'Enemy', 46, 17),
|
||||
('etur2', 'adblocker', 'Enemy', 52, 18),
|
||||
('etur3', 'adblocker', 'Enemy', 43, 11),
|
||||
('escrap1', 'scraper', 'Enemy', 47, 20),
|
||||
('escrap2', 'scraper', 'Enemy', 50, 20),
|
||||
('emob1', 'drmwalker', 'Enemy', 44, 20),
|
||||
('emob2', 'drmwalker', 'Enemy', 53, 22),
|
||||
('emob3', 'bufferbot', 'Enemy', 46, 22),
|
||||
('emob4', 'bufferbot', 'Enemy', 47, 22),
|
||||
('emob5', 'bufferbot', 'Enemy', 48, 22),
|
||||
# forward picket in the middle
|
||||
('mid1', 'adblocker', 'Enemy', 34, 28),
|
||||
('mid2', 'bufferbot', 'Enemy', 33, 30),
|
||||
('mid3', 'bufferbot', 'Enemy', 35, 30),
|
||||
]
|
||||
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: 03 - The Drop
|
||||
|
||||
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
|
||||
Bot: wax
|
||||
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) if o == 'Enemy' else (240, 210, 90))
|
||||
im.resize((W * 3, H * 3), Image.NEAREST).save(os.path.join(MAP, 'map.png'))
|
||||
print('m03 written')
|
||||
Loading…
Reference in New Issue
Block a user