Subversion Repositories eduke32

Rev

Rev 4423 | Rev 4429 | 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);
4423 helixhorne 37
void LM_Register(const char *name, luamenufunc_t funcptr, const char *description);
4419 helixhorne 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
 
4426 helixhorne 302
    -- ok, errmsg, nummoreblends = engine.savePaletteDat(
303
    --  filename [, palnum [, blendnum [, moreblends [, lognumalphatabs]]]])
304
    function engine.savePaletteDat(filename, palnum, blendnum, moreblends, lognumalphatabs)
4311 helixhorne 305
        local sht = engine.getshadetab(palnum or 0)
306
        local tab = engine.getblendtab(blendnum or 0)
307
 
308
        if (sht == nil) then
309
            return nil, "no shade table with number "..palnum
310
        elseif (tab == nil) then
311
            return nil, "no blending table with number "..blendnum
312
        end
313
 
4336 helixhorne 314
        local blendnumtab, blendptrtab = validate_more_blendtabs(
315
            moreblends, "blending", C.getblendtab)
4312 helixhorne 316
 
4426 helixhorne 317
        if (not (type(lognumalphatabs)=="number" and lognumalphatabs >= 1 and lognumalphatabs <= 7)) then
318
            error("invalid argument #5: must be a number in [1 .. 7]", 2)
319
        end
320
 
4331 helixhorne 321
        local f, errmsg = io.open(filename, "wb+")
4311 helixhorne 322
        if (f == nil) then
323
            return nil, errmsg
324
        end
325
 
4312 helixhorne 326
        local n1 = C.fwrite(C.palette, 3, 256, f)
4311 helixhorne 327
        f:write("\032\000")  -- int16_t numshades
4312 helixhorne 328
        local n3 = C.fwrite(sht, 256, 32, f)
329
        local n4 = C.fwrite(tab, 256, 256, f)
4311 helixhorne 330
 
331
        if (n1 ~= 256 or n3 ~= 32 or n4 ~= 256) then
4312 helixhorne 332
            return nil, "failed writing classic PALETTE.DAT data"
4311 helixhorne 333
        end
334
 
4312 helixhorne 335
        if (blendnumtab ~= nil) then
336
            f:write("MoreBlendTab")
337
            f:write(string.char(#blendnumtab))
338
 
339
            for i=1,#blendnumtab do
340
                f:write(string.char(blendnumtab[i]))
341
                if (C.fwrite(blendptrtab[i], 256, 256, f) ~= 256) then
342
                    return nil, "failed writing additional blending table"
343
                end
344
            end
4426 helixhorne 345
 
346
            if (lognumalphatabs) then
347
                -- XXX: no checking whether these blending tables 1 to
348
                -- 1<<lognumalphatabs have been written.
349
                f:write(string.char(lognumalphatabs))
350
            end
4312 helixhorne 351
        end
352
 
353
        f:close()
354
 
4420 helixhorne 355
        return true, nil, (blendnumtab ~= nil) and #blendnumtab or 0
4311 helixhorne 356
    end
4336 helixhorne 357
 
358
    -- ok, errmsg = engine.saveLookupDat(filename, lookups)
359
    function engine.saveLookupDat(filename, lookups)
360
        if (lookups == nil) then
361
            -- set to an invalid value, validate_more_blendtabs will error
362
            lookups = 0
363
        end
364
 
365
        local lookupnumtab, lookupptrtab = validate_more_blendtabs(
366
            lookups, "lookup", engine.getshadetab)
367
 
368
        local f, errmsg = io.open(filename, "wb+")
369
        if (f == nil) then
370
            return nil, errmsg
371
        end
372
 
373
        f:write(string.char(#lookupnumtab))
374
 
375
        for i=1,#lookupnumtab do
376
            f:write(string.char(lookupnumtab[i]))
377
            if (C.fwrite(lookupptrtab[i], 1, 256, f) ~= 256) then
378
                return nil, "failed writing lookup table"
379
            end
380
        end
381
 
382
        -- Write five base palettes
383
        for i=1,5 do
384
            local bpi = (i==3 or i==4) and 4+3-i or i
385
 
386
            if (C.fwrite(C.basepaltable[bpi], 1, 768, f) ~= 768) then
387
                return nil, "failed writing base palette"
388
            end
389
        end
390
 
391
        f:close()
392
 
393
        return true
394
    end
395
 
4337 helixhorne 396
    local hexmap = {
397
        [0] = 0, -14,  -- 0, 1: gray ramp
398
        14, 0,  -- 2, 3: skin color ramp
399
        0, 14,  -- 4, 5: blue ramp (second part first)
400
        14, 0,  -- 6, 7: nightvision yellow/green
401
        14,  -- 8: red first part...
402
        8,   -- 9: yellow (slightly more red than green)
403
        14, 0,  -- 10, 11: almost gray ramp, but with a slight red hue
404
        8,   -- 12: "dirty" orange
405
        0,   -- 13: ...red second part
406
        8,   -- 14: blue-purple-red
407
    }
408
 
409
    -- Setup base palette 1 (water) to contain one color for each consecutive
410
    -- 16-tuple (which I'm calling a 'hex' for brevity), except for the last
411
    -- one with the fullbrights.
4336 helixhorne 412
    function engine.setupDebugBasePal()
413
        for i=0,14 do
414
            local ptr = C.basepaltable[1] + 3*(16*i)
4337 helixhorne 415
            local src = C.basepaltable[0] + 3*(16*i) + 3*hexmap[i]
4336 helixhorne 416
            local r, g, b = src[0], src[1], src[2]
417
 
418
            for j=0,15 do
419
                local dst = ptr + 3*j
420
                dst[0], dst[1], dst[2] = r, g, b
421
            end
422
        end
423
    end
4404 helixhorne 424
 
425
    function engine.linearizeBasePal()
426
        for _, begi in ipairs{0, 32, 96, 160} do
427
            local ptr = C.basepaltable[0] + 3*begi
428
            local refcol = ptr + 3*31
429
 
430
            for i=0,30 do
431
                for c=0,2 do
432
                    ptr[3*i + c] = i*refcol[c]/31
433
                end
434
            end
435
        end
436
 
437
        for _, begi in ipairs{128, 144, 192, 208, 224} do
438
            local ptr = C.basepaltable[0] + 3*begi
439
 
440
            for i=0,3*15+2 do
441
                ptr[i] = 0
442
            end
443
        end
444
    end
4419 helixhorne 445
 
446
    -- Interfaces to Mapster32's status bar menu
447
 
448
    local pcall = pcall
449
 
450
    function engine.clearMenu()
451
        C.LM_Clear()
452
    end
453
 
4423 helixhorne 454
    function engine.registerMenuFunc(name, func, description)
4419 helixhorne 455
        if (type(name) ~= "string") then
456
            error("invalid argument #1: must be a string", 2)
457
        end
458
        if (type(func) ~= "function") then
459
            error("invalid argument #2: must be a function", 2)
460
        end
4423 helixhorne 461
        if (description~=nil and type(description)~="string") then
462
            error("invalid argument #3: must be nil or a string", 2)
463
        end
4419 helixhorne 464
 
465
        local safefunc = function()
466
            local ok, errmsg = pcall(func)
467
            if (not ok) then
468
                return errmsg
469
            end
470
        end
471
 
4423 helixhorne 472
        C.LM_Register(name, safefunc, description)
4419 helixhorne 473
    end
474
 
475
    engine.GETNUMFLAG = {
476
        NEG_ALLOWED = 1,
477
        AUTOCOMPL_NAMES = 2,
478
        AUTOCOMPL_TAGLAB = 4,
479
        RET_M1_ON_CANCEL = 8,
480
 
481
        NEXTFREE = 16,
482
    }
483
 
484
    function engine.getnumber16(namestart, num, maxnumber, flags)
485
        if (C.qsetmode == 200) then
486
            error("getnumber16 must be called from 2D mode", 2)
487
        end
488
        if (type(namestart)~="string") then
489
            error("invalid argument #1: must be a string", 2)
490
        end
491
 
4420 helixhorne 492
        return C._getnumber16(namestart, num, maxnumber, flags or 8, nil)  -- RET_M1_ON_CANCEL
4419 helixhorne 493
    end
4420 helixhorne 494
 
495
    function engine.getstring(querystr)
496
        if (type(querystr) ~= "string") then
497
            error("invalid argument #2: must be a string", 2)
498
        end
499
        local cstr = C.getstring_simple(querystr, nil, 0, 0)
500
        return cstr~=nil and ffi.string(cstr) or nil
501
    end
4311 helixhorne 502
end
503
 
504
 
4236 helixhorne 505
-- Done!
506
return engine