Subversion Repositories eduke32

Rev

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