515 lines
23 KiB
Text
515 lines
23 KiB
Text
-- An American Laser Games title as it ships, driven by algDrive.singe and traced. PORT_GAME
|
|
-- names the title's folder under the library and PORT_SCRIPT its script under it (the Hypseus
|
|
-- layout: singe/<Title>/<Title>.singe); run with -v the title's framefile.
|
|
local here = ((debug.getinfo(1, "S").source:gsub("^@", "")):match("^(.*[/\\])") or "")
|
|
local library = os.getenv("SINGE_LIBRARY") or (os.getenv("HOME") .. "/claude/singetest/hypseus")
|
|
local game = os.getenv("PORT_GAME") or "maddog-hd"
|
|
local script = library .. "/" .. game .. "/" .. (os.getenv("PORT_SCRIPT") or ("singe/" .. game .. "/" .. game .. ".singe"))
|
|
|
|
dofile(here .. "algDrive.singe")
|
|
|
|
SINGE_LEGACY_SPRITE_ARGS = true
|
|
local realTime = os.time
|
|
os.time = function(...) return 12345 end
|
|
local realScriptPath = singeGetScriptPath
|
|
singeGetScriptPath = function() return script end
|
|
local realSkip = discSkipToFrame
|
|
discSkipToFrame = function(frame)
|
|
algTraceSeek(frame)
|
|
return realSkip(frame)
|
|
end
|
|
local realSearch = discSearch
|
|
discSearch = function(frame)
|
|
algTraceSeek(frame)
|
|
return realSearch(frame)
|
|
end
|
|
-- One mouse: a single gun joins by START, as the game does with one mouse.
|
|
local realMice = mouseHowMany
|
|
mouseHowMany = function() return 1 end
|
|
dofile(script)
|
|
singeGetScriptPath = realScriptPath
|
|
|
|
local realUpdate = onOverlayUpdate
|
|
|
|
-- PORT_HOOK=1 names the line the script is on every ten million instructions: where a run
|
|
-- that never ends is spinning.
|
|
if os.getenv("PORT_HOOK") == "1" then
|
|
debug.sethook(function()
|
|
local where = debug.getinfo(2, "Sl")
|
|
|
|
debugPrint("HOOK " .. tostring(where and where.short_src) .. ":" .. tostring(where and where.currentline))
|
|
end, "", 10000000)
|
|
end
|
|
|
|
|
|
function algPress(switch, device, down)
|
|
if down then
|
|
onInputPressed(switch, device)
|
|
else
|
|
onInputReleased(switch, device)
|
|
end
|
|
end
|
|
|
|
|
|
-- A key released, as the typing edition reads it (its keysym, in MODE_FULL).
|
|
function algKey(keysym)
|
|
onInputReleased(keysym)
|
|
end
|
|
|
|
-- The pointer as the game's onMouseMoved computes it: a raw position that lands on the point
|
|
-- asked for, in the game's own coordinates.
|
|
function algPointer(device, x, y)
|
|
local rx = (x + (ratioxOffset or 0)) / ((ratiox or 1) * (gunscale or 1))
|
|
local ry = (y + (ratioyOffset or 0)) / ((ratioy or 1) * (gunscale or 1))
|
|
|
|
onMouseMoved(math.floor(rx), math.floor(ry), 0, 0, device)
|
|
end
|
|
|
|
|
|
-- The hitmap's coordinates scaled as the game scales them, where it does (the multiplayer
|
|
-- editions); the one-player editions aim in the hitmap's own.
|
|
local function hmx(v)
|
|
if ResHMx then
|
|
return ResHMx(v)
|
|
end
|
|
return v
|
|
end
|
|
|
|
|
|
local function hmy(v)
|
|
if ResHMy then
|
|
return ResHMy(v)
|
|
end
|
|
return v
|
|
end
|
|
|
|
|
|
-- The hitboxes of a frame in the current move's hitmap range, in pointer coordinates.
|
|
local function boxesAt(rel)
|
|
local boxes = {}
|
|
|
|
for k = (move[currentMove][hitmapStart or hitboxStart] or 1), (hitmapTotal or 0) do
|
|
local row = hitmap[k]
|
|
|
|
if row and (row[hitmapFrame] == rel) and (row[hitmapCount] > 0) then
|
|
for j = row[hitmapIndex], row[hitmapIndex] + row[hitmapCount] - 1 do
|
|
local b = hitbox[j]
|
|
|
|
-- (A box given back to front -- Platoon's hitbox lines are, often -- can never be
|
|
-- hit by the game's own test, so it is no aim.)
|
|
if b and (b[bbx1] <= b[bbx2]) and (b[bby1] <= b[bby2]) then
|
|
boxes[#boxes + 1] = { hmx(b[bbx1]), hmy(b[bby1]), hmx(b[bbx2]), hmy(b[bby2]) }
|
|
end
|
|
end
|
|
break
|
|
end
|
|
end
|
|
|
|
return boxes
|
|
end
|
|
|
|
|
|
local function firstBox()
|
|
-- The first hitbox of this frame, aimed at a point that also lies inside a box of the next
|
|
-- frame when the two overlap: the press the drive makes now is judged on this frame or the
|
|
-- next, as the clock falls. Without an overlap (a fast target) the box of this frame stands.
|
|
if (move == nil) or (move[currentMove] == nil) or (hitmap == nil) then
|
|
return nil
|
|
end
|
|
local rel = currentFrame - (thisOffset or 0)
|
|
local now = boxesAt(rel)
|
|
local b = now[1]
|
|
|
|
if b == nil then
|
|
return nil
|
|
end
|
|
for _, n in ipairs(boxesAt(rel + 1)) do
|
|
local x1, y1, x2, y2 = math.max(b[1], n[1]), math.max(b[2], n[2]), math.min(b[3], n[3]), math.min(b[4], n[4])
|
|
|
|
if (x1 <= x2) and (y1 <= y2) then
|
|
return { x1, y1, x2, y2 }
|
|
end
|
|
end
|
|
|
|
return b
|
|
end
|
|
|
|
|
|
local function view()
|
|
local window = nil
|
|
|
|
-- A level of Mad Dog's (101 to 121) or of Mad Dog II's (its trails, and levelStart); Mad Dog II's
|
|
-- move rows have no shoot window of their own, so the move's frames stand in.
|
|
local inLevel = (currentLevel ~= nil) and (((frameShootStart ~= nil) and (currentLevel >= 101) and (currentLevel <= 121)) or ((frameShootStart == nil) and (((currentLevel >= 1) and (currentLevel <= 30)) or (currentLevel == levelStart))))
|
|
|
|
if ((practiceHit ~= nil) or (fRndPopOrder ~= nil)) and (currentLevel == levelPractice) then
|
|
inLevel = false
|
|
end
|
|
if (levelSigns ~= nil) and (currentLevel == levelSigns) then
|
|
inLevel = false
|
|
end
|
|
if (levelTieIntro ~= nil) and ((currentLevel == levelTie) or (currentLevel == levelTieIntro)) then
|
|
inLevel = false
|
|
end
|
|
|
|
-- Space Pirates's puzzles and crystals are targets, not windows: the band and Ursula's colour
|
|
-- at the doors, Fellina's skull and arrow, the sockets of the crystal build.
|
|
if (levelCrystalBuild ~= nil) and ((currentLevel == levelCrystalBuild) or ((currentLevel == levelDoor2) and (lvlState == lvlPlayClip4)) or ((currentLevel == levelDoor3) and (lvlState == lvlPlayClip3)) or ((currentLevel == levelFellina1) and ((lvlState == lvlPlayPuzzle1) or (lvlState == lvlPlayPuzzle2)))) then
|
|
inLevel = false
|
|
end
|
|
|
|
-- Who Shot Johnny Rock's store, clue items, and safe are targets, not windows.
|
|
if (GetMapArray01 ~= nil) and ((currentLevel == levelStore) or (currentLevel == levelMansion2) or (currentLevel == levelMansion4)) then
|
|
inLevel = false
|
|
end
|
|
|
|
-- Space Pirates's Reaper wants exactly as many hits as he laughed: once the drive has them,
|
|
-- the moves left are let pass.
|
|
if (levelReaper2 ~= nil) and (currentLevel == levelReaper2) and (currentMove > 1) and (iReaperHits >= iReaperLaughs) then
|
|
inLevel = false
|
|
end
|
|
|
|
-- The typing edition of Mad Dog II plays on the keyboard: the key the game waits for now (the
|
|
-- next letter of the word, a sprinkled letter, the return key at a menu), and no gun at all.
|
|
local typing = nil
|
|
|
|
if validKey ~= nil then
|
|
inLevel = false
|
|
local function nextOf(word)
|
|
if word and (iKeyIndex or 1) <= string.len(word) then
|
|
return string.byte(word, iKeyIndex or 1)
|
|
end
|
|
return nil
|
|
end
|
|
-- (The game starts with the key slot at zero, not at none, and never clears it.)
|
|
local waiting = (iKey == SWITCH_NONE) or (iKey == 0)
|
|
|
|
if not waiting then
|
|
typing = nil
|
|
elseif (currentLevel == levelTitle) and (lvlState == lvlRunning) then
|
|
typing = SWITCH_RETURN
|
|
elseif (currentLevel == levelMenu) and (lvlState == lvlRunning) then
|
|
typing = SWITCH_RETURN
|
|
elseif (currentLevel == levelHighScore) and (lvlState == lvlRunning) then
|
|
-- (Once, before the name's frames run out; the return key seeks to the entry's end.)
|
|
if discGetFrame() < offsetMenus + 3275 then
|
|
typing = SWITCH_RETURN
|
|
end
|
|
elseif bShowSprinkles and sprinkles then
|
|
for k = 1, (iSprinkleMax or 0) do
|
|
if sprinkles[k] and (sprinkles[k][4] == true) then
|
|
typing = string.byte(sprinkles[k][3])
|
|
break
|
|
end
|
|
end
|
|
elseif (currentLevel == levelItem1) and (lvlState == lvlPlayClip3) then
|
|
typing = nextOf(sMagicWord)
|
|
elseif move and move[currentMove] and ((lvlState == lvlPauseAction) or ((lvlState == lvlRunning) and (discGetFrame() >= move[currentMove][moveFrameStart]) and (discGetFrame() < move[currentMove][moveFrameEnd]))) then
|
|
typing = nextOf(move[currentMove][(currentLevel == levelItem2) and moveType or wordval])
|
|
end
|
|
end
|
|
|
|
-- Platoon's patrols open with a clip the first press of the trigger jumps past, whatever the
|
|
-- state: no window until that press is in, and a target anywhere to make it.
|
|
local skipping = (levelCharlie ~= nil) and bOnce and (currentLevel ~= nil) and (currentLevel >= 1) and (currentLevel <= 6) and (lvlState == lvlRunning)
|
|
|
|
if skipping then
|
|
inLevel = false
|
|
end
|
|
|
|
-- A good guy's move (the Crime Patrol family marks the row) is no window: shooting him is a
|
|
-- death, and the drive would take it at every level that has one.
|
|
if inLevel and move and move[currentMove] and (rndMoveType ~= nil) and (GOODGUY ~= nil) and (move[currentMove][rndMoveType] == GOODGUY) then
|
|
inLevel = false
|
|
end
|
|
if inLevel and move and move[currentMove] then
|
|
window = { move[currentMove][frameShootStart or moveFrameStart], move[currentMove][frameShootEnd or moveFrameEnd] }
|
|
end
|
|
local quadrants = nil
|
|
|
|
if (currentLevel == levelTownSelect) and stage then
|
|
quadrants = {}
|
|
local w, h = resX or 360, resY or 240
|
|
|
|
-- In the order the game itself takes when the select times out: the saloon before the
|
|
-- sheriff, whose level needs the key from it.
|
|
if not stage[STAGE_CORRAL] then quadrants[#quadrants + 1] = { w * 0.25, h * 0.25 } end
|
|
if not stage[STAGE_SALOON] then quadrants[#quadrants + 1] = { w * 0.75, h * 0.25 } end
|
|
if not stage[STAGE_SHERIFF] then quadrants[#quadrants + 1] = { w * 0.25, h * 0.75 } end
|
|
if not stage[STAGE_BANK] then quadrants[#quadrants + 1] = { w * 0.75, h * 0.75 } end
|
|
end
|
|
|
|
-- The signpost's sign and the mine's lantern and item, as a list of points to shoot in
|
|
-- order (the middle line of each, scaled as the game scales them).
|
|
local targets = nil
|
|
local targetStep = nil
|
|
local function lineMiddle(rows, count)
|
|
local row = rows and rows[math.floor((count + 1) / 2)]
|
|
|
|
if row then
|
|
return { hmx((row[lineStartX] + row[lineEndX]) / 2), hmy(row[lineY]) }
|
|
end
|
|
return nil
|
|
end
|
|
|
|
if skipping then
|
|
targets = { { 180, 120 } }
|
|
targetStep = lvlRunning
|
|
elseif (currentLevel == levelSignSelect) and sign then
|
|
targets = {}
|
|
if stage[STAGE_FUSE] == false then
|
|
targets[1] = lineMiddle(sign[SIGN_PROSPECTOR], 48)
|
|
elseif stage[STAGE_MINE] == false then
|
|
targets[1] = lineMiddle(sign[SIGN_MINE], 28)
|
|
else
|
|
targets[1] = lineMiddle(sign[SIGN_HIDEOUT], 58)
|
|
end
|
|
elseif (currentLevel == levelMine) and mine then
|
|
targets = {}
|
|
local counts = { [MINE_SIGN] = 21, [MINE_LANTERN] = 20, [MINE_PAN] = 12, [MINE_PITCHFORK] = 23, [MINE_SKULL] = 25 }
|
|
|
|
if not bLanternHit then targets[#targets + 1] = lineMiddle(mine[MINE_LANTERN], counts[MINE_LANTERN]) end
|
|
if not bItemHit then targets[#targets + 1] = lineMiddle(mine[iWhatToHit], counts[iWhatToHit]) end
|
|
elseif (currentLevel == levelMaze) and maze and maze[curMazePos] and (maze[curMazePos][mazeType] > 0) and sign then
|
|
-- A maze sign: the sign for the way to go.
|
|
local frame, dir = maze[curMazePos][signType], maze[curMazePos][signDirection]
|
|
local counts = { [32] = { 51, 49 }, [175] = { 59, 17 }, [340] = { 67, 90 }, [500] = { 24, 42 }, [630] = { 11, 30 }, [446] = { 59, 64 } }
|
|
local pair = counts[frame]
|
|
|
|
if pair and sign[dir] then
|
|
targets = { lineMiddle(sign[dir], pair[dir]) }
|
|
end
|
|
elseif (levelItem1 ~= nil) and (ResHMx ~= nil) and (currentLevel == levelItem1) and (lvlState == lvlPlayClip3) then
|
|
-- Mad Dog II's first item level: the item the guide asked for, from the game's own
|
|
-- rectangles.
|
|
local boxes = { [ITEM_LANTERN] = { 57, 66, 117, 129 }, [ITEM_AMMOBOX] = { 120, 148, 111, 132 }, [ITEM_MOONSHINE] = { 260, 272, 60, 72 } }
|
|
local b = boxes[iThisItem]
|
|
|
|
if b then
|
|
targets = { { hmx((b[1] + b[2]) / 2), hmy((b[3] + b[4]) / 2) } }
|
|
targetStep = lvlPlayClip3
|
|
end
|
|
elseif (levelProspector1 ~= nil) and (ResHMx ~= nil) and (currentLevel == levelProspector1) and (lvlState == lvlPlayClip4) then
|
|
-- Mad Dog II's prospector: the skull he chose, from the game's own rectangles.
|
|
local boxes = { { 305, 315, 59, 76 }, { 68, 75, 73, 82 }, { 147, 154, 58, 67 }, { 119, 127, 101, 115 } }
|
|
local b = boxes[iSkull] or boxes[4]
|
|
|
|
targets = { { hmx((b[1] + b[2]) / 2), hmy((b[3] + b[4]) / 2) } }
|
|
targetStep = lvlPlayClip4
|
|
elseif (panelHit ~= nil) and (currentLevel == levelMenu) and (lvlState == lvlRunning) and area then
|
|
-- Crime Patrol's mission menus: the first panel whose stage is open, by the middle line
|
|
-- of its area (the count of lines is the game's own).
|
|
local MENUS = {}
|
|
|
|
if MENU_SIERRA ~= nil then
|
|
MENUS[MENU_SIERRA] = { { PANEL_PRACTICE, STAGE_PRACTICE, 99 }, { PANEL_BAR, STAGE_BAR, 99 }, { PANEL_CARCHASE, STAGE_CARCHASE, 99 }, { PANEL_HIDEOUT, STAGE_HIDEOUT, 99 } }
|
|
MENUS[MENU_CHICAGO] = { { PANEL_HELI, STAGE_HELI, 111 }, { PANEL_COURTROOM, STAGE_COURTROOM, 111 }, { PANEL_BUSCHASE, STAGE_BUSCHASE, 111 } }
|
|
MENUS[MENU_BORDER] = { { PANEL_SHIPYARD, STAGE_SHIPYARD, 111 }, { PANEL_BOATHOUSE, STAGE_BOATHOUSE, 111 }, { PANEL_BEACH, STAGE_BEACH, 111 } }
|
|
MENUS[MENU_SA] = { { PANEL_BASE, STAGE_BASE, 111 }, { PANEL_NIGHTVISION, STAGE_NIGHTVISION, 111 }, { PANEL_TOWN, STAGE_TOWN, 111 } }
|
|
end
|
|
if MENU_ROOKIE ~= nil then
|
|
MENUS = { [MENU_ROOKIE] = { { PANEL_PRACTICE, STAGE_PRACTICE, 98 }, { PANEL_STORE, STAGE_STORE, 99 }, { PANEL_GANG, STAGE_GANG, 99 }, { PANEL_WAREHOUSE, STAGE_WAREHOUSE, 97 } },
|
|
[MENU_UNDERCOVER] = { { PANEL_AIRPORT, STAGE_AIRPORT, 104 }, { PANEL_GARAGE, STAGE_GARAGE, 104 }, { PANEL_STRIPCLUB, STAGE_STRIPCLUB, 104 } },
|
|
[MENU_SWAT] = { { PANEL_DRUGS1, STAGE_DRUGS1, 111 }, { PANEL_DRUGS2, STAGE_DRUGS2, 111 }, { PANEL_BANK, STAGE_ROBBERY, 103 } },
|
|
[MENU_DELTA] = { { PANEL_HANGAR, STAGE_HANGAR, 108 }, { PANEL_AMBUSH, STAGE_AMBUSH, 103 }, { PANEL_AIRTERRORISTS, STAGE_AIRTERRORISTS, 109 } } }
|
|
end
|
|
|
|
for _, entry in ipairs(MENUS[iCurrentMenu] or {}) do
|
|
if not panel[entry[2]] then
|
|
targets = { lineMiddle(area[entry[1]], entry[3]) }
|
|
break
|
|
end
|
|
end
|
|
elseif (GetArrayPanels ~= nil) and (currentLevel == levelMenu) and (lvlState == lvlRunning) and area then
|
|
-- The Last Bounty Hunter's wanted posters: the first outlaw still at large, by the middle of
|
|
-- his panel's rectangle.
|
|
for _, stage in ipairs({ STAGE_HARRY, STAGE_DAN, STAGE_LOCO, STAGE_CACTUS }) do
|
|
local r = (panel[stage] == ATLARGE) and area[stage] and area[stage][1]
|
|
|
|
if r then
|
|
targets = { { hmx((r[lineStartX] + r[lineEndX]) / 2), hmy((r[lineStartY] + r[lineEndY]) / 2) } }
|
|
break
|
|
end
|
|
end
|
|
elseif (levelSigns ~= nil) and (currentLevel == levelSigns) and (lvlState == lvlEnd) then
|
|
-- The Last Bounty Hunter's signs: the practice range, in the game's own pointer coordinates.
|
|
targets = { { 175, 101 } }
|
|
targetStep = lvlEnd
|
|
elseif (iLastMove ~= nil) and (levelFinal ~= nil) and (currentLevel == levelFinal) and (lvlState == lvlPlayClip2) then
|
|
-- Drug Wars's last stand: the tank, the blanket, or the gas cans, in the game's own
|
|
-- pointer coordinates.
|
|
local STAND = { { 262, 329, 133, 172 }, { 101, 111, 160, 188 }, { 209, 243, 201, 215 } }
|
|
local b = STAND[iLastMove] or STAND[3]
|
|
|
|
targets = { { (b[1] + b[2]) / 2, (b[3] + b[4]) / 2 } }
|
|
targetStep = lvlPlayClip2
|
|
elseif (practiceHit ~= nil) and (currentLevel == levelPractice) and (lvlState == lvlRunning) then
|
|
-- Crime Patrol's practice range: the first standing target of the still, in the game's
|
|
-- own pointer coordinates, or the cutout of the move (the middle line of its outline).
|
|
if currentMove == 0 then
|
|
local STILL = { { "bTopRight", 261, 300, 9, 60 }, { "bTopLeft", 78, 119, 70, 120 }, { "bMiddle", 205, 243, 70, 123 }, { "bBottomLeft", 120, 158, 124, 181 }, { "bBottomRight", 268, 297, 132, 183 } }
|
|
|
|
for _, t in ipairs(STILL) do
|
|
if _G[t[1]] then
|
|
targets = { { (t[2] + t[3]) / 2, (t[4] + t[5]) / 2 } }
|
|
break
|
|
end
|
|
end
|
|
elseif (currentMove >= 1) and move and move[currentMove] and target then
|
|
local kind = move[currentMove][rndMoveType]
|
|
local counts = { [BADGUY01] = 98, [CIV01] = 82, [BADGUY02] = 97, [CIV02] = 107, [BADGUY03] = 79, [CIV03] = 148 }
|
|
|
|
if kind and counts[kind] and target[kind] then
|
|
targets = { lineMiddle(target[kind], counts[kind]) }
|
|
end
|
|
end
|
|
elseif (levelFirePractice ~= nil) and (currentLevel == levelMenu) and (lvlState == lvlRunning) and panel then
|
|
-- Space Pirates's planet menu: the first panel not yet done, in the order the game takes
|
|
-- when the clip runs out, by the middle of its quadrant (pointer coordinates scaled as the
|
|
-- game scales them).
|
|
local PANELS = { { STAGE_FELLINA, 807, 1280, 565, 916 }, { STAGE_REAPER, 245, 720, 565, 916 }, { STAGE_SCRAPYARD, 807, 1280, 100, 450 }, { STAGE_MOUNTAIN, 245, 720, 100, 450 } }
|
|
|
|
for _, p in ipairs(PANELS) do
|
|
if panel[p[1]] == false then
|
|
targets = { { UnX * (p[2] + p[3]) / 2, UnY * (p[4] + p[5]) / 2 } }
|
|
break
|
|
end
|
|
end
|
|
elseif (levelDoor2 ~= nil) and (currentLevel == levelDoor2) and (lvlState == lvlPlayClip4) and band then
|
|
-- Space Pirates's band of colour: the right one, within its frames, by the middle of its
|
|
-- line.
|
|
local row = band[iBand] and band[iBand][1]
|
|
|
|
if row and (discGetFrame() >= bandFrameStart + 3) and (discGetFrame() < bandFrameEnd) then
|
|
targets = { { hmx((row[lineStartX] + row[lineEndX]) / 2), hmy((row[lineStartY] + row[lineEndY]) / 2) } }
|
|
targetStep = lvlPlayClip4
|
|
end
|
|
elseif (levelDoor3 ~= nil) and (currentLevel == levelDoor3) and (lvlState == lvlPlayClip3) and (ursie or (ursulaHit ~= nil)) then
|
|
-- Space Pirates's commander: Ursula shot within her colour's frames.
|
|
local WINDOWS = { [COLOR_RED] = { 2243, 2306 }, [COLOR_GREEN] = { 2312, 2371 }, [COLOR_BLUE] = { 2377, 2439 } }
|
|
local w = WINDOWS[iUrsulaColor]
|
|
local rel = discGetFrame() - thisOffset
|
|
|
|
if w and (rel >= w[1] + 3) and (rel <= w[2] - 3) then
|
|
-- (The one-player edition builds her rectangles inside its hit test, so before the first
|
|
-- shot the game's own numbers stand in.)
|
|
local box = (ursie and ursie[1]) or { 152, 137, 210, 238 }
|
|
|
|
targets = { { hmx((box[1] + box[3]) / 2), hmy((box[2] + box[4]) / 2) } }
|
|
targetStep = lvlPlayClip3
|
|
end
|
|
elseif (levelFellina1 ~= nil) and (currentLevel == levelFellina1) and ((lvlState == lvlPlayPuzzle1) or (lvlState == lvlPlayPuzzle2)) and thingie then
|
|
-- Space Pirates's Fellina puzzles: the skull she chose, then the arrow, by the middle of
|
|
-- the game's own line.
|
|
local which = (lvlState == lvlPlayPuzzle1) and iFellinaSkull or iFellinaArrow
|
|
local row = thingie[which] and thingie[which][1]
|
|
|
|
if row and ((lvlState == lvlPlayPuzzle2) or (discGetFrame() >= iSkullStart + 3)) then
|
|
targets = { { hmx((row[lineStartX] + row[lineEndX]) / 2), hmy((row[lineStartY] + row[lineEndY]) / 2) } }
|
|
targetStep = lvlState
|
|
end
|
|
elseif (levelCrystalBuild ~= nil) and (currentLevel == levelCrystalBuild) and (lvlState == lvlRunning) and hole and socket then
|
|
-- Space Pirates's crystal build: the next socket in the order shown.
|
|
local ORDERS = { [ORDER_RGB] = { SOCKET_RED, SOCKET_GREEN, SOCKET_BLUE }, [ORDER_GBR] = { SOCKET_GREEN, SOCKET_BLUE, SOCKET_RED }, [ORDER_BRG] = { SOCKET_BLUE, SOCKET_RED, SOCKET_GREEN } }
|
|
|
|
for _, which in ipairs(ORDERS[iCrystalOrder] or {}) do
|
|
if not socket[which] then
|
|
local row = hole[which] and hole[which][1]
|
|
|
|
if row then
|
|
targets = { { hmx((row[lineStartX] + row[lineEndX]) / 2), hmy((row[lineStartY] + row[lineEndY]) / 2) } }
|
|
end
|
|
break
|
|
end
|
|
end
|
|
elseif (GetMapArray01 ~= nil) and (currentLevel == levelMenu) and (lvlState == lvlRunning) and stage then
|
|
-- Who Shot Johnny Rock's city map: the killer's place once revealed, the mansion once the
|
|
-- clues are all had, else the first place still open (the casino, the pool hall, the
|
|
-- warehouse, the garage), by the middle of its panel.
|
|
local which = nil
|
|
|
|
if bKillerRevealed then
|
|
which = ({ [JILL] = PLACE_CASINO, [MUMPS] = PLACE_POOL, [MEASLES] = PLACE_WAREHOUSE, [POX] = PLACE_GARAGE })[iKiller]
|
|
elseif bGotAllClues then
|
|
which = PLACE_MANSION
|
|
elseif not stage[levelCasino] then
|
|
which = PLACE_CASINO
|
|
elseif not stage[levelPool] then
|
|
which = PLACE_POOL
|
|
elseif not (stage[levelWarehouse1] and stage[levelWarehouse2] and stage[levelWarehouse3]) then
|
|
which = PLACE_WAREHOUSE
|
|
elseif not stage[levelGarage] then
|
|
which = PLACE_GARAGE
|
|
end
|
|
if which then
|
|
GetMapArray01()
|
|
local row = place[which] and place[which][1]
|
|
|
|
if row then
|
|
targets = { { hmx((row[lineStartX] + row[lineEndX]) / 2), hmy((row[lineStartY] + row[lineEndY]) / 2) } }
|
|
end
|
|
end
|
|
elseif (GetMapArray01 ~= nil) and (currentLevel == levelMansion2) and (lvlState == lvlRunning) and clue and thisItem then
|
|
-- Who Shot Johnny Rock's clue items: the item of the clue asked for, in the game's second
|
|
-- scale.
|
|
GetArrayItems()
|
|
local row = item[clue[thisItem]] and item[clue[thisItem]][1]
|
|
|
|
if row then
|
|
targets = { { ResHMx2((row[lineStartX] + row[lineEndX]) / 2), ResHMy2((row[lineStartY] + row[lineEndY]) / 2) } }
|
|
end
|
|
elseif (GetMapArray01 ~= nil) and (currentLevel == levelMansion4) and (lvlState == lvlRunning) and combination and iSafeNumber then
|
|
-- Who Shot Johnny Rock's safe: the dial shot while it shows the combination's next digit
|
|
-- (on this frame and the next, since the press is judged on the next).
|
|
local digit = tonumber(string.sub(combination, iSafeNumber, iSafeNumber))
|
|
local rel = discGetFrame() - thisOffset
|
|
|
|
if (getSafeNumber(rel) == digit) and (getSafeNumber(rel + 1) == digit) then
|
|
targets = { { hmx((151 + 207) / 2), hmy((64 + 88) / 2) } }
|
|
end
|
|
elseif (GUIDE_BEAVER ~= nil) and (ResHMx ~= nil) and (currentLevel == levelMenu) and panel then
|
|
-- Mad Dog II's guide menu: the first guide whose trail is not done, by its panel.
|
|
targets = {}
|
|
local x = (panel[GUIDE_BEAVER] == false) and 90 or ((panel[GUIDE_BONNIE] == false) and 186 or ((panel[GUIDE_PROF] == false) and 282 or nil))
|
|
|
|
if x then
|
|
targets[1] = { hmx(x), hmy(121) }
|
|
end
|
|
end
|
|
|
|
-- At the attract loop: Mad Dog's state machine names it currentState, Mad Dog II's gameflow.
|
|
local playing = ((currentState or gameflow) == statePlaying)
|
|
local intro = playing and (currentLevel == (lvlIntro or levelIntro))
|
|
|
|
-- The service screen: the option under the cursor and every dip, for the trace; a screen
|
|
-- the stick drives rather than the pointer (Hologram Time Traveler).
|
|
local dips = {}
|
|
|
|
for key, value in pairs(_G) do
|
|
if (type(key) == "string") and (key:sub(1, 4) == "dip_") and (type(value) ~= "function") then
|
|
dips[#dips + 1] = key:sub(5) .. "=" .. tostring(value)
|
|
end
|
|
end
|
|
table.sort(dips)
|
|
|
|
return { screen = currentLevel, step = lvlState, move = currentMove, frame = discGetFrame(), frames = frames, score = iScore, lives = iLives or iMoney, ammo = iBullets, active = p1Active, intro = intro,
|
|
service = (levelService ~= nil) and (currentLevel == levelService), serviceKeys = (TURNRIGHTSHOOT ~= nil), opt = optSel, dips = table.concat(dips, ","), width = overlayGetWidth(), height = overlayGetHeight(),
|
|
board = (levelHighScore ~= nil) and (currentLevel == levelHighScore),
|
|
window = window, box = window and firstBox() or nil, quadrants = quadrants, targets = targets, targetStep = targetStep, continuing = (currentLevel == levelContinue), noSelect = (mouseIndexSelect == nil), typing = typing, typed = (validKey ~= nil), offscreen = { -5, -5 }, free = bAmmo, paused = bPause,
|
|
-- The one-player editions (no player select, lives rather than money) spend a shot
|
|
-- before the level tests for ammo, so their last shot is never judged: a spare to keep.
|
|
spare = ((mouseIndexSelect == nil) and (iMoney == nil) and (iBullets ~= nil) and ((panelHit == nil) or (resX ~= nil)) and (levelCharlie == nil)) and 1 or 0,
|
|
-- The library's one-player Crime Patrol family judges a shot before the release takes its
|
|
-- bullet (its HD twins take it on the press, as the others do), and reloads by its second
|
|
-- button (immediately, as its config ships); so does Platoon.
|
|
reloadSwitch = (((panelHit ~= nil) and (mouseIndexSelect == nil)) or (levelCharlie ~= nil)) and SWITCH_BUTTON1 or SWITCH_BUTTON3 }
|
|
end
|
|
|
|
|
|
onOverlayUpdate = function(...)
|
|
local result = realUpdate(...)
|
|
|
|
algDrive(view())
|
|
|
|
return result
|
|
end
|