1-- Game control module for Lunatic. 2 3local require = require 4local ffi = require("ffi") 5local ffiC = ffi.C 6local jit = require("jit") 7 8-- Lua C API functions, this comes from El_PushCFunctions() in lunatic_game.c. 9local CF = CF 10 11local bit = require("bit") 12local debug = require("debug") 13local io = require("io") 14local math = require("math") 15local table = require("table") 16 17local bcheck = require("bcheck") 18local con_lang = require("con_lang") 19 20local byte = require("string").byte 21local setmetatable = setmetatable 22 23local band, bor = bit.band, bit.bor 24local rshift = bit.rshift 25local tobit = bit.tobit 26 27local floor = math.floor 28 29local assert = assert 30local error = error 31local ipairs = ipairs 32local pairs = pairs 33local print = print 34local rawget = rawget 35local rawset = rawset 36local select = select 37local tostring = tostring 38local type = type 39local unpack = unpack 40 41local format = require("string").format 42 43local actor, player = assert(actor), assert(player) 44local dc = require("defs_common") 45local cansee, hitscan, neartag = dc.cansee, dc.hitscan, dc.neartag 46local inside = dc.inside 47 48local sector, wall, sprite = dc.sector, dc.wall, dc.sprite 49local wallsofsect = dc.wallsofsect 50local spritesofsect, spritesofstat = dc.spritesofsect, dc.spritesofstat 51 52local check_sector_idx = bcheck.sector_idx 53local check_tile_idx = bcheck.tile_idx 54local check_sprite_idx = bcheck.sprite_idx 55local check_player_idx = bcheck.player_idx 56local check_sound_idx = bcheck.sound_idx 57local check_number = bcheck.number 58local check_type = bcheck.type 59 60local lprivate = require("lprivate") 61local GET, WEAPON = lprivate.GET, lprivate.WEAPON 62 63ffi.cdef[[ 64size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, void * restrict stream); 65]] 66 67local OUR_REQUIRE_STRING = [[ 68 local _con=require'con' 69 local _ga,_av,_pv=_con._gamearray,_con.actorvar,_con.playervar 70]] 71local function our_get_require() 72 return OUR_REQUIRE_STRING 73end 74 75 76module(...) 77 78 79---=== ACTION/MOVE/AI HELPERS ===--- 80 81local lastid = { action=0, move=0, ai=0 } 82 83local con_action_ct = ffi.typeof("const con_action_t") 84local con_move_ct = ffi.typeof("const con_move_t") 85local con_ai_ct = ffi.typeof("const con_ai_t") 86 87-- All-zero action and move with IDs. Mostly for CON support. 88local literal_act = { [0]=con_action_ct(0), [1]=con_action_ct(1) } 89local literal_mov = { [0]=con_move_ct(0), [1]=con_move_ct(1) } 90 91local literal_am = { action=literal_act, move=literal_mov } 92-- Const-qualified 'full' action and move (with ID): 93local am_ctype_full_const = { action=con_action_ct, move=con_move_ct } 94-- Non-const-qualified 'bare' action and move (without ID): 95local am_ctype_bare = { action=ffi.typeof("struct action"), move=ffi.typeof("struct move") } 96 97-- CODEDUP lunacon.lua 98local function truetab(tab) 99 local ttab = {} 100 for i=1,#tab do 101 ttab[tab[i]] = true 102 end 103 return ttab 104end 105 106-- KEEPINSYNC lunacon.lua 107local ALLOWED_VIEWTYPE = truetab { 0, 1, 2, 3,4, 5, 7, 8, -5, -7, -8 } 108 109local function def_action_or_move(what, tab) 110 if (lastid[what] <= -(2^31)) then 111 error("Too many "..what.."s defined", 3); 112 end 113 114 bcheck.top_level(what, 4) 115 116 -- NOTE: tab[0]~=nil check for "Special default values" below. 117 if (type(tab) ~= "table" or tab[0]~=nil) then 118 error("invalid argument to con."..what..": must be a table", 3) 119 end 120 121 -- Pass args table to ffi.new, which can take either: a table with numeric 122 -- indices, or a table with key-value pairs, *but not in combination*. 123 -- See http://luajit.org/ext_ffi_semantics.html#init_table 124 local am = am_ctype_bare[what](tab) 125 126 -- Now, set all string keys as they have been ignored if tab[1] was 127 -- non-nil. 128 for key, val in pairs(tab) do 129 if (type(key)=="string") then 130 am[key] = val 131 end 132 end 133 134 if (what=="action") then 135 -- Special default values or checking of actor members. 136 -- KEEPINSYNC with ACTOR_CHECK in lunacon.lua for consistency. 137 local numframes = tab[2] or tab.numframes 138 local viewtype = tab[3] or tab.viewtype 139 local incval = tab[4] or tab.incval 140 141 if (numframes==nil) then 142 am.numframes = 1 143 else 144 check_number(numframes, 4) 145 if (numframes < 0) then 146 error("action has negative number of frames", 3) 147 end 148 end 149 150 if (viewtype==nil) then 151 am.viewtype = 1 152 else 153 check_number(viewtype, 4) 154 if (ALLOWED_VIEWTYPE[viewtype] == nil) then 155 error("action has disallowed viewtype "..viewtype, 3) 156 end 157 end 158 159 if (incval==nil) then 160 am.incval = 1 161 end 162 end 163 164 -- Named actions or moves have negative ids so that non-negative ones 165 -- can be used as (different) placeholders for all-zero ones. 166 lastid[what] = lastid[what]-1 167 168 return am_ctype_full_const[what](lastid[what], am) 169end 170 171---=== ACTION/MOVE/AI FUNCTIONS ===--- 172 173function action(tab) 174 return def_action_or_move("action", tab) 175end 176 177function move(tab) 178 return def_action_or_move("move", tab) 179end 180 181-- Get action or move for an 'ai' definition. 182local function get_action_or_move(what, val, argi) 183 if (val == nil) then 184 return literal_am[what][0] 185 elseif (ffi.istype(am_ctype_full_const[what], val)) then 186 return val 187 elseif (type(val)=="number") then 188 if (val==0 or val==1) then 189 return literal_am[what][val] 190 end 191 end 192 193 error("bad argument #"..argi.." to ai: must be nil/nothing, 0, 1, or "..what, 3) 194end 195 196function ai(action, move, flags) 197 bcheck.top_level("ai") 198 199 if (lastid.ai <= -(2^31)) then 200 error("Too many AIs defined", 2); 201 end 202 203 local act = get_action_or_move("action", action, 2) 204 local mov = get_action_or_move("move", move, 3) 205 206 if (flags~=nil) then 207 if (type(flags)~="number" or not (flags>=0 and flags<=32767)) then 208 error("bad argument #4 to ai: must be a number in [0..32767]", 2) 209 end 210 else 211 flags = 0 212 end 213 214 lastid.ai = lastid.ai-1 215 return con_ai_ct(lastid.ai, act, mov, flags) 216end 217 218 219---=== RUNTIME CON FUNCTIONS ===--- 220 221-- Will contain [<label>]=number mappings after CON translation. 222local D = { true } 223 224 225local function krandand(mask) 226 return band(ffiC.krand(), mask) 227end 228 229local function check_allnumbers(...) 230 local vals = {...} 231 for i=1,#vals do 232 assert(type(vals[i])=="number") 233 end 234end 235 236 237-- Table of all non-NODEFAULT per-actor gamevars active in the system. 238-- [<actorvar reference>] = true 239local g_actorvar = setmetatable({}, { __mode="k" }) 240 241local function A_ResetVars(i) 242 for acv in pairs(g_actorvar) do 243 acv:_clear(i) 244 end 245end 246 247ffiC.A_ResetVars = A_ResetVars 248 249-- Reset per-actor gamevars for the sprite that would be inserted by the next 250-- insertsprite() call. 251-- TODO_MP (Net_InsertSprite() is not handled) 252-- 253-- NOTE: usually, a particular actor's code doesn't use ALL per-actor gamevars, 254-- so there should be a way to clear only a subset of them (maybe those that 255-- were defined in "its" module?). 256local function A_ResetVarsNextIns() 257 -- KEEPINSYNC with insertsprite() logic in engine.c! 258 local i = ffiC.headspritestat[ffiC.MAXSTATUS] 259 if (i < 0) then 260 return 261 end 262 263 ffiC.g_noResetVars = 1 264 return A_ResetVars(i) 265end 266 267 268-- Lunatic's "insertsprite" is a wrapper around the game "A_InsertSprite", not 269-- the engine "insertsprite". 270-- 271-- Forms: 272-- 1. table-call: insertsprite{tilenum, pos, sectnum [, statnum [, owner]] [, key=val...]} 273-- valid keys are: owner, statnum, shade, xrepeat, yrepeat, xvel, zvel 274-- 2. position-call: insertsprite(tilenum, pos, sectnum [, statnum [, owner]]) 275function insertsprite(tab_or_tilenum, ...) 276 local tilenum, pos, sectnum -- mandatory 277 -- optional with defaults: 278 local owner, statnum 279 local shade, xrepeat, yrepeat, ang, xvel, zvel = 0, 48, 48, 0, 0, 0 280 281 if (type(tab_or_tilenum)=="table") then 282 local tab = tab_or_tilenum 283 tilenum, pos, sectnum = unpack(tab, 1, 3) 284 statnum = tab[4] or tab.statnum or 0 285 owner = tab[5] or tab.owner or -1 286 shade = tab.shade or shade 287 xrepeat = tab.xrepeat or xrepeat 288 yrepeat = tab.yrepeat or yrepeat 289 ang = tab.ang or ang 290 xvel = tab.xvel or xvel 291 zvel = tab.zvel or zvel 292 else 293 tilenum = tab_or_tilenum 294 local args = {...} 295 pos, sectnum = unpack(args, 1, 2) 296 statnum = args[3] or 0 297 owner = args[4] or -1 298 end 299 300 if (type(sectnum)~="number" or type(tilenum) ~= "number") then 301 error("invalid insertsprite call: 'sectnum' and 'tilenum' must be numbers", 2) 302 end 303 304 check_tile_idx(tilenum) 305 check_sector_idx(sectnum) 306 check_allnumbers(shade, xrepeat, yrepeat, ang, xvel, zvel, owner) 307 if (owner ~= -1) then 308 check_sprite_idx(owner) 309 end 310 311 if (not (statnum >= 0 and statnum < ffiC.MAXSTATUS)) then 312 error("invalid 'statnum' argument to insertsprite: must be a status number [0 .. MAXSTATUS-1]", 2) 313 end 314 315 A_ResetVarsNextIns() 316 317 local i = CF.A_InsertSprite(sectnum, pos.x, pos.y, pos.z, tilenum, 318 shade, xrepeat, yrepeat, ang, xvel, zvel, 319 owner, statnum) 320 if (owner == -1) then 321 ffiC.sprite[i]:_set_owner(i) 322 end 323 return i 324end 325 326-- INTERNAL USE ONLY. 327function _addtodelqueue(spritenum) 328 check_sprite_idx(spritenum) 329 CF.A_AddToDeleteQueue(spritenum) 330end 331 332-- This corresponds to the first (spawn from parent sprite) form of A_Spawn(). 333function spawn(tilenum, parentspritenum, addtodelqueue) 334 check_tile_idx(tilenum) 335 check_sprite_idx(parentspritenum) 336 337 if (addtodelqueue and ffiC.g_deleteQueueSize == 0) then 338 return -1 339 end 340 341 A_ResetVarsNextIns() 342 343 local i = CF.A_Spawn(parentspritenum, tilenum) 344 if (addtodelqueue) then 345 CF.A_AddToDeleteQueue(i) 346 end 347 return i 348end 349 350-- This is the second A_Spawn() form. INTERNAL USE ONLY. 351function _spawnexisting(spritenum) 352 check_sprite_idx(spritenum) 353 return CF.A_Spawn(-1, spritenum) 354end 355 356-- A_SpawnMultiple clone 357-- ow: parent sprite number 358function _spawnmany(ow, label, n) 359 local tilenum = D[label] 360 if (tilenum ~= nil) then 361 local spr = sprite[ow] 362 363 for i=n,1, -1 do 364 local j = insertsprite{ tilenum, spr^(ffiC.krand()%(47*256)), spr.sectnum, 5, ow, 365 shade=-32, xrepeat=8, yrepeat=8, ang=krandand(2047) } 366 _spawnexisting(j) 367 sprite[j].cstat = krandand(8+4) 368 end 369 end 370end 371 372local int16_st = ffi.typeof "struct { int16_t s; }" 373 374-- Get INT32_MIN for the following constant; passing 0x80000000 would be 375-- out of the range for an int32_t and thus undefined behavior! 376local SHOOT_HARDCODED_ZVEL = tobit(0x80000000) 377 378function shoot(tilenum, i, zvel) 379 check_sprite_idx(i) 380 check_sector_idx(ffiC.sprite[i].sectnum) -- accessed in A_ShootWithZvel 381 check_tile_idx(tilenum) 382 383 zvel = zvel and int16_st(zvel).s or SHOOT_HARDCODED_ZVEL 384 385 return CF.A_ShootWithZvel(i, tilenum, zvel) 386end 387 388local BADGUY_MASK = bor(con_lang.SFLAG.SFLAG_HARDCODED_BADGUY, con_lang.SFLAG.SFLAG_BADGUY) 389 390function isenemytile(tilenum) 391 return (band(ffiC.g_tile[tilenum]._flags, BADGUY_MASK)~=0) 392end 393 394-- The 'rotatesprite' wrapper used by the CON commands. 395function _rotspr(x, y, zoom, ang, tilenum, shade, pal, orientation, 396 alpha, cx1, cy1, cx2, cy2) 397 check_tile_idx(tilenum) 398 orientation = band(orientation, 4095) -- ROTATESPRITE_MAX-1 399 400 if (band(orientation, 2048) == 0) then -- ROTATESPRITE_FULL16 401 x = 65536*x 402 y = 65536*y 403 end 404 405 local blendidx = 0 406 if (alpha < 0) then 407 -- See NEG_ALPHA_TO_BLEND. 408 blendidx = -alpha 409 alpha = 0 410 orientation = bor(orientation, 1) -- RS_TRANS1 411 end 412 413 ffiC.rotatesprite_(x, y, zoom, ang, tilenum, shade, pal, bor(2,orientation), 414 alpha, blendidx, cx1, cy1, cx2, cy2) 415end 416 417-- The external legacy tile drawing function for Lunatic. 418function rotatesprite(x, y, zoom, ang, tilenum, shade, pal, orientation, 419 alpha, cx1, cy1, cx2, cy2) 420 -- Disallow <<16 coordinates from Lunatic. They only unnecessarily increase 421 -- complexity; you already have more precision in the FP number fraction. 422 if (band(orientation, 2048) ~= 0) then 423 error('left-shift-by-16 coordinates forbidden', 2) 424 end 425 426 return _rotspr(x, y, zoom, ang, tilenum, shade, pal, orientation, 427 alpha, cx1, cy1, cx2, cy2) 428end 429 430function _myos(x, y, zoom, tilenum, shade, orientation, pal) 431 if (pal==nil) then 432 local sect = player[ffiC.screenpeek].cursectnum 433 pal = (sect>=0) and sector[sect].floorpal or 0 434 end 435 436 ffiC.VM_DrawTileGeneric(x, y, zoom, tilenum, shade, orientation, pal) 437end 438 439function _inittimer(ticspersec) 440 if (not (ticspersec >= 1)) then 441 error("ticspersec must be >= 1", 2) 442 end 443 ffiC.G_InitTimer(ticspersec) 444end 445 446function _gettimedate() 447 local v = ffi.new("int32_t [8]") 448 ffiC.G_GetTimeDate(v) 449 return v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7] 450end 451 452function rnd(x) 453 return (rshift(ffiC.krand(), 8) >= (255-x)) 454end 455 456--- Legacy operators --- 457 458function _rand(x) 459 return floor((ffiC.krand()*(x+1))/65536) 460end 461 462function _displayrand(x) 463 return floor((math.random(0, 32767)*(x+1))/32768) 464end 465 466do 467 -- Arithmetic operations -- 468 local INT32_MIN = tobit(0x80000000) 469 local INT32_MAX = tobit(0x7fffffff) 470 471 -- Trapping multiplication. 472 function _mulTR(a,b) 473 local c = a*b 474 if (not (c >= INT32_MIN and c <= INT32_MAX)) then 475 error("overflow in multiplication", 2) 476 end 477 return c 478 end 479 480 -- Wrapping multiplication. 481 function _mulWR(a,b) 482 -- XXX: problematic if a*b in an infinity or NaN. 483 return tobit(a*b) 484 end 485 486 function _div(a,b) 487 if (b==0) then 488 error("divide by zero", 2) 489 end 490 -- NOTE: don't confuse with math.modf! 491 return (a - math.fmod(a,b))/b 492 end 493 494 function _mod(a,b) 495 if (b==0) then 496 error("mod by zero", 2) 497 end 498 return (math.fmod(a,b)) 499 end 500end 501 502-- Sect_ToggleInterpolation() clone 503function _togglesectinterp(sectnum, doset) 504 for w in wallsofsect(sectnum) do 505 ffiC.G_ToggleWallInterpolation(w, doset) 506 507 local nw = wall[w].nextwall 508 if (nw >= 0) then 509 ffiC.G_ToggleWallInterpolation(nw, doset) 510 ffiC.G_ToggleWallInterpolation(wall[nw].point2, doset) 511 end 512 end 513end 514 515-- Support for translated CON code: get cached sprite, actor and player structs 516-- (-fcache-sap option). 517function _getsap(aci, pli) 518 return (aci>=0) and sprite[aci], (aci>=0) and actor[aci], (pli>=0) and player[pli] 519end 520 521function _get_userdef_check(pli) 522 if (pli ~= ffiC.myconnectindex) then 523 error(format("userdefs access with non-local current player %d (we: %d)", 524 pli, ffiC.myconnectindex), 2) 525 end 526 return ffiC.ud 527end 528 529function _get_userdef(pli) 530 return ffiC.ud 531end 532 533function _err_if_negative(val) 534 if (not (val >= 0)) then 535 error("setting tag to negative value", 2) 536 end 537 return val 538end 539 540--- player/actor/sprite searching functions --- 541 542local xmath = require("xmath") 543local abs = math.abs 544local bangvec, kangvec = xmath.bangvec, xmath.kangvec 545local dist, ldist = xmath.dist, xmath.ldist 546local vec3, ivec3 = xmath.vec3, xmath.ivec3 547local rotate = xmath.rotate 548 549local function A_FP_ManhattanDist(ps, spr) 550 local distvec = ps.pos - spr^(28*256) 551 return distvec:touniform():mhlen() 552end 553 554-- Returns: player index, distance 555-- TODO_MP 556function _findplayer(pli, spritenum) 557 return 0, A_FP_ManhattanDist(player[pli], sprite[spritenum]) 558end 559 560local STAT = actor.STAT 561 562local FN_STATNUMS = { 563 [false] = { STAT.ACTOR }, 564 [true] = {}, 565} 566 567-- TODO: Python-like range() and xrange()? 568for i=0,ffiC.MAXSTATUS-1 do 569 FN_STATNUMS[true][i+1] = ffiC.MAXSTATUS-1-i 570end 571 572local FN_DISTFUNC = { 573 d2 = function(s1, s2, d) 574 return (ldist(s1, s2) < d) 575 end, 576 577 d3 = function(s1, s2, d) 578 return (dist(s1, s2) < d) 579 end, 580 581 z = function(s1, s2, d, zd) 582 return (ldist(s1, s2) < d and abs(s1.z-s2.z) < zd) 583 end, 584} 585 586function _findnear(spritenum, allspritesp, distkind, picnum, maxdist, maxzdist) 587 local statnums = FN_STATNUMS[allspritesp] 588 local distfunc = FN_DISTFUNC[distkind] 589 local spr = sprite[spritenum] 590 591 for _,st in ipairs(statnums) do 592 for i in spritesofstat(st) do 593 if (i ~= spritenum and sprite[i].picnum==picnum) then 594 if (distfunc(spr, sprite[i], maxdist, maxzdist)) then 595 return i 596 end 597 end 598 end 599 end 600 601 return -1 602end 603 604 605---=== Weapon stuff ===--- 606 607 608--- Helper functions (might be exported later) --- 609 610local function have_ammo_at_max(ps, weap) 611 return (ps.ammo_amount[weap] >= ps.max_ammo_amount[weap]) 612end 613 614function _tossweapon(pli) -- P_DropWeapon replacement 615 -- NOTE: We're passing player index, C-CON passes APLAYER sprite. 616 check_player_idx(pli) 617 local ps = ffiC.g_player[pli].ps 618 619 bcheck.weapon_idx(ps.curr_weapon) 620 local cw = ffiC.g_playerWeapon[pli][ps.curr_weapon].workslike 621 622 if (cw >= ffiC.MAX_WEAPONS+0ULL) then 623 return 624 end 625 626 if (krandand(1) ~= 0) then 627 spawn(ffiC.WeaponPickupSprites[cw], ps.i) 628 elseif (cw==WEAPON.RPG or cw==WEAPON.HANDBOMB) then 629 if (D.EXPLOSION2 ~= nil) then 630 spawn(D.EXPLOSION2, ps.i) 631 end 632 end 633end 634 635local function P_AddAmmo(ps, weap, amount) 636 if (not have_ammo_at_max(ps, weap)) then 637 local curamount = ps.ammo_amount[weap] 638 local maxamount = ps.max_ammo_amount[weap] 639 -- NOTE: no clamping towards the bottom 640 ps.ammo_amount[weap] = math.min(curamount+amount, maxamount) 641 end 642end 643 644local function P_AddWeaponAmmoCommon(ps, weap, amount) 645 P_AddAmmo(ps, weap, amount) 646 647 if (ps.curr_weapon==WEAPON.KNEE and ps:has_weapon(weap)) then 648 CF.P_AddWeaponMaybeSwitchI(ps.weapon._p, weap); 649 end 650end 651 652 653--- Functions that must be exported because they are used by LunaCON generated code, 654--- but which are off limits to users. (That is, we need to think about how to 655--- expose the functionality in a better fashion than merely giving access to 656--- the C functions.) 657--- TODO: Move these to a separate module like "con_private". 658 659-- quotes 660local REALMAXQUOTES = con_lang.REALMAXQUOTES 661local MAXQUOTELEN = con_lang.MAXQUOTELEN 662 663-- CON redefinequote command 664function _definequote(qnum, quotestr) 665 -- NOTE: this is more permissive than C-CON: we allow to redefine quotes 666 -- that were not previously defined. 667 bcheck.quote_idx(qnum, true) 668 assert(type(quotestr)=="string") 669 ffiC.C_DefineQuote(qnum, quotestr) 670 return (#quotestr >= MAXQUOTELEN) 671end 672 673function _quote(pli, qnum) 674 bcheck.quote_idx(qnum) 675 check_player_idx(pli) 676 ffiC.P_DoQuote(qnum+REALMAXQUOTES, ffiC.g_player[pli].ps) 677end 678 679function _echo(qnum) 680 local cstr = bcheck.quote_idx(qnum) 681 ffiC.OSD_Printf("%s\n", cstr) 682end 683 684function _userquote(qnum) 685 local cstr = bcheck.quote_idx(qnum) 686 -- NOTE: G_AddUserQuote strcpy's the string 687 ffiC.G_AddUserQuote(cstr) 688end 689 690local function strlen(cstr) 691 for i=0,math.huge do 692 if (cstr[i]==0) then 693 return i 694 end 695 end 696 assert(false) 697end 698 699-- NOTE: dst==src is OK (effectively a no-op) 700local function strcpy(dst, src) 701 local i=-1 702 repeat 703 i = i+1 704 dst[i] = src[i] 705 until (src[i]==0) 706end 707 708function _qstrlen(qnum) 709 return strlen(bcheck.quote_idx(qnum)) 710end 711 712function _qsubstr(qdst, qsrc, start, length) 713 local cstr_dst = bcheck.quote_idx(qdst) 714 local cstr_src = bcheck.quote_idx(qsrc) 715 716 if (not (start >= 0 and start < MAXQUOTELEN)) then 717 error("invalid start position "..start, 2) 718 end 719 720 if (not (length >= 0)) then -- NOTE: no check for start+length! 721 error("invalid length "..length, 2) 722 end 723 724 local si = 0 725 while (cstr_src[si] ~= 0 and si < start) do 726 si = si+1 727 end 728 729 for i=0,math.huge do 730 cstr_dst[i] = cstr_src[si + i] 731 732 if (si + i == MAXQUOTELEN-1 or i==length or cstr_dst[i] == 0) then 733 cstr_dst[i] = 0 734 break 735 end 736 end 737end 738 739function _qstrcpy(qdst, qsrc) 740 local cstr_dst = bcheck.quote_idx(qdst) 741 local cstr_src = bcheck.quote_idx(qsrc) 742 strcpy(cstr_dst, cstr_src) 743end 744 745-- NOTE: qdst==qsrc is OK (duplicates the quote) 746function _qstrcat(qdst, qsrc, n) 747 local cstr_dst = bcheck.quote_idx(qdst) 748 local cstr_src = bcheck.quote_idx(qsrc) 749 750 if (cstr_src[0]==0) then 751 return 752 end 753 754 if (cstr_dst[0]==0) then 755 return strcpy(cstr_dst, cstr_src) 756 end 757 758 if (n == nil) then 759 n = 0x7fffffff 760 elseif (n < 0) then 761 error("invalid number of chars to concatenate: "..n, 2) 762 end 763 764 -- From here on: destination and source quote (potentially aliased) are 765 -- nonempty. 766 767 local slen_dst = strlen(cstr_dst) 768 assert(slen_dst <= MAXQUOTELEN-1) 769 770 if (slen_dst == MAXQUOTELEN-1) then 771 return 772 end 773 774 local i = slen_dst 775 local j = 0 776 777 repeat 778 -- NOTE: don't copy the first char yet, so that the qdst==qsrc case 779 -- works correctly. 780 n = n-1 781 i = i+1 782 j = j+1 783 cstr_dst[i] = cstr_src[j] 784 until (n == 0 or i >= MAXQUOTELEN-1 or cstr_src[j]==0) 785 786 -- Now copy the first char! 787 cstr_dst[slen_dst] = cstr_src[0] 788 cstr_dst[i] = 0 789end 790 791local buf = ffi.new("char [?]", MAXQUOTELEN) 792 793function _qsprintf(qdst, qsrc, ...) 794 -- NOTE: more permissive than C-CON, see _definequote 795 if (bcheck.quote_idx(qdst, true) == nil) then 796 ffiC.C_DefineQuote(qdst, "") -- allocate quote 797 end 798 799 local dst = bcheck.quote_idx(qdst) 800 local src = bcheck.quote_idx(qsrc) 801 local vals = {...} 802 803 local i, j, vi = 0, 0, 1 804 805 while (true) do 806 local ch = src[j] 807 local didfmt = false 808 809 if (ch==0) then 810 break 811 end 812 813 if (ch==byte'%') then 814 local nch = src[j+1] 815 if (nch==byte'd' or (nch==byte'l' and src[j+2]==byte'd')) then 816 -- number 817 didfmt = true 818 819 if (vi > #vals) then 820 break 821 end 822 823 local numstr = tostring(vals[vi]) 824 assert(type(numstr)=="string") 825 vi = vi+1 826 827 local ncopied = math.min(#numstr, MAXQUOTELEN-1-i) 828 ffi.copy(buf+i, numstr, ncopied) 829 830 i = i+ncopied 831 j = j+1+(nch==byte'd' and 1 or 2) 832 elseif (nch==byte's') then 833 -- string 834 didfmt = true 835 if (vi > #vals) then 836 break 837 end 838 839 local k = -1 840 local tmpsrc = bcheck.quote_idx(vals[vi]) 841 vi = vi+1 842 843 i = i-1 844 repeat 845 i = i+1 846 k = k+1 847 buf[i] = tmpsrc[k] 848 until (i >= MAXQUOTELEN-1 or tmpsrc[k]==0) 849 850 j = j+2 851 end 852 end 853 854 if (not didfmt) then 855 buf[i] = src[j] 856 i = i+1 857 j = j+1 858 end 859 860 if (i >= MAXQUOTELEN-1) then 861 break 862 end 863 end 864 865 buf[i] = 0 866 strcpy(dst, buf) 867end 868 869function _getkeyname(qdst, gfuncnum, which) 870 local cstr_dst = bcheck.quote_idx(qdst) 871 872 if (not (gfuncnum >= 0 and gfuncnum < ffiC.NUMGAMEFUNCTIONS)) then 873 error("invalid game function number "..gfuncnum, 2) 874 end 875 876 if (not (which >= 0 and which < 3)) then 877 error("third argument to getkeyname must be 0, 1 or 2", 2) 878 end 879 880 local cstr_src 881 882 for i = (which==2 and 0 or which), (which==2 and 1 or which) do 883 local scancode = ffiC.ud.config.KeyboardKeys[gfuncnum][i] 884 cstr_src = ffiC.KB_ScanCodeToString(scancode) 885 if (cstr_src[0] ~= 0) then 886 break 887 end 888 end 889 890 if (cstr_src[0] ~= 0) then 891 -- All key names are short, no problem strcpy'ing them 892 strcpy(cstr_dst, cstr_src) 893 end 894end 895 896local EDUKE32_VERSION_STR = "EDuke32 2.0.0devel "..ffi.string(ffiC.s_buildRev) 897 898local function quote_strcpy(dst, src) 899 local i=-1 900 repeat 901 i = i+1 902 dst[i] = src[i] 903 until (src[i]==0 or i==MAXQUOTELEN-1) 904 dst[i] = 0 905end 906 907function _qgetsysstr(qdst, what, pli) 908 local dst = bcheck.quote_idx(qdst) 909 910 local idx = ffiC.ud.volume_number*con_lang.MAXLEVELS + ffiC.ud.level_number 911 local MAXIDX = ffi.sizeof(ffiC.g_mapInfo) / ffi.sizeof(ffiC.g_mapInfo[0]) 912 local mapnamep = (what == ffiC.STR_MAPNAME) 913 914 if (mapnamep or what == ffiC.STR_MAPFILENAME) then 915 assert(not (idx >= MAXIDX+0ULL)) 916 local src = mapnamep and ffiC.g_mapInfo[idx].name or ffiC.g_mapInfo[idx].filename 917 if (src == nil) then 918 error(format("attempted access to %s of non-existent map (vol=%d, lev=%d)", 919 mapnamep and "name" or "file name", 920 ffiC.ud.volume_number, ffiC.ud.level_number), 2) 921 end 922 quote_strcpy(dst, src) 923 elseif (what == ffiC.STR_PLAYERNAME) then 924 check_player_idx(pli) 925 ffi.copy(dst, ffiC.g_player[pli].user_name, ffi.sizeof(ffiC.g_player[0].user_name)) 926 elseif (what == ffiC.STR_VERSION) then 927 ffi.copy(dst, EDUKE32_VERSION_STR) 928 elseif (what == ffiC.STR_GAMETYPE) then 929 ffi.copy(dst, "multiplayer not yet implemented") -- TODO_MP 930 elseif (what == ffiC.STR_VOLUMENAME) then 931 local vol = ffiC.ud.volume_number 932 bcheck.volume_idx(vol) 933 ffi.copy(dst, ffiC.g_volumeNames[vol], ffi.sizeof(ffiC.g_volumeNames[0])) 934 elseif (what == ffiC.STR_YOURTIME) then 935 ffi.copy(dst, ffi.string(ffiC.G_PrintYourTime())) 936 elseif (what == ffiC.STR_PARTIME) then 937 ffi.copy(dst, ffi.string(ffiC.G_PrintParTime())) 938 elseif (what == ffiC.STR_DESIGNERTIME) then 939 ffi.copy(dst, ffi.string(ffiC.G_PrintDesignerTime())) 940 elseif (what == ffiC.STR_BESTTIME) then 941 ffi.copy(dst, ffi.string(ffiC.G_PrintBestTime())) 942 else 943 error("unknown system string ID "..what, 2) 944 end 945end 946 947function _getpname(qnum, pli) 948 bcheck.quote_idx(qnum, true) 949 check_player_idx(pli) 950 local uname = ffiC.g_player[pli].user_name 951 ffiC.C_DefineQuote(qnum, (uname[0] ~= 0) and uname or tostring(pli)) 952end 953 954 955-- switch statement support 956function _switch(swtab, testval, aci,pli,dst) 957 local func = swtab[testval] or swtab.default 958 if (func) then 959 func(aci, pli, dst) 960 end 961end 962 963 964--== Text rendering ==-- 965 966-- For external use. NOTE: <pal> comes before <shade>. 967function minitext(x, y, str, pal, shade) 968 check_type(str, "string") 969 ffiC.minitext_(x, y, str, shade or 0, pal or 0, 2+8+16) 970end 971 972-- For CON only. 973function _minitext(x, y, qnum, shade, pal) 974 local cstr = bcheck.quote_idx(qnum) 975 ffiC.minitext_(x, y, cstr, shade, pal, 2+8+16) 976end 977 978function _digitalnumber(tilenum, x, y, num, shade, pal, 979 orientation, cx1, cy1, cx2, cy2, zoom) 980 if (not (tilenum >= 0 and tilenum < ffiC.MAXTILES-9)) then 981 error("invalid base tile number "..tilenum, 2) 982 end 983 984 ffiC.G_DrawTXDigiNumZ(tilenum, x, y, num, shade, pal, 985 orientation, cx1, cy1, cx2, cy2, zoom) 986end 987 988local function text_check_common(tilenum, orientation) 989 if (not (tilenum >= 0 and tilenum < ffiC.MAXTILES-255)) then 990 error("invalid base tile number "..tilenum, 3) 991 end 992 993 return band(orientation, 4095) -- ROTATESPRITE_MAX-1 994end 995 996function _gametext(tilenum, x, y, qnum, shade, pal, orientation, 997 cx1, cy1, cx2, cy2, zoom) 998 orientation = text_check_common(tilenum, orientation) 999 local cstr = bcheck.quote_idx(qnum) 1000 1001 ffiC.G_PrintGameText(tilenum, bit.arshift(x,1), y, cstr, shade, pal, 1002 orientation, cx1, cy1, cx2, cy2, zoom) 1003end 1004-- XXX: JIT-compiling FFI calls to G_PrintGameText crashes LuaJIT somewhere in 1005-- its internal routines. I'm not sure who is to blame here but I suspect we 1006-- have some undefined behavior somewhere. Reproducible with DukePlus 2.35 on 1007-- x86 when clicking wildly through its menu. 1008jit.off(_gametext) 1009 1010function _screentext(tilenum, x, y, z, blockangle, charangle, q, shade, pal, orientation, 1011 alpha, xspace, yline, xbetween, ybetween, f, x1, y1, x2, y2) 1012 orientation = text_check_common(tilenum, orientation) 1013 local cstr = bcheck.quote_idx(q) 1014 1015 ffiC.G_ScreenText(tilenum, x, y, z, blockangle, charangle, cstr, shade, pal, bor(2,orientation), 1016 alpha, xspace, yline, xbetween, ybetween, f, x1, y1, x2, y2) 1017end 1018 1019function _qstrdim(tilenum, x, y, z, blockangle, q, orientation, 1020 xspace, yline, xbetween, ybetween, f, x1, y1, x2, y2) 1021 orientation = text_check_common(tilenum, orientation) 1022 local cstr = bcheck.quote_idx(q) 1023 1024 local dim = ffiC.G_ScreenTextSize(tilenum, x, y, z, blockangle, cstr, orientation, 1025 xspace, yline, xbetween, ybetween, f, x1, y1, x2, y2); 1026 return dim.x, dim.y 1027end 1028 1029function _showview(x, y, z, a, horiz, sect, x1, y1, x2, y2, unbiasedp) 1030 check_sector_idx(sect) 1031 1032 if (x1 < 0 or y1 < 0 or x2 >= 320 or y2 >= 200 or x2 < x1 or y2 < y1) then 1033 local str = format("(%d,%d)--(%d,%d)", x1, y1, x2, y2) 1034 error("invalid coordinates "..str, 2) 1035 end 1036 1037 CF.G_ShowViewXYZ(x, y, z, a, horiz, sect, x1, y1, x2, y2, unbiasedp); 1038end 1039 1040 1041---=== DEFINED LABELS ===--- 1042 1043-- Check if <picnum> equals to the number defined by <label> from CON. 1044-- If there is no such label, return nil. 1045local function ispic(picnum, label) 1046 return D[label] and (picnum==D[label]) 1047end 1048 1049-- Which tiles should use .yvel instead of .hitag for the respawned tile? 1050-- Will be [<number>] = true after CON translation. 1051local RESPAWN_USE_YVEL = { 1052 "STATUE", "NAKED1", "PODFEM1", "FEM1", "FEM2", 1053 "FEM3", "FEM5", "FEM4", "FEM6", "FEM8", 1054 "FEM7", "FEM9", "FEM10", 1055} 1056 1057-- Is an inventory tile? 1058-- Will be [<number>] = true after CON translation. 1059local INVENTILE = { 1060 "FIRSTAID", "STEROIDS", "AIRTANK", "JETPACK", "HEATSENSOR", 1061 "BOOTS", "HOLODUKE", 1062} 1063 1064local function totruetab(tab) 1065 local numelts = #tab 1066 for i=1,numelts do 1067 local label = tab[i] 1068 if (D[label]) then 1069 tab[label] = true 1070 end 1071 tab[i] = nil 1072 end 1073end 1074 1075-- This will be run after CON has been translated. 1076function _setuplabels(conlabels) 1077 assert(D[1]) -- Allow running this function exactly once. 1078 D = conlabels 1079 totruetab(RESPAWN_USE_YVEL) 1080 totruetab(INVENTILE) 1081end 1082 1083 1084function _A_DoGuts(i, gutstile, n) 1085 check_tile_idx(gutstile) 1086 local spr = sprite[i] 1087 local smallguts = spr.xrepeat < 16 and spr:isenemy() 1088 local xsz = smallguts and 8 or 32 1089 local ysz = xsz 1090 local z = math.min(spr.z, sector[spr.sectnum]:floorzat(spr)) - 8*256 1091 1092 if (ispic(spr.picnum, "COMMANDER")) then 1093 z = z - (24*256) 1094 end 1095 1096 for i=n,1, -1 do 1097 local pos = vec3(spr.x+krandand(255)-128, spr.y+krandand(255)-128, z-krandand(8191)) 1098 local j = insertsprite{ gutstile, pos, spr.sectnum, 5, i, shade=-32, xrepeat=xsz, yrepeat=ysz, 1099 ang=krandand(2047), xvel=48+krandand(31), zvel=-512-krandand(2047) } 1100 local newspr = sprite[j] 1101 if (ispic(newspr.picnum, "JIBS2")) then 1102 -- This looks silly, but EVENT_EGS code could have changed the size 1103 -- between the insertion and here. 1104 newspr.xrepeat = newspr.xrepeat/4 1105 newspr.yrepeat = newspr.yrepeat/4 1106 end 1107 newspr.pal = spr.pal 1108 end 1109end 1110 1111function _debris(i, dtile, n) 1112 local spr = sprite[i] 1113 if (spr.sectnum >= ffiC.numsectors+0ULL) then 1114 return 1115 end 1116 1117 for j=n-1,0, -1 do 1118 local isblimpscrap = (ispic(spr.picnum, "BLIMP") and ispic(dtile, "SCRAP1")) 1119 local picofs = isblimpscrap and 0 or krandand(3) 1120 local pos = spr + vec3(krandand(255)-128, krandand(255)-128, -(8*256)-krandand(8191)) 1121 local jj = insertsprite{ dtile+picofs, pos, spr.sectnum, 5, i, 1122 shade=spr.shade, xrepeat=32+krandand(15), yrepeat=32+krandand(15), 1123 ang=krandand(2047), xvel=32+krandand(127), zvel=-krandand(2047) } 1124 -- NOTE: g_blimpSpawnItems[14] (its array size is 15) will never be chosen 1125 sprite[jj]:set_yvel(isblimpscrap and ffiC.g_blimpSpawnItems[math.mod(jj, 14)] or -1) 1126 sprite[jj].pal = spr.pal 1127 end 1128end 1129 1130function _A_SpawnGlass(i, n) 1131 if (D.GLASSPIECES) then 1132 local spr = sprite[i] 1133 1134 for j=n,1, -1 do 1135 local k = insertsprite{ D.GLASSPIECES+n%3, spr^(256*krandand(16)), spr.sectnum, 5, i, 1136 shade=krandand(15), xrepeat=36, yrepeat=36, ang=krandand(2047), 1137 xvel=32+krandand(63), zvel=-512-krandand(2047) } 1138 sprite[k].pal = spr.pal 1139 end 1140 end 1141end 1142 1143function _A_IncurDamage(sn) 1144 check_sprite_idx(sn) 1145 return ffiC.A_IncurDamage(sn) 1146end 1147 1148function _sizeto(i, xr, yr) 1149 local spr = sprite[i] 1150 local dr = (xr-spr.xrepeat) 1151 -- NOTE: could "overflow" (e.g. goal repeat is 256, gets converted to 0) 1152 spr.xrepeat = spr.xrepeat + ((dr == 0) and 0 or (dr < 0 and -1 or 1)) 1153 -- TODO: y stretching is conditional 1154 dr = (yr-spr.yrepeat) 1155 spr.yrepeat = spr.yrepeat + ((dr == 0) and 0 or (dr < 0 and -1 or 1)) 1156end 1157 1158function _pstomp(ps, i) 1159 if (ps.knee_incs == 0 and sprite[ps.i].xrepeat >= 40) then 1160 local spr = sprite[i] 1161 if (cansee(spr^(4*256), spr.sectnum, ps.pos^(-16*256), sprite[ps.i].sectnum)) then 1162 for j=ffiC.g_mostConcurrentPlayers-1,0 do 1163 if (player[j].actorsqu == i) then 1164 return 1165 end 1166 end 1167 ps.actorsqu = i 1168 ps.knee_incs = 1 1169 if (ps.weapon_pos == 0) then 1170 ps.weapon_pos = -1 1171 end 1172 end 1173 end 1174end 1175 1176function _pkick(ps, spr) 1177 -- TODO_MP 1178 if (not ispic(spr.picnum, "APLAYER") and ps.quick_kick==0) then 1179 ps.quick_kick = 14 1180 end 1181end 1182 1183function _VM_ResetPlayer2(snum, flags) 1184 check_player_idx(snum) 1185 return (CF.VM_ResetPlayer2(snum, flags)~=0) 1186end 1187 1188local PALBITS = { [0]=1, [21]=2, [23]=4 } 1189local ICONS = { 1190 [GET.FIRSTAID] = 1, -- ICON_FIRSTAID 1191 [GET.STEROIDS] = 2, 1192 [GET.HOLODUKE] = 3, 1193 [GET.JETPACK] = 4, 1194 [GET.HEATS] = 5, 1195 [GET.SCUBA] = 6, 1196 [GET.BOOTS] = 7, 1197} 1198 1199function _addinventory(ps, inv, amount, i) 1200 if (inv == GET.ACCESS) then 1201 local pal = sprite[i].pal 1202 if (PALBITS[pal]) then 1203 ps.got_access = bor(ps.got_access, PALBITS[pal]) 1204 end 1205 else 1206 if (ICONS[inv]) then 1207 ps.inven_icon = ICONS[inv] 1208 end 1209 1210 if (inv == GET.SHIELD) then 1211 amount = math.min(ps.max_shield_amount, amount) 1212 end 1213 -- NOTE: this is more permissive than CON, e.g. allows 1214 -- GET_DUMMY1 too. 1215 ps.inv_amount[inv] = amount 1216 end 1217end 1218 1219function _checkpinventory(ps, inv, amount, i) 1220 if (inv==GET.SHIELD) then 1221 return ps.inv_amount[inv] ~= ps.max_shield_amount 1222 elseif (inv==GET.ACCESS) then 1223 local palbit = PALBITS[sprite[i].pal] 1224 return palbit and (band(ps.got_access, palbit)~=0) 1225 else 1226 return ps.inv_amount[inv] ~= amount 1227 end 1228end 1229 1230local INV_SELECTION_ORDER = { 1231 GET.FIRSTAID, 1232 GET.STEROIDS, 1233 GET.JETPACK, 1234 GET.HOLODUKE, 1235 GET.HEATS, 1236 GET.SCUBA, 1237 GET.BOOTS, 1238} 1239 1240-- checkavailinven CON command 1241function _selectnextinv(ps) 1242 for _,inv in ipairs(INV_SELECTION_ORDER) do 1243 if (ps.inv_amount[inv] > 0) then 1244 ps.inven_icon = ICONS[inv] 1245 return 1246 end 1247 end 1248 1249 ps.inven_icon = 0 1250end 1251 1252function _checkavailweapon(pli) 1253 check_player_idx(pli) 1254 CF.P_CheckWeaponI(pli) 1255end 1256 1257function _addphealth(ps, aci, hlthadd) 1258 if (ps.newowner >= 0) then 1259 ffiC.G_ClearCameraView(ps) 1260 end 1261 1262 if (ffiC.ud.god ~= 0) then 1263 return 1264 end 1265 1266 local notatomic = not ispic(sprite[aci].picnum, "ATOMICHEALTH") 1267 local j = sprite[ps.i].extra 1268 1269 if (notatomic and j > ps.max_player_health and hlthadd > 0) then 1270 return 1271 end 1272 1273 if (j > 0) then 1274 j = j + hlthadd 1275 end 1276 1277 if (notatomic) then 1278 if (hlthadd > 0) then 1279 j = math.min(j, ps.max_player_health) 1280 end 1281 else 1282 j = math.min(j, 2*ps.max_player_health) 1283 end 1284 1285 j = math.max(j, 0) 1286 1287 if (hlthadd > 0) then 1288 local qmaxhlth = rshift(ps.max_player_health, 2) 1289 if (j-hlthadd < qmaxhlth and j >= qmaxhlth) then 1290 -- XXX: DUKE_GOTHEALTHATLOW 1291 _sound(aci, 229) 1292 end 1293 1294 ps.last_extra = j 1295 end 1296 1297 sprite[ps.i].extra = j 1298end 1299 1300-- The return value is true iff the ammo was at the weapon's max. 1301-- In that case, no action is taken. 1302function _addammo(ps, weap, amount) 1303 return have_ammo_at_max(ps, weap) or P_AddWeaponAmmoCommon(ps, weap, amount) 1304end 1305 1306function _addweapon(ps, weap, amount) 1307 bcheck.weapon_idx(weap) 1308 1309 if (not ps:has_weapon(weap)) then 1310 CF.P_AddWeaponMaybeSwitchI(ps.weapon._p, weap); 1311 elseif (have_ammo_at_max(ps, weap)) then 1312 return true 1313 end 1314 1315 P_AddWeaponAmmoCommon(ps, weap, amount) 1316end 1317 1318function _A_RadiusDamage(i, r, hp1, hp2, hp3, hp4) 1319 check_sprite_idx(i) 1320 check_allnumbers(r, hp1, hp2, hp3, hp4) 1321 CF.A_RadiusDamage(i, r, hp1, hp2, hp3, hp4) 1322end 1323 1324local NEAROP = { 1325 [9] = true, 1326 [15] = true, 1327 [16] = true, 1328 [17] = true, 1329 [18] = true, 1330 [19] = true, 1331 [20] = true, 1332 [21] = true, 1333 [22] = true, 1334 [23] = true, 1335 [25] = true, 1336 [26] = true, 1337 [29] = true, 1338} 1339 1340function _operate(spritenum) 1341 local spr = sprite[spritenum] 1342 1343 if (sector[spr.sectnum].lotag == 0) then 1344 local tag = neartag(spr^(32*256), spr.sectnum, spr.ang, 768, 4+1) 1345 if (tag.sector >= 0) then 1346 local sect = sector[tag.sector] 1347 local lotag = sect.lotag 1348 local lotag_lo = band(lotag, 0xff) 1349 1350 if (NEAROP[lotag_lo]) then 1351 if (lotag_lo == 23 or sect.floorz == sect.ceilingz) then 1352 if (band(lotag, 32768+16384) == 0) then 1353 for j in spritesofsect(tag.sector) do 1354 if (ispic(sprite[j].picnum, "ACTIVATOR")) then 1355 return 1356 end 1357 end 1358 CF.G_OperateSectors(tag.sector, spritenum) 1359 end 1360 end 1361 end 1362 end 1363 end 1364end 1365 1366function _operatesectors(sectnum, spritenum) 1367 check_sector_idx(sectnum) 1368 check_sprite_idx(spritenum) -- XXX: -1 permissible under certain circumstances? 1369 CF.G_OperateSectors(sectnum, spritenum) 1370end 1371 1372function _operateactivators(tag, playernum) 1373 check_player_idx(playernum) 1374 -- NOTE: passing oob playernum would be safe because G_OperateActivators 1375 -- bound-checks it 1376 assert(type(tag)=="number") 1377 CF.G_OperateActivators(tag, playernum) 1378end 1379 1380function _activatebysector(sectnum, spritenum) 1381 local didit = false 1382 for i in spriteofsect(sectnum) do 1383 if (ispic(sprite[i].picnum, "ACTIVATOR")) then 1384 CF.G_OperateActivators(sprite[i].lotag, -1) 1385 end 1386 end 1387 if (didit) then 1388 _operatesectors(sectnum, spritenum) 1389 end 1390end 1391 1392function _checkactivatormotion(tag) 1393 return ffiC.G_CheckActivatorMotion(tag) 1394end 1395 1396function _endofgame(pli, timebeforeexit) 1397 player[pli].timebeforeexit = timebeforeexit 1398 player[pli].customexitsound = -1 1399 ffiC.ud.eog = 1 1400end 1401 1402function _bulletnear(i) 1403 return (ffiC.A_Dodge(sprite[i]) == 1) 1404end 1405 1406-- d is a distance 1407function _awayfromwall(spr, d) 1408 local vec2 = xmath.vec2 1409 local vecs = { vec2(d,d), vec2(-d,-d), vec2(d,-d), vec2(-d,d) } 1410 for i=1,4 do 1411 if (not inside(vecs[i]+spr, spr.sectnum)) then 1412 return false 1413 end 1414 end 1415 return true 1416end 1417 1418-- TODO: xmath.vec3 'mhlen2' method? 1419local function manhatdist(v1, v2) 1420 return abs(v1.x-v2.x) + abs(v1.y-v2.y) 1421end 1422 1423-- "otherspr" is either player or holoduke sprite 1424local function A_FurthestVisiblePoint(aci, otherspr) 1425 if (band(actor[aci]:get_count(), 63) ~= 0) then 1426 return 1427 end 1428 1429 -- TODO_MP 1430 local angincs = (ffiC.ud.player_skill < 3) and 1024 or 2048/(1+krandand(1)) 1431 local spr = sprite[aci] 1432 1433 local j = 0 1434 repeat 1435 local ray = kangvec(otherspr.ang + j, 16384-krandand(32767)) 1436 local hit = hitscan(otherspr^(16*256), otherspr.sectnum, ray, ffiC.CLIPMASK1) 1437 local dother = manhatdist(hit.pos, otherspr) 1438 local dactor = manhatdist(hit.pos, spr) 1439 1440 if (dother < dactor and hit.sector >= 0) then 1441 if (cansee(hit.pos, hit.sector, spr^(16*256), spr.sectnum)) then 1442 return hit 1443 end 1444 end 1445 1446 j = j + (angincs - krandand(511)) 1447 until (j >= 2048) 1448end 1449 1450local MAXSLEEPDIST = 16384 1451local SLEEPTIME = 1536 1452 1453function _cansee(aci, ps) 1454 -- Select sprite for monster to target. 1455 local spr = sprite[aci] 1456 local s = sprite[ps.i] 1457 1458 -- This is kind of redundant, but points the error messages to the CON code. 1459 check_sector_idx(spr.sectnum) 1460 check_sector_idx(s.sectnum) 1461 1462 if (ps.holoduke_on >= 0) then 1463 -- If holoduke is on, let them target holoduke first. 1464 local hs = sprite[ps.holoduke_on] 1465 1466 if (cansee(spr^krandand(8191), spr.sectnum, s, s.sectnum)) then 1467 s = hs 1468 end 1469 end 1470 1471 -- Can they see player (or player's holoduke)? 1472 local can = cansee(spr^krandand(47*256), spr.sectnum, s^(24*256), s.sectnum) 1473 1474 if (not can) then 1475 -- Search around for target player. 1476 local hit = A_FurthestVisiblePoint(aci, s) 1477 if (hit ~= nil) then 1478 can = true 1479 actor[aci].lastvx = hit.pos.x 1480 actor[aci].lastvy = hit.pos.y 1481 end 1482 else 1483 -- Else, they did see it. Save where we were looking... 1484 actor[aci].lastvx = s.x 1485 actor[aci].lastvy = s.y 1486 end 1487 1488 if (can and (spr.statnum==STAT.ACTOR or spr.statnum==STAT.STANDABLE)) then 1489 actor[aci].timetosleep = SLEEPTIME 1490 end 1491 1492 return can 1493end 1494 1495function _isvalid(i) 1496 check_sprite_idx(i) 1497 return ffiC.sprite[i].statnum ~= ffiC.MAXSTATUS and 1 or 0 1498end 1499 1500function _canseespr(s1, s2) 1501 check_sprite_idx(s1) 1502 check_sprite_idx(s2) 1503 1504 local spr1, spr2 = ffiC.sprite[s1], ffiC.sprite[s2] 1505 -- Redundant, but points the error messages to the CON code: 1506 check_sector_idx(spr1.sectnum) 1507 check_sector_idx(spr2.sectnum) 1508 1509 return cansee(spr1, spr1.sectnum, spr2, spr2.sectnum) and 1 or 0 1510end 1511 1512-- TODO: replace ivec3 allocations with stores to a static ivec3, like in 1513-- updatesector*? 1514 1515-- CON "hitscan" command 1516function _hitscan(x, y, z, sectnum, vx, vy, vz, cliptype) 1517 local srcv = ivec3(x, y, z) 1518 local ray = ivec3(vx, vy, vz) 1519 local hit = hitscan(srcv, sectnum, ray, cliptype) 1520 return hit.sector, hit.wall, hit.sprite, hit.pos.x, hit.pos.y, hit.pos.z 1521end 1522 1523-- CON "neartag" command 1524function _neartag(x, y, z, sectnum, ang, range, tagsearch) 1525 local pos = ivec3(x, y, z) 1526 local near = neartag(pos, sectnum, ang, range, tagsearch) 1527 return near.sector, near.wall, near.sprite, near.dist 1528end 1529 1530-- CON "getzrange" command 1531function _getzrange(x, y, z, sectnum, walldist, clipmask) 1532 check_sector_idx(sectnum) 1533 local ipos = ivec3(x, y, z) 1534 local hit = sector[sectnum]:zrangeat(ipos, walldist, clipmask) 1535 -- return: ceilz, ceilhit, florz, florhit 1536 return hit.c.z, hit.c.num + (hit.c.spritep and 49152 or 16384), 1537 hit.f.z, hit.f.num + (hit.f.spritep and 49152 or 16384) 1538end 1539 1540-- CON "clipmove" and "clipmovenoslide" commands 1541function _clipmovex(x, y, z, sectnum, xv, yv, wd, cd, fd, clipmask, noslidep) 1542 check_sector_idx(sectnum) 1543 local ipos = ivec3(x, y, z) 1544 local sect = ffi.new("int16_t [1]", sectnum) 1545 local ret = ffiC.clipmovex(ipos, sect, xv, yv, wd, cd, fd, clipmask, noslidep) 1546 -- Return: clipmovex() return value; updated x, y, sectnum 1547 return ret, ipos.x, ipos.y, sect[0] 1548end 1549 1550function _sleepcheck(aci, dst) 1551 local acs = actor[aci] 1552 if (dst > MAXSLEEPDIST and acs.timetosleep == 0) then 1553 acs.timetosleep = SLEEPTIME 1554 end 1555end 1556 1557function _canseetarget(spr, ps) 1558 -- NOTE: &41 ? 1559 return cansee(spr^(256*krandand(41)), spr.sectnum, 1560 ps.pos, sprite[ps.i].sectnum) 1561end 1562 1563function _movesprite(spritenum, x, y, z, cliptype) 1564 check_sprite_idx(spritenum) 1565 local vel = ivec3(x, y, z) 1566 return ffiC.A_MoveSpriteClipdist(spritenum, vel, cliptype, -1) 1567end 1568 1569-- Also known as A_SetSprite(). 1570function _ssp(i, cliptype) 1571 check_sprite_idx(i) 1572 local spr = ffiC.sprite[i] 1573 local vec = spr.xvel * bangvec(spr.ang) -- XXX: slightly different rounding? 1574 local ivec = vec:toivec3() 1575 ivec.z = spr.zvel 1576 1577 return (ffiC.A_MoveSpriteClipdist(i, ivec, cliptype, -1)==0) 1578end 1579 1580-- CON's 'setsprite' function on top of the Lunatic-provided ones. 1581-- (Lunatic's sprite setting functions have slightly different semantics.) 1582local updatesect = sprite.updatesect 1583function _setsprite(i, pos) 1584 check_sprite_idx(i) 1585 local spr = ffiC.sprite[i] 1586 1587 -- First, unconditionally set the sprite's position. 1588 spr:setpos(pos) 1589 1590 -- Next, update the sector number, but if updatesector() returns -1, don't 1591 -- change it. (This is exactly what sprite.updatesect() provides.) 1592 updatesect(i) 1593end 1594 1595-- NOTE: returns two args (in C version, hit sprite is a pointer input arg) 1596local function A_CheckHitSprite(spr, angadd) 1597 local zoff = (spr:isenemy() and 42*256) or (ispic(spr.picnum, "APLAYER") and 39*256) or 0 1598 1599 local hit = hitscan(spr^zoff, spr.sectnum, kangvec(spr.ang+angadd), ffiC.CLIPMASK1) 1600 if (hit.wall >= 0 and wall[hit.wall]:ismasked() and spr:isenemy()) then 1601 return -1, nil 1602 end 1603 1604 return hit.sprite, ldist(hit.pos, spr) 1605end 1606 1607function _canshoottarget(dst, aci) 1608 if (dst > 1024) then 1609 local spr = sprite[aci] 1610 1611 local hitspr, hitdist = A_CheckHitSprite(spr, 0) 1612 if (hitdist == nil) then 1613 return true 1614 end 1615 1616 local bigenemy = (spr:isenemy() and spr.xrepeat > 56) 1617 1618 local sclip = bigenemy and 3084 or 768 1619 local angdif = bigenemy and 48 or 16 1620 1621 local sclips = { sclip, sclip, 768 } 1622 local angdifs = { 0, angdif, -angdif } 1623 1624 for i=1,3 do 1625 if (i > 1) then 1626 hitspr, hitdist = A_CheckHitSprite(spr, angdifs[i]) 1627 end 1628 1629 if (hitspr >= 0 and sprite[hitspr].picnum == spr.picnum) then 1630 if (hitdist > sclips[i]) then 1631 return false 1632 end 1633 end 1634 end 1635 end 1636 1637 return true 1638end 1639 1640function _getlastpal(spritenum) 1641 local spr = sprite[spritenum] 1642 if (ispic(spr.picnum, "APLAYER")) then 1643 local pidx = spr.yvel 1644 check_player_idx(pidx) 1645 spr.pal = player[pidx].palookup 1646 else 1647 if (spr.pal == 1 and spr.extra == 0) then -- hack for frozen 1648 spr.extra = spr.extra+1 1649 end 1650 spr.pal = actor[spritenum].tempang 1651 end 1652 actor[spritenum].tempang = 0 1653end 1654 1655-- G_GetAngleDelta(a1, a2) 1656function _angdiff(a1, a2) 1657 a1 = band(a1, 2047) 1658 a2 = band(a2, 2047) 1659 1660 if (abs(a2-a1) >= 1024) then 1661 if (a2 > 1024) then a2 = a2 - 2048 end 1662 if (a1 > 1024) then a1 = a1 - 2048 end 1663 end 1664 1665 -- a1, a2 are in [-1023, 1024] 1666 return a2-a1 1667end 1668 1669function _angdiffabs(a1, a2) 1670 return abs(_angdiff(a1, a2)) 1671end 1672 1673function _angtotarget(aci) 1674 local spr = sprite[aci] 1675 return ffiC.getangle(actor[aci].lastvx-spr.x, actor[aci].lastvy-spr.y) 1676end 1677 1678function _hypot(a, b) 1679 return math.sqrt(a*a + b*b) 1680end 1681 1682function _rotatepoint(pivotx, pivoty, posx, posy, ang) 1683 local pos = ivec3(posx, posy) 1684 local pivot = ivec3(pivotx, pivoty) 1685 pos = rotate(pos, ang, pivot):toivec3() 1686 return pos.x, pos.y 1687end 1688 1689local holdskey = player.holdskey 1690 1691function _ifp(flags, pli, aci) 1692 local l = flags 1693 local ps = player[pli] 1694 local vel = sprite[ps.i].xvel 1695 1696 if (band(l,8)~=0 and ps.on_ground and holdskey(pli, "CROUCH")) then 1697 return true 1698 elseif (band(l,16)~=0 and ps.jumping_counter == 0 and not ps.on_ground and ps.vel.z > 2048) then 1699 return true 1700 elseif (band(l,32)~=0 and ps.jumping_counter > 348) then 1701 return true 1702 elseif (band(l,1)~=0 and vel >= 0 and vel < 8) then 1703 return true 1704 elseif (band(l,2)~=0 and vel >= 8 and not holdskey(pli, "RUN")) then 1705 return true 1706 elseif (band(l,4)~=0 and vel >= 8 and holdskey(pli, "RUN")) then 1707 return true 1708 elseif (band(l,64)~=0 and ps.pos.z < (sprite[aci].z-(48*256))) then 1709 return true 1710 elseif (band(l,128)~=0 and vel <= -8 and not holdskey(pli, "RUN")) then 1711 return true 1712 elseif (band(l,256)~=0 and vel <= -8 and holdskey(pli, "RUN")) then 1713 return true 1714 elseif (band(l,512)~=0 and (ps.quick_kick > 0 or (ps.curr_weapon == 0 and ps.kickback_pic > 0))) then 1715 return true 1716 elseif (band(l,1024)~=0 and sprite[ps.i].xrepeat < 32) then 1717 return true 1718 elseif (band(l,2048)~=0 and ps.jetpack_on) then 1719 return true 1720 elseif (band(l,4096)~=0 and ps.inv_amount.STEROIDS > 0 and ps.inv_amount.STEROIDS < 400) then 1721 return true 1722 elseif (band(l,8192)~=0 and ps.on_ground) then 1723 return true 1724 elseif (band(l,16384)~=0 and sprite[ps.i].xrepeat > 32 and sprite[ps.i].extra > 0 and ps.timebeforeexit == 0) then 1725 return true 1726 elseif (band(l,32768)~=0 and sprite[ps.i].extra <= 0) then 1727 return true 1728 elseif (band(l,65536)~=0) then 1729 -- TODO_MP 1730 if (_angdiffabs(ps.ang, ffiC.getangle(sprite[aci].x-ps.pos.x, sprite[aci].y-ps.pos.y)) < 128) then 1731 return true 1732 end 1733 end 1734 1735 return false 1736end 1737 1738function _squished(aci, pli) 1739 check_sprite_idx(aci) 1740 check_player_idx(pli) 1741 check_sector_idx(sprite[aci].sectnum) 1742 1743 return (ffiC.VM_CheckSquished2(aci, pli)~=0) 1744end 1745 1746function _checkspace(sectnum, floorp) 1747 local sect = sector[sectnum] 1748 local picnum = floorp and sect.floorpicnum or sect.ceilingpicnum 1749 local stat = floorp and sect.floorstat or sect.ceilingstat 1750 return band(stat,1)~=0 and sect.ceilingpal == 0 and 1751 (ispic(picnum, "MOONSKY1") or ispic(picnum, "BIGORBIT1")) 1752end 1753 1754function _flash(spr, ps) 1755 spr.shade = -127 1756 ps.visibility = -127 -- NOTE: negative value not a problem anymore 1757end 1758 1759function _G_OperateRespawns(tag) 1760 for i in spritesofstat(STAT.FX) do 1761 local spr = sprite[i] 1762 1763 if (spr.lotag==tag and ispic(spr.picnum, "RESPAWN")) then 1764 if (ffiC.ud.monsters_off==0 or not isenemytile(spr.hitag)) then 1765 if (D.TRANSPORTERSTAR) then 1766 local j = spawn(D.TRANSPORTERSTAR, i) 1767 sprite[j].z = sprite[j].z - (32*256) 1768 end 1769 1770 -- Just a way to killit (see G_MoveFX(): RESPAWN__STATIC) 1771 spr.extra = 66-12 1772 end 1773 end 1774 end 1775end 1776 1777function _G_OperateMasterSwitches(tag) 1778 for i in spritesofstat(STAT.STANDABLE) do 1779 local spr = sprite[i] 1780 if (ispic(spr.picnum, "MASTERSWITCH") and spr.lotag==tag and spr.yvel==0) then 1781 spr:set_yvel(1) 1782 end 1783 end 1784end 1785 1786function _respawnhitag(spr) 1787 if (RESPAWN_USE_YVEL[spr.picnum]) then 1788 if (spr.yvel ~= 0) then 1789 _G_OperateRespawns(spr.yvel) 1790 end 1791 else 1792 _G_OperateRespawns(spr.hitag) 1793 end 1794end 1795 1796function _checkrespawn(spr) 1797 if (spr:isenemy()) then 1798 return (ffiC.ud.respawn_monsters~=0) 1799 end 1800 if (INVENTILE[spr.picnum]) then 1801 return (ffiC.ud.respawn_inventory~=0) 1802 end 1803 return (ffiC.ud.respawn_items~=0) 1804end 1805 1806-- SOUNDS 1807function _ianysound(aci) 1808 check_sprite_idx(aci) 1809 return (ffiC.A_CheckAnySoundPlaying(aci)~=0) 1810end 1811 1812function _sound(aci, sndidx) 1813 check_sprite_idx(aci) 1814 -- A_PlaySound() returns early if the sound index is oob, but IMO it's good 1815 -- style to throw an error instead of silently failing. 1816 check_sound_idx(sndidx) 1817 CF.A_PlaySound(sndidx, aci) 1818end 1819 1820-- NOTE: This command is really badly named in CON. It issues a sound that 1821-- emanates from the current player instead of being 'system-global'. 1822function _globalsound(pli, sndidx) 1823 -- TODO: conditional on coop, fake multimode 1824 if (pli==ffiC.screenpeek) then 1825 _sound(player[pli].i, sndidx) 1826 end 1827end 1828 1829-- This one unconditionally plays a session-wide sound. 1830function _screensound(sndidx) 1831 check_sound_idx(sndidx) 1832 CF.A_PlaySound(sndidx, -1) 1833end 1834 1835-- This is a macro for EDuke32 (game.h) 1836local function S_StopSound(sndidx) 1837 ffiC.S_StopEnvSound(sndidx, -1) 1838end 1839 1840function _soundplaying(aci, sndidx) 1841 if (aci ~= -1) then 1842 check_sprite_idx(aci) 1843 end 1844 check_sound_idx(sndidx) 1845 return (ffiC.S_CheckSoundPlaying(aci, sndidx) ~= 0) 1846end 1847 1848function _stopsound(aci, sndidx) 1849 -- XXX: This is weird: the checking is done wrt a sprite, but the sound not. 1850 -- NOTE: S_StopSound() stops sound <sndidx> that started playing most recently. 1851 if (_soundplaying(aci, sndidx)) then 1852 S_StopSound(sndidx) 1853 end 1854end 1855 1856function _stopactorsound(aci, sndidx) 1857 if (_soundplaying(aci, sndidx)) then 1858 ffiC.S_StopEnvSound(sndidx, aci) 1859 end 1860end 1861 1862function _soundonce(aci, sndidx) 1863 if (not _soundplaying(aci, sndidx)) then 1864 _sound(aci, sndidx) 1865 end 1866end 1867 1868function _stopallsounds(pli) 1869 if (ffiC.screenpeek==pli) then 1870 ffiC.S_StopAllSounds() 1871 end 1872end 1873 1874function _setactorsoundpitch(aci, sndidx, pitchoffset) 1875 check_sprite_idx(aci) 1876 check_sound_idx(sndidx) 1877 ffiC.S_ChangeSoundPitch(sndidx, aci, pitchoffset) 1878end 1879 1880function _starttrack(level) 1881 bcheck.level_idx(level) 1882 1883 if (ffiC.G_StartTrack(level) ~= 0) then 1884 -- Issue a 'soft error', not breaking the control flow. 1885 local errmsg = debug.traceback( 1886 format("null music for volume %d level %d", ffiC.ud.volume_number, level), 2) 1887 errmsg = lprivate.tweak_traceback_msg(errmsg) 1888 ffiC.El_OnError(errmsg) 1889 print("^10error: "..errmsg) 1890 end 1891end 1892 1893function _getmusicposition() 1894 return ffiC.S_GetMusicPosition() 1895end 1896 1897function _setmusicposition(position) 1898 ffiC.S_SetMusicPosition(position) 1899end 1900 1901function _startlevel(volume, level) 1902 bcheck.volume_idx(volume) 1903 bcheck.level_idx(level) 1904 1905 ffiC.ud.m_volume_number = volume 1906 ffiC.ud.volume_number = volume 1907 ffiC.ud.m_level_number = level 1908 ffiC.ud.level_number = level 1909 1910 ffiC.ud.display_bonus_screen = 0 1911 1912 -- TODO_MP 1913 player[0].gm = bor(player[0].gm, 0x00000008) -- MODE_EOL 1914end 1915 1916function _setaspect(viewingrange, yxaspect) 1917 if (viewingrange==0) then 1918 error('invalid argument #1: must be nonzero', 2) 1919 end 1920 if (yxaspect==0) then 1921 error('invalid argument #2: must be nonzero', 2) 1922 end 1923 1924 -- XXX: surely not all values are sane 1925 ffiC.setaspect(viewingrange, yxaspect) 1926end 1927 1928function _setgamepalette(pli, basepal) 1929 check_player_idx(pli) 1930 ffiC.P_SetGamePalette(ffiC.g_player_ps[pli], basepal, 2+16) 1931end 1932 1933-- Map state persistence. 1934function _savemapstate() 1935 ffiC.G_SaveMapState() 1936end 1937 1938function _loadmapstate() 1939 ffiC.G_RestoreMapState() 1940end 1941 1942function _clearmapstate(idx) 1943 bcheck.linear_map_idx(idx) 1944 ffiC.G_FreeMapState(idx) 1945end 1946 1947-- Gamevar persistence in the configuration file 1948 1949function _savegamevar(name, val) 1950 if (ffiC.ud.config.scripthandle < 0) then 1951 return 1952 end 1953 1954 assert(type(name)=="string") 1955 assert(type(val)=="number") 1956 1957 ffiC.SCRIPT_PutNumber(ffiC.ud.config.scripthandle, "Gamevars", name, 1958 val, 0, 0); 1959end 1960 1961function _readgamevar(name, ov) 1962 if (ffiC.ud.config.scripthandle < 0) then 1963 return ov 1964 end 1965 1966 assert(type(name)=="string") 1967 1968 local v = ffi.new("int32_t [1]") 1969 ffiC.SCRIPT_GetNumber(ffiC.ud.config.scripthandle, "Gamevars", name, v); 1970 -- NOTE: doesn't examine SCRIPT_GetNumber() return value and returns 0 if 1971 -- there was no such gamevar saved, like C-CON. 1972 return v[0] 1973end 1974 1975 1976--- Wrapper of kopen4load file functions in a Lua-like file API 1977-- TODO: move to common side? 1978 1979local kfile_mt = { 1980 __gc = function(self) 1981 self:close() 1982 end, 1983 1984 __index = { 1985 close = function(self) 1986 if (self.fd > 0) then 1987 ffiC.kclose(self.fd) 1988 self.fd = -1 1989 end 1990 end, 1991 1992 seek = function(self, whence, offset) 1993 local w = whence=="set" and 0 -- SEEK_SET 1994 or whence=="end" and 2 -- SEEK_END 1995 or error("invalid 'whence' for seek", 2) -- "cur" NYI 1996 1997 local pos = ffiC.klseek(self.fd, offset or 0, w) 1998 1999 if (pos >= 0) then 2000 return pos 2001 else 2002 return nil, "?" 2003 end 2004 end, 2005 2006 read = function(self, nbytes) 2007 assert(type(nbytes)=="number") -- other formats NYI 2008 assert(nbytes > 0) 2009 2010 local bytes = ffi.new("char [?]", nbytes) 2011 local bytesread = ffiC.kread(self.fd, bytes, nbytes) 2012 2013 if (bytesread ~= nbytes) then 2014 return nil 2015 end 2016 2017 return ffi.string(bytes, nbytes) 2018 end, 2019 2020 -- Read <nints> little-endian 32-bit integers. 2021 read_le_int32 = function(self, nints) 2022 local ints = ffi.new("int32_t [?]", nints) 2023 local bytesread = ffiC.kread(self.fd, ints, nints*4) 2024 2025 if (bytesread ~= nints*4) then 2026 return nil 2027 end 2028 2029 if (ffi.abi("be")) then 2030 for i=0,nints-1 do 2031 ints[i] = bit.bswap(ints[i]) 2032 end 2033 end 2034 2035 return ints 2036 end, 2037 }, 2038} 2039 2040local kfile_t = ffi.metatype("struct { int32_t fd; }", kfile_mt) 2041 2042local function kopen4load(fn, searchfirst) 2043 local fd = ffiC.kopen4load(fn, searchfirst) 2044 2045 if (fd < 0) then 2046 return nil, "no such file?" 2047 end 2048 2049 return kfile_t(fd) 2050end 2051 2052 2053local function serialize_value(strtab, i, v) 2054 -- Save only user values (i.e. not 'meta-fields' like '_size'). 2055 if (type(i)=="number" and v~=nil) then 2056 strtab[#strtab+1] = "["..i.."]="..tostring(v).."," 2057 end 2058end 2059 2060-- Common serialization function for gamearray and actorvar. 2061local function serialize_array(ar, strtab, maxnum, suffix) 2062 for i=0,maxnum-1 do 2063 serialize_value(strtab, i, rawget(ar, i)) 2064 end 2065 2066 strtab[#strtab+1] = "}"..(suffix or "")..")" 2067 2068 return table.concat(strtab) 2069end 2070 2071 2072--- Game arrays --- 2073 2074local function moddir_filename(cstr_fn) 2075 local fn = ffi.string(cstr_fn) 2076 local moddir = ffi.string(ffiC.g_modDir); 2077 2078 if (moddir=="/") then 2079 return fn 2080 else 2081 return format("%s/%s", moddir, fn) 2082 end 2083end 2084 2085local GAR_FOOTER = "\001\002EDuke32GameArray\003\004" 2086local GAR_FOOTER_SIZE = #GAR_FOOTER 2087 2088local function gamearray_file_common(qnum, writep) 2089 -- NOTE: suffix with '.gar' so that we can have C-CON and LunaCON gamearray 2090 -- files side-by-side. 2091 local fn = moddir_filename(bcheck.quote_idx(qnum))..".gar" 2092 local f, errmsg 2093 2094 if (writep) then 2095 f, errmsg = io.open(fn, "rb") 2096 if (f == nil) then 2097 -- file, numints, isnewgar, filename 2098 return nil, nil, true, fn 2099 end 2100 else 2101 f, errmsg = kopen4load(fn, 0) 2102 if (f == nil) then 2103 if (f==false) then 2104 error(format([[failed opening "%s" for reading: %s]], fn, errmsg), 3) 2105 else 2106 return 2107 end 2108 end 2109 end 2110 2111 local fsize = assert(f:seek("end")) 2112 2113 local isnewgar = false 2114 if (fsize >= GAR_FOOTER_SIZE) then 2115 assert(f:seek("end", -GAR_FOOTER_SIZE)) 2116 isnewgar = (assert(f:read(GAR_FOOTER_SIZE)) == GAR_FOOTER) 2117 if (isnewgar) then 2118 fsize = fsize - GAR_FOOTER_SIZE 2119 end 2120 end 2121 2122 return f, floor(fsize/4), isnewgar, fn 2123end 2124 2125local function check_gamearray_idx(gar, idx, addstr) 2126 -- If the actual table has no "_size" field, then we're dealing with a 2127 -- system gamearray: currently, only g_tile.sizx/sizy. 2128 local size = rawget(gar, '_size') or ffiC.MAXTILES 2129 2130 if (not (idx >= 0 and idx < size)) then 2131 addstr = addstr or "" 2132 error("invalid "..addstr.."array index "..idx, 3) 2133 end 2134end 2135 2136function _gar_copy(sar, sidx, dar, didx, numelts) 2137 -- XXX: Strictest bound checking, see later if we need to relax it. 2138 check_gamearray_idx(sar, sidx, "lower source ") 2139 check_gamearray_idx(sar, sidx+numelts-1, "upper source ") 2140 check_gamearray_idx(dar, didx, "lower destination ") 2141 check_gamearray_idx(dar, didx+numelts-1, "upper destination ") 2142 2143 -- Source is user gamearray? 2144 local sisuser = (rawget(sar, '_size') ~= nil) 2145 2146 for i=0,numelts-1 do 2147 local val = sisuser and rawget(sar, sidx+i) or sar[sidx+i] 2148 rawset(dar, didx+i, val) 2149 end 2150end 2151 2152local gamearray_methods = { 2153 resize = function(gar, newsize) 2154 -- NOTE: size 0 is valid (then, no index is valid) 2155 if (newsize < 0) then 2156 error("invalid new array size "..newsize, 2) 2157 end 2158 2159 local MAXELTS = floor(0x7fffffff/4) 2160 if (newsize > MAXELTS) then 2161 -- mainly for some sanity with kread() (which we don't use, but still) 2162 error("new array size "..newsize.." too large (max="..MAXELTS.." elements)", 2) 2163 end 2164 2165 -- clear trailing elements in case we're shrinking 2166 for i=gar._size,newsize-1 do 2167 rawset(gar, i, nil) 2168 end 2169 2170 gar._size = newsize 2171 end, 2172 2173 read = function(gar, qnum) 2174 local f, nelts, isnewgar = gamearray_file_common(qnum, false) 2175 2176 if (f==nil) then 2177 return 2178 end 2179 2180 assert(f:seek("set")) 2181 local ints = f:read_le_int32(nelts) 2182 if (ints == nil) then 2183 error("failed reading whole file into gamearray", 2) 2184 end 2185 2186 gar:resize(nelts) 2187 2188 for i=0,nelts-1 do 2189 rawset(gar, i, (ints[i]==0) and nil or ints[i]) 2190 end 2191 2192 f:close() 2193 end, 2194 2195 write = function(gar, qnum) 2196 local f, _, isnewgar, fn = gamearray_file_common(qnum, true) 2197 2198 if (f ~= nil) then 2199 f:close() 2200 end 2201 2202 if (not isnewgar) then 2203 error("refusing to overwrite a file not created by a previous `writearraytofile'", 2) 2204 end 2205 2206 local f, errmsg = io.open(fn, "wb+") 2207 if (f == nil) then 2208 error([[failed opening "%s" for writing: %s]], fn, errmsg, 3) 2209 end 2210 2211 local nelts = gar._size 2212 local ar = ffi.new("int32_t [?]", nelts) 2213 local isbe = ffi.abi("be") -- is big-endian? 2214 2215 for i=0,nelts-1 do 2216 ar[i] = isbe and bit.bswap(gar[i]) or gar[i] 2217 end 2218 2219 local ok = (ffiC.fwrite(ar, 4, nelts, f) == nelts) 2220 if (ok) then 2221 f:write(GAR_FOOTER) 2222 end 2223 2224 f:close() 2225 2226 if (not ok) then 2227 error([[failed writing all data to "%s"]], fn, 3) 2228 end 2229 end, 2230 2231 2232 --- Internal routines --- 2233 2234 -- * All values equal to the default one (0) are cleared. 2235 _cleanup = function(gar) 2236 for i=0,gar._size-1 do 2237 if (rawget(gar, i)==0) then 2238 rawset(gar, i, nil) 2239 end 2240 end 2241 end, 2242 2243 2244 --- Serialization --- 2245 _get_require = our_get_require, 2246 2247 _serialize = function(gar) 2248 gar:_cleanup() 2249 local strtab = { "_ga(", tostring(gar._size), ",{" } 2250 return serialize_array(gar, strtab, gar._size) 2251 end, 2252} 2253 2254local gamearray_mt = { 2255 __index = function(gar, key) 2256 if (type(key)=="number") then 2257 check_gamearray_idx(gar, key) 2258 return 0 2259 else 2260 return gamearray_methods[key] 2261 end 2262 end, 2263 2264 __newindex = function(gar, idx, val) 2265 check_gamearray_idx(gar, idx) 2266 rawset(gar, idx, val) 2267 end, 2268 2269 __metatable = "serializeable", 2270} 2271 2272-- Common constructor helper for gamearray and actorvar. 2273local function set_values_from_table(ar, values) 2274 if (values ~= nil) then 2275 for i,v in pairs(values) do 2276 ar[i] = v 2277 end 2278 end 2279 return ar 2280end 2281 2282-- NOTE: Gamearrays are internal because users are encouraged to use tables 2283-- from Lua code. 2284-- <values>: optional, a table of <index>=value 2285function _gamearray(size, values) 2286 local gar = setmetatable({ _size=size }, gamearray_mt) 2287 return set_values_from_table(gar, values) 2288end 2289 2290 2291--- More functions of the official API --- 2292 2293-- Non-local control flow. These ones call the original error(), not our 2294-- redefinition in _defs_game.lua. 2295function longjmp() 2296 error(false) 2297end 2298 2299function killit() 2300 -- TODO: guard against deletion of player sprite? 2301 error(true) 2302end 2303 2304 2305--== Per-actor variable ==-- 2306local perxvar_allowed_types = { 2307 ["boolean"]=true, ["number"]=true, 2308} 2309 2310local function check_perxval_type(val) 2311 if (perxvar_allowed_types[type(val)] == nil) then 2312 error("type forbidden as per-* variable value: "..type(val), 3) 2313 end 2314end 2315 2316local actorvar_methods = { 2317 --- Internal routines --- 2318 2319 -- * All values for sprites not in the game world are cleared (non-NODEFAULT only). 2320 -- * All values equal to the default one are cleared. 2321 _cleanup = function(acv) 2322 for i=0,ffiC.MAXSPRITES-1 do 2323 -- NOTE: NODEFAULT per-actor gamevars are used in a non-actor fashion 2324 if ((not acv:_is_nodefault() and ffiC.sprite[i].statnum == ffiC.MAXSTATUS) 2325 or rawget(acv, i)==acv._defval) then 2326 acv:_clear(i) 2327 end 2328 end 2329 end, 2330 2331 _clear = function(acv, i) 2332 rawset(acv, i, nil) 2333 end, 2334 2335 _is_nodefault = function(acv, i) 2336 return rawget(acv, '_nodefault') 2337 end, 2338 2339 2340 --- Serialization --- 2341 _get_require = our_get_require, 2342 2343 _serialize = function(acv) 2344 -- NOTE: We also clean up when spawning a sprite, too. (See 2345 -- A_ResetVars() and related functions above.) 2346 acv:_cleanup() 2347 local strtab = { "_av(", tostring(acv._defval), ",{" } 2348 return serialize_array(acv, strtab, ffiC.MAXSPRITES, 2349 acv:_is_nodefault() and ",true") 2350 end, 2351} 2352 2353local actorvar_mt = { 2354 __index = function(acv, idx) 2355 if (type(idx)=="number") then 2356 check_sprite_idx(idx) 2357 return acv._defval 2358 else 2359 return actorvar_methods[idx] 2360 end 2361 end, 2362 2363 __newindex = function(acv, idx, val) 2364 check_sprite_idx(idx) 2365 check_perxval_type(val) 2366 rawset(acv, idx, val) 2367 end, 2368 2369 __metatable = "serializeable", 2370} 2371 2372-- <initval>: default value for per-actor variable. 2373-- <values>: optional, a table of <spritenum>=value 2374function actorvar(initval, values, nodefault) 2375 check_perxval_type(initval) 2376 local acv = setmetatable({ _defval=initval, _nodefault=nodefault }, actorvar_mt) 2377 if (not nodefault) then 2378 g_actorvar[acv] = true 2379 end 2380 return set_values_from_table(acv, values) 2381end 2382 2383 2384--== Per-player variable (kind of CODEDUP) ==-- 2385local playervar_methods = { 2386 --- Serialization --- 2387 _get_require = our_get_require, 2388 2389 _serialize = function(plv) 2390 local strtab = { "_pv(", tostring(plv._defval), ",{" } 2391 return serialize_array(plv, strtab, ffiC.MAXSPRITES) 2392 end, 2393} 2394 2395local playervar_mt = { 2396 __index = function(plv, idx) 2397 if (type(idx)=="number") then 2398 check_player_idx(idx) 2399 return plv._defval 2400 else 2401 return playervar_methods[idx] 2402 end 2403 end, 2404 2405 __newindex = function(plv, idx, val) 2406 check_player_idx(idx) 2407 check_perxval_type(val) 2408 rawset(plv, idx, val) 2409 end, 2410 2411 __metatable = "serializeable", 2412} 2413 2414-- <initval>: default value for per-player variable. 2415-- <values>: optional, a table of <playeridx>=value 2416function playervar(initval, values) 2417 check_perxval_type(initval) 2418 local plv = setmetatable({ _defval=initval }, playervar_mt) 2419 return set_values_from_table(plv, values) 2420end 2421