1--[[
2 Usage: in Mapster32,
3  > lua "shadexfog=reload'shadexfog'"
4  -- for example
5  > lua "shadexfog.create(100, 255,255,255)"
6  > lua "shadexfog.translate(100, 2)"
7 In EDuke32, simply pass this module at the command line.
8--]]
9
10local assert = assert
11local error = error
12local print = print
13local printf = printf
14local tonumber = tonumber
15local type = type
16local unpack = unpack
17
18local bit = require("bit")
19local math = require("math")
20local string = require("string")
21local min, max = math.min, math.max
22local floor = math.floor
23
24local sector, wall, sprite = sector, wall, sprite
25
26local engine = require("engine")
27local gv = gv
28
29----------
30
31local shadexfog = {}
32
33-- Example:
34--  lua "shadexfog.createremap(30, {[2]=0, [3]=1, [12]=0, [13]=1})"
35-- creates a pal 30 which maps the blue and orange ramps to the gray ones.
36-- (Compare with the rows of http://wiki.eduke32.com/wiki/File:Pala.png)
37--
38-- Sexdecatuple remappings of Duke3D pals loaded from LOOKUP.DAT:
39-- Remappings that are not expressible as such and identity maps (pal 3 and 9)
40-- omitted.
41--
42--  2:  { [0]=8, [1]=13, [2]=8, [3]=13, [4]=13, [5]=8, [6]=8, [7]=13, [9]=8, [10]=8, [11]=13, [12]=8, [14]=8, }
43--  5:  { [8]=2, [13]=3, }
44--  7:  { [0]=10, [1]=9, [2]=10, [3]=9, [4]=9, [5]=10, [6]=10, [7]=9, [8]=10, [11]=9, [12]=9, [13]=9, [14]=9, }
45--  8:  { [0]=6, [1]=7, [2]=6, [3]=7, [4]=7, [5]=6, [8]=6, [9]=7, [10]=6, [11]=7, [12]=7, [13]=7, [14]=6, }
46-- 11:  { [4]=7, [5]=6, }
47-- 12:  { [4]=1, [5]=0, }
48-- 15:  { [4]=3, [5]=2, }
49-- 17:  { [2]=5, [3]=4, [4]=7, [5]=6, [6]=5, [7]=4, [12]=5, [14]=4, }
50-- 18:  { [4]=1, [5]=0, }
51-- 19:  { [2]=8, [3]=13, [4]=1, [5]=0, [6]=8, [7]=13, [12]=8, [14]=13, }
52-- 20:  { [2]=5, [3]=4, [4]=1, [5]=0, [6]=5, [7]=4, [12]=5, [14]=4, }
53-- 21:  { [4]=13, [5]=8, }
54-- 22:  { [4]=7, [5]=6, }
55-- 25:  { [6]=8, [7]=13, }
56function shadexfog.createremap(palnum, remaptab)
57    local sht = engine.getshadetab(0)
58    engine.setshadetab(palnum, sht:remap16(remaptab))
59end
60
61-- Create 32 palookups corrensponding to different *shade levels* of a fog
62-- palookup, called a "shade-x-fog" palookup set in the following.
63--
64-- Pals <startpalnum> .. <startpalnum>+31 will be taken.
65-- <fogr>, <fogg>, <fogb>: intensities of the fog color, [0 .. 255]
66function shadexfog.create(startpalnum, fogr, fogg, fogb)
67    local MAXPALNUM = 255-31-engine.RESERVEDPALS
68    if (not (startpalnum >= 1 and startpalnum <= MAXPALNUM)) then
69        error("invalid startpalnum, max="..MAXPALNUM, 2)
70    end
71
72    local basesht = engine.getshadetab(0)
73
74    -- Encode the shade in different pal numbers! The shade tables are
75    -- constructed with a fog in their place.
76    for dummyshade=0,31 do
77        local sht = engine.shadetab()
78
79        for f=0,31 do
80            for i=0,255 do
81                local r, g, b = engine.getrgb(basesht[dummyshade][i])
82
83                local nr, ng, nb =
84                    (r*(32-f) + fogr*f) / 32,
85                    (g*(32-f) + fogg*f) / 32,
86                    (b*(32-f) + fogb*f) / 32
87
88                sht[f][i] = engine.nearcolor(nr, ng, nb)
89            end
90        end
91
92        engine.setshadetab(startpalnum + dummyshade, sht)
93    end
94end
95
96local function trans(what, startpalnum, fogintensity)
97    if (what.pal >= startpalnum and what.pal <= startpalnum+31) then
98        -- Auto-detect earlier translation with the same <startpalnum>.
99        what.shade = what.pal - startpalnum
100    end
101
102    local shade = min(max(what.shade, 0), 31)
103    what.pal = startpalnum + shade
104    what.shade = fogintensity
105end
106
107-- shadexfog.translate(startpalnum, fogintensity [, vis])
108--
109-- Translate the whole map for use with a shade-x-fog palookup set.
110--  .pal becomes the <startpalnum> + former .shade
111--  .shade becomes the <fogintensity> [0 .. 31]
112-- If <vis> is passed and >= 0, set all sector's visibility to that value.
113--
114-- Notes:
115--  - auto-detects when the translation has been applied with the *same*
116--   <startpalnum> (if a different one is desired, must reload map).
117--  - if shades < 0 or > 31 present, loss of information
118function shadexfog.translate(startpalnum, fogintensity, vis)
119    for i=0,gv.numsectors-1 do
120        trans(sector[i].ceiling, startpalnum, fogintensity)
121        trans(sector[i].floor, startpalnum, fogintensity)
122        if (vis and vis >= 0) then
123            sector[i].visibility = vis
124        end
125    end
126
127    for i=0,gv.numwalls-1 do
128        trans(wall[i], startpalnum, fogintensity)
129    end
130end
131
132if (gv.LUNATIC_CLIENT == gv.LUNATIC_CLIENT_EDUKE32 and LUNATIC_FIRST_TIME) then
133    shadexfog.create(100, 255,255,255)
134    print("created shadexfog palookups")
135end
136
137---------- BASE SHADE TABLE TESTS ----------
138
139-- sht = shadexfog.create_depth_shtab([palnum])
140function shadexfog.create_depth_shtab(palnum)
141    local sht = engine.shadetab()
142
143    for s=0,31 do
144        for i=0,255 do
145            sht[s][i] = s
146        end
147    end
148
149    if (palnum) then
150        engine.setshadetab(palnum, sht)
151    end
152    return sht
153end
154
155function shadexfog.create_vismarker_shtab(palnum)
156    local sht = engine.getshadetab(0)
157
158    for i=0,255 do
159        sht[1][i] = 242
160        sht[30][i] = 245
161    end
162
163    if (palnum) then
164        engine.setshadetab(palnum, sht)
165    end
166    return sht
167end
168
169-- Basic test of whether for a color index i corresponding to a color (r,g,b),
170-- getclosestcol() returns a color index ii corresponding to the same color.
171-- (In the Duke3D palette, there are duplicates, so the requirement i==ii is
172-- too strict.)
173function shadexfog.test_nearcolor()
174    for i=0,255 do
175        local r, g, b = engine.getrgb(i)
176        local ii = engine.nearcolor(r, g, b)
177        local rr, gg, bb = engine.getrgb(ii)
178
179        if (r~=rr or g~=gg or b~=bb) then
180            printf("diff %d: %d,%d,%d  %d,%d,%d", i, r,g,b, rr,gg,bb)
181        end
182    end
183end
184
185-- Change the .pal member of all sector ceilings/floors, walls and sprites to
186-- <palnum>.
187function shadexfog.challpal(palnum)
188    for i=0,gv.numsectors-1 do
189        sector[i].ceilingpal = palnum
190        sector[i].floorpal = palnum
191    end
192    for i=0,gv.numwalls-1 do
193        wall[i].pal = palnum
194    end
195    for i in sprite.all() do
196        sprite[i].pal = palnum
197    end
198end
199
200-- Create our version of the base shade table (palookup 0)
201--
202-- NOTE: Nope, the base shade table is NOT created by applying a linear ramp to
203-- the base palette colors!!!
204local function create_base_shtab(basesht)
205    local basesht = basesht or engine.getshadetab(0)
206
207    local sht = engine.shadetab()
208    sht[0] = basesht[0]
209    for sh=1,31 do
210        for i=0,255-16 do
211            -- NOTE that this fails, see BASESHT_0_NOT_IDENTITY:
212--            assert(basesht[0][i] == i)
213            local r, g, b = engine.getrgb(i)
214            local f = 1
215            r = ((32-f*sh+0.5)*r)/32
216            g = ((32-f*sh+0.5)*g)/32
217            b = ((32-f*sh+0.5)*b)/32
218            r, g, b = max(0,r), max(0,g), max(0,b)  -- if f is > 1
219            sht[sh][i] = engine.nearcolor(r, g, b)
220        end
221
222        for i=255-16+1,255 do
223            -- fullbrights
224            sht[sh][i] = basesht[0][i]
225        end
226    end
227
228    return sht
229end
230
231local function create_base_shtab_2(basesht)
232    local basesht = basesht or engine.getshadetab(0)
233
234    local perm16 = { [0]=0,1, 2,3, 5,4, 6,7, 8,13, 10,11, 12,9, 14,15 }
235    basesht = basesht:remap16(perm16)
236
237    local iperm16 = {}
238    for i=0,15 do
239        iperm16[perm16[i]] = i
240    end
241
242    local iperm = {}
243    for i=0,255 do
244        iperm[i] = 16*(iperm16[floor(i/16)]) + i%16
245    end
246
247    local baseidx = {}
248    for i=0,255-16 do
249        baseidx[i] = i < 192 and 32*floor(i/32) or 16*floor(i/16)
250    end
251
252    local sht = engine.shadetab()
253
254    for sh=0,31 do
255        for i=0,255-16 do
256            local bi = baseidx[i]
257            local cidx = bi + floor(((31-sh)*(i - bi))/31)
258            sht[sh][i] = iperm[cidx]
259        end
260
261        for i=255-16+1,255 do
262            -- fullbrights
263            sht[sh][i] = basesht[0][i]
264        end
265    end
266
267    return sht:remap16(iperm16)
268end
269
270local ismapster32 = (gv.LUNATIC_CLIENT == gv.LUNATIC_CLIENT_MAPSTER32)
271
272if (ismapster32) then
273    -- Wrapper around engine.savePaletteDat() that errors on unexpected events.
274    function shadexfog.save(filename, palnum, blendnum, moreblends, lognumalphatabs)
275        local ok, errmsg, nummoreblends = engine.savePaletteDat(
276            filename, palnum, blendnum, moreblends, lognumalphatabs)
277        if (not ok) then
278            error(errmsg)
279        end
280
281        printf('Wrote base palette, shade and translucency tables to "%s".', filename)
282        if (nummoreblends > 0) then
283            printf("  Also wrote %d additional translucency tables.", nummoreblends)
284        end
285    end
286
287    function shadexfog.saveLookupDat(filename, lookups)
288        local ok, errmsg, numlookups = engine.saveLookupDat(filename, lookups)
289        if (not ok) then
290            error(errmsg)
291        end
292
293        printf('Wrote %d lookup tables and 5 base palettes to "%s".',
294               numlookups, filename)
295    end
296end
297
298-- Create our (failed) version of the base shade table at set it to palookup
299-- number <palnum>.
300-- <secver>: use second attempt?
301function shadexfog.create0(palnum, secver)
302    local sht0 = secver and create_base_shtab_2() or create_base_shtab()
303    engine.setshadetab(palnum, sht0)
304end
305
306function shadexfog.test_create0()
307    local basesht = engine.getshadetab(0)
308
309    for i=0,255 do
310        if (basesht[0][i] ~= i) then
311            -- BASESHT_0_NOT_IDENTITY
312            printf("Base shade table at index %d: %d", i, basesht[0][i])
313        end
314    end
315
316    local sht = create_base_shtab(basesht)
317
318    local ok = true
319    for sh=1,31 do
320        for i=0,255 do
321            local ouri, origi = sht[sh][i], basesht[sh][i]
322--            if (sht[sh][i] ~= basesht[sh][i]) then
323            if (math.abs(ouri - origi) > 1) then
324                printf("Constructed shade table DIFFERS AT shade %d index %d: orig %d ours %d",
325                       sh, i, basesht[sh][i], sht[sh][i])
326                ok = false
327                goto out
328            end
329        end
330    end
331
332    ::out::
333    if (ok) then
334        printf("Constructed shade table IDENTICAL WITH original one")
335    end
336end
337
338---------- Blending table tests ----------
339
340-- shadexfog.create_trans(startblendidx, func [, numtables [, fullbrightsOK]])
341--
342-- <func>: must be
343--  rr, gg, bb = f(r,g,b, R,G,B, level, numtables)
344--  If reverse translucency bit clear, (r,g,b) is background and (R,G,B) is
345--  foreground (incoming).
346--  ('level' is the table index, from 1 to <numtables>)
347-- <numtables>: number of tables to create, from <startblendidx> on. Default: 1
348function shadexfog.create_trans(startblendidx, func, numtables, fullbrightsOK)
349    numtables = numtables or 1
350    local lastokcol = fullbrightsOK and 255 or 255-16
351
352    local tab = engine.blendtab()
353
354    for level=1,numtables do
355        for i=0,255 do
356            local r,g,b = engine.getrgb(i)
357            for j=0,255 do
358                local R,G,B = engine.getrgb(j)
359
360                local rr, gg, bb = func(r,g,b, R,G,B, level, numtables)
361                tab[i][j] = engine.nearcolor(rr, gg, bb, lastokcol)
362            end
363        end
364
365        engine.setblendtab(startblendidx + level-1, tab)
366    end
367end
368
369local function check_numtables(numtables)
370    if (numtables ~= nil) then
371        if (type(numtables) ~= "number" or not (numtables >= 1 and numtables <= 128)) then
372            error("invalid argument #2: must be a number in [1 .. 128]", 2)
373        end
374
375        if (bit.band(numtables, numtables-1) ~= 0) then
376            error("invalid argument #2: must be a power of two", 2)
377        end
378    end
379end
380
381-- shadexfog.create_alpha_trans(startblendidx [, numtables [, fullbrightsOK]])
382--
383-- Creates <numtables> blending tables of smooth alpha translucency, with
384-- fractions 1/(2*numtables), 2/(2*numtables) ... numtables/(2*numtables).
385-- <numtables> must be a power of two in [1 .. 128].
386function shadexfog.create_alpha_trans(startblendidx, numtables, fullbrightsOK)
387    check_numtables(numtables)
388
389    shadexfog.create_trans(
390        startblendidx,
391
392        function(r,g,b, R,G,B, alpha, numtabs)
393            local f = alpha/(2*numtabs)
394            local F = 1-f
395            return f*r+F*R, f*g+F*G, f*b+F*B
396        end,
397
398        numtables, fullbrightsOK
399    )
400end
401
402-- shadexfog.create_additive_trans(startblendidx [, numtables [, fullbrightsOK]])
403function shadexfog.create_additive_trans(startblendidx, numtables, fullbrightsOK)
404    shadexfog.create_trans(
405        startblendidx,
406
407        function(r,g,b, R,G,B, level, numtabs)
408            local f = level/numtabs
409            return min(f*r+R, 255), min(f*g+G, 255), min(f*b+B, 255)
410        end,
411
412        numtables, fullbrightsOK
413    )
414end
415
416-- shadexfog.create_brightpass_trans(startblendidx [, numtables [, fullbrightsOK]])
417function shadexfog.create_brightpass_trans(startblendidx, numtables, fullbrightsOK)
418    shadexfog.create_trans(
419        startblendidx,
420
421        function(r,g,b, R,G,B, alpha, numtabs)
422            local a = alpha/numtabs
423            local F = 1 - min(a, (R+G+B) / (3*255))
424            local f = 1 - F
425            return f*r+F*R, f*g+F*G, f*b+F*B
426        end,
427
428        numtables, fullbrightsOK
429    )
430end
431
432if (not ismapster32) then
433    return shadexfog
434end
435
436--========== Mapster32 Lua menu hooks ==========--
437
438local getnumber16 = engine.getnumber16
439local GNF = engine.GETNUMFLAG
440local GNF_BOOL = GNF.NEXTFREE
441
442local df = GNF.RET_M1_ON_CANCEL  -- default getnumber16() flags
443
444local MAXUSERPALOOKUP = 256-1-8  -- KEEPINSYNC engine.lua:check_palidx()
445
446-- wrapped_func = CreateMenuFunction(argdesc)
447--
448-- <argdesc>: table with [0]=<func> and then, entries { name, init, max [, noret] }
449local function CreateMenuFunction(argdesc)
450    return function()
451        local func = argdesc[0]
452        assert(type(func) == "function")
453        local args = {}
454
455        for i=1,#argdesc do
456            local ad = argdesc[i]
457            assert(type(ad) == "table" and #ad == 3 or #ad == 4)
458
459            local moreflags = ad[4] or 0
460            args[i] = getnumber16(ad[1]..": ", ad[2], ad[3], bit.bor(df, moreflags))
461            if (bit.band(moreflags, GNF.NEG_ALLOWED)==0 and args[i] < 0) then
462                return
463            end
464            if (bit.band(moreflags, GNF_BOOL)~=0) then
465                args[i] = (args[i] > 0)
466            end
467        end
468
469        func(unpack(args))
470    end
471end
472
473-- Replace chevrons (angle brackets!) with printext16 markup.
474local function replchev(matchstr) return "^15"..matchstr:sub(2,-2).."^O" end
475-- Replace ASCII code escapes like \XXX. We can't use these escapes in Lua [[ ... ]] strings.
476local function replascii(matchstr) return string.char(tonumber(matchstr)) end
477-- Format a whole string for the menu system:
478local function formatHelp(str)
479    return str:gsub(
480        "(%b<>)", replchev):gsub(
481        "%*(.-)%*", "^014%1^O"):gsub(
482        "%\\(%d+)", replascii)
483end
484
485----------
486
487engine.clearMenu()
488
489engine.registerMenuFunc(
490    "Create shadexfog palset",
491    CreateMenuFunction{
492        [0] = shadexfog.create,
493        { "Starting palnum", 100, MAXUSERPALOOKUP-31 },
494        { "Red fog color [0-255]", 0, 255 },
495        { "Green fog color [0-255]", 0, 255 },
496        { "Blue fog color [0-255]", 0, 255 },
497    },
498
499formatHelp
500[[
501<shadexfog.create(startpalnum, fogr, fogg, fogb)>
502<_______________________________________________>
503
504Creates 32 shade tables corresponding to different *shade levels*
505of a fog palookup, together called a *shade-x-fog* palookup set.
506
507 Pals <startpalnum> to <startpalnum>+31 will be taken.
508 <fogr>, <fogg>, <fogb>: intensities of the fog color, [0 .. 255]
509]]
510)
511
512engine.registerMenuFunc(
513    "Translate map for shxfog",
514    CreateMenuFunction{
515        [0] = shadexfog.translate,
516        { "Starting palnum", 100, MAXUSERPALOOKUP-31 },
517        { "Fog intensity [0-31]", 0, 31 },
518        { "Change all sectors' visibility to (Esc: don't)", 0, 255, GNF.NEG_ALLOWED },
519    },
520
521formatHelp
522[[
523<shadexfog.translate(startpalnum, fogintensity [, vis])>
524<______________________________________________________>
525
526Translates the whole map for use with a shade-x-fog palookup set.
527
528 .pal becomes the <startpalnum> + former .shade
529 .shade becomes the <fogintensity> [0 .. 31]
530
531 If <vis> is passed and >= 0, set all sector's visibility to that
532 value.
533
534 *Notes:*
535  - auto-detects when the translation has been applied with the *same*
536    <startpalnum> (if a different one is desired, must reload map).
537  - if shades > 31 or < 0 present, there is loss of information
538]]
539)
540
541engine.registerMenuFunc(
542    "Change pal of everything",
543    CreateMenuFunction{
544        [0] = shadexfog.challpal,
545        { "Pal to change to", 0, MAXUSERPALOOKUP },
546    },
547
548formatHelp
549[[
550<shadexfog.challpal(palnum)>
551<__________________________>
552
553Changes the .pal member of all sector ceilings/floors, walls and
554sprites to <palnum>.
555]]
556)
557
558engine.registerMenuFunc(
559    "Create alpha trans. tabs",
560    CreateMenuFunction{
561        [0] = shadexfog.create_alpha_trans,
562        { "Starting blendnum", 1, 255 },
563        { "Number of blending tables", 32, 255 },
564        { "Fullbright result colors OK?", 0, 1, GNF_BOOL },
565    },
566
567formatHelp
568[=[
569<shadexfog.create_alpha_trans(startblend [, numtables [, fullbriOK]])>
570<____________________________________________________________________>
571
572Creates <numtables> blending tables of smooth alpha translucency,
573starting with the blending number <startblend>, with values of alpha
574
575  1/(2\255<numtables>), 2/(2\255<numtables>) ... <numtables>/(2\255<numtables>).
576
577 <numtables> must be a power of two in [1 .. 128].
578
579 <fullbriOK>: should fullbright color indices (>= 240) be permitted as
580            the blending result of two color indices?
581]=]
582)
583
584engine.registerMenuFunc(
585    "Create addtv. trans. tabs",
586    CreateMenuFunction{
587        [0] = shadexfog.create_additive_trans,
588        { "Starting blendnum", 1, 255 },
589        { "Number of blending tables", 32, 255 },
590        { "Fullbright result colors OK?", 0, 1, GNF_BOOL },
591    },
592
593formatHelp
594[=[
595<shadexfog.create_additive_trans(startbl [, numtables [, fullbriOK]])>
596<____________________________________________________________________>
597
598Creates <numtables> blending tables of smooth additive translucency,
599starting with the blending number <startbl>, with factors for the
600background color
601
602  1/<numtables>, 2/<numtables> ... <numtables>/<numtables>.
603
604 <numtables> must be a power of two in [1 .. 128].
605
606 <fullbriOK>: should fullbright color indices (>= 240) be permitted as
607            the blending result of two color indices?
608]=]
609)
610
611engine.registerMenuFunc(
612    "Create bri.pass tr. tabs",
613    CreateMenuFunction{
614        [0] = shadexfog.create_brightpass_trans,
615        { "Starting blendnum", 1, 255 },
616        { "Number of blending tables", 32, 255 },
617        { "Fullbright result colors OK?", 0, 1, GNF_BOOL },
618    },
619
620formatHelp
621[=[
622<shadexfog.create_brightpass_trans(startbl [, numtabs [, fullbriOK]])>
623<____________________________________________________________________>
624
625Creates <numtabs> blending tables of "brightpass" translucency,
626starting with the blending number <startbl>, with fractions
627
628  1/<numtables>, 2/<numtables> ... <numtables>/<numtables>.
629
630 <fullbriOK>: should fullbright color indices (>= 240) be permitted as
631            the blending result of two color indices?
632]=]
633)
634
635engine.registerMenuFunc(
636    "Create base shade table",
637    CreateMenuFunction{
638        [0] = shadexfog.create0,
639        { "Pal number", 100, MAXUSERPALOOKUP },
640        { "Second attempt?", 1, 1, GNF_BOOL },
641    },
642
643formatHelp
644[[
645<shadexfog.create0(palnum, secver)>
646<_________________________________>
647
648Creates our version of the base shade table at set it to palookup
649number <palnum>.
650
651 <secver>: use second attempt instead of the first? This one is more
652   similar to the base shade table shipped with Duke3D, but still
653   shows significant differences.
654]]
655)
656
657engine.registerMenuFunc(
658    "Create c.index remapping",
659    function()
660        local palnum = getnumber16("Pal number: ", 100, MAXUSERPALOOKUP)
661        if (palnum < 0) then return end
662
663        local remaptab = {}
664        while (true) do
665            local srchex = getnumber16("Source hexadecatuple (0: finish): ", 0, 14)
666            if (srchex < 0) then return end
667            local dsthex = getnumber16("Destn. hexadecatuple (0: finish): ", 0, 14)
668            if (dsthex < 0) then return end
669
670            if (srchex == 0 and dsthex == 0) then
671                break
672            end
673
674            remaptab[srchex] = dsthex
675        end
676
677        shadexfog.createremap(palnum, remaptab)
678    end,
679
680formatHelp
681[[
682<shadexfog.createremap(palnum, remaptab)>
683<_______________________________________>
684
685Creates a color index remapping expressed as mappings of sexdeca-
686tuples (16-tuples) of the base palette at pal <palnum>.
687
688 Duke3D's default base palette can be considered to consist of six
689 ramps of 32 colors each, three ramps of 16 colors each and a
690 remaining fullbright color set. The sexdecatuples are as follows:
691
692<  0,  1>: gray ramp
693<  2,  3>: skin color ramp
694<  5,  4>: blue ramp (note that the 16-tuples are in reverse order)
695<  6,  7>: nightvision yellow/green
696<  8, 13>: red ramp
697< 10, 11>: almost gray ramp, but with a slight red hue
698
699    <  9>: yellow (slightly more red than green)
700    < 12>: "dirty" orange
701    < 14>: blue-purple-red
702]]
703)
704
705local function getNumberRange(what, whating)
706    local str = engine.getstring("Additional "..what.." numbers (e.g. '64,100-131,255'): ")
707    if (str == nil) then return end
708    if (str == "") then return {} end
709
710    if (not str:find("^[%d,%-]+$")) then
711        error("Additional "..whating.." numbers string must contain only digits or ',' or '-'", 2)
712    end
713
714    local moreblends = {}
715    local didnumstr = {}
716
717    for n1, n2 in str:gmatch("(%d+)%-(%d+)") do  -- parse number ranges
718        moreblends[#moreblends+1] = { tonumber(n1), tonumber(n2) }
719        didnumstr[n1] = true
720        didnumstr[n2] = true
721    end
722
723    for n in str:gmatch("%d+") do  -- parse single numbers
724        if (not didnumstr[n]) then
725            moreblends[#moreblends+1] = tonumber(n)
726        end
727    end
728
729    return moreblends
730end
731
732engine.registerMenuFunc(
733    "Save pal+sh+trans DAT f.",
734    function()
735        local filename = engine.getstring("File name: ")
736        if (filename == nil) then return end
737
738        local palnum = getnumber16("Pal number of base shade table: ", 0, MAXUSERPALOOKUP)
739        if (palnum < 0) then return end
740        local blendnum = getnumber16("Blendnum of base transluc. table: ", 0, 255)
741        if (blendnum < 0) then return end
742
743        local moreblends = getNumberRange("blend", "blending")
744        if (moreblends == nil) then return end
745
746        local lognumalphatabs
747        if (#moreblends > 0) then
748            lognumalphatabs = getnumber16("log2 of last alpha blending table index (1-7, 0: none): ", 0, 7)
749            if (lognumalphatabs < 0) then return end
750            if (lognumalphatabs == 0) then lognumalphatabs = nil end
751        end
752
753        shadexfog.save(filename, palnum, blendnum, moreblends, lognumalphatabs)
754    end,
755
756formatHelp
757[[
758<shadexfog.save(filename, palnum, blendnum, moreblends, lognumalpha)>
759<___________________________________________________________________>
760
761Writes out a full PALETTE.DAT-formatted file named <filename> with the
762base shade table numbered <palnum> and the base translucency table
763numbered <blendnum>.
764
765Finally, you are asked to specify additional blending tables that can
766be stored in EDuke32's extended PALETTE.DAT format. If one or more
767additional blending table is specified, you are also queried for the
768log2 of the last alpha blending table index, <lognumalpha>. Since alpha
769blending tables are assumed to be set up at indices 1 to
770exp(2, <lognumalpha>), it is also the log2 of their total count.
771]]
772)
773
774engine.registerMenuFunc(
775    "Save lookups DAT file",
776    function()
777        local filename = engine.getstring("File name: ")
778        if (filename ~= nil and filename ~= "") then
779            local lookups = {
780                -- Duke3D 1.5 LOOKUP.DAT order
781                1,2,6,7,8, 3,4,5,9,10,
782                12,13,15,16,18, 19,11,14,17,20,
783                21,22,23,24,25
784            }
785
786            local morelookups = getNumberRange("lookup", "lookup")
787            if (morelookups == nil) then return end
788
789            if (#morelookups > 0) then
790                for i=1,#morelookups do
791                    lookups[#lookups+1] = morelookups[i]
792                end
793            end
794
795            shadexfog.saveLookupDat(filename, lookups)
796        end
797    end,
798
799formatHelp
800[[
801<shadexfog.saveLookupDat(filename, lookups)>
802<__________________________________________>
803
804Saves the color index lookups (i.e. first 256 values of each shade
805table) of the pal numbers which have lookups in Duke3D's unaltered
806LOOKUP.DAT, plus optional ones provided by the user.
807
808The default ones are, in this order:
8091,2,6,7,8, 3,4,5,9,10, 12,13,15,16,18, 19,11,14,17,20, 21,22,23,24,25.
810(All pal numbers from 1 to 25 are present.)
811
812 <filename>: the name of the LOOKUP.DAT-formatted file to create
813]]
814)
815
816engine.registerMenuFunc("_________DEBUG_________", function() end
817--[[
818,
819" \01\02\03\04\05\06\07\08\09\10\11\12\13\14\15\
820\16\17\18\19\20\21\22\23\24\25\26\27\28\29\30\31\
821\128\129\130\131\132\133\134\135\136\137\138\139\140\141\142\143\
822\144\145\146\147\148\149\150\151\152\153\154\155\156\157\158\159\
823\160\161\162\163\164\165\166\167\168\169\170\171\172\173\174\175\
824\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\191\
825\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\
826\208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\
827\224\225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\
828\240\241\242\243\244\245\246\247\248\249\250\251\252\253\254\255"
829--]]
830)
831
832engine.registerMenuFunc("Setup dbg. water basepal", engine.setupDebugBasePal,
833formatHelp
834[[
835<engine.setupDebugBasePal()>
836<__________________________>
837
838Overwrites the water base palette with one where each 16-tuple
839(except the fullbrights) consists of a single representative
840color.
841
842This can be used to get a quick glimpse about what ramps are present
843in particular tiles. With this information, custom lookups can be
844created more directedly with the <Create c.index remapping> menu
845entry.
846]]
847)
848
849engine.registerMenuFunc(
850    "Create depth shade tab",
851    CreateMenuFunction{
852        [0] = shadexfog.create_depth_shtab,
853        { "Pal number", 100, MAXUSERPALOOKUP },
854    },
855
856formatHelp
857[[
858<shadexfog.create_depth_shtab(palnum)>
859<____________________________________>
860
861Creates a shade table for debugging purposes at pal <palnum>.
862
863 For every color index, the shade table maps shade index <i> to
864 color index <i> (of the first ramp of gray colors, assuming Build
865 has loaded a base shade table with 32 shade levels).
866]]
867)
868
869engine.registerMenuFunc(
870    "Create vismarker sh. tab",
871    CreateMenuFunction{
872        [0] = shadexfog.create_vismarker_shtab,
873        { "Pal number", 100, MAXUSERPALOOKUP },
874    },
875
876formatHelp
877[[
878<shadexfog.create_vismarker_shtab(palnum)>
879<________________________________________>
880
881Creates a shade table for debugging purposes at pal <palnum>.
882
883 For every color index, the shade table maps shade index 1 to
884 a ^14bright yellow^O color and shade index 30 to a ^13purple^O color.
885 Thus, it can be useful in visualizing the limits of the
886 fog/visibility attenuation.
887]]
888)
889
890engine.registerMenuFunc("Linearize default basep.", engine.linearizeBasePal,
891formatHelp
892[[
893<engine.linearizeBasePal()>
894<_________________________>
895
896Overwrites the default base palette with one where certain ramps have
897their attenuation linearized. This is mainly useful for debugging
898purposes as it excludes the effect of this nonlinearity for
899comparison of fog/visibility between classic and OpenGL modes.
900]]
901)
902
903
904do
905    return shadexfog
906end
907