singe/testScripts/scene91.singe
2026-09-14 22:32:53 -05:00

308 lines
8.8 KiB
Text

-- Forge tutorial 9, "A rail shooter": a camera on a rail with stops, a painted backdrop and an
-- occluder pillar that is only in the picture, zombies that spawn at each stop and walk to the
-- camera, a gun on the pointer, ragdolls, and the rail moving on. Starts from New game.
TUTORIAL = { number = 9, name = "09-rail", tag = "TUTORIAL9" }
dofile("testScripts/tutorialDrive.singe")
local frames = 0
local stage = 0
local game = nil
local fired = 0
local shot = false
tutorialBegin(nil)
local function lightType(name)
local names = {}
local from, to = 1, 1
for typeName in pairs(FORGE.game.types) do
names[#names + 1] = typeName
end
table.sort(names)
for at, typeName in ipairs(names) do
if typeName == FORGE.typeName then
from = at
end
if typeName == name then
to = at
end
end
for _ = 1, math.abs(to - from) do
key((to > from) and SCANCODE.DOWN or SCANCODE.UP)
end
end
local function ruleGo(index, part)
local guard = 0
while ((FORGE.rule ~= index) or (FORGE.part ~= (part or 0))) and (guard < 60) do
local past = (FORGE.rule > index) or ((FORGE.rule == index) and (FORGE.part > (part or 0)))
key(past and SCANCODE.UP or SCANCODE.DOWN)
guard = guard + 1
end
end
local function meshType(name, w, h, d, r, g, b)
key(SCANCODE.MAIN_2)
key(SCANCODE.A)
form("name", name)
form("look", "mesh")
form("look.shape", "box")
form("look.w", w)
form("look.h", h)
form("look.d", d)
if r then
form("look.r", r)
form("look.g", g)
form("look.b", b)
end
end
local function placeAt(id, x, y, z)
key(SCANCODE.MAIN_1)
key(SCANCODE.A)
form("id", id)
form("x", x)
form("y", y)
form("z", z)
end
local function rule(note, on, filters)
key(SCANCODE.N)
form("note", note)
key(SCANCODE.E)
pick(on)
for _, pair in ipairs(filters or {}) do
form(pair[1], pair[2])
end
end
local function part(partKey, name, fields)
key(partKey)
pick(name)
for _, pair in ipairs(fields or {}) do
form(pair[1], pair[2])
end
end
-- A rail point: K puts one where the editor camera stands, and the form moves it.
local function railPoint(at, x, y, z, lookX, lookY, lookZ, stop)
key(SCANCODE.K)
form("at", at)
form("x", x)
form("y", y)
form("z", z)
form("lookX", lookX)
form("lookY", lookY)
form("lookZ", lookZ)
if stop then
form("stop", stop)
end
end
local function step()
if stage == 1 then
key(SCANCODE.MAIN_1)
selectEntity("hero")
key(SCANCODE.DELETE)
selectEntity("ground")
key(SCANCODE.DELETE)
key(SCANCODE.MAIN_2)
lightType("hero")
key(SCANCODE.DELETE)
lightType("ground")
key(SCANCODE.DELETE)
key(SCANCODE.MAIN_3)
ruleGo(1, 0)
key(SCANCODE.DELETE)
key(SCANCODE.DELETE)
key(SCANCODE.DELETE)
key(SCANCODE.MAIN_1)
forgeSelect(nil)
form("title", "The Dead Yard")
form("vars", "score=0, health=3")
form("layers", "scene3d, overlay")
form("scene3d.fov", 60)
elseif stage == 2 then
-- The room: a floor, the yard as a painting at the back, and the pillar in it as an
-- occluder, so a zombie walks behind a pillar that is only paint.
meshType("floor", 40, 0.2, 60, 70, 75, 90)
form("behaviours", "solid")
meshType("painting", 24, 16, 0.2, 255, 255, 255)
form("look.texture", "yard.png")
form("look.unlit", "true")
meshType("pillar", 1.2, 8, 1.2)
form("look.occluder", "true")
tutorialShot("room")
elseif stage == 3 then
key(SCANCODE.A)
form("name", "camera")
form("look", "none")
form("behaviours", "camera")
form("camera.mode", "rail")
form("camera.track", "rail")
key(SCANCODE.A)
form("name", "gun")
form("look", "none")
form("vars", "ammo=6")
form("behaviours", "gun")
form("gun.player", 1)
form("gun.ammo", 6)
form("gun.reload", "offscreen")
key(SCANCODE.A)
form("name", "zombie")
form("look", "model")
form("look.file", "fox.glb")
form("look.scale", 0.01)
form("look.w", 0.8)
form("look.h", 0.9)
form("look.d", 1.4)
form("vars", "health=2")
form("behaviours", "health, seek, animator, target, timer")
form("health.max", 2)
form("health.ragdoll", "true")
form("health.linger", 2)
form("seek.target", "camera")
form("seek.speed", 1.6)
form("seek.stopAt", 2.2)
form("animator.idle", "Survey")
form("animator.walk", "Walk")
form("animator.attack", "Run")
form("timer.name", "bite")
form("timer.after", 1.5)
form("timer.start", "false")
tutorialShot("zombie")
elseif stage == 4 then
lightType("floor")
placeAt("floor", 0, -0.1, -20)
key(SCANCODE.MAIN_2)
lightType("painting")
placeAt("painting", 0, 6, -30)
key(SCANCODE.MAIN_2)
lightType("pillar")
placeAt("pillar", 0, 4, -22)
key(SCANCODE.MAIN_2)
lightType("camera")
placeAt("camera", 0, 1.6, 0)
key(SCANCODE.MAIN_2)
lightType("gun")
placeAt("gun", 0, 0, 0)
selectEntity("pillar")
tutorialShot("placed")
elseif stage == 5 then
-- The rail: a track of points in time, two of them stops.
key(SCANCODE.MAIN_4)
key(SCANCODE.N)
form("name", "rail")
railPoint(0, 0, 1.6, 0, 0, 1.2, -10)
railPoint(4, 0, 1.6, -6, 0, 1.2, -16, "gate")
railPoint(10, 2, 1.6, -14, 2, 1.2, -24, "pillar")
railPoint(14, 2, 1.6, -18, 2, 1.2, -28)
tutorialShot("rail")
elseif stage == 6 then
key(SCANCODE.MAIN_3)
rule("The gate: two come out of the dark", "stopped", { { "name", "gate" } })
part(SCANCODE.T, "spawn", { { "type", "zombie" }, { "x", -2 }, { "y", 0 }, { "z", -14 } })
part(SCANCODE.T, "spawn", { { "type", "zombie" }, { "x", 2 }, { "y", 0 }, { "z", -15 } })
rule("The pillar: three more, one from behind it", "stopped", { { "name", "pillar" } })
part(SCANCODE.T, "spawn", { { "type", "zombie" }, { "x", -1 }, { "y", 0 }, { "z", -23 } })
part(SCANCODE.T, "spawn", { { "type", "zombie" }, { "x", 0 }, { "y", 0 }, { "z", -26 } })
part(SCANCODE.T, "spawn", { { "type", "zombie" }, { "x", 4 }, { "y", 0 }, { "z", -24 } })
rule("A zombie walks the moment it is made", "spawn", { { "type", "zombie" } })
part(SCANCODE.T, "setState", { { "state", "walk" } })
rule("A shot lands", "hit", { { "type", "zombie" } })
part(SCANCODE.T, "damage", { { "amount", 1 } })
part(SCANCODE.T, "addScore", { { "amount", 100 } })
part(SCANCODE.T, "playSound", { { "file", "hit.wav" } })
tutorialShot("hitRule")
elseif stage == 7 then
rule("It reaches the camera and rears up", "arrived", { { "type", "zombie" } })
part(SCANCODE.T, "setState", { { "state", "attack" } })
part(SCANCODE.T, "timerStart", { { "name", "bite" }, { "after", 1.5 } })
rule("The bite lands unless it was killed in time", "timer", { { "type", "zombie" }, { "name", "bite" } })
part(SCANCODE.C, "inState", { { "state", "attack" } })
part(SCANCODE.T, "addVar", { { "name", "health" }, { "amount", -1 } })
clear("entity")
part(SCANCODE.T, "flash", { { "r", 255 }, { "g", 40 }, { "b", 40 }, { "seconds", 0.3 } })
part(SCANCODE.T, "shake", { { "amount", 0.3 }, { "seconds", 0.4 } })
part(SCANCODE.T, "timerStart", { { "name", "bite" }, { "after", 1.5 } })
rule("The stop is clear: move on", "frame", {})
part(SCANCODE.C, "waiting", { { "entity", "camera" } })
part(SCANCODE.C, "test", { { "expr", 'count("zombie") == 0' } })
part(SCANCODE.T, "pathNext", { { "entity", "camera" } })
rule("Out of health", "frame", {})
part(SCANCODE.C, "test", { { "expr", "health <= 0" } })
part(SCANCODE.C, "once", { { "tag", "over" }, { "scope", "game" } })
part(SCANCODE.T, "say", { { "text", '"YOU ARE DEAD"' }, { "seconds", 2 } })
part(SCANCODE.T, "gameOver", {})
rule("The end of the rail", "railEnd", {})
part(SCANCODE.T, "say", { { "text", '"STAGE CLEAR"' }, { "seconds", 2 } })
ruleGo(1, 1)
form("text", '"score " .. score .. " health " .. health .. " ammo " .. gun.ammo')
tutorialShot("rules")
elseif stage == 8 then
key(SCANCODE.S)
local built = tutorialEnd()
local mine = onOverlayUpdate
dofile(built)
game = onOverlayUpdate
onOverlayUpdate = mine
end
end
function onOverlayUpdate()
frames = frames + 1
if frames % 12 == 0 and stage < 8 then
stage = stage + 1
step()
end
if game ~= nil then
local t = authorTime()
-- At the gate stop, shoot the first zombie through the middle of the picture.
if (t > 5) and (fired < 2) and (authorCount("zombie") > 0) then
local zombie = authorEntity and nil
local sx, sy = sceneProject(-2, 0.6, -14)
fired = fired + 1
authorSetPointer(1, sx, sy)
authorSwitchDown(SWITCH_BUTTON3)
authorSwitchUp(SWITCH_BUTTON3)
end
if (t > 6) and not shot then
shot = true
tutorialShot("playing")
end
if t > 8 then
debugPrint(string.format("%s after 8 seconds: score %s, health %s, %d zombies, camera %s", TUTORIAL.tag, tostring(AUTHOR_VARS.score), tostring(AUTHOR_VARS.health), authorCount("zombie"), authorWaiting(authorEntity("camera")) and "waiting" or "riding"))
if authorCount("zombie") == 0 then
debugPrint(TUTORIAL.tag .. " FAIL the gate stop should have spawned zombies")
end
debugPrint(TUTORIAL.tag .. " RESULT done")
singeQuit()
end
return game()
end
forgeDraw(nil, nil)
return OVERLAY_UPDATED
end