Lunatic: add 'engine' module, currently allowing to create custom shade tables.

Available in the game and editor. Provide test/shadexfog.lua, containing a
function to creating a set of 32 shade tables corresponding to different shades
of the same fog palookup table, together with some tests and convenience
functions.

Also,
- Add gv.LUNATIC_CLIENT{,_EDUKE32,_MAPSTER32}
- Add LUNATIC_FIRST_TIME in the global env for the game
- defs_m32.lua: add reload() convenience function
- Failed attempt at recreating the base shade table. It is NOT a linear ramp
  of the base palette colors to (0,0,0). That is, it's not created by
  build/util/transpal.exe!

git-svn-id: https://svn.eduke32.com/eduke32@4236 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2013-12-31 11:52:00 +00:00
parent 75f5449e9a
commit 13c125db02
11 changed files with 408 additions and 9 deletions

View file

@ -144,7 +144,7 @@ MISCEDITORDEPS=
## Lunatic devel ## Lunatic devel
ifneq (0,$(LUNATIC)) 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) EDITOROBJS+= $(OBJ)/lunatic_m32.$o $(LUNATIC_COMMON_OBJS)
GAMEOBJS+= $(OBJ)/lunatic_game.$o $(LUNATIC_COMMON_OBJS) GAMEOBJS+= $(OBJ)/lunatic_game.$o $(LUNATIC_COMMON_OBJS)

View file

@ -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 // makepalookup
@ -14319,12 +14349,7 @@ void makepalookup(int32_t palnum, const char *remapbuf, int8_t r, int8_t g, int8
remapbuf = idmap; remapbuf = idmap;
} }
if (palookup[palnum] == NULL || (palnum!=0 && palookup[palnum] == palookup[0])) maybe_alloc_palookup(palnum);
{
alloc_palookup(palnum);
if (palookup[palnum] == NULL)
exit(1);
}
if (dastat == 0) return; if (dastat == 0) return;
if ((r|g|b|63) != 63) return; if ((r|g|b|63) != 63) return;

View file

@ -10745,6 +10745,8 @@ static void G_Startup(void)
Gv_FinalizeWeaponDefaults(); Gv_FinalizeWeaponDefaults();
G_PostCreateGameState(); G_PostCreateGameState();
#ifdef LUNATIC #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); VM_OnEvent(EVENT_INIT, -1, -1, -1, 0);
#endif #endif
if (g_netServer || ud.multimode > 1) G_CheckGametype(); if (g_netServer || ud.multimode > 1) G_CheckGametype();

View file

@ -65,6 +65,15 @@ end
lprivate.decl = decl 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. -- Load the definitions common to the game's and editor's Lua interface.
local defs_c = require("defs_common") local defs_c = require("defs_common")
local cansee = defs_c.cansee local cansee = defs_c.cansee
@ -608,6 +617,7 @@ int32_t (*El_RestoreGamevars)(const char *savecode);
const char *s_buildRev; const char *s_buildRev;
const char *g_sizes_of_what[]; const char *g_sizes_of_what[];
int32_t g_sizes_of[]; int32_t g_sizes_of[];
int32_t g_elFirstTime;
int32_t g_elCallDepth; int32_t g_elCallDepth;
int32_t block_deletesprite; int32_t block_deletesprite;
const char **g_elModules; const char **g_elModules;
@ -1570,6 +1580,7 @@ local allowed_modules = {
}, },
randgen = randgen, randgen = randgen,
engine = require("engine"),
stat = require("stat"), stat = require("stat"),
bitar = require("bitar"), bitar = require("bitar"),
xmath = require("xmath"), xmath = require("xmath"),
@ -2186,6 +2197,8 @@ G_.actor = actor
G_.projectile = projectile G_.projectile = projectile
G_.g_tile = g_tile 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. -- A table that can be used for temporary data when debugging from the OSD.
G_.d = {} G_.d = {}
@ -2552,4 +2565,6 @@ if (not g_firstRun) then
i = i+1 i = i+1
end end
ffiC.g_elFirstTime = 0
end end

View file

@ -4,6 +4,15 @@
local ffi = require("ffi") local ffi = require("ffi")
local ffiC = ffi.C 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. --== First, load the definitions common to the game's and editor's Lua interface.
decl = ffi.cdef decl = ffi.cdef
local defs_c = require("defs_common") local defs_c = require("defs_common")
@ -11,6 +20,8 @@ defs_c.finish_spritetype({})
defs_c.create_globals(_G) defs_c.create_globals(_G)
-- TODO: provide access to only a subset, restict access to ffiC?
gv = ffiC
--== Mapster32-specific initialization --== Mapster32-specific initialization
@ -32,4 +43,13 @@ while (true) do
initp = 0 initp = 0
end 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) --print('Lua load path: '..package.path)

View file

@ -66,6 +66,11 @@ clipmovex;
rotatesprite_; rotatesprite_;
setaspect; setaspect;
getclosestcol;
palookup;
palette;
setpalookup;
kopen4load; kopen4load;
kfilelength; kfilelength;
kclose; kclose;
@ -91,6 +96,7 @@ crc32once;
luaJIT_BC_defs_common; luaJIT_BC_defs_common;
luaJIT_BC_engine_maptext; luaJIT_BC_engine_maptext;
luaJIT_BC_engine;
g_argv; g_argv;
@ -108,6 +114,7 @@ El_RestoreGamevars;
s_buildRev; s_buildRev;
g_sizes_of_what; g_sizes_of_what;
g_sizes_of; g_sizes_of;
g_elFirstTime;
g_elCallDepth; g_elCallDepth;
block_deletesprite; block_deletesprite;
g_RETURN; g_RETURN;

View file

@ -66,6 +66,11 @@ clipmovex;
rotatesprite_; rotatesprite_;
setaspect; setaspect;
getclosestcol;
palookup;
palette;
setpalookup;
kopen4load; kopen4load;
kfilelength; kfilelength;
kclose; kclose;
@ -87,6 +92,7 @@ crc32once;
luaJIT_BC_defs_common; luaJIT_BC_defs_common;
luaJIT_BC_engine_maptext; luaJIT_BC_engine_maptext;
luaJIT_BC_engine;
g_argv; g_argv;

View file

@ -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

View file

@ -37,6 +37,9 @@ int32_t g_elSessionVar[8]; // MAXSESSIONVARS, KEEPINSYNC con_lang.lua
// Set to 1 on error in event. // Set to 1 on error in event.
int32_t g_elEventError; 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_elCallDepth = 0;
int32_t g_RETURN; int32_t g_RETURN;

View file

@ -170,6 +170,9 @@ gameevent
-- sprite.picnum may happen as a thinko/typo kind of error (spr.picnum was meant) -- 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("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 end
} }
@ -327,8 +330,6 @@ gameevent
-- NOTE: setting colors partially is bad! E.g. after an item is -- NOTE: setting colors partially is bad! E.g. after an item is
-- picked up, col[0] and col[1] remain and tint everything greenish. -- picked up, col[0] and col[1] remain and tint everything greenish.
if (DBG_ ~= nil) then 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[2] = 20
ps._pals.f = 30 ps._pals.f = 30
end end
@ -888,3 +889,7 @@ do
printf('Time for %d runs: getangle: %.03f ms, math.atan2: %.03f ms', N, t1, t2) printf('Time for %d runs: getangle: %.03f ms, math.atan2: %.03f ms', N, t1, t2)
print('----------') print('----------')
end end
if (LUNATIC_FIRST_TIME) then
require("test.shadexfog").test_create0()
end

View file

@ -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 <tartpalnum> .. <startpalnum>+31 will be taken.
-- <fogr>, <fogg>, <fogb>: 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 <startpalnum> + former .shade
-- .shade becomes the <fogintensity> [0 .. 31]
-- If <vis> 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
-- <pal>.
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 <palnum>.
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