Subversion Repositories eduke32

Rev

Rev 4972 | Details | Compare with Previous | Last modification | View Log | RSS feed

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