Subversion Repositories eduke32

Rev

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