--[[ * * Singe 2 * Copyright (C) 2006-2024 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 -- 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 then 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 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