Subversion Repositories eduke32

Rev

Rev 4962 | 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
3894 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
3894 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
 
4977 hendricks2 1275
function Cmd.undefinevolume(vol)
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_UndefineVolume(vol)
1283
    end
1284
end
1285
 
3516 helixhorne 1286
function Cmd.definegamefuncname(idx, name)
1287
    local NUMGAMEFUNCTIONS = (ffi and ffiC.NUMGAMEFUNCTIONS or 56)
1288
    if (not (idx >= 0 and idx < NUMGAMEFUNCTIONS)) then
1289
        errprintf("function number exceeds number of game functions.")
1290
        return
1291
    end
1292
 
1293
    assert(type(name)=="string")
1294
    -- XXX: in place of C-CON's "invalid character in function name" report:
3656 helixhorne 1295
    name = name:gsub("[^A-Za-z0-9]", "_")
3516 helixhorne 1296
 
1297
    if (ffi) then
1298
        ffiC.C_DefineGameFuncName(idx, name)
1299
    end
1300
end
1301
 
3826 helixhorne 1302
function Cmd.definegametype(idx, flags, name)
1303
    if (not (idx >= 0 and idx < conl.MAXGAMETYPES)) then
1304
        errprintf("gametype number exceeds maximum gametype count.")
1305
        return
1306
    end
1307
 
1308
    if (ffi) then
1309
        ffiC.C_DefineGameType(idx, flags, name)
1310
    end
1311
end
1312
 
3373 helixhorne 1313
-- strip whitespace from front and back
1314
local function stripws(str)
1315
    return str:match("^%s*(.*)%s*$")
1316
end
1317
 
3391 helixhorne 1318
function Cmd.definequote(qnum, quotestr)
3357 helixhorne 1319
    if (not (qnum >= 0 and qnum < conl.MAXQUOTES)) then
1320
        errprintf("quote number is negative or exceeds limit of %d.", conl.MAXQUOTES-1)
3656 helixhorne 1321
        return ""
3357 helixhorne 1322
    end
2764 helixhorne 1323
 
3373 helixhorne 1324
    quotestr = stripws(quotestr)
3357 helixhorne 1325
 
3511 helixhorne 1326
    if (#quotestr >= conl.MAXQUOTELEN) then
1327
        -- NOTE: Actually, C_DefineQuote takes care of this! That is,
1328
        -- standalone, the string isn't truncated.
1329
        warnprintf("quote %d truncated to %d characters.", qnum, conl.MAXQUOTELEN-1)
1330
    end
1331
 
3357 helixhorne 1332
    if (ffi) then
1333
        ffiC.C_DefineQuote(qnum, quotestr)
1334
    end
1335
 
1336
    g_data.quote[qnum] = quotestr
3656 helixhorne 1337
    return ""
2764 helixhorne 1338
end
1339
 
3865 helixhorne 1340
local PROJ = {}
1341
for key, val in pairs(conl.PROJ) do
1342
    -- Strip "PROJ_"
1343
    PROJ[key:sub(6)] = val
1344
end
1345
 
3463 helixhorne 1346
function Cmd.defineprojectile(tilenum, what, val)
3865 helixhorne 1347
    local ok = check.tile_idx(tilenum)
3463 helixhorne 1348
 
3865 helixhorne 1349
    if (what==PROJ.WORKSLIKE) then
4152 helixhorne 1350
        check.reserved_bits(val, 2^21-1, "for PROJ_WORKSLIKE")
3865 helixhorne 1351
    elseif (what==PROJ.SOUND or what==PROJ.ISOUND or what==PROJ.BSOUND) then
1352
        ok = ok and (val==-1 or check.sound_idx(val))
1353
    elseif (what==PROJ.SPAWNS or what==PROJ.DECAL or what==PROJ.TRAIL) then
1354
        ok = ok and (val==-1 or check.tile_idx(val))
1355
    end
1356
 
3516 helixhorne 1357
    if (ffi and ok) then
3463 helixhorne 1358
        ffiC.C_DefineProjectile(tilenum, what, val)
1359
    end
1360
end
1361
 
4372 helixhorne 1362
-- <override>: override-set flags? The default is to bitwise OR with existing.
1363
function Cmd.xspriteflags(tilenum, flags, override)
3865 helixhorne 1364
    local ok = check.tile_idx(tilenum)
4372 helixhorne 1365
    check.reserved_bits(flags, conl.user_sflags, "for sprite flags")
3516 helixhorne 1366
 
4373 helixhorne 1367
    local loc = g_code.aflagsloc[tilenum]
1368
 
1369
    if (override and loc ~= nil) then
1370
        warnprintf("'spriteflags' after %s %d", loc[4], tilenum)
1371
        contprintf(false, "at %s %d:%d", loc[1], loc[2], loc[3])
1372
    end
1373
 
4841 helixhorne 1374
    -- Mark the last 'spriteflags' or 'sprite*' directive for the given actor.
4373 helixhorne 1375
    g_code.aflagsloc[tilenum] = getLocation(format("'%s' for actor", g_lastkw), pos)
1376
 
3516 helixhorne 1377
    if (ffi and ok) then
4372 helixhorne 1378
        local tile = ffiC.g_tile[tilenum]
1379
        tile._flags = bit.bor(override and 0 or tile._flags, flags)
3516 helixhorne 1380
    end
1381
end
1382
 
4291 helixhorne 1383
function Cmd.precache(tilenum0, tilenum1, flagnum)
1384
    local ok = check.tile_idx(tilenum0) and check.tile_idx(tilenum1)
1385
 
1386
    if (ffi and ok) then
4372 helixhorne 1387
        local tile = ffiC.g_tile[tilenum0]
1388
        tile._cacherange = tilenum1;
4291 helixhorne 1389
        if (flagnum) then
4372 helixhorne 1390
            tile._flags = bit.bor(tile._flags, conl.SFLAG.SFLAG_CACHE)
4291 helixhorne 1391
        end
1392
    end
1393
end
1394
 
3516 helixhorne 1395
function Cmd.cheatkeys(sc1, sc2)
1396
    if (ffi) then
1397
        ffiC.CheatKeys[0] = sc1
1398
        ffiC.CheatKeys[1] = sc2
1399
    end
1400
end
1401
 
1402
function Cmd.setdefname(filename)
1403
    assert(type(filename)=="string")
1404
    if (ffi) then
1405
        if (ffiC.C_SetDefName(filename) ~= 0) then
1406
            error("OUT OF MEMORY", 0)
1407
        end
1408
    end
1409
end
1410
 
4143 helixhorne 1411
function Cmd.setcfgname(filename)
1412
    assert(type(filename)=="string")
1413
    if (ffi) then
1414
        ffiC.C_SetCfgName(filename)
1415
    end
1416
end
1417
 
3391 helixhorne 1418
function Cmd.gamestartup(...)
3343 helixhorne 1419
    local args = {...}
2764 helixhorne 1420
 
3806 helixhorne 1421
    -- TODO: PRE13: detection of other g_scriptVersion.
3343 helixhorne 1422
    if (#args ~= 26 and #args ~= 30) then
2764 helixhorne 1423
        errprintf("must pass either 26 (1.3D) or 30 (1.5) values")
1424
        return
1425
    end
1426
 
3355 helixhorne 1427
    if (ffi) then
3343 helixhorne 1428
        -- running from EDuke32
3355 helixhorne 1429
        if (#args == 30) then
3343 helixhorne 1430
            ffiC.g_scriptVersion = 14
1431
        end
1432
        local params = ffi.new("int32_t [30]", args)
1433
        ffiC.G_DoGameStartup(params)
1434
    end
1435
 
1436
    g_data.startup = args  -- TODO: sanity-check them
2764 helixhorne 1437
end
1438
 
3847 helixhorne 1439
function Cmd.definesound(sndlabel, fn, ...)
1440
    local sndnum
1441
 
1442
    if (type(sndlabel)=="string") then
4860 helixhorne 1443
        -- HANDLE_RAWDEFINE
3847 helixhorne 1444
        local pos, minus, label = sndlabel:match("(.-):(.-):(.+)")
1445
        sndnum = lookup.defined_label(tonumber(pos), minus, label)
1446
 
1447
        if (ffi and g_dynsoundi and (sndnum>=0 and sndnum<conl.MAXSOUNDS)) then
1448
            dynmap.maybe_init(g_dynsoundi, ffiC.g_dynSoundList)
1449
            dynmap.maybe_process(g_dynsoundi, ffiC.g_dynSoundList, label, sndnum)
1450
        end
1451
    else
1452
        assert(type(sndlabel)=="number")
1453
        sndnum = sndlabel
1454
    end
1455
 
3357 helixhorne 1456
    if (not (sndnum >= 0 and sndnum < conl.MAXSOUNDS)) then
3373 helixhorne 1457
        errprintf("sound number is negative or exceeds sound limit of %d", conl.MAXSOUNDS-1)
2764 helixhorne 1458
        return
1459
    end
1460
 
3882 helixhorne 1461
    local params = {...}  -- TODO: sanity-check them some more
4152 helixhorne 1462
    check.reserved_bits(params[4], 31+128, "for sound flags")
3882 helixhorne 1463
 
3355 helixhorne 1464
    if (ffi) then
1465
        local cparams = ffi.new("int32_t [5]", params)
1466
        assert(type(fn)=="string")
1467
        ffiC.C_DefineSound(sndnum, fn, cparams)
1468
    end
2764 helixhorne 1469
 
1470
    g_data.sound[sndnum] = { fn=fn, params=params }
1471
end
1472
 
3391 helixhorne 1473
function Cmd.music(volnum, ...)
3817 helixhorne 1474
    if (not (volnum >= 0 and volnum <= conl.MAXVOLUMES+1)) then
4588 helixhorne 1475
        -- The passed volume number is 1-based.
1476
        -- Both 0 and MAXVOLUMES+1 means "special music"
1477
        errprintf("volume number must be between 0 and MAXVOLUMES+1=%d", conl.MAXVOLUMES+1)
3558 helixhorne 1478
        return
4588 helixhorne 1479
    elseif (volnum == conl.MAXVOLUMES+1) then
1480
        warnprintf("volume number MAXVOLUMES+1 is discouraged, use 0 instead")
3558 helixhorne 1481
    end
1482
 
4588 helixhorne 1483
    if (volnum == 0) then
1484
        volnum = conl.MAXVOLUMES+1  -- special music
1485
    end
1486
 
2764 helixhorne 1487
    local filenames = {...}
4588 helixhorne 1488
    local MAXFNS = conl.MAXLEVELS
2764 helixhorne 1489
 
3567 helixhorne 1490
    if (#filenames > MAXFNS) then
1491
        warnprintf("ignoring extraneous %d music file names", #filenames-MAXFNS)
1492
        for i=MAXFNS+1,#filenames do
2764 helixhorne 1493
            filenames[i] = nil
1494
        end
1495
    end
1496
 
3373 helixhorne 1497
    if (ffi) then
1498
        for i=1,#filenames do
1499
            assert(type(filenames[i])=="string")
3558 helixhorne 1500
            ffiC.C_DefineMusic(volnum-1, i-1, "/"..filenames[i])
3373 helixhorne 1501
        end
1502
    end
1503
 
2764 helixhorne 1504
    g_data.music[volnum] = filenames
1505
end
1506
 
1507
 
3390 helixhorne 1508
--- GAMEVARS / GAMEARRAYS
1509
 
3503 helixhorne 1510
function Cmd.gamearray(identifier, initsize)
4152 helixhorne 1511
    if (check.sysvar_def_attempt(identifier)) then
3503 helixhorne 1512
        return
1513
    end
1514
 
3533 helixhorne 1515
    if (not (initsize >= 0 and initsize < 0x7fffffff)) then
3503 helixhorne 1516
        errprintf("invalid initial size %d for gamearray `%s'", initsize, identifier)
1517
        return
1518
    end
1519
 
1520
    local oga = g_gamearray[identifier]
1521
    if (oga) then
3562 helixhorne 1522
        if (oga.sysp) then
1523
            errprintf("attempt to define system gamearray `%s'", identifier)
1524
            return
1525
        elseif (initsize ~= oga.size) then
3503 helixhorne 1526
            errprintf("duplicate gamearray definition `%s' has different size", identifier)
1527
            return
1528
        else
1529
            warnprintf("duplicate gamearray definition `%s' ignored", identifier)
1530
            return
1531
        end
1532
    end
1533
 
1534
    if (g_gamevar[identifier]) then
1535
        warnprintf("symbol `%s' already used for game variable", identifier)
4153 helixhorne 1536
        inform.oldgv_location(identifier, false)
3503 helixhorne 1537
    end
1538
 
1539
    local ga = { name=mangle_name(identifier, "A"), size=initsize }
1540
    g_gamearray[identifier] = ga
1541
 
3891 helixhorne 1542
    addcode("if _S then")
3518 helixhorne 1543
    addcodef("%s=_con._gamearray(%d)", ga.name, initsize)
3891 helixhorne 1544
    addcode("end")
3503 helixhorne 1545
end
1546
 
3391 helixhorne 1547
function Cmd.gamevar(identifier, initval, flags)
4152 helixhorne 1548
    if (check.sysvar_def_attempt(identifier)) then
3503 helixhorne 1549
        return
1550
    end
1551
 
3431 helixhorne 1552
    if (bit.band(flags, bit.bnot(GVFLAG.USER_MASK)) ~= 0) then
3390 helixhorne 1553
        -- TODO: a couple of the presumably safe ones
3431 helixhorne 1554
        errprintf("gamevar flags other than 1, 2, 1024 or 131072: NYI or forbidden")
3427 helixhorne 1555
        return
3390 helixhorne 1556
    end
1557
 
4112 helixhorne 1558
    local perPlayer = (bit.band(flags, GVFLAG.PERPLAYER) ~= 0)
1559
    local perActor = (bit.band(flags, GVFLAG.PERACTOR) ~= 0)
1560
 
1561
    if (perPlayer and perActor) then
3390 helixhorne 1562
        errprintf("invalid gamevar flags: must be either PERPLAYER or PERACTOR, not both")
3427 helixhorne 1563
        return
3390 helixhorne 1564
    end
1565
 
1566
    local ogv = g_gamevar[identifier]
4570 helixhorne 1567
    -- handle NORESET or NODEFAULT
4112 helixhorne 1568
    local isSessionVar = (bit.band(flags, GVFLAG.NODEFAULT) ~= 0)
4140 helixhorne 1569
    local storeWithSavegames = (bit.band(flags, GVFLAG.NORESET) == 0)
3390 helixhorne 1570
 
4112 helixhorne 1571
    if (isSessionVar and (perPlayer or perActor)) then
1572
        if (ogv == nil) then  -- warn only once per gamevar
4140 helixhorne 1573
            warnprintf("per-%s session gamevar `%s': NYI, made %s",
4112 helixhorne 1574
                       perPlayer and "player" or "actor",
4140 helixhorne 1575
                       identifier,
1576
                       perPlayer and "global" or "non-session")
4112 helixhorne 1577
        end
1578
 
1579
        if (perActor) then
1580
            flags = bit.band(flags, bit.bnot(GVFLAG.NODEFAULT))
1581
            isSessionVar = false
1582
        elseif (perPlayer) then
1583
            flags = bit.band(flags, bit.bnot(GVFLAG.PERPLAYER))
1584
            perPlayer = false
1585
        end
1586
    end
1587
 
3390 helixhorne 1588
    if (ogv ~= nil) then
4581 helixhorne 1589
        local oflags = bit.band(ogv.flags, bit.bnot(GVFLAG.CON_PERPLAYER))
1590
 
3419 helixhorne 1591
        if (oflags ~= flags) then
4112 helixhorne 1592
            if (bit.band(oflags, GVFLAG.SYSTEM) ~= 0 and not isSessionVar) then
3419 helixhorne 1593
                -- Attempt to override a system gamevar. See if it's read-only...
1594
                if (bit.band(oflags, GVFLAG.READONLY) ~= 0) then
1595
                    errprintf("attempt to override read-only system gamevar `%s'", identifier)
3427 helixhorne 1596
                    return
3419 helixhorne 1597
                end
1598
 
1599
                local flagsnosys = bit.band(oflags, bit.bnot(GVFLAG.SYSTEM))
3439 helixhorne 1600
                if (flagsnosys ~= flags and g_warn["system-gamevar"]) then
3419 helixhorne 1601
                    warnprintf("overrode initial value of `%s', but kept "..
1602
                               "flags (%d)", identifier, flagsnosys)
1603
                end
1604
 
3563 helixhorne 1605
                if (ogv.rbits and bit.band(ogv.rbits, initval)~=0) then
1606
                    warnprintf("set one or more reserved bits (0x%s) in overriding `%s'",
1607
                               bit.tohex(bit.band(ogv.rbits, initval)), identifier)
1608
                end
1609
 
3574 helixhorne 1610
                local linestr = "--"..getlinecol(g_lastkwpos)
1611
 
3419 helixhorne 1612
                -- Emit code to set the variable at Lua parse time.
3891 helixhorne 1613
                -- XXX: How does this interact with savegame restoration?
3419 helixhorne 1614
                if (bit.band(oflags, GVFLAG.PERPLAYER) ~= 0) then
3848 helixhorne 1615
                    -- Replace player index by 0. PLAYER_0.
3563 helixhorne 1616
                    -- TODO_MP: init for all players.
3419 helixhorne 1617
                    local pvar, numrepls = ogv.name:gsub("_pli", "0")
1618
                    assert(numrepls>=1)
3574 helixhorne 1619
                    addcodef("%s=%d%s", pvar, initval, linestr)
3419 helixhorne 1620
                else
3574 helixhorne 1621
                    addcodef("%s=%d%s", ogv.name, initval, linestr)
3419 helixhorne 1622
                end
3427 helixhorne 1623
                return
3419 helixhorne 1624
            end
1625
 
4581 helixhorne 1626
            errprintf("duplicate definition of gamevar `%s' has different flags (new: %x, old: %x)", identifier, flags, oflags)
4153 helixhorne 1627
            inform.oldgv_location(identifier, true)
3427 helixhorne 1628
            return
3390 helixhorne 1629
        else
4151 helixhorne 1630
            warnprintf("duplicate definition of gamevar `%s' ignored", identifier)
4153 helixhorne 1631
            inform.oldgv_location(identifier, false)
3427 helixhorne 1632
            return
3390 helixhorne 1633
        end
1634
    end
1635
 
1636
    local ltype = g_labeltype[identifier]
1637
    if (ltype ~= nil) then
4151 helixhorne 1638
        warnprintf("Symbol `%s' already used for a defined %s.", identifier, LABEL[ltype])
4153 helixhorne 1639
        inform.olddef_location(identifier, false)
3390 helixhorne 1640
    end
1641
 
4112 helixhorne 1642
    if (isSessionVar) then
1643
        if (g_numSessionVars == conl.MAXSESSIONVARS) then
1644
            errprintf("Declared too many session gamevars (flag 1024), can have at most %d.",
1645
                      conl.MAXSESSIONVARS)
1646
            return
1647
        end
1648
 
1649
        -- Declare new session gamevar.
4570 helixhorne 1650
        local gv = { name=format("_gv._sessionVar[%d]", g_numSessionVars),
1651
                     flags=flags, loc=getLocation(), used=0 }
4112 helixhorne 1652
        g_numSessionVars = g_numSessionVars+1
4570 helixhorne 1653
 
1654
        g_gamevar[identifier] = gv;
1655
        -- Initialize it (i.e. set to the declared initial value) on first run,
1656
        -- but not from savegames.
1657
        addcodef("if _S then %s=%d end", gv.name, initval)
1658
 
4112 helixhorne 1659
        return
1660
    end
1661
 
4299 helixhorne 1662
    local gv = { name=mangle_name(identifier, "V"), flags=flags, loc=getLocation(), used=0 }
3390 helixhorne 1663
    g_gamevar[identifier] = gv
1664
 
4140 helixhorne 1665
    if (storeWithSavegames) then
1666
        addcode("if _S then")
1667
    end
3891 helixhorne 1668
 
4112 helixhorne 1669
    if (perActor) then
3798 helixhorne 1670
        addcodef("%s=_con.actorvar(%d)", gv.name, initval)
4112 helixhorne 1671
    elseif (perPlayer and g_cgopt["playervar"]) then
3842 helixhorne 1672
        gv.flags = bit.bor(gv.flags, GVFLAG.CON_PERPLAYER)
1673
        addcodef("%s=_con.playervar(%d)", gv.name, initval)
3390 helixhorne 1674
    else
3518 helixhorne 1675
        addcodef("%s=%d", gv.name, initval)
3390 helixhorne 1676
    end
3891 helixhorne 1677
 
4140 helixhorne 1678
    if (storeWithSavegames) then
1679
        addcode("end")
1680
    end
3390 helixhorne 1681
end
1682
 
3817 helixhorne 1683
function Cmd.dynamicremap()
1684
    if (g_dyntilei==nil) then
1685
        print("Using dynamic tile remapping");
3847 helixhorne 1686
        g_dyntilei = {};
3817 helixhorne 1687
    end
1688
end
1689
 
3847 helixhorne 1690
function Cmd.dynamicsoundremap()
1691
    if (g_dynsoundi==nil) then
1692
        print("Using dynamic sound remapping");
1693
        g_dynsoundi = {};
1694
    end
1695
end
1696
 
3503 helixhorne 1697
function lookup.gamearray(identifier)
1698
    local ga = g_gamearray[identifier]
1699
    if (ga == nil) then
1700
        errprintf("symbol `%s' is not a game array", identifier)
1701
        return "_INVALIDGA"
1702
    end
1703
    return ga.name
1704
end
1705
 
3842 helixhorne 1706
local function thisactor_to_pli(var)
1707
    return (var=="_aci") and "_pli" or var
1708
end
1709
 
4111 helixhorne 1710
function lookup.error_not_gamevar(identifier)
1711
    errprintf("symbol `%s' is not a game variable", identifier)
1712
    return "_INVALIDGV"
1713
end
1714
 
3469 helixhorne 1715
-- <aorpvar>: code for actor or player index
1716
function lookup.gamevar(identifier, aorpvar, writable)
3390 helixhorne 1717
    local gv = g_gamevar[identifier]
1718
 
1719
    if (gv == nil) then
4111 helixhorne 1720
        return lookup.error_not_gamevar(identifier)
3390 helixhorne 1721
    end
1722
 
3392 helixhorne 1723
    if (writable and bit.band(gv.flags, GVFLAG.READONLY) ~= 0) then
3570 helixhorne 1724
        errprintf("gamevar `%s' is read-only", identifier)
3392 helixhorne 1725
        return "_READONLYGV"
1726
    end
1727
 
4299 helixhorne 1728
    gv.used = bit.bor(gv.used, writable and 2 or 1)
1729
 
3469 helixhorne 1730
    if (bit.band(gv.flags, GVFLAG.PERACTOR)~=0) then
1731
        return format("%s[%s]", gv.name, aorpvar)
3842 helixhorne 1732
    elseif (bit.band(gv.flags, GVFLAG.CON_PERPLAYER)~=0 and g_cgopt["playervar"]) then
1733
        return format("%s[%s]", gv.name, thisactor_to_pli(aorpvar))
3390 helixhorne 1734
    else
1735
        return gv.name
1736
    end
1737
end
1738
 
1739
local function maybe_gamevar_Cmt(subj, pos, identifier)
1740
    if (g_gamevar[identifier]) then
3469 helixhorne 1741
        return true, lookup.gamevar(identifier, "_aci", false)
3390 helixhorne 1742
    end
1743
end
1744
 
1745
 
2594 helixhorne 1746
----==== patterns ====----
1747
 
1748
---- basic ones
2616 helixhorne 1749
-- Windows, *nix and Mac newlines all exist in the wild!
1750
local newline = "\r"*Pat("\n")^-1 + "\n"
1751
local EOF = Pat(-1)
2594 helixhorne 1752
local anychar = Pat(1)
1753
-- comments
1754
local comment = "/*" * match_until(anychar, "*/") * "*/"
1755
local linecomment = "//" * match_until(anychar, newline)
1756
local whitespace = Var("whitespace")
1757
local sp0 = whitespace^0
2616 helixhorne 1758
-- This "WS+" pattern matches EOF too, so that a forgotten newline at EOF is
1759
-- properly handled
1760
local sp1 = whitespace^1 + EOF
2594 helixhorne 1761
local alpha = Range("AZ", "az")  -- locale?
1762
local alphanum = alpha + Range("09")
2616 helixhorne 1763
--local alnumtok = alphanum + Set("{}/\\*-_.")  -- see isaltok() in gamedef.c
2594 helixhorne 1764
 
3390 helixhorne 1765
--- Basic lexical elements ("tokens"). See the final grammar ("Grammar") for
1766
--- their definitions.
3391 helixhorne 1767
local tok =
1768
{
1769
    maybe_minus = (Pat("-") * sp0)^-1,
1770
    number = Var("t_number"),
2594 helixhorne 1771
 
4800 helixhorne 1772
    -- Valid identifier names are disjoint from keywords!
3432 helixhorne 1773
    -- XXX: CON is more permissive with identifier name characters:
3391 helixhorne 1774
    identifier = Var("t_identifier"),
3432 helixhorne 1775
    -- This one matches keywords, too:
3391 helixhorne 1776
    identifier_all = Var("t_identifier_all"),
4860 helixhorne 1777
 
3391 helixhorne 1778
    define = Var("t_define"),
3847 helixhorne 1779
    rawdefine = Var("t_rawdefine"),
4860 helixhorne 1780
    actordefine = g_cgopt["names"] and Var("t_rawdefine") or Var("t_define"),
1781
 
3391 helixhorne 1782
    move = Var("t_move"),
1783
    ai = Var("t_ai"),
1784
    action = Var("t_action"),
2594 helixhorne 1785
 
3432 helixhorne 1786
    -- NOTE: no chance to whitespace and double quotes in filenames:
3391 helixhorne 1787
    filename = lpeg.C((anychar-Set(" \t\r\n\""))^1),
1788
    newline_term_str = match_until(anychar, newline),
2594 helixhorne 1789
 
3391 helixhorne 1790
    rvar = Var("t_rvar"),
1791
    wvar = Var("t_wvar"),
3503 helixhorne 1792
    gamearray = Var("t_gamearray"),
3391 helixhorne 1793
 
3432 helixhorne 1794
    -- for definelevelname
3391 helixhorne 1795
    time = lpeg.C(alphanum*alphanum^-1*":"*alphanum*alphanum^-1),
3409 helixhorne 1796
 
1797
    state_ends = Pat("ends")
1798
        + POS() * "else" * sp1 * "ends"
1799
        / function(pos) pwarnprintf(pos, "stray `else' at end of state") end,
3391 helixhorne 1800
}
1801
 
1802
 
2594 helixhorne 1803
---- helper patterns / pattern constructing functions
3391 helixhorne 1804
local maybe_quoted_filename = ('"' * tok.filename * '"' + tok.filename)
2616 helixhorne 1805
-- empty string is handled too; we must not eat the newline then!
2749 helixhorne 1806
local newline_term_string = (#newline + EOF)*lpeg.Cc("")
3391 helixhorne 1807
    + (whitespace-newline)^1 * lpeg.C(tok.newline_term_str)
2594 helixhorne 1808
 
1809
 
3391 helixhorne 1810
-- (sp1 * tok.define) repeated exactly n times
2594 helixhorne 1811
local function n_defines(n)  -- works well only for small n
1812
    local pat = Pat(true)
1813
    for i=1,n do
3391 helixhorne 1814
        pat = sp1 * tok.define * pat
2594 helixhorne 1815
    end
1816
    return pat
1817
end
1818
 
1819
 
3503 helixhorne 1820
local D, R, W, I, GARI, AC, MV, AI = -1, -2, -3, -4, -5, -6, -7, -8
1821
local TOKEN_PATTERN = { [D]=tok.define, [R]=tok.rvar, [W]=tok.wvar,
1822
                        [I]=tok.identifier, [GARI]=tok.gamearray,
3391 helixhorne 1823
                        [AC]=tok.action, [MV]=tok.move, [AI]=tok.ai }
2594 helixhorne 1824
 
1825
-- Generic command pattern, types given by varargs.
1826
-- The command name to be matched is attached later.
1827
-- Example:
1828
--  "command" writtenvar readvar def def:  gencmd(W,R,D,D)
3391 helixhorne 1829
--    -->  sp1 * tok.wvar * sp1 * tok.rvar * sp1 * tok.define * sp1 * tok.define
2594 helixhorne 1830
--  "command_with_no_args":  gencmd()
1831
--    --> Pat(true)
1832
local function cmd(...)
1833
    local pat = Pat(true)
1834
    local vartypes = {...}
1835
 
1836
    for i=1,#vartypes do
2765 helixhorne 1837
        pat = pat * sp1 * assert(TOKEN_PATTERN[vartypes[i]])
2594 helixhorne 1838
    end
1839
 
1840
    return pat
1841
end
1842
 
1843
 
1844
-- The command names will be attached to the front of the patterns later!
1845
 
1846
--== Top level CON commands ==--
2616 helixhorne 1847
-- XXX: many of these are also allowed inside actors/states/events in CON.
3391 helixhorne 1848
local Couter = {
2594 helixhorne 1849
    --- 1. Preprocessor
3516 helixhorne 1850
    include = sp1 * maybe_quoted_filename
1851
        / Cmd.include,
1852
    includedefault = cmd()
1853
        / Cmd.NYI("`includedefault'"),
1854
    define = cmd(I,D)
3916 helixhorne 1855
        / Define.label,
2594 helixhorne 1856
 
1857
    --- 2. Defines and Meta-Settings
3516 helixhorne 1858
    dynamicremap = cmd()
3817 helixhorne 1859
        / Cmd.dynamicremap,
3845 helixhorne 1860
    dynamicsoundremap = cmd()
3847 helixhorne 1861
        / Cmd.dynamicsoundremap,
3516 helixhorne 1862
    setcfgname = sp1 * tok.filename
4143 helixhorne 1863
        / Cmd.setcfgname,
3516 helixhorne 1864
    setdefname = sp1 * tok.filename
1865
        / Cmd.setdefname,
1866
    setgamename = newline_term_string
1867
        / Cmd.nyi("`setgamename'"),
2594 helixhorne 1868
 
3516 helixhorne 1869
    precache = cmd(D,D,D)
4291 helixhorne 1870
        / Cmd.precache,
3259 helixhorne 1871
    scriptsize = cmd(D)
1872
        / "",  -- no-op
3516 helixhorne 1873
    cheatkeys = cmd(D,D)
1874
        / Cmd.cheatkeys,
2594 helixhorne 1875
 
3516 helixhorne 1876
    definecheat = newline_term_string  -- XXX: actually tricker syntax (TS)
1877
        , -- / Cmd.nyi("`definecheat'"),
1878
    definegamefuncname = sp1 * tok.define * newline_term_string  -- XXX: TS?
1879
        / Cmd.definegamefuncname,
1880
    definegametype = n_defines(2) * newline_term_string
3826 helixhorne 1881
        / Cmd.definegametype,
3391 helixhorne 1882
    definelevelname = n_defines(2) * sp1 * tok.filename * sp1 * tok.time * sp1 * tok.time *
3516 helixhorne 1883
        newline_term_string
1884
        / Cmd.definelevelname,
1885
    defineskillname = sp1 * tok.define * newline_term_string
1886
        / Cmd.defineskillname,
1887
    definevolumename = sp1 * tok.define * newline_term_string
1888
        / Cmd.definevolumename,
2594 helixhorne 1889
 
3516 helixhorne 1890
    definequote = sp1 * tok.define * newline_term_string
1891
        / Cmd.definequote,
1892
    defineprojectile = cmd(D,D,D)
1893
        / Cmd.defineprojectile,
3847 helixhorne 1894
    definesound = sp1 * tok.rawdefine * sp1 * maybe_quoted_filename * n_defines(5)
3516 helixhorne 1895
        / Cmd.definesound,
2594 helixhorne 1896
 
2763 helixhorne 1897
    -- NOTE: gamevar.ogg and the like is OK, too
3516 helixhorne 1898
    music = sp1 * tok.define * match_until(sp1 * tok.filename, sp1 * conl.keyword * sp1)
1899
        / Cmd.music,
2594 helixhorne 1900
 
4977 hendricks2 1901
    undefinelevel = cmd(D,D)
1902
        / Cmd.undefinelevel,
1903
    undefineskill = cmd(D)
1904
        / Cmd.undefineskill,
1905
    undefinevolume = cmd(D)
1906
        / Cmd.undefinevolume,
1907
 
2594 helixhorne 1908
    --- 3. Game Settings
3254 helixhorne 1909
    -- gamestartup has 26/30 fixed defines, depending on 1.3D/1.5 version:
3516 helixhorne 1910
    gamestartup = (sp1 * tok.define)^26
1911
        / Cmd.gamestartup,
1912
    spritenopal = cmd(D)
4372 helixhorne 1913
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_NOPAL) end,
3516 helixhorne 1914
    spritenoshade = cmd(D)
4372 helixhorne 1915
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_NOSHADE) end,
3516 helixhorne 1916
    spritenvg = cmd(D)
4372 helixhorne 1917
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_NVG) end,
3516 helixhorne 1918
    spriteshadow = cmd(D)
4372 helixhorne 1919
        / function(tilenum, flags) Cmd.xspriteflags(tilenum, conl.SFLAG.SFLAG_SHADOW) end,
2594 helixhorne 1920
 
4841 helixhorne 1921
    spriteflags = (sp1 * tok.define)^2  -- also see inner
1922
        / function(tilenum, ...) Cmd.xspriteflags(tilenum, bit.bor(...), true) end,
2594 helixhorne 1923
 
1924
    --- 4. Game Variables / Arrays
3516 helixhorne 1925
    gamevar = cmd(I,D,D)
1926
        / Cmd.gamevar,
1927
    gamearray = cmd(I,D)
1928
        / Cmd.gamearray,
2594 helixhorne 1929
 
1930
    --- 5. Top level commands that are also run-time commands
3516 helixhorne 1931
    move = sp1 * tok.identifier * (sp1 * tok.define)^-2  -- hvel, vvel
3916 helixhorne 1932
        / function(...) Define.composite(LABEL.MOVE, ...) end,
3226 helixhorne 1933
 
2765 helixhorne 1934
    -- startframe, numframes, viewtype, incval, delay:
3516 helixhorne 1935
    action = sp1 * tok.identifier * (sp1 * tok.define)^-5
3916 helixhorne 1936
        / function(...) Define.composite(LABEL.ACTION, ...) end,
2594 helixhorne 1937
 
2765 helixhorne 1938
    -- action, move, flags...:
3391 helixhorne 1939
    ai = sp1 * tok.identifier * (sp1 * tok.action *
3516 helixhorne 1940
                                 (sp1 * tok.move * (sp1 * tok.define)^0)^-1
1941
                                )^-1
3916 helixhorne 1942
        / function(...) Define.composite(LABEL.AI, ...) end,
2765 helixhorne 1943
 
2594 helixhorne 1944
    --- 6. Deprecated TLCs
1945
    betaname = newline_term_string,
1946
    enhanced = cmd(D),
1947
}
1948
 
1949
 
1950
--== Run time CON commands ==--
1951
--- 1. Gamevar Operators
3854 helixhorne 1952
local Op = {}
1953
Op.var = cmd(W,D)
1954
Op.varvar = cmd(W,R)
2594 helixhorne 1955
 
3949 helixhorne 1956
function Op.var_common(thecmd, defaultop, trapop, wrapop)
1957
    local theop =
1958
        g_cgopt["trapv"] and trapop or
1959
        g_cgopt["wrapv"] and wrapop or
1960
        assert(defaultop)
1961
 
1962
    if (#theop <= 2) then
1963
        return thecmd / ("%1=%1"..theop.."%2")
3392 helixhorne 1964
    else
3949 helixhorne 1965
        return thecmd / ("%1="..theop.."(%1,%2)")
3392 helixhorne 1966
    end
1967
end
1968
 
3949 helixhorne 1969
function Op.varf(...)
1970
    return Op.var_common(Op.var, ...)
3392 helixhorne 1971
end
1972
 
3949 helixhorne 1973
function Op.varvarf(...)
1974
    return Op.var_common(Op.varvar, ...)
1975
end
1976
 
2616 helixhorne 1977
-- Allow nesting... stuff like
1978
--   ifvarl actorvar[sprite[THISACTOR].owner].burning 0
1979
-- is kinda breaking the classic "no array nesting" rules
3324 helixhorne 1980
-- (if there ever were any) but making our life harder else.
3391 helixhorne 1981
local arraypat = sp0 * "[" * sp0 * tok.rvar * sp0 * "]"
4287 helixhorne 1982
-- For {get,set}userdef:
1983
local arraypat_maybe_empty = sp0 * "[" * sp0 * (tok.rvar * sp0)^-1 * "]"
2616 helixhorne 1984
 
3854 helixhorne 1985
-- Table of various patterns that are (parts of) more complex inner commands.
1986
local patt = {}
1987
 
3432 helixhorne 1988
-- Have to bite the bullet here and list actor/player members with second
1989
-- parameters, even though it's ugly to make it part of the syntax.  Also,
1990
-- stuff like
2616 helixhorne 1991
--   actor[xxx].loogiex parm2 x
3432 helixhorne 1992
-- will be wrongly accepted at the parsing stage (loogiex is player's member)
1993
-- because we don't discriminate between actor and player here.
3854 helixhorne 1994
patt.parm2member = lpeg.C(Pat("htg_t") + "loogiex" + "loogiey" + "ammo_amount" +
4581 helixhorne 1995
                          "weaprecs" + "gotweapon" + "pals" + "Pals" + "max_ammo_amount") * sp1 * tok.rvar
1996
-- XXX: "pals" + "Pals": this sucks! It means that we for this list of members
1997
-- requiring second parameters, we will have to enumerate all lower/uppercase
1998
-- instances encountered in the wild.
2594 helixhorne 1999
 
3854 helixhorne 2000
-- The member name must match keywords, too (_all), because e.g. cstat is a
2001
-- member of sprite[].
2002
patt.bothmember = sp0 * "." * sp0 * lpeg.Ct(patt.parm2member + tok.identifier_all)
2003
patt.singlemember = sp0 * "." * sp0 * tok.identifier_all
2594 helixhorne 2004
 
3854 helixhorne 2005
patt.cmdgetstruct =  -- get<structname>[<idx>].<member> (<parm2>)? <<var>>
2006
    arraypat * patt.bothmember * sp1 * tok.wvar
2594 helixhorne 2007
 
3854 helixhorne 2008
patt.cmdsetstruct =  -- set<structname>[<idx>].<<member>> (<parm2>)? <var>
2009
    arraypat * patt.bothmember * sp1 * tok.rvar
2594 helixhorne 2010
 
3854 helixhorne 2011
patt.cmdgetperxvar =  -- get<actor/player>var[<idx>].<varname> <<var>>
2012
    arraypat * patt.singlemember * sp1 * tok.wvar
2594 helixhorne 2013
 
3854 helixhorne 2014
patt.cmdsetperxvar = -- set<actor/player>var[<idx>].<<varname>> <var>
2015
    arraypat * patt.singlemember * sp1 * tok.rvar
2016
 
3433 helixhorne 2017
-- Function generating code for a struct read/write access.
2018
local function StructAccess(Structname, writep, index, membertab)
2019
    assert(type(membertab)=="table")
3473 helixhorne 2020
    -- Lowercase the member name for CON compatibility
2021
    local member, parm2 = membertab[1]:lower(), membertab[2]
2594 helixhorne 2022
 
3454 helixhorne 2023
    local MemberCode = conl.StructAccessCode[Structname] or conl.StructAccessCode2[Structname]
3433 helixhorne 2024
    -- Look up array+member name first, e.g. "spriteext[%s].angoff".
3454 helixhorne 2025
    local armembcode = MemberCode[member]
3433 helixhorne 2026
    if (armembcode == nil) then
3473 helixhorne 2027
        errprintf("%s: invalid %s member `.%s'", g_lastkw, Structname, member)
3433 helixhorne 2028
        return "_MEMBINVALID"
2029
    end
3432 helixhorne 2030
 
4195 helixhorne 2031
    -- Function checking a literal number for being OK for assignment to this
2032
    -- member. Can also be a table {min, max}. See con_lang.lua, LITERAL_CHECKING.
2033
    local lit_ok_func_or_table
2034
 
3433 helixhorne 2035
    if (type(armembcode)=="table") then
2036
        -- Read and write accesses differ.
4195 helixhorne 2037
        if (writep) then
2038
            lit_ok_func_or_table = armembcode[3]
2039
        end
3433 helixhorne 2040
        armembcode = armembcode[writep and 2 or 1]
2041
        if (armembcode==nil) then
2042
            errprintf("%s access to %s[].%s is not available",
2043
                      writep and "write" or "read", Structname, member)
2044
            return "_MEMBNOACCESS"
3432 helixhorne 2045
        end
3433 helixhorne 2046
    end
3432 helixhorne 2047
 
3473 helixhorne 2048
    if (Structname~="userdef") then
2049
        -- Count number of parameters ("%s"), don't count "%%s".
2050
        local _, numparms = armembcode:gsub("[^%%]%%s", "", 2)
2051
        if (#membertab ~= numparms) then
2052
            local nums = { "one", "two" }
2053
            errprintf("%s[].%s has %s parameter%s, but %s given", Structname,
2054
                      member, nums[numparms], numparms==1 and "" or "s",
2055
                      nums[#membertab])
2056
            return "_MEMBINVPARM"
2057
        end
3433 helixhorne 2058
    end
3432 helixhorne 2059
 
3516 helixhorne 2060
    -- THISACTOR special meanings
2061
    if (Structname=="player" or Structname=="input") then
2062
        index = thisactor_to_pli(index)
2063
    elseif (Structname=="sector") then
2064
        if (index=="_aci") then
2065
            index = SPS".sectnum"
2066
        end
2067
    end
2068
 
3444 helixhorne 2069
    -- METHOD_MEMBER
2070
    local ismethod = (armembcode:find("%%s",1,true)~=nil)
2071
    -- If ismethod is true, then the formatted string will now have an "%s"
3561 helixhorne 2072
    local code
3473 helixhorne 2073
 
2074
    if (Structname=="userdef") then
2075
--        assert(index==nil)
2076
        assert(parm2==nil)
3561 helixhorne 2077
        code = format(armembcode, parm2)
3473 helixhorne 2078
    else
3561 helixhorne 2079
        code = format(armembcode, index, parm2)
3473 helixhorne 2080
    end
3561 helixhorne 2081
 
2082
    if (csapp()) then
2083
        if (Structname=="player") then
2084
            code = code:gsub("^player%[_pli%]", "_ps")
2085
        elseif (Structname=="sprite") then
2086
            code = code:gsub("^actor%[_aci%]", "_a")
2087
            code = code:gsub("^sprite%[_aci%]", "_spr")
2088
        end
2089
    end
2090
 
4195 helixhorne 2091
    return code, ismethod, lit_ok_func_or_table
3433 helixhorne 2092
end
2093
 
2094
function lookup.array_expr(writep, structname, index, membertab)
2095
    if (conl.StructAccessCode[structname] == nil) then
3503 helixhorne 2096
        -- Try a gamearray
3516 helixhorne 2097
        local ganame = g_gamearray[structname] and lookup.gamearray(structname)
2098
        if (ganame == nil) then
3503 helixhorne 2099
            if (structname=="actorvar") then
2100
                -- actorvar[] inline array expr
2101
                -- XXX: kind of CODEDUP with GetOrSetPerxvarCmd() factory
2102
                local gv = g_gamevar[structname]
2103
                if (gv and bit.band(gv.flags, GVFLAG.PERX_MASK)~=GVFLAG.PERACTOR) then
3570 helixhorne 2104
                    errprintf("gamevar `%s' is not per-actor", structname, "actor")
3503 helixhorne 2105
                end
2106
 
2107
                if (membertab == nil) then
2108
                    errprintf("actorvar[] requires a pseudo member (gamevar) name")
2109
                    return "_INVALIDAV"
2110
                end
2111
 
2112
                if (#membertab > 1) then
2113
                    errprintf("actorvar[] cannot be used with a second parameter")
2114
                    return "_INVALIDAV"
2115
                end
2116
 
4299 helixhorne 2117
                if (gv) then
2118
                    gv.used = bit.bor(gv.used, writep and 2 or 1)
2119
                end
2120
 
3503 helixhorne 2121
                assert(#membertab == 1)
2122
                return lookup.gamevar(membertab[1], index, writep)
2123
            end
2124
 
2125
            errprintf("symbol `%s' is neither a struct nor a gamearray", structname)
2126
            return "_INVALIDAR"
2127
        end
2128
 
2129
        if (membertab ~= nil) then
2130
            errprintf("gamearrays cannot be indexed with member names")
2131
            return "_INVALIDAR"
2132
        end
2133
 
3516 helixhorne 2134
        assert(type(ganame)=="string")
2135
        return format("%s[%s]", ganame, index)
3433 helixhorne 2136
    end
2137
 
3444 helixhorne 2138
    local membercode, ismethod = StructAccess(structname, writep, index, membertab)
2139
    -- Written METHOD_MEMBER syntax not supported as "qwe:method(asd) = val"
2140
    -- isn't valid Lua syntax.
2141
    assert(not (writep and ismethod))
2142
    return membercode
3433 helixhorne 2143
end
2144
 
2145
local Access =
2146
{
2147
    sector = function(...) return StructAccess("sector", ...) end,
2148
    wall = function(...) return StructAccess("wall", ...) end,
2149
    xsprite = function(...) return StructAccess("sprite", ...) end,
2150
    player = function(...) return StructAccess("player", ...) end,
3454 helixhorne 2151
 
2152
    tspr = function(...) return StructAccess("tspr", ...) end,
3463 helixhorne 2153
    projectile = function(...) return StructAccess("projectile", ...) end,
3466 helixhorne 2154
    thisprojectile = function(...) return StructAccess("thisprojectile", ...) end,
3473 helixhorne 2155
    userdef = function(...) return StructAccess("userdef", ...) end,
3477 helixhorne 2156
    input = function(...) return StructAccess("input", ...) end,
3432 helixhorne 2157
}
2158
 
3473 helixhorne 2159
local function GetStructCmd(accessfunc, pattern)
3854 helixhorne 2160
    return (pattern or patt.cmdgetstruct) /
3469 helixhorne 2161
      function(idx, memb, var)
3432 helixhorne 2162
        return format("%s=%s", var, accessfunc(false, idx, memb))
3469 helixhorne 2163
      end
3432 helixhorne 2164
end
2165
 
3473 helixhorne 2166
local function SetStructCmd(accessfunc, pattern)
3469 helixhorne 2167
    local function capfunc(idx, memb, var)
4195 helixhorne 2168
        -- litok: function or table
2169
        local membercode, ismethod, litok = accessfunc(true, idx, memb)
2170
 
2171
        -- Light static checking for literal values being OK for member
2172
        -- assignment. LITERAL_CHECKING.
2173
        if (type(var)=="number" and litok) then
2174
            if (type(litok)=="table" and not (var>=litok[1] and var<=litok[2]) or
2175
                    type(litok)=="function" and not litok(var)) then
2176
                local member = memb[1]:lower()
2177
                warnprintf("setting member '.%s' to %d will fail at game time",
2178
                           member, var)
2179
            end
3626 helixhorne 2180
        end
4195 helixhorne 2181
 
3444 helixhorne 2182
        if (ismethod) then
2183
            -- METHOD_MEMBER syntax
2184
 
2185
            -- BE EXTRA CAREFUL! We must be sure that percent characters have
2186
            -- not been smuggled into the member code string via variable names
2187
            -- etc.
2188
            local _, numpercents = membercode:gsub("%%", "", 2)
2189
            assert(numpercents==1)
2190
 
2191
            return format(membercode, var)
2192
        else
2193
            return format("%s=%s", membercode, var)
2194
        end
3439 helixhorne 2195
    end
3469 helixhorne 2196
 
3854 helixhorne 2197
    return (pattern or patt.cmdsetstruct) / capfunc
3439 helixhorne 2198
end
3432 helixhorne 2199
 
3469 helixhorne 2200
-- <Setp>: whether the perxvar is set
2201
local function GetOrSetPerxvarCmd(Setp, Actorp)
2202
    local EXPECTED_PERX_BIT = Actorp and GVFLAG.PERACTOR or GVFLAG.PERPLAYER
3854 helixhorne 2203
    local pattern = (Setp and patt.cmdsetperxvar or patt.cmdgetperxvar)
3439 helixhorne 2204
 
3469 helixhorne 2205
    local function capfunc(idx, perxvarname, var)
2206
        local gv = g_gamevar[perxvarname]
2207
        if (gv and bit.band(gv.flags, GVFLAG.PERX_MASK)~=EXPECTED_PERX_BIT) then
3570 helixhorne 2208
            -- [gs]set*var for wrong gamevar type. See if it's a getactorvar,
3909 helixhorne 2209
            -- in which case we may only warn and access that instead. Note
2210
            -- that accesses of player gamevars with actor indices are usually
2211
            -- meaningless.
3570 helixhorne 2212
            local warnp = not Setp and Actorp and not g_warn["error-bad-getactorvar"]
2213
            local xprintf = warnp and warnprintf or errprintf
2214
 
2215
            xprintf("gamevar `%s' is not per-%s", perxvarname, Actorp and "actor" or "player")
4266 helixhorne 2216
 
2217
            if (warnp and bit.band(gv.flags, GVFLAG.PERX_MASK)==GVFLAG.PERPLAYER
2218
                    and g_cgopt["bad-getactorvar-use-pli"]) then
2219
                -- For getactorvar[] accesses to per-player gamevars, if
2220
                -- -fbad-getactorvar-use-pli is provided, use current player
2221
                -- index, for compatibility with CON.
2222
                idx = "_pli"
2223
            end
3469 helixhorne 2224
        end
2225
 
3516 helixhorne 2226
        if (not Actorp) then
2227
            -- THISACTOR -> player index for {g,s}etplayervar
2228
            idx = thisactor_to_pli(idx)
2229
        end
2230
 
4299 helixhorne 2231
        if (gv) then
2232
            gv.used = bit.bor(gv.used, Setp and 2 or 1)
2233
        end
2234
 
3469 helixhorne 2235
        if (Setp) then
2236
            return format("%s=%s", lookup.gamevar(perxvarname, idx, true), var)
2237
        else
2238
            return format("%s=%s", var, lookup.gamevar(perxvarname, idx, false))
2239
        end
2240
    end
2241
 
2242
    return pattern / capfunc
2243
end
2244
 
2245
 
3507 helixhorne 2246
local function n_s_fmt(n)
2247
    return string.rep("%s,", n-1).."%s"
2248
end
2249
 
3431 helixhorne 2250
-- Various inner command handling functions / string capture strings.
3391 helixhorne 2251
local handle =
2252
{
3431 helixhorne 2253
    NYI = function()
2254
        errprintf("command `%s' not yet implemented", g_lastkw)
3523 helixhorne 2255
        return ""
3431 helixhorne 2256
    end,
2257
 
3516 helixhorne 2258
    dynNYI = function()
3882 helixhorne 2259
        return format([[print(%q..":%d: `%s' not yet implemented")]],
3516 helixhorne 2260
                      g_filename, getlinecol(g_lastkwpos), g_lastkw)
2261
    end,
2262
 
3431 helixhorne 2263
    addlog = function()
3882 helixhorne 2264
        return format("print(%q..':%d: addlog')", g_filename, getlinecol(g_lastkwpos))
3431 helixhorne 2265
    end,
2266
 
2267
    addlogvar = function(val)
3882 helixhorne 2268
        return format("printf(%q..':%d: addlogvar %%s', %s)", g_filename, getlinecol(g_lastkwpos), val)
3431 helixhorne 2269
    end,
2270
 
2271
    debug = function(val)
3882 helixhorne 2272
        return format("print(%q..':%d: debug %d')", g_filename, getlinecol(g_lastkwpos), val)
3431 helixhorne 2273
    end,
2274
 
3524 helixhorne 2275
    getzrange = function(...)
2276
        local v = {...}
2277
        assert(#v == 10)  -- 4R 4W 2R
2278
        return format("%s,%s,%s,%s=_con._getzrange(%s,%s,%s,%s,%s,%s)",
2279
                      v[5], v[6], v[7], v[8],  -- outargs
2280
                      v[1], v[2], v[3], v[4], v[9], v[10])  -- inargs
2281
    end,
2282
 
3491 helixhorne 2283
    hitscan = function(...)
2284
        local v = {...}
2285
        assert(#v == 14)  -- 7R 6W 1R
2286
        local vals = {
2287
            v[8], v[9], v[10], v[11], v[12], v[13],  -- outargs
2288
            v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[14]  -- inargs
2289
        }
2290
        return format("%s,%s,%s,%s,%s,%s=_con._hitscan(%s,%s,%s,%s,%s,%s,%s,%s)",
2291
                     unpack(vals))
2292
    end,
2293
 
2294
    neartag = function(...)
2295
        local v = {...}
2296
        assert(#v == 11)  -- 5R 4W 2R
2297
        local vals = {
2298
            v[6], v[7], v[8], v[9],  -- outargs
2299
            v[1], v[2], v[3], v[4], v[5], v[10], v[11]  -- inargs
2300
        }
2301
        return format("%s,%s,%s,%s=_con._neartag(%s,%s,%s,%s,%s,%s,%s)",
2302
                      unpack(vals))
2303
    end,
2304
 
3812 helixhorne 2305
    clipmove = function(noslidep, ...)
2306
        local v = {...}
2307
        assert(#v == 11)  -- 3W 1R 1W 6R
2308
        local vals = {
2309
            v[1], v[2], v[3], v[5],  -- outargs
2310
            v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10], v[11],  -- inargs
2311
            noslidep
2312
        }
2313
        return format("%s,%s,%s,%s=_con._clipmovex("..n_s_fmt(11)..")",
2314
                     unpack(vals))
2315
    end,
2316
 
3391 helixhorne 2317
    palfrom = function(...)
2318
        local v = {...}
2319
        return format(PLS":_palfrom(%d,%d,%d,%d)",
2320
                      v[1] or 0, v[2] or 0, v[3] or 0, v[4] or 0)
2321
    end,
3253 helixhorne 2322
 
3507 helixhorne 2323
    qsprintf = function(qdst, qsrc, ...)
2324
        local codes = {...}
3805 helixhorne 2325
        return format("_con._qsprintf(%s,%s%s%s)", qdst, qsrc,
3523 helixhorne