diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 000000000..f51e7c9f6 --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,57 @@ +SINGE 2.10 +========== + + Released ...? + + +New Features +------------ + +- spriteDraw() now has two more forms. In addition to being able to draw + regular sprites and streteched sprites it can now draw both using the + sprite's center as the anchor instead of the upper right. This is highly + useful when dealing with rotated sprites. + +- Sprite scaling and rotation! Optional separate X & Y scaling. + +- Optional sprite anti-aliasing. + +- SINGE_DEAD_ZONE global variable now available based on the DEAD_ZONE + controller configuration option. + +- SINGE_FRAMEWORK_VERSION variable added. + +- All new Lua library handling. Previously Singe "injected" libraries + directly into the script context. Now there is a proper Lua module search + handler. Scripts can properly require() modules from the following: + - Lua Standard Library + - Lua Auxillary Library + - LuaFileSystem + - LuaSocket + - LuaSec + - LuaRS232 + +- New command line option: -p (or --program). This is similar to the + existing -t (or --trace) option. Where "trace" displays and logs script + execution, "program" displays and logs Singe internals. This should make + troubleshooting difficult setups and odd script crashes easier. + +- Auto-installer for the new GAME, TOOL, and PATCH archive formats to make + managing games easier. + + +Fixes +----- + +- USB controllers now work in games launched from the included menu. + +- Framework.singe was missing keyboard mode constants. MODE_NORMAL and + MODE_FULL are now included. + +- Taking screenshots could sometimes crash Singe. + +- PNG files no longer generate warnings on the console. + +- Building Singe depended on a lot of undocumented software cobbled together + from other projects. This has been vastly improved and should be usable by + actual humans now. diff --git a/INSTALL.txt b/INSTALL.txt index 7ce909bae..8498d00a3 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -40,7 +40,7 @@ COMMAND LINE ___ ___ _ _ ___ ___ / __|_ _| \| |/ __| __| Somewhat Interactive Nostalgic Game Engine v2.10 \__ \| || .` | (_ | _| Copyright (c) 2006-2024 Scott C. Duensing -|___/___|_|\_|\___|___| https://kangaroopunch.com +|___/___|_|\_|\___|___| https://KangarooPunch.com https://SingeEngine.com Usage: Singe-v2.10-Windows-x86_64.exe [OPTIONS] scriptName{.singe} @@ -54,6 +54,7 @@ Usage: Singe-v2.10-Windows-x86_64.exe [OPTIONS] scriptName{.singe} -l, --volume_vldp=PERCENT specify laserdisc volume in percent -m, --nomouse disable mouse -n, --nocrosshair request game not display gun crosshairs + -p, --program trace Singe execution to screen and file -s, --nosound, --mutesound mutes all sound -t, --trace trace script execution to screen and file -u, --stretch use ugly stretched video diff --git a/assets/Framework.singe b/assets/Framework.singe index 896aeca4f..f916fc7e9 100644 --- a/assets/Framework.singe +++ b/assets/Framework.singe @@ -24,6 +24,9 @@ -- Singe 2.xx Features ------------------------------------------------------- +SINGE_FRAMEWORK_VERSION = 2.10 + + function utilDeepCopy(orig) local orig_type = type(orig) local copy diff --git a/assets/Menu.singe b/assets/Menu.singe index 92f21ab15..a89e54017 100644 --- a/assets/Menu.singe +++ b/assets/Menu.singe @@ -26,8 +26,19 @@ dofile("Singe/Framework.singe") lfs = require("lfs") +function cleanTitle(a) + local output = string.lower(a) + output = string.gsub(output, '%p', ' ') + output = string.gsub(output, '^ +', '') + output = string.gsub(output, '^the ', '') + output = string.gsub(output, '^a ', '') + output = string.gsub(output, '^an ', '') + return output +end + + function compareTitles(a, b) - return a.TITLE < b.TITLE + return cleanTitle(a.TITLE) < cleanTitle(b.TITLE) end diff --git a/src/main.c b/src/main.c index 6636503db..b87e61811 100644 --- a/src/main.c +++ b/src/main.c @@ -871,14 +871,28 @@ void unpackGames(void) { char *extension; char *filename; char *toplevel = NULL; + int packageType = -1; + char *types[] = { "Game", "Tool", "Patch", 0 }; char *badFilenames[] = { "Framework.singe", 0 }; char *badExtensions[] = { "exe", "sh", "bat", "cmd", "index", 0 }; + // Games cannot include forbidden extensions, Singe binaries, multiple folders, etc. + // Tools are like games but do not need a games.dat file. + // Patches can be pretty much anything except forbidden extensions. + if (dir == NULL) utilDie("Could not open the current directory."); while ((de = readdir(dir)) != NULL) { if (de->d_type == DT_REG || de->d_type == DT_LNK || de->d_type == DT_UNKNOWN) { - if (utilStricmp(utilGetFileExtension(de->d_name), "game") == 0) { + packageType = -1; + x = 0; + while (types[x] != NULL) { + if (utilStricmp(utilGetFileExtension(de->d_name), types[x]) == 0) { + packageType = x; + break; + } + } + if (packageType >= 0) { showHeader(); count++; ok = true; @@ -891,7 +905,7 @@ void unpackGames(void) { archive_read_support_format_all(a); r = archive_read_open_filename(a, de->d_name, 10240); if (r != ARCHIVE_OK) { - utilSay("!!! Cannot read game: %s", de->d_name); + utilSay("!!! Cannot read %s: %s", types[packageType], de->d_name); ok = false; } else { while (archive_read_next_header(a, &entry) == ARCHIVE_OK && ok) { @@ -899,36 +913,35 @@ void unpackGames(void) { filename = utilGetLastPathComponent(e); extension = utilGetFileExtension(e); - // Do we have a top level folder name yet? - if (toplevel == NULL) { + // If not a patch, do we have a top level folder name yet? + if ((utilStricmp(types[packageType], "Patch") != 0) && (toplevel == NULL)) { // No. Is this a folder? if (strstr(e, "/") == NULL) { // No. BAD! No files in the root! ok = false; - utilSay("!!! Game has files in root: %s", de->d_name); + utilSay("!!! %s has files in root: %s", types[packageType], de->d_name); } else { // Remember this folder. toplevel = strdup(e); gamesDat = utilCreateString("%sgames.dat", toplevel); } } else { - // Yes, we have a top level. Is this entry inside it? - if (!utilStartsWith(e, toplevel)) { + // If this is not a patch, check for a top level. Is this entry inside it? + if ((utilStricmp(types[packageType], "Patch") != 0) && (!utilStartsWith(e, toplevel))) { // No. BAD! Everything has to be in the top level. ok = false; if (strstr(e, "/") == NULL) { - utilSay("!!! Game has files in root: %s", de->d_name); + utilSay("!!! %s has files in root: %s", types[packageType], de->d_name); } else { - utilSay("!!! Game has multiple top level directories: %s", de->d_name); + utilSay("!!! %s has multiple top level directories: %s", types[packageType], de->d_name); } - utilSay("[%s] %s", toplevel, e); } else { // Is this a forbidden file? x = 0; while (badFilenames[x] != NULL && ok) { if (utilStricmp(filename, badFilenames[x]) == 0) { ok = false; - utilSay("!!! Game has %s: %s", badFilenames[x], de->d_name); + utilSay("!!! %s has %s: %s", types[packageType], badFilenames[x], de->d_name); } x++; } @@ -937,14 +950,14 @@ void unpackGames(void) { while (badExtensions[x] != NULL && ok) { if (utilStricmp(extension, badExtensions[x]) == 0) { ok = false; - utilSay("!!! Game has %s file: %s", badExtensions[x], de->d_name); + utilSay("!!! %s has %s file: %s", types[packageType], badExtensions[x], de->d_name); } x++; } - // No extension, starts with 'singe' - could be unix binary. - if (ok && strlen(extension) == 0 && utilStartsWith(filename, "singe")) { + // Not a patch, no extension, starts with 'singe' - could be unix binary. + if ((utilStricmp(types[packageType], "Patch") != 0) && (ok && strlen(extension) == 0 && utilStartsWith(filename, "singe"))) { ok = false; - utilSay("!!! Game has singe file: %s", de->d_name); + utilSay("!!! %s has singe file: %s", types[packageType], de->d_name); } // Is this games.dat? if (ok && !hasGamesDat && utilStricmp(e, gamesDat) == 0) hasGamesDat = true; @@ -963,16 +976,16 @@ void unpackGames(void) { gamesDat = NULL; } - // Did we get a games.dat? - if (ok && !hasGamesDat) { + // If it's a game, did we get a games.dat? + if (((utilStricmp(types[packageType], "Game") == 0)) && (ok && !hasGamesDat)) { ok = false; - utilSay("!!! Game has no games.dat: %s", de->d_name); + utilSay("!!! %s has no games.dat: %s", types[packageType], de->d_name); } } // Unpack it! if (ok) { - utilSay(">>> Unpacking Game: %s", de->d_name); + utilSay(">>> Unpacking %s: %s", types[packageType], de->d_name); // https://github.com/libarchive/libarchive/wiki/Examples#user-content-A_Complete_Extractor flags = ARCHIVE_EXTRACT_TIME; flags |= ARCHIVE_EXTRACT_PERM; @@ -1033,7 +1046,7 @@ void unpackGames(void) { if (ok) unlink(de->d_name); } // if ok - } // if extension is game + } // if extension is valid } // if it's a file } // while files diff --git a/src/singe.c b/src/singe.c index bd4fbd58c..2d31280bf 100644 --- a/src/singe.c +++ b/src/singe.c @@ -4623,31 +4623,27 @@ void startControllers(void) { void startLuaContext(lua_State *L) { size_t length; - luaL_openlibs(L); + // What to do when bad things happen lua_atpanic(L, luaError); - // Get the package global table. + // Register the standard libraries + luaL_openlibs(L); + + // Share configured controller DEAD_ZONE with the script + lua_pushinteger(L, _global.controllerDeadZone); + lua_setglobal(L, "SINGE_DEAD_ZONE"); + + // Get the package global table lua_getglobal(L, "package"); - // Get the list of searchers in the package table. + // Get the list of searchers in the package table lua_getfield(L, -1, "searchers"); - // Get the number of existing searchers in the table. + // Get the number of existing searchers in the table length = lua_rawlen(L, -1); - // Add our own searcher to the list. + // Add our own searcher to the list lua_pushcfunction(L, luaSearcher); lua_rawseti(L, -2, length + 1); - // Remove the seachers and the package tables from the stack. + // Remove the seachers and the package tables from the stack lua_pop(L, 2); - - /* - --[[ - http = require("socket.http") - http.request{ - url = "http://www.columbia.edu/~fdc/sample.html", - sink = ltn12.sink.file(io.stdout) - } - --]] - - */ } @@ -4690,23 +4686,33 @@ void takeScreenshot(void) { char filename[1024]; void *pixels = NULL; SDL_Surface *surface = NULL; + SDL_Surface *save = NULL; SDL_Rect viewport; - while (x <= 999) { + while (x <= 9999) { snprintf(filename, 1024, "%ssinge%03d.png", _global.conf->dataDir, x); if (!utilFileExists(filename)) break; x++; } - if (x > 999) utilDie("Seriously? You have 1000 screenshots in this folder? Remove some."); + if (x > 9999) utilDie("Seriously? You have 10,000 screenshots in this folder? Remove some."); - SDL_RenderGetViewport(_global.renderer, &viewport); - surface = SDL_CreateRGBSurface(0, viewport.w, viewport.h, 32, 0, 0, 0, 0); - if (!surface) utilDie("%s", SDL_GetError()); - if (SDL_RenderReadPixels(_global.renderer, NULL, surface->format->format, surface->pixels, surface->pitch) != 0) utilDie("%s", SDL_GetError()); - //***FIX*** This crashes with a SEGFAULT sometimes. Not sure why. - if (IMG_SavePNG(surface, filename) < 0) utilDie("%s", IMG_GetError()); - SDL_FreeSurface(surface); - free(pixels); + surface = SDL_GetWindowSurface(_global.window); + pixels = (uint8_t *)malloc(surface->w * surface->h * surface->format->BytesPerPixel); + SDL_RenderReadPixels(_global.renderer, &surface->clip_rect, surface->format->format, pixels, surface->w * surface->format->BytesPerPixel); + save = SDL_CreateRGBSurfaceFrom(pixels, + surface->w, + surface->h, + surface->format->BitsPerPixel, + surface->w * surface->format->BytesPerPixel, + surface->format->Rmask, + surface->format->Gmask, + surface->format->Bmask, + surface->format->Amask + ); + if (IMG_SavePNG(save, filename) < 0) utilDie("%s", IMG_GetError()); + SDL_FreeSurface(save); + SDL_FreeSurface(surface); + free(pixels); }