// m32 script editor extensions.
// to load, enter "include samples/a" (without quotes) in the console
// or into a file named m32_autoexec.cfg for automatic loading on startup.
// to enable all events, "enableevent all"

////////// USER SETTINGS //////////

// settings for the tweaking of Polymer pr_override*-variables with the keypad keys
define MINPARALLAXSCALE -10  // minimum pr_parallaxscale value
define MAXPARALLAXSCALE 10
define MINPARALLAXBIAS -10
define MAXPARALLAXBIAS 10
define PSCALESTEPS 1000  // 0..1 is mapped to 0..PSCALESTEPS
define PBIASSTEPS 1000

define MINSPECULARPOWER -10
define MAXSPECULARPOWER 1000
define MINSPECULARFACTOR -10
define MAXSPECULARFACTOR 1000
define SPOWERSTEPS 100
define SFACTORSTEPS 100

// color of various drawing enhancements
define PREVIEW_DRAW_COLOR 11
gamevar showpal 0 0

// whether to use overridden aspect/range values when entering 3d mode (software/Polymost).
// tweak with keys 7,8,9,0 on the top row
gamevar use_custom_aspect 0 0  // this is now the same as r_usenewaspect
gamevar davr 65536 0
gamevar dayx 65536 0

// whether to remap certain widely-available keys to rare ones
// (notebook/netbook convenience, see EVENT_KEYPRESS for details)
gamevar use_notebook_keys 0 0

// Whether RESPAWN sprites show the respawned sprite picnum unconditionally instead of
// only when aimed at (and locked onto) the RESPAWN sprite in 3D mode.
gamevar showrespawn_always 0 0
// When set to 1, previewed sprites are 66% translucent instead of 33%:
gamevar showrespawn_fulltrans 0 0

// Set to basepal index to override
gamevar override_basepal -1 0

gamevar move_by_one 0 0

// Polymer "head" light
gamevar headlight_enabled 0 0
gamevar headlight_range 4000 0

// see end of file for more user settings and examples

////////// END USER SETTINGS //////////


include names.h

// flag 1: per-block (top-level, event, or state) variable
gamevar i 0 1
gamevar j 0 1
gamevar k 0 1

gamevar p 0 1
gamevar r 0 1

gamevar x 0 0
gamevar y 0 0
gamevar z 0 0

gamevar dx 0 0
gamevar dy 0 0
gamevar dz 0 0
gamevar dang 0 0

gamevar tmp 0 0
gamevar cnt 0 0

gamevar sec 0 0
gamevar wal 0 0

gamevar drawcol 9 0


define TQUOTE 0
definequote TQUOTE  >>> write on me! <<<

//light
define LIGHTQUOTE 1
//                                  x  y  z       r  g  b                mins maxs
definequote LIGHTQUOTE light  %d   %d %d %d  %d  %d %d %d   %d %d %d %d   %d %d   %d %d
//                            sec          range       radius fade ang horiz    prio tile


define PRSCALE 1000
define MAXSPECULAR 100000

define TMPARLEN 128
gamearray ar TMPARLEN
gamearray xx TMPARLEN
gamearray yy TMPARLEN

gamearray parm 8

// prints out maphack light definitions based on SE lights in map
defstate printlights
    "Print Polymer lights"
    var flags

    print "--PRLIGHTS--"
    for i allsprites, ifactor SECTOREFFECTOR, ifge .lotag 49, ifle .lotag 50
    {
        ife .lotag 50
        {
            set j 128, sub j .shade, shiftl j 1
            set k j, mul k 3, div k 4
        }
        else
        {
            set j 0, set k 0
        }

        set flags 0
        ifand .cstat 2
        {
            ifand .cstat 512
                set flags 4
            else
                set flags 2
        }
        //                                            range     r   g   b
        qsprintf TQUOTE LIGHTQUOTE .sectnum .x .y .z .hitag .xvel .yvel .zvel
                                   j k    .ang .extra .xoffset .yoffset flags .owner
        //                    radius fade      horiz  minshade maxshade prio  tile
        print TQUOTE
    }
    print "--ENDPRLIGHTS--"
ends

defstate insertlights
    "Insert active SE lights"
    var sectnum

    set k 0
    for i activelights
    {
        insertsprite light[i].sector

        // I is the inserted sprites' index now
        set .picnum 1

        set .x light[i].x
        set .y light[i].y
        set .z light[i].z

        set .hitag light[i].range
        set .xvel light[i].r
        set .yvel light[i].g
        set .zvel light[i].b

        set .extra light[i].horiz

        set .xoffset light[i].minshade
        set .yoffset light[i].maxshade

        set .owner light[i].tilenum

        set .xrepeat 48
        set .yrepeat 48

        // now those that are calculated
        ifge light[i].priority 4 or .cstat 512
        ifge light[i].priority 2 or .cstat 2

        ife light[i].radius 0
        {
            set .lotag 49
        }
        else
        {
            set .lotag 50
            set j light[i].radius, shiftr j 1, sub j 128, inv j
            clamp j -128 127
            set .shade j
        }

        add k 1
    }

    qsprintf TQUOTE "* Inserted %d SE sprites based on active lights." k
    print TQUOTE
ends

// convenient polymer SE light manipulation with keypad keys
// when aiming at light SE (49 or 50):
//  KP 4,5,6,8: angle/horiz
//  KP 7,9: range
//  KP+/-: radius
//  KP 1,2,3: RGB color (Shift inverts)
//
// when aiming at wall or sector and the respective overrides are on:
//  KP 5,8: parallaxscale
//  KP 4,6: parallaxbias
//  KP /,*: specularfactor
//  KP 7,9: specularpower
//
// Also try Shift and/or Ctrl modifiers for different increments
defstate fiddlewithlights
    var minval maxval
    var val

    // if aiming at sector or wall
    ifaimingsprite nullop
    else ifn pr_overrideparallax 0
    {
        set j 0 set k 0

        ifhitkey KEY_KP5 set j -1, ifhitkey KEY_KP8 set j 1
        ifn j 0 set k 1

        ifhitkey KEY_KP4 set j -1, ifhitkey KEY_KP6 set j 1
        ife k 0 ifn j 0 set k 2

        ifeitheralt mul j 10
        ifeitherctrl mul j 10

        switch k
        case 0: break;
        case 1:
        {
            set k pr_parallaxscale
            ftoi k PSCALESTEPS  // must convert to scaled integer, maps 0..1 to 0..PSCALESTEPS
            set minval MINPARALLAXSCALE, mul minval PSCALESTEPS
            set maxval MAXPARALLAXSCALE, mul maxval PSCALESTEPS
            add k j, clamp k minval maxval
            itof k PSCALESTEPS  // convert back
            qsprintf TQUOTE "PARALLAX SCALE: %f" k, quote TQUOTE
            set pr_parallaxscale k
            break;
        }
        case 2:
        {
            set k pr_parallaxbias
            ftoi k PBIASSTEPS
            set minval MINPARALLAXBIAS, mul minval PBIASSTEPS
            set maxval MAXPARALLAXBIAS, mul maxval PBIASSTEPS
            add k j, clamp k minval maxval
            itof k PBIASSTEPS
            qsprintf TQUOTE "PARALLAX BIAS: %f" k, quote TQUOTE
            set pr_parallaxbias k
            break;
        }
        endswitch
//quote "zxczxc"
    }
//quote "asdasd"
    // if aiming at sector or wall
    ifaimingsprite nullop
    else ifn pr_overridespecular 0
    {
        set j 0 set k 0

        ifhitkey KEY_gSLASH set j -1, ifhitkey KEY_gSTAR set j 1
        ifn j 0 set k 3

        ifhitkey KEY_KP7 set j -1, ifhitkey KEY_KP9 set j 1
        ife k 0 ifn j 0 set k 4

        ifeitheralt mul j 10
        ifeitherctrl mul j 10

        switch k
        case 0: break;
        case 3:
        {
            set k pr_specularfactor
            ftoi k SFACTORSTEPS
            set minval MINSPECULARFACTOR, mul minval SFACTORSTEPS
            set maxval MAXSPECULARFACTOR, mul maxval SFACTORSTEPS
            add k j, clamp k minval maxval
            itof k SFACTORSTEPS
            qsprintf TQUOTE "SPECULAR FACTOR: %f" k, quote TQUOTE
            set pr_specularfactor k
            break;
        }
        case 4:
        {
            set k pr_specularpower
            ftoi k SPOWERSTEPS
            set minval MINSPECULARPOWER, mul minval SPOWERSTEPS
            set maxval MAXSPECULARPOWER, mul maxval SPOWERSTEPS
            add k j, clamp k minval maxval
            itof k SPOWERSTEPS
            qsprintf TQUOTE "SPECULAR POWER: %f" k, quote TQUOTE
            set pr_specularpower k
            break;
        }
        endswitch
    }

//quote "qweqwe"
    // if aiming at a sprite that's not a Polymer light
    ifaimingsprite set k 1 else set k 0

    ife k 1 ife sprite[searchwall].picnum SECTOREFFECTOR
        ifge sprite[searchwall].lotag 49 ifle sprite[searchwall].lotag 50
            set k 0

    ife k 1
    {
        seti searchwall

        // [xyz]vel / owner
        ifeitheralt
        {
            ifhitkey KEY_KP1
            {
                getnumber256 .xvel "XVEL: " 0
                getnumber256 .yvel "YVEL: " 0
                getnumber256 .zvel "ZVEL: " 0
            }
            else ifeithershift ifhitkey KEY_KP2 // alt-kp2 collides with KP* replacement
            {
                getnumber256 .owner "OWNER: " 0
                ifl .owner 0 set .owner -1
            }
        }
    }


    // if aiming at an SE with lotag 49 or 50 (Polymer light)
    ifaimingsprite ife sprite[searchwall].picnum SECTOREFFECTOR
    ifge sprite[searchwall].lotag 49 ifle sprite[searchwall].lotag 50
    {
        set i searchwall, seti i  // set current sprite = targeted sprite

        ife .lotag 50
        {
            // KP8/KP5: horiz
            ifeithershift set j 1 else set j 10

            ifhitkey KEY_gUP nullop
            else ifhitkey KEY_gKP5 mul j -1
            else set j 0

            ifvarn j 0
            {
                // change it
                set val .extra
                add val j
                clamp val -32768 32767
                set .extra val
            }

            // KP4/KP6: angle
            set j 128
            ifeitherctrl set j 4
            ifeithershift { ifeitherctrl set j 1 else set j 32 }
            ifhitkey KEY_gLEFT sub .ang j
            else ifhitkey KEY_gRIGHT add .ang j

            // KP+/KP-: radius
            ifeitherctrl
            {
                ifholdkey KEY_gMINUS add .shade 9
                else ifholdkey KEY_gPLUS sub .shade 9

                clamp .shade -118 117
            }
        }

        // range
        ifeithershift set j 10
        else ifeitherctrl set j 1000
        else set j 100

        set k .hitag
        ifhitkey KEY_KP9 add k j
        else ifhitkey KEY_KP7 sub k j
        clamp k 0 32767
        set .hitag k

        // min/max shade
        ifeithershift set j -1 else set j 1
        ifeitherctrl
        {
            ifhitkey KEY_gSLASH
            {
                set .xoffset 0
                set .yoffset 0
                quote "Reset X and Y OFFSET (min/max shade) to 0"
            }
        }
        else
        {
            set k 0
            ifhitkey KEY_gSLASH { add .xoffset j, set k 1 }
            else ifhitkey KEY_gSTAR { add .yoffset j, set k 1 }

            ife k 1
            {
                qsprintf TQUOTE "XY OFFSET (min/max shade): %d %d" .xoffset .yoffset
                quote TQUOTE
            }
        }

        // color/picnum
        ifeitheralt
        {
            ifhitkey KEY_KP1
            {
                getnumber256 .xvel "XVEL (red): " 255
                getnumber256 .yvel "YVEL (green): " 255
                getnumber256 .zvel "ZVEL (blue): " 255
            }
            else ifhitkey KEY_KP2
            {
                getnumber256 .owner "OWNER (projection picnum): " -MAXTILES
                ifl .owner 0 set .owner -1
            }
        }
        else
        {
            ifeitherctrl set j 1 else set j 10
            ifeithershift inv j

            set k 0
            ifhitkey KEY_B { add .xvel j, set k 1 }
            ifhitkey KEY_N { add .yvel j, set k 1 }
            ifhitkey KEY_M { add .zvel j, set k 1 }

            ifhitkey KEY_KP1 { add .xvel j, set k 1 }
            ifhitkey KEY_KP2 { add .yvel j, set k 1 }
            ifhitkey KEY_KP3 { add .zvel j, set k 1 }

            ife k 1
            {
                clamp .xvel 1 255
                clamp .yvel 1 255
                clamp .zvel 1 255

                qsprintf TQUOTE "XYZ VEL (RGB color): %d %d %d" .xvel .yvel .zvel
                quote TQUOTE
            }
        }
    }
    else
    {
        // not aiming at sprite

        ifeitherctrl ifeithershift ifhitkey KEY_gMINUS
        {
            ife headlight_enabled 0
            {
                set headlight_enabled 1
                quote "head light enabled"
            }
            else
            {
                set headlight_enabled 0
                quote "head light disabled"
            }
        }
    }
ends

defstate do_move_by_one
    ifeithershift
    {
        ifhitkey KEY_UP sub horiz 1
        ifhitkey KEY_DOWN add horiz 1
        ifhitkey KEY_LEFT sub ang 1
        ifhitkey KEY_RIGHT add ang 1
        and ang 2047
    }
    else
    {
        // absolute x/y/z navigation
        ifhitkey KEY_LEFT sub posx 1
        ifhitkey KEY_RIGHT add posx 1
        ifhitkey KEY_DOWN sub posy 1
        ifhitkey KEY_UP add posy 1
        ifhitkey KEY_A sub posz 1
        ifhitkey KEY_Z add posz 1
    }
ends

defstate check_move_by_one
    ifn move_by_one 0
        set hardcoded_movement 0
    else
        set hardcoded_movement 1
ends

// forward refs
defstate userkeys_3d ends
defstate userdrawlabel ends

onevent EVENT_PREKEYS3D
//    state testkeyavail

    state check_move_by_one
    ifn move_by_one 0
        state do_move_by_one

    state fiddlewithlights
    state userkeys_3d
endevent


// rotate highlighted sprites around selected (closest to mouse) sprite
// global parameter: dang
defstate rotselspr
    ifg highlightcnt 0
    ifge pointhighlight 16384, ifl pointhighlight 32768
    {
        ife dang 0 return

        set p pointhighlight, and p 16383

        add sprite[p].ang dang

        for i selsprites, ifn i p
        {
            rotatepoint sprite[p].x sprite[p].y .x .y dang (x y)
            add .ang dang
            bsetsprite I x y .z
        }
    }
ends


gamevar do_batch_extension 0 0
gamevar batch_ohl_i -1 0
gamevar batch_tmp 0 0

////// sector collecting stuff
gamearray collectedsectors MAXSECTORS  // shared with 'old-highlighted sectors'
gamevar ohlsecs 0 0

// save or restore highlighted sectors
defstate save_restore_hlsectors
    "Save/restore hl. sectors"

    ifge highlightsectorcnt 0
    {
        // save
        for i range highlightsectorcnt
            set collectedsectors[i] highlightsector[i]
        set ohlsecs highlightsectorcnt
        quote "Highlighted sectors saved"
    }
    else ifl highlightcnt 0
    {
        for i range ohlsecs
            sethighlightsector collectedsectors[i] 1
        quote "Highlighted sectors restored"
    }
ends

defstate extendhlsectors
    "Extend all hl. sectors"

    ifvarle highlightsectorcnt 0 return

    state save_restore_hlsectors  // will save them
    for i range highlightsectorcnt
        sethighlightsector highlightsector[i] 0

    set do_batch_extension 1
    set batch_ohl_i 0
ends


onevent EVENT_PREKEYS2D
    state check_move_by_one
    ifn move_by_one 0
        state do_move_by_one

    ifvare do_batch_extension 1
        set do_batch_extension 2
    else ifvare do_batch_extension 2
    {
        ifge batch_ohl_i ohlsecs
        {
            // done!
            set do_batch_extension 0
            set batch_ohl_i -1

            set keystatus[0x1d] 0
            set keystatus[0x12] 0
        }
        else
        {
            ifvare batch_tmp 0
            {
                // highlight that sector
                sethighlightsector collectedsectors[batch_ohl_i] 1
                set batch_tmp 1
            }
            else  // ... skip a mainloop iteration so that a frame can be displayed
            {
                set batch_tmp 0
                add batch_ohl_i 1

                // fake a Ctrl-E
                set keystatus[0x1d] 1
                set keystatus[0x12] 1
            }
        }

        break
    }

//    state testkeyavail

    set j 0
    ifeitherctrl
    {
        ifhitkey KEY_COMMA set j -1
        ifhitkey KEY_PERIOD set j 1
    }

    ifn j 0
    {
        mul j 512
        set dang j
        state rotselspr
    }
endevent

gamevar fyx 0 0
gamevar fvr 0 0

defstate setas
    set fyx dayx
    mul fyx 4 mul fyx 100  // the correction factor 100/107 has been found
    div fyx 3 div fyx 107  // out experimentally. squares ftw!
    set fvr davr
    mul fvr xdim mul fvr 3
    div fvr ydim div fvr 4
    setaspect fvr fyx //davr dayx
ends

onevent EVENT_ENTER3DMODE
    ifn use_custom_aspect 0
        state setas
endevent


defstate try_nextsector_teleporting
    var nexts
    set nexts RETURN

    set RETURN 0
    for i spritesofsector nexts
    {
        ife .picnum SECTOREFFECTOR ife .lotag 7
        {
            set RETURN 1
            break
        }
    }
ends

defstate try_nextsector_ceilingsky
    ifand sector[RETURN].ceilingstat 1
        set RETURN 1
    else
        set RETURN 0
ends

defstate collect_teleporting_sectors // (sec)
    "Collect telep. sectors"
    var numsects

    set numsects numsectors, sub numsects 1
    ifinteractive
    {
        set sec searchsector
        getnumberfromuser sec "starting sectnum: " numsects 8
        ifl sec 0, return
    }

    set ohlsecs 0  // reset old hl'd sectors

    collectsectors collectedsectors sec numsects try_nextsector_teleporting
    for i range numsects
        sethighlightsector collectedsectors[i] 1
ends

// XXX: CODEDUP
defstate collect_ceilsky_sectors // (sec)
    "Collect sky ceilings"
    var numsects

    set numsects numsectors, sub numsects 1
    ifinteractive
    {
        set sec searchsector
        getnumberfromuser sec "starting sectnum: " numsects 8
        ifl sec 0, return
    }

    set ohlsecs 0  // reset old hl'd sectors

    collectsectors collectedsectors sec numsects try_nextsector_ceilingsky
    for i range numsects
        sethighlightsector collectedsectors[i] 1
ends

////// show LOCATORS
defstate cmp_by_lotag  // comparator subroutine for sorting
    set RETURN sprite[SV2].lotag
    sub RETURN sprite[SV1].lotag
ends

defstate connectlocators
    // connect LOCATORS in a sector with lines
    getarraysize ar tmp
    set j 0
    for i spritesofsector cursectnum
    {
        ifge j tmp nullop else
        ifactor LOCATORS
        {
            set ar[j] i
            add j 1
        }
    }

    set tmp j
    sort ar tmp cmp_by_lotag

    sub tmp 1
    for i range tmp
    {
        set j ar[i]
        set k i, add k 1, set k ar[k]
        drawline16z sprite[j].x sprite[j].y sprite[j].z sprite[k].x sprite[k].y sprite[k].z drawcol
    }
ends

defstate draw_prlightprojections
////////// polymer light 2d projections //////////
    var c d h r x2 y2 oldpat

    array xx 2
    array yy 2

    set oldpat drawlinepat
    set drawlinepat 0x11111111
    for i spritesofsector cursectnum
    {
        ifactor SECTOREFFECTOR
        {
            ife .lotag 49  // point light
            {
                set d .hitag  // light distance
                mul d d, mul d 2, sqrt d d, div d 2

                getclosestcol .xvel .yvel .zvel c  // light color
                inv c

                set x .x, set x2 .x, set y .y, set y2 .y
                add x d, add y d, sub x2 d, sub y2 d
                drawline16z x y .z x2 y2 .z c

                set x .x, set x2 .x, set y .y, set y2 .y
                add x d, sub y d, sub x2 d, add y2 d
                drawline16z x y .z x2 y2 .z c
                drawcircle16z .x .y .z .hitag c
            }
            else ife .lotag 50  // spot light
            {
                set d .hitag  // light distance
                and d 65535
                set r 128, sub r .shade, shiftl r 1  // light cone radius (BUILD angles)
                getclosestcol .xvel .yvel .zvel c  // light color
                inv c

                set x .x, set y .y, add x d
                rotatepoint .x .y x y .ang x y

                set h .extra  // horiz
                sub h 100
                set tmp h, mul tmp tmp, add tmp 40000
                sqrt tmp tmp
                divscale h 200 tmp 15  // h: horizontal distance fraction

                set dx .x, sub dx x
                set dy .y, sub dy y
                set tmp 32768, sub tmp h
                mulscale dx dx tmp 15
                mulscale dy dy tmp 15

                set tmp 0, sub tmp r
                rotatepoint .x .y x y tmp xx[0] yy[0]
                set tmp 0, add tmp r
                rotatepoint .x .y x y tmp xx[1] yy[1]

                add xx[0] dx, add yy[0] dy
                add xx[1] dx, add yy[1] dy

                drawline16z .x .y .z xx[0] yy[0] .z c
                drawline16z .x .y .z xx[1] yy[1] .z c
            }
        }
    }
    set drawlinepat oldpat
ends

defstate previewdoors2d
    // preview swinging and sliding doors in 2d mode
    var valid sect lo
    var i j w numw ang trange dx dy

    set valid 0
    set sect searchsector

    ifge sect 0 ifl sect numsectors
    {
        set lo sector[sect].lotag
        ife lo 23 set valid 1
        else ife lo 25 set valid 1
    }

    ife valid 1
    {
        set valid 0
        for i spritesofsector sect
            ifactor SECTOREFFECTOR
            {
                ife .lotag 11 ife lo 23 set valid 1  // swinging door
                ife .lotag 15 ife lo 25 set valid 1  // slide door
                ife valid 1 { set j i, break }
            }

        ife valid 1
            seti j
        else return

        ife .lotag 15
        {
            set trange 256
            for i spritesofsector sect
                ifactor GPSPEED { set trange .lotag, break }
            mul trange 2  // now equals distance of sliding door to travel
        }

        set i 0
        for w loopofwall sector[sect].wallptr
        {
            ifge i TMPARLEN break
            set xx[i] wall[w].x
            set yy[i] wall[w].y
            add i 1
        }
        ifl i TMPARLEN
        {
            set xx[i] xx[0]
            set yy[i] yy[0]
            add i 1
        }
        set numw i

        ife .lotag 11
        {
            ifg .ang 1024 set ang -512 else set ang 512
            for i range numw
                rotatepoint .x .y xx[i] yy[i] ang xx[i] yy[i]
        }
        else  // if .lotag 15
        {
            set ang .ang, add ang 1024
            a2xy ang dx dy
            mulscale dx trange dx 14
            mulscale dy trange dy 14

            for i range numw
            {
                add xx[i] dx
                add yy[i] dy
            }
        }

        set drawlinepat 0x33333333
        sub numw 1
        for i range numw
        {
            set j i, add j 1
            drawline16z xx[i] yy[i] sector[sect].floorz xx[j] yy[j] sector[sect].floorz PREVIEW_DRAW_COLOR
        }
        set drawlinepat 0xffffffff
    }
ends


// LOCATORS auto-incrementer
onevent EVENT_INSERTSPRITE2D
    set k I
    set j -1
    for i spritesofsector .sectnum
    {
        ifn i k, ifactor LOCATORS, ifg .lotag j
            set j .lotag
    }
    ifg j -1
    {
        add j 1
        set .lotag j
    }
endevent

defstate print_parallel_midpts  // print the midpoints of parallel wall-lines
    var p0 p1 p2 s1dx s1dy s2dx s2dy tmp nw

    quote "----------------"
    for p1 allwalls
    {
        lastwall p1 p0  // get the 'inverse point2'
        set p2 wall[p1].point2  // ... and the forward one

        set tmp 0

        // both white: OK  (may be false positive due to TROR)
        ifl wall[p0].nextwall 0 ifl wall[p1].nextwall 0 set tmp 1

        // both red and no wall originating from midpoint on the other side: OK
        ifge wall[p0].nextwall 0 ifge wall[p1].nextwall 0
        {
            set nw wall[p1].nextwall
            ifl p1 nw, ife wall[p0].nextwall wall[nw].point2, set tmp 1
        }

        ife tmp 1
        {
            set s1dx wall[p1].x, sub s1dx wall[p0].x
            set s1dy wall[p1].y, sub s1dy wall[p0].y

            set s2dx wall[p2].x, sub s2dx wall[p1].x
            set s2dy wall[p2].y, sub s2dy wall[p1].y

            // now have segment dx/dy's

            mul s1dx -s2dy
            mul s1dy s2dx
            set tmp s1dx, add tmp s1dy  // tmp = (s1dx, s1dy) . (-s2dy, s2dx)

            ife tmp 0
            {
                qsprintf TQUOTE "%d" p1
                quote TQUOTE
            }
        }
    }
ends


//////////////////// SPRITE DUPLICATORS ////////////////////

defstate transcnt
    ifle cnt 0 { inv cnt, add cnt 128 }
ends

define DUP_ROT_MAGIC 123

// duplicates and rotates selected sprites around
// pivot sprite with fields
//  .extra=123 (magic)
//  .ang: angle delta
//  .yrepeat*32: z delta (positive if pal!=0, i.e. going down)
//  .shade: count (-128 to 0 -> 255 to 128)
defstate duprot
    ifaimingsprite nullop else return
    ifn sprite[searchwall].extra DUP_ROT_MAGIC return

    set p searchwall  // pivot sprite
    set cnt sprite[p].shade, state transcnt

    set sprite[p].extra -1

    for i range cnt
    {
        for j selsprites, ifn j p
        {
            dupsprite j  // duplicate sprite j, I becomes index of newly created sprite

            set dang i, add dang 1, mul dang sprite[p].ang
            rotatepoint sprite[p].x sprite[p].y .x .y dang (x y)
            add .ang dang

            set z i, add z 1, mul z sprite[p].yrepeat, shiftl z 5
            ife sprite[p].pal 0, inv z
            add z .z

            bsetsprite I x y z
        }
    }
ends

// same as above but with tsprite[], as a kind of preview
defstate tduprot
    ifaimingsprite nullop else return
    ifl searchwall 0 return
    ifn sprite[searchwall].extra DUP_ROT_MAGIC return

    set p searchwall  // pivot sprite
    set cnt sprite[p].shade, state transcnt

    for i range cnt
    {
        for j selsprites, ifn j p
        {
            set k spritesortcnt
            tdupsprite j

            set dang i, add dang 1, mul dang sprite[p].ang
            rotatepoint sprite[p].x sprite[p].y tsprite[k].x tsprite[k].y dang (x y)
            add tsprite[k].ang dang

            set z i, add z 1, mul z sprite[p].yrepeat, shiftl z 5
            ife sprite[p].pal 0 inv z
            add z tsprite[k].z

            set tsprite[k].x x
            set tsprite[k].y y
            set tsprite[k].z z

            or tsprite[k].cstat 514
        }
    }
ends

define DUP_LIN_MAGIC 234
define DUP_LIN_MAGIC2 345

// duplicates and translates selected sprites in the direction between two
// reference sprites with fields
//  .extra=234 (1st sprite), =345 (2nd, aimed at sprite)
//  .shade: count (-128 to 0 -> 255 to 128)
defstate duplin
    ifaimingsprite nullop else return
    ifn sprite[searchwall].extra DUP_LIN_MAGIC2 return

    set r searchwall  // 2nd reference point

    set cnt sprite[r].shade, state transcnt

    set p -1  // 1st reference point
    for i selsprites, ifn i r
    {
        ife .extra DUP_LIN_MAGIC { set p i, break }
    }
    ifl p 0 return

    set sprite[p].extra -1
    set sprite[r].extra -1

    set dx sprite[r].x, sub dx sprite[p].x
    set dy sprite[r].y, sub dy sprite[p].y
    set dz sprite[r].z, sub dz sprite[p].z

    for i range cnt
    {
        for j selsprites, ifn j r
        {
            dupsprite j

            set x i, add x 1, mul x dx, add x .x
            set y i, add y 1, mul y dy, add y .y
            set z i, add z 1, mul z dz, add z .z
            bsetsprite I x y z
        }
    }
ends

defstate tduplin
    ifaimingsprite nullop else return
    ifn sprite[searchwall].extra DUP_LIN_MAGIC2 return

    set r searchwall  // 2nd reference point

    set cnt sprite[r].shade, state transcnt

    set p -1  // 1st reference point
    for i selsprites, ifn i r
    {
        ife .extra DUP_LIN_MAGIC { set p i, break }
    }
    ifl p 0 return

    set dx sprite[r].x, sub dx sprite[p].x
    set dy sprite[r].y, sub dy sprite[p].y
    set dz sprite[r].z, sub dz sprite[p].z

    for i range cnt
    {
        for j selsprites, ifn j r
        {
            set k spritesortcnt
            tdupsprite j

            set tmp i, add tmp 1, mul tmp dx
            add tsprite[k].x tmp
            set tmp i, add tmp 1, mul tmp dy
            add tsprite[k].y tmp
            set tmp i, add tmp 1, mul tmp dz
            add tsprite[k].z tmp

            or tsprite[k].cstat 514
//            bsetsprite I x y z
        }
    }
ends

defstate respawnpreview
    var ts ow pic z
    var k onumtspr

    ife showrespawn_always 0
    {
        ifaimingsprite ife sprite[searchwall].picnum RESPAWN nullop
        else return
    }

    set onumtspr spritesortcnt

    for ts range onumtspr
    {
        set ow tsprite[ts].owner
        ife showrespawn_always 0 ifn ow searchwall nullop else
        {
            ife sprite[ow].picnum RESPAWN
            {
                set k spritesortcnt
                tdupsprite ow

                // XXX: doesn't work with E1L5 boss RESPAWN...
                // Something's wrong with setting tsprite[] members.
                sub tsprite[k].z 4096

                set tsprite[k].xrepeat 16
                set tsprite[k].yrepeat 16

                set pic sprite[ow].hitag
                ifge pic 0 ifl pic MAXTILES
                    set tsprite[k].picnum pic

                and tsprite[k].cstat 0xfffffdff  // clear bit 512 (reverse translucency)
                or tsprite[k].cstat 2  // set bit 2 (33% translucency)
                ifvarn showrespawn_fulltrans 0
                    or tsprite[k].cstat 512
            }
        }
    }
ends

onevent EVENT_ANALYZESPRITES
    state respawnpreview
    state tduprot
    state tduplin
endevent

defstate update_headlight
    var li hrz
    set li 0

    ifvare headlight_enabled 0
        break

    ifvarl cursectnum 0
        break

    ifvarn light[li].active 1
        break

    set light[li].x posx
    set light[li].y posy
    set light[li].z posz
    set light[li].sector cursectnum
    set hrz horiz
    set light[li].horiz hrz
    set light[li].angle ang

    set light[li].range headlight_range
    set light[li].radius 256
    set light[li].faderadius 128

    set light[li].r 255
    set light[li].g 255
    set light[li].b 255
    set light[li].priority 0

    set light[li].tilenum 0
ends

onevent EVENT_PREDRAW3DSCREEN
    state update_headlight
endevent

onevent EVENT_KEYS3D
    var l m

    ifvarge override_basepal 0
        setgamepalette override_basepal

    // door sound tester
    ifeitherctrl ifeithershift ifaimingwall
    ifholdkey KEY_SPACE
    {
        set k wall[searchwall].nextsector
        ifl k 0 set k searchsector

        ife sector[k].lotag 0 return

        for i spritesofsector k
        {
            ifactor MUSICANDSFX
            ifge .lotag 0 ifl .lotag MAXSOUNDS
            {
                getsoundflags .lotag m
                ifand m 1 nullop else soundonce .lotag
                resetkey KEY_SPACE
            }
        }
    }

    // swinging doors tester -- hit Ctrl-Shift-SPACE on a door wall
    ifeitherctrl ifeithershift ifaimingwall
    ifholdkey KEY_SPACE  // SE11 ST23 up:ccw
    {
        set k wall[searchwall].nextsector
        ifl k 0 return
        ifn sector[k].lotag 23 return

        resetkey KEY_SPACE

        set tmp 0
        for i loopofwall searchwall
        {
            ifl wall[i].nextsector 0 set tmp 1 else
            ifn wall[i].nextsector k set tmp 1
        }
// a weaker condition
//        for i loopofwall wall[searchwall].nextwall
//        {
//            ifl wall[i].nextsector 0 set tmp 1 else
//            ifn wall[i].nextsector searchsector set tmp 1
//        }
        ifn tmp 0
        {
            quote "door sector not an island sector!"
            return
        }

        set l -1
        for i spritesofsector k
        {
            ifactor SECTOREFFECTOR ife sprite[i].lotag 11
            {
                set l i
                ifn sprite[i].ang 512 ifn sprite[i].ang 1024 ifn sprite[i].ang 1536 set l -1
            }
        }
        ifl l 0
        {
            quote "door sector has no SE sprite!"
            return
        }

        for tmp wallsofsector k
        {
            rotatepoint (sprite[l].x sprite[l].y) (wall[tmp].x wall[tmp].y) sprite[l].ang (i j)
            dragpoint tmp i j
        }
        for tmp spritesofsector k
        {
            ifn tmp l
            {
                rotatepoint (sprite[l].x sprite[l].y) (sprite[tmp].x sprite[tmp].y) sprite[l].ang (i j)
                bsetsprite tmp i j sprite[tmp].z
            }
        }
        inv sprite[l].ang
    }

    // teleporter -- works on SE7 and SE17 (elevator)
    ifeitheralt ifaimingsprite
    ifholdkey KEY_SPACE
    {
        ife sprite[searchwall].picnum SECTOREFFECTOR
        {
            set tmp 0
            ife sprite[searchwall].lotag 7 set tmp 1
            ife sprite[searchwall].lotag 17 set tmp 1
            ife tmp 0 return

            resetkey KEY_SPACE

            for i allsprites
            {
                ifn i searchwall, ifactor SECTOREFFECTOR, ife sprite[i].lotag sprite[searchwall].lotag
                ife sprite[i].hitag sprite[searchwall].hitag
                {
                    add posx sprite[i].x, sub posx sprite[searchwall].x
                    add posy sprite[i].y, sub posy sprite[searchwall].y
                    add posz sprite[i].z, sub posz sprite[searchwall].z

                    updatecursectnum

                    return
                }
            }
        }
        else ife sprite[searchwall].extra DUP_ROT_MAGIC
        {
            state duprot
            resetkey KEY_SPACE
        }
        else ife sprite[searchwall].extra DUP_LIN_MAGIC2
        {
            state duplin
            resetkey KEY_SPACE
        }
    }


    set j 0

    set k 0
    ifholdkey KEY_7 set k -1
    ifholdkey KEY_0 set k 1
    ifn k 0
    {
        set j 1
        ifeithershift nullop else mul k 256
        add davr k
        ifl davr 32768 set davr 32768
        ifg davr 256000 set davr 256000
    }

    set k 0
    ifholdkey KEY_8 set k -1
    ifholdkey KEY_9 set k 1
    ifn k 0
    {
        set j 1
        ifeithershift nullop else mul k 256
        add dayx k
        ifl dayx 32768 set dayx 32768
        ifg dayx 256000 set dayx 256000
    }
    ife j 1
    {
//        setaspect davr dayx
        state setas
        qsprintf TQUOTE "ASPECT: davr=%d, dayx=%d | FVR=%d, FYX=%d" davr dayx fvr fyx
        quote TQUOTE
    }
endevent

defstate replacestuff
    for i spritesofsector searchsector
//        ife sprite[i].picnum AMMO set sprite[i].picnum BATTERYAMMO
        ifactor parm[0] cactor parm[1]
ends

defstate convlights  // convert (0,0,0) lights to (255,255,255)-ones
	for i allsprites ifactor 1 ifge .lotag 49 ifle .lotag 50 ife .xvel 0 ife .yvel 0 ife .zvel 0
        { set .xvel 255 set .yvel 255 set .zvel 255 }
ends

defstate resetallws  // reset all sprites and walls to default repeat/panning
    for i allsprites
	{
		set .xrepeat 64
        set .yrepeat 64
	}
    for i allwalls
	{
		set wall[i].cstat 0
		set wall[i].xpanning 0
		set wall[i].ypanning 0
		set wall[i].yrepeat 8
		fixrepeats i
	}
ends

defstate js  // jump to current sprite
    set posx .x
    set posy .y
    set posz .z
    updatecursectnum
ends

defstate jumptowal  // (wal)
    ifge wal 0 ifl wal numwalls nullop else return
    set posx wall[wal].x
    set posy wall[wal].y
    updatecursectnum
ends

defstate jumptosec  // (sec)
    ifge sec 0 ifl sec numsectors nullop else return
    set wal sector[sec].wallptr
    state jumptowal
ends


onevent EVENT_DRAW2DSCREEN
    var tmp tmp2
    var xx xx2 yy

    state userdrawlabel

    ifge cursectnum 0
    {
        state connectlocators
        state draw_prlightprojections
    }

    state previewdoors2d

    ifn showpal 0
    {
        set xx 100
        for tmp range 256
        {
            set tmp2 tmp
            mod tmp2 64
            ife tmp2 32
            {
                drawline16 xx 95 xx 99 15
                qsprintf TQUOTE "%d" tmp
                printext16 TQUOTE xx 80 23 0 0
            }
            else ife tmp2 0
            {
                drawline16 xx 90 xx 99 15
                qsprintf TQUOTE "%d" tmp
                printext16 TQUOTE xx 80 15 0 0
            }

            drawline16 xx 100 xx 200 -tmp
            add xx 1
            drawline16 xx 100 xx 200 -tmp
            add xx 1
            ifge tmp 240
            {
                drawline16 xx 100 xx 200 -tmp
                add xx 1
                drawline16 xx 100 xx 200 -tmp
                add xx 1
            }
        }

        set xx 100, set xx2 260
        set yy 208
        for tmp range 256
        {
            qsprintf TQUOTE "editor color %d" tmp
            printext16 TQUOTE xx yy tmp 0 0
            printext16 TQUOTE xx2 yy 0 tmp 0

            add yy 8

            set tmp2 tmp
            mod tmp2 64

            ife tmp2 63
            {
                set yy 208
                add xx 320
                add xx2 320
            }
        }
    }
endevent


defstate mkterrain
    var w2 w3 idx bit tmp sec
    var warned

    set warned 0

    for i selwalls
    {
        sectorofwall j i
        set tmp 0, ifand sector[j].floorstat 2, set tmp 1  // already handled
        ife tmp 0 ife sector[j].wallnum 3
        {
            set w2 wall[i].point2
            set idx w2, shiftr idx 3
            set tmp w2, and tmp 7, set bit 1, shiftl bit tmp
            ifand show2dwall[idx] bit
            {
                setfirstwall j i

                set z 0x7ffffff
                ifin3dmode
                {
                    ife searchstat 2  // floor
                        set z sector[searchsector].floorz
                }
                else
                {
                    for k allsectors
                    {
                        ifinside mousxplc mousyplc k
                        {
                            set z sector[k].floorz
                            break
                        }
                    }
                }

                ife z 0x7ffffff
                {
                    ife warned 0
                    {
                        quote "Mouse pointer must be aiming at sector floor."
                        set warned 1
                    }
                }
                else
                {
                    set w3 wall[w2].point2
                    set sec wall[i].nextsector
                    ifge sec 0
                        set sector[j].floorz sector[sec].floorz
                    alignflorslope j wall[w3].x wall[w3].y z
                }
            }
        }
    }
ends

defstate chselshade
    for i selsectors
    {
        set sector[i].floorshade tempshade
        set sector[i].ceilingshade tempshade

        for j spritesofsector i
            set .shade tempshade

        for j wallsofsector i
            set wall[j].shade tempshade
    }
ends

defstate listusedtags
    for i allsprites
    {
        getspritelinktype i k

        ifn k 0
        {
            ife k 1
                qsprintf TQUOTE "sprite %d lotag %d" i sprite[i].lotag
            else ife k 2
                qsprintf TQUOTE "sprite %d hitag %d" i sprite[i].hitag
            quote TQUOTE
        }
    }
ends

defstate moveselsects
    var p1 dx dy

    for i selsectors
    {
        set p1 sector[i].wallptr
        set dx wall[p1].x, set dy wall[p1].y
        for j wallsofsector i
        {
            sub wall[j].x dx
            sub wall[j].y dy
        }
        for j spritesofsector i
        {
            sub .x dx
            sub .y dy
        }
    }
ends

defstate sanitize_underwater_sprites
    "Xvel=1 for wall/flr spr."
    for i selsprites
    {
        set j .cstat
        and j 49
        ife j 17 set .xvel 1
        ife j 33 set .xvel 1
    }
    printmessage16 "Set xvel=1 on highlighted wall/floor sprites"
ends

define BZ_MAX 8388608

defstate uniformceil
    var avgz n

    for i selsectors
    {
        ifand sector[i].ceilingstat 2 xor sector[i].ceilingstat 2
        add avgz sector[i].ceilingz
        add n 1
    }

    ife n 0 break

    div avgz n
    and avgz 0xfffffc00  // round to one PGUP/PNDN unit (1024 z units)

    ifvarge avgz -BZ_MAX ifvarle avgz BZ_MAX nullop
    else quote "ERROR: average z value outside [-8388608 .. 8388608]"  // BZ_MAX

    for i selsectors
        set sector[i].ceilingz avgz
ends

// XXX: CODEDUP
defstate uniformfloor
    var avgz n

    for i selsectors
    {
        ifand sector[i].floorstat 2 xor sector[i].floorstat 2
        add avgz sector[i].floorz
        add n 1
    }

    ife n 0 break

    div avgz n
    and avgz 0xfffffc00  // round to one PGUP/PNDN unit (1024 z units)

    ifvarge avgz -BZ_MAX ifvarle avgz BZ_MAX nullop
    else quote "ERROR: average z value outside [-8388608 .. 8388608]"  // BZ_MAX

    for i selsectors
        set sector[i].floorz avgz
ends

defstate for_sprites_near_picnum
    "Ch. lotag for near spr."

    var ii dst
    var picnumi picnumj maxldist lotag

    getnumberfromuser picnumi "picnum i: " MAXTILES 10
    ifvarl picnumi 0 break
    getnumberfromuser picnumj "picnum j: " MAXTILES 10
    ifvarl picnumj 0 break

    getnumberfromuser maxldist "max ldist: " 65536 8
    ifvarl maxldist 0 break

    getnumberfromuser lotag "lotag (unsigned) to change to: " 65536 8
    ifvarl lotag 0 break

    for i allsprites ife sprite[i].picnum picnumi
        for j allsprites ife sprite[j].picnum picnumj
        {
            ldist dst i j
            ifle dst maxldist
                set sprite[j].lotag lotag
        }

    qsprintf TQUOTE "changed lotag of sprites w/ tile %d near those w/ tile %d to %d" picnumj picnumi lotag
    quote TQUOTE
ends

// This must be *compiled* with script_expertmode
/*
defstate setstartpos
    "Set start pos. from spr."
    var spr
    getnumberfromuser spr "sprite number: " MAXSPRITES 8

    seti spr
    set startposx .x
    set startposy .y
    set startposz .z
    set startsector .sectnum
    set startang .ang
ends
*/

////////// USER AREA //////////

// key settings
defstate userkeys_3d
/*
    ifge searchwall 0 ifl searchwall MAXWALLS
    ifholdkey KEY_KP5
    {
        add wall[searchwall].ypanning 1
        set DOSCRSHOT 1
    }
*/
    ifholdkey KEY_SEMI ifhitkey KEY_C state chselshade
ends

gamevar scrshot 0 1

////////// DRAW TILE (ROTATESPRITE) TESTING //////////
gamevar dt_t -1 0  // tilenum
gamevar dt_x 160 0 // x
gamevar dt_y 100 0 // y
gamevar dt_z 65536 0  // zoom
gamevar dt_a 0 0  // angle
gamevar dt_s 0 0  // shade
gamevar dt_p 0 0  // pal
gamevar dt_o 0 0  // orientation
gamevar dt_A 0 0  // alpha

onevent EVENT_DRAW3DSCREEN
    ifge dt_t 0
        rotatespritea dt_x dt_y dt_z dt_a dt_t dt_s dt_p dt_o dt_A 0 0 xdim ydim

    ifn move_by_one 0
    {
        qsprintf TQUOTE "x,y,z = %d, %d, %d" posx posy posz
        printext256 TQUOTE 30 30 -15 0 0

        qsprintf TQUOTE "horiz,ang = %d, %d" horiz ang
        printext256 TQUOTE 30 40 -15 0 0
    }

/*
    ifge searchwall 0, ifl searchwall MAXWALLS
    {
        // Which wall is considered targeted?
        qsprintf TQUOTE "searchwall = %d" searchwall
        printext256 TQUOTE 30 30 -15 0 0

        qsprintf TQUOTE "y panning = %d" wall[searchwall].ypanning
        printext256 TQUOTE 30 40 -15 0 0
    }
*/
endevent

// convenience rebindings for notebooks:
//  Alt-F11 --> SCROLL LOCK (set first position)
//  Alt-arrows --> KP arrows (change pan/repeat in 3D mode)
onevent EVENT_KEYPRESS
    var thekey
    set thekey RETURN

    ifvare use_notebook_keys 0, return
/*
    ifholdkey thekey
        qsprintf TQUOTE "pressed %d" thekey
    else
        qsprintf TQUOTE "released %d" thekey
    print TQUOTE
*/
    ifholdkey thekey  // if the callback key was pressed (and not released)
    {
        ifholdkey KEY_LALT
        {
            ifhitkey KEY_F11, setkey KEY_SCROLL

            ifhitkey KEY_UP, setkey KEY_KP8
            ifhitkey KEY_DOWN, setkey KEY_KP2
            ifhitkey KEY_LEFT, setkey KEY_KP4
            ifhitkey KEY_RIGHT, setkey KEY_KP6

// would be 'cleaner' (only checking cb key) but is too much code for my taste:
/*
            switch (thekey)
            {
                case KEY_F11: setkey KEY_SCROLL; resetkey thekey; break;

                // and so on with the rest...
            }
            endswitch
*/
        }
    }
    else  // cb key was released
    {
        switch (thekey)
        {
            case KEY_LALT:
            {
                resetkey KEY_KP8;
                resetkey KEY_KP2;
                resetkey KEY_KP4;
                resetkey KEY_KP6;
                break;
            }

            case KEY_UP: resetkey KEY_KP8; break;
            case KEY_DOWN: resetkey KEY_KP2; break;
            case KEY_LEFT: resetkey KEY_KP4; break;
            case KEY_RIGHT: resetkey KEY_KP6; break;
        }
        endswitch
    }
endevent

/*
// example for custom labels
defstate userdrawlabel
    for i allsprites
    {
        ifactor 2978
        {
            qsprintf TQUOTE "MOVABLE EX:%d OW:%d", sprite[i].owner, sprite[i].extra
            drawlabel TQUOTE .x .y .z 0 31
        }
    }
ends
*/