254 lines
6.2 KiB
Text
254 lines
6.2 KiB
Text
-- Forge tutorial 6, "A light gun game": the kit's clip as the disc, a gun on the pointer, hit
|
|
-- boxes drawn on the timeline where the target and the hand are, ammo and a reload off the
|
|
-- picture, and a score. Starts from New game.
|
|
TUTORIAL = { number = 6, name = "06-gun", tag = "TUTORIAL6" }
|
|
dofile("testScripts/tutorialDrive.singe")
|
|
|
|
local frames = 0
|
|
local stage = 0
|
|
local game = nil
|
|
local fired = {}
|
|
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 place(x, y)
|
|
key(SCANCODE.MAIN_1)
|
|
key(SCANCODE.A)
|
|
local entry = forgeRoom().entities[FORGE.selected]
|
|
|
|
forgePress(entry.x, entry.y)
|
|
forgeDrag(x, y)
|
|
forgeRelease()
|
|
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
|
|
|
|
|
|
-- The cursor to a frame, by the leap and step keys, as a reader would.
|
|
local function cursorTo(frame)
|
|
while FORGE.cursor + 10 <= frame do
|
|
key(SCANCODE.RIGHTBRACKET)
|
|
end
|
|
while FORGE.cursor < frame do
|
|
key(SCANCODE.PERIOD)
|
|
end
|
|
while FORGE.cursor > frame do
|
|
key(SCANCODE.COMMA)
|
|
end
|
|
end
|
|
|
|
|
|
-- A key on the selected track at a frame, its box typed.
|
|
local function keyAt(frame, x, y, w, h)
|
|
cursorTo(frame)
|
|
key(SCANCODE.K)
|
|
form("x", x)
|
|
form("y", y)
|
|
form("w", w)
|
|
form("h", h)
|
|
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", "Draw")
|
|
form("vars", "score=0, misses=0")
|
|
form("layers", "disc, overlay")
|
|
form("disc.file", "clip.mkv")
|
|
|
|
elseif stage == 2 then
|
|
-- The gun, and the two things it can hit.
|
|
key(SCANCODE.MAIN_2)
|
|
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", "target")
|
|
form("look", "none")
|
|
form("vars", "health=1")
|
|
form("behaviours", "hitbox, health")
|
|
form("hitbox.track", "target")
|
|
form("health.max", 1)
|
|
key(SCANCODE.A)
|
|
form("name", "hand")
|
|
form("look", "none")
|
|
form("behaviours", "hitbox")
|
|
form("hitbox.track", "hand")
|
|
tutorialShot("types")
|
|
|
|
elseif stage == 3 then
|
|
lightType("gun")
|
|
place(30, 450)
|
|
key(SCANCODE.MAIN_2)
|
|
lightType("target")
|
|
place(450, 210)
|
|
key(SCANCODE.MAIN_2)
|
|
lightType("hand")
|
|
place(600, 210)
|
|
|
|
elseif stage == 4 then
|
|
-- The tracks: where the target is, frame by frame, and where the hand is.
|
|
key(SCANCODE.MAIN_4)
|
|
key(SCANCODE.N)
|
|
form("name", "target")
|
|
keyAt(150, 405, 165, 90, 90)
|
|
tutorialShot("firstKey")
|
|
|
|
elseif stage == 5 then
|
|
keyAt(210, 405, 165, 90, 90)
|
|
key(SCANCODE.N)
|
|
form("name", "hand")
|
|
keyAt(220, 480, 157, 240, 105)
|
|
keyAt(260, 480, 157, 240, 105)
|
|
cursorTo(180)
|
|
tutorialShot("tracks")
|
|
|
|
elseif stage == 6 then
|
|
key(SCANCODE.MAIN_3)
|
|
rule("The target is hit", "hit", { { "type", "target" } })
|
|
part(SCANCODE.T, "addScore", { { "amount", 100 } })
|
|
part(SCANCODE.T, "damage", { { "amount", 1 } })
|
|
part(SCANCODE.T, "playSound", { { "file", "hit.wav" } })
|
|
rule("The hand is hit", "hit", { { "type", "hand" } })
|
|
part(SCANCODE.T, "addScore", { { "amount", -200 } })
|
|
part(SCANCODE.T, "flash", { { "r", 255 }, { "g", 40 }, { "b", 40 }, { "seconds", 0.3 } })
|
|
rule("A shot at nothing", "miss", {})
|
|
part(SCANCODE.C, "test", { { "expr", "not event.offscreen" } })
|
|
part(SCANCODE.T, "addVar", { { "name", "misses" }, { "amount", 1 } })
|
|
clear("entity")
|
|
part(SCANCODE.T, "playSound", { { "file", "shot.wav" } })
|
|
rule("Draw!", "frameReached", { { "frame", 150 } })
|
|
part(SCANCODE.T, "say", { { "text", '"DRAW!"' }, { "seconds", 1 } })
|
|
ruleGo(1, 1)
|
|
form("text", '"score " .. score .. " ammo " .. gun.ammo .. " misses " .. misses')
|
|
tutorialShot("rules")
|
|
|
|
elseif stage == 7 then
|
|
key(SCANCODE.S)
|
|
local built = tutorialEnd()
|
|
local mine = onOverlayUpdate
|
|
|
|
dofile(built)
|
|
game = onOverlayUpdate
|
|
onOverlayUpdate = mine
|
|
end
|
|
end
|
|
|
|
|
|
-- A shot from the pointer at a frame of the disc, once.
|
|
local function fireAt(frame, x, y, now)
|
|
if (now >= frame) and not fired[frame] then
|
|
fired[frame] = true
|
|
authorSetPointer(1, x, y)
|
|
authorSwitchDown(SWITCH_BUTTON3)
|
|
authorSwitchUp(SWITCH_BUTTON3)
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
frames = frames + 1
|
|
if frames % 12 == 0 and stage < 7 then
|
|
stage = stage + 1
|
|
step()
|
|
end
|
|
if game ~= nil then
|
|
local frame = discGetFrame()
|
|
|
|
fireAt(170, 450, 210, frame) -- the target
|
|
fireAt(190, 100, 100, frame) -- the wall
|
|
fireAt(240, 620, 210, frame) -- the hand
|
|
if (frame >= 175) and not shot then
|
|
shot = true
|
|
tutorialShot("playing")
|
|
end
|
|
if frame >= 270 then
|
|
debugPrint(string.format("%s at frame %d: score %s, misses %s, ammo %s", TUTORIAL.tag, frame, tostring(AUTHOR_VARS.score), tostring(AUTHOR_VARS.misses), tostring(authorEntity("gun").vars.ammo)))
|
|
if (AUTHOR_VARS.score ~= -100) or (AUTHOR_VARS.misses ~= 1) then
|
|
debugPrint(TUTORIAL.tag .. " FAIL a hit on the target, a miss on the wall, and a hit on the hand should score -100 with one miss")
|
|
end
|
|
debugPrint(TUTORIAL.tag .. " RESULT done")
|
|
singeQuit()
|
|
end
|
|
return game()
|
|
end
|
|
forgeDraw(nil, nil)
|
|
|
|
return OVERLAY_UPDATED
|
|
end
|