diff --git a/polymer/eduke32/source/lunatic/con_lang.lua b/polymer/eduke32/source/lunatic/con_lang.lua index 7ff256a10..0afd94ad6 100644 --- a/polymer/eduke32/source/lunatic/con_lang.lua +++ b/polymer/eduke32/source/lunatic/con_lang.lua @@ -220,6 +220,8 @@ SFLAG = { SFLAG_CACHE = -0x00010000, SFLAG_ROTFIXED = -0x00020000, SFLAG_HARDCODED_BADGUY = -0x00040000, + -- RESERVED for actor.FLAGS.CHAIN_BEG/CHAIN_END/REPLACE: + -- 0x20000000, 0x40000000 } STAT = { diff --git a/polymer/eduke32/source/lunatic/defs.ilua b/polymer/eduke32/source/lunatic/defs.ilua index 7440bfa79..2211f4cda 100644 --- a/polymer/eduke32/source/lunatic/defs.ilua +++ b/polymer/eduke32/source/lunatic/defs.ilua @@ -710,6 +710,11 @@ do our_SFLAG.enemystayput = con_lang.SFLAG.SFLAG_BADGUY + con_lang.SFLAG.SFLAG_BADGUYSTAYPUT our_SFLAG.rotfixed = con_lang.SFLAG.SFLAG_ROTFIXED + -- Callback function chaining control flags. + our_SFLAG.chain_beg = bit.tobit(0x20000000) + our_SFLAG.chain_end = bit.tobit(0x40000000) + our_SFLAG.replace = bit.bor(our_SFLAG.chain_beg, our_SFLAG.chain_end) + -- XXX: CON doesn't export BADGUYSTAYPUT or ROTFIXED SFLAGs, but they are considered -- external for Lunatic. our_SFLAG.USER_MASK = bit.bor(USER_MASK, our_SFLAG.enemystayput, our_SFLAG.rotfixed) @@ -729,6 +734,7 @@ player = setmtonce({}, defs_c.GenStructMetatable("g_player_ps", "playerswhenstar -- needed by "control" actor = setmtonce({}, defs_c.GenStructMetatable("actor", "MAXSPRITES", actor_static_members)) local BNOT_SFLAG_USER_MASK = bit.bnot(actor.FLAGS.USER_MASK) +local BNOT_CHAIN_MASK = bit.bnot(actor.FLAGS.replace) local projectile = defs_c.creategtab(ffiC.ProjectileData, ffiC.MAXTILES, "projectile[]") local g_tile = setmtonce({}, defs_c.GenStructMetatable("g_tile", "MAXTILES", tile_static_members)) @@ -1423,6 +1429,22 @@ G_._VERSION = _VERSION G_._G = G_ +-- Chain together two functions taking 3 input args. +local function chain_func3(func1, func2) + if (func1==nil or func2==nil) then + return assert(func1 or func2) + end + + -- Return a function that runs first and then tail-calls . + return function(aci, pli, dist) + func1(aci, pli, dist) + return func2(aci, pli, dist) + end +end + +-- Actor functions, saved for actor chaining +local actor_funcs = {} + local gameactor_internal = gameactor_internal -- included in lunatic.c -- gameactor(tilenum [, flags [, strength [, act [, mov [, movflags]]]]], actor_func) local function our_gameactor(tilenum, ...) @@ -1454,54 +1476,88 @@ local function our_gameactor(tilenum, ...) error("invalid 'flags' argument to gameactor: must be a number", 2) end + local AF = actor.FLAGS + local chainflags = bit.band(flags, AF.replace) + flags = bit.band(flags, BNOT_CHAIN_MASK) + + -- Default chaining behavior: don't, replace the old actor instead (like CON). + if (chainflags == 0) then + chainflags = AF.replace + end + + local replacep = (chainflags==AF.replace) + if (not replacep and not actor_funcs[tilenum]) then + error("attempt to chain code to nonexistent actor tile "..tilenum, 2) + end + local flags_rbits = bit.band(flags, BNOT_SFLAG_USER_MASK) if (flags_rbits ~= 0) then error("invalid 'flags' argument to gameactor: must not set reserved bits (0x" ..(bit.tohex(flags_rbits))..")", 2) end - local strength = (numargs > 2) and args[2] or 0 - if (type(strength) ~= "number") then - error("invalid 'strength' argument to gameactor: must be a number", 2) + local strength = (numargs > 2) and args[2] or (replacep and 0 or nil) + if (replacep or strength~=nil) then + if (type(strength) ~= "number") then + error("invalid 'strength' argument to gameactor: must be a number", 2) + end end -- TODO: literal number action other than 0? - local act = (numargs > 3) and args[3] or "NO" - if (type(act)=="string") then - act = AC[act] - end - if (not ffi.istype(con_action_ct, act)) then - error("invalid 'act' argument to gameactor: must be a string or action", 2) + local act = (numargs > 3) and args[3] or (replacep and "NO" or nil) + if (replacep or act ~= nil) then + if (type(act)=="string") then + act = AC[act] + end + if (not ffi.istype(con_action_ct, act)) then + error("invalid 'act' argument to gameactor: must be a string or action", 2) + end end -- TODO: literal number move other than 0? - local mov = (numargs > 4) and args[4] or "NO" - if (type(mov)=="string") then - mov = MV[mov] - end - if (not ffi.istype(con_move_ct, mov)) then - error("invalid 'mov' argument to gameactor: must be a string or move", 2) + local mov = (numargs > 4) and args[4] or (replacep and "NO" or nil) + if (replacep or mov ~= nil) then + if (type(mov)=="string") then + mov = MV[mov] + end + if (not ffi.istype(con_move_ct, mov)) then + error("invalid 'mov' argument to gameactor: must be a string or move", 2) + end end - local movflags = (numargs > 5) and args[5] or 0 - if (type(movflags) ~= "number") then - error("invalid 'movflags' argument to gameactor: must be a number", 2) + local movflags = (numargs > 5) and args[5] or (replacep and 0 or nil) + if (replacep or movflags ~= nil) then + if (type(movflags) ~= "number") then + error("invalid 'movflags' argument to gameactor: must be a number", 2) + end end -- All good, set the tile bits and register the actor! local tile = ffiC.g_tile[tilenum] - tile.flags = bit.bor(tile.flags, flags) + local func = args[numargs] - gameactor_internal(tilenum, strength, act, mov, movflags, args[numargs]) + -- NOTE: when chaining, this allows passing different flags which are + -- silently ORed. This may or may not be what the user intends, but it + -- allows e.g. adding a stayput bit to an already defined enemy. + -- Modifying existing behavior is the whole point of chaining after all. + tile.flags = replacep and flags or bit.bor(tile.flags, flags) + + local newfunc = replacep and func + or (chainflags==AF.chain_beg) and chain_func3(func, actor_funcs[tilenum]) + or (chainflags==AF.chain_end) and chain_func3(actor_funcs[tilenum], func) + or assert(false) + + gameactor_internal(tilenum, strength, act, mov, movflags, newfunc) + actor_funcs[tilenum] = newfunc end -- Event functions, saved for event chaining local event_funcs = {} local gameevent_internal = gameevent_internal -- included in lunatic.c --- gameevent(, ) -local function our_gameevent(event, func) +-- gameevent( [, flags], ) +local function our_gameevent(event, flags, func) if (ffiC.g_elCallDepth > 0) then error("Invalid use of gameevent: must be called from top level", 2) end @@ -1516,26 +1572,39 @@ local function our_gameevent(event, func) event = eventidx end if (type(event) ~= "number") then - error("invalid argument #1 to gameevent: must be a number", 2) + error("invalid argument #1 to gameevent: must be a number or event label", 2) end if (event >= con_lang.MAXEVENTS+0ULL) then error("invalid argument #1 to gameevent: must be an event number (0 .. MAXEVENTS-1)", 2) end - if (type(func) ~= "function") then - error("invalid argument #2 to gameevent: must be a function", 2) - end + local AF = actor.FLAGS - local newfunc = func - local oldfunc = event_funcs[event] - -- event chaining: events defined later come first - if (oldfunc ~= nil) then - newfunc = function(aci, pli, dist) - func(aci, pli, dist) - return oldfunc(aci, pli, dist) + -- Event chaining: in Lunatic, chaining at the *end* is the default. + if (func==nil) then + func = flags + flags = AF.chain_end + else + if (type(flags) ~= "number") then + error("invalid 'flags' argument to gameevent: must be a number", 2) + end + if (bit.band(flags, BNOT_CHAIN_MASK) ~= 0) then + error("invalid 'flags' argument to gameevent: must not set reserved bits", 2) + end + if (flags == 0) then + flags = AF.chain_end end end + if (type(func) ~= "function") then + error("invalid last argument to gameevent: must be a function", 2) + end + + local newfunc = (flags==AF.replace) and func + or (flags==AF.chain_beg) and chain_func3(func, event_funcs[event]) + or (flags==AF.chain_end) and chain_func3(event_funcs[event], func) + or assert(false) + gameevent_internal(event, newfunc) event_funcs[event] = newfunc end diff --git a/polymer/eduke32/source/lunatic/lunacon.lua b/polymer/eduke32/source/lunatic/lunacon.lua index e37d296c8..37f72440d 100644 --- a/polymer/eduke32/source/lunatic/lunacon.lua +++ b/polymer/eduke32/source/lunatic/lunacon.lua @@ -102,7 +102,7 @@ local g_warn = { ["not-redefined"]=true, ["bad-identifier"]=false, -- Code generation and output options. local g_cgopt = { ["no"]=false, ["debug-lineinfo"]=false, ["gendir"]=nil, - ["cache-sap"]=false, } + ["cache-sap"]=false, ["no-error-nostate"]=false, } local function csapp() return g_cgopt["cache-sap"] end -- How many 'if' statements are following immediately each other, @@ -1772,8 +1772,11 @@ local handle = state = function(statename) if (g_funcname[statename]==nil) then - errprintf("state `%s' not found.", statename) - return "_NULLSTATE()" + local warn = g_cgopt["no-error-nostate"] + local xprintf = warn and warnprintf or errprintf + + xprintf("state `%s' not found.", statename) + return warn and "" or "_NULLSTATE()" end return format("%s(_aci,_pli,_dist)", g_funcname[statename]) end, @@ -3192,7 +3195,7 @@ local function handle_cmdline_arg(str) g_warn[warnstr] = val ok = true end - elseif (str:sub(2,4)=="fno") then + elseif (str:sub(2)=="fno") then -- Disable printing code. if (#str >= 5 and str:sub(5)=="=onlycheck") then g_cgopt["no"] = "onlycheck" @@ -3209,6 +3212,9 @@ local function handle_cmdline_arg(str) elseif (str:sub(2)=="fcache-sap") then g_cgopt["cache-sap"] = true ok = true + elseif (str:sub(2)=="fno-error-nostate") then + g_cgopt["no-error-nostate"] = true + ok = true elseif (str:sub(2)=="fdebug-lineinfo") then g_cgopt["debug-lineinfo"] = true ok = true diff --git a/polymer/eduke32/source/lunatic/lunatic_game.c b/polymer/eduke32/source/lunatic/lunatic_game.c index afe52a149..98bfccd8a 100644 --- a/polymer/eduke32/source/lunatic/lunatic_game.c +++ b/polymer/eduke32/source/lunatic/lunatic_game.c @@ -414,10 +414,7 @@ static int32_t SetEvent_CF(lua_State *L) // gameactor(actortile, strength, act, mov, movflags, lua_function) static int32_t SetActor_CF(lua_State *L) { - int32_t actortile, strength, movflags; - const con_action_t *act; - const con_move_t *mov; - + int32_t actortile; el_actor_t *a; Bassert(lua_gettop(L) == 6); @@ -425,22 +422,23 @@ static int32_t SetActor_CF(lua_State *L) actortile = luaL_checkint(L, 1); Bassert((unsigned)actortile < MAXTILES); - strength = luaL_checkint(L, 2); - movflags = luaL_checkint(L, 5); - - act = lua_topointer(L, 3); - mov = lua_topointer(L, 4); - a = &g_elActors[actortile]; L_CheckAndRegisterFunction(L, a); + + // Set actor properties. They can only be nil if we're chaining actor code. + + if (!lua_isnil(L, 2)) + a->strength = luaL_checkint(L, 2); + if (!lua_isnil(L, 5)) + a->movflags = luaL_checkint(L, 5); + + if (!lua_isnil(L, 3)) + Bmemcpy(&a->act, lua_topointer(L, 3), sizeof(con_action_t)); + if (!lua_isnil(L, 4)) + Bmemcpy(&a->mov, lua_topointer(L, 4), sizeof(con_move_t)); + a->haveit = 1; - a->strength = strength; - a->movflags = movflags; - - Bmemcpy(&a->act, act, sizeof(con_action_t)); - Bmemcpy(&a->mov, mov, sizeof(con_move_t)); - return 0; } diff --git a/polymer/eduke32/source/lunatic/test.elua b/polymer/eduke32/source/lunatic/test.elua index 40f1b1210..dc35f821f 100644 --- a/polymer/eduke32/source/lunatic/test.elua +++ b/polymer/eduke32/source/lunatic/test.elua @@ -365,7 +365,8 @@ local TROOPSTRENGTH = 30 local AF = actor.FLAGS -gameactor(1680, AF.enemy, TROOPSTRENGTH, "TROOPSTAND", -- LIZTROOP +-- Also test actor code chaining: strength is doubled. +gameactor(1680, AF.chain_end+AF.enemy, 2*TROOPSTRENGTH, "TROOPSTAND", -- LIZTROOP function(i, playeri, dist) spriteext[i]:make_animated()