Subversion Repositories eduke32

Rev

Rev 4419 | Rev 4423 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4236 helixhorne 1
 
2
local ffi = require("ffi")
3
local C = ffi.C
4
 
5
local bcarray = require("bcarray")
6
 
4312 helixhorne 7
local assert = assert
4236 helixhorne 8
local error = error
4404 helixhorne 9
local ipairs = ipairs
4236 helixhorne 10
local type = type
11
 
12
local decl = assert(decl)  -- comes from above (defs.ilua or defs_m32.lua)
13
 
14
local ismapster32 = (C.LUNATIC_CLIENT == C.LUNATIC_CLIENT_MAPSTER32)
15
 
16
----------
17
 
18
decl[[
4419 helixhorne 19
const int32_t qsetmode;
4308 helixhorne 20
int32_t getclosestcol_lim(int32_t r, int32_t g, int32_t b, int32_t lastokcol);
4236 helixhorne 21
char *palookup[256];  // MAXPALOOKUPS
22
uint8_t palette[768];
4336 helixhorne 23
uint8_t *basepaltable[];
4236 helixhorne 24
 
4301 helixhorne 25
const char *getblendtab(int32_t blend);
26
void setblendtab(int32_t blend, const char *tab);
27
 
4236 helixhorne 28
int32_t setpalookup(int32_t palnum, const uint8_t *shtab);
29
]]
30
 
4419 helixhorne 31
if (ismapster32) then
32
    ffi.cdef[[
33
int32_t _getnumber16(const char *namestart, int32_t num, int32_t maxnumber, char sign, const char *(func)(int32_t));
4420 helixhorne 34
const char *getstring_simple(const char *querystr, const char *defaultstr, int32_t maxlen, int32_t completion);
4419 helixhorne 35
 
36
typedef const char *(*luamenufunc_t)(void);
37
void LM_Register(const char *name, luamenufunc_t funcptr);
38
void LM_Clear(void);
39
]]
40
end
41
 
4236 helixhorne 42
----------
43
 
4242 helixhorne 44
 
4236 helixhorne 45
-- The API table
46
local engine = {}
47
 
4242 helixhorne 48
 
49
local shtab_t  -- forward-decl
50
 
4301 helixhorne 51
local function cast_u8ptr(sth)
52
    return ffi.cast("uint8_t *", sth)
4242 helixhorne 53
end
54
 
55
local shtab_methods = {
56
    -- Remap consecutive blocks of 16 color indices and return this new shade
57
    -- table.
58
    --
4256 helixhorne 59
    -- <idxs16>: table with idxs16[0] .. idxs16[15] >= 0 and <= 15
60
    --  (i.e. 0-based indices of such 16-tuples)
4242 helixhorne 61
    --
62
    -- For example, the table
63
    --  { [0]=0,1, 2,3, 5,4, 6,7, 8,13, 10,11, 12,9, 14,15 }
64
    -- TODO (...)
65
    remap16 = function(sht, idxs16)
4332 helixhorne 66
        if (type(idxs16) ~= "table") then
67
            error("invalid argument #2: must be a table", 2)
4242 helixhorne 68
        end
69
 
70
        for i=0,15 do
4332 helixhorne 71
            local idx = idxs16[i]
72
            if (not (idx==nil or type(idx)=="number" and idx >= 0 and idx <= 15)) then
73
                error("invalid reordering table: elements must be numbers in [0 .. 15], or nil", 2)
4242 helixhorne 74
            end
75
        end
76
 
77
        local newsht = shtab_t()
78
        for sh=0,31 do
79
            for i=0,15 do
80
                ffi.copy(cast_u8ptr(newsht[sh]) + 16*i,
4332 helixhorne 81
                         cast_u8ptr(sht[sh]) + 16*(idxs16[i] or i), 16)
4242 helixhorne 82
            end
83
        end
84
        return newsht
85
    end,
86
}
87
 
88
local function shtab_mt__index(sht, idx)
89
    local method = shtab_methods[idx]
90
    if (method) then
91
        return method
92
    end
93
end
94
 
4301 helixhorne 95
local pal256_t = bcarray.new("uint8_t", 256, "color index 256-tuple")
4236 helixhorne 96
-- The shade table type, effectively a bound-checked uint8_t [32][256]:
4242 helixhorne 97
shtab_t = bcarray.new(pal256_t, 32, "shade table", nil, nil, { __index = shtab_mt__index })
4236 helixhorne 98
local SIZEOF_SHTAB = ffi.sizeof(shtab_t)
99
 
4301 helixhorne 100
local blendtab_t = bcarray.new(pal256_t, 256, "blending table")
101
local SIZEOF_BLENDTAB = ffi.sizeof(blendtab_t)
102
 
4236 helixhorne 103
local RESERVEDPALS = 8  -- KEEPINSYNC build.h: assure that ours is >= theirs
104
engine.RESERVEDPALS = RESERVEDPALS
105
 
4301 helixhorne 106
local MAXBLENDTABS = 256  -- KEEPINSYNC build.h
107
 
4236 helixhorne 108
local function check_palidx(i)
109
    if (type(i) ~= "number" or not (i >= 0 and i <= 255-RESERVEDPALS)) then
4262 helixhorne 110
        error("invalid argument #1: palette swap index must be in the range [0 .. "..255-RESERVEDPALS.."]", 3)
4236 helixhorne 111
    end
112
end
113
 
4301 helixhorne 114
local function check_blendidx(i)
115
    if (type(i) ~= "number" or not (i >= 0 and i <= MAXBLENDTABS-1)) then
116
        error("invalid argument #1: blending table index must be in the range [0 .. ".. MAXBLENDTABS-1 .."]", 3)
117
    end
118
end
119
 
4236 helixhorne 120
local function err_uncommon_shade_table(ret)
121
    if (ret == -1) then
122
        error("loaded engine shade tables don't have 32 gradients of shade", 3)
123
    end
124
end
125
 
126
local function palookup_isdefault(palnum)  -- KEEPINSYNC engine.c
127
    return (C.palookup[palnum] == nil or (palnum ~= 0 and C.palookup[palnum] == C.palookup[0]))
128
end
129
 
130
function engine.shadetab()
131
    return shtab_t()
132
end
133
 
4301 helixhorne 134
function engine.blendtab()
135
    return blendtab_t()
136
end
137
 
4236 helixhorne 138
function engine.getshadetab(palidx)
139
    check_palidx(palidx)
140
    if (palookup_isdefault(palidx)) then
141
        return nil
142
    end
143
 
144
    local ret = C.setpalookup(palidx, nil)
145
    err_uncommon_shade_table(ret)
146
 
147
    local sht = shtab_t()
148
    ffi.copy(sht, C.palookup[palidx], SIZEOF_SHTAB)
149
    return sht
150
end
151
 
4301 helixhorne 152
function engine.getblendtab(blendidx)
153
    check_blendidx(blendidx)
154
 
155
    local ptr = C.getblendtab(blendidx)
156
    if (ptr == nil) then
157
        return nil
158
    end
159
 
160
    local tab = blendtab_t()
161
    ffi.copy(tab, ptr, SIZEOF_BLENDTAB)
162
    return tab
163
end
164
 
165
 
166
local function check_first_time()
4236 helixhorne 167
    if (not ismapster32 and C.g_elFirstTime == 0) then
4301 helixhorne 168
        error("may be called only while LUNATIC_FIRST_TIME is true", 3)
4236 helixhorne 169
    end
4301 helixhorne 170
end
4236 helixhorne 171
 
4301 helixhorne 172
function engine.setshadetab(palidx, shtab)
173
    check_first_time()
4236 helixhorne 174
    check_palidx(palidx)
4301 helixhorne 175
 
176
    if (not ffi.istype(shtab_t, shtab)) then
4236 helixhorne 177
        error("invalid argument #2: must be a shade table obtained by shadetab()", 2)
178
    end
179
 
180
    if (not ismapster32 and not palookup_isdefault(palidx)) then
181
        error("attempt to override already defined shade table", 2)
182
    end
183
 
4301 helixhorne 184
    local ret = C.setpalookup(palidx, cast_u8ptr(shtab))
4236 helixhorne 185
    err_uncommon_shade_table(ret)
186
end
187
 
4301 helixhorne 188
function engine.setblendtab(blendidx, tab)
189
    check_first_time()
190
    check_blendidx(blendidx)
4236 helixhorne 191
 
4301 helixhorne 192
    if (not ffi.istype(blendtab_t, tab)) then
193
        error("invalid argument #2: must be a blending table obtained by blendtab()", 2)
194
    end
195
 
196
    if (not ismapster32 and C.getblendtab(blendidx) ~= nil) then
197
        error("attempt to override already defined blending table", 2)
198
    end
199
 
200
    C.setblendtab(blendidx, cast_u8ptr(tab))
201
end
202
 
203
 
4236 helixhorne 204
local function check_colcomp(a)
205
    if (type(a) ~= "number" or not (a >= 0 and a <= 63)) then
206
        error("color component must be in the range [0 .. 63]", 3)
207
    end
208
end
209
 
210
 
211
-- TODO: other base palettes?
212
function engine.getrgb(colidx)
213
    if (type(colidx) ~= "number" or not (colidx >= 0 and colidx <= 255)) then
214
        error("color index must be in the range [0 .. 255]", 2)
215
    end
216
 
4301 helixhorne 217
    -- NOTE: In the game, palette[255*{0..2}] is set to 0 in
218
    -- G_LoadExtraPalettes() via G_Startup(). However, that's after Lua state
219
    -- initialization (i.e. when LUNATIC_FIRST_TIME would be true), and in the
220
    -- editor, it's never changed from the purple color. Therefore, I think
221
    -- it's more useful to always return the fully black color here.
222
    if (colidx == 255) then
223
        return 0, 0, 0
224
    end
225
 
4236 helixhorne 226
    local rgbptr = C.palette + 3*colidx
227
    return rgbptr[0], rgbptr[1], rgbptr[2]
228
end
229
 
4308 helixhorne 230
function engine.nearcolor(r, g, b, lastokcol)
4236 helixhorne 231
    check_colcomp(r)
232
    check_colcomp(g)
233
    check_colcomp(b)
4308 helixhorne 234
 
235
    if (lastokcol == nil) then
236
        lastokcol = 255
237
    elseif (type(lastokcol)~="number" or not (lastokcol >= 0 and lastokcol <= 255)) then
238
        error("invalid argument #4 <lastokcol>: must be in the range [0 .. 255]", 2)
239
    end
240
 
241
    return C.getclosestcol_lim(r, g, b, lastokcol)
4236 helixhorne 242
end
243
 
244
 
4311 helixhorne 245
---------- Mapster32-only functions ----------
246
 
247
if (ismapster32) then
248
    local io = require("io")
4312 helixhorne 249
    local math = require("math")
250
    local string = require("string")
4311 helixhorne 251
 
252
    ffi.cdef[[size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, void * restrict stream);]]
253
 
4336 helixhorne 254
    local function validate_more_blendtabs(moreblends, kindname, gettabfunc)
4312 helixhorne 255
        if (moreblends == nil) then
256
            return nil, nil
257
        end
258
 
259
        -- Additional blending tables: validate <moreblends> table.
260
        if (type(moreblends) ~= "table") then
261
            error("invalid argument #4: must be a table", 3)
262
        end
263
 
264
        local haveblend = { [0]=true }
265
        local blendnumtab, blendptrtab = {}, {}
266
 
267
        for i=1,#moreblends do
268
            local tmp = moreblends[i]
269
            local blendspec = (type(tmp) == "number") and { tmp, tmp } or tmp
270
 
271
            if (not (type(blendspec) == "table" and #blendspec == 2)) then
272
                error("invalid argument #4: must contain numbers or 2-tables", 3)
273
            end
274
 
275
            local blend1, blend2 = math.floor(blendspec[1]), math.floor(blendspec[2])
276
 
277
            if (not (type(blend1)=="number" and blend1 >= 1 and blend1 <= 255 and
278
                     type(blend2)=="number" and blend2 >= 1 and blend2 <= 255)) then
4336 helixhorne 279
                error("invalid argument #4: "..kindname.." table numbers must be in [1 .. 255]", 3)
4312 helixhorne 280
            end
281
 
282
            for bi=blend1,blend2 do
283
                if (haveblend[bi]) then
4336 helixhorne 284
                    error("invalid argument #4: duplicate "..kindname.." table number "..bi, 3)
4312 helixhorne 285
                end
286
                haveblend[bi] = true
287
 
4336 helixhorne 288
                local ptr = gettabfunc(bi)
4312 helixhorne 289
                if (ptr == nil) then
4336 helixhorne 290
                    error("invalid argument #4: "..kindname.." table for number "..bi.." is void", 3)
4312 helixhorne 291
                end
292
 
293
                blendnumtab[#blendnumtab+1] = bi
294
                blendptrtab[#blendptrtab+1] = ptr
295
            end
296
        end
297
 
298
        assert(#blendnumtab <= 255)
299
        return blendnumtab, blendptrtab
300
    end
301
 
4420 helixhorne 302
    -- ok, errmsg, nummoreblends = engine.savePaletteDat(filename [, palnum [, blendnum [, moreblends]]])
4312 helixhorne 303
    function engine.savePaletteDat(filename, palnum, blendnum, moreblends)
4311 helixhorne 304
        local sht = engine.getshadetab(palnum or 0)
305
        local tab = engine.getblendtab(blendnum or 0)
306
 
307
        if (sht == nil) then
308
            return nil, "no shade table with number "..palnum
309
        elseif (tab == nil) then
310
            return nil, "no blending table with number "..blendnum
311
        end
312
 
4336 helixhorne 313
        local blendnumtab, blendptrtab = validate_more_blendtabs(
314
            moreblends, "blending", C.getblendtab)
4312 helixhorne 315
 
4331 helixhorne 316
        local f, errmsg = io.open(filename, "wb+")
4311 helixhorne 317
        if (f == nil) then
318
            return nil, errmsg
319
        end
320
 
4312 helixhorne 321
        local n1 = C.fwrite(C.palette, 3, 256, f)
4311 helixhorne 322
        f:write("\032\000")  -- int16_t numshades
4312 helixhorne 323
        local n3 = C.fwrite(sht, 256, 32, f)
324
        local n4 = C.fwrite(tab, 256, 256, f)
4311 helixhorne 325
 
326
        if (n1 ~= 256 or n3 ~= 32 or n4 ~= 256) then
4312 helixhorne 327
            return nil, "failed writing classic PALETTE.DAT data"
4311 helixhorne 328
        end
329
 
4312 helixhorne 330
        if (blendnumtab ~= nil) then
331
            f:write("MoreBlendTab")
332
            f:write(string.char(#blendnumtab))
333
 
334
            for i=1,#blendnumtab do
335
                f:write(string.char(blendnumtab[i]))
336
                if (C.fwrite(blendptrtab[i], 256, 256, f) ~= 256) then
337
                    return nil, "failed writing additional blending table"
338
                end
339
            end
340
        end
341
 
342
        f:close()
343
 
4420 helixhorne 344
        return true, nil, (blendnumtab ~= nil) and #blendnumtab or 0
4311 helixhorne 345
    end
4336 helixhorne 346
 
347
    -- ok, errmsg = engine.saveLookupDat(filename, lookups)
348
    function engine.saveLookupDat(filename, lookups)
349
        if (lookups == nil) then
350
            -- set to an invalid value, validate_more_blendtabs will error
351
            lookups = 0
352
        end
353
 
354
        local lookupnumtab, lookupptrtab = validate_more_blendtabs(
355
            lookups, "lookup", engine.getshadetab)
356
 
357
        local f, errmsg = io.open(filename, "wb+")
358
        if (f == nil) then
359
            return nil, errmsg
360
        end
361
 
362
        f:write(string.char(#lookupnumtab))
363
 
364
        for i=1,#lookupnumtab do
365
            f:write(string.char(lookupnumtab[i]))
366
            if (C.fwrite(lookupptrtab[i], 1, 256, f) ~= 256) then
367
                return nil, "failed writing lookup table"
368
            end
369
        end
370
 
371
        -- Write five base palettes
372
        for i=1,5 do
373
            local bpi = (i==3 or i==4) and 4+3-i or i
374
 
375
            if (C.fwrite(C.basepaltable[bpi], 1, 768, f) ~= 768) then
376
                return nil, "failed writing base palette"
377
            end
378
        end
379
 
380
        f:close()
381
 
382
        return true
383
    end
384
 
4337 helixhorne 385
    local hexmap = {
386
        [0] = 0, -14,  -- 0, 1: gray ramp
387
        14, 0,  -- 2, 3: skin color ramp
388
        0, 14,  -- 4, 5: blue ramp (second part first)
389
        14, 0,  -- 6, 7: nightvision yellow/green
390
        14,  -- 8: red first part...
391
        8,   -- 9: yellow (slightly more red than green)
392
        14, 0,  -- 10, 11: almost gray ramp, but with a slight red hue
393
        8,   -- 12: "dirty" orange
394
        0,   -- 13: ...red second part
395
        8,   -- 14: blue-purple-red
396
    }
397
 
398
    -- Setup base palette 1 (water) to contain one color for each consecutive
399
    -- 16-tuple (which I'm calling a 'hex' for brevity), except for the last
400
    -- one with the fullbrights.
4336 helixhorne 401
    function engine.setupDebugBasePal()
402
        for i=0,14 do
403
            local ptr = C.basepaltable[1] + 3*(16*i)
4337 helixhorne 404
            local src = C.basepaltable[0] + 3*(16*i) + 3*hexmap[i]
4336 helixhorne 405
            local r, g, b = src[0], src[1], src[2]
406
 
407
            for j=0,15 do
408
                local dst = ptr + 3*j
409
                dst[0], dst[1], dst[2] = r, g, b
410
            end
411
        end
412
    end
4404 helixhorne 413
 
414
    function engine.linearizeBasePal()
415
        for _, begi in ipairs{0, 32, 96, 160} do
416
            local ptr = C.basepaltable[0] + 3*begi
417
            local refcol = ptr + 3*31
418
 
419
            for i=0,30 do
420
                for c=0,2 do
421
                    ptr[3*i + c] = i*refcol[c]/31
422
                end
423
            end
424
        end
425
 
426
        for _, begi in ipairs{128, 144, 192, 208, 224} do
427
            local ptr = C.basepaltable[0] + 3*begi
428
 
429
            for i=0,3*15+2 do
430
                ptr[i] = 0
431
            end
432
        end
433
    end
4419 helixhorne 434
 
435
    -- Interfaces to Mapster32's status bar menu
436
 
437
    local pcall = pcall
438
 
439
    function engine.clearMenu()
440
        C.LM_Clear()
441
    end
442
 
443
    function engine.registerMenuFunc(name, func)
444
        if (type(name) ~= "string") then
445
            error("invalid argument #1: must be a string", 2)
446
        end
447
        if (type(func) ~= "function") then
448
            error("invalid argument #2: must be a function", 2)
449
        end
450
 
451
        local safefunc = function()
452
            local ok, errmsg = pcall(func)
453
            if (not ok) then
454
                return errmsg
455
            end
456
        end
457
 
458
        C.LM_Register(name, safefunc)
459
    end
460
 
461
    engine.GETNUMFLAG = {
462
        NEG_ALLOWED = 1,
463
        AUTOCOMPL_NAMES = 2,
464
        AUTOCOMPL_TAGLAB = 4,
465
        RET_M1_ON_CANCEL = 8,
466
 
467
        NEXTFREE = 16,
468
    }
469
 
470
    function engine.getnumber16(namestart, num, maxnumber, flags)
471
        if (C.qsetmode == 200) then
472
            error("getnumber16 must be called from 2D mode", 2)
473
        end
474
        if (type(namestart)~="string") then
475
            error("invalid argument #1: must be a string", 2)
476
        end
477
 
4420 helixhorne 478
        return C._getnumber16(namestart, num, maxnumber, flags or 8, nil)  -- RET_M1_ON_CANCEL
4419 helixhorne 479
    end
4420 helixhorne 480
 
481
    function engine.getstring(querystr)
482
        if (type(querystr) ~= "string") then
483
            error("invalid argument #2: must be a string", 2)
484
        end
485
        local cstr = C.getstring_simple(querystr, nil, 0, 0)
486
        return cstr~=nil and ffi.string(cstr) or nil
487
    end
4311 helixhorne 488
end
489
 
490
 
4236 helixhorne 491
-- Done!
492
return engine