From d580c1e998f0f26a0d37b7475d49780a6882d10d Mon Sep 17 00:00:00 2001 From: helixhorned Date: Thu, 4 Jul 2013 19:38:42 +0000 Subject: [PATCH] Lunatic: many changes, motivated by writing test "helixspawner". - some renames of functions/methods - con.actorvar: explicitly allow (for now) boolean and number values - added gv.gametic, player.holdskey(), player.all(), actor.check*()/hit*(), gv.rendmode, gv.REND, :mask(), spr:getheightofs() - make read of nil var in "finalized" (live) module produce error, too - add test/helixspawner.lua git-svn-id: https://svn.eduke32.com/eduke32@3928 1a8010ca-5511-0410-912e-c29ae57300e0 --- polymer/eduke32/source/lunatic/con_lang.lua | 2 +- polymer/eduke32/source/lunatic/control.lua | 80 +++++----- polymer/eduke32/source/lunatic/defs.ilua | 141 ++++++++++++++---- .../eduke32/source/lunatic/defs_common.lua | 17 ++- polymer/eduke32/source/lunatic/dynsymlist | 2 + polymer/eduke32/source/lunatic/dynsymlist_m32 | 1 + polymer/eduke32/source/lunatic/lunacon.lua | 4 +- polymer/eduke32/source/lunatic/savegame.lua | 6 +- .../source/lunatic/test/helixspawner.lua | 130 ++++++++++++++++ 9 files changed, 311 insertions(+), 72 deletions(-) create mode 100644 polymer/eduke32/source/lunatic/test/helixspawner.lua diff --git a/polymer/eduke32/source/lunatic/con_lang.lua b/polymer/eduke32/source/lunatic/con_lang.lua index 5f414cd78..a566e9c04 100644 --- a/polymer/eduke32/source/lunatic/con_lang.lua +++ b/polymer/eduke32/source/lunatic/con_lang.lua @@ -474,7 +474,7 @@ local PlayerLabels = { -- NOTE the special case; "%%s" is used to mark settable members -- with METHOD_MEMBER syntax, it's the value to be set. - gotweapon = { "("..PL":have_weapon(%s) and 1 or 0)", PL":_gt_weapon(%s,%%s)" }, + gotweapon = { "("..PL":has_weapon(%s) and 1 or 0)", PL":_gt_weapon(%s,%%s)" }, zoom = PL".zoom", loogiex = {}, diff --git a/polymer/eduke32/source/lunatic/control.lua b/polymer/eduke32/source/lunatic/control.lua index 946833059..1f00db61f 100644 --- a/polymer/eduke32/source/lunatic/control.lua +++ b/polymer/eduke32/source/lunatic/control.lua @@ -537,7 +537,7 @@ end local function P_AddWeaponAmmoCommon(ps, weap, amount) P_AddAmmo(ps, weap, amount) - if (ps.curr_weapon==ffiC.KNEE_WEAPON and ps:have_weapon(weap)) then + if (ps.curr_weapon==ffiC.KNEE_WEAPON and ps:has_weapon(weap)) then CF.P_AddWeaponMaybeSwitchI(ps.weapon._p, weap); end end @@ -1167,11 +1167,9 @@ function _addammo(ps, weap, amount) end function _addweapon(ps, weap, amount) - if (weap >= ffiC.MAX_WEAPONS+0ULL) then - error("Invalid weapon ID "..weap, 2) - end + bcheck.weapon_idx(weap) - if (not ps:have_weapon(weap)) then + if (not ps:has_weapon(weap)) then CF.P_AddWeaponMaybeSwitchI(ps.weapon._p, weap); elseif (have_ammo_at_max(ps, weap)) then return true @@ -1186,32 +1184,23 @@ function _A_RadiusDamage(i, r, hp1, hp2, hp3, hp4) CF.A_RadiusDamage(i, r, hp1, hp2, hp3, hp4) end -function _testkey(pli, synckey) - check_player_idx(pli) - if (synckey >= 32ULL) then - error("Invalid argument #2 to _testkey: must be in [0..31]", 2) - end - local bits = ffiC.g_player[pli].sync.bits - return (bit.band(bits, bit.lshift(1,synckey)) ~= 0) -end +local NEAROP = { + [9] = true, + [15] = true, + [16] = true, + [17] = true, + [18] = true, + [19] = true, + [20] = true, + [21] = true, + [22] = true, + [23] = true, + [25] = true, + [26] = true, + [29] = true, +} function _operate(spritenum) - local NEAROP = { - [9] = true, - [15] = true, - [16] = true, - [17] = true, - [18] = true, - [19] = true, - [20] = true, - [21] = true, - [22] = true, - [23] = true, - [25] = true, - [26] = true, - [29] = true, - } - local spr = sprite[spritenum] if (sector[spr.sectnum].lotag == 0) then @@ -1527,10 +1516,7 @@ function _rotatepoint(pivotx, pivoty, posx, posy, ang) return pos.x, pos.y end -local SK = { - CROUCH = 1, - RUN = 5, -} +local holdskey = player.holdskey function _ifp(flags, pli, aci) local l = flags @@ -1538,7 +1524,7 @@ function _ifp(flags, pli, aci) local vel = sprite[ps.i].xvel local band = bit.band - if (band(l,8)~=0 and ps.on_ground and _testkey(pli, SK.CROUCH)) then + if (band(l,8)~=0 and ps.on_ground and holdskey(pli, "CROUCH")) then return true elseif (band(l,16)~=0 and ps.jumping_counter == 0 and not ps.on_ground and ps.vel.z > 2048) then return true @@ -1546,15 +1532,15 @@ function _ifp(flags, pli, aci) return true elseif (band(l,1)~=0 and vel >= 0 and vel < 8) then return true - elseif (band(l,2)~=0 and vel >= 8 and not _testkey(pli, SK.RUN)) then + elseif (band(l,2)~=0 and vel >= 8 and not holdskey(pli, "RUN")) then return true - elseif (band(l,4)~=0 and vel >= 8 and _testkey(pli, SK.RUN)) then + elseif (band(l,4)~=0 and vel >= 8 and holdskey(pli, "RUN")) then return true elseif (band(l,64)~=0 and ps.pos.z < (sprite[aci].z-(48*256))) then return true - elseif (band(l,128)~=0 and vel <= -8 and not _testkey(pli, SK.RUN)) then + elseif (band(l,128)~=0 and vel <= -8 and not holdskey(pli, "RUN")) then return true - elseif (band(l,256)~=0 and vel <= -8 and _testkey(pli, SK.RUN)) then + elseif (band(l,256)~=0 and vel <= -8 and holdskey(pli, "RUN")) then return true elseif (band(l,512)~=0 and (ps.quick_kick > 0 or (ps.curr_weapon == ffiC.KNEE_WEAPON and ps.kickback_pic > 0))) then return true @@ -2123,6 +2109,16 @@ end --== Per-actor variable ==-- +local perxvar_allowed_types = { + ["boolean"]=true, ["number"]=true, +} + +local function check_perxval_type(val) + if (perxvar_allowed_types[type(val)] == nil) then + error("type forbidden as per-* variable value: "..type(val), 3) + end +end + local actorvar_methods = { --- Internal routines --- @@ -2153,7 +2149,6 @@ local actorvar_methods = { end, } --- XXX: How about types other than numbers? local actorvar_mt = { __index = function(acv, idx) if (type(idx)=="number") then @@ -2166,7 +2161,7 @@ local actorvar_mt = { __newindex = function(acv, idx, val) check_sprite_idx(idx) - check_number(val) + check_perxval_type(val) rawset(acv, idx, val) end, @@ -2176,6 +2171,7 @@ local actorvar_mt = { -- : default value for per-actor variable. -- : optional, a table of =value function actorvar(initval, values) + check_perxval_type(initval) local acv = setmetatable({ _defval=initval }, actorvar_mt) g_actorvar[acv] = true return set_values_from_table(acv, values) @@ -2193,7 +2189,6 @@ local playervar_methods = { end, } --- XXX: How about types other than numbers? local playervar_mt = { __index = function(plv, idx) if (type(idx)=="number") then @@ -2206,7 +2201,7 @@ local playervar_mt = { __newindex = function(plv, idx, val) check_player_idx(idx) - check_number(val) + check_perxval_type(val) rawset(plv, idx, val) end, @@ -2216,6 +2211,7 @@ local playervar_mt = { -- : default value for per-player variable. -- : optional, a table of =value function playervar(initval, values) + check_perxval_type(initval) local plv = setmetatable({ _defval=initval }, playervar_mt) return set_values_from_table(plv, values) end diff --git a/polymer/eduke32/source/lunatic/defs.ilua b/polymer/eduke32/source/lunatic/defs.ilua index 1f9865cd0..f57c19ed6 100644 --- a/polymer/eduke32/source/lunatic/defs.ilua +++ b/polymer/eduke32/source/lunatic/defs.ilua @@ -119,6 +119,7 @@ enum dukeweapon_t { enum { MAXPLAYERS = 16, + GTICSPERSEC = 30, // The real number of movement updates per second }; ]]) @@ -145,7 +146,9 @@ typedef struct { } con_ai_t; ]] +defs_c.bitint_new_struct_type("int16_t", "SBit16") defs_c.bitint_new_struct_type("int32_t", "SBit32") +defs_c.bitint_new_struct_type("uint32_t", "UBit32") -- Struct template for actor_t. It already has 'const' fields (TODO: might need -- to make more 'const'), but still has array members exposed, so is unsuited @@ -165,7 +168,9 @@ __attribute__((packed)) struct { const int16_t picnum; int16_t ang, extra; const int16_t owner; - int16_t _movflag,tempang,timetosleep; //6b + // NOTE: not to be confused with .movflags: +]]..defs_c.bitint_member("SBit16", "_movflag")..[[ + int16_t tempang, timetosleep; int16_t stayputsect; const int16_t dispicnum; @@ -402,7 +407,7 @@ ffi.cdef([[ typedef struct { int32_t _p; } weaponaccess_t; typedef struct { - uint32_t bits; // 4b +]]..defs_c.bitint_member("UBit32", "bits")..[[ int16_t fvel, svel; // 4b int8_t avel, horz; // 2b int8_t extbits, filler; // 2b @@ -632,6 +637,7 @@ int32_t g_scriptVersion; const int32_t g_currentFrameRate; const int32_t g_currentMenu; uint16_t g_earthquakeTime; +uint32_t g_moveThingsCount; char CheatKeys[2]; // Must not have functions here that may call events directly or @@ -788,6 +794,40 @@ player_static_members._INPUT_EXT_BITS = defs_c.conststruct TURN_RIGHT = 32, } +local band = bit.band +local lsh = bit.lshift + +do + -- player.all() iterator + local function iter_allplayers(_nil, pli) + if (pli+1 < ffiC.playerswhenstarted) then + return pli+1 + end + end + + function player_static_members.all() + return iter_allplayers, nil, -1 + end + + -- player.holdskey(pli, keyname) + local KEYS = { -- SK_CROUCH etc. -- "sync keys" + CROUCH = lsh(1,1), + RUN = lsh(1,5), + OPEN = lsh(1,29), + } + + function player_static_members.holdskey(pli, keyname) + bcheck.player_idx(pli) + if (KEYS[keyname] == nil) then + error("invalid key name: "..tostring(keyname), 2) + end + + return ffiC.g_player[pli].sync.bitsbits:test(KEYS[keyname]) + end +end + +local player_holdskey = player_static_members.holdskey + -- Actor flags local actor_static_members = {} do @@ -1100,6 +1140,36 @@ local actor_mt = { check_sprite_idx(owner) ffi.cast(actor_ptr_ct, a).owner = owner end, + + --- Custom methods --- + + -- Checkers for whether the movement update made the actor hit + -- something. + + checkhit = function(a) + -- Check whether we hit *anything*, including ceiling/floor. + return a._movflagbits:test(49152) + end, + + checkbump = function(a) + -- Check whether we bumped into a wall or sprite. + return (a._movflagbits:mask(49152) >= 32768) + end, + + hitwall = function(a) + if (a._movflagbits:mask(49152) == 32768) then + return a._movflagbits:mask(32767) + end + end, + + hitsprite = function(a) + if (a._movflagbits:mask(49152) == 49152) then + return a._movflagbits:mask(32767) + end + end, + + -- NOTE: no 'hitsector' or 'hitceiling' / 'hitfloor' for now because + -- more research is needed as to what the best way of checking c/f is. }, } @@ -1200,16 +1270,21 @@ local player_mt = { stomp = con._pstomp, - have_weapon = function(p, weap) - return (bit.band(p.gotweapon, bit.lshift(1, weap)) ~= 0) + holdskey = function(p, keyname) + -- XXX: on invalid , error will point to this next line: + return player_holdskey(p.weapon._p, keyname) + end, + + has_weapon = function(p, weap) + return (band(p.gotweapon, lsh(1, weap)) ~= 0) end, give_weapon = function(p, weap) - p.gotweapon = bit.bor(p.gotweapon, bit.lshift(1, weap)) + p.gotweapon = bit.bor(p.gotweapon, lsh(1, weap)) end, take_weapon = function(p, weap) - p.gotweapon = bit.band(p.gotweapon, bit.bnot(bit.lshift(1, weap))) + p.gotweapon = band(p.gotweapon, bit.bnot(lsh(1, weap))) end, -- Give or take weapon, for implementation of CON's .gotweapon access. @@ -1226,7 +1301,7 @@ local player_mt = { if (not no_return_to_center) then p.return_to_center = 9 end - local n = bit.arshift(128-bit.band(ffiC.krand(),255), 1) + local n = bit.arshift(128-band(ffiC.krand(),255), 1) p.rotscrnang = n p.look_ang = n end, @@ -1388,6 +1463,13 @@ gv_access._ud = ffiC.ud -- Support for some CON global system gamevars. RETURN handled separately. gv_access._csv = ffi.new "struct { int32_t LOTAG, HITAG, TEXTURE; }" +gv_access.REND = defs_c.conststruct +{ + CLASSIC = 0, + POLYMOST = 3, + POLYMER = 4, +} + function gv_access._get_yxaspect() return ffiC.yxaspect end @@ -1425,11 +1507,6 @@ function gv_access.currentLevel() return ffiC.ud.level_number end -function gv_access.currentRenderMode() - -- TODO: USE_OPENGL=0 build - return ffiC.rendmode -end - function gv_access.doQuake(gametics, snd) ffiC.g_earthquakeTime = gametics if (snd ~= nil) then @@ -1556,11 +1633,17 @@ local function getnumlocals(l) end end +local function error_on_nil_read(_, varname) + error("attempt to read nil variable '"..varname.."'", 2) +end + local required_module_mt = { + __index = error_on_nil_read, + __newindex = function() - -- TODO: allow gamevars to be nil? error("modifying module table forbidden", 2) end, + __metatable = true, } @@ -1737,9 +1820,7 @@ end local module_mt = { - __index = function (_, n) - error("attempt to read undeclared variable '"..n.."'", 2) - end, + __index = error_on_nil_read, } -- Our 'module' replacement doesn't get the module name from the function args @@ -1872,7 +1953,7 @@ local function our_gameactor(args) lastargi = 1/0 end if (type(func) ~= "function") then - error("invalid gameactor call: must provide a function with last numeric arg or .func".." "..type(func), 2) + error("invalid gameactor call: must provide a function with last numeric arg or .func", 2) end local flags = (lastargi > 2 and args[2]) or args.flags or 0 @@ -1881,14 +1962,14 @@ local function our_gameactor(args) end local AF = actor.FLAGS - local chainflags = bit.band(flags, AF._CHAIN_MASK_ACTOR) - flags = bit.band(flags, BNOT.CHAIN_MASK_ACTOR) + local chainflags = band(flags, AF._CHAIN_MASK_ACTOR) + flags = band(flags, BNOT.CHAIN_MASK_ACTOR) -- Default chaining behavior: don't, replace the old actor instead, but -- unlike CON, also replace its flags. (CON ORs them instead.) if (chainflags == 0) then chainflags = AF.replace_hard - elseif (bit.band(chainflags, chainflags-1) ~= 0) then + elseif (band(chainflags, chainflags-1) ~= 0) then error("invalid chaining control flags to gameactor", 2) end @@ -1897,7 +1978,7 @@ local function our_gameactor(args) error("attempt to chain code to nonexistent actor tile "..tilenum, 2) end - local flags_rbits = bit.band(flags, BNOT.USER_MASK) + local flags_rbits = band(flags, BNOT.USER_MASK) if (flags_rbits ~= 0) then error("invalid 'flags' argument (#2) to gameactor: must not set reserved bits (0x" ..(bit.tohex(flags_rbits))..")", 2) @@ -2007,7 +2088,7 @@ local function our_gameevent(args) error("invalid 'flags' argument (#2) to gameevent: must be a number", 2) end - if (bit.band(flags, BNOT.CHAIN_MASK_EVENT) ~= 0) then + if (band(flags, BNOT.CHAIN_MASK_EVENT) ~= 0) then error("invalid 'flags' argument to gameevent: must not set reserved bits", 2) end @@ -2194,14 +2275,24 @@ end -- error(..., 2) is to blame the caller and get its line numbers +-- Map of 'gv' variable names to C ones. +local varnameMap = { + gametic = "g_moveThingsCount", +} + +gv_access.gametic = true + local tmpmt = { __index = function(_, key) if (gv_access[key] == nil) then + -- Read access allowed. return ffiC[key] - end - if (type(gv_access[key])~="boolean") then - -- overridden... + elseif (type(gv_access[key])~="boolean") then + -- Overridden 'gv' pseudo-member... return gv_access[key] + elseif (varnameMap[key]) then + -- Variable known under a different name on the C side. + return ffiC[varnameMap[key]] end error("access forbidden", 2) end, diff --git a/polymer/eduke32/source/lunatic/defs_common.lua b/polymer/eduke32/source/lunatic/defs_common.lua index 3ebfc9318..d5787f05e 100644 --- a/polymer/eduke32/source/lunatic/defs_common.lua +++ b/polymer/eduke32/source/lunatic/defs_common.lua @@ -90,6 +90,10 @@ local bitint_mt = { test = function(self, bits) return (band(self._v, bits) ~= 0) end, + + mask = function(self, bits) + return band(self._v, bits) + end, }, __metatable = true, @@ -379,13 +383,13 @@ if (_LUNATIC_AUX) then return end +ffi.cdef "const int32_t rendmode;" decl[[ int32_t yxaspect; int32_t viewingrange; int32_t spritesortcnt; int32_t guniqhudid; -const int32_t rendmode; const int16_t headspritesect[MAXSECTORS+1], headspritestat[MAXSTATUS+1]; const int16_t prevspritesect[MAXSPRITES], prevspritestat[MAXSPRITES]; const int16_t nextspritesect[MAXSPRITES], nextspritestat[MAXSPRITES]; @@ -401,6 +405,7 @@ int32_t getceilzofslopeptr(const sectortype *sec, int32_t dax, int32_t day); int32_t getflorzofslopeptr(const sectortype *sec, int32_t dax, int32_t day); void getzsofslopeptr(const sectortype *sec, int32_t dax, int32_t day, int32_t *ceilz, int32_t *florz); +int32_t spriteheightofsptr(const spritetype *spr, int32_t *height, int32_t alsotileyofs); int32_t changespritesect(int16_t spritenum, int16_t newsectnum); int32_t changespritestat(int16_t spritenum, int16_t newstatnum); @@ -603,6 +608,8 @@ local spritetype_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(SPRITE_STRUCT -- NOTE: this is the *protected* tspritetype pointer. local tspritetype_ptr_ct = ffi.typeof("$ *", ffi.typeof("tspritetype")) +local intarg = ffi.new("int32_t[1]") + local spritetype_mt = { __pow = function(s, zofs) return vec3_ct(s.x, s.y, s.z-zofs) @@ -630,6 +637,14 @@ local spritetype_mt = { check_sprite_idx(owner) ffi.cast(spritetype_ptr_ct, s).owner = owner end, + + --- Custom methods --- + + getheightofs = function(s) + -- XXX: better reimplement in Lua? + local zofs = ffiC.spriteheightofsptr(s, intarg, 0) + return intarg[0], zofs + end, }, } diff --git a/polymer/eduke32/source/lunatic/dynsymlist b/polymer/eduke32/source/lunatic/dynsymlist index 46d8ba56e..cc42a684c 100644 --- a/polymer/eduke32/source/lunatic/dynsymlist +++ b/polymer/eduke32/source/lunatic/dynsymlist @@ -33,6 +33,7 @@ sectorofwall_noquick; getceilzofslopeptr; getflorzofslopeptr; getzsofslopeptr; +spriteheightofsptr; changespritesect; changespritestat; @@ -160,6 +161,7 @@ g_scriptVersion; g_currentFrameRate; g_currentMenu; g_earthquakeTime; +g_moveThingsCount; CheatKeys; g_logoFlags; diff --git a/polymer/eduke32/source/lunatic/dynsymlist_m32 b/polymer/eduke32/source/lunatic/dynsymlist_m32 index dcbb86205..72363ed8e 100644 --- a/polymer/eduke32/source/lunatic/dynsymlist_m32 +++ b/polymer/eduke32/source/lunatic/dynsymlist_m32 @@ -33,6 +33,7 @@ sectorofwall_noquick; getceilzofslopeptr; getflorzofslopeptr; getzsofslopeptr; +spriteheightofsptr; changespritesect; changespritestat; diff --git a/polymer/eduke32/source/lunatic/lunacon.lua b/polymer/eduke32/source/lunatic/lunacon.lua index d745d8ca0..ed31bf2a0 100644 --- a/polymer/eduke32/source/lunatic/lunacon.lua +++ b/polymer/eduke32/source/lunatic/lunacon.lua @@ -265,7 +265,7 @@ local function new_initial_gvartab() totalclock = RO "_gv.totalclock", framerate = RO "_gv._currentFramerate()", current_menu = RO "_gv._currentMenu()", - rendmode = RO "_gv.currentRenderMode()", + rendmode = RO "_gv.rendmode", screenpeek = RO "_gv.screenpeek", @@ -2689,7 +2689,7 @@ local Cif = { ifhitweapon = cmd() / "_con._A_IncurDamage(_aci)>=0", ifhitspace = cmd() - / "_con._testkey(_pli,29)", -- XXX + / "player.holdskey(_pli,'OPEN')", ifdead = cmd() / SPS".extra<=0", ifclient = cmd() diff --git a/polymer/eduke32/source/lunatic/savegame.lua b/polymer/eduke32/source/lunatic/savegame.lua index c98d15352..b1ce23583 100644 --- a/polymer/eduke32/source/lunatic/savegame.lua +++ b/polymer/eduke32/source/lunatic/savegame.lua @@ -111,7 +111,11 @@ local savebuffer_mt = { -- We have a serializeable object from Lunatic -- (e.g. actorvar). self:getRequire(value) - self:emitT(refcode, value:_serialize(), value) + local restoreCode = value:_serialize() + if (restoreCode == nil) then -- XXX: check UNUSED? + return true + end + self:emitT(refcode, restoreCode, value) valcode = refcode elseif (type(value)=="table") then diff --git a/polymer/eduke32/source/lunatic/test/helixspawner.lua b/polymer/eduke32/source/lunatic/test/helixspawner.lua new file mode 100644 index 000000000..6e16ebc5b --- /dev/null +++ b/polymer/eduke32/source/lunatic/test/helixspawner.lua @@ -0,0 +1,130 @@ + +-- A spawner (NUKEBUTTON+3) of colored TRANSPORTERSTAR+4 sprites in a helical +-- arrangement. + +local require = require +local math = require("math") + +local con = require("con") +local xmath = require("xmath") + +local gv = gv +local actor = actor +local player = player +local sprite = sprite + +local gameactor = gameactor + + +module(...) + +-- Dual-typed per-actor array: if a broken nuke switch is not enabled, +-- start game tic of when it was enabled otherwise. +-- NOTE: for objects that are not supposed to be deleted such as this one, it +-- would also be OK to use a plain table. +local nukeswStart = con.actorvar(false) + +-- This one should be a per-actor variable because it holds info about +-- "volatile" actors. +local starPal = con.actorvar(0) + +require("end_gamevars") + + +local function bangvec(bang) + bang = math.floor(bang) + return xmath.vec3(xmath.cosb(bang), xmath.sinb(bang)) +end + +local function angvec(ang) + return xmath.vec3(math.cos(ang), math.sin(ang)) +end + + +local D = require("CON.DEFS") +local GTICSPERSEC = gv.GTICSPERSEC + +-- color per decasecond +local COLOR = { 1, 2, 6, 7, 8 } + +gameactor +{ + D.TRANSPORTERSTAR+4, + + flags = actor.FLAGS.NOCLIP, + + move = con.move{100}, + movflags = actor.MOVFLAGS.geth, + + func = function(aci) + -- NOTE: this is prettier than calling it 'a', even if 'act' is used to + -- denote an action in other places: + local act = actor[aci] + + if (act:has_action(0)) then + act:set_action(1) -- TODO: actor constructors, i.e. 'init' callbacks + + local spr = sprite[aci] + local decasec = math.floor((gv.gametic - nukeswStart[spr.owner])/(GTICSPERSEC*10)) + + local pal = COLOR[decasec+1] + if (pal ~= nil) then + starPal[aci] = pal + end + + -- At one point, we stop coloring the spawned stars. This tests + -- per-actor variable resetting to the default value. + spr.pal = starPal[aci] + end + + if (act:checkbump()) then + con.killit() + end + end +} + +local CS = sprite.CSTAT +local SPAWNSPERTIC = 10 --> 300/second --> 18000 per minute +local TWOPI = 2*math.pi + +gameactor +{ + D.NUKEBUTTON+3, -- destroyed end-of-level nuke switch + + function(aci) + local spr = sprite[aci] + + for pi in player.all() do + -- XXX: how to emulate "use switch" distance checking code, but in + -- an actor-centric fashion? + if (not nukeswStart[aci] and player.holdskey(pi, "OPEN") + and (player[pi].pos - spr):len2sq() < 256^2) then + -- Enable us. + nukeswStart[aci] = gv.gametic + spr.cstatbits:clear(CS.TRANS_BITMASK) + spr.cstatbits:set(CS.TRANS1) + break + end + end + + local startgtic = nukeswStart[aci] + if (not startgtic) then + return + end + + local hei, zofs = spr:getheightofs() + local radius = hei/2 + + for i=0,SPAWNSPERTIC-1 do + -- Make one second go once round the circle, spawning + -- SPAWNSPERTIC*GTICSPERSEC stars. + local ii = ((gv.gametic*SPAWNSPERTIC)%(GTICSPERSEC*SPAWNSPERTIC)) + i + local v = (radius/16)*angvec(ii*TWOPI/(GTICSPERSEC*SPAWNSPERTIC)) + local circvec = xmath.rotate(xmath.vec3(0, v.x, 16*v.y), xmath.vec3(), spr.ang) -- XXX + local pos = spr^(zofs + radius) + 256*bangvec(spr.ang) + circvec + + con.insertsprite{D.TRANSPORTERSTAR+4, pos, spr.sectnum, statnum=actor.STAT.ACTOR, + xrepeat=3, yrepeat=3, ang=spr.ang, owner=aci} + end + end +}