singe/assets/Framework.singe
2026-09-12 01:22:02 -05:00

704 lines
26 KiB
Text

--[[
*
* Singe 3
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*
--]]
-- Singe 2.xx Features -------------------------------------------------------
-- SINGE_FRAMEWORK_VERSION, SWITCH_*, FONT_QUALITY_*, MODE_*, MOUSE_*, OVERLAY_*,
-- RENDER_*, DISC_*, SOUND_ERROR_* and the SINGE_* input code layout are all
-- pushed by the engine before this file runs, so they are defined in exactly
-- one place: singe.c.
if singeGetScriptPath ~= nil then
DIR = singeGetScriptPath():match("(.*[/\\])") or "./"
end
function utilDeepCopy(original)
local copy
if type(original) == 'table' then
copy = {}
for key, value in next, original, nil do
copy[utilDeepCopy(key)] = utilDeepCopy(value)
end
setmetatable(copy, utilDeepCopy(getmetatable(original)))
else -- number, string, boolean, etc
copy = original
end
return copy
end
function utilDump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then
k = '"'..k..'"'
end
s = s .. '['..k..'] = ' .. utilDump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
function utilGetTableSize(t)
local count = 0
for _ in pairs(t) do
count = count + 1
end
return count
end
-- Returns a copy of a { NAME = { name = NAME, value = N } } table with every value shifted.
function utilOffsetTable(source, offset)
local copy = utilDeepCopy(source)
for _, entry in pairs(copy) do
entry.value = entry.value + offset
end
return copy
end
function utilTrim(s)
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
SCANCODE = {
A = { name = "A", value = 4 },
B = { name = "B", value = 5 },
C = { name = "C", value = 6 },
D = { name = "D", value = 7 },
E = { name = "E", value = 8 },
F = { name = "F", value = 9 },
G = { name = "G", value = 10 },
H = { name = "H", value = 11 },
I = { name = "I", value = 12 },
J = { name = "J", value = 13 },
K = { name = "K", value = 14 },
L = { name = "L", value = 15 },
M = { name = "M", value = 16 },
N = { name = "N", value = 17 },
O = { name = "O", value = 18 },
P = { name = "P", value = 19 },
Q = { name = "Q", value = 20 },
R = { name = "R", value = 21 },
S = { name = "S", value = 22 },
T = { name = "T", value = 23 },
U = { name = "U", value = 24 },
V = { name = "V", value = 25 },
W = { name = "W", value = 26 },
X = { name = "X", value = 27 },
Y = { name = "Y", value = 28 },
Z = { name = "Z", value = 29 },
MAIN_1 = { name = "MAIN_1", value = 30 },
MAIN_2 = { name = "MAIN_2", value = 31 },
MAIN_3 = { name = "MAIN_3", value = 32 },
MAIN_4 = { name = "MAIN_4", value = 33 },
MAIN_5 = { name = "MAIN_5", value = 34 },
MAIN_6 = { name = "MAIN_6", value = 35 },
MAIN_7 = { name = "MAIN_7", value = 36 },
MAIN_8 = { name = "MAIN_8", value = 37 },
MAIN_9 = { name = "MAIN_9", value = 38 },
MAIN_0 = { name = "MAIN_0", value = 39 },
RETURN = { name = "RETURN", value = 40 },
ESCAPE = { name = "ESCAPE", value = 41 },
BACKSPACE = { name = "BACKSPACE", value = 42 },
TAB = { name = "TAB", value = 43 },
SPACE = { name = "SPACE", value = 44 },
MINUS = { name = "MINUS", value = 45 },
EQUALS = { name = "EQUALS", value = 46 },
LEFTBRACKET = { name = "LEFTBRACKET", value = 47 },
RIGHTBRACKET = { name = "RIGHTBRACKET", value = 48 },
BACKSLASH = { name = "BACKSLASH", value = 49 },
NONUSHASH = { name = "NONUSHASH", value = 50 },
SEMICOLON = { name = "SEMICOLON", value = 51 },
APOSTROPHE = { name = "APOSTROPHE", value = 52 },
GRAVE = { name = "GRAVE", value = 53 },
COMMA = { name = "COMMA", value = 54 },
PERIOD = { name = "PERIOD", value = 55 },
SLASH = { name = "SLASH", value = 56 },
CAPSLOCK = { name = "CAPSLOCK", value = 57 },
F1 = { name = "F1", value = 58 },
F2 = { name = "F2", value = 59 },
F3 = { name = "F3", value = 60 },
F4 = { name = "F4", value = 61 },
F5 = { name = "F5", value = 62 },
F6 = { name = "F6", value = 63 },
F7 = { name = "F7", value = 64 },
F8 = { name = "F8", value = 65 },
F9 = { name = "F9", value = 66 },
F10 = { name = "F10", value = 67 },
F11 = { name = "F11", value = 68 },
F12 = { name = "F12", value = 69 },
PRINTSCREEN = { name = "PRINTSCREEN", value = 70 },
SCROLLLOCK = { name = "SCROLLLOCK", value = 71 },
PAUSE = { name = "PAUSE", value = 72 },
INSERT = { name = "INSERT", value = 73 },
HOME = { name = "HOME", value = 74 },
PAGEUP = { name = "PAGEUP", value = 75 },
DELETE = { name = "DELETE", value = 76 },
END = { name = "END", value = 77 },
PAGEDOWN = { name = "PAGEDOWN", value = 78 },
RIGHT = { name = "RIGHT", value = 79 },
LEFT = { name = "LEFT", value = 80 },
DOWN = { name = "DOWN", value = 81 },
UP = { name = "UP", value = 82 },
NUMLOCKCLEAR = { name = "NUMLOCKCLEAR", value = 83 },
KP_DIVIDE = { name = "KP_DIVIDE", value = 84 },
KP_MULTIPLY = { name = "KP_MULTIPLY", value = 85 },
KP_MINUS = { name = "KP_MINUS", value = 86 },
KP_PLUS = { name = "KP_PLUS", value = 87 },
KP_ENTER = { name = "KP_ENTER", value = 88 },
KP_1 = { name = "KP_1", value = 89 },
KP_2 = { name = "KP_2", value = 90 },
KP_3 = { name = "KP_3", value = 91 },
KP_4 = { name = "KP_4", value = 92 },
KP_5 = { name = "KP_5", value = 93 },
KP_6 = { name = "KP_6", value = 94 },
KP_7 = { name = "KP_7", value = 95 },
KP_8 = { name = "KP_8", value = 96 },
KP_9 = { name = "KP_9", value = 97 },
KP_0 = { name = "KP_0", value = 98 },
KP_PERIOD = { name = "KP_PERIOD", value = 99 },
NONUSBACKSLASH = { name = "NONUSBACKSLASH", value = 100 },
APPLICATION = { name = "APPLICATION", value = 101 },
POWER = { name = "POWER", value = 102 },
KP_EQUALS = { name = "KP_EQUALS", value = 103 },
F13 = { name = "F13", value = 104 },
F14 = { name = "F14", value = 105 },
F15 = { name = "F15", value = 106 },
F16 = { name = "F16", value = 107 },
F17 = { name = "F17", value = 108 },
F18 = { name = "F18", value = 109 },
F19 = { name = "F19", value = 110 },
F20 = { name = "F20", value = 111 },
F21 = { name = "F21", value = 112 },
F22 = { name = "F22", value = 113 },
F23 = { name = "F23", value = 114 },
F24 = { name = "F24", value = 115 },
EXECUTE = { name = "EXECUTE", value = 116 },
HELP = { name = "HELP", value = 117 },
MENU = { name = "MENU", value = 118 },
SELECT = { name = "SELECT", value = 119 },
STOP = { name = "STOP", value = 120 },
AGAIN = { name = "AGAIN", value = 121 },
UNDO = { name = "UNDO", value = 122 },
CUT = { name = "CUT", value = 123 },
COPY = { name = "COPY", value = 124 },
PASTE = { name = "PASTE", value = 125 },
FIND = { name = "FIND", value = 126 },
MUTE = { name = "MUTE", value = 127 },
VOLUMEUP = { name = "VOLUMEUP", value = 128 },
VOLUMEDOWN = { name = "VOLUMEDOWN", value = 129 },
KP_COMMA = { name = "KP_COMMA", value = 133 },
KP_EQUALSAS400 = { name = "KP_EQUALSAS400", value = 134 },
INTERNATIONAL1 = { name = "INTERNATIONAL1", value = 135 },
INTERNATIONAL2 = { name = "INTERNATIONAL2", value = 136 },
INTERNATIONAL3 = { name = "INTERNATIONAL3", value = 137 },
INTERNATIONAL4 = { name = "INTERNATIONAL4", value = 138 },
INTERNATIONAL5 = { name = "INTERNATIONAL5", value = 139 },
INTERNATIONAL6 = { name = "INTERNATIONAL6", value = 140 },
INTERNATIONAL7 = { name = "INTERNATIONAL7", value = 141 },
INTERNATIONAL8 = { name = "INTERNATIONAL8", value = 142 },
INTERNATIONAL9 = { name = "INTERNATIONAL9", value = 143 },
LANG1 = { name = "LANG1", value = 144 },
LANG2 = { name = "LANG2", value = 145 },
LANG3 = { name = "LANG3", value = 146 },
LANG4 = { name = "LANG4", value = 147 },
LANG5 = { name = "LANG5", value = 148 },
LANG6 = { name = "LANG6", value = 149 },
LANG7 = { name = "LANG7", value = 150 },
LANG8 = { name = "LANG8", value = 151 },
LANG9 = { name = "LANG9", value = 152 },
ALTERASE = { name = "ALTERASE", value = 153 },
SYSREQ = { name = "SYSREQ", value = 154 },
CANCEL = { name = "CANCEL", value = 155 },
CLEAR = { name = "CLEAR", value = 156 },
PRIOR = { name = "PRIOR", value = 157 },
RETURN2 = { name = "RETURN2", value = 158 },
SEPARATOR = { name = "SEPARATOR", value = 159 },
OUT = { name = "OUT", value = 160 },
OPER = { name = "OPER", value = 161 },
CLEARAGAIN = { name = "CLEARAGAIN", value = 162 },
CRSEL = { name = "CRSEL", value = 163 },
EXSEL = { name = "EXSEL", value = 164 },
KP_00 = { name = "KP_00", value = 176 },
KP_000 = { name = "KP_000", value = 177 },
THOUSANDSSEPARATOR = { name = "THOUSANDSSEPARATOR", value = 178 },
DECIMALSEPARATOR = { name = "DECIMALSEPARATOR", value = 179 },
CURRENCYUNIT = { name = "CURRENCYUNIT", value = 180 },
CURRENCYSUBUNIT = { name = "CURRENCYSUBUNIT", value = 181 },
KP_LEFTPAREN = { name = "KP_LEFTPAREN", value = 182 },
KP_RIGHTPAREN = { name = "KP_RIGHTPAREN", value = 183 },
KP_LEFTBRACE = { name = "KP_LEFTBRACE", value = 184 },
KP_RIGHTBRACE = { name = "KP_RIGHTBRACE", value = 185 },
KP_TAB = { name = "KP_TAB", value = 186 },
KP_BACKSPACE = { name = "KP_BACKSPACE", value = 187 },
KP_A = { name = "KP_A", value = 188 },
KP_B = { name = "KP_B", value = 189 },
KP_C = { name = "KP_C", value = 190 },
KP_D = { name = "KP_D", value = 191 },
KP_E = { name = "KP_E", value = 192 },
KP_F = { name = "KP_F", value = 193 },
KP_XOR = { name = "KP_XOR", value = 194 },
KP_POWER = { name = "KP_POWER", value = 195 },
KP_PERCENT = { name = "KP_PERCENT", value = 196 },
KP_LESS = { name = "KP_LESS", value = 197 },
KP_GREATER = { name = "KP_GREATER", value = 198 },
KP_AMPERSAND = { name = "KP_AMPERSAND", value = 199 },
KP_DBLAMPERSAND = { name = "KP_DBLAMPERSAND", value = 200 },
KP_VERTICALBAR = { name = "KP_VERTICALBAR", value = 201 },
KP_DBLVERTICALBAR = { name = "KP_DBLVERTICALBAR", value = 202 },
KP_COLON = { name = "KP_COLON", value = 203 },
KP_HASH = { name = "KP_HASH", value = 204 },
KP_SPACE = { name = "KP_SPACE", value = 205 },
KP_AT = { name = "KP_AT", value = 206 },
KP_EXCLAM = { name = "KP_EXCLAM", value = 207 },
KP_MEMSTORE = { name = "KP_MEMSTORE", value = 208 },
KP_MEMRECALL = { name = "KP_MEMRECALL", value = 209 },
KP_MEMCLEAR = { name = "KP_MEMCLEAR", value = 210 },
KP_MEMADD = { name = "KP_MEMADD", value = 211 },
KP_MEMSUBTRACT = { name = "KP_MEMSUBTRACT", value = 212 },
KP_MEMMULTIPLY = { name = "KP_MEMMULTIPLY", value = 213 },
KP_MEMDIVIDE = { name = "KP_MEMDIVIDE", value = 214 },
KP_PLUSMINUS = { name = "KP_PLUSMINUS", value = 215 },
KP_CLEAR = { name = "KP_CLEAR", value = 216 },
KP_CLEARENTRY = { name = "KP_CLEARENTRY", value = 217 },
KP_BINARY = { name = "KP_BINARY", value = 218 },
KP_OCTAL = { name = "KP_OCTAL", value = 219 },
KP_DECIMAL = { name = "KP_DECIMAL", value = 220 },
KP_HEXADECIMAL = { name = "KP_HEXADECIMAL", value = 221 },
LCTRL = { name = "LCTRL", value = 224 },
LSHIFT = { name = "LSHIFT", value = 225 },
LALT = { name = "LALT", value = 226 },
LGUI = { name = "LGUI", value = 227 },
RCTRL = { name = "RCTRL", value = 228 },
RSHIFT = { name = "RSHIFT", value = 229 },
RALT = { name = "RALT", value = 230 },
RGUI = { name = "RGUI", value = 231 },
MODE = { name = "MODE", value = 257 },
AUDIONEXT = { name = "AUDIONEXT", value = 258 },
AUDIOPREV = { name = "AUDIOPREV", value = 259 },
AUDIOSTOP = { name = "AUDIOSTOP", value = 260 },
AUDIOPLAY = { name = "AUDIOPLAY", value = 261 },
AUDIOMUTE = { name = "AUDIOMUTE", value = 262 },
MEDIASELECT = { name = "MEDIASELECT", value = 263 },
WWW = { name = "WWW", value = 264 },
MAIL = { name = "MAIL", value = 265 },
CALCULATOR = { name = "CALCULATOR", value = 266 },
COMPUTER = { name = "COMPUTER", value = 267 },
AC_SEARCH = { name = "AC_SEARCH", value = 268 },
AC_HOME = { name = "AC_HOME", value = 269 },
AC_BACK = { name = "AC_BACK", value = 270 },
AC_FORWARD = { name = "AC_FORWARD", value = 271 },
AC_STOP = { name = "AC_STOP", value = 272 },
AC_REFRESH = { name = "AC_REFRESH", value = 273 },
AC_BOOKMARKS = { name = "AC_BOOKMARKS", value = 274 },
BRIGHTNESSDOWN = { name = "BRIGHTNESSDOWN", value = 275 },
BRIGHTNESSUP = { name = "BRIGHTNESSUP", value = 276 },
DISPLAYSWITCH = { name = "DISPLAYSWITCH", value = 277 },
KBDILLUMTOGGLE = { name = "KBDILLUMTOGGLE", value = 278 },
KBDILLUMDOWN = { name = "KBDILLUMDOWN", value = 279 },
KBDILLUMUP = { name = "KBDILLUMUP", value = 280 },
EJECT = { name = "EJECT", value = 281 },
SLEEP = { name = "SLEEP", value = 282 },
APP1 = { name = "APP1", value = 283 },
APP2 = { name = "APP2", value = 284 },
AUDIOREWIND = { name = "AUDIOREWIND", value = 285 },
AUDIOFASTFORWARD = { name = "AUDIOFASTFORWARD", value = 286 }
}
MODIFIER = {
NONE = { name = "NONE", value = 0x0000 },
LSHIFT = { name = "LSHIFT", value = 0x0001 },
RSHIFT = { name = "RSHIFT", value = 0x0002 },
LCTRL = { name = "LCTRL", value = 0x0040 },
RCTRL = { name = "RCTRL", value = 0x0080 },
LALT = { name = "LALT", value = 0x0100 },
RALT = { name = "RALT", value = 0x0200 },
LGUI = { name = "LGUI", value = 0x0400 },
RGUI = { name = "RGUI", value = 0x0800 },
NUM = { name = "NUM", value = 0x1000 },
CAPS = { name = "CAPS", value = 0x2000 },
MODE = { name = "MODE", value = 0x4000 },
SCROLL = { name = "SCROLL", value = 0x8000 },
SHIFT = { name = "SHIFT", value = 0x0001 + 0x0002 },
CTRL = { name = "CTRL", value = 0x0040 + 0x0080 },
ALT = { name = "ALT", value = 0x0100 + 0x0200 },
GUI = { name = "GUI", value = 0x0400 + 0x0800 }
}
-- Controller codes are built from the engine's layout: SINGE_GAMEPAD_BASE plus
-- SINGE_GAMEPAD_STRIDE per controller; each axis takes SINGE_AXIS_STRIDE codes
-- (axis, negative direction, positive direction) and buttons follow at
-- SINGE_GAMEPAD_BUTTON_OFFSET in SDL's button order.
local function gamepadAxisCodes(axis, name, negative, positive)
local base = SINGE_GAMEPAD_BASE + axis * SINGE_AXIS_STRIDE
return {
{ name, base }, { name .. "_" .. negative, base + 1 }, { name .. "_" .. positive, base + 2 }
}
end
local gamepadCodes = {}
for _, entry in ipairs(gamepadAxisCodes(0, "AXIS_LEFT_X", "L", "R")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(1, "AXIS_LEFT_Y", "U", "D")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(2, "AXIS_RIGHT_X", "L", "R")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(3, "AXIS_RIGHT_Y", "U", "D")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(4, "AXIS_LEFT_TRIGGER", "N", "P")) do gamepadCodes[#gamepadCodes + 1] = entry end
for _, entry in ipairs(gamepadAxisCodes(5, "AXIS_RIGHT_TRIGGER", "N", "P")) do gamepadCodes[#gamepadCodes + 1] = entry end
local gamepadButtons = {
"BUTTON_A", "BUTTON_B", "BUTTON_X", "BUTTON_Y", "BUTTON_BACK", "BUTTON_GUIDE", "BUTTON_START",
"BUTTON_LEFT_STICK", "BUTTON_RIGHT_STICK", "BUTTON_LEFT_BUMPER", "BUTTON_RIGHT_BUMPER",
"DPAD_UP", "DPAD_DOWN", "DPAD_LEFT", "DPAD_RIGHT"
}
for index, name in ipairs(gamepadButtons) do
gamepadCodes[#gamepadCodes + 1] = { name, SINGE_GAMEPAD_BASE + SINGE_GAMEPAD_BUTTON_OFFSET + index - 1 }
end
GAMEPAD_0 = {}
for _, entry in ipairs(gamepadCodes) do
GAMEPAD_0[entry[1]] = { name = entry[1], value = entry[2] }
end
GAMEPAD_1 = utilOffsetTable(GAMEPAD_0, SINGE_GAMEPAD_STRIDE)
GAMEPAD_2 = utilOffsetTable(GAMEPAD_0, SINGE_GAMEPAD_STRIDE * 2)
GAMEPAD_3 = utilOffsetTable(GAMEPAD_0, SINGE_GAMEPAD_STRIDE * 3)
GAMEPAD_AXIS_LEFT_X = 0
GAMEPAD_AXIS_LEFT_Y = 1
GAMEPAD_AXIS_RIGHT_X = 2
GAMEPAD_AXIS_RIGHT_Y = 3
GAMEPAD_AXIS_LEFT_TRIGGER = 4
GAMEPAD_AXIS_RIGHT_TRIGGER = 5
-- Mouse codes: SINGE_MOUSE_BASE plus SINGE_MOUSE_STRIDE per mouse; buttons then wheel.
local mouseNames = { "BUTTON_LEFT", "BUTTON_RIGHT", "BUTTON_MIDDLE", "BUTTON_X1", "BUTTON_X2", "WHEEL_UP", "WHEEL_DOWN" }
MOUSE_0 = {}
for index, name in ipairs(mouseNames) do
MOUSE_0[name] = { name = name, value = SINGE_MOUSE_BASE + index - 1 }
end
MOUSE_1 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE)
MOUSE_2 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE * 2)
MOUSE_3 = utilOffsetTable(MOUSE_0, SINGE_MOUSE_STRIDE * 3)
-- Older names for engine functions that gained a setter.
if mouseSetEnabled ~= nil then
mouseEnable = function() mouseSetEnabled(true) end
mouseDisable = function() mouseSetEnabled(false) end
end
if singeSetPauseKeyEnabled ~= nil then
singeEnablePauseKey = function() singeSetPauseKeyEnabled(true) end
singeDisablePauseKey = function() singeSetPauseKeyEnabled(false) end
end
-- Hypseus Singe's name for the quit key setting, with the opposite sense: catching the
-- key means the engine stops quitting on it.
if singeSetQuitKeyEnabled ~= nil then
keyboardCatchQuit = function(catch) singeSetQuitKeyEnabled(not catch) end
end
-- Hypseus Singe Compatibility ------------------------------------------------
-- Names Hypseus Singe registers in its own engine and Singe answers from here.
-- Every one is guarded, so an engine that grows a real implementation wins.
-- Hypseus extension. Chooses an overlay resolution and hands it to
-- overlaySetResolution, which is all Hypseus's own call does: 1 is the disc's
-- video size, 3 is 360x240, 4 is the size given (ignored without one, as in
-- Hypseus) and anything else, 2 included, is half the disc on both axes. Singe's
-- discGetWidth and discGetHeight report the canvas size when there is no disc,
-- which is the space a disc-less game draws into, so no special case is needed.
local OVERLAY_PRESET_FULL = 1
local OVERLAY_PRESET_NONSQUARE = 3
local OVERLAY_PRESET_CUSTOM = 4
local OVERLAY_NONSQUARE_WIDTH = 360
local OVERLAY_NONSQUARE_HEIGHT = 240
local OVERLAY_HALF = 2
if setOverlaySize == nil and overlaySetResolution ~= nil then
function setOverlaySize(preset, width, height)
if preset == OVERLAY_PRESET_FULL then
overlaySetResolution(discGetWidth(), discGetHeight())
elseif preset == OVERLAY_PRESET_NONSQUARE then
overlaySetResolution(OVERLAY_NONSQUARE_WIDTH, OVERLAY_NONSQUARE_HEIGHT)
elseif preset == OVERLAY_PRESET_CUSTOM then
if width ~= nil and height ~= nil then
overlaySetResolution(width, height)
end
else
overlaySetResolution(discGetWidth() // OVERLAY_HALF, discGetHeight() // OVERLAY_HALF)
end
end
end
-- Hypseus extension. Hypseus registers one C function under two names and Singe
-- already has the other one, so this is a pure alias of overlaySetResolution.
if setOverlayResolution == nil and overlaySetResolution ~= nil then
setOverlayResolution = overlaySetResolution
end
-- Hypseus extension. True in Hypseus while it is rewriting a game's asset paths
-- for an -espath install. Singe's virtual file system reaches a game's files
-- wherever they were installed, so there is nothing to rewrite and false is the
-- right answer: the games that ask keep the MYDIR they already have.
if rewriteStatus == nil then
function rewriteStatus() return false end
end
-- Singe 3.00 moved the sprite handle to the first argument. Games written for
-- 2.10 can set SINGE_LEGACY_SPRITE_ARGS = true (or LEGACY_SPRITE_ARGS = true in
-- games.dat) to keep calling the old way.
if SINGE_LEGACY_SPRITE_ARGS and spriteDraw ~= nil and not SINGE_LEGACY_SPRITE_WRAPPED then
-- Once only: a game may load this file more than once, and a second wrap would undo the first.
SINGE_LEGACY_SPRITE_WRAPPED = true
local newSpriteDraw = spriteDraw
spriteDraw = function(...)
local args = { ... }
local id = table.remove(args)
return newSpriteDraw(id, table.unpack(args))
end
local newSpriteLoop = spriteLoop
spriteLoop = function(loop, id) return newSpriteLoop(id, loop) end
local newSpriteQuality = spriteQuality
spriteQuality = function(smooth, id) return newSpriteQuality(id, smooth) end
local newSpriteRotate = spriteRotate
spriteRotate = function(angle, id) return newSpriteRotate(id, angle) end
local newSpriteRotateAndScale = spriteRotateAndScale
spriteRotateAndScale = function(...)
local args = { ... }
local id = table.remove(args)
return newSpriteRotateAndScale(id, table.unpack(args))
end
local newSpriteScale = spriteScale
spriteScale = function(...)
local args = { ... }
local id = table.remove(args)
return newSpriteScale(id, table.unpack(args))
end
local newSpriteSetFrame = spriteSetFrame
spriteSetFrame = function(frame, id) return newSpriteSetFrame(id, frame) end
-- Hypseus extensions ship in the old order too: (x, y, frame, spr),
-- (x, y, scale, frame, spr) or (x, y, scaleX, scaleY, frame, spr).
local newSpriteDrawFrame = spriteDrawFrame
spriteDrawFrame = function(...)
local args = { ... }
local id = table.remove(args)
local frame = table.remove(args)
return newSpriteDrawFrame(id, args[1], args[2], frame, table.unpack(args, 3, #args))
end
local newSpriteResetColorKey = spriteResetColorKey
spriteResetColorKey = function(enabled, id) return newSpriteResetColorKey(id, enabled) end
local newSpriteRotateFrame = spriteRotateFrame
spriteRotateFrame = function(degrees, frame, id) return newSpriteRotateFrame(id, degrees, frame) end
local newSpriteDrawRotatedFrame = spriteDrawRotatedFrame
spriteDrawRotatedFrame = function(...)
local args = { ... }
local id = table.remove(args)
return newSpriteDrawRotatedFrame(id, table.unpack(args))
end
-- spriteDrawGrid is the one Hypseus writes with the handle in the middle,
-- (x, y, spr, rectX, rectY, width, height), not on the end.
local newSpriteDrawGrid = spriteDrawGrid
spriteDrawGrid = function(x, y, id, sourceX, sourceY, width, height)
return newSpriteDrawGrid(id, x, y, sourceX, sourceY, width, height)
end
end
-- Hypseus names for calls Singe already has, and nothing more: each is a plain
-- alias, so it takes exactly the arguments the Singe call takes. They are set
-- after the legacy wrapping above, which means the six spriteAnim ones follow
-- SINGE_LEGACY_SPRITE_ARGS with the rest of the sprite family -- old order when
-- a game asked for it, handle first when it did not -- and a game never has two
-- argument orders in play at once.
if hypseusGetHeight == nil and singeGetHeight ~= nil then
hypseusGetHeight = singeGetHeight
end
if hypseusGetWidth == nil and singeGetWidth ~= nil then
hypseusGetWidth = singeGetWidth
end
if takeScreenshot == nil and singeScreenshot ~= nil then
takeScreenshot = singeScreenshot
end
-- Hypseus counts real mice apart from the ones it derives from a gamepad; Singe
-- has no derived mice, so mouseHowMany is already the real count.
if mouseHowManyReal == nil and mouseHowMany ~= nil then
mouseHowManyReal = mouseHowMany
end
if spriteAnimGetFrame == nil and spriteGetFrame ~= nil then
spriteAnimGetFrame = spriteGetFrame
end
if spriteAnimIsPlaying == nil and spriteIsPlaying ~= nil then
spriteAnimIsPlaying = spriteIsPlaying
end
if spriteAnimLoop == nil and spriteLoop ~= nil then
spriteAnimLoop = spriteLoop
end
if spriteAnimPause == nil and spritePause ~= nil then
spriteAnimPause = spritePause
end
if spriteAnimPlay == nil and spritePlay ~= nil then
spriteAnimPlay = spritePlay
end
if spriteAnimSetFrame == nil and spriteSetFrame ~= nil then
spriteAnimSetFrame = spriteSetFrame
end
if videoGetLanguageDescription ~= nil then
discGetLanguageDescription = videoGetLanguageDescription
end
-- Singe 1.xx Features -------------------------------------------------------
-- Make old random number calls still work
random = {}
random.new = math.random
-- Map old Daphne calls to Singe calls
if singeGetHeight ~= nil then
daphneGetHeight = singeGetHeight
daphneGetWidth = singeGetWidth
daphneScreenshot = singeScreenshot
end
-- Singe 2.10 Threaded Application Support ------------------------------------
if singeMain ~= nil then
local singeThread = coroutine.create(singeMain)
onOverlayUpdate = function()
local ok, err = coroutine.resume(singeThread)
if not ok then
error(err, 0)
end
if coroutine.status(singeThread) == "dead" then
singeQuit()
end
return OVERLAY_UPDATED
end
singeYield = coroutine.yield
end
-- ===== Online high scores =====
--
-- A thin layer over Singe/Master.singe so a game says scoreSubmit(1000) and nothing else. The
-- account, the server address and the player's name belong to the machine, not to the game, and
-- were set up once in the menu's Online Account tool.
--
-- Nothing here waits on the network. A score is written to a queue on disk and sent when there is
-- a connection, because a cabinet is often offline and a game must never stall on the wire to show
-- its own board. scoreUpdate() has to run once a frame for any of it to progress.
local masterLoaded = false
local function scoreReady()
if not masterLoaded then
-- Loaded on first use rather than at startup: a game that never posts a score should not
-- pay for the module or open the file.
dofile("Singe/Master.singe")
masterLoaded = true
end
return masterSignedIn()
end
-- The id a game is known by, from its games.dat entry. A game without one can be played but not
-- ranked: there is nothing stable to key a board on, and inventing one here would make a different
-- board every time the file moved.
function scoreGameId()
return singeGetGameId()
end
-- Say that a play is starting. Optional, and worth calling: the service then times the play by its
-- own clock, which is the one thing in a submission that has not been through this machine. A
-- cabinet with no connection simply records no time.
function scoreBegin()
local id = scoreGameId()
if not id or not scoreReady() then
return false
end
masterPlayStart(id, function() end)
return true
end
-- Queue a score. Returns false when the cabinet has no account or the game has no id, so a game
-- can decide whether to say anything about it.
function scoreSubmit(value, board, meta)
local id = scoreGameId()
if not id or not scoreReady() then
return false
end
masterSubmitScore(id, value, board, meta)
return true
end
-- The board, as a callback: onDone(ok, result) where result.top is the list and result.standing is
-- this player's place.
function scoreBoard(onDone, board)
local id = scoreGameId()
if not id or not scoreReady() then
onDone(false, "this cabinet is not signed in")
return false
end
masterBoard(id, board, onDone)
return true
end
function scorePlayerName()
return scoreReady() and MASTER.name or nil
end
function scoreWaiting()
return masterLoaded and masterQueueLength() or 0
end
-- Once a frame, from onOverlayUpdate. Safe to call in a game that never posts anything.
function scoreUpdate()
if masterLoaded then
masterPump()
end
end