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, <bitint>: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
This commit is contained in:
helixhorned 2013-07-04 19:38:42 +00:00
parent 44f71d313f
commit d580c1e998
9 changed files with 311 additions and 72 deletions

View file

@ -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 = {},

View file

@ -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 = {
-- <initval>: default value for per-actor variable.
-- <values>: optional, a table of <spritenum>=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 = {
-- <initval>: default value for per-player variable.
-- <values>: optional, a table of <playeridx>=value
function playervar(initval, values)
check_perxval_type(initval)
local plv = setmetatable({ _defval=initval }, playervar_mt)
return set_values_from_table(plv, values)
end

View file

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

View file

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

View file

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

View file

@ -33,6 +33,7 @@ sectorofwall_noquick;
getceilzofslopeptr;
getflorzofslopeptr;
getzsofslopeptr;
spriteheightofsptr;
changespritesect;
changespritestat;

View file

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

View file

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

View file

@ -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: <false> 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
}