Subversion Repositories eduke32

Rev

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

Rev Author Line No. Line
2594 helixhorne 1
-- LunaCON CON to Lunatic translator
2
-- requires LPeg, http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html
3
 
3256 helixhorne 4
local require = require
2594 helixhorne 5
local lpeg = require("lpeg")
6
 
3357 helixhorne 7
local bit
3256 helixhorne 8
local math = require("math")
9
local string = require("string")
10
local table = require("table")
11
 
3259 helixhorne 12
 
3256 helixhorne 13
local arg = arg
14
 
15
local assert = assert
3854 helixhorne 16
local error = error
3388 helixhorne 17
local ipairs = ipairs
3523 helixhorne 18
local loadstring = loadstring
3256 helixhorne 19
local pairs = pairs
20
local pcall = pcall
21
local print = print
3535 helixhorne 22
local setmetatable = setmetatable
3256 helixhorne 23
local tonumber = tonumber
3315 helixhorne 24
local tostring = tostring
3256 helixhorne 25
local type = type
3324 helixhorne 26
local unpack = unpack
3256 helixhorne 27
 
3343 helixhorne 28
-- non-nil if running from EDuke32
29
-- (read_into_string~=nil  iff  string.dump==nil)
30
local read_into_string = read_into_string
3355 helixhorne 31
local ffi, ffiC
3343 helixhorne 32
 
3533 helixhorne 33
if (string.dump) then  -- running stand-alone
34
    local ljp = pcall(function() require("ffi") end)
35
    -- "lbit" is the same module as LuaJIT's "bit" (LuaBitOp:
36
    -- http://bitop.luajit.org/), but under a different name for (IMO) less
37
    -- confusion. Useful for running with Rio Lua for cross-checking.
38
    bit = ljp and require("bit") or require("lbit")
2762 helixhorne 39
    require("strict")
3355 helixhorne 40
else
3357 helixhorne 41
    bit = require("bit")
3355 helixhorne 42
    ffi = require("ffi")
43
    ffiC = ffi.C
2762 helixhorne 44
end
2616 helixhorne 45
 
2762 helixhorne 46
 
3805 helixhorne 47
 
3315 helixhorne 48
module("lunacon")
3256 helixhorne 49
 
50
 
2762 helixhorne 51
-- I think that the "too many pending calls/choices" is unavoidable in general.
52
-- This limit is of course still arbitrary, but writing long if/else cascades
2616 helixhorne 53
-- in CON isn't pretty either (though sometimes necessary because nested switches
54
-- don't work?)
55
-- See also:  http://lua-users.org/lists/lua-l/2010-03/msg00086.html
56
lpeg.setmaxstack(1024);
57
 
58
 
2594 helixhorne 59
local Pat, Set, Range, Var = lpeg.P, lpeg.S, lpeg.R, lpeg.V
3534 helixhorne 60
local POS, Cc, Ctab = lpeg.Cp, lpeg.Cc, lpeg.Ct
2594 helixhorne 61
 
3375 helixhorne 62
-- CON language definitions (among other things, all keywords pattern).
3246 helixhorne 63
local conl = require("con_lang")
2594 helixhorne 64
 
65
 
66
local function match_until(matchsp, untilsp)  -- (!untilsp matchsp)* in PEG
67
    -- sp: string or pattern
68
    return (matchsp - Pat(untilsp))^0
69
end
70
 
3881 helixhorne 71
local format = string.format
3805 helixhorne 72
--[[
3881 helixhorne 73
format = function(fmt, ...)
3805 helixhorne 74
    local ok, res = pcall(string.format, fmt, ...)
75
    if (not ok) then
76
        error(string.format("FAILED format(%q, ...) | message: %s", fmt, res))
77
    end
78
    return res
79
end
80
--]]
3246 helixhorne 81
 
2748 helixhorne 82
local function printf(fmt, ...)
3246 helixhorne 83
    print(format(fmt, ...))
2748 helixhorne 84
end
2594 helixhorne 85
 
3944 helixhorne 86
--- some constants
2748 helixhorne 87
 
3944 helixhorne 88
local C = {
89
    -- These two are not used except for predefined labels.
90
    -- NOTE: in-game, MAXSPRITES may be 4096 for a V7 build!
91
    MAXSTATUS = ffiC and ffiC.MAXSTATUS or 1024,
92
    MAXSPRITES = ffiC and ffiC.MAXSPRITES or 16384,
93
 
94
    MAXTILES = ffiC and ffiC.MAXTILES or 30720,
95
    MAX_WEAPONS = ffiC and ffiC.MAX_WEAPONS or 12,
96
}
97
 
2748 helixhorne 98
---=== semantic action functions ===---
99
 
2749 helixhorne 100
local inf = 1/0
2762 helixhorne 101
local NaN = 0/0
2749 helixhorne 102
 
103
-- Last keyword position, for error diagnosis.
104
local g_lastkwpos = nil
105
local g_lastkw = nil
106
local g_badids = {}  -- maps bad id strings to 'true'
107
 
108
local g_recurslevel = -1  -- 0: base CON file, >0 included
2756 helixhorne 109
local g_filename = "???"
2749 helixhorne 110
local g_directory = ""  -- with trailing slash if not empty
3256 helixhorne 111
local g_maxerrors = 20
2749 helixhorne 112
local g_numerrors = 0
113
 
3439 helixhorne 114
-- Default directory to search for GAME.CON etc.
115
-- Stand-alone LunaCON only.
116
local g_defaultDir = nil
117
 
3255 helixhorne 118
-- Warning options. Key names are the same as cmdline options, e.g.
119
-- -Wno-bad-identifier for disabling the "bad identifier" warning.
3569 helixhorne 120
local g_warn = { ["not-redefined"]=true, ["bad-identifier"]=false,
3570 helixhorne 121
                 ["number-conversion"]=true, ["system-gamevar"]=true,
4299 helixhorne 122
                 ["error-bad-getactorvar"]=false, ["chained-loadactor"]=true,
123
                 ["never-used-gamevar"]=false, ["never-read-gamevar"]=false, }
3255 helixhorne 124
 
3540 helixhorne 125
-- Code generation and output options.
3561 helixhorne 126
local g_cgopt = { ["no"]=false, ["debug-lineinfo"]=false, ["gendir"]=nil,
3842 helixhorne 127
                  ["cache-sap"]=false, ["error-nostate"]=true,
4266 helixhorne 128
                  ["playervar"]=true, ["trapv"]=false, ["wrapv"]=false,
4290 helixhorne 129
                  ["bad-getactorvar-use-pli"]=false,
4356 helixhorne 130
                  ["error-nonlocal-userdef"]=true,
131
                  ["error-negative-tag-write"]=false, }
3842 helixhorne 132
 
3561 helixhorne 133
local function csapp() return g_cgopt["cache-sap"] end
3392 helixhorne 134
 
3949 helixhorne 135
local function handle_cmdline_arg(str)
136
    if (str:sub(1,1)=="-") then
137
        if (#str == 1) then
138
            printf("Warning: input from stdin not supported")
139
        else
140
            local ok = false
141
            local kind = str:sub(2,2)
142
 
143
            -- -W(no-)*: warnings
144
            if (kind=="W" and #str >= 3) then
145
                local val = true
146
                local warnstr = str:sub(3)
147
 
148
                if (warnstr == "all") then
149
                    -- Enable all warnings.
150
                    for wopt in pairs(g_warn) do
151
                        g_warn[wopt] = true
152
                    end
153
                    ok = true
154
                else
155
                    -- Enable or disable a particular warning.
156
                    if (warnstr:sub(1,3)=="no-") then
157
                        val = false
158
                        warnstr = warnstr:sub(4)
159
                    end
160
 
161
                    if (type(g_warn[warnstr])=="boolean") then
162
                        g_warn[warnstr] = val
163
                        ok = true
164
                    end
165
                end
166
 
167
            -- -fno* special handling
168
            elseif (str:sub(2)=="fno") then
169
                -- Disable printing code entirely.
170
                g_cgopt["no"] = true
171
                ok = true
172
            elseif (str:sub(2)=="fno=onlycheck") then
173
                -- Disable printing code, only do syntax check of gen'd code.
174
                g_cgopt["no"] = "onlycheck"
175
                ok = true
176
 
177
            -- -fgendir=<directory>: specify directory for generated code
178
            elseif (str:sub(2,9)=="fgendir=" and #str >= 10) then
179
                g_cgopt["gendir"] = str:sub(10)
180
                ok = true
181
 
182
            -- -f(no-)*: code generation options
183
            elseif (kind=="f" and #str >= 3) then
184
                local val = true
185
                local cgstr = str:sub(3)
186
 
187
                if (cgstr:sub(1,3)=="no-") then
188
                    val = false
189
                    cgstr = cgstr:sub(4)
190
                end
191
 
192
                if (type(g_cgopt[cgstr])=="boolean") then
193
                    g_cgopt[cgstr] = val
194
                    ok = true
195
                end
196
 
197
            -- -I<directory>: default search directory (only ONCE, not search path)
198
            elseif (kind=="I" and #str >= 3) then
199
                g_defaultDir = str:sub(3)
200
                ok = true
201
            end
202
 
203
            if (not ffi and not ok) then
204
                printf("Warning: Unrecognized option %s", str)
205
            end
206
        end
207
 
208
        return true
209
    end
210
end
211
 
212
-- Handle command line arguments. Has to happen before pattern construction,
213
-- because some of them depend on codegen options (specifically, -ftrapv,
214
-- -fwrapv).
215
if (string.dump) then
216
    -- running stand-alone
217
    local i = 1
218
    while (arg[i]) do
219
        if (handle_cmdline_arg(arg[i])) then
220
            table.remove(arg, i)  -- remove processed cmdline arg
221
        else
222
            i = i+1
223
        end
224
    end
225
else
226
    -- running from EDuke32
227
    local i=0
228
    while (ffiC.g_argv[i] ~= nil) do
229
        handle_cmdline_arg(ffi.string(ffiC.g_argv[i]))
230
        i = i+1
231
    end
232
end
233
 
4356 helixhorne 234
if (g_cgopt["error-negative-tag-write"]) then
235
    conl.setup_negative_tag_check("_st")
236
end
237
 
3529 helixhorne 238
-- Stack with *true* on top if the innermost block is a "whilevar*n".
239
local g_isWhile = {}
240
-- Sequence number of 'while' statements, used to implement CON "break" inside
241
-- whilevar*n, which really behaves like what sane languages call "continue"...
242
local g_whilenum = 0
2756 helixhorne 243
 
2792 helixhorne 244
---=== Code generation ===---
3392 helixhorne 245
local GVFLAG = {
246
    PERPLAYER=1, PERACTOR=2, PERX_MASK=3,
247
    SYSTEM   = 0x00000800,
248
    READONLY = 0x00001000,
3431 helixhorne 249
 
250
    NODEFAULT = 0x00000400,  -- don't reset on actor spawn
251
    NORESET   = 0x00020000,  -- don't reset when restoring map state
3842 helixhorne 252
 
253
    CON_PERPLAYER = 0x40000000,  -- LunaCON internal
3392 helixhorne 254
}
3390 helixhorne 255
 
3431 helixhorne 256
-- NOTE: This differs from enum GamevarFlags_t's GAMEVAR_USER_MASK
257
GVFLAG.USER_MASK = GVFLAG.PERX_MASK + GVFLAG.NODEFAULT + GVFLAG.NORESET
258
 
3375 helixhorne 259
-- CON --> mangled Lua function name, also existence check:
260
local g_funcname = {}
3513 helixhorne 261
-- while parsing a block, it is a table of "gencode" tables:
262
local g_switchCode = nil
263
-- Global number of switch statements:
264
local g_switchCount = 0
4112 helixhorne 265
-- Number of session gamevars:
266
local g_numSessionVars = 0
3392 helixhorne 267
-- [identifier] = { name=<mangled name / code>, flags=<gamevar flags> }
3390 helixhorne 268
local g_gamevar = {}
3503 helixhorne 269
-- [identifier] = { name=<mangled name / code>, size=<initial size> }
270
local g_gamearray = {}
3375 helixhorne 271
 
3568 helixhorne 272
-- * nil if dynamic tile remapping disabled
3847 helixhorne 273
-- * {} if enabled but no remappings made
274
-- * else, a nonempty table { [name]=<g_dynTileList index> }
3568 helixhorne 275
local g_dyntilei = nil
3847 helixhorne 276
-- Analogously for sounds.
277
local g_dynsoundi = nil
3568 helixhorne 278
 
3325 helixhorne 279
local g_have_file = {}  -- [filename]=true
2792 helixhorne 280
local g_curcode = nil  -- a table of string pieces or other "gencode" tables
281
 
4152 helixhorne 282
-- will be a table, see reset.codegen()
3515 helixhorne 283
local g_code = nil
2792 helixhorne 284
 
285
 
3561 helixhorne 286
local function ACS(s) return (csapp() and "_a" or "actor[_aci]")..s end
287
local function SPS(s) return (csapp() and "_spr" or "sprite[_aci]")..s end
288
local function PLS(s) return (csapp() and "_ps" or "player[_pli]")..s end
289
local function PLSX(s) return "player[_pli]"..s end
3392 helixhorne 290
 
291
 
2748 helixhorne 292
local function getlinecol(pos) end -- fwd-decl
293
 
3325 helixhorne 294
local function new_initial_codetab()
3574 helixhorne 295
    -- NOTE: Keep this one line per line to not confuse the Lua->CON line
296
    -- mapping system.
3325 helixhorne 297
    return {
3646 helixhorne 298
        -- Requires.
3891 helixhorne 299
        "local require=require",
3646 helixhorne 300
        "local _con, _bit, _math = require'con', require'bit', require'math'",
3909 helixhorne 301
        "local _xmath = require'xmath'",
3513 helixhorne 302
 
3646 helixhorne 303
        -- Cache globals into locals.
3937 helixhorne 304
        "local sector, sprite, wall, spriteext, _atsprite = sector, sprite, wall, spriteext, _atsprite",
3821 helixhorne 305
        "local actor, player, projectile, g_tile = actor, player, projectile, g_tile",
3646 helixhorne 306
        "local gameactor, gameevent, _gv = gameactor, gameevent, gv",
307
        "local updatesector, updatesectorz, cansee = updatesector, updatesectorz, cansee",
308
        "local print, printf = print, printf",
309
 
310
        -- Cache a couple of often-used functions.
3949 helixhorne 311
        "local _div, _mod, _mulTR, _mulWR = _con._div, _con._mod, _con._mulTR, _con._mulWR",
3646 helixhorne 312
        "local _band, _bor, _bxor = _bit.band, _bit.bor, _bit.bxor",
313
        "local _lsh, _rsh, _arsh = _bit.lshift, _bit.rshift, _bit.arshift",
4149 helixhorne 314
        "local _setsprite,_ssp = _con._setsprite,_con._ssp",
4290 helixhorne 315
        g_cgopt["error-nonlocal-userdef"]
316
            and "local _gud=_con._get_userdef_check" or "local _gud=_con._get_userdef",
4356 helixhorne 317
        "local _st=_con._err_if_negative",
3646 helixhorne 318
 
3891 helixhorne 319
        -- * CON "states" (subroutines) and
320
        -- * Switch function table, indexed by global switch sequence number:
321
        "local _F,_SW = {},{}",
3558 helixhorne 322
 
3891 helixhorne 323
        -- CON gamevars and gamearrays (see mangle_name()), set up for
324
        -- restoration from savegames.
325
        "module(...)",
326
        "_V,_A={},{}",
327
        "-- NOTE to the reader: This require's result is Lunatic-private API! DO NOT USE!",
328
        "local _dummy,_S=require'end_gamevars'",
4119 helixhorne 329
-- XXX: Currently commented out because of gamevar restoration from loadmapstate.
330
--        "local _V,_A=_V,_A",
3916 helixhorne 331
        "local _C,_M,_I={},{},{}",  -- actions, moves, ais
3891 helixhorne 332
 
3558 helixhorne 333
        -- Static ivec3s so that no allocations need to be made.
3909 helixhorne 334
        "local _IVEC = { _xmath.ivec3(), _xmath.ivec3() }",
3574 helixhorne 335
        "local function _IV(num, x, y, z)",
336
        "  local v=_IVEC[num]; v.x=x; v.y=y; v.z=z; return v;",
337
        "end",
3325 helixhorne 338
           }
339
end
340
 
3480 helixhorne 341
-- CON global system gamevar
3489 helixhorne 342
local function CSV(var) return "_gv._csv"..var end
3466 helixhorne 343
 
3392 helixhorne 344
-- Creates the table of predefined game variables.
345
-- KEEPINSYNC gamevars.c: Gv_AddSystemVars()
346
local function new_initial_gvartab()
347
    local wmembers = conl.wdata_members
348
 
3419 helixhorne 349
    local function GamevarCreationFunc(addflags)
350
        return function(varname)
4299 helixhorne 351
            -- 'used' is a bitmask: 1 is 'was read', 2 is 'was written to'
352
            return { name=varname, flags=GVFLAG.SYSTEM+addflags, used=3 }
3419 helixhorne 353
        end
3406 helixhorne 354
    end
355
 
3419 helixhorne 356
    local RW = GamevarCreationFunc(0)
357
    local RO = GamevarCreationFunc(GVFLAG.READONLY)
358
    local PRW = GamevarCreationFunc(GVFLAG.PERPLAYER)
359
    local PRO = GamevarCreationFunc(GVFLAG.READONLY+GVFLAG.PERPLAYER)
3406 helixhorne 360
 
361
    local gamevar = {
3516 helixhorne 362
        -- NOTE: THISACTOR can mean different things in some contexts.
3406 helixhorne 363
        THISACTOR = RO "_aci",
364
 
4031 helixhorne 365
        RETURN = RW "_gv.RETURN",
3480 helixhorne 366
        HITAG = RW(CSV".HITAG"),
367
        LOTAG = RW(CSV".LOTAG"),
368
        TEXTURE = RW(CSV".TEXTURE"),
3406 helixhorne 369
 
3556 helixhorne 370
        -- This will warn when defining from CON, but it's the most
371
        -- straightforward implementation.
372
        LOGO_FLAGS = RW "_gv.g_logoFlags",
373
 
3406 helixhorne 374
        xdim = RO "_gv.xdim",
375
        ydim = RO "_gv.ydim",
3409 helixhorne 376
        windowx1 = RO "_gv.windowx1",
377
        windowy1 = RO "_gv.windowy1",
378
        windowx2 = RO "_gv.windowx2",
379
        windowy2 = RO "_gv.windowy2",
3406 helixhorne 380
 
3475 helixhorne 381
        yxaspect = RO "_gv._get_yxaspect()",
3480 helixhorne 382
        viewingrange = RO "_gv._get_viewingrange()",
3964 helixhorne 383
        -- TODO: gravitationalconstant, gametype_flags
3475 helixhorne 384
 
3406 helixhorne 385
        numsectors = RO "_gv.numsectors",
386
        NUMSECTORS = RO "_gv.numsectors",
387
        NUMWALLS = RO "_gv.numwalls",
4475 helixhorne 388
        Numsprites = RO "_gv.Numsprites",
3406 helixhorne 389
 
3409 helixhorne 390
        randomseed = RW "_gv.randomseed",
391
        totalclock = RO "_gv.totalclock",
3431 helixhorne 392
        framerate = RO "_gv._currentFramerate()",
393
        current_menu = RO "_gv._currentMenu()",
3928 helixhorne 394
        rendmode = RO "_gv.rendmode",
3409 helixhorne 395
 
396
        screenpeek = RO "_gv.screenpeek",
397
 
3406 helixhorne 398
        camerax = RW "_gv.cam.pos.x",
399
        cameray = RW "_gv.cam.pos.y",
400
        cameraz = RW "_gv.cam.pos.z",
401
        cameraang = RW "_gv.cam.ang",
402
        camerahoriz = RW "_gv.cam.horiz",
403
        camerasect = RW "_gv.cam.sect",
404
        cameradist = RW "_gv.cam.dist",
405
        cameraclock = RW "_gv.cam.clock",
3409 helixhorne 406
 
407
        -- HUD weapon gamevars
408
        currentweapon = RW "_gv.hudweap.cur",
409
        weaponcount = RW "_gv.hudweap.count",
410
        weapon_xoffset = RW "_gv.hudweap.gunposx",
411
        looking_angSR1 = RW "_gv.hudweap.lookhalfang",
412
        gun_pos = RW "_gv.hudweap.gunposy",
413
        looking_arc = RW "_gv.hudweap.lookhoriz",
414
        gs = RW "_gv.hudweap.shade",
415
 
416
        -- Some per-player gamevars
3561 helixhorne 417
        ZRANGE = PRW(PLSX".zrange"),
418
        ANGRANGE = PRW(PLSX".angrange"),
419
        AUTOAIMANGLE = PRW(PLSX".autoaimang"),
3409 helixhorne 420
 
3561 helixhorne 421
        PIPEBOMB_CONTROL = PRW(PLSX".pipebombControl"),
422
        GRENADE_LIFETIME = PRW(PLSX".pipebombLifetime"),
423
        GRENADE_LIFETIME_VAR = PRW(PLSX".pipebombLifetimeVar"),
424
        TRIPBOMB_CONTROL = PRW(PLSX".tripbombControl"),
425
        STICKYBOMB_LIFETIME = PRW(PLSX".tripbombLifetime"),
426
        STICKYBOMB_LIFETIME_VAR = PRW(PLSX".tripbombLifetimeVar"),
3409 helixhorne 427
 
3817 helixhorne 428
        -- Some *writable* system gamevars relating to multiplayer.
429
        -- TODO_MP.
430
        RESPAWN_MONSTERS = RO "0",
431
        RESPAWN_ITEMS = RO "0",
432
        RESPAWN_INVENTORY = RO "0",
433
        MONSTERS_OFF = RO "0",
434
        MARKER = RO "0",
435
 
3415 helixhorne 436
        -- These are not 100% authentic (they're only updated in certain
437
        -- circumstances, see player.c: P_SetWeaponGamevars()). But IMO it's
438
        -- more useful like this.
3561 helixhorne 439
        WEAPON = PRO(PLSX".curr_weapon"),
440
        WORKSLIKE = PRO(format(PLSX".weapon[%s].workslike", PLSX".curr_weapon")),
3415 helixhorne 441
 
4260 helixhorne 442
        VOLUME = RO "_gv._ud.volume_number",
443
        LEVEL = RO "_gv._ud.level_number",
3406 helixhorne 444
    }
445
 
3563 helixhorne 446
    -- Reserved bits
4567 helixhorne 447
    gamevar.LOGO_FLAGS.rbits = bit.bnot(0x001fffff)
3563 helixhorne 448
 
3944 helixhorne 449
    for w=0,C.MAX_WEAPONS-1 do
3392 helixhorne 450
        for i=1,#wmembers do
3574 helixhorne 451
            local member = wmembers[i]:gsub(".*_t ","")  -- strip e.g. "const int32_t "
452
                                      :gsub("^_","")  -- strip potentially leading underscore
3392 helixhorne 453
            local name = format("WEAPON%d_%s", w, member:upper())
3561 helixhorne 454
            gamevar[name] = PRW(format(PLSX".weapon[%d].%s", w, member))
3563 helixhorne 455
 
456
            if (member=="flags") then
457
                gamevar[name].rbits = bit.bnot(0x1ffff)
458
            end
3392 helixhorne 459
        end
460
    end
461
 
462
    return gamevar
463
end
464
 
4152 helixhorne 465
local reset = {}
466
 
467
function reset.codegen()
3375 helixhorne 468
    g_funcname = {}
3513 helixhorne 469
    g_switchCode = nil
470
    g_switchCount = 0
4112 helixhorne 471
    g_numSessionVars = 0
3392 helixhorne 472
    g_gamevar = new_initial_gvartab()
3562 helixhorne 473
    g_gamearray = {
3940 helixhorne 474
        -- SYSTEM_GAMEARRAY
3944 helixhorne 475
        tilesizx = { name="g_tile.sizx", size=C.MAXTILES, sysp=true },
476
        tilesizy = { name="g_tile.sizy", size=C.MAXTILES, sysp=true },
3562 helixhorne 477
    }
3375 helixhorne 478
 
3568 helixhorne 479
    g_dyntilei = nil
3847 helixhorne 480
    g_dynsoundi = nil
3568 helixhorne 481
 
3325 helixhorne 482
    g_have_file = {}
483
    g_curcode = new_initial_codetab()
4373 helixhorne 484
    -- actor, event, loadactor: [{actor, event, actor}num] = gencode_table
485
    --
486
    -- aflagsloc[actornum]: location of '(user)actor' token, 'spriteflags' or
487
    -- 'sprite*' command; result of getLocation(<kind>, <pos>)
488
    g_code = { actor={}, event={}, loadactor={}, aflagsloc={} }
3325 helixhorne 489
 
490
    g_recurslevel = -1
491
    g_numerrors = 0
2792 helixhorne 492
end
493
 
3940 helixhorne 494
-- Is SYSTEM_GAMEARRAY?
495
local function issysgar(str)
496
    return str:match("^g_tile.siz[xy]")
497
end
498
 
2792 helixhorne 499
local function addcode(x)
3246 helixhorne 500
    assert(type(x)=="string" or type(x)=="table")
2792 helixhorne 501
    g_curcode[#g_curcode+1] = x
502
end
503
 
504
local function addcodef(fmt, ...)
3246 helixhorne 505
    addcode(format(fmt, ...))
2792 helixhorne 506
end
507
 
3604 helixhorne 508
local function paddcodef(pos, fmt, ...)
509
    addcodef(fmt.."--"..getlinecol(pos), ...)
510
end
511
 
3373 helixhorne 512
local function add_code_and_end(codetab, endstr)
513
    assert(type(codetab)=="table")
514
    addcode(codetab)
515
    addcode(endstr)
516
end
517
 
3561 helixhorne 518
local function get_cache_sap_code()
519
    return csapp() and "local _spr,_a,_ps=_con._getsap(_aci,_pli)" or ""
520
end
521
 
3604 helixhorne 522
-- fwd-decls
4151 helixhorne 523
local warnprintf, errprintf, pwarnprintf, perrprintf, contprintf
4373 helixhorne 524
local getLocation
3515 helixhorne 525
 
3513 helixhorne 526
local on = {}
527
 
3597 helixhorne 528
-- Map from CON actor usertype to SFLAGs.
529
local MAP_ACTOR_FLAGS = {
530
    [0] = 0,
531
    [1] = conl.SFLAG.SFLAG_BADGUY,
532
    [2] = conl.SFLAG.SFLAG_BADGUY + conl.SFLAG.SFLAG_BADGUYSTAYPUT,
533
    [3] = conl.SFLAG.SFLAG_BADGUY + conl.SFLAG.SFLAG_BADGUYSTAYPUT,
534
}
535
for i=4,7 do
536
    MAP_ACTOR_FLAGS[i] = MAP_ACTOR_FLAGS[i-4] + conl.SFLAG.SFLAG_ROTFIXED
537
end
538
 
539
 
3604 helixhorne 540
function on.actor_end(pos, usertype, tsamm, codetab)
2792 helixhorne 541
    local tilenum = tsamm[1]
3597 helixhorne 542
    local flags = 0
2792 helixhorne 543
 
3597 helixhorne 544
    if (usertype ~= nil) then  -- useractor
545
        if (not (bit.band(usertype, bit.bnot(7)) == 0)) then
3604 helixhorne 546
            perrprintf(pos, "invalid usertype: must be bitwise OR of 1, 2 and/or 4")
3597 helixhorne 547
        else
548
            flags = MAP_ACTOR_FLAGS[usertype]
549
        end
550
    end
551
 
4374 helixhorne 552
    -- 0x08000000: actor.FLAGS.replace
553
    flags = bit.bor(flags, 0x08000000)
3829 helixhorne 554
 
3597 helixhorne 555
    local str = flags..","
3315 helixhorne 556
    for i=2,math.min(#tsamm,4) do
557
        str = str .. tostring(tsamm[i])..","
558
    end
3597 helixhorne 559
    if (#tsamm >= 5) then
560
        local movflags = bit.bor(unpack(tsamm, 5))
561
        str = str .. movflags..","
3315 helixhorne 562
    end
563
 
3894 helixhorne 564
    paddcodef(pos, "gameactor{%d,%sfunction(_aci,_pli,_dist)", tilenum, str)
3561 helixhorne 565
    addcode(get_cache_sap_code())
3873 helixhorne 566
    add_code_and_end(codetab, "end}")
3373 helixhorne 567
 
3515 helixhorne 568
    if (g_code.actor[tilenum] ~= nil) then
3604 helixhorne 569
        pwarnprintf(pos, "redefined actor %d", tilenum)
3515 helixhorne 570
    end
3390 helixhorne 571
    g_code.actor[tilenum] = codetab
4373 helixhorne 572
    g_code.aflagsloc[tilenum] = getLocation("definition of actor", pos)
2792 helixhorne 573
end
574
 
3933 helixhorne 575
-- NOTE: in C-CON, the slash and backslash can also be part of an identifier,
576
-- but this is likely to support file names in other places.
577
local BAD_ID_CHARS0 = "_*?"  -- allowed 1st identifier chars
4473 helixhorne 578
local BAD_ID_CHARS1 = "_*-+?."  -- allowed following identifier chars
3375 helixhorne 579
 
3855 helixhorne 580
local function truetab(tab)
581
    local ttab = {}
582
    for i=1,#tab do
583
        ttab[tab[i]] = true
584
    end
585
    return ttab
586
end
587
 
588
-- Lua 5.2 keywords. Not 5.1 because we use "goto" for codegen.
589
local LUA_KEYW = truetab {
590
    "and", "break", "do", "else", "elseif", "end",
591
    "false", "for", "function", "goto", "if", "in",
592
    "local", "nil", "not", "or", "repeat", "return",
593
    "then", "true", "until", "while"
594
}
595
 
3518 helixhorne 596
-- Return the Lua code by which the CON object <name> is referenced in the
597
-- translated code.
3375 helixhorne 598
local function mangle_name(name, prefix)
3855 helixhorne 599
    if (name:match("^[A-Za-z_][A-Za-z_0-9]*$") and not LUA_KEYW[name]) then
3646 helixhorne 600
        return format("_%s.%s", prefix, name)
601
    else
602
        return format("_%s[%q]", prefix, name)
603
    end
3375 helixhorne 604
end
605
 
3513 helixhorne 606
function on.state_begin_Cmt(_subj, _pos, statename)
3409 helixhorne 607
    -- We must register the state name early (Cmt) because otherwise, it won't
608
    -- be found in a recursive state. XXX: The real issue seems to be the use
609
    -- of "Cmt"s in other places, which messes up the sequence of running the
610
    -- semantic actions.
3375 helixhorne 611
    local ourname = mangle_name(statename, "F")
612
    g_funcname[statename] = ourname
3409 helixhorne 613
    return true, ourname
3375 helixhorne 614
end
615
 
3604 helixhorne 616
function on.state_end(pos, funcname, codetab)
3894 helixhorne 617
    paddcodef(pos, "%s=function(_aci,_pli,_dist)", funcname)
3561 helixhorne 618
    addcode(get_cache_sap_code())
3373 helixhorne 619
    add_code_and_end(codetab, "end")
3255 helixhorne 620
end
621
 
3604 helixhorne 622
function on.event_end(pos, eventidx, codetab)
3526 helixhorne 623
    assert(type(codetab)=="table")
3639 helixhorne 624
    -- 0x20000000: actor.FLAGS.chain_beg
3894 helixhorne 625
    paddcodef(pos, "gameevent{%d,0x20000000,function (_aci,_pli,_dist)", eventidx)
3561 helixhorne 626
    addcode(get_cache_sap_code())
3526 helixhorne 627
    addcode(codetab)
3873 helixhorne 628
    addcode("end}")
3373 helixhorne 629
 
3390 helixhorne 630
    g_code.event[eventidx] = codetab
3373 helixhorne 631
end
632
 
3604 helixhorne 633
function on.eventloadactor_end(pos, tilenum, codetab)
3515 helixhorne 634
    -- Translate eventloadactor into a chained EVENT_LOADACTOR block
3894 helixhorne 635
    paddcodef(pos, "gameevent{'LOADACTOR', function (_aci,_pli,_dist)")
3561 helixhorne 636
    addcode(get_cache_sap_code())
3515 helixhorne 637
    addcodef("if (%s==%d) then", SPS".picnum", tilenum)
638
    addcode(codetab)
639
    addcode("end")
3873 helixhorne 640
    addcode("end}")
3515 helixhorne 641
 
3813 helixhorne 642
    if (g_code.loadactor[tilenum] ~= nil and g_warn["chained-loadactor"]) then
3604 helixhorne 643
        -- NOTE: C-CON redefines loadactor code if encountered multiple times.
644
        pwarnprintf(pos, "chained additional loadactor %d code", tilenum)
3515 helixhorne 645
    end
646
    g_code.loadactor[tilenum] = codetab
647
end
648
 
2792 helixhorne 649
----------
650
 
2749 helixhorne 651
local function linecolstr(pos)
652
    local line, col = getlinecol(pos)
3246 helixhorne 653
    return format("%d:%d", line, col)
2749 helixhorne 654
end
655
 
3256 helixhorne 656
local function increment_numerrors()
657
    g_numerrors = g_numerrors+1
658
    if (g_numerrors == g_maxerrors) then
659
        g_numerrors = inf
660
        printf("Too many errors (%d), aborting...", g_maxerrors)
661
    end
662
end
663
 
3604 helixhorne 664
function perrprintf(pos, fmt, ...)
4151 helixhorne 665
    printf("%s %s: error: "..fmt, g_filename,
666
           pos and linecolstr(pos) or "???", ...)
3256 helixhorne 667
    increment_numerrors()
2792 helixhorne 668
end
669
 
3604 helixhorne 670
function errprintf(fmt, ...)
4151 helixhorne 671
    perrprintf(g_lastkwpos, fmt, ...)
2749 helixhorne 672
end
673
 
3604 helixhorne 674
function pwarnprintf(pos, fmt, ...)
4151 helixhorne 675
    printf("%s %s: warning: "..fmt, g_filename,
676
           pos and linecolstr(pos) or "???", ...)
2792 helixhorne 677
end
678
 
3604 helixhorne 679
function warnprintf(fmt, ...)
4151 helixhorne 680
    pwarnprintf(g_lastkwpos, fmt, ...)
2756 helixhorne 681
end
682
 
4151 helixhorne 683
-- Print a continuation line to an error or warning.
684
function contprintf(iserr, fmt, ...)
685
    printf("%s %s: %s  "..fmt, g_filename,
686
           g_lastkwpos and linecolstr(g_lastkwpos) or "???",
687
           iserr and "     " or "       ", ...)
688
end
689
 
2792 helixhorne 690
local function parse_number(pos, numstr)
3571 helixhorne 691
    -- <numstr> is a full number string, potentially prefixed with a minus sign.
3375 helixhorne 692
    local num = tonumber((numstr:gsub("h$", "")))
3571 helixhorne 693
--    local onum = num
694
    local hex = numstr:match("0[xX]([^h]*)h?")  -- get hex digits, if any
2748 helixhorne 695
 
3439 helixhorne 696
    -- num==nil for Rio Lua, which doesn't handle large hex literals.
697
    if (num==nil or not (num >= -0x80000000 and num <= 0xffffffff)) then
3571 helixhorne 698
        -- number is <INT32_MIN or >UINT32_MAX or NaN
699
        if (hex and #hex>8 and hex:sub(1,#hex-8):match("^[fF]$")) then
700
            -- Too many hex digits, but they're all Fs.
701
            pwarnprintf(pos, "number %s truncated to 32 bits", numstr)
3909 helixhorne 702
            num = bit.tobit(num)
3571 helixhorne 703
        else
704
            perrprintf(pos, "number %s out of the range of a 32-bit integer", numstr)
705
            -- Be careful not to write bound checks like
706
            -- "if (i<LOWBOUND or i>HIGHBOUND) then error('...') end":
707
            num = NaN
708
        end
709
    elseif (num >= 0x80000000) then
4266 helixhorne 710
        num = bit.tobit(num)
3571 helixhorne 711
        if (not hex and g_warn["number-conversion"]) then
4266 helixhorne 712
            pwarnprintf(pos, "number %s converted to %d", numstr, num)
3256 helixhorne 713
        end
2748 helixhorne 714
    end
715
 
3571 helixhorne 716
--    printf("numstr:%s, num=%d (0x%s) '%s', resnum=%d (0x%s)",
717
--           numstr, onum, bit.tohex(onum), hex, num, bit.tohex(num))
2748 helixhorne 718
    return num
719
end
720
 
3865 helixhorne 721
-- Bound checking functions that generate a compilation error on failure.
722
local check = {}
723
 
724
function check.tile_idx(tilenum)
3944 helixhorne 725
    if (not (tilenum >= 0 and tilenum < C.MAXTILES)) then
3516 helixhorne 726
        errprintf("invalid tile number %d", tilenum)
727
        return false
728
    end
3865 helixhorne 729
    return true
730
end
2748 helixhorne 731
 
3865 helixhorne 732
function check.sound_idx(sidx)
733
    if (not (sidx >= 0 and sidx < conl.MAXSOUNDS)) then
734
        errprintf("invalid sound number %d", sidx)
735
        return false
736
    end
3516 helixhorne 737
    return true
738
end
739
 
740
 
3226 helixhorne 741
-- Mapping of various "define" types to the respective number of members and
742
-- vice versa
743
local LABEL = { MOVE=2, AI=3, ACTION=5, [2]="move", [3]="ai", [5]="action",
744
                NUMBER=1, [1]="number" }
2749 helixhorne 745
 
3916 helixhorne 746
-- Function names in the 'con' module:
3226 helixhorne 747
local LABEL_FUNCNAME = { [2]="move", [3]="ai", [5]="action" }
3916 helixhorne 748
local LABEL_PREFIX = { [2]="M", [3]="I", [5]="C" }  -- _C, _M, _I in the gen'd code
2765 helixhorne 749
 
3226 helixhorne 750
local g_labeldef = {}  -- Lua numbers for numbers, strings for composites
751
local g_labeltype = {}
3870 helixhorne 752
local g_labelspecial = {}  -- [<label>] = true
4151 helixhorne 753
local g_labelloc = {}  -- [<label>] = { filename, linenum, colnum }
2749 helixhorne 754
 
4153 helixhorne 755
-- Get location table for use in continued warning/error reporting.
4373 helixhorne 756
--[[ local --]]
757
function getLocation(kind, pos)
758
    local loc = { g_filename, getlinecol(pos or g_lastkwpos) }
759
    loc[4] = kind
760
    return loc
4153 helixhorne 761
end
762
 
4152 helixhorne 763
function reset.labels()
3325 helixhorne 764
    g_badids = {}
765
 
3226 helixhorne 766
    -- NO is also a valid `move', `ai' or `action', but they are handled
3433 helixhorne 767
    -- separately in lookup.composite().
3406 helixhorne 768
    g_labeldef = {
769
        NO = 0,
770
        -- NOTE: these are read-only gamevars in C-CON
771
        CLIPMASK0 = 65536+1,  -- blocking
772
        CLIPMASK1 = (256*65536)+64,  -- hittable
3537 helixhorne 773
        -- TODO_MP
3431 helixhorne 774
        COOP = 0,
3826 helixhorne 775
        MULTIMODE = 1,
3431 helixhorne 776
        numplayers = 1,
3432 helixhorne 777
        myconnectindex = 0,
3944 helixhorne 778
        -- Predefined constants
779
        MAXSTATUS = C.MAXSTATUS,
780
        MAXSPRITES = C.MAXSPRITES,
781
        MAX_WEAPONS = C.MAX_WEAPONS,
3406 helixhorne 782
    }
3226 helixhorne 783
 
4152 helixhorne 784
    g_labeltype = {}
785
    g_labelspecial = {}
786
    g_labelloc = {}
787
 
3431 helixhorne 788
    for varname,_ in pairs(g_labeldef) do
789
        g_labeltype[varname] = LABEL.NUMBER
3870 helixhorne 790
        g_labelspecial[varname] = true
3431 helixhorne 791
    end
3406 helixhorne 792
 
3226 helixhorne 793
    -- Initialize default defines.
3246 helixhorne 794
    for i=1,#conl.labels do
795
        for label, val in pairs(conl.labels[i]) do
2762 helixhorne 796
            g_labeldef[label] = val
3226 helixhorne 797
            g_labeltype[label] = LABEL.NUMBER
2762 helixhorne 798
        end
799
    end
800
end
801
 
3433 helixhorne 802
-- Table of functions doing various lookups (label, gamevar, ...)
803
local lookup = {}
804
 
805
function lookup.defined_label(pos, maybe_minus_str, identifier)
2749 helixhorne 806
    local num = g_labeldef[identifier]
807
 
808
    if (num == nil) then
2792 helixhorne 809
        perrprintf(pos, "label \"%s\" is not defined", identifier)
2763 helixhorne 810
        return -inf  -- return a number for type cleanness
2749 helixhorne 811
    end
812
 
3226 helixhorne 813
    if (g_labeltype[identifier] ~= LABEL.NUMBER) then
814
        perrprintf(pos, "label \"%s\" is not a `define'd number", identifier)
2765 helixhorne 815
        return -inf
816
    end
817
 
3226 helixhorne 818
    assert(type(num)=="number")
819
 
2765 helixhorne 820
    return (maybe_minus_str=="" and 1 or -1) * num
2749 helixhorne 821
end
822
 
3847 helixhorne 823
assert(not BAD_ID_CHARS1:find(":"))
824
function lookup.raw_defined_label(pos, maybe_minus_str, identifier)
825
    return pos..":"..maybe_minus_str..":"..identifier
826
end
827
 
828
local dynmap = {}
829
-- When necessary, initialize dynamic {tile,sound} mapping list.
830
function dynmap.maybe_init(dyni, dynList)
831
    if (dyni[1]==nil) then
832
        dyni[1] = true
833
        -- Init name -> g_dyn*List index mapping
834
        for i=0,math.huge do
835
            local str = dynList[i].str
836
            if (str==nil) then
837
                break
838
            end
839
 
840
            dyni[ffi.string(str)] = i
841
        end
842
    end
843
end
844
 
845
-- Potentially process one dynamic {tile,sound} remapping.
846
function dynmap.maybe_process(dyni, dynList, identifier, num)
847
    if (dyni[identifier]) then
848
        local di = dynList[dyni[identifier]]
849
 
850
        if (ffiC._DEBUG_LUNATIC~=0 and di.staticval~=num) then
851
            printf("REMAP %s (%d) --> %d", ffi.string(di.str), di.staticval, num)
852
        end
853
        di.dynvalptr[0] = num
854
    end
855
end
856
 
4152 helixhorne 857
-- The 'check' table is also used to hold a couple of misc checkers.
858
 
859
function check.sysvar_def_attempt(identifier)
3503 helixhorne 860
    if (identifier=="actorvar") then
861
        errprintf("cannot define reserved symbol `actorvar'")
862
        return true
863
    end
4140 helixhorne 864
    if (identifier=="_IS_NORESET_GAMEVAR") then
865
        errprintf("cannot define reserved symbol `_IS_NORESET_GAMEVAR'")
866
        return true
867
    end
3503 helixhorne 868
end
869
 
4153 helixhorne 870
 
871
local inform = {}
872
 
873
function inform.common(loc, iserr)
4151 helixhorne 874
    if (loc) then
4373 helixhorne 875
        contprintf(iserr, "Old definition is at %s %d:%d", loc[1], loc[2], loc[3])
4151 helixhorne 876
    else
877
        contprintf(iserr, "Old definition is built-in")
878
    end
879
end
880
 
4153 helixhorne 881
function inform.olddef_location(identifier, iserr)
882
    inform.common(g_labelloc[identifier], iserr)
883
end
884
 
885
function inform.oldgv_location(identifier, iserr)
886
    inform.common(g_gamevar[identifier].loc, iserr)
887
end
888
 
889
 
890
local Define = {}
891
 
3916 helixhorne 892
function Define.label(identifier, num)
4152 helixhorne 893
    if (check.sysvar_def_attempt(identifier)) then
3503 helixhorne 894
        return
895
    end
896
 
3226 helixhorne 897
    local oldtype = g_labeltype[identifier]
2765 helixhorne 898
    local oldval = g_labeldef[identifier]
2749 helixhorne 899
 
2765 helixhorne 900
    if (oldval) then
3226 helixhorne 901
        if (oldtype ~= LABEL.NUMBER) then
4151 helixhorne 902
            errprintf("Refusing to overwrite `%s' label \"%s\" with a `define'd number.",
3226 helixhorne 903
                      LABEL[oldtype], identifier)
4153 helixhorne 904
            inform.olddef_location(identifier, true)
3226 helixhorne 905
        else
3246 helixhorne 906
            -- conl.labels[...]: don't warn for wrong PROJ_ redefinitions
3255 helixhorne 907
            if (g_warn["not-redefined"]) then
3373 helixhorne 908
                if (oldval ~= num and conl.PROJ[identifier]==nil) then
4151 helixhorne 909
                    warnprintf("Label \"%s\" not redefined with new value %d (old: %d).",
3255 helixhorne 910
                               identifier, num, oldval)
4153 helixhorne 911
                    inform.olddef_location(identifier, false)
3255 helixhorne 912
                end
3226 helixhorne 913
            end
2749 helixhorne 914
        end
3226 helixhorne 915
    else
3390 helixhorne 916
        if (g_gamevar[identifier]) then
917
            warnprintf("symbol `%s' already used for game variable", identifier)
4153 helixhorne 918
            inform.oldgv_location(identifier, false)
3390 helixhorne 919
        end
920
 
3944 helixhorne 921
        if (ffi and g_dyntilei and (num>=0 and num<C.MAXTILES)) then
3847 helixhorne 922
            dynmap.maybe_init(g_dyntilei, ffiC.g_dynTileList)
923
            dynmap.maybe_process(g_dyntilei, ffiC.g_dynTileList, identifier, num)
3568 helixhorne 924
        end
925
 
3226 helixhorne 926
        -- New definition of a label
927
        g_labeldef[identifier] = num
928
        g_labeltype[identifier] = LABEL.NUMBER
4153 helixhorne 929
        g_labelloc[identifier] = getLocation()
2749 helixhorne 930
    end
931
end
932
 
4152 helixhorne 933
function check.composite_literal(labeltype, pos, num)
3226 helixhorne 934
    if (num==0 or num==1) then
935
        return (num==0) and "0" or "1"
936
    else
937
        perrprintf(pos, "literal `%s' number must be either 0 or 1", LABEL[labeltype])
938
        return "_INVALIT"
2765 helixhorne 939
    end
940
end
941
 
3433 helixhorne 942
function lookup.composite(labeltype, pos, identifier)
2765 helixhorne 943
    if (identifier=="NO") then
3226 helixhorne 944
        -- NO is a special case and is valid for move, action and ai,
945
        -- being the same as passing a literal 0.
946
        return "0"
2765 helixhorne 947
    end
948
 
949
    local val = g_labeldef[identifier]
4025 helixhorne 950
    local typ = g_labeltype[identifier]
2765 helixhorne 951
 
952
    if (val == nil) then
2792 helixhorne 953
        perrprintf(pos, "label \"%s\" is not defined", identifier)
3226 helixhorne 954
        return "_NOTDEF"
4025 helixhorne 955
    elseif (typ ~= labeltype) then
956
        if (identifier=="randomangle" and labeltype==LABEL.MOVE and typ==LABEL.NUMBER) then
957
            -- Be forgiving with a 1.3/1.5 GAME.CON type error.
3325 helixhorne 958
            pwarnprintf(pos, "label \"randomangle\" is not a `move' value, assuming 0")
959
            return "0"
4025 helixhorne 960
        elseif (identifier=="BLIMPRESPAWNTIME" and labeltype==LABEL.ACTION and typ==LABEL.NUMBER) then
961
            -- Be forgiving with a 1.3 GAME.CON type error.
962
            pwarnprintf(pos, "label \"BLIMPRESPAWNTIME\" is not an `action' value, assuming 0")
963
            return "0"
3325 helixhorne 964
        else
965
            perrprintf(pos, "label \"%s\" is not a%s `%s' value", identifier,
966
                       labeltype==LABEL.MOVE and "" or "n", LABEL[labeltype])
967
            return "_WRONGTYPE"
968
        end
2765 helixhorne 969
    end
970
 
971
    return val
972
end
973
 
4152 helixhorne 974
function check.reserved_bits(flags, allowedbits, suffix)
3882 helixhorne 975
    local rbits = bit.bnot(allowedbits)
976
    if (bit.band(flags, rbits) ~= 0) then
977
        warnprintf("set one or more reserved bits (0x%s) "..suffix,
978
                   bit.tohex(bit.band(flags, rbits)))
979
    end
980
end
981
 
3924 helixhorne 982
Define.ALLOWED_VIEWTYPE = truetab { 0, 1, 3,4, 5, 7, 8, -5, -7 }
983
 
3916 helixhorne 984
function Define.composite(labeltype, identifier, ...)
3226 helixhorne 985
    local oldtype = g_labeltype[identifier]
2765 helixhorne 986
    local oldval = g_labeldef[identifier]
987
 
988
    if (oldval) then
3226 helixhorne 989
        if (oldtype ~= labeltype) then
4151 helixhorne 990
            errprintf("Refusing to overwrite `%s' label \"%s\" with a `%s' value.",
3226 helixhorne 991
                      LABEL[oldtype], identifier, LABEL[labeltype])
4153 helixhorne 992
            inform.olddef_location(identifier, true)
3226 helixhorne 993
        else
4151 helixhorne 994
            warnprintf("Duplicate `%s' definition of \"%s\" ignored.",
3226 helixhorne 995
                       LABEL[labeltype], identifier)
4153 helixhorne 996
            inform.olddef_location(identifier, false)
2765 helixhorne 997
        end
998
        return
999
    end
1000
 
3541 helixhorne 1001
    -- Fill up omitted arguments denoting composites with zeros.
3226 helixhorne 1002
    local isai = (labeltype == LABEL.AI)
1003
    local args = {...}
1004
    for i=#args+1,labeltype do
3541 helixhorne 1005
        -- Passing nil/nothing as remaining args to con.ai will make the
1006
        -- action/move the null one.
3226 helixhorne 1007
        args[i] = (isai and i<=2) and "nil" or 0
2765 helixhorne 1008
    end
1009
 
3226 helixhorne 1010
    if (isai) then
1011
        assert(type(args[1])=="string")
1012
        assert(type(args[2])=="string")
1013
 
2765 helixhorne 1014
        -- OR together the flags
3226 helixhorne 1015
        for i=#args,LABEL.AI+1, -1 do
1016
            args[LABEL.AI] = bit.bor(args[LABEL.AI], args[i])
1017
            args[i] = nil
2765 helixhorne 1018
        end
3882 helixhorne 1019
 
1020
        -- Check whether movflags use reserved bits.
4152 helixhorne 1021
        check.reserved_bits(args[LABEL.AI], 4096+2047, "for ai's movflags")
2765 helixhorne 1022
    end
1023
 
3924 helixhorne 1024
    if (labeltype == LABEL.ACTION) then
1025
        -- Sanity-check action members.
3974 helixhorne 1026
        -- KEEPINSYNC with ACTOR_CHECK in control.lua for consistency.
3924 helixhorne 1027
        if (not (args[2] >= 0)) then
1028
            errprintf("action \"%s\" has negative number of frames", identifier)
1029
        end
1030
        if (Define.ALLOWED_VIEWTYPE[args[3]] == nil) then
1031
            errprintf("action \"%s\" has disallowed viewtype %d", identifier, args[3])
1032
        end
1033
        if (not (args[4] >= -1 and args[4] <= 1)) then
1034
            warnprintf("action \"%s\" has incval different from -1, 0 or 1", identifier)
1035
        end
1036
    end
1037
 
3226 helixhorne 1038
    -- Make a string out of that.
1039
    for i=1+(isai and 2 or 0),#args do
3246 helixhorne 1040
        args[i] = format("%d", args[i])
3226 helixhorne 1041
    end
1042
 
3916 helixhorne 1043
    local refcode = mangle_name(identifier, LABEL_PREFIX[labeltype])
1044
    addcodef(isai and "%s=_con.%s(%s)" or "%s=_con.%s{%s}",  -- ai has parens
1045
             refcode, LABEL_FUNCNAME[labeltype], table.concat(args, ","))
3226 helixhorne 1046
 
3916 helixhorne 1047
    g_labeldef[identifier] = refcode
3226 helixhorne 1048
    g_labeltype[identifier] = labeltype
4153 helixhorne 1049
    g_labelloc[identifier] = getLocation()
2765 helixhorne 1050
end
1051
 
1052
 
2749 helixhorne 1053
local function parse(contents) end -- fwd-decl
1054
 
3809 helixhorne 1055
local function do_include_file(dirname, filename, isroot)
3343 helixhorne 1056
    assert(type(filename)=="string")
2749 helixhorne 1057
 
3343 helixhorne 1058
    if (g_have_file[filename] ~= nil) then
3881 helixhorne 1059
        printf("[%d] Fatal error: infinite loop including \"%s\"", g_recurslevel, filename)
3343 helixhorne 1060
        g_numerrors = inf
1061
        return
1062
    end
1063
 
1064
    local contents
1065
 
1066
    if (read_into_string) then
1067
        -- running from EDuke32
1068
        contents = read_into_string(filename)
1069
    else
1070
        -- running stand-alone
2749 helixhorne 1071
        local io = require("io")
1072
 
1073
        local fd, msg = io.open(dirname..filename)
3862 helixhorne 1074
        while (fd == nil and not isroot and filename:find("/")) do
2749 helixhorne 1075
            -- strip up to and including first slash:
3439 helixhorne 1076
            filename = filename:gsub("^.-/", "")
2749 helixhorne 1077
            fd, msg = io.open(dirname..filename)
3862 helixhorne 1078
        end
2749 helixhorne 1079
 
3862 helixhorne 1080
        -- As a last resort, try the "default directory"
3933 helixhorne 1081
        if (fd==nil and not isroot and g_defaultDir) then
3862 helixhorne 1082
            -- strip up to and including last slash (if any):
1083
            filename = filename:gsub("^.*/", "")
1084
            dirname = g_defaultDir.."/"
1085
            fd, msg = io.open(dirname..filename)
3809 helixhorne 1086
        end
3439 helixhorne 1087
 
3809 helixhorne 1088
        if (fd == nil) then
1089
            printf("[%d] Fatal error: couldn't open %s", g_recurslevel, msg)
1090
            g_numerrors = inf
1091
            return
2749 helixhorne 1092
        end
1093
 
3343 helixhorne 1094
        contents = fd:read("*all")
2749 helixhorne 1095
        fd:close()
3343 helixhorne 1096
    end
2749 helixhorne 1097
 
3343 helixhorne 1098
    if (contents == nil) then
1099
        -- maybe that file name turned out to be a directory or other
1100
        -- special file accidentally
1101
        printf("[%d] Fatal error: couldn't read from \"%s\"",
1102
               g_recurslevel, dirname..filename)
1103
        g_numerrors = inf
1104
        return
1105
    end
2749 helixhorne 1106
 
3343 helixhorne 1107
    printf("%s[%d] Translating file \"%s\"", (g_recurslevel==-1 and "\n---- ") or "",
1108
           g_recurslevel+1, dirname..filename);
2765 helixhorne 1109
 
3343 helixhorne 1110
    local oldfilename = g_filename
1111
    g_filename = filename
1112
    parse(contents)
1113
    g_filename = oldfilename
1114
end
2749 helixhorne 1115
 
3391 helixhorne 1116
-- Table of various outer command handling functions.
1117
local Cmd = {}
1118
 
3516 helixhorne 1119
function Cmd.NYI(msg)
1120
    return function()
1121
        errprintf(msg.." not yet implemented")
1122
    end
1123
end
1124
 
1125
function Cmd.nyi(msg)
1126
    return function()
1127
        warnprintf(msg.." not yet implemented")
1128
    end
1129
end
1130
 
3391 helixhorne 1131
function Cmd.include(filename)
3809 helixhorne 1132
    do_include_file(g_directory, filename, false)
2749 helixhorne 1133
end
1134
 
2763 helixhorne 1135
--- Per-module game data
1136
local g_data = {}
3246 helixhorne 1137
local EPMUL = conl.MAXLEVELS
2749 helixhorne 1138
 
4152 helixhorne 1139
function reset.gamedata()
2763 helixhorne 1140
    g_data = {}
1141
 
1142
    -- [EPMUL*ep + lev] = { ptime=<num>, dtime=<num>, fn=<str>, name=<str> }
2764 helixhorne 1143
    g_data.level = {}
1144
    -- [ep] = <str>
1145
    g_data.volname = {}
1146
    -- [skillnum] = <str>
1147
    g_data.skillname = {}
1148
    -- [quotenum] = <str>
1149
    g_data.quote = {}
1150
    -- table of length 26 or 30 containg numbers
1151
    g_data.startup = {}
1152
    -- [soundnum] = { fn=<str>, params=<table of length 5> }
1153
    g_data.sound = {}
1154
    -- [volnum] = <table of length numlevels (<= MAXLEVELS) of <str>>
1155
    g_data.music = {}
2763 helixhorne 1156
end
1157
 
3806 helixhorne 1158
-- TODO: PRE13 has no <dtstr> (3D Realms time).
3391 helixhorne 1159
function Cmd.definelevelname(vol, lev, fn, ptstr, dtstr, levname)
3419 helixhorne 1160
    if (not (vol >= 0 and vol < conl.MAXVOLUMES)) then
2763 helixhorne 1161
        errprintf("volume number exceeds maximum volume count.")
2764 helixhorne 1162
        return
2763 helixhorne 1163
    end
1164
 
3419 helixhorne 1165
    if (not (lev >= 0 and lev < conl.MAXLEVELS)) then
2763 helixhorne 1166
        errprintf("level number exceeds maximum number of levels per episode.")
2764 helixhorne 1167
        return
2763 helixhorne 1168
    end
1169
 
1170
    -- TODO: Bcorrectfilename(fn)
1171
 
1172
    local function secs(tstr)
1173
        local m, s = string.match(tstr, ".+:.+")
1174
        m, s = tonumber(m), tonumber(s)
1175
        return (m and s) and m*60+s or 0
1176
    end
1177
 
3373 helixhorne 1178
    local map = {
1179
        ptime=secs(ptstr), dtime=secs(dtstr), fn="/"..fn, name=levname
2763 helixhorne 1180
    }
3373 helixhorne 1181
 
1182
    if (ffi) then
1183
        ffiC.C_DefineLevelName(vol, lev, map.fn, map.ptime, map.dtime, map.name)
1184
    end
1185
 
1186
    g_data.level[EPMUL*vol+lev] = map
2763 helixhorne 1187
end
1188
 
3375 helixhorne 1189
local function defineXname(what, ffiCfuncname, X, name)
1190
    name = name:upper()
1191
    if (ffi) then
1192
        ffiC[ffiCfuncname](X, name)
1193
        if (#name > 32) then
1194
            warnprintf("%s %d name truncated to 32 characters.", what, X)
1195
        end
1196
    end
1197
    return name
1198
end
1199
 
3391 helixhorne 1200
function Cmd.defineskillname(skillnum, name)
3419 helixhorne 1201
    if (not (skillnum >= 0 and skillnum < conl.MAXSKILLS)) then
3375 helixhorne 1202
        errprintf("skill number is negative or exceeds maximum skill count.")
2764 helixhorne 1203
        return
1204
    end
1205
 
3375 helixhorne 1206
    name = defineXname("skill", "C_DefineSkillName", skillnum, name)
1207
    g_data.skillname[skillnum] = name
2764 helixhorne 1208
end
1209
 
3391 helixhorne 1210
function Cmd.definevolumename(vol, name)
3419 helixhorne 1211
    if (not (vol >= 0 and vol < conl.MAXVOLUMES)) then
2764 helixhorne 1212
        errprintf("volume number is negative or exceeds maximum volume count.")
1213
        return
1214
    end
1215
 
3375 helixhorne 1216
    name = defineXname("volume", "C_DefineVolumeName", vol, name)
3373 helixhorne 1217
    g_data.volname[vol] = name
2764 helixhorne 1218
end
1219
 
3516 helixhorne 1220
function Cmd.definegamefuncname(idx, name)
1221
    local NUMGAMEFUNCTIONS = (ffi and ffiC.NUMGAMEFUNCTIONS or 56)
1222
    if (not (idx >= 0 and idx < NUMGAMEFUNCTIONS)) then
1223
        errprintf("function number exceeds number of game functions.")
1224
        return
1225
    end
1226
 
1227
    assert(type(name)=="string")
1228
    -- XXX: in place of C-CON's "invalid character in function name" report:
3656 helixhorne 1229
    name = name:gsub("[^A-Za-z0-9]", "_")
3516 helixhorne 1230
 
1231
    if (ffi) then
1232
        ffiC.C_DefineGameFuncName(idx, name)
1233
    end
1234
end
1235
 
3826 helixhorne 1236
function Cmd.definegametype(idx, flags, name)
1237
    if (not (idx >= 0 and idx < conl.MAXGAMETYPES)) then
1238
        errprintf("gametype number exceeds maximum gametype count.")
1239
        return
1240
    end
1241
 
1242
    if (ffi) then
1243
        ffiC.C_DefineGameType(idx, flags, name)
1244
    end
1245
end
1246
 
3373 helixhorne 1247
-- strip whitespace from front and back
1248
local function stripws(str)
1249
    return str:match("^%s*(.*)%s*$")
1250
end
1251
 
3391 helixhorne 1252
function Cmd.definequote(qnum, quotestr)
3357 helixhorne 1253
    if (not (qnum >= 0 and qnum < conl.MAXQUOTES)) then
1254
        errprintf("quote number is negative or exceeds limit of %d.", conl.MAXQUOTES-1)
3656 helixhorne 1255
        return ""
3357 helixhorne 1256
    end
2764 helixhorne 1257
 
3373 helixhorne 1258
    quotestr = stripws(quotestr)
3357 helixhorne 1259
 
3511 helixhorne 1260
    if (#quotestr >= conl.MAXQUOTELEN) then
1261
        -- NOTE: Actually, C_DefineQuote takes care of this! That is,
1262
        -- standalone, the string isn't truncated.
1263
        warnprintf("quote %d truncated to %d characters.", qnum, conl.MAXQUOTELEN-1)
1264
    end
1265
 
3357 helixhorne 1266
    if (ffi) then
1267
        ffiC.C_DefineQuote(qnum, quotestr)
1268
    end
1269
 
1270
    g_data.quote[qnum] = quotestr
3656 helixhorne 1271
    return ""
2764 helixhorne 1272
end
1273
 
3865 helixhorne 1274
local PROJ = {}
1275
for key, val in pairs(conl.PROJ) do
1276
    -- Strip "PROJ_"
1277
    PROJ[key:sub(6)] = val
1278
end
1279
 
3463 helixhorne 1280
function Cmd.defineprojectile(tilenum, what, val)
3865 helixhorne 1281
    local ok = check.tile_idx(tilenum)
3463 helixhorne 1282
 
3865 helixhorne 1283
    if (what==PROJ.WORKSLIKE) then
4152 helixhorne 1284
        check.reserved_bits(val, 2^21-1, "for PROJ_WORKSLIKE")
3865 helixhorne 1285
    elseif (what==PROJ.SOUND or what==PROJ.ISOUND or what==PROJ.BSOUND) then
1286
        ok = ok and (val==-1 or check.sound_idx(val))
1287
    elseif (what==PROJ.SPAWNS or what==PROJ.DECAL or what==PROJ.TRAIL) then
1288
        ok = ok and (val==-1 or check.tile_idx(val))
1289
    end
1290
 
3516 helixhorne 1291
    if (ffi and ok) then
3463 helixhorne 1292
        ffiC.C_DefineProjectile(tilenum, what, val)
1293
    end
1294
end
1295
 
4372 helixhorne 1296
-- <override>: override-set flags? The default is to bitwise OR with existing.
1297
function Cmd.xspriteflags(tilenum, flags, override)
3865 helixhorne 1298
    local ok = check.tile_idx(tilenum)
4372 helixhorne 1299
    check.reserved_bits(flags, conl.user_sflags, "for sprite flags")
3516 helixhorne 1300
 
4373 helixhorne 1301
    local loc = g_code.aflagsloc[tilenum]
1302
 
1303
    if (override and loc ~= nil) then
1304
        warnprintf("'spriteflags' after %s %d", loc[4], tilenum)
1305
        contprintf(false, "at %s %d:%d", loc[1], loc[2], loc[3])
1306
    end
1307
 
1308
    g_code.aflagsloc[tilenum] = getLocation(format("'%s' for actor", g_lastkw), pos)
1309
 
3516 helixhorne 1310
    if (ffi and ok) then
4372 helixhorne 1311
        local tile = ffiC.g_tile[tilenum]
1312
        tile._flags = bit.bor(override and 0 or tile._flags, flags)
3516 helixhorne 1313
    end
1314
end
1315
 
4291 helixhorne 1316
function Cmd.precache(tilenum0, tilenum1, flagnum)
1317
    local ok = check.tile_idx(tilenum0) and check.tile_idx(tilenum1)
1318
 
1319
    if (ffi and ok) then
4372 helixhorne 1320
        local tile = ffiC.g_tile[tilenum0]
1321
        tile._cacherange = tilenum1;
4291 helixhorne 1322
        if (flagnum) then
4372 helixhorne 1323
            tile._flags = bit.bor(tile._flags, conl.SFLAG.SFLAG_CACHE)
4291 helixhorne 1324
        end
1325
    end
1326
end
1327
 
3516 helixhorne 1328
function Cmd.cheatkeys(sc1, sc2)
1329
    if (ffi) then
1330
        ffiC.CheatKeys[0] = sc1
1331
        ffiC.CheatKeys[1] = sc2
1332
    end
1333
end
1334
 
1335
function Cmd.setdefname(filename)
1336
    assert(type(filename)=="string")
1337
    if (ffi) then
1338
        if (ffiC.C_SetDefName(filename) ~= 0) then
1339
            error("OUT OF MEMORY", 0)
1340
        end
1341
    end
1342
end
1343
 
4143 helixhorne 1344
function Cmd.setcfgname(filename)
1345
    assert(type(filename)=="string")
1346
    if (ffi) then
1347
        ffiC.C_SetCfgName(filename)
1348
    end
1349
end
1350
 
3391 helixhorne 1351
function Cmd.gamestartup(...)
3343 helixhorne 1352
    local args = {...}
2764 helixhorne 1353
 
3806 helixhorne 1354
    -- TODO: PRE13: detection of other g_scriptVersion.
3343 helixhorne 1355
    if (#args ~= 26 and #args ~= 30) then
2764 helixhorne 1356
        errprintf("must pass either 26 (1.3D) or 30 (1.5) values")
1357
        return
1358
    end
1359
 
3355 helixhorne 1360
    if (ffi) then
3343 helixhorne 1361
        -- running from EDuke32
3355 helixhorne 1362
        if (#args == 30) then
3343 helixhorne 1363
            ffiC.g_scriptVersion = 14
1364
        end
1365
        local params = ffi.new("int32_t [30]", args)
1366
        ffiC.G_DoGameStartup(params)
1367
    end
1368
 
1369
    g_data.startup = args  -- TODO: sanity-check them
2764 helixhorne 1370
end
1371
 
3847 helixhorne 1372
function Cmd.definesound(sndlabel, fn, ...)
1373
    local sndnum
1374
 
1375
    if (type(sndlabel)=="string") then
1376
        local pos, minus, label = sndlabel:match("(.-):(.-):(.+)")
1377
        sndnum = lookup.defined_label(tonumber(pos), minus, label)
1378
 
1379
        if (ffi and g_dynsoundi and (sndnum>=0 and sndnum<conl.MAXSOUNDS)) then
1380
            dynmap.maybe_init(g_dynsoundi, ffiC.g_dynSoundList)
1381
            dynmap.maybe_process(g_dynsoundi, ffiC.g_dynSoundList, label, sndnum)
1382
        end
1383
    else
1384
        assert(type(sndlabel)=="number")
1385
        sndnum = sndlabel
1386
    end
1387
 
3357 helixhorne 1388
    if (not (sndnum >= 0 and sndnum < conl.MAXSOUNDS)) then
3373 helixhorne 1389
        errprintf("sound number is negative or exceeds sound limit of %d", conl.MAXSOUNDS-1)
2764 helixhorne 1390
        return
1391
    end
1392
 
3882 helixhorne 1393
    local params = {...}  -- TODO: sanity-check them some more
4152 helixhorne 1394
    check.reserved_bits(params[4], 31+128, "for sound flags")
3882 helixhorne 1395
 
3355 helixhorne 1396
    if (ffi) then
1397
        local cparams = ffi.new("int32_t [5]", params)
1398
        assert(type(fn)=="string")
1399
        ffiC.C_DefineSound(sndnum, fn, cparams)
1400
    end
2764 helixhorne 1401
 
1402
    g_data.sound[sndnum] = { fn=fn, params=params }
1403
end
1404
 
3391 helixhorne 1405
function Cmd.music(volnum, ...)
3567 helixhorne 1406
    local envmusicp = (volnum==0)
2764 helixhorne 1407
 
3817 helixhorne 1408
    if (not (volnum >= 0 and volnum <= conl.MAXVOLUMES+1)) then
1409
        -- NOTE: Also allow MAXVOLUMES+1.
3567 helixhorne 1410
        errprintf("volume number must be between 0 and MAXVOLUMES=%d", conl.MAXVOLUMES)
3558 helixhorne 1411
        return
1412
    end
1413
 
2764 helixhorne 1414
    local filenames = {...}
3567 helixhorne 1415
    local MAXFNS = envmusicp and conl.MAXVOLUMES or conl.MAXLEVELS
2764 helixhorne 1416
 
3567 helixhorne 1417
    if (#filenames > MAXFNS) then
1418
        warnprintf("ignoring extraneous %d music file names", #filenames-MAXFNS)
1419
        for i=MAXFNS+1,#filenames do
2764 helixhorne 1420
            filenames[i] = nil
1421
        end
1422
    end
1423
 
3373 helixhorne 1424
    if (ffi) then
1425
        for i=1,#filenames do
1426
            assert(type(filenames[i])=="string")
3558 helixhorne 1427
            ffiC.C_DefineMusic(volnum-1, i-1, "/"..filenames[i])
3373 helixhorne 1428
        end
1429
    end
1430
 
2764 helixhorne 1431
    g_data.music[volnum] = filenames
1432
end
1433
 
1434
 
3390 helixhorne 1435
--- GAMEVARS / GAMEARRAYS
1436
 
3503 helixhorne 1437
function Cmd.gamearray(identifier, initsize)
4152 helixhorne 1438
    if (check.sysvar_def_attempt(identifier)) then
3503 helixhorne 1439
        return
1440
    end
1441
 
3533 helixhorne 1442
    if (not (initsize >= 0 and initsize < 0x7fffffff)) then
3503 helixhorne 1443
        errprintf("invalid initial size %d for gamearray `%s'", initsize, identifier)
1444
        return
1445
    end
1446
 
1447
    local oga = g_gamearray[identifier]
1448
    if (oga) then
3562 helixhorne 1449
        if (oga.sysp) then
1450
            errprintf("attempt to define system gamearray `%s'", identifier)
1451
            return
1452
        elseif (initsize ~= oga.size) then
3503 helixhorne 1453
            errprintf("duplicate gamearray definition `%s' has different size", identifier)
1454
            return
1455
        else
1456
            warnprintf("duplicate gamearray definition `%s' ignored", identifier)
1457
            return
1458
        end
1459
    end
1460
 
1461
    if (g_gamevar[identifier]) then
1462
        warnprintf("symbol `%s' already used for game variable", identifier)
4153 helixhorne 1463
        inform.oldgv_location(identifier, false)
3503 helixhorne 1464
    end
1465
 
1466
    local ga = { name=mangle_name(identifier, "A"), size=initsize }
1467
    g_gamearray[identifier] = ga
1468
 
3891 helixhorne 1469
    addcode("if _S then")
3518 helixhorne 1470
    addcodef("%s=_con._gamearray(%d)", ga.name, initsize)
3891 helixhorne 1471
    addcode("end")
3503 helixhorne 1472
end
1473
 
3391 helixhorne 1474
function Cmd.gamevar(identifier, initval, flags)
4152 helixhorne 1475
    if (check.sysvar_def_attempt(identifier)) then
3503 helixhorne 1476
        return
1477
    end
1478
 
3431 helixhorne 1479
    if (bit.band(flags, bit.bnot(GVFLAG.USER_MASK)) ~= 0) then
3390 helixhorne 1480
        -- TODO: a couple of the presumably safe ones
3431 helixhorne 1481
        errprintf("gamevar flags other than 1, 2, 1024 or 131072: NYI or forbidden")
3427 helixhorne 1482
        return
3390 helixhorne 1483
    end
1484
 
4112 helixhorne 1485
    local perPlayer = (bit.band(flags, GVFLAG.PERPLAYER) ~= 0)
1486
    local perActor = (bit.band(flags, GVFLAG.PERACTOR) ~= 0)
1487
 
1488
    if (perPlayer and perActor) then
3390 helixhorne 1489
        errprintf("invalid gamevar flags: must be either PERPLAYER or PERACTOR, not both")
3427 helixhorne 1490
        return
3390 helixhorne 1491
    end
1492
 
1493
    local ogv = g_gamevar[identifier]
4570 helixhorne 1494
    -- handle NORESET or NODEFAULT
4112 helixhorne 1495
    local isSessionVar = (bit.band(flags, GVFLAG.NODEFAULT) ~= 0)
4140 helixhorne 1496
    local storeWithSavegames = (bit.band(flags, GVFLAG.NORESET) == 0)
3390 helixhorne 1497
 
4112 helixhorne 1498
    if (isSessionVar and (perPlayer or perActor)) then
1499
        if (ogv == nil) then  -- warn only once per gamevar
4140 helixhorne 1500
            warnprintf("per-%s session gamevar `%s': NYI, made %s",
4112 helixhorne 1501
                       perPlayer and "player" or "actor",
4140 helixhorne 1502
                       identifier,
1503
                       perPlayer and "global" or "non-session")
4112 helixhorne 1504
        end
1505
 
1506
        if (perActor) then
1507
            flags = bit.band(flags, bit.bnot(GVFLAG.NODEFAULT))
1508
            isSessionVar = false
1509
        elseif (perPlayer) then
1510
            flags = bit.band(flags, bit.bnot(GVFLAG.PERPLAYER))
1511
            perPlayer = false
1512
        end
1513
    end
1514
 
3390 helixhorne 1515
    if (ogv ~= nil) then
3419 helixhorne 1516
        local oflags = ogv.flags
1517
        if (oflags ~= flags) then
4112 helixhorne 1518
            if (bit.band(oflags, GVFLAG.SYSTEM) ~= 0 and not isSessionVar) then
3419 helixhorne 1519
                -- Attempt to override a system gamevar. See if it's read-only...
1520
                if (bit.band(oflags, GVFLAG.READONLY) ~= 0) then
1521
                    errprintf("attempt to override read-only system gamevar `%s'", identifier)
3427 helixhorne 1522
                    return
3419 helixhorne 1523
                end
1524
 
1525
                local flagsnosys = bit.band(oflags, bit.bnot(GVFLAG.SYSTEM))
3439 helixhorne 1526
                if (flagsnosys ~= flags and g_warn["system-gamevar"]) then
3419 helixhorne 1527
                    warnprintf("overrode initial value of `%s', but kept "..
1528
                               "flags (%d)", identifier, flagsnosys)
1529
                end
1530
 
3563 helixhorne 1531
                if (ogv.rbits and bit.band(ogv.rbits, initval)~=0) then
1532
                    warnprintf("set one or more reserved bits (0x%s) in overriding `%s'",
1533
                               bit.tohex(bit.band(ogv.rbits, initval)), identifier)
1534
                end
1535
 
3574 helixhorne 1536
                local linestr = "--"..getlinecol(g_lastkwpos)
1537
 
3419 helixhorne 1538
                -- Emit code to set the variable at Lua parse time.
3891 helixhorne 1539
                -- XXX: How does this interact with savegame restoration?
3419 helixhorne 1540
                if (bit.band(oflags, GVFLAG.PERPLAYER) ~= 0) then
3848 helixhorne 1541
                    -- Replace player index by 0. PLAYER_0.
3563 helixhorne 1542
                    -- TODO_MP: init for all players.
3419 helixhorne 1543
                    local pvar, numrepls = ogv.name:gsub("_pli", "0")
1544
                    assert(numrepls>=1)
3574 helixhorne 1545
                    addcodef("%s=%d%s", pvar, initval, linestr)
3419 helixhorne 1546
                else
3574 helixhorne 1547
                    addcodef("%s=%d%s", ogv.name, initval, linestr)
3419 helixhorne 1548
                end
3427 helixhorne 1549
                return
3419 helixhorne 1550
            end
1551
 
4151 helixhorne 1552
            errprintf("duplicate definition of gamevar `%s' has different flags", identifier)
4153 helixhorne 1553
            inform.oldgv_location(identifier, true)
3427 helixhorne 1554
            return
3390 helixhorne 1555
        else
4151 helixhorne 1556
            warnprintf("duplicate definition of gamevar `%s' ignored", identifier)
4153 helixhorne 1557
            inform.oldgv_location(identifier, false)
3427 helixhorne 1558
            return
3390 helixhorne 1559
        end
1560
    end
1561
 
1562
    local ltype = g_labeltype[identifier]
1563
    if (ltype ~= nil) then
4151 helixhorne 1564
        warnprintf("Symbol `%s' already used for a defined %s.", identifier, LABEL[ltype])
4153 helixhorne 1565
        inform.olddef_location(identifier, false)
3390 helixhorne 1566
    end
1567
 
4112 helixhorne 1568
    if (isSessionVar) then
1569
        if (g_numSessionVars == conl.MAXSESSIONVARS) then
1570
            errprintf("Declared too many session gamevars (flag 1024), can have at most %d.",
1571
                      conl.MAXSESSIONVARS)
1572
            return
1573
        end
1574
 
1575
        -- Declare new session gamevar.
4570 helixhorne 1576
        local gv = { name=format("_gv._sessionVar[%d]", g_numSessionVars),
1577
                     flags=flags, loc=getLocation(), used=0 }
4112 helixhorne 1578
        g_numSessionVars = g_numSessionVars+1
4570 helixhorne 1579
 
1580
        g_gamevar[identifier] = gv;
1581
        -- Initialize it (i.e. set to the declared initial value) on first run,
1582
        -- but not from savegames.
1583
        addcodef("if _S then %s=%d end", gv.name, initval)
1584
 
4112 helixhorne 1585
        return
1586
    end
1587
 
4299 helixhorne 1588
    local gv = { name=mangle_name(identifier, "V"), flags=flags, loc=getLocation(), used=0 }
3390 helixhorne 1589
    g_gamevar[identifier] = gv
1590
 
4140 helixhorne 1591
    if (storeWithSavegames) then
1592
        addcode("if _S then")
1593
    end
3891 helixhorne 1594
 
4112 helixhorne 1595
    if (perActor) then
3798 helixhorne 1596
        addcodef("%s=_con.actorvar(%d)", gv.name, initval)
4112 helixhorne 1597
    elseif (perPlayer and g_cgopt["playervar"]) then
3842 helixhorne 1598
        gv.flags = bit.bor(gv.flags, GVFLAG.CON_PERPLAYER)
1599
        addcodef("%s=_con.playervar(%d)", gv.name, initval)
3390 helixhorne 1600
    else
3518 helixhorne 1601
        addcodef("%s=%d", gv.name, initval)
3390 helixhorne 1602
    end
3891 helixhorne 1603
 
4140 helixhorne 1604
    if (storeWithSavegames) then
1605
        addcode("end")
1606
    end
3390 helixhorne 1607
end
1608
 
3817 helixhorne 1609
function Cmd.dynamicremap()
1610
    if (g_dyntilei==nil) then
1611
        print("Using dynamic tile remapping");
3847 helixhorne 1612
        g_dyntilei = {};
3817 helixhorne 1613
    end
1614
end
1615
 
3847 helixhorne 1616
function Cmd.dynamicsoundremap()
1617
    if (g_dynsoundi==nil) then
1618
        print("Using dynamic sound remapping");
1619
        g_dynsoundi = {};
1620
    end
1621
end
1622
 
3503 helixhorne 1623
function lookup.gamearray(identifier)
1624
    local ga = g_gamearray[identifier]
1625
    if (ga == nil) then
1626
        errprintf("symbol `%s' is not a game array", identifier)
1627
        return "_INVALIDGA"
1628
    end
1629
    return ga.name
1630
end
1631
 
3842 helixhorne 1632
local function thisactor_to_pli(var)
1633
    return (var=="_aci") and "_pli" or var
1634
end
1635
 
4111 helixhorne 1636
function lookup.error_not_gamevar(identifier)
1637
    errprintf("symbol `%s' is not a game variable", identifier)
1638
    return "_INVALIDGV"
1639
end
1640
 
3469 helixhorne 1641
-- <aorpvar>: code for actor or player index
1642
function lookup.gamevar(identifier, aorpvar, writable)
3390 helixhorne 1643
    local gv = g_gamevar[identifier]
1644
 
1645
    if (gv == nil) then
4111 helixhorne 1646
        return lookup.error_not_gamevar(identifier)
3390 helixhorne 1647
    end
1648
 
3392 helixhorne 1649
    if (writable and bit.band(gv.flags, GVFLAG.READONLY) ~= 0) then
3570 helixhorne 1650
        errprintf("gamevar `%s' is read-only", identifier)
3392 helixhorne 1651
        return "_READONLYGV"
1652
    end
1653
 
4299 helixhorne 1654
    gv.used = bit.bor(gv.used, writable and 2 or 1)
1655
 
3469 helixhorne 1656
    if (bit.band(gv.flags, GVFLAG.PERACTOR)~=0) then
1657
        return format("%s[%s]", gv.name, aorpvar)
3842 helixhorne 1658
    elseif (bit.band(gv.flags, GVFLAG.CON_PERPLAYER)~=0 and g_cgopt["playervar"]) then
1659
        return format("%s[%s]", gv.name, thisactor_to_pli(aorpvar))
3390 helixhorne 1660
    else
1661
        return gv.name
1662
    end
1663
end
1664
 
1665
local function maybe_gamevar_Cmt(subj, pos, identifier)
1666
    if (g_gamevar[identifier]) then
3469 helixhorne 1667
        return true, lookup.gamevar(identifier, "_aci", false)
3390 helixhorne 1668
    end
1669
end
1670
 
1671
 
2594 helixhorne 1672
----==== patterns ====----
1673
 
1674
---- basic ones
2616 helixhorne 1675
-- Windows, *nix and Mac newlines all exist in the wild!
1676
local newline = "\r"*Pat("\n")^-1 + "\n"
1677
local EOF = Pat(-1)
2594 helixhorne 1678
local anychar = Pat(1)
1679
-- comments
1680
local comment = "/*" * match_until(anychar, "*/") * "*/"
1681
local linecomment = "//" * match_until(anychar, newline)
1682
local whitespace = Var("whitespace")
1683
local sp0 = whitespace^0
2616 helixhorne 1684
-- This "WS+" pattern matches EOF too, so that a forgotten newline at EOF is
1685
-- properly handled
1686
local sp1 = whitespace^1 + EOF
2594 helixhorne 1687
local alpha = Range("AZ", "az")  -- locale?
1688
local alphanum = alpha + Range("09")
2616 helixhorne 1689
--local alnumtok = alphanum + Set("{}/\\*-_.")  -- see isaltok() in gamedef.c
2594 helixhorne 1690
 
3390 helixhorne 1691
--- Basic lexical elements ("tokens"). See the final grammar ("Grammar") for
1692
--- their definitions.
3391 helixhorne 1693
local tok =
1694
{
1695
    maybe_minus = (Pat("-") * sp0)^-1,
1696
    number = Var("t_number"),
2594 helixhorne 1697
 
3432 helixhorne 1698
    -- Valid identifier names are disjunct from keywords!
1699
    -- XXX: CON is more permissive with identifier name characters:
3391 helixhorne 1700
    identifier = Var("t_identifier"),
3432 helixhorne 1701
    -- This one matches keywords, too:
3391 helixhorne 1702
    identifier_all = Var("t_identifier_all"),
1703
    define = Var("t_define"),
3847 helixhorne 1704
    rawdefine = Var("t_rawdefine"),
3391 helixhorne 1705
    move = Var("t_move"),
1706
    ai = Var("t_ai"),
1707
    action = Var("t_action"),
2594 helixhorne 1708
 
3432 helixhorne 1709
    -- NOTE: no chance to whitespace and double quotes in filenames:
3391 helixhorne 1710
    filename = lpeg.C((anychar-Set(" \t\r\n\""))^1),
1711
    newline_term_str = match_until(anychar, newline),
2594 helixhorne 1712
 
3391 helixhorne 1713
    rvar = Var("t_rvar"),
1714
    wvar = Var("t_wvar"),
3503 helixhorne 1715
    gamearray = Var("t_gamearray"),
3391 helixhorne 1716
 
3432 helixhorne 1717
    -- for definelevelname
3391 helixhorne 1718
    time = lpeg.C(alphanum*alphanum^-1*":"*alphanum*alphanum^-1),
3409 helixhorne 1719
 
1720
    state_ends = Pat("ends")
1721
        + POS() * "else" * sp1 * "ends"
1722
        / function(pos) pwarnprintf(pos, "stray `else' at end of state") end,
3391 helixhorne 1723
}
1724
 
1725
 
2594 helixhorne 1726
---- helper patterns / pattern constructing functions
3391 helixhorne 1727
local maybe_quoted_filename = ('"' * tok.filename * '"' + tok.filename)
2616 helixhorne 1728
-- empty string is handled too; we must not eat the newline then!
2749 helixhorne 1729
local newline_term_string = (#newline + EOF)*lpeg.Cc("")
3391 helixhorne 1730
    + (whitespace-newline)^1 * lpeg.C(tok.newline_term_str)
2594 helixhorne 1731
 
1732
 
3391 helixhorne 1733
-- (sp1 * tok.define) repeated exactly n times
2594 helixhorne 1734
local function n_defines(n)  -- works well only for small n
1735
    local pat = Pat(true)
1736
    for i=1,n do
3391 helixhorne 1737
        pat = sp1 * tok.define * pat
2594 helixhorne 1738
    end
1739
    return pat
1740
end
1741
 
1742
 
3503 helixhorne 1743
local D, R, W, I, GARI, AC, MV, AI = -1, -2, -3, -4, -5, -6, -7, -8
1744
local TOKEN_PATTERN = { [D]=tok.define, [R]=tok.rvar, [W]=tok.wvar,
1745
                        [I]=tok.identifier, [GARI]=tok.gamearray,
3391 helixhorne 1746
                        [AC]=tok.action, [MV]=tok.move, [AI]=tok.ai }
2594 helixhorne 1747
 
1748
-- Generic command pattern, types given by varargs.
1749
-- The command name to be matched is attached later.
1750
-- Example:
1751
--  "command" writtenvar readvar def def:  gencmd(W,R,D,D)
3391 helixhorne 1752
--    -->  sp1 * tok.wvar * sp1 * tok.rvar * sp1 * tok.define * sp1 * tok.define
2594 helixhorne 1753
--  "command_with_no_args":  gencmd()
1754
--    --> Pat(true)
1755
local function cmd(...)
1756
    local pat = Pat(true)
1757
    local vartypes = {...}
1758
 
1759
    for i=1,#vartypes do
2765 helixhorne 1760
        pat = pat * sp1 * assert(TOKEN_PATTERN[vartypes[i]])
2594 helixhorne 1761
    end
1762
 
1763
    return pat
1764
end
1765
 
1766
 
1767
-- The command names will be attached to the front of the patterns later!
1768
 
1769
--== Top level CON commands ==--
2616 helixhorne 1770
-- XXX: many of these are also allowed inside actors/states/events in CON.
3391 helixhorne 1771
local Couter = {
2594 helixhorne 1772
    --- 1. Preprocessor
3516 helixhorne 1773
    include = sp1 * maybe_quoted_filename
1774
        / Cmd.include,
1775
    includedefault = cmd()
1776
        / Cmd.NYI("`includedefault'"),
1777
    define = cmd(I,D)
3916 helixhorne 1778
        / Define.label,
2594 helixhorne 1779
 
1780
    --- 2. Defines and Meta-Settings
3516 helixhorne 1781
    dynamicremap = cmd()
3817 helixhorne 1782
        / Cmd.dynamicremap,
3845 helixhorne 1783
    dynamicsoundremap = cmd()
3847 helixhorne 1784
        / Cmd.dynamicsoundremap,
3516 helixhorne 1785
    setcfgname = sp1 * tok.filename
4143 helixhorne 1786
        / Cmd.setcfgname,
3516 helixhorne 1787
    setdefname = sp1 * tok.filename
1788
        / Cmd.setdefname,
1789
    setgamename = newline_term_string
1790
        / Cmd.nyi("`setgamename'"),
2594 helixhorne 1791
 
3516 helixhorne 1792
    precache = cmd(D,D,D)
4291 helixhorne 1793
        / Cmd.precache,
3259 helixhorne 1794
    scriptsize = cmd(D)
1795
        / "",  -- no-op
3516 helixhorne 1796
    cheatkeys = cmd(D,D)
1797
        / Cmd.cheatkeys,
2594 helixhorne 1798
 
3516 helixhorne 1799
    definecheat = newline_term_string  -- XXX: actually tricker syntax (TS)
1800
        , -- / Cmd.nyi("`definecheat'"),
1801
    definegamefuncname = sp1 * tok.define * newline_term_string  -- XXX: TS?
1802
        / Cmd.definegamefuncname,
1803
    definegametype = n_defines(2) * newline_term_string
3826 helixhorne 1804
        / Cmd.definegametype,
3391 helixhorne 1805
    definelevelname = n_defines(2) * sp1 * tok.filename * sp1 * tok.time * sp1 * tok.time *
3516 helixhorne 1806
        newline_term_string
1807
        / Cmd.definelevelname,
1808
    defineskillname = sp1 * tok.define * newline_term_string
1809
        / Cmd.defineskillname,
1810
    definevolumename = sp1 * tok.define * newline_term_string
1811
        / Cmd.definevolumename,
2594 helixhorne 1812
 
3516 helixhorne 1813
    definequote = sp1 * tok.define * newline_term_string
1814
        / Cmd.definequote,
1815
    defineprojectile = cmd(D,D,D)
1816
        / Cmd.defineprojectile,
3847 helixhorne 1817
    definesound = sp1 * tok.rawdefine * sp1 * maybe_quoted_filename * n_defines(5)
3516 helixhorne 1818
        / Cmd.definesound,
2594 helixhorne 1819
 
2763 helixhorne 1820
    -- NOTE: gamevar.ogg and the like is OK, too
3516 helixhorne 1821
    music = sp1 * tok.define * match_until(sp1 * tok.filename, sp1 * conl.keyword * sp1)
1822
        / Cmd.music,
2594 helixhorne 1823
 
1824
    --- 3. Game Settings
3254 helixhorne 1825
    -- gamestartup has 26/30 fixed defines, depending on 1.3D/1.5 version:
3516 helixhorne 1826
    gamestartup = (sp1 * tok.define)^26
1827
        / Cmd.gamestartup,
1828
    spritenopal = cmd(D)
4372 helixhorne 1829
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_NOPAL) end,
3516 helixhorne 1830
    spritenoshade = cmd(D)
4372 helixhorne 1831
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_NOSHADE) end,
3516 helixhorne 1832
    spritenvg = cmd(D)
4372 helixhorne 1833
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_NVG) end,
3516 helixhorne 1834
    spriteshadow = cmd(D)
4372 helixhorne 1835
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_SHADOW) end,
2594 helixhorne 1836
 
3516 helixhorne 1837
    spriteflags = cmd(D,D)  -- also see inner
4372 helixhorne 1838
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, flags, true) end,
2594 helixhorne 1839
 
1840
    --- 4. Game Variables / Arrays
3516 helixhorne 1841
    gamevar = cmd(I,D,D)
1842
        / Cmd.gamevar,
1843
    gamearray = cmd(I,D)
1844
        / Cmd.gamearray,
2594 helixhorne 1845
 
1846
    --- 5. Top level commands that are also run-time commands
3516 helixhorne 1847
    move = sp1 * tok.identifier * (sp1 * tok.define)^-2  -- hvel, vvel
3916 helixhorne 1848
        / function(...) Define.composite(LABEL.MOVE, ...) end,
3226 helixhorne 1849
 
2765 helixhorne 1850
    -- startframe, numframes, viewtype, incval, delay:
3516 helixhorne 1851
    action = sp1 * tok.identifier * (sp1 * tok.define)^-5
3916 helixhorne 1852
        / function(...) Define.composite(LABEL.ACTION, ...) end,
2594 helixhorne 1853
 
2765 helixhorne 1854
    -- action, move, flags...:
3391 helixhorne 1855
    ai = sp1 * tok.identifier * (sp1 * tok.action *
3516 helixhorne 1856
                                 (sp1 * tok.move * (sp1 * tok.define)^0)^-1
1857
                                )^-1
3916 helixhorne 1858
        / function(...) Define.composite(LABEL.AI, ...) end,
2765 helixhorne 1859
 
2594 helixhorne 1860
    --- 6. Deprecated TLCs
1861
    betaname = newline_term_string,
1862
    enhanced = cmd(D),
1863
}
1864
 
1865
 
1866
--== Run time CON commands ==--
1867
--- 1. Gamevar Operators
3854 helixhorne 1868
local Op = {}
1869
Op.var = cmd(W,D)
1870
Op.varvar = cmd(W,R)
2594 helixhorne 1871
 
3949 helixhorne 1872
function Op.var_common(thecmd, defaultop, trapop, wrapop)
1873
    local theop =
1874
        g_cgopt["trapv"] and trapop or
1875
        g_cgopt["wrapv"] and wrapop or
1876
        assert(defaultop)
1877
 
1878
    if (#theop <= 2) then
1879
        return thecmd / ("%1=%1"..theop.."%2")
3392 helixhorne 1880
    else
3949 helixhorne 1881
        return thecmd / ("%1="..theop.."(%1,%2)")
3392 helixhorne 1882
    end
1883
end
1884
 
3949 helixhorne 1885
function Op.varf(...)
1886
    return Op.var_common(Op.var, ...)
3392 helixhorne 1887
end
1888
 
3949 helixhorne 1889
function Op.varvarf(...)
1890
    return Op.var_common(Op.varvar, ...)
1891
end
1892
 
2616 helixhorne 1893
-- Allow nesting... stuff like
1894
--   ifvarl actorvar[sprite[THISACTOR].owner].burning 0
1895
-- is kinda breaking the classic "no array nesting" rules
3324 helixhorne 1896
-- (if there ever were any) but making our life harder else.
3391 helixhorne 1897
local arraypat = sp0 * "[" * sp0 * tok.rvar * sp0 * "]"
4287 helixhorne 1898
-- For {get,set}userdef:
1899
local arraypat_maybe_empty = sp0 * "[" * sp0 * (tok.rvar * sp0)^-1 * "]"
2616 helixhorne 1900
 
3854 helixhorne 1901
-- Table of various patterns that are (parts of) more complex inner commands.
1902
local patt = {}
1903
 
3432 helixhorne 1904
-- Have to bite the bullet here and list actor/player members with second
1905
-- parameters, even though it's ugly to make it part of the syntax.  Also,
1906
-- stuff like
2616 helixhorne 1907
--   actor[xxx].loogiex parm2 x
3432 helixhorne 1908
-- will be wrongly accepted at the parsing stage (loogiex is player's member)
1909
-- because we don't discriminate between actor and player here.
3854 helixhorne 1910
patt.parm2member = lpeg.C(Pat("htg_t") + "loogiex" + "loogiey" + "ammo_amount" +
1911
                          "weaprecs" + "gotweapon" + "pals" + "max_ammo_amount") * sp1 * tok.rvar
2594 helixhorne 1912
 
3854 helixhorne 1913
-- The member name must match keywords, too (_all), because e.g. cstat is a
1914
-- member of sprite[].
1915
patt.bothmember = sp0 * "." * sp0 * lpeg.Ct(patt.parm2member + tok.identifier_all)
1916
patt.singlemember = sp0 * "." * sp0 * tok.identifier_all
2594 helixhorne 1917
 
3854 helixhorne 1918
patt.cmdgetstruct =  -- get<structname>[<idx>].<member> (<parm2>)? <<var>>
1919
    arraypat * patt.bothmember * sp1 * tok.wvar
2594 helixhorne 1920
 
3854 helixhorne 1921
patt.cmdsetstruct =  -- set<structname>[<idx>].<<member>> (<parm2>)? <var>
1922
    arraypat * patt.bothmember * sp1 * tok.rvar
2594 helixhorne 1923
 
3854 helixhorne 1924
patt.cmdgetperxvar =  -- get<actor/player>var[<idx>].<varname> <<var>>
1925
    arraypat * patt.singlemember * sp1 * tok.wvar
2594 helixhorne 1926
 
3854 helixhorne 1927
patt.cmdsetperxvar = -- set<actor/player>var[<idx>].<<varname>> <var>
1928
    arraypat * patt.singlemember * sp1 * tok.rvar
1929
 
3433 helixhorne 1930
-- Function generating code for a struct read/write access.
1931
local function StructAccess(Structname, writep, index, membertab)
1932
    assert(type(membertab)=="table")
3473 helixhorne 1933
    -- Lowercase the member name for CON compatibility
1934
    local member, parm2 = membertab[1]:lower(), membertab[2]
2594 helixhorne 1935
 
3454 helixhorne 1936
    local MemberCode = conl.StructAccessCode[Structname] or conl.StructAccessCode2[Structname]
3433 helixhorne 1937
    -- Look up array+member name first, e.g. "spriteext[%s].angoff".
3454 helixhorne 1938
    local armembcode = MemberCode[member]
3433 helixhorne 1939
    if (armembcode == nil) then
3473 helixhorne 1940
        errprintf("%s: invalid %s member `.%s'", g_lastkw, Structname, member)
3433 helixhorne 1941
        return "_MEMBINVALID"
1942
    end
3432 helixhorne 1943
 
4195 helixhorne 1944
    -- Function checking a literal number for being OK for assignment to this
1945
    -- member. Can also be a table {min, max}. See con_lang.lua, LITERAL_CHECKING.
1946
    local lit_ok_func_or_table
1947
 
3433 helixhorne 1948
    if (type(armembcode)=="table") then
1949
        -- Read and write accesses differ.
4195 helixhorne 1950
        if (writep) then
1951
            lit_ok_func_or_table = armembcode[3]
1952
        end
3433 helixhorne 1953
        armembcode = armembcode[writep and 2 or 1]
1954
        if (armembcode==nil) then
1955
            errprintf("%s access to %s[].%s is not available",
1956
                      writep and "write" or "read", Structname, member)
1957
            return "_MEMBNOACCESS"
3432 helixhorne 1958
        end
3433 helixhorne 1959
    end
3432 helixhorne 1960
 
3473 helixhorne 1961
    if (Structname~="userdef") then
1962
        -- Count number of parameters ("%s"), don't count "%%s".
1963
        local _, numparms = armembcode:gsub("[^%%]%%s", "", 2)
1964
        if (#membertab ~= numparms) then
1965
            local nums = { "one", "two" }
1966
            errprintf("%s[].%s has %s parameter%s, but %s given", Structname,
1967
                      member, nums[numparms], numparms==1 and "" or "s",
1968
                      nums[#membertab])
1969
            return "_MEMBINVPARM"
1970
        end
3433 helixhorne 1971
    end
3432 helixhorne 1972
 
3516 helixhorne 1973
    -- THISACTOR special meanings
1974
    if (Structname=="player" or Structname=="input") then
1975
        index = thisactor_to_pli(index)
1976
    elseif (Structname=="sector") then
1977
        if (index=="_aci") then
1978
            index = SPS".sectnum"
1979
        end
1980
    end
1981
 
3444 helixhorne 1982
    -- METHOD_MEMBER
1983
    local ismethod = (armembcode:find("%%s",1,true)~=nil)
1984
    -- If ismethod is true, then the formatted string will now have an "%s"
3561 helixhorne 1985
    local code
3473 helixhorne 1986
 
1987
    if (Structname=="userdef") then
1988
--        assert(index==nil)
1989
        assert(parm2==nil)
3561 helixhorne 1990
        code = format(armembcode, parm2)
3473 helixhorne 1991
    else
3561 helixhorne 1992
        code = format(armembcode, index, parm2)
3473 helixhorne 1993
    end
3561 helixhorne 1994
 
1995
    if (csapp()) then
1996
        if (Structname=="player") then
1997
            code = code:gsub("^player%[_pli%]", "_ps")
1998
        elseif (Structname=="sprite") then
1999
            code = code:gsub("^actor%[_aci%]", "_a")
2000
            code = code:gsub("^sprite%[_aci%]", "_spr")
2001
        end
2002
    end
2003
 
4195 helixhorne 2004
    return code, ismethod, lit_ok_func_or_table
3433 helixhorne 2005
end
2006
 
2007
function lookup.array_expr(writep, structname, index, membertab)
2008
    if (conl.StructAccessCode[structname] == nil) then
3503 helixhorne 2009
        -- Try a gamearray
3516 helixhorne 2010
        local ganame = g_gamearray[structname] and lookup.gamearray(structname)
2011
        if (ganame == nil) then
3503 helixhorne 2012
            if (structname=="actorvar") then
2013
                -- actorvar[] inline array expr
2014
                -- XXX: kind of CODEDUP with GetOrSetPerxvarCmd() factory
2015
                local gv = g_gamevar[structname]
2016
                if (gv and bit.band(gv.flags, GVFLAG.PERX_MASK)~=GVFLAG.PERACTOR) then
3570 helixhorne 2017
                    errprintf("gamevar `%s' is not per-actor", structname, "actor")
3503 helixhorne 2018
                end
2019
 
2020
                if (membertab == nil) then
2021
                    errprintf("actorvar[] requires a pseudo member (gamevar) name")
2022
                    return "_INVALIDAV"
2023
                end
2024
 
2025
                if (#membertab > 1) then
2026
                    errprintf("actorvar[] cannot be used with a second parameter")
2027
                    return "_INVALIDAV"
2028
                end
2029
 
4299 helixhorne 2030
                if (gv) then
2031
                    gv.used = bit.bor(gv.used, writep and 2 or 1)
2032
                end
2033
 
3503 helixhorne 2034
                assert(#membertab == 1)
2035
                return lookup.gamevar(membertab[1], index, writep)
2036
            end
2037
 
2038
            errprintf("symbol `%s' is neither a struct nor a gamearray", structname)
2039
            return "_INVALIDAR"
2040
        end
2041
 
2042
        if (membertab ~= nil) then
2043
            errprintf("gamearrays cannot be indexed with member names")
2044
            return "_INVALIDAR"
2045
        end
2046
 
3516 helixhorne 2047
        assert(type(ganame)=="string")
2048
        return format("%s[%s]", ganame, index)
3433 helixhorne 2049
    end
2050
 
3444 helixhorne 2051
    local membercode, ismethod = StructAccess(structname, writep, index, membertab)
2052
    -- Written METHOD_MEMBER syntax not supported as "qwe:method(asd) = val"
2053
    -- isn't valid Lua syntax.
2054
    assert(not (writep and ismethod))
2055
    return membercode
3433 helixhorne 2056
end
2057
 
2058
local Access =
2059
{
2060
    sector = function(...) return StructAccess("sector", ...) end,
2061
    wall = function(...) return StructAccess("wall", ...) end,
2062
    xsprite = function(...) return StructAccess("sprite", ...) end,
2063
    player = function(...) return StructAccess("player", ...) end,
3454 helixhorne 2064
 
2065
    tspr = function(...) return StructAccess("tspr", ...) end,
3463 helixhorne 2066
    projectile = function(...) return StructAccess("projectile", ...) end,
3466 helixhorne 2067
    thisprojectile = function(...) return StructAccess("thisprojectile", ...) end,
3473 helixhorne 2068
    userdef = function(...) return StructAccess("userdef", ...) end,
3477 helixhorne 2069
    input = function(...) return StructAccess("input", ...) end,
3432 helixhorne 2070
}
2071
 
3473 helixhorne 2072
local function GetStructCmd(accessfunc, pattern)
3854 helixhorne 2073
    return (pattern or patt.cmdgetstruct) /
3469 helixhorne 2074
      function(idx, memb, var)
3432 helixhorne 2075
        return format("%s=%s", var, accessfunc(false, idx, memb))
3469 helixhorne 2076
      end
3432 helixhorne 2077
end
2078
 
3473 helixhorne 2079
local function SetStructCmd(accessfunc, pattern)
3469 helixhorne 2080
    local function capfunc(idx, memb, var)
4195 helixhorne 2081
        -- litok: function or table
2082
        local membercode, ismethod, litok = accessfunc(true, idx, memb)
2083
 
2084
        -- Light static checking for literal values being OK for member
2085
        -- assignment. LITERAL_CHECKING.
2086
        if (type(var)=="number" and litok) then
2087
            if (type(litok)=="table" and not (var>=litok[1] and var<=litok[2]) or
2088
                    type(litok)=="function" and not litok(var)) then
2089
                local member = memb[1]:lower()
2090
                warnprintf("setting member '.%s' to %d will fail at game time",
2091
                           member, var)
2092
            end
3626 helixhorne 2093
        end
4195 helixhorne 2094
 
3444 helixhorne 2095
        if (ismethod) then
2096
            -- METHOD_MEMBER syntax
2097
 
2098
            -- BE EXTRA CAREFUL! We must be sure that percent characters have
2099
            -- not been smuggled into the member code string via variable names
2100
            -- etc.
2101
            local _, numpercents = membercode:gsub("%%", "", 2)
2102
            assert(numpercents==1)
2103
 
2104
            return format(membercode, var)
2105
        else
2106
            return format("%s=%s", membercode, var)
2107
        end
3439 helixhorne 2108
    end
3469 helixhorne 2109
 
3854 helixhorne 2110
    return (pattern or patt.cmdsetstruct) / capfunc
3439 helixhorne 2111
end
3432 helixhorne 2112
 
3469 helixhorne 2113
-- <Setp>: whether the perxvar is set
2114
local function GetOrSetPerxvarCmd(Setp, Actorp)
2115
    local EXPECTED_PERX_BIT = Actorp and GVFLAG.PERACTOR or GVFLAG.PERPLAYER
3854 helixhorne 2116
    local pattern = (Setp and patt.cmdsetperxvar or patt.cmdgetperxvar)
3439 helixhorne 2117
 
3469 helixhorne 2118
    local function capfunc(idx, perxvarname, var)
2119
        local gv = g_gamevar[perxvarname]
2120
        if (gv and bit.band(gv.flags, GVFLAG.PERX_MASK)~=EXPECTED_PERX_BIT) then
3570 helixhorne 2121
            -- [gs]set*var for wrong gamevar type. See if it's a getactorvar,
3909 helixhorne 2122
            -- in which case we may only warn and access that instead. Note
2123
            -- that accesses of player gamevars with actor indices are usually
2124
            -- meaningless.
3570 helixhorne 2125
            local warnp = not Setp and Actorp and not g_warn["error-bad-getactorvar"]
2126
            local xprintf = warnp and warnprintf or errprintf
2127
 
2128
            xprintf("gamevar `%s' is not per-%s", perxvarname, Actorp and "actor" or "player")
4266 helixhorne 2129
 
2130
            if (warnp and bit.band(gv.flags, GVFLAG.PERX_MASK)==GVFLAG.PERPLAYER
2131
                    and g_cgopt["bad-getactorvar-use-pli"]) then
2132
                -- For getactorvar[] accesses to per-player gamevars, if
2133
                -- -fbad-getactorvar-use-pli is provided, use current player
2134
                -- index, for compatibility with CON.
2135
                idx = "_pli"
2136
            end
3469 helixhorne 2137
        end
2138
 
3516 helixhorne 2139
        if (not Actorp) then
2140
            -- THISACTOR -> player index for {g,s}etplayervar
2141
            idx = thisactor_to_pli(idx)
2142
        end
2143
 
4299 helixhorne 2144
        if (gv) then
2145
            gv.used = bit.bor(gv.used, Setp and 2 or 1)
2146
        end
2147
 
3469 helixhorne 2148
        if (Setp) then
2149
            return format("%s=%s", lookup.gamevar(perxvarname, idx, true), var)
2150
        else
2151
            return format("%s=%s", var, lookup.gamevar(perxvarname, idx, false))
2152
        end
2153
    end
2154
 
2155
    return pattern / capfunc
2156
end
2157
 
2158
 
3507 helixhorne 2159
local function n_s_fmt(n)
2160
    return string.rep("%s,", n-1).."%s"
2161
end
2162
 
3431 helixhorne 2163
-- Various inner command handling functions / string capture strings.
3391 helixhorne 2164
local handle =
2165
{
3431 helixhorne 2166
    NYI = function()
2167
        errprintf("command `%s' not yet implemented", g_lastkw)
3523 helixhorne 2168
        return ""
3431 helixhorne 2169
    end,
2170
 
3516 helixhorne 2171
    dynNYI = function()
3882 helixhorne 2172
        return format([[print(%q..":%d: `%s' not yet implemented")]],
3516 helixhorne 2173
                      g_filename, getlinecol(g_lastkwpos), g_lastkw)
2174
    end,
2175
 
3431 helixhorne 2176
    addlog = function()
3882 helixhorne 2177
        return format("print(%q..':%d: addlog')", g_filename, getlinecol(g_lastkwpos))
3431 helixhorne 2178
    end,
2179
 
2180
    addlogvar = function(val)
3882 helixhorne 2181
        return format("printf(%q..':%d: addlogvar %%s', %s)", g_filename, getlinecol(g_lastkwpos), val)
3431 helixhorne 2182
    end,
2183
 
2184
    debug = function(val)
3882 helixhorne 2185
        return format("print(%q..':%d: debug %d')", g_filename, getlinecol(g_lastkwpos), val)
3431 helixhorne 2186
    end,
2187
 
3524 helixhorne 2188
    getzrange = function(...)
2189
        local v = {...}
2190
        assert(#v == 10)  -- 4R 4W 2R
2191
        return format("%s,%s,%s,%s=_con._getzrange(%s,%s,%s,%s,%s,%s)",
2192
                      v[5], v[6], v[7], v[8],  -- outargs
2193
                      v[1], v[2], v[3], v[4], v[9], v[10])  -- inargs
2194
    end,
2195
 
3491 helixhorne 2196
    hitscan = function(...)
2197
        local v = {...}
2198
        assert(#v == 14)  -- 7R 6W 1R
2199
        local vals = {
2200
            v[8], v[9], v[10], v[11], v[12], v[13],  -- outargs
2201
            v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[14]  -- inargs
2202
        }
2203
        return format("%s,%s,%s,%s,%s,%s=_con._hitscan(%s,%s,%s,%s,%s,%s,%s,%s)",
2204
                     unpack(vals))
2205
    end,
2206
 
2207
    neartag = function(...)
2208
        local v = {...}
2209
        assert(#v == 11)  -- 5R 4W 2R
2210
        local vals = {
2211
            v[6], v[7], v[8], v[9],  -- outargs
2212
            v[1], v[2], v[3], v[4], v[5], v[10], v[11]  -- inargs
2213
        }
2214
        return format("%s,%s,%s,%s=_con._neartag(%s,%s,%s,%s,%s,%s,%s)",
2215
                      unpack(vals))
2216
    end,
2217
 
3812 helixhorne 2218
    clipmove = function(noslidep, ...)
2219
        local v = {...}
2220
        assert(#v == 11)  -- 3W 1R 1W 6R
2221
        local vals = {
2222
            v[1], v[2], v[3], v[5],  -- outargs
2223
            v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10], v[11],  -- inargs
2224
            noslidep
2225
        }
2226
        return format("%s,%s,%s,%s=_con._clipmovex("..n_s_fmt(11)..")",
2227
                     unpack(vals))
2228
    end,
2229
 
3391 helixhorne 2230
    palfrom = function(...)
2231
        local v = {...}
2232
        return format(PLS":_palfrom(%d,%d,%d,%d)",
2233
                      v[1] or 0, v[2] or 0, v[3] or 0, v[4] or 0)
2234
    end,
3253 helixhorne 2235
 
3507 helixhorne 2236
    qsprintf = function(qdst, qsrc, ...)
2237
        local codes = {...}
3805 helixhorne 2238
        return format("_con._qsprintf(%s,%s%s%s)", qdst, qsrc,
3523 helixhorne 2239
                      #codes>0 and "," or "", table.concat(codes, ','))
3507 helixhorne 2240
    end,
2241
 
3391 helixhorne 2242
    move = function(mv, ...)
2243
        local flags = {...}
2244
        return format(ACS":set_move(%s,%d)", mv, (flags[1] and bit.bor(...)) or 0)
2245
    end,
3259 helixhorne 2246
 
3480 helixhorne 2247
    rotatesprite = function(...)
3770 helixhorne 2248
        return format("_con._rotspr(%s,%s,%s,%s,%s,%s,%s,%s,0,%s,%s,%s,%s)", ...)
3480 helixhorne 2249
    end,
2250
 
3611 hendricks2 2251
    rotatesprite16 = function(...)  -- (orientation|ROTATESPRITE_FULL16)
3770 helixhorne 2252
        return format("_con._rotspr(%s,%s,%s,%s,%s,%s,%s,_bor(%s,2048),0,%s,%s,%s,%s)", ...)
3480 helixhorne 2253
    end,
2254
 
3610 hendricks2 2255
    rotatespritea = function(...)
3770 helixhorne 2256
        return format("_con._rotspr(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", ...)
3610 hendricks2 2257
    end,
2258
 
3940 helixhorne 2259
    -- <fmt>: format string, number of %s's must match number of varargs
2260
    arraycmd = function(fmt, dstargi, ...)
2261
        local args = {...}
2262
        if (issysgar(args[dstargi])) then
2263
            errprintf("%s: system gamearray not supported", g_lastkw)
2264
        end
2265
        return format(fmt, ...)
2266
    end,
2267
 
3533 helixhorne 2268
    -- readgamevar or savegamevar
2269
    RSgamevar = function(identifier, dosave)
2270
        -- check identifier for sanity
3534 helixhorne 2271
        if (not identifier:match("^[A-Za-z][A-Za-z0-9_%-]*$")) then
2272
            errprintf("%s: bad identifier `%s' for config file persistence",
2273
                      g_lastkw, identifier)
3533 helixhorne 2274
            return "_BADRSGV()"
2275
        end
2276
 
2277
        local gv = g_gamevar[identifier]
4111 helixhorne 2278
        if (gv == nil) then
2279
            return lookup.error_not_gamevar(identifier)
2280
        end
3533 helixhorne 2281
 
3570 helixhorne 2282
        -- For per-actor or per-player gamevars, the value at the current actor or
2283
        -- player index gets saved / loaded.
2284
        local gvkind = bit.band(gv.flags, GVFLAG.PERX_MASK)
2285
        local index = (gvkind==GVFLAG.PERACTOR) and "_aci" or
2286
            (gvkind==GVFLAG.PERPLAYER) and "_pli" or nil
2287
 
3533 helixhorne 2288
        -- NOTE: more strict than C-CON: we require the gamevar being writable
2289
        -- even if we're saving it.
3570 helixhorne 2290
        local code = lookup.gamevar(identifier, index, true)
3533 helixhorne 2291
 
4299 helixhorne 2292
        gv.used = bit.bor(gv.used, not dosave and 2 or 1)
2293
 
3533 helixhorne 2294
        if (dosave) then
2295
            return format("_con._savegamevar(%q,%s)", identifier, code)
2296
        else
4256 helixhorne 2297
            return format("%s=_con._readgamevar(%q,%s)", code, identifier, code)
3533 helixhorne 2298
        end
2299
    end,
2300
 
3391 helixhorne 2301
    state = function(statename)
2302
        if (g_funcname[statename]==nil) then
3656 helixhorne 2303
            local warn = not g_cgopt["error-nostate"]
3629 helixhorne 2304
            local xprintf = warn and warnprintf or errprintf
2305
 
2306
            xprintf("state `%s' not found.", statename)
2307
            return warn and "" or "_NULLSTATE()"
3391 helixhorne 2308
        end
2309
        return format("%s(_aci,_pli,_dist)", g_funcname[statename])
2310
    end,