Subversion Repositories eduke32

Rev

Rev 6056 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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