353 lines
9.9 KiB
Text
353 lines
9.9 KiB
Text
-- Forge tutorial 7, "A point-and-click adventure": a painted yard, a walk area drawn on it,
|
|
-- a walker sent by clicks, hotspots with verbs, an inventory, a guard with a dialogue, and a
|
|
-- door to a second painted room. Starts from New game.
|
|
TUTORIAL = { number = 7, name = "07-adventure", tag = "TUTORIAL7" }
|
|
dofile("testScripts/tutorialDrive.singe")
|
|
|
|
local frames = 0
|
|
local stage = 0
|
|
local game = nil
|
|
local talked = 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 kitType(name, file, anchor)
|
|
key(SCANCODE.MAIN_2)
|
|
key(SCANCODE.A)
|
|
form("name", name)
|
|
form("look", "sprite")
|
|
form("look.file", file)
|
|
if anchor then
|
|
form("look.anchor", anchor)
|
|
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
|
|
|
|
|
|
-- Up or down the dialogue outline to a row: a dialogue by name, a node by name, or a choice by
|
|
-- its text, as a reader would with the arrows.
|
|
local function dialogueGo(level, name)
|
|
local guard = 0
|
|
|
|
local function here()
|
|
for _, row in ipairs(forgeDialogueRows()) do
|
|
local selected = (row.dialogue == FORGE.dialogue) and (row.node == FORGE.dnode) and ((row.choice or 0) == (FORGE.dchoice or 0)) and ((row.act or 0) == (FORGE.dact or 0))
|
|
|
|
if selected and (row.level == level) then
|
|
if (level == "dialogue") and (row.dialogue == name) then
|
|
return true
|
|
elseif (level == "node") and (row.node == name) then
|
|
return true
|
|
elseif (level == "choice") and row.label:find(name, 1, true) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Straight to the top, then down until the row is the one.
|
|
while not here() and (guard < 40) do
|
|
key(SCANCODE.UP)
|
|
guard = guard + 1
|
|
if FORGE.dnode == nil and level == "dialogue" and FORGE.dialogue == name then
|
|
break
|
|
end
|
|
end
|
|
guard = 0
|
|
while not here() and (guard < 40) do
|
|
key(SCANCODE.DOWN)
|
|
guard = guard + 1
|
|
end
|
|
if not here() then
|
|
debugPrint(string.format("%s FAIL the outline never reached %s %s", TUTORIAL.tag, level, name))
|
|
end
|
|
end
|
|
|
|
|
|
-- A walk area: V, a press on each corner, V again.
|
|
-- The panel steps aside on its own while the corners are pressed.
|
|
local function walkArea(corners)
|
|
key(SCANCODE.MAIN_1)
|
|
forgeSelect(nil)
|
|
key(SCANCODE.V)
|
|
for _, corner in ipairs(corners) do
|
|
forgePress(corner[1], corner[2])
|
|
end
|
|
key(SCANCODE.V)
|
|
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 Yard")
|
|
form("vars", "score=0, allowed=false")
|
|
form("verbs", "walk, look, take, use, open, talk")
|
|
form("layers", "overlay")
|
|
form("name", "yard")
|
|
form("depthSort", "true")
|
|
form("scaleBy", "300:0.6, 460:1.0")
|
|
|
|
elseif stage == 2 then
|
|
-- The painting, the walk area on its floor, and the walker.
|
|
kitType("backdrop", "yard.png")
|
|
place(360, 240)
|
|
walkArea({ { 60, 320 }, { 660, 320 }, { 700, 460 }, { 40, 460 } })
|
|
forgePanelShow(false)
|
|
tutorialShot("walkArea")
|
|
forgePanelShow(true)
|
|
kitType("hero", "hero.png", "feet")
|
|
form("look.frames", 8)
|
|
form("behaviours", "walker, frames")
|
|
form("walker.speed", 160)
|
|
form("frames.fps", 10)
|
|
form("frames.states", "idle=1-1, walk=2-5")
|
|
place(150, 440)
|
|
|
|
elseif stage == 3 then
|
|
-- The things a verb can be used on.
|
|
kitType("door", "door.png", "feet")
|
|
form("behaviours", "hotspot")
|
|
form("hotspot.name", "door")
|
|
form("hotspot.walkX", 590)
|
|
form("hotspot.walkY", 330)
|
|
place(590, 300)
|
|
kitType("key", "key.png")
|
|
form("behaviours", "hotspot")
|
|
form("hotspot.name", "key")
|
|
form("hotspot.walkX", 200)
|
|
form("hotspot.walkY", 400)
|
|
place(200, 380)
|
|
kitType("guard", "hero.png", "feet")
|
|
form("look.frames", 8)
|
|
form("look.faces", "left")
|
|
form("behaviours", "hotspot")
|
|
form("hotspot.name", "guard")
|
|
form("hotspot.walkX", 480)
|
|
form("hotspot.walkY", 360)
|
|
place(520, 340)
|
|
tutorialShot("hotspots")
|
|
|
|
elseif stage == 4 then
|
|
-- The guard's dialogue.
|
|
key(SCANCODE.MAIN_5)
|
|
key(SCANCODE.A)
|
|
form("name", "guard")
|
|
key(SCANCODE.DOWN)
|
|
form("who", "Guard")
|
|
form("text", "Nobody passes.")
|
|
key(SCANCODE.A)
|
|
form("text", "Why not?")
|
|
form("next", "why")
|
|
key(SCANCODE.A)
|
|
form("text", "Fine.")
|
|
dialogueGo("dialogue", "guard")
|
|
key(SCANCODE.A)
|
|
form("name", "why")
|
|
form("who", "Guard")
|
|
form("text", "The door is locked, and I have no key.")
|
|
key(SCANCODE.A)
|
|
form("text", "I have a key.")
|
|
form("when", 'has("key")')
|
|
form("next", "ok")
|
|
key(SCANCODE.T)
|
|
pick("setVar")
|
|
form("name", "allowed")
|
|
form("value", "true")
|
|
clear("entity")
|
|
dialogueGo("node", "why")
|
|
key(SCANCODE.A)
|
|
form("text", "I see.")
|
|
dialogueGo("dialogue", "guard")
|
|
key(SCANCODE.A)
|
|
form("name", "ok")
|
|
form("who", "Guard")
|
|
form("text", "Then go ahead.")
|
|
tutorialShot("dialogue")
|
|
|
|
elseif stage == 5 then
|
|
key(SCANCODE.MAIN_3)
|
|
rule("A click on the floor walks there", "pressed", { { "switch", "SWITCH_BUTTON3" }, { "interrupt", "true" } })
|
|
part(SCANCODE.T, "walkToPointer", { { "entity", "hero" }, { "player", 1 } })
|
|
rule("Look at anything", "verb", { { "verb", "look" } })
|
|
part(SCANCODE.T, "say", { { "text", '"It is a " .. event.target .. "."' }, { "seconds", 1 } })
|
|
rule("Take the key", "verb", { { "verb", "take" }, { "target", "key" } })
|
|
part(SCANCODE.T, "walkToHotspot", { { "entity", "hero" } })
|
|
part(SCANCODE.T, "give", { { "item", "key" } })
|
|
part(SCANCODE.T, "addScore", { { "amount", 1 } })
|
|
part(SCANCODE.T, "destroy", {})
|
|
rule("Talk to the guard", "verb", { { "verb", "talk" }, { "target", "guard" } })
|
|
part(SCANCODE.T, "walkToHotspot", { { "entity", "hero" } })
|
|
part(SCANCODE.T, "face", { { "entity", "hero" }, { "target", "self" } })
|
|
part(SCANCODE.T, "talk", { { "dialogue", "guard" } })
|
|
tutorialShot("verbRules")
|
|
|
|
elseif stage == 6 then
|
|
rule("The door, while the guard objects", "verb", { { "verb", "open" }, { "target", "door" } })
|
|
part(SCANCODE.C, "test", { { "expr", "not allowed" } })
|
|
part(SCANCODE.T, "say", { { "text", '"The guard shakes his head."' }, { "seconds", 1 } })
|
|
rule("The door opens", "verb", { { "verb", "open" }, { "target", "door" }, { "controls", "false" } })
|
|
part(SCANCODE.C, "test", { { "expr", "allowed" } })
|
|
part(SCANCODE.T, "walkToHotspot", { { "entity", "hero" } })
|
|
part(SCANCODE.T, "playSound", { { "file", "door.wav" } })
|
|
part(SCANCODE.T, "fade", { { "seconds", 0.5 }, { "out", "true" } })
|
|
part(SCANCODE.T, "goTo", { { "room", "hall" }, { "entity", "hero" }, { "x", 120 }, { "y", 420 } })
|
|
part(SCANCODE.T, "fade", { { "seconds", 0.5 }, { "out", "false" } })
|
|
part(SCANCODE.T, "addScore", { { "amount", 1 } })
|
|
rule("Anything else", "verb", {})
|
|
part(SCANCODE.T, "say", { { "text", '"That does not work."' }, { "seconds", 1 } })
|
|
rule("The verb keys", "pressed", { { "key", "L" } })
|
|
part(SCANCODE.T, "setVerb", { { "verb", "look" } })
|
|
rule("Take", "pressed", { { "key", "T" } })
|
|
part(SCANCODE.T, "setVerb", { { "verb", "take" } })
|
|
rule("Open", "pressed", { { "key", "O" } })
|
|
part(SCANCODE.T, "setVerb", { { "verb", "open" } })
|
|
rule("Talk", "pressed", { { "key", "K" } })
|
|
part(SCANCODE.T, "setVerb", { { "verb", "talk" } })
|
|
ruleGo(1, 1)
|
|
form("text", '(sentence or "") .. " score " .. score')
|
|
|
|
elseif stage == 7 then
|
|
-- The hall: a second painting, its floor, and the readout.
|
|
key(SCANCODE.MAIN_1)
|
|
key(SCANCODE.N)
|
|
form("name", "hall")
|
|
form("depthSort", "true")
|
|
key(SCANCODE.MAIN_2)
|
|
lightType("backdrop")
|
|
place(360, 240)
|
|
walkArea({ { 40, 300 }, { 680, 300 }, { 700, 460 }, { 40, 460 } })
|
|
key(SCANCODE.MAIN_1)
|
|
selectEntity("backdrop")
|
|
form("type", "backdrop")
|
|
key(SCANCODE.MAIN_2)
|
|
lightType("readout")
|
|
place(12, 12)
|
|
tutorialShot("hall")
|
|
|
|
elseif stage == 8 then
|
|
key(SCANCODE.S)
|
|
local built = tutorialEnd()
|
|
local mine = onOverlayUpdate
|
|
|
|
dofile(built)
|
|
game = onOverlayUpdate
|
|
onOverlayUpdate = mine
|
|
|
|
elseif stage == 10 then
|
|
-- Take the key: the take verb on the key hotspot.
|
|
authorSetVerb("take")
|
|
authorSetPointer(1, 200, 380)
|
|
authorSwitchDown(SWITCH_BUTTON3)
|
|
authorSwitchUp(SWITCH_BUTTON3)
|
|
elseif stage == 16 then
|
|
tutorialShot("playing")
|
|
local hx, hy = authorPosition(authorEntity("hero"))
|
|
|
|
debugPrint(string.format("%s the key is %s, the score %s, the hero at %.0f, %.0f", TUTORIAL.tag, authorHas("key") and "taken" or "still there", tostring(AUTHOR_VARS.score), hx, hy))
|
|
if not authorHas("key") then
|
|
debugPrint(TUTORIAL.tag .. " FAIL the take verb on the key should walk there and take it")
|
|
end
|
|
elseif stage == 17 then
|
|
debugPrint(TUTORIAL.tag .. " RESULT done")
|
|
singeQuit()
|
|
end
|
|
end
|
|
|
|
|
|
function onOverlayUpdate()
|
|
frames = frames + 1
|
|
if frames % 12 == 0 and stage < 17 then
|
|
stage = stage + 1
|
|
step()
|
|
end
|
|
if game ~= nil then
|
|
return game()
|
|
end
|
|
forgeDraw(nil, nil)
|
|
|
|
return OVERLAY_UPDATED
|
|
end
|