--[[ * * Singe 3 * Copyright (C) 2006-2026 Scott Duensing * * 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