1-- test script for ELua/Lunatic Interpreter
2
3-- error=nil -- must not affect "require"
4local require = require
5local string = require("string")
6local bit = require("bit")
7local math = require("math")
8
9local pcall = pcall
10local DBG_ = require("_LUNATIC_DBG")
11
12local gv, sector, wall, sprite, spriteext = gv, sector, wall, sprite, spriteext
13local actor, player, projectile = actor, player, projectile
14local gameevent, gameactor = gameevent, gameactor
15
16local spritesofsect = spritesofsect
17local hitscan = hitscan
18
19local assert, error, print, tostring = assert, error, print, tostring
20
21local D = require("CON.DEFS")
22
23print('---=== ELua Test script ===---')
24
25local function printf(fmt, ...)
26    print(string.format(fmt, ...))
27end
28
29local function checkfail(funcstr, expectedmsg)
30    if (DBG_ == nil) then
31        return
32    end
33
34    local status, errmsg = pcall(DBG_.loadstring(funcstr))
35    if (status) then
36        print('^21ERROR:^O '..funcstr.." DIDN'T fail")
37    else
38        if (expectedmsg==nil or string.find(errmsg, expectedmsg, 1, true)) then
39            print("^11SUCCESS:^O "..funcstr.." failed: "..errmsg)
40        else
41            -- XXX: beginning with "^10" is counted as error in OSD_Printf()
42            print("^10ERROR*:^O "..funcstr.." failed: "..errmsg..
43                  ", but expected error message was: "..expectedmsg)
44        end
45    end
46end
47
48gameevent
49{
50    "ENTERLEVEL",
51
52    function()
53        local vol, lev = gv.currentEpisode(), gv.currentLevel()
54        printf('volume=%d, level=%d', vol, lev)
55
56        if (vol ~= 4) then
57            -- Tweak some sector pals.
58            print('tweaking sector pals')
59            print('numsectors: ' .. gv.numsectors .. ' of ' .. gv.MAXSECTORS)
60
61            local SF = sector.STAT
62            for i = 0, gv.numsectors/2 do
63                local sec = sector[i]
64                sec.floorpal = 1;
65                sector[i].floor.shade = sec.floor.shade + 4
66                sector[i].ceilingpal = 2;
67                local ceil = sec.ceiling
68                ceil.shade = sector[i].ceiling.shade + 8
69                ceil.statbits:flip(SF.SMOOSH)
70                sec.floorstatbits:flip(SF.SWAPXY)
71            end
72        end
73
74        if (vol==1 and lev==1) then  -- E1L1
75            print('tweaking some sprites 2')
76            local i = 562
77            spriteext[i].alpha = 0.5;
78            sprite[i].cstat = bit.bor(sprite[i].cstat, 2+512);
79            spriteext[i].pitch = 128;
80            spriteext[i].roll = 256;
81
82            i = 107  -- pistol ammo at rooftop
83            spriteext[i].pitch = 128;
84            spriteext[i].roll = 256;
85
86            for spr in spritesofsect(307) do  -- some fence sprites in E1L1
87                printf('spr %d', spr)
88                sprite[spr].pal = 6
89            end
90
91            actor[562].flags = bit.bor(actor[562].flags, 2);   -- pal 6 with goggles on front SEENINE
92        end
93---[[
94        if (vol==1 and lev==8) then
95            local havebunch = false
96            for i=0,gv.numsectors-1 do
97                havebunch = havebunch or (sector[i].ceilingbunch >= 0)
98            end
99            if (havebunch) then
100                print('tweaking bunch 1');
101                -- trueror1.map
102                for i, what in sectorsofbunch(1, gv.BOTH_CF) do
103                    sector[i][what].z = sector[i][what].z - 3*1024;
104                end
105            end
106        end
107--]]
108    end
109}
110
111gameevent
112{
113    "JUMP",
114
115    function()
116        print("tweaking forcefield with lotag 34 (E2L1)")
117
118        for w=0,gv.numwalls-1 do
119            local wal = wall[w]
120            if (wal.overpicnum == D.W_FORCEFIELD or wal.overpicnum == D.W_FORCEFIELD+1) then
121                if (wal.lotag==34) then
122                    wal.cstat = wal.cstatbits:test(85) and 0 or 85
123                end
124            end
125        end
126    end
127}
128
129local unsafe = pcall(function() string.UNSAFE=true; end)
130
131checkfail("tostring = nil", "attempt to write into the global environment")
132--DBG_.printkv('_G in test.elua', _G)
133
134-- direct gv array access forbidden
135checkfail('gv.sprite[0].yrepeat = 100', "access forbidden")
136
137checkfail('print(sprite[100000].ceilingpal)', "out-of-bounds sprite[] read access")
138
139checkfail('print(gv.sprite[0])', "access forbidden")
140
141-- set metatable forbidden
142checkfail('setmetatable(sprite, {})', "attempt to read undeclared variable 'setmetatable'")
143
144gameevent
145{
146    "ENTERLEVEL",
147
148    function()
149        -- OOB write access.
150        -- Note that indexing ("reading") sector fails, even if the user wants to
151        -- assign to a sector member. Potentially confusing error message.
152        checkfail('sector[-1].ceilingpal = 4', "out-of-bounds sector[] read access")
153
154        -- wallnum member is read-only
155        checkfail('sector[0].wallnum = 0', "attempt to write to constant location")  -- this comes from LJ/FFI
156
157        -- direct sector write access forbidden
158        checkfail('sector[4] = sector[6]', "cannot write directly to sector[]")
159
160        -- creating new keys forbidden... handled by LuaJIT
161        checkfail('wall[4].QWE = 123', "has no member named 'QWE'")
162
163        -- no pointer arithmetic!
164        checkfail('local spr = sprite[0]; local x=spr+1', "attempt to perform arithmetic on")
165
166        -- actor[].t_data[] is not accessible for now
167        checkfail('local i = actor[0].t_data[15]', "has no member named 't_data'")
168
169        -- sprite.picnum may happen as a thinko/typo kind of error (spr.picnum was meant)
170        checkfail("local pic = sprite.picnum", "invalid access to static data")
171
172        checkfail("require('engine').setshadetab(200, nil)",
173                  "setshadetab() may be run only while LUNATIC_FIRST_TIME is true")
174
175        checkfail("sprite[0]:set_picnum(-10)", "invalid tile number")
176    end
177}
178
179-- gv.numsectors is read-only
180checkfail('gv.numsectors = 4', "attempt to write to constant location")
181
182-- cannot create new fields in 'gv'
183checkfail('gv.QWE = 4', "write access forbidden")
184
185-- that would be horrible...
186checkfail('sprite._nextspritesect[4] = -666', "cannot write directly to nextspritesect[]")
187
188-- we're indexing a plain array!
189checkfail('print(sprite._nextspritesect[4].whatfield)', "attempt to index a number value")
190
191-- our 'require' has only safe stuff
192--checkfail("require('os')")
193
194-- gamevars are created using a special different mechanism
195checkfail("new_global = 345", "attempt to write into the global environment")
196-- Can't reassign to existing vars either
197assert(actor ~= nil)
198checkfail("actor = 345", "attempt to write into the global environment")
199
200-- can't redefine constants in 'gv'
201checkfail('gv.CEILING = 3', "attempt to write to constant location")
202
203-- string.dump is unavailable
204checkfail('local s=require[[string]]; local tmp=s.dump(gameevent)',
205          "attempt to call field 'dump' (a nil value)")
206
207if (not unsafe) then
208    -- changing base module tables is disallowed
209    checkfail('local s=require[[string]]; s.format=nil', "modifying base module table forbidden")
210else
211    print('WARNING: RUNNING WITH UNPROTECTED BASE MODULES')
212end
213
214print('')
215-- This is problematic, even though pretty much every access will yield a
216-- "missing declaration" error.
217-- See http://luajit.org/ext_ffi_api.html#ffi_C about what stuff ffi.C contains.
218checkfail('gv.luaJIT_setmode(nil, 0, 0)', "missing declaration for symbol 'luaJIT_setmode'")
219
220checkfail('gv.luaJIT_BC_con_lang', "attempt to call a nil value")
221checkfail('gv.gethiticks = nil', "attempt to write to constant location")
222
223checkfail('gameactor{1680, 0}', "must provide a function with last numeric arg or .func")
224
225checkfail("do local bt=require'test.test_bitar'; bt.QWE=1; end", "modifying module table forbidden")
226-- the cdata returned by player[] can't be made into a pointer!
227checkfail("do local pl=player[0]; i=pl[1]; end")
228checkfail("do local ud=gv.ud.camerasprite; end", "access forbidden")  -- test for proper decl()
229checkfail("gv.g_sizes_of=nil; print(gv.g_sizes_of[0])", "write access forbidden")
230checkfail("gv.cam.sect=-1", "invalid sector number")
231checkfail("local flag=gv.SFLAG_NULL", "missing declaration")
232
233-- NOTE: player[0] is forbidden at file scope, this is just for testing purposes.
234player[0].wackedbyactor = -1  -- should succeed
235checkfail("player[0].curr_weapon = -1", "Invalid weapon ID")
236player[0].curr_weapon = 1
237checkfail("local w = player[0].weapon[-1]", "out-of-bounds weapon read access")
238
239-- XXX: This gives a very strange error message: "attempt to write to constant location". Why?
240-- (note how I forgot to index weapon with a weapon index or name)
241--player[0].weapon.firesound = 1e5
242checkfail("player[0].weapon.SHOTGUN.firesound = 1e5", "invalid sound number")
243checkfail("player[0].weapon.SHOTGUN.firesound = 0/0", "must be a non-NaN number")
244checkfail("player[0].weapon.SHOTGUN.firesound = 1/0", "invalid sound number")
245checkfail("gameactor{1680, action=require('con').action{numframes=-10}, function() end}",
246          "action has negative number of frames")
247-- NOTE: It should only be relied on that setting e.g. .firesound to -1 sets it
248-- to 0, not other negative values.
249player[0].weapon.SHOTGUN.firesound = -1/0
250assert(player[0].weapon.SHOTGUN.firesound == 0)
251
252gameevent{gv.EVENT_JUMP,
253          function(actori, playeri, dist)
254              printf("jump i=%d p=%d d=%d", actori, playeri, dist)
255              error("greetings from EVENT_JUMP")
256          end
257         }
258
259--[[
260gameevent
261{
262    "PROCESSINPUT",
263
264    -- Input test.
265    -- NOTE: I don't think that exposing g_player[].sync (aka "input") is a good idea...
266    func = function(actori, playeri, dist)
267        local IB = player._INPUT_BITS
268        local input = player[playeri]._input
269        if (bit.band(input.bits, IB.JUMP) ~= 0) then
270            print("JUMPED")
271            -- ... because for example this doesn't work
272            -- (P_HandleSharedKeys, where the JETPACK bit is tested, runs
273            -- before P_ProcessInput):
274            input.bits = bit.bor(input.bits, IB.JETPACK)
275        end
276    end
277}
278--]]
279
280local WEAPON = gv.WEAPON
281
282-- Bad hack to test out pitch/roll: EVENT_GAME is discouraged from Lunatic.
283local PITCH_PICNUM = { [D.SEENINE]=true, }
284gameevent{ "GAME",
285    function(aci)
286        local spr = sprite[aci]
287        if (PITCH_PICNUM[spr.picnum]) then
288            local height = spr:getheightofs()
289
290            local sexy = spriteext[aci]
291            sexy.pitch = gv.totalclock
292            sexy.mdoff.x = -height/16  -- test xoff + pitch
293            sexy.mdoff.z = -height  -- test zoff + pitch
294
295            -- Test roll + yoff
296            sexy.roll = gv.totalclock
297            sexy.mdoff.y = -height/16
298        end
299    end
300}
301
302-- test event chaining
303gameevent
304{
305    "JUMP",
306
307    flags = actor.FLAGS.chain_beg,
308
309    function(actori, playeri, dist)
310        local ps = player[playeri]
311        print("\n--- I'm first!")
312--        DBG_.oom()
313        local pistol = ps.weapon.PISTOL
314        if (pistol.shoots ~= D.RPG) then
315            pistol.shoots = D.RPG
316        else
317            pistol.shoots = D.SHOTSPARK1
318        end
319        ps.weapon[WEAPON.PISTOL].firesound = D.LIGHTNING_SLAP
320
321        -- This succeeds, because sound2time is a time, not a sound.
322        ps.weapon.SHOTGUN.sound2time = 5000
323        printf("set shotgun's sound2time to %d", ps.weapon.SHOTGUN.sound2time)
324
325        -- Set pipebomb and tripbomb to timed mode.
326        -- XXX: Provide either named constants or methods?
327        -- XXX: These are probably reset to default on new game.
328        ps.pipebombControl = 2
329        ps.tripbombControl = 2
330
331        -- Test of INTERNAL member _pals.
332        -- NOTE: setting colors partially is bad! E.g. after an item is
333        -- picked up, col[0] and col[1] remain and tint everything greenish.
334        if (DBG_ ~= nil) then
335            ps._pals[2] = 20
336            ps._pals.f = 30
337        end
338    end
339}
340
341local xmath = require "xmath"
342
343gameevent
344{
345    gv.EVENT_ENTERLEVEL,
346
347    function()
348        if (DBG_ ~= nil) then
349            DBG_.testmembread()
350        end
351
352        -- NOTE: times are for helixhorned (Core2Duo 3GHz)
353        local i
354        local N = 1e6
355        local t = gv.gethiticks()
356
357        for i=3,N do
358            gv.gethiticks()
359        end
360
361        t = gv.gethiticks()-t
362
363        -- x86_64: 35ns/call, x86: 280 ns/call
364        -- Windows 32-bit: about 1 us/call?
365        printf("%d gethiticks() calls took %.03f ms (%.03f us/call)",
366               N, t, (t*1000)/N)
367
368        local sum=0
369        t = gv.gethiticks()
370        for i=1,N do sum = sum+gv.ksqrt(i) end
371        t = gv.gethiticks()-t
372        -- x86_64: 14ns/call
373        printf("%d ksqrt() calls took %.03f ms (%.03f us/call) [sum=%f]",
374               N, t, (t*1000)/N, sum)
375
376        sum=0
377        t = gv.gethiticks()
378        for i=1,N do sum = sum+math.sqrt(i) end
379        t = gv.gethiticks()-t
380        -- x86_64: 7ns/call
381        printf("%d math.sqrt() calls took %.03f ms (%.03f us/call) [sum=%f]",
382               N, t, (t*1000)/N, sum)
383
384        printf("sqrt(0xffffffff) = %f(ksqrt) %f(math.sqrt)",
385               gv.ksqrt(0xffffffff), math.sqrt(0xffffffff))
386
387        local pl = player[0]
388        -- MAX < current is "allowed"
389        pl.max_ammo_amount[WEAPON.RPG] = 17
390        pl:give_weapon(WEAPON.RPG)
391        pl.ammo_amount[WEAPON.RPG] = 54
392
393        pl:give_weapon(WEAPON.SHRINKER)
394        -- This looks much prettier:
395        pl.ammo_amount.SHRINKER = 2
396
397        -- MORTER2 from test/weaponvars.con
398        local PNUM = 1653
399        local proj = projectile[PNUM]
400        if (proj ~= nil) then
401            printf('Have projectile %d', PNUM)
402            player[0].weapon.SHOTGUN.shoots = PNUM
403            proj.drop = 0
404            proj:set_trail(D.SMALLSMOKE)
405        else
406            printf('^10Do NOT have projectile %d, test/weaponvars.con not loaded?', PNUM)
407        end
408
409        if (gv._LUNATIC_STRICT == 0) then
410            t = gv.gethiticks()
411            local N=1
412            for n=1,N do
413                for i=0,gv.MAXSPRITES-1 do
414                    sprite[i].blend = 1
415                end
416                for i=gv.MAXSPRITES-1,0,-1 do
417                    sprite[i].shade = 1
418                end
419                for i=0,gv.MAXSPRITES-1 do
420                    sprite[i].xoffset = 0
421                end
422                for i=gv.MAXSPRITES-1,0,-1 do
423                    sprite[i].yoffset = 1
424                end
425            end
426            t = gv.gethiticks()-t
427            printf("%d x four 0..MAXSPRITES-1 iterations took %.03f us per outer iteration", N, (1000*t)/N)
428            -- Results on x86:
429            -- N=1: 480-1000 us (too large variance)
430            -- N=10: 190-210 us * 10 = 1.9-2.1 ms
431            -- N=100: about 160 us * 100 = about 16 ms
432        end
433
434        -- Make the DUKECAR in E1L1 into a zombie actor (temporarily)
435        -- NOTE: Use static value (not the one from 'D').
436        if (sprite[24].picnum==2491) then
437            sprite.changestat(24, actor.STAT.ZOMBIEACTOR)
438        end
439
440        checkfail("gameevent('GAME', function() print('qwe') end)",
441                  "must be called from top level")
442
443        -- Test vec3 + wall. Pseudo wall member 'z' will be accessed.
444        local mpos = xmath.vec3()
445        for i=0,gv.numwalls-1 do
446            mpos = mpos + wall[i]
447        end
448        mpos = mpos/gv.numwalls
449        local impos = xmath.ivec3(mpos)^20  -- test ivec3 with dvec3 arg, test '^' op
450        assert(impos.z == -20)
451        printf("Map center point: (%d,%f)", mpos.x, impos.y)
452    end
453}
454
455gameevent{"LOADACTOR", function(i)
456    local spr = sprite[i]
457    if (i==614 and spr.picnum==930) then
458        -- "police line" ribbon in E1L1
459        -- clear the hitag so that it doesn't spawn as FALLER from premap
460        -- Rather a HACK: relies on an implementation detail (A_Spawn()
461        -- "hard-wired" code).
462        spr.hitag = 0
463    end
464end}
465
466gameactor
467{
468    -- "police line" ribbon
469    930, nil, 1,
470
471    func = function(i)
472        local spr = sprite[i]
473        local r = math.random
474        local d = 20
475        -- NOTE: __add metamethod is called because of the RHS:
476        local v = spr + xmath.vec3(r(-d,d), r(-d,d))
477        spr:setpos(v):updatesect()
478
479        -- Test vec3 constructor with cdata.
480        local tempvec = xmath.vec3(player[0].pos)
481    end
482}
483
484local stat = require("stat")
485local hs = stat.new()
486
487local con = require("con")
488local AC, MV = con.AC, con.MV
489
490local CAC, CMV, CAI = require("CON.ACTION"), require("CON.MOVE"), require("CON.AI")
491assert(CAC); assert(CMV); assert(CAI)
492
493local AC, MV = {}, {}
494
495AC.TROOPSTAND = assert(CAC.ATROOPSTAND) -- or con.action{0,1,5,1,1}
496AC.TROOPFLINTCH = con.action{50, 1, 1, 1, 6}
497MV.SHRUNKVELS = con.move{hvel=32}
498con.ai(AC.TROOPFLINTCH, MV.SHRUNKVELS, 0)  -- unused; TODO: test
499
500local TROOPSTRENGTH = 30
501
502local AF = actor.FLAGS
503local CS = sprite.CSTAT
504
505-- Crosshair sprite.
506-- NOTE: This ought to be a gamevar -- if a savegame is restored, a new one
507-- will be spawned.
508local chair
509
510gameactor{ D.APLAYER, AF.chain_end,
511    function(aci, pli)
512        if (chair == nil) then
513            chair = con.spawn(555, aci)
514            printf("Spawned our crosshair: sprite %d", chair)
515            local spr = sprite[chair]
516            -- Set to STAT_MISC because otherwise interpolation goes crazy (old
517            -- value never updated; dunno why...)
518            sprite.changestat(chair, actor.STAT.MISC)
519            spr.xrepeat, spr.yrepeat = 96, 96
520            spr.cstatbits:set(CS.CENTER)
521        end
522
523        local ps = player[pli]
524        local ray = xmath.kangvec(ps.ang, -(ps.horiz-100)*2048)
525
526        local hit = hitscan(ps.pos, ps.cursectnum, ray, 0)
527        if (hit.sector >= 0) then
528            sprite[chair]:setpos(hit.pos)
529            sprite.changesect(chair, hit.sector)
530        end
531    end
532}
533
534-- Add NODAMAGEPUSH flag to NEWBEAST.
535gameactor { D.NEWBEAST, AF.chain_end + AF.NODAMAGEPUSH, function() end }
536
537-- Also test actor code chaining: strength is doubled.
538gameactor
539{
540    D.LIZTROOP, AF.chain_end+AF.enemy, 2*TROOPSTRENGTH,
541
542    action = AC.TROOPSTAND,
543
544    func = function(i, playeri, dist)
545        sprite[i].pal = math.random(32)
546--        sprite[i].ang = bit.band(sprite[i].ang-20, 2047)
547
548        local spr = sprite[i]
549
550        local t = gv.gethiticks()
551        local hit = hitscan(spr, spr.sectnum, {x=10, y=10, z=0}, gv.CLIPMASK0)
552
553        hs:add(1000*(gv.gethiticks()-t))
554
555        if (hs.n == 300) then
556            printf("hitscan: %s", hs:getstatstr())
557            hs:reset()
558            error("greetings from LIZTROOP actor")
559        end
560
561        local actr = actor[i]
562        if (actr:get_count() % 30 == 0) then
563            spr.cstatbits:flip(CS.YFLIP)
564        end
565
566        -- Test of bitint's ":test()" for actor[].flags.
567        actr.flagsbits:test(AF.NVG)
568
569        if (dist < 4096) then
570            -- Duke Vader / Anakin Nukewalker?
571            actor[i]:set_action(AC.TROOPFLINTCH)
572            actor[i]:set_move(MV.SHRUNKVELS)
573
574            if (dist < 1024) then
575                con.killit()
576            end
577        end
578
579        if (actr:has_action(CAC.ATROOPWALKING)) then
580            if (actr:get_count() % 50 == 0) then
581                actr.movflagsbits:flip(actor.MOVFLAGS.spin)
582            end
583        end
584    end,
585
586    -- NOTE: the animate callback is not yet documented and thus not official API!
587    animate = function(tspr)
588        local tspr2 = tspr:dup()
589        if (tspr2) then
590            tspr2.x = tspr2.x + 512*math.cos(gv.totalclock/60)
591            tspr2.y = tspr2.y + 512*math.sin(gv.totalclock/60)
592            tspr2.cstatbits:set(CS.TRANS_BITMASK)
593        end
594
595        -- XXX: inserted tsprites have floor shadow in classic! (r_shadow)
596        -- G_DoSpriteAnimations() is passed as callback to the engine on occasion,
597        -- in other words, created tsprites may be fed back to G_DoSpriteAnimations()!
598        -- classic: shows shadow for both "ghost" liztroop and aim "reticle"
599        -- Polymost: only for "ghost"
600        -- Polymer: none
601        local aimv = 256*xmath.bangvec(tspr.ang)
602        local hit = hitscan(tspr^(16*256), tspr.sectnum, aimv, gv.CLIPMASK1)
603
604        if (hit.wall >= 0) then
605            local aimtspr = tspr:dup()
606            if (aimtspr) then
607                aimtspr.pal = 2
608                aimtspr:set_picnum(555)
609                aimtspr:setpos(hit.pos, hit.sector)
610            end
611        end
612    end,
613}
614
615gameactor
616{
617    1275,  -- newspaper, E4L6 sprite #513
618
619    action = con.action{0, 4, delay=20},  -- Same as {0, 4, 1, 1, 20}
620    move = 1,  -- so that the ID is nonzero and it will move
621
622    func = function(aci)
623        local a = actor[aci]
624        local delay = math.sin(0.1 * 2*math.pi*gv.totalclock/120)
625        a:set_action_delay(20 + 10*delay)
626        if (sprite[aci].pal ~= 0) then
627            a:set_hvel(1024/30)
628            a:set_vvel(-1024/30)
629        else
630            a:set_hvel(50*delay)
631        end
632        a.movflags = actor.MOVFLAGS.geth + actor.MOVFLAGS.getv
633    end,
634}
635
636gameevent
637{
638    "DISPLAYROOMS",
639
640    function(aci, pli)
641        local ps = player[pli]
642        local cam = gv.cam
643
644        if (ps.newowner < 0) then
645            cam.pos.z = cam.pos.z + 64*16*math.sin(gv.totalclock/30)
646        end
647
648        if (ps.cursectnum >= 0) then
649            local hit = sector[ps.cursectnum]:zrangeat(cam.pos)
650            if (gv.totalclock%200==0) then
651                printf("hit %s %d at z=%d, %s %d at z=%d",
652                       hit.c.spritep and "sprite" or "sector", hit.c.num, hit.c.z,
653                       hit.f.spritep and "sprite" or "sector", hit.f.num, hit.f.z)
654            end
655        end
656    end
657}
658
659gameevent
660{
661    "DISPLAYREST",
662
663    function()
664        for i=0,10 do
665            for j=1,100 do
666                -- XXX: This is slower in the Polymodes than with classic!
667--                con._gametext(2822, j, i, 12, 0, 0, 16, 0,0,gv.xdim,gv.ydim,8192)
668            end
669        end
670
671        -- Clear showing every sector with the pavement floor tile. (Unless we're
672        -- in such a sector or an adjoining one.)
673        -- XXX: We need a better place to do this, maybe an event in
674        -- G_DisplayRest() where show2dsector[] is tweaked?
675        -- NOT YET OFFICIAL API.
676        local show2dsector = sector.showbitmap
677        for i=0,gv.numsectors-1 do
678            if (sector[i].floorpicnum==815) then
679                show2dsector:set0(i)
680            end
681        end
682    end
683}
684
685gameactor
686{
687    D.APLAYER, actor.FLAGS.chain_beg,
688
689    function(aci, pli)
690        if (con._squished(aci, pli)) then
691            printf("Squished LunaCON test")
692        end
693    end
694}
695
696gameactor
697{
698    -- Innocent sign, similar to test/weaponvars.con actor 909 (tree trunk)
699    1211, actor.FLAGS.replace,
700
701    function(aci, pli)
702        local a = actor[aci]
703
704        if (a:get_count() >= 120) then
705            local i = con.spawn(D.TRANSPORTERSTAR, aci)
706            sprite[i].z = sprite[i].z - 1024*16
707            con.shoot(D.MORTER, aci, -4096)
708            a:set_count(0)
709        end
710    end
711}
712
713local function testbit(num, b)
714    return bit.band(num,b)~=0 and 1 or 0
715end
716
717local FADE_SPEED = {
718    [WEAPON.KNEE] = 1/2.5,
719
720    1/128,
721    1/5,
722    1/3,
723    1/2,
724    1,  -- pipebomb: ~1 sec
725    2,
726    3,
727    5,
728    127,  -- freezer; such a fast fade is not visible, but it clears any
729          -- existing one (if of higher priority)
730    [WEAPON.GROW] = 9.9,  -- test banker's rounding -- should be like 10
731}
732
733-- Test player[]:fadecol(), a better palfrom.
734gameevent
735{
736    "CHANGEWEAPON",
737
738    function (aci, pli)
739        local ps = player[pli]
740        local neww, curw = gv.RETURN, ps.curr_weapon
741
742        local r, g, b = testbit(neww, 1), testbit(neww, 2), testbit(neww, 4)
743        local speed = FADE_SPEED[neww] or 1
744        player[pli]:fadecol(0.5, r, g, b, speed, neww-5)
745    end
746}
747
748-- Time the above p:fadecol() test for verification of the <speed> argument.
749local last_f, last_t = 0, 0
750local last_secs = nil
751gameevent
752{
753    "DISPLAYREST",
754
755    function(aci, pli)
756        local ps = player[pli]
757        -- WARNING: _pals in INTERNAL and off-limits to users!
758        local curf = ps._pals.f
759        if (curf > last_f) then
760            -- Starting a tint
761            last_secs = nil
762            last_f = curf
763            last_t = gv.getticks()
764        elseif (last_t > 0 and curf==0) then
765            -- Fade has ended
766            last_secs = (gv.getticks()-last_t)/1000
767            last_f, last_t = 0, 0
768        end
769
770        if (last_secs ~= nil) then
771            con.minitext(10, 10, string.format("Last fade time: %.03f s (%.03f gametics)",
772                                               last_secs, last_secs*30))
773        end
774    end,
775}
776
777printf("EVENT_INIT = %d", gv.EVENT_INIT)  -- tests default defines
778
779local bittest = require "test.test_bitar"
780bittest.sieve()
781
782require("test.test_geom", 123123)
783require("test.test_rotspr")
784
785do
786    -- Test ksin vs. sinb
787    local xmath = require "xmath"
788    local sum = 0
789
790    local N = 1000
791    local t = gv.gethiticks()
792    for i=0,N*2048-1 do
793        sum = sum+xmath.ksin(i)
794    end
795    t = gv.gethiticks()-t
796    sum = sum*1e12
797    printf("ksin: sum*1e12=%.03f, %.03fus per 0-2047 cycle", sum, t)
798
799    sum = 0
800    local t = gv.gethiticks()
801    for i=0,N*2048-1 do
802        sum = sum+xmath.sinb(i)
803    end
804    t = gv.gethiticks()-t
805    sum = sum*1e12
806    printf("sinb: sum*1e12=%.03f, %.03fus per 0-2047 cycle", sum, t)
807
808    -- Results (helixhorned x86):
809    -- ksin: sum*1e12=0.000, 3.801us per 0-2047 cycle
810    -- sinb: sum*1e12=0.052, 2.886us per 0-2047 cycle
811end
812
813do
814    -- Test getflorzofslopeptr()/sector[]:floorzat()
815    local N = 100
816    local t = gv.gethiticks()
817
818    for n=1,N do
819        for i=0,gv.numsectors-1 do
820            local sec = sector[i]
821            local w1 = sec.wallptr
822            sec:floorzat(wall[w1])
823        end
824    end
825
826    printf("%d x %d floorzat: %.03f us", N, gv.numsectors, (gv.gethiticks()-t)*1000)
827
828    -- Results for 100 x 325 floorzat (helixhorned x86):
829    -- cdecl getflorzofslope(): 572.165 us
830    -- __fastcall getflorzofslope(): 830.147 us (sic, but tested only once!)
831end
832
833print('---=== END TEST SCRIPT ===---')
834
835--[[
836function check_sector_idx()
837    error("bla")
838end
839spritesofsect(0)
840--]]
841
842--DBG_.oom()
843
844-- This will complain about wrong usage of 'error'. In particular,
845-- the nil must not propagate to C!
846checkfail('error(nil)', "error using 'error': error message must be a string")
847
848local D = require("CON.DEFS")
849checkfail('require("CON.DEFS").APLAYER=123', "modifying base module table forbidden")
850-- Test with lunatic/test/rotfixed_actor.con.
851print("DUKECAR="..tostring(D.DUKECAR))
852
853do
854    print('---------- getangle test')
855
856    local function CreateGetAngFunc(roundfunc)
857        return function(x, y)
858            local ang = (1024/math.pi)*math.atan2(y, x)  -- note the swapped args
859            return bit.band(roundfunc(ang), 2047)
860        end
861    end
862
863    local ourgetang = CreateGetAngFunc(math.ceil)
864    local ourgetangf = CreateGetAngFunc(math.floor)
865
866    local function printang(x, y)
867        printf('%4d,%4d: %13d, %16d, %16d', x, y,
868               gv.getangle(x, y), ourgetang(x, y), ourgetangf(x, y))
869    end
870
871    print "   x,   y: getangle(x, y) | math.atan2/ceil | math.atan2/floor"
872    printang(10, 100)
873    printang(10, -100)
874    printang(-10, -100)
875    printang(-10, 100)
876
877    printang(0, 0)
878
879    printang(1, 0)
880    printang(0, 1)
881    printang(-1, 0)
882    printang(0, -1)
883
884    local N=1e5
885    local r = 0
886
887    local t = gv.gethiticks()
888    for i=1,N do
889        r = r + gv.getangle(10, i)
890    end
891    local t1 = gv.gethiticks()-t
892
893    r = 0
894    t = gv.gethiticks()
895    for i=1,N do
896        r = r + ourgetang(10, i)
897    end
898    local t2 = gv.gethiticks()-t
899
900    printf('Time for %d runs: getangle: %.03f ms, math.atan2: %.03f ms', N, t1, t2)
901    print('----------')
902end
903
904if (LUNATIC_FIRST_TIME) then
905    require("test.shadexfog").test_create0()
906end
907