diff --git a/polymer/eduke32/Makefile b/polymer/eduke32/Makefile index 68465579e..62fde7b43 100644 --- a/polymer/eduke32/Makefile +++ b/polymer/eduke32/Makefile @@ -144,7 +144,7 @@ MISCEDITORDEPS= ## Lunatic devel ifneq (0,$(LUNATIC)) - LUNATIC_COMMON_OBJS = $(OBJ)/luaJIT_BC_defs_common.$o $(OBJ)/luaJIT_BC_engine_maptext.$o + LUNATIC_COMMON_OBJS = $(OBJ)/luaJIT_BC_defs_common.$o $(OBJ)/luaJIT_BC_engine_maptext.$o $(OBJ)/luaJIT_BC_engine.$o EDITOROBJS+= $(OBJ)/lunatic_m32.$o $(LUNATIC_COMMON_OBJS) GAMEOBJS+= $(OBJ)/lunatic_game.$o $(LUNATIC_COMMON_OBJS) diff --git a/polymer/eduke32/build/src/engine.c b/polymer/eduke32/build/src/engine.c index 1c4326c53..68d5afe67 100644 --- a/polymer/eduke32/build/src/engine.c +++ b/polymer/eduke32/build/src/engine.c @@ -14285,6 +14285,36 @@ void rotatesprite_(int32_t sx, int32_t sy, int32_t z, int16_t a, int16_t picnum, } } +static int32_t palookup_isdefault(int32_t palnum) // KEEPINSYNC engine.lua +{ + return (palookup[palnum] == NULL || (palnum!=0 && palookup[palnum] == palookup[0])); +} + +static void maybe_alloc_palookup(int32_t palnum) +{ + if (palookup_isdefault(palnum)) + { + alloc_palookup(palnum); + if (palookup[palnum] == NULL) + exit(1); + } +} + +#ifdef LUNATIC +int32_t setpalookup(int32_t palnum, const uint8_t *shtab) +{ + if (numshades != 32) + return -1; + + if (shtab != NULL) + { + maybe_alloc_palookup(palnum); + Bmemcpy(palookup[palnum], shtab, 256*numshades); + } + + return 0; +} +#endif // // makepalookup @@ -14319,12 +14349,7 @@ void makepalookup(int32_t palnum, const char *remapbuf, int8_t r, int8_t g, int8 remapbuf = idmap; } - if (palookup[palnum] == NULL || (palnum!=0 && palookup[palnum] == palookup[0])) - { - alloc_palookup(palnum); - if (palookup[palnum] == NULL) - exit(1); - } + maybe_alloc_palookup(palnum); if (dastat == 0) return; if ((r|g|b|63) != 63) return; diff --git a/polymer/eduke32/source/game.c b/polymer/eduke32/source/game.c index cf081172b..e93792ff0 100644 --- a/polymer/eduke32/source/game.c +++ b/polymer/eduke32/source/game.c @@ -10745,6 +10745,8 @@ static void G_Startup(void) Gv_FinalizeWeaponDefaults(); G_PostCreateGameState(); #ifdef LUNATIC + // NOTE: This is only effective for CON-defined EVENT_INIT. See EVENT_INIT + // not in defs.ilua. VM_OnEvent(EVENT_INIT, -1, -1, -1, 0); #endif if (g_netServer || ud.multimode > 1) G_CheckGametype(); diff --git a/polymer/eduke32/source/lunatic/defs.ilua b/polymer/eduke32/source/lunatic/defs.ilua index 7c83a1c7f..9f9111dfc 100644 --- a/polymer/eduke32/source/lunatic/defs.ilua +++ b/polymer/eduke32/source/lunatic/defs.ilua @@ -65,6 +65,15 @@ end lprivate.decl = decl +ffi.cdef[[ +enum { + LUNATIC_CLIENT_MAPSTER32 = 0, + LUNATIC_CLIENT_EDUKE32 = 1, + + LUNATIC_CLIENT = LUNATIC_CLIENT_EDUKE32 +} +]] + -- Load the definitions common to the game's and editor's Lua interface. local defs_c = require("defs_common") local cansee = defs_c.cansee @@ -608,6 +617,7 @@ int32_t (*El_RestoreGamevars)(const char *savecode); const char *s_buildRev; const char *g_sizes_of_what[]; int32_t g_sizes_of[]; +int32_t g_elFirstTime; int32_t g_elCallDepth; int32_t block_deletesprite; const char **g_elModules; @@ -1570,6 +1580,7 @@ local allowed_modules = { }, randgen = randgen, + engine = require("engine"), stat = require("stat"), bitar = require("bitar"), xmath = require("xmath"), @@ -2186,6 +2197,8 @@ G_.actor = actor G_.projectile = projectile G_.g_tile = g_tile +G_.LUNATIC_FIRST_TIME = (ffiC.g_elFirstTime ~= 0) + -- A table that can be used for temporary data when debugging from the OSD. G_.d = {} @@ -2552,4 +2565,6 @@ if (not g_firstRun) then i = i+1 end + + ffiC.g_elFirstTime = 0 end diff --git a/polymer/eduke32/source/lunatic/defs_m32.ilua b/polymer/eduke32/source/lunatic/defs_m32.ilua index 093f203ec..399db6432 100644 --- a/polymer/eduke32/source/lunatic/defs_m32.ilua +++ b/polymer/eduke32/source/lunatic/defs_m32.ilua @@ -4,6 +4,15 @@ local ffi = require("ffi") local ffiC = ffi.C +ffi.cdef[[ +enum { + LUNATIC_CLIENT_MAPSTER32 = 0, + LUNATIC_CLIENT_EDUKE32 = 1, + + LUNATIC_CLIENT = LUNATIC_CLIENT_MAPSTER32 +} +]] + --== First, load the definitions common to the game's and editor's Lua interface. decl = ffi.cdef local defs_c = require("defs_common") @@ -11,6 +20,8 @@ defs_c.finish_spritetype({}) defs_c.create_globals(_G) +-- TODO: provide access to only a subset, restict access to ffiC? +gv = ffiC --== Mapster32-specific initialization @@ -32,4 +43,13 @@ while (true) do initp = 0 end +-- Helper functions +local package = package +local require = require + +function reload(modname) + package.loaded[modname] = nil + return require(modname) +end + --print('Lua load path: '..package.path) diff --git a/polymer/eduke32/source/lunatic/dynsymlist b/polymer/eduke32/source/lunatic/dynsymlist index 4f2948936..a4bb73501 100644 --- a/polymer/eduke32/source/lunatic/dynsymlist +++ b/polymer/eduke32/source/lunatic/dynsymlist @@ -66,6 +66,11 @@ clipmovex; rotatesprite_; setaspect; +getclosestcol; +palookup; +palette; +setpalookup; + kopen4load; kfilelength; kclose; @@ -91,6 +96,7 @@ crc32once; luaJIT_BC_defs_common; luaJIT_BC_engine_maptext; +luaJIT_BC_engine; g_argv; @@ -108,6 +114,7 @@ El_RestoreGamevars; s_buildRev; g_sizes_of_what; g_sizes_of; +g_elFirstTime; g_elCallDepth; block_deletesprite; g_RETURN; diff --git a/polymer/eduke32/source/lunatic/dynsymlist_m32 b/polymer/eduke32/source/lunatic/dynsymlist_m32 index eeecbc8ea..98a65d98d 100644 --- a/polymer/eduke32/source/lunatic/dynsymlist_m32 +++ b/polymer/eduke32/source/lunatic/dynsymlist_m32 @@ -66,6 +66,11 @@ clipmovex; rotatesprite_; setaspect; +getclosestcol; +palookup; +palette; +setpalookup; + kopen4load; kfilelength; kclose; @@ -87,6 +92,7 @@ crc32once; luaJIT_BC_defs_common; luaJIT_BC_engine_maptext; +luaJIT_BC_engine; g_argv; diff --git a/polymer/eduke32/source/lunatic/engine.lua b/polymer/eduke32/source/lunatic/engine.lua new file mode 100644 index 000000000..b4c874dc7 --- /dev/null +++ b/polymer/eduke32/source/lunatic/engine.lua @@ -0,0 +1,117 @@ + +local ffi = require("ffi") +local C = ffi.C + +local bcarray = require("bcarray") + +local error = error +local type = type + +local decl = assert(decl) -- comes from above (defs.ilua or defs_m32.lua) + +local ismapster32 = (C.LUNATIC_CLIENT == C.LUNATIC_CLIENT_MAPSTER32) + +---------- + +decl[[ +int32_t getclosestcol(int32_t r, int32_t g, int32_t b); +char *palookup[256]; // MAXPALOOKUPS +uint8_t palette[768]; + +int32_t setpalookup(int32_t palnum, const uint8_t *shtab); +]] + +---------- + +-- The API table +local engine = {} + +local pal256_t = bcarray.new("uint8_t", 256, "shade table 256-tuple") +-- The shade table type, effectively a bound-checked uint8_t [32][256]: +local shtab_t = bcarray.new(pal256_t, 32, "shade table") +local SIZEOF_SHTAB = ffi.sizeof(shtab_t) + +local RESERVEDPALS = 8 -- KEEPINSYNC build.h: assure that ours is >= theirs +engine.RESERVEDPALS = RESERVEDPALS + +local function check_palidx(i) + if (type(i) ~= "number" or not (i >= 0 and i <= 255-RESERVEDPALS)) then + error("invalid argument #1: palette index must be in the range [0 .. "..255-RESERVEDPALS.."]", 3) + end +end + +local function err_uncommon_shade_table(ret) + if (ret == -1) then + error("loaded engine shade tables don't have 32 gradients of shade", 3) + end +end + +local function palookup_isdefault(palnum) -- KEEPINSYNC engine.c + return (C.palookup[palnum] == nil or (palnum ~= 0 and C.palookup[palnum] == C.palookup[0])) +end + +function engine.shadetab() + return shtab_t() +end + +function engine.getshadetab(palidx) + check_palidx(palidx) + if (palookup_isdefault(palidx)) then + return nil + end + + local ret = C.setpalookup(palidx, nil) + err_uncommon_shade_table(ret) + + local sht = shtab_t() + ffi.copy(sht, C.palookup[palidx], SIZEOF_SHTAB) + return sht +end + +function engine.setshadetab(palidx, shtab) + if (not ismapster32 and C.g_elFirstTime == 0) then + error("setshadetab() may be run only while LUNATIC_FIRST_TIME is true", 2) + end + + check_palidx(palidx) + if (not ffi.istype(shtab_t, shtab_t)) then + error("invalid argument #2: must be a shade table obtained by shadetab()", 2) + end + + if (not ismapster32 and not palookup_isdefault(palidx)) then + error("attempt to override already defined shade table", 2) + end + + local ret = C.setpalookup(palidx, ffi.cast("uint8_t *", shtab)) + err_uncommon_shade_table(ret) +end + + +local function check_colcomp(a) + if (type(a) ~= "number" or not (a >= 0 and a <= 63)) then + error("color component must be in the range [0 .. 63]", 3) + end +end + + +-- TODO: other base palettes? +function engine.getrgb(colidx) + if (type(colidx) ~= "number" or not (colidx >= 0 and colidx <= 255)) then + error("color index must be in the range [0 .. 255]", 2) + end + + local rgbptr = C.palette + 3*colidx + return rgbptr[0], rgbptr[1], rgbptr[2] +end + +-- TODO: flag whether fullbrights are OK +function engine.nearcolor(r, g, b) + check_colcomp(r) + check_colcomp(g) + check_colcomp(b) + return C.getclosestcol(r, g, b) +end + + +-- Done! +return engine diff --git a/polymer/eduke32/source/lunatic/lunatic_game.c b/polymer/eduke32/source/lunatic/lunatic_game.c index 58a84af73..bc1fa89bf 100644 --- a/polymer/eduke32/source/lunatic/lunatic_game.c +++ b/polymer/eduke32/source/lunatic/lunatic_game.c @@ -37,6 +37,9 @@ int32_t g_elSessionVar[8]; // MAXSESSIONVARS, KEEPINSYNC con_lang.lua // Set to 1 on error in event. int32_t g_elEventError; +// Will be set to 0 after the first time that user Lua modules are run. +int32_t g_elFirstTime = 1; + int32_t g_elCallDepth = 0; int32_t g_RETURN; diff --git a/polymer/eduke32/source/lunatic/test.lua b/polymer/eduke32/source/lunatic/test.lua index 78e4fa789..a115cfa15 100644 --- a/polymer/eduke32/source/lunatic/test.lua +++ b/polymer/eduke32/source/lunatic/test.lua @@ -170,6 +170,9 @@ gameevent -- sprite.picnum may happen as a thinko/typo kind of error (spr.picnum was meant) checkfail("local pic = sprite.picnum", "invalid access to static data") + + checkfail("require('engine').setshadetab(200, nil)", + "setshadetab() may be run only while LUNATIC_FIRST_TIME is true") end } @@ -327,8 +330,6 @@ gameevent -- NOTE: setting colors partially is bad! E.g. after an item is -- picked up, col[0] and col[1] remain and tint everything greenish. if (DBG_ ~= nil) then - -- XXX (unrelated to these lines): issuing tinting sometimes makes - -- it flicker in the GL modes. ps._pals[2] = 20 ps._pals.f = 30 end @@ -888,3 +889,7 @@ do printf('Time for %d runs: getangle: %.03f ms, math.atan2: %.03f ms', N, t1, t2) print('----------') end + +if (LUNATIC_FIRST_TIME) then + require("test.shadexfog").test_create0() +end diff --git a/polymer/eduke32/source/lunatic/test/shadexfog.lua b/polymer/eduke32/source/lunatic/test/shadexfog.lua new file mode 100644 index 000000000..c7a023107 --- /dev/null +++ b/polymer/eduke32/source/lunatic/test/shadexfog.lua @@ -0,0 +1,199 @@ +--[[ + Usage: in Mapster32, + > lua "shadexfog=reload'shadexfog'" + -- for example + > lua "shadexfog.create(100, 63,63,63)" + > lua "shadexfog.translate(100, 2)" + In EDuke32, simply pass this module at the command line. +--]] + +local error = error + +local math = require("math") +local min, max = math.min, math.max + +local sector, wall, sprite = sector, wall, sprite + +local engine = require("engine") +local gv = gv + +---------- + +local shadexfog = {} + +-- Create 32 palookups corrensponding to different *shade levels* of a fog +-- palookup, called a "shade-x-fog" palookup set in the following. +-- +-- Pals .. +31 will be taken. +-- , , : intensities of the fog color, [0 ..63] +function shadexfog.create(startpalnum, fogr, fogg, fogb) + local MAXPALNUM = 255-31-engine.RESERVEDPALS + if (not (startpalnum >= 1 and startpalnum <= MAXPALNUM)) then + error("invalid startpalnum, max="..MAXPALNUM, 2) + end + + local basesht = engine.getshadetab(0) + + -- Encode the shade in different pal numbers! The shade tables are + -- constructed with a fog in their place. + for dummyshade=1,31 do + local sht = engine.shadetab() + + for f=0,31 do + for i=0,255 do + local r, g, b = engine.getrgb(basesht[dummyshade][i]) + + local nr, ng, nb = + (r*(32-f) + fogr*f) / 32, + (g*(32-f) + fogg*f) / 32, + (b*(32-f) + fogb*f) / 32 + + sht[f][i] = engine.nearcolor(nr, ng, nb) + end + end + + engine.setshadetab(startpalnum + dummyshade, sht) + end +end + +local function trans(what, startpalnum, fogintensity) + local shade = min(max(what.shade, 0), 31) + what.pal = startpalnum + shade + what.shade = fogintensity +end + +-- shadexfog.translate(startpalnum, fogintensity [, vis]) +-- +-- Translate the whole map for use with a shade-x-fog palookup set. +-- .pal becomes the + former .shade +-- .shade becomes the [0 .. 31] +-- If is passed, set all sector's visibility to that value. +-- +-- Notes: +-- - works only a single time (TODO: autodetection if already applied) +-- - if shades < 0 or > 31 present, loss of information +function shadexfog.translate(startpalnum, fogintensity, vis) + for i=0,gv.numsectors-1 do + trans(sector[i].ceiling, startpalnum, fogintensity) + trans(sector[i].floor, startpalnum, fogintensity) + if (vis) then + sector[i].visibility = vis + end + end + + for i=0,gv.numwalls-1 do + trans(wall[i], startpalnum, fogintensity) + end +end + +if (gv.LUNATIC_CLIENT == gv.LUNATIC_CLIENT_EDUKE32 and LUNATIC_FIRST_TIME) then + shadexfog.create(100, 63,63,63) + print("created shadexfog palookups") +end + +---------- BASE SHADE TABLE TESTS ---------- + +-- Basic test of whether for a color index i corresponding to a color (r,g,b), +-- getclosestcol() returns a color index ii corresponding to the same color. +-- (In the Duke3D palette, there are duplicates, so the requirement i==ii is +-- too strict.) +function shadexfog.test_nearcolor() + for i=0,255 do + local r, g, b = engine.getrgb(i) + local ii = engine.nearcolor(r, g, b) + local rr, gg, bb = engine.getrgb(ii) + + if (r~=rr or g~=gg or b~=bb) then + printf("diff %d: %d,%d,%d %d,%d,%d", i, r,g,b, rr,gg,bb) + end + end +end + +-- Change the .pal member of all sector ceilings/floors, walls and sprites to +-- . +function shadexfog.challpal(palnum) + for i=0,gv.numsectors-1 do + sector[i].ceilingpal = palnum + sector[i].floorpal = palnum + end + for i=0,gv.numwalls-1 do + wall[i].pal = palnum + end + for i in sprite.all() do + sprite[i].pal = palnum + end +end + +-- Create our version of the base shade table (palookup 0) +-- +-- NOTE: Nope, the base shade table is NOT created by applying a linear ramp to +-- the base palette colors!!! +local function create_base_shtab(basesht) + local basesht = basesht or engine.getshadetab(0) + + local sht = engine.shadetab() + sht[0] = basesht[0] + for sh=1,31 do + for i=0,255-16 do + -- NOTE that this fails, see BASESHT_0_NOT_IDENTITY: +-- assert(basesht[0][i] == i) + local r, g, b = engine.getrgb(i) + local f = 1 + r = ((32-f*sh+0.5)*r)/32 + g = ((32-f*sh+0.5)*g)/32 + b = ((32-f*sh+0.5)*b)/32 + r, g, b = max(0,r), max(0,g), max(0,b) -- if f is > 1 + sht[sh][i] = engine.nearcolor(r, g, b) + end + + for i=255-16+1,255 do + -- fullbrights + sht[sh][i] = basesht[0][i] + end + end + + return sht +end + +-- Create our (failed) version of the base shade table at set it to palookup +-- number . +function shadexfog.create0(palnum) + local sht0 = create_base_shtab() + engine.setshadetab(palnum, sht0) +end + +function shadexfog.test_create0() + local basesht = engine.getshadetab(0) + + for i=0,255 do + if (basesht[0][i] ~= i) then + -- BASESHT_0_NOT_IDENTITY + printf("Base shade table at index %d: %d", i, basesht[0][i]) + end + end + + local sht = create_base_shtab(basesht) + + local ok = true + for sh=1,31 do + for i=0,255 do + local ouri, origi = sht[sh][i], basesht[sh][i] +-- if (sht[sh][i] ~= basesht[sh][i]) then + if (math.abs(ouri - origi) > 1) then + printf("Constructed shade table DIFFERS AT shade %d index %d: orig %d ours %d", + sh, i, basesht[sh][i], sht[sh][i]) + ok = false + goto out + end + end + end + + ::out:: + if (ok) then + printf("Constructed shade table IDENTICAL WITH original one") + end +end + +do + return shadexfog +end