0 ,,,qmi@8(i8U‰U 02TvͫgE#2Tv2Tv#0!#0!#111!#""""1111""""2222111""""""23""""-""1111""0!#0!#0!#0!#2222!#!#!#!#!""2222!#!"!-!-!"""20!#000!#22!)!)!""""#111!#0!#0!#""""1111""""2111222 ""03#."" ""0-.""""1133QUQQQUU!!1!1!!!""""233#""""001111111::::111!!!!!!"!#!#!#!#!#!#!""222222"0000::::!ffrm'qawawgvqq!'r!gvqWva}aeVqvgww""""233#""""  111001111:111:::" " """""""""""""""""" """"""""""""""""""" " """ """"" "    """"""""""""ݺ݈؍݈ۍ諺"""""""""""""""""""""FFFffFDDfffffffDDDDffff ꪪFFFFFFFFꪩꪪ """"""""""""""""""" """"ݛؽ눈ݎ؍؈؍UUUUgfffgDDdgfFfgfdfgFffgDDdgfffꪩꪩ3333!"""!"!/"/!/!/"/!/"/!"""3333!"""!/!/""!"!""/!"!"""3333!"""!"/"!"!///!"/"!"/"!"""3333!"""!"/"!"/"!///!"!"/"!"""3333!"""!"/"!""!/!""!"/"!""" 3#33"#""#""#""#""#""#""#"3333""""""""""""""""""""""""""""3333"""""""""""""""""/"/""3333"!""!"!!"!""!"""""3333!!""""!!!!!!!!""""3333!"""!"/"!""!/!""!"/"!""" "#""#""#""#""#""#""#""""""""""""""""""""""""""""""/""/""""""""""""""""""-"""""""""""""""""躰+髙ꩩ 誙陙誺蹮逈(((aaaaaa+     騺陙̘ 933333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333033333333333333333333333333330003333333333333333333333333333333333333333333333333333333303  lffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff DD@DDDDDDDD@DDDDDDDDDDDD@@DDD@DD@DD@DDDDDDDDDD @DDDDDD@D@DDDDDDDDDDDDDDDDDDD@DD@DD@DD@DD@DD@DDDDDDDDDDDDDDD@   wwwpwwwwwwwwpwwwpwwwwwwwwwwwwwwwwwwww pwwwpwwwwwwwwwpwwwwwwwwwwwwwwwwwwwwwwbLLL8888888888LLLG33ILLL888888888G   @IC0CLLLGIGI3888LLLGQ3@IGYXXXWY3 D 373IG897988888888888888888887I8879#!#!3TIG3 CIG33 IQICPCTC#WT988G IG TTTTTI V@I""""""""G3 3IG3 I888888GIQI8888888888888888888888XXWTIXXWYXXXWIGT@IGYXXXXXXXXXXXXXXXXXG@@@I@TT7TIGQ3IG IGC CTIPGIGR @@@I3Q33G IGTIGIG33I@G33IG YXXXXXXXXXXXXXXW@@@I G@IXXXXXXXXXG3V397IWYGIXXX8888888IXXXGI3Q#!33GYXGVI5 GIIG33IGI@0GIGYWI33GIGCCI7I""""!""""""G3I GIG33IG988798887I"+X8798888888887IGCC0IG0@00@0IG IG0@IGYXXWYXXXWIG IXW(YXXGIGCCIGIGI TTT33GIG33IGIG33YXXXXXX """"!"#"""GI888XXXXXXX8888GIGI G@@@9888888888888887IGI33 G@@@IG IP G@@@IG33I33#""""XXXXXXXXXXXXXXXXXXXXXXXG988"""G SIGYXXXWUYXXXXXX879G@@IG@@AIG@@IXXXXxyxyvwtuvwtuvwtuvwtuvwtuvwtuvwtuvw././././>?>?>?>?tuvwtuvwtuvwtuvwtuvwtuvwtuvwtu {@@EJLNNOOOOOOOOOOOOOOOOOOOOOOO p      0000@P`ppppp           s 7643211000123 5 6789 : ; <=>???????02 3 4 5 55667889:::;;;<<====>>>>>00001358=??????? ? 00000@0@?P?`?p?p0p0p0p0p?p3024 6 89;=>????????????????????saaabcfhijkllmnnnoooooooooooooo   013579:;<>????????????????????: 515;==>>?????????????????????+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@!!A!!A!!A!!!!!!A!!A!!A!!A!!!!!A!!A!!A!!!!!!A!!A!!A!! aaa a a a aaAAa AA aA A a A Aa A Aa a A A a AAaA Aa AAa A A a A A a A AaAAaAAaaA Aa AA a A Aaaaaa Aaa````````````````````````````````````````````````````````` `` `` `` `""""""""""""""" B`````````````B"B" B"B"B"" "" """" "" """B"B" B"B"B" "" """" "" ""                       B B BBBBB B BB B B B B BB !! ! ! ! ! !!!!!!    ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !a aaaAa aaA Aa a a aa a aaa aa a a aa a a a aaa a a a a aa aaaa='D](DL(D='D](DL(D"B "B BB "B "B BB4'dT(dO(D4'dT(dO(DBBB B B BBBB B B BF8ˁ0K4ˁ0K4ˁ0K4ˁ0K4 E#MEEE#ME%ME%ME%M0uu \\\\GG`? 뾚 뾚             뾚  뾚        虉 躈隊 뾚誙뾚髙詙(ꩩ( 뫻 +(+ "" " "  " "  """ """"" """ " """ """ " """ """   "" "" """ """""""" " " "  " "" """""""""" """ "" """""" """" """"" " "" """111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"223"22322332233"223"22322332233"223"22322332233"223"22322332233"223"22322332233"223"22322332233"223"22322332233"223"22322332233323332332233223332333233223322333233323322332233323332332233223332333233223322333233323322332233323332332233223332333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333233223322333233323322332233323332332233223332333233223322333233323322332233323332332233223332333233223322333233323322332233"223"22322332233"223"22322332233"2"22322332233"22?23?332233"22"22322332233"223"22322332233"223"22322332233"222"222""22""22"222"222""22""22"222"222""2/22?/"22"22?2222""22"222""22""22"222"222""22""22"222"222""22""22"""2"""2""22""22"""2"""2""22""?""2/"??/"?"""/""2/2"/22"/"""2"2"""2"""2""22""22"""2"""2""22""22"""""""""""""""""""""""""""""""""""""""""""""/""/"/""""""/""/"""/"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""/"""/"""""""""""""""""""""""""""""""""""""""""""""""""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!""""""!"""""""/""""""!"""""""!"""""""!"""""""!"""""""!"!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1""1""1""1""!3""!3""!3""!3""""""!!#!!!#!!!#!!!#!"31""31""31""31""#1""#1""#1""#1""#1""#1""#1""#1""#13"#13"#13"#13!#!#!#!#"""#"""#"""#"""#2#"#2#"#2#"#2#"#3"#3"#3"#3"#####!"#!"#!"#!"#""3#""3#""3#""3#"!"#"!"#"!"#"!"#"!"3"!"3"!"3"!"3"!"#"!"#"!"#"!"#21#21#21#21# 3"!# 3"!# 3"!# 3"!#2222!2"#2"#2"#2"#!2"#2"#2"#2"#2#2#2#2#!"2!"2!"2!"2""""!!3""!3""!3""!3""!!1""1""1""1""!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!!"!""!!!""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""!"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""2""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"""2""22""22"""2"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""22""22"222"222""232233"223"22322332233"223"22322332233"223"22322332233"223"22322332233"223"22322332233"223"22322332233"223"22322332233"22332332233223332333233223322333233323322332233323332332233223332333233223322333233323322332233323332332233223332333233223333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 -- title: DooM80part2but1color_fanMade -- engine: Bruno Oliveira -- doom: id Software -- author: AshtheRam -- desc: FPS80 made into Doom. -- script: lua -- saveid: dm802but1col --GENERATED CODE. DO NOT EDIT. --how bout i do anyway --Edit the source .lua files instead. --nah fam local sin,cos,PI=math.sin,math.cos,math.pi local floor,ceil,sqrt=math.floor,math.ceil,math.sqrt local min,max,abs,HUGE=math.min,math.max,math.abs,math.huge local random=math.random function clamp(x,lo,hi) return max(min(x,hi),lo) end local SCRW=240 local SCRH=136 local TID={ STONE=116, -- stone wall WOOD=256, -- wood wall DOOR=112, -- door LDOOR=264, -- locked door CYC_W1=320, -- cyclops walk 1 CYC_ATK=324, -- cyclops attack CYC_W2=384, -- cyclops walk 2 CYC_PRE=448, -- cyclops prepare CBOW_N=268, -- crossbow neutral CBOW_D=270, -- crossbow drawn CBOW_E=300, -- crossbow empty ARROW=412, -- arrow flying STIM=457, -- stimpack AMMO=456, -- ammo (shells) DEMON_1=388, -- flying demon, flying 1 DEMON_2=390, -- flying demon, flying 2 DEMON_PRE=420, -- flying demon, prepare DEMON_ATK=422, -- flying demon, attack KEY_1=458, -- key (on floor, to pick up) KEY_2=459, SPITTER_1=452, -- fire spitting demon SPITTER_2=454, -- SPITTER_PRE=484, -- preparing to shoot SPITTER_ATK=488, -- shooting FIREBALL_1=392, FIREBALL_2=394, GREN=413, -- grenade GREN_BOX=398, -- box of grenades (item to PILLAR=14, LEVER=328, -- wall with a lever on it LEVER_P=332, -- wall with a lever on it (pulled) GATE=260, LIGHT=174, PORTAL_1=152,PORTAL_2=154,PORTAL_3=156, HELL_1=120,HELL_2=122,HELL_3=124, SOUL=488, -- soul sphere BONUS=460, -- health bonus SKULL=207, } local S3={ ILEAVE=true, VP_L=0,VP_T=0,VP_R=239,VP_B=119, FLOOR_Y=0, CEIL_Y=50, FOG_S=10000, FOG_E=80000, FLIC_AMP=1500, FLIC_FM=0.003, -- frequency multiplier FLOOR_CLR=9, CEIL_CLR=0, TEX={ [TID.STONE]={w=32,h=32}, [TID.WOOD]={w=32,h=32}, [TID.DOOR]={w=32,h=32}, [TID.LDOOR]={w=32,h=32}, [TID.CYC_W1]={w=32,h=32}, [TID.CYC_ATK]={w=32,h=32}, [TID.CYC_W2]={w=32,h=32}, [TID.CYC_PRE]={w=32,h=32}, [TID.CBOW_N]={w=16,h=16}, [TID.CBOW_D]={w=16,h=16}, [TID.CBOW_E]={w=32,h=16}, [TID.ARROW]={w=8,h=8}, [TID.STIM]={w=8,h=8}, [TID.AMMO]={w=8,h=8}, [TID.DEMON_1]={w=16,h=16}, [TID.DEMON_2]={w=16,h=16}, [TID.DEMON_PRE]={w=16,h=16}, [TID.DEMON_ATK]={w=16,h=16}, [TID.KEY_1]={w=8,h=8}, [TID.KEY_2]={w=8,h=8}, [TID.SPITTER_1]={w=16,h=16}, [TID.SPITTER_2]={w=16,h=16}, [TID.SPITTER_PRE]={w=16,h=16}, [TID.SPITTER_ATK]={w=16,h=16}, [TID.FIREBALL_1]={w=16,h=16}, [TID.FIREBALL_2]={w=16,h=16}, [TID.GREN]={w=8,h=8}, [TID.GREN_BOX]={w=16,h=16}, [TID.PILLAR]={w=16,h=32}, [TID.LEVER]={w=32,h=32}, [TID.LEVER_P]={w=32,h=32}, [TID.GATE]={w=32,h=32}, [TID.LIGHT]={w=8,h=16}, [TID.PORTAL_1]={w=16,h=16}, [TID.PORTAL_2]={w=16,h=16}, [TID.PORTAL_3]={w=16,h=16}, [TID.HELL_1]={w=16,h=16}, [TID.HELL_2]={w=16,h=16}, [TID.HELL_3]={w=16,h=16}, [TID.SOUL]={w=16,h=16}, [TID.BONUS]={w=8,h=8}, [TID.SKULL]={w=8,h=16}, }, lastFT=nil, ex=0, ey=0, ez=0, yaw=0, cosMy=0, sinMy=0, termA=0, termB=0, t=0, NCLIP=0.1, FCLIP=1000, walls={}, hbuf={}, clrM={ {1,2,3,15}, {7,6,5,4}, {8,9,10,11} }, clrMR={}, -- computed on init PVS_CELL=50, pvstab={}, stencil={}, bills={}, overs={}, zobills={}, flashes={}, parts={}, partPool={}, } function S3Init() _S3InitClr() S3Reset() _S3StencilInit() end function S3Reset() S3SetCam(0,0,0,0) S3.walls={} S3.pvstab={} S3.overs={} S3.bills={} S3.zobills={} S3.flashes={} S3.parts={} S3.partPool={} end function _S3InitClr() for c=15,1,-1 do S3.clrMR[c]=nil end for _,ramp in pairs(S3.clrM) do for i=1,#ramp do local thisC=ramp[i] S3.clrMR[thisC]={ramp=ramp,i=i} end end end function _S3ClrMod(c,f,x,y) if f==1 then return c end local mr=S3.clrMR[c] if not mr then return c end local di=mr.i*f -- desired intensity local int if x then local loi=floor(di) local hii=ceil(di) local fac=di-loi local ent=(x+y)%3 int=fac>0.9 and hii or ((fac>0.5 and ent~=1) and hii or ((fac>0.1 and ent==1) and hii or loi)) else int=S3Round(di) end return int<=0 and 0 or mr.ramp[S3Clamp(int,1,#mr.ramp)] end function S3WallAdd(w) assert(w.lx) assert(w.lz) assert(w.rx) assert(w.rz) assert(w.tid) table.insert(S3.walls,w) _S3WallReg(w.lx,w.lz,w) _S3WallReg(w.rx,w.rz,w) end function S3WallDel(w) w.dead=true end function _S3WallReg(x,z,w) local RADIUS=6 local RADIUS2=RADIUS*RADIUS x,z=S3Round(x),S3Round(z) local centerC,centerR=x//S3.PVS_CELL,z//S3.PVS_CELL for r=centerR-RADIUS,centerR+RADIUS do for c=centerC-RADIUS,centerC+RADIUS do local dist2=(c-centerC)*(c-centerC)+ (r-centerR)*(r-centerR) if dist2<=RADIUS2 then _S3PvsAdd(c,r,w) end end end end function _S3PvsAdd(c,r,w) local t=S3.pvstab[r*32768+c] if not t then t={} S3.pvstab[r*32768+c]=t end for i=1,#t do if t[i]==w then return end -- already in table end table.insert(t,w) end local _S3PvsGet_empty={} function _S3PvsGet(x,z) x,z=S3Round(x),S3Round(z) local c,r=x//S3.PVS_CELL,z//S3.PVS_CELL return S3.pvstab[r*32768+c] or _S3PvsGet_empty; end function S3SetCam(ex,ey,ez,yaw) S3.ex,S3.ey,S3.ez,S3.yaw=ex,ey,ez,yaw S3.cosMy,S3.sinMy=cos(-yaw),sin(-yaw) S3.termA=-ex*S3.cosMy-ez*S3.sinMy S3.termB=ex*S3.sinMy-ez*S3.cosMy end function S3Proj(x,y,z) local c,s,a,b=S3.cosMy,S3.sinMy,S3.termA,S3.termB local px=0.9815*c*x+0.9815*s*z+0.9815*a local py=1.7321*y-1.7321*S3.ey local pz=s*x-z*c-b-0.2 local pw=x*s-z*c-b local ndcx=px/abs(pw) local ndcy=py/abs(pw) return 120+ndcx*120,68-ndcy*68,pz end function S3Rend() local nowMs=time() S3.dt=S3.lastFT and (0.001*(nowMs-S3.lastFT)) or 16 S3.lastFT=nowMs _S3PrepFlashes() local pvs=_S3PvsGet(S3.ex,S3.ez) local hbuf=S3.hbuf _S3PrepHbuf(hbuf,pvs) _S3RendOvers() _S3RendBills() _S3RendHbuf(hbuf) _S3RendFlats(hbuf) _S3PartsUpdate() _S3RendParts() S3.t=S3.t+1 end function _S3ResetHbuf(hbuf) for x=S3.VP_L,S3.VP_R do hbuf[x+1]=hbuf[x+1] or {} local b=hbuf[x+1] b.wall=nil b.z=HUGE end end function _S3PrepFlashes() for i=#S3.flashes,1,-1 do local f=S3.flashes[i] local unused f.sx,unused,f.sz=S3Proj(f.x,S3.ey,f.z) end end function _S3ProjWall(w,boty,topy) topy=topy or S3.CEIL_Y boty=boty or S3.FLOOR_Y local nclip=S3.NCLIP local fclip=S3.FCLIP local lx,lz,rx,rz=w.lx,w.lz,w.rx,w.rz for try=1,2 do -- second try is with z clipping local ltx,lty,ltz=S3Proj(lx,topy,lz) local rtx,rty,rtz=S3Proj(rx,topy,rz) if rtx<=ltx then return false end -- cull back side if rtxS3.VP_R then return false end local lbx,lby,lbz=S3Proj(lx,boty,lz) local rbx,rby,rbz=S3Proj(rx,boty,rz) w.slx,w.slz,w.slty,w.slby=ltx,ltz,lty,lby w.srx,w.srz,w.srty,w.srby=rtx,rtz,rty,rby if w.slzfclip and w.srz>fclip then return false elseif try==2 then return true elseif w.slz=240 and endx>=240 then return nil,nil,nil end startx=max(startx,0) endx=min(endx,239) if not S3.ILEAVE then return startx,endx,1 end if startx%2~=S3.t%2 then return startx+1,endx,2 else return startx,endx,2 end end function _S3PrepHbuf(hbuf,walls) _S3ResetHbuf(hbuf) for i=1,#walls do local w=walls[i] if not w.dead then if _S3ProjWall(w) then _AddWallToHbuf(hbuf,w) end end end for x=S3.VP_L,S3.VP_R do local hb=hbuf[x+1] -- hbuf is 1-indexed if hb.wall then local w=hb.wall hb.ty=S3Round(_S3Interp(w.slx,w.slty,w.srx,w.srty,x)) hb.by=S3Round(_S3Interp(w.slx,w.slby,w.srx,w.srby,x)) end end end function _AddWallToHbuf(hbuf,w) local startx=max(S3.VP_L,S3Round(w.slx)) local endx=min(S3.VP_R,S3Round(w.srx)) local step local nclip,fclip=S3.NCLIP,S3.FCLIP startx,endx,step=_S3AdjHbufIter(startx,endx) if not startx then return end for x=startx,endx,step do local hbx=hbuf[x+1] local z=_S3Interp(w.slx,w.slz,w.srx,w.srz,x) if z>nclip and zz then -- depth test. hbx.z,hbx.wall=z,w -- write new depth. else end end end end function _S3RendHbuf(hbuf) local startx,endx,step=_S3AdjHbufIter(S3.VP_L,S3.VP_R) if not startx then return end for x=startx,endx,step do local hb=hbuf[x+1] -- hbuf is 1-indexed local w=hb.wall if w then local z=_S3Interp(w.slx,w.slz,w.srx,w.srz,x) local u=_S3PerspTexU(w,x) _S3RendTexCol(w.tid,x,hb.ty,hb.by,u,z,nil,nil,nil, nil,nil,w.cmt) end end end function _S3StencilInit() for i=1,240*136+1 do S3.stencil[i]=-1 end end function _S3StencilRead(x,y) return S3.stencil[240*y+x+1]==S3.t end function _S3StencilWrite(x,y) if x>=0 and y>=0 and x<240 and y<136 then S3.stencil[240*y+x+1]=S3.t end end function S3BillAdd(bill) assert(bill.x and bill.y and bill.z and bill.w and bill.h and bill.tid) table.insert(S3.bills,bill) return bill end function S3BillDel(bill) local bills=S3.bills for i=1,#bills do if bills[i]==bill then bills[i]=bills[#bills] table.remove(bills) return true end end return false end function S3OverAdd(over) assert(over.sx) assert(over.sy) assert(over.tid) over.scale=over.scale or 1 table.insert(S3.overs,over) return over end function S3FlashAdd(flash) assert(flash.x) assert(flash.z) assert(flash.int) assert(flash.fod2) table.insert(S3.flashes,flash) return flash end function S3FlashDel(flash) for i=1,#S3.flashes do if S3.flashes[i]==flash then S3.flashes[i]=S3.flashes[#S3.flashes] table.remove(S3.flashes) return end end end function _S3RendBill(b) if b.slxS3.VP_R and b.srx>S3.VP_R then return end local lx,rx,z=b.slx,b.srx,b.sz local startx,endx,step=_S3AdjHbufIter(lx,rx) if not startx then return end local tid=b.tid local ty,by=b.sty,b.sby local hbuf=S3.hbuf for x=startx,endx,step do local hb=hbuf[x+1] -- 1-indexed if not hb or not hb.wall or hb.z>z then local u=_S3Interp(lx,0,rx,1,x) if _S3RendTexCol(tid,x,ty,by,u,z,nil,nil, 0,true,b.clrO,b.cmt) then b.vis=true end end end end function _S3RendOvers() local overs=S3.overs for i=1,#overs do _S3RendOver(overs[i]) end end function _S3RendOver(o) local td=S3.TEX[o.tid] assert(td) local scale=o.scale local w=td.w*scale local h=td.h*scale local lx,rx=o.sx,o.sx+w-1 local startx,endx,step=_S3AdjHbufIter(lx,rx) if not startx then return end for x=startx,endx,step do for y=o.sy,o.sy+h-1 do local t=_S3GetTexel(o.tid,(x-lx)//scale, (y-o.sy)//scale) if t>0 then pix(x,y,t) _S3StencilWrite(x,y) end end end end function _S3ProjBill(b) b.sx,b.sy,b.sz=S3Proj(b.x,b.y,b.z) b.sw=117.78*b.w/b.sz b.sh=117.78*b.h/b.sz b.slx,b.srx=S3Round(b.sx-0.5*b.sw), S3Round(b.sx+0.5*b.sw) b.sty,b.sby=S3Round(b.sy-0.5*b.sh), S3Round(b.sy+0.5*b.sh) end function _S3RendBills() local nclip,fclip=S3.NCLIP,S3.FCLIP local bills=S3.bills local r={} for i=1,#bills do local b=bills[i] b.vis=false _S3ProjBill(b) if b.slx<=S3.VP_R and b.srx>=S3.VP_L and b.sz>nclip and b.szb.sz then table.insert(l,i,b) return end end table.insert(l,b) end function _S3LightF(sx,sz) local FOG_S,FOG_E=S3.FOG_S,S3.FOG_E local csx=120-sx -- centered sx local d2=csx*csx+sz*sz if S3.FLIC_AMP>0 then local f=sin(time()*S3.FLIC_FM)*S3.FLIC_AMP d2=d2+f end local f=d20 then p=pool[#pool] table.remove(pool) else p={} end local ux,uy,uz=sin(theta)*cos(phi), sin(theta)*sin(phi),cos(theta) p.x,p.y,p.z=cx+ux*r,cy+uy*r,cz+uz*r local spd=spec.minSpd+ (spec.maxSpd-spec.minSpd)*random() p.vx,p.vy,p.vz=ux*spd,uy*spd,uz*spd p.ax,p.ay,p.az=0,(spec.fall and -200 or 0),0 if #spec.clr then p.clr=spec.clr[random(1,#spec.clr)] else p.clr=spec.clr end p.ttl=spec.ttl p.size=spec.size table.insert(parts,p) end end function _S3PartsUpdate() local parts=S3.parts local pool=S3.partPool local dt=S3.dt local floorY=S3.FLOOR_Y for i=#parts,1,-1 do local p=parts[i] p.ttl=p.ttl-dt p.vx,p.vy,p.vz=p.vx+p.ax*dt,p.vy+p.ay*dt,p.vz+p.az*dt p.x,p.y,p.z=p.x+p.vx*dt,p.y+p.vy*dt,p.z+p.vz*dt if p.ttl<0 or p.yvpL and sx+p.size=vpT and sy+p.sizenclip and szhi and hi or x) end function _S3Interp(x1,y1,x2,y2,x) if x2=x2 and y2 or (y1+(y2-y1)*(x-x1)/(x2-x1))) end function _S3TexSamp(tid,td,u,v) local SX,SY=td.w,td.h local tx=floor(u*SX)%SX local ty=floor(v*SY)%SY return _S3GetTexel(tid,tx,ty) end function _S3GetTexel(tid,texx,texy) local spid=tid+(texy//8)*16+(texx//8) texx=texx%8 texy=texy%8 return peek4(0x8000+spid*64+texy*8+texx) end function S3Dot(ax,az,bx,bz) return ax*bx+az*bz end function S3Norm(x,z) return sqrt(x*x+z*z) end function S3Normalize(x,z) local l=S3Norm(x,z) return x/l,z/l end local D_INVULN=false local D_SHOWFPS=false local D_STARTGREN=nil local D_HCL=nil local D_HASKEY=false local D_NOAWAKE=false local D_CLEARPMEM=false local TSIZE=50 local PLR_CS=20 local FLOOR_Y=S3.FLOOR_Y local CEIL_Y=S3.CEIL_Y local ORIG_PAL={ [0]=0x140c1c, [1]=0x2c2c2c, [2]=0x716d69, [3]=0xaaaeaa, [4]=0x04ff04, [5]=0x04ce04, [6]=0x008100, [7]=0x004000, [8]=0x382800, [9]=0x693800, [10]=0x9d5500, [11]=0xc28900, [12]=0x0055ff, [13]=0xff0000, [14]=0xffdd00, [15]=0xffffff, } local BTN={ FWD=0, BACK=1, LEFT=2, RIGHT=3, FIRE=4, LOB=5, STRAFE=6, OPEN=7, } local PLR_ATK={ {tid=TID.CBOW_D,t=0.0,fire=true}, {tid=TID.CBOW_D,t=0.2,fire=true}, {tid=TID.CBOW_N,t=0.2,fire=false}, {tid=TID.CBOW_E,t=0.5,fire=false} } local MODE={ TITLE=0, -- title screen. LVLSEL=1, -- level select screen. PREROLL=2, -- level name preroll. INSTRUX=3, -- instructions screen. PLAY=4, -- playing level. DEAD=5, -- player is dead. MINIMAP=6, -- showing minimap. EOL=7, -- end of level. WIN=8, -- beat entire game. } local MUS={ LVL=-3, -- play level specific music (in Level) KEEP=-2, -- keep playing previous. NONE=-1, -- no music. E1M1=0, -- "at doom's gate" INTER=1, -- "intermission from doom" SANDY=2, -- "into sandy's city" README=3, -- "doom 2 text cluster" NOBODY=4, -- "nobody told me about id" } local MODEMUS={ [MODE.TITLE]=MUS.INTER, [MODE.LVLSEL]=MUS.KEEP, [MODE.PREROLL]=MUS.KEEP, [MODE.INSTRUX]=MUS.NONE, [MODE.PLAY]=MUS.LVL, [MODE.DEAD]=MUS.NONE, [MODE.MINIMAP]=MUS.KEEP, [MODE.EOL]=MUS.INTER, [MODE.WIN]=MUS.README, } local A={ -- A for "App" mode=MODE.TITLE, mclk=0, -- time elapsed in current mode lftime=-1, -- last frame time, -1 if none. sel=1, -- current selection in menu } local G=nil -- deep copied from G_INIT local G_INIT={ ex=350, ey=25, ez=350, yaw=30, lftime=-1, -- last frame time clk=0, -- game clock, seconds lvlNo=0, lvl=nil, iwalls={}, doorAnim=nil, PSPD=120,PASPD=2.0, ents={}, hp=100, ammo=20, grens=D_STARTGREN or 0, lastGrenT=-999, justHurt=nil, weapOver=nil, atk=0, atke=0, msg="", msgCd=0, hasKey=D_HASKEY or false, flash=nil, focC=nil,focR=nil, minimapC=0, mmox,mmoy=0,0, mmseen={}, otiles={}, spawners={}, trigdone={}, } local T={ VOID=0, -- void tiles on map (player can't FLOOR=16, -- floor tile on map (space where player TRIGGER=7, -- trigger tile (triggers spawners) SPAWNER=6, -- spawner (spawns entities) SECRET=52, -- secret area tile } local S={ FLAG=240, META_0=241, HUD_KEY=230, LOCK=188, ARROW=187, } local E={ ZOMB=32, POTION=48, AMMO=64, DEMON=49, KEY=65, SPITTER=50, GREN_BOX=66, PILLAR=51, LIGHT=67, PORTAL=80, HELL=81, BIGZOMB=82, ARROW=1000, FIREBALL=1001, GREN=1002, SOUL=83, BONUS=84, SKULL=86, } local MMTILES={ [T.FLOOR]=true, [E.PILLAR]=true, [E.LIGHT]=true, [E.SKULL]=true, [E.HELL]=true, [E.PORTAL]=true, } local ANIM={ ZOMBW={inter=0.2,tids={TID.CYC_W1,TID.CYC_W2}}, DEMON={inter=0.2,tids={TID.DEMON_1,TID.DEMON_2}}, KEY={inter=0.2,tids={TID.KEY_1,TID.KEY_2}}, SPITTER={inter=0.2,tids={TID.SPITTER_1, TID.SPITTER_2}}, FIREBALL={inter=0.2,tids={TID.FIREBALL_1, TID.FIREBALL_2}}, PORTAL={inter=0.2,tids={TID.PORTAL_1,TID.PORTAL_2, TID.PORTAL_3,TID.PORTAL_2}}, HELL={inter=0.4,tids={TID.HELL_1,TID.HELL_2,TID.HELL_1,TID.HELL_3}}, } local SND={ ARROW={sfx=58,note="F-5",dur=30,vol=15,spd=0}, HIT={sfx=62,note="E-3",dur=6,vol=15,spd=0}, KILL={sfx=62,note="C-2",dur=12,vol=15,spd=-2}, BONUS={sfx=61,note="C-4",dur=6,vol=15,spd=-2}, HURT={sfx=60,note="C-6",dur=6,vol=15,spd=-2}, DOOR={sfx=59,note="C-2",dur=6,vol=15,spd=-1}, DIE={sfx=58,note="E-2",dur=30,vol=15,spd=-1}, BOOM={sfx=57,note="G-3",dur=30,vol=15,spd=-2}, SPIT={sfx=56,note="C-2",dur=6,vol=15,spd=1}, FAIL={sfx=55,note="C-2",dur=10,vol=15,spd=0}, EOL={sfx=54,note="F-2",dur=30,vol=15,spd=-2}, SOUL={sfx=53,note="F-4",dur=30,vol=15,spd=0}, } local YANCH={ FLOOR=0, -- entity anchors to the floor CENTER=1, -- entity is centered vertically CEIL=2, -- entity anchors to the ceiling } local ECFG_DFLT={ yanch=YANCH.FLOOR, } local ECFG={ [E.ZOMB]={ w=50,h=50, anim=ANIM.ZOMBW, pursues=true, idealDist2=2500, wanderTime=0.5, wanderOnHurt=true, speed=50, attacks=true, dmgMin=4,dmgMax=10, hp=10, vuln=true, solid=true, asleep=true, attseq={ {t=0.3,tid=TID.CYC_PRE}, {t=0.5,tid=TID.CYC_ATK,dmg=true}, {t=0.8,tid=TID.CYC_W1}, }, }, [E.BIGZOMB]={ w=70,h=70, anim=ANIM.ZOMBW, cmt={[8]=7,[9]=6,[10]=5,[11]=4,[14]=13}, pursues=true, idealDist2=5000, wanderTime=0.0, wanderOnHurt=false, killEndLvl=true, speed=90, attacks=true, shoots=true, shot=E.FIREBALL, shotInt=1.0, shotSpd=100, shotSnd=SND.SPIT, dmgMin=10,dmgMax=25, hp=100, vuln=true, solid=true, asleep=true, attseq={ {t=0.3,tid=TID.CYC_PRE}, {t=0.5,tid=TID.CYC_ATK,dmg=true}, {t=0.8,tid=TID.CYC_W1}, }, }, [E.DEMON]={ w=20,h=20, yanch=YANCH.CENTER, anim=ANIM.DEMON, pursues=true, idealDist2=2500, wanderTime=1.2, speed=50, attacks=true, dmgMin=5,dmgMax=15, hp=20, asleep=true, vuln=true, solid=true, attseq={ {t=0.3,tid=TID.DEMON_PRE}, {t=0.5,tid=TID.DEMON_ATK,dmg=true}, {t=0.8,tid=TID.DEMON_2}, }, }, [E.SPITTER]={ w=20,h=20, yanch=YANCH.CENTER, anim=ANIM.SPITTER, pursues=true, idealDist2=80000, wanderTime=4, speed=70, attacks=false, shoots=true, shot=E.FIREBALL, shotInt=2.0, shotSpd=200, shotSnd=SND.SPIT, hp=1, vuln=true, solid=true, asleep=true, attseq={ {t=0.3,tid=TID.SPITTER_PRE}, {t=0.5,tid=TID.SPITTER_ATK,dmg=true}, {t=0.8,tid=TID.SPITTER_2}, }, }, [E.ARROW]={ w=8,h=8, ttl=2, tid=TID.ARROW, yanch=YANCH.CENTER, }, [E.POTION]={ w=16,h=16, tid=TID.STIM, }, [E.SOUL]={ w=32,h=32, tid=TID.SOUL, }, [E.BONUS]={ w=16,h=16, tid=TID.BONUS, }, [E.AMMO]={ w=16,h=16, tid=TID.AMMO, }, [E.KEY]={ w=16,h=16, anim=ANIM.KEY, }, [E.FIREBALL]={ w=16,h=16, anim=ANIM.FIREBALL, ttl=2, yanch=YANCH.CENTER, hurtsPlr=true, dmgMin=20, dmgMax=50,collRF=3, fragile=true, -- can't go through solid tiles }, [E.GREN]={ w=8,h=8, tid=TID.GREN, yanch=YANCH.CENTER, falls=true, fallVy0=40, }, [E.GREN_BOX]={ w=16,h=16, tid=TID.GREN_BOX, }, [E.PILLAR]={ w=32,h=64, tid=TID.PILLAR, solid=true, }, [E.PORTAL]={ w=48,h=48, anim=ANIM.PORTAL, }, [E.LIGHT]={ w=20,h=40, tid=TID.LIGHT, solid=true, }, [E.HELL]={ w=30,h=30, anim=ANIM.HELL, yanch=YANCH.CENTER, solid=true, }, [E.SKULL]={ w=8,h=16, tid=TID.SKULL, yanch=YANCH.CEIL, }, } local TF={ N=1,E=2,S=4,W=8, NSLD=0x10, DOOR=0x20, LOCKED=0x40, LEVER=0x80, GATE=0x100, PORTAL=0x200, } TF.INTEREST=TF.DOOR|TF.LEVER|TF.GATE|TF.PORTAL local TD={ [1]={f=TF.S|TF.E,tid=TID.STONE}, [2]={f=TF.S,tid=TID.STONE}, [3]={f=TF.S|TF.W,tid=TID.STONE}, [17]={f=TF.E,tid=TID.STONE}, [19]={f=TF.W,tid=TID.STONE}, [33]={f=TF.N|TF.E,tid=TID.STONE}, [34]={f=TF.N,tid=TID.STONE}, [35]={f=TF.N|TF.W,tid=TID.STONE}, [55]={f=TF.S|TF.E,tid=TID.WOOD}, [56]={f=TF.S,tid=TID.WOOD}, [57]={f=TF.S|TF.W,tid=TID.WOOD}, [71]={f=TF.E,tid=TID.WOOD}, [73]={f=TF.W,tid=TID.WOOD}, [87]={f=TF.N|TF.E,tid=TID.WOOD}, [88]={f=TF.N,tid=TID.WOOD}, [89]={f=TF.N|TF.W,tid=TID.WOOD}, [5]={f=TF.S|TF.DOOR,tid=TID.DOOR}, [20]={f=TF.E|TF.DOOR,tid=TID.DOOR}, [22]={f=TF.W|TF.DOOR,tid=TID.DOOR}, [37]={f=TF.N|TF.DOOR,tid=TID.DOOR}, [8]={f=TF.S|TF.DOOR|TF.LOCKED,tid=TID.LDOOR}, [23]={f=TF.E|TF.DOOR|TF.LOCKED,tid=TID.LDOOR}, [25]={f=TF.W|TF.DOOR|TF.LOCKED,tid=TID.LDOOR}, [40]={f=TF.N|TF.DOOR|TF.LOCKED,tid=TID.LDOOR}, [11]={f=TF.S|TF.LEVER,tid=TID.LEVER}, [26]={f=TF.E|TF.LEVER,tid=TID.LEVER}, [28]={f=TF.W|TF.LEVER,tid=TID.LEVER}, [43]={f=TF.N|TF.LEVER,tid=TID.LEVER}, [53]={f=TF.S|TF.GATE,tid=TID.GATE}, [68]={f=TF.E|TF.GATE,tid=TID.GATE}, [70]={f=TF.W|TF.GATE,tid=TID.GATE}, [85]={f=TF.N|TF.GATE,tid=TID.GATE}, [E.PORTAL]={f=TF.PORTAL|TF.NSLD}, } local LVL={ { name="KNEE DEEP IN THE DEAD", pg=0,pgw=1,pgh=1, floorC=2,ceilC=8, lmus=MUS.E1M1, par=30, }, { name="AGAINST THEE WICKEDLY", pg=1,pgw=1,pgh=2, floorC=9,ceilC=1, lmus=MUS.SANDY, par=30, wallCmt={ [TID.DOOR]={[2]=6,[1]=7,[3]=5,[15]=4}, [TID.WOOD]={[9]=6,[8]=7,[10]=5}, }, }, { name="THE AQUADUCTS", pg=2,pgw=1,pgh=2, floorC=7,ceilC=1, lmus=MUS.INTER, par=80, wallCmt={ [TID.DOOR]={[9]=12}, [TID.LDOOR]={[9]=12}, }, }, { name="THE TOWER OF BABEL", pg=3,pgw=2,pgh=2, floorC=8,ceilC=18, lmus=MUS.NOBODY, par=50, wallCmt={ [TID.STONE]={[1]=7,[2]=6,[3]=5,[15]=4}, [TID.PILLAR]={[1]=7,[2]=6,[3]=13}, }, }, } DEBUGS=nil local PFX={ KILL={ count=30,minR=5,maxR=20,minSpd=40,maxSpd=100, fall=true,clr={13},ttl=2,size=2, }, BLAST={ count=50,minR=5,maxR=20,minSpd=150,maxSpd=200, fall=true,clr={14,15,11},ttl=2,size=4, }, } function EntAdd(etype,x,z) local e=Overlay({}, Overlay(ECFG_DFLT,ECFG[etype] or {})) e.x,e.z=x,z e.y=e.yanch==YANCH.FLOOR and FLOOR_Y+e.h*0.5 or (e.yanch==YANCH.CEIL and CEIL_Y-e.h*0.5 or (FLOOR_Y+CEIL_Y)*0.5) e.tid=e.anim and e.anim.tids[1] or e.tid e.etype=etype e.ctime=G.clk e.bill={x=e.x,y=e.y,z=e.z,w=e.w,h=e.h, tid=e.tid,ent=e,cmt=e.cmt} S3BillAdd(e.bill) table.insert(G.ents,e) return e end function CheckEntHits() local ents=G.ents for i=1,#ents do local e=ents[i] if e.etype==E.ARROW then CheckArrowHit(e) elseif e.etype==E.GREN then CheckGrenBlast(e) elseif e.etype==E.POTION or e.etype==E.AMMO or e.etype==E.KEY or e.etype==E.GREN_BOX or e.etype==E.SOUL or e.etype==E.BONUS then CheckPickUp(e) end end end function CalcHitTarget(proj) local ents=G.ents local zob=S3.zobills for i=1,#zob do local e=zob[i].ent if not e.dead and e.vuln and e.bill.vis and ProjHitEnt(proj,e) then return e end end return nil end function CheckArrowHit(arrow) local e=CalcHitTarget(arrow) if not e then return end arrow.dead=true local age=G.clk-arrow.ctime local dmg=max(1,S3Round(10-age*20)) HurtEnt(e,dmg) end function HurtEnt(e,dmg) e.hp=e.hp-dmg e.hurtT=G.clk if e.hp<0 then e.dead=true Snd(SND.KILL) S3PartsSpawn(e.x,e.y,e.z,PFX.KILL) LVLKil=LVLKil+1 ftmr=-20 if e.killEndLvl then enk=true CheckLevelEnd() end else Snd(SND.HIT) if e.wanderOnHurt then EntStartWander(e,0.3) end end end function CheckPickUp(item) if DistSqToPlr(item.x,item.z)<400 then if item.etype==E.POTION and G.hp<100 then G.hp=min(100,G.hp+25) Say("Stimpack +25") item.dead=true Snd(SND.BONUS) elseif item.etype==E.AMMO and G.ammo<100 then G.ammo=min(100,G.ammo+4) Say("Shells +4") item.dead=true Snd(SND.BONUS) elseif item.etype==E.KEY then G.hasKey=true Say("Keycard") item.dead=true Snd(SND.BONUS) elseif item.etype==E.GREN_BOX then G.grens=min(G.grens+3,20) Say("Grenades +3") item.dead=true Snd(SND.BONUS) elseif item.etype==E.SOUL then G.hp=min(200,G.hp+199) Say("Soulsphere") LVLItm=LVLItm+1 item.dead=true Snd(SND.SOUL) ftmr=-40 elseif item.etype==E.BONUS then G.hp=min(200,G.hp+1) Say("Health Bonus +1") LVLItm=LVLItm+1 item.dead=true Snd(SND.BONUS) end end end function ProjHitEnt(p,e) local dt=G.dt local r2=0.25*e.w*e.w for u=0,100,25 do local px,pz=p.x-G.dt*p.vx*u*0.01, p.z-G.dt*p.vz*u*0.01 if r2>DistSqXZ(px,pz,e.x,e.z) then return true end end return false end function UpdateEnts() local ents=G.ents UpdateFlash() for i=1,#ents do local e=ents[i] UpdateEnt(e) if not D_NOAWAKE then e.asleep=e.asleep and not e.bill.vis end end for i=#ents,1,-1 do if ents[i].dead then ents[i].bill.ent=nil -- break cycle, help GC S3BillDel(ents[i].bill) ents[i]=ents[#ents] table.remove(ents) end end end function CheckGrenBlast(gren) local BLASTR2=30000 -- blast radius, squared local VBLASTR2=45000 -- visual blast radius, squared local solid=IsInSolidTile(gren.x,gren.z) local et=CalcHitTarget(gren) if not solid and not et and gren.y>FLOOR_Y then return end gren.dead=true if G.flash then S3FlashDel(G.flash) end G.flash=S3FlashAdd({x=gren.x,z=gren.z, int=10,fod2=VBLASTR2}) for i=1,#G.ents do local e=G.ents[i] if e.vuln and e.bill.vis and e.hp then local d2=DistSqXZ(e.x,e.z,gren.x,gren.z) local dmg=(e==et and 40 or 10) if d2250000 then return end local dt=G.dt if e.pursueWcd then e.pursueWcd=e.pursueWcd-dt if e.pursueWcd<0 then e.pursueWcd=nil end local px,pz=e.x+e.pursueWvx*dt, e.z+e.pursueWvz*dt if IsPosValid(px,pz,e) then e.x,e.z=px,pz return end e.pursueWcd=nil end local bestx,bestz,bestd2,bestmx,bestmz= nil,nil,nil,nil,nil for mz=-1,1 do for mx=-1,1 do local px,pz=e.x+mx*e.speed*dt, e.z+mz*e.speed*dt if IsPosValid(px,pz,e) then local d2=DistSqToPlr(px,pz) if not bestd2 or abs(d2-ideald2)3000 then return end -- too far. e.origAnim=e.anim e.att=1 e.atte=0 -- elapsed e.anim=nil end e.atte=e.atte+G.dt if e.atte>e.attseq[e.att].t then e.att=e.att+1 if e.att>#e.attseq then e.att=nil e.anim=e.origAnim elseif e.attseq[e.att].dmg then HurtPlr(random(e.dmgMin,e.dmgMax)) end end if e.att then e.tid=e.attseq[e.att].tid end end function EntBehShoots(e) assert(e.shot) assert(e.shotInt) assert(e.shotSpd) e.shootC=(e.shootC or e.shotInt)-G.dt if e.shootC>0 then return end e.shootC=e.shotInt local vx,vz=V2Normalize(G.ex-e.x,G.ez-e.z) local shot=EntAdd(e.shot,e.x,e.z) shot.vx,shot.vz=vx*e.shotSpd,vz*e.shotSpd if e.shotSnd and DistSqToPlr(e.x,e.z)<80000 then Snd(e.shotSnd) end end function EntBehHurtsPlr(e) local d2=DistSqToPlr(e.x,e.z) local r=(e.collRF or 1)*e.w*0.5 if d21.5 then G.doorAnim=nil return end end function GetFocusTile() local fx,fz=PlrFwdVec(TSIZE) local c,r= floor((G.ex+fx)/TSIZE),floor((G.ez+fz)/TSIZE) local t=LvlTile(c,r) local td=TD[t] if not td then return nil,nil end if 0~=td.f&TF.INTEREST then return c,r else return nil,nil end end function UpdateFocusTile() G.focC,G.focR=GetFocusTile() end function Interact() if not G.focC then return end local td=TD[LvlTile(G.focC,G.focR)] if td.f&TF.DOOR~=0 then DoorOpen(G.focC,G.focR) elseif td.f&TF.LEVER~=0 then PullLever(G.focC,G.focR) end end function LoadLevel(lvlNo) PalSet() G=DeepCopy(G_INIT) G.lvlNo=lvlNo G.lvl=LVL[lvlNo] lvlmus=G.lvl.lmus sec=0 ftmr=0 face=388 sf={} enk=false LVLSec=0 LVLKil=0 LVLItm=0 local lvl=G.lvl S3Reset() S3.FLOOR_CLR,S3.CEIL_CLR=lvl.floorC,lvl.ceilC G.ex=nil LoadSpawners() for r=0,lvl.pgh*17-1 do for c=0,lvl.pgw*30-1 do local cx,cz=(c+0.5)*TSIZE,(r+0.5)*TSIZE local t=LvlTile(c,r) local lbl=TileLabel(c,r) local td=TD[t] if td then AddWalls(c,r,td) end if t==S.FLAG then assert(lbl) if lbl==0 then G.ex,G.ez=cx,cz end end if not D_NOENTS and ECFG[t] then EntAdd(t,cx,cz) end end end assert(G.ex,"Start pos flag not found.") G.weapOver=S3OverAdd({sx=104,sy=88,tid=460,scale=2}) end function LoadSpawners() local cols,rows=LvlSize() G.spawners={} for r=0,rows-1 do for c=0,cols-1 do local t=LvlTile(c,r) if t==T.SPAWNER then LoadSpawner(c,r) end end end end function LoadSpawner(c0,r0) local lbl=TileLabel(c0,r0) assert(lbl) LvlTile(c0,r0,T.FLOOR) -- remove spawner from map if not G.spawners[lbl] then G.spawners[lbl]={} end for r=r0-1,r0+1 do for c=c0-1,c0+1 do local t=LvlTile(c,r) if ECFG[t] then LvlTile(c,r,T.FLOOR) local cx,cz=(c+0.5)*TSIZE,(r+0.5)*TSIZE table.insert(G.spawners[lbl],{x=cx,z=cz,eid=t}) end end end end function CheckTriggers() local c,r=floor(G.ex/TSIZE),floor(G.ez/TSIZE) if LvlTile(c,r)==T.SECRET then local secl=c..","..r if not table.contains(sf,secl) then table.insert(sf,secl) sec=50 LVLSec=LVLSec+1 end return end if LvlTile(c,r)~=T.TRIGGER then return end local lbl=TileLabel(c,r) if not lbl or G.trigdone[lbl] then return end G.trigdone[lbl]=true local spawner=G.spawners[lbl] if not spawner then return end for i=1,#spawner do local s=spawner[i] local ent=EntAdd(s.eid,s.x,s.z) ent.asleep=false end end function LvlTile(c,r,newval) local cols,rows=LvlSize() if c>=cols or r>=rows or c<0 or r<0 then return 0 end local val=G.otiles[r*240+c] if not val then local c0,r0=MapPageStart(G.lvl.pg) val=mget(c0+c,r0+r) end if newval then G.otiles[r*240+c]=newval end return val end function LvlSize() return G.lvl.pgw*30,G.lvl.pgh*17 end function LvlTileAtXz(x,z) local c,r=floor(x/TSIZE),floor(z/TSIZE) return LvlTile(c,r) end function PullLever(c,r) LvlTile(c,r,T.SOLID) local w=IwallAt(c,r) if w then w.tid=TID.LEVER_P end local cols,rows=LvlSize() local gatec,gater=nil,nil for r=0,rows-1 do for c=0,cols-1 do local t=LvlTile(c,r) if TD[t] and TD[t].f&TF.GATE~=0 then gatec,gater=c,r break end end end assert(gatec) local w=IwallAt(gatec,gater) assert(w) IwallDel(gatec,gater) S3WallDel(w) LvlTile(gatec,gater,T.FLOOR) Say("THE GATE OPENED!") end function GetInteractHint() local c,r=G.focC,G.focR if not c then return end local hint=nil local td=TD[LvlTile(c,r)] if td.f&TF.DOOR~=0 then if td.f&TF.LOCKED~=0 then if G.hasKey then return "Press S to unlock" else return "You need a key" end else return "Press S to open door" end elseif td.f&TF.LEVER~=0 then return "Press S to activate" elseif td.f&TF.GATE~=0 then return "Gate opens elsewhere!" elseif td.f&TF.PORTAL~=0 then return "Step into the portal!" end return nil end function CheckLevelEnd() local t=LvlTileAtXz(G.ex,G.ez) if (TD[t] and TD[t].f&TF.PORTAL)or enk==true then SetMode(MODE.EOL) Snd(SND.EOL) MarkLvlDone(G.lvlNo) end end function PalSet(tint) tint=tint or {r=0,g=0,b=0,a=0} for c=0,15 do local origR=(ORIG_PAL[c]&0xff0000)>>16 local origG=(ORIG_PAL[c]&0xff00)>>8 local origB=(ORIG_PAL[c]&0xff) local r=_S3Interp(0,origR,255,tint.r,tint.a) local g=_S3Interp(0,origG,255,tint.g,tint.a) local b=_S3Interp(0,origB,255,tint.b,tint.a) poke(0x3fc0+3*c,clamp(S3Round(r),0,255)) poke(0x3fc0+3*c+1,clamp(S3Round(g),0,255)) poke(0x3fc0+3*c+2,clamp(S3Round(b),0,255)) end end function Rend() S3SetCam(G.ex,G.ey,G.ez,G.yaw) S3Rend() RendHud(false) RendHint() if sec>0 then sec=sec-1 PrintC("A secret has been revealed.",120,50,14) end if G.msgCd>0 then G.msgCd=G.msgCd-G.dt print(G.msg,8,100,13) end if DEBUGS then print(DEBUGS,4,12) end end function RendHud(full) local HUDY=120 local BOXW,BOXH,BOXCLR=14,8,2 local HPX,HPY=29,HUDY+6 local AMMOX,AMMOY=9,HUDY+6 local GRENX,GRENY=105,HUDY+6 local FACEX,FACEY=100,HUDY+6 if full then local c0,r0=MapPageStart(63) map(c0,r0,30,2,0,HUDY) print("Hold S for",22*8,HUDY+4,1) print("map/help",22*8,HUDY+10,1) else rect(HPX,HPY,BOXW+5,BOXH,BOXCLR) rect(AMMOX,AMMOY,BOXW,BOXH,BOXCLR) --rect(GRENX,GRENY,BOXW,BOXH,BOXCLR) end if G.hp>20 or Blink(0.3,0.2) then print(G.hp,HPX+2,HPY+1, G.hp>20 and 15 or 14,true) end print(To2Dig(G.ammo),AMMOX+2,AMMOY+1,15,true) --print(To2Dig(G.grens),GRENX+2,GRENY+1,15,true) RendFace(FACEX,FACEY) if G.justHurt then print("-"..G.justHurt.hp,100,10,15,true,2) end if G.hasKey and not G.paintedKey then spr(S.HUD_KEY,18*8,HUDY+8) G.paintedKey=true end if G.lvlNo==1 and G.clk<5 then rect(0,5,200,9,15) print("Z = shoot",2,7,0) end end function RendFace(fx,fy) ftmr=ftmr+1 if G.justHurt then face=391 else if ftmr<0 then face=404 elseif ftmr%7==0 then face=random(388,390) end end spr(face,fx,fy,2) end function RendHint() local hint=GetInteractHint() if hint then local X,Y,W,H=120,5,120,8 rect(X,Y,W,H,15) print(hint,X+2,Y+2,0) end end function To2Dig(n) n=floor(n) return n<0 and "00" or (n<10 and "0"..n or ((n<100) and n or 99)) end function IsPosValid(x,z,ent) local cs=ent and ent.w*0.4 or PLR_CS local solid=IsInSolidTile(x-cs,z-cs) or IsInSolidTile(x-cs,z+cs) or IsInSolidTile(x+cs,z-cs) or IsInSolidTile(x+cs,z+cs) if solid then return false end local ents=G.ents for i=1,#ents do local e=ents[i] if e~=ent and e.solid then local d2=DistSqXZ(e.x,e.z,x,z) if d2<0.25*(cs*cs+e.w*e.w) then return false end end end if ent and DistSqToPlr(x,z)<2500 then return false end return true end function IsInSolidTile(x,z) local c,r=floor(x/TSIZE),floor(z/TSIZE) local t=LvlTile(c,r) if t==T.VOID then return true end if t==T.FLOOR then return false end local td=TD[t] if not td then return false end return 0==td.f&TF.NSLD end function RotPoint(ox,oz,px,pz,alpha) local ux,uz=px-ox,pz-oz local c,s=cos(alpha),sin(alpha) return ox+ux*c-uz*s,oz+uz*c+ux*s end function Overlay(a,b) local result=DeepCopy(a) for k,v in pairs(b) do if result[k] and type(result[k])=="table" and type(v)=="table" then result[k]=Overlay(result[k],v) else result[k]=DeepCopy(v) end end return result end function DistSqXZ(x1,z1,x2,z2) return (x1-x2)*(x1-x2)+(z1-z2)*(z1-z2) end function DistXZ(x1,z1,x2,z2) return sqrt(DistSqXZ(x1,z1,x2,z2)) end function DistSqToPlr(x,z) return DistSqXZ(x,z,G.ex,G.ez) end function DistToPlr(x,z) return DistXZ(x,z,G.ex,G.ez) end function PlrFwdVec(scale) scale=scale or 1 return -sin(G.yaw)*scale,-cos(G.yaw)*scale end function V2Mag(x,z) return sqrt(x*x+z*z) end function V2Normalize(x,z) local mag=V2Mag(x,z) if mag>0.001 then return x/mag,z/mag else return 0,0 end end function DeepCopy(t) if type(t)~="table" then return t end local r={} for k,v in pairs(t) do if type(v)=="table" then r[k]=DeepCopy(v) else r[k]=v end end return r end function MapPageStart(pg) return (pg%8)*30,(pg//8)*17 end function MetaValue(t) if t>=S.META_0 and t<=S.META_0+9 then return t-S.META_0 end end function TileLabel(tc,tr) for c=tc-1,tc+1 do for r=tr-1,tr+1 do local mv=MetaValue(LvlTile(c,r)) if mv then return mv end end end return nil end function Blink(ondur,offdur,phase) ondur=ondur or 0.2 offdur=offdur or ondur phase=phase or 0 return ondur>math.fmod(A.mclk,(ondur+offdur)) end function GetDpad() local dx=btn(BTN.LEFT) and -1 or (btn(BTN.RIGHT) and 1 or 0) local dz=btn(BTN.FWD) and 1 or (btn(BTN.BACK) and -1 or 0) return dx,dz end function GetDpadP() local dx=btnp(BTN.LEFT) and -1 or (btnp(BTN.RIGHT) and 1 or 0) local dz=btnp(BTN.FWD) and 1 or (btnp(BTN.BACK) and -1 or 0) return dx,dz end function PrintC(text,cx,cy,color,fixed,scale) color=color or 15 fixed=fixed or false scale=scale or 1 local w,h=print(text,0,-10,color,fixed,scale),6 print(text,cx-w*0.5,cy-h*0.5,color,fixed,scale) end function IsLvlLocked(l) local hcl=D_HCL or (pmem(0) or 0) return l>hcl+1 end function MarkLvlDone(l) pmem(0,max(pmem(0) or 0,l)) end function Boot() if D_CLEARPMEM then pmem(0,0) end S3Init() SetMode(MODE.TITLE) end function SetMode(m) local old=A.mode A.mode,A.mclk=m,0 PalSet() local mus=MODEMUS[m] or MUS.KEEP or MUS.LVL if old==MODE.MINIMAP and m==MODE.PLAY then mus=MUS.KEEP end if mus~=MUS.KEEP then music(mus) end if mus==MUS.LVL then music(lvlmus) end end function TIC() local stime=time() local dtmillis=A.lftime and (stime-A.lftime) or 16 A.lftime=stime A.dt=dtmillis*.001 -- convert to seconds local dt=A.dt A.mclk=A.mclk+dt local f=TICF[A.mode] if f then f() end if D_SHOWFPS then print(S3Round(1000/(time()-stime)).."fps") end end function TICTitle() local c,r=MapPageStart(62) map(c,r,30,17) PrintC("3D first-person shooter", 120,50,2) if Blink(0.3,0.2) then PrintC("- Press Z to play -",120,80) end if btnp(BTN.FIRE) then SetMode(IsLvlLocked(2) and MODE.INSTRUX or MODE.LVLSEL) end end function TICDead() Rend() rect(0,50,240,30,0) print("You died.",80,60,15,false,2) if A.mclk>5 then SetMode(MODE.TITLE) return end end function TICWin() cls(0) PrintC("As the remains of the biggest demon",120,10,13) PrintC("you've ever seen crumbles into red",120,15,13) PrintC("giblets before your eyes, you can't",120,20,13) PrintC("help but think, \"I've done it.\"",120,25,13) PrintC("You've saved earth from the imminent",120,30,13) PrintC("invasion and complete destruction.",120,35,13) PrintC("You Win... for now.",120,45,13) if btnp(BTN.FIRE) then SetMode(MODE.TITLE) end end function TICPreroll() cls(0) PrintC("ENTERING",120,10,13) PrintC(G.lvl.name,120,20,13) if btnp(BTN.FIRE) and A.mclk>1 then EndPreroll() end end function EndPreroll() cls(0) SetMode(MODE.PLAY) RendHud(true) end function TICInstrux() local c,r=MapPageStart(61) cls(0) map(c,r,30,17) print("CONTROLS",100,10,8) local X=88 local Y=32 print("Strafe",X,Y,3) print("Open/Use",X,Y+16,3) print("Flame orb",X,Y+48,14) print("FIRE",X,Y+64,4) print("Move",168,96,15) if Blink(0.5) then print("- PRESS FIRE TO CONTINUE -",50,124,4) end if btnp(BTN.FIRE) then StartLevel(1) end end function TICLvlSel() local mx,my=GetDpadP() A.sel=S3Clamp(A.sel-my,1,#LVL) local c,r=MapPageStart(62) cls(0) map(c,r+10,30,17,0,80) PrintC("SELECT LEVEL",120,10,15) local X,Y=40,20 local RH=10 for i=1,#LVL do local y=Y+RH*i local locked=IsLvlLocked(i) if locked then spr(S.LOCK,X+10,y-2) end print(i..": "..LVL[i].name,X+20,y, locked and 2 or (i==A.sel and 14 or 15)) end spr(S.ARROW,X,A.sel*RH+Y) if btnp(BTN.FIRE) and not IsLvlLocked(A.sel) then StartLevel(A.sel) end if IsLvlLocked(A.sel) then PrintC("THIS LEVEL IS LOCKED.",120,80,2) else if Blink(0.3,0.2) then PrintC("PRESS Z TO SELECT.",120,80,4) end end end function StartLevel(lvlNo) LoadLevel(lvlNo) SetMode(MODE.PREROLL) end function TICEol() cls(0) PrintC(G.lvl.name,120,10,13) PrintC("COMPLETED",120,20,13) PrintC("KILLS: "..LVLKil,120,60,13) PrintC("ITEMS: "..LVLItm,120,70,13) PrintC("SECRETS: "..LVLSec,120,80,13) if G.clk<420 then lvt=floor(G.clk) else lvt="SUCKS!" end PrintC("TIME: "..lvt,60,100,13) PrintC("PAR: "..G.lvl.par,180,100,13) if btnp(BTN.FIRE) and A.mclk>1 then if G.lvlNo>=#LVL then SetMode(MODE.WIN) else StartLevel(G.lvlNo+1) end end end function TICPlay() G.dt=A.dt G.clk=G.clk+A.dt local PSPD=G.PSPD local dt=G.dt local mx,mz=GetDpad() local vx,vz=PlrFwdVec(mz) MovePlr(PSPD*dt,vx,vz) if btn(BTN.STRAFE) then vx=-math.sin(G.yaw-1.5708)*mx vz=-math.cos(G.yaw-1.5708)*mx MovePlr(PSPD*dt,vx,vz) else G.yaw=G.yaw-mx*G.PASPD*dt end if btnp(BTN.OPEN) then Interact() end G.minimapC=btn(BTN.OPEN) and G.minimapC+dt or 0 if G.minimapC>0.5 then G.minimapC=0 MinimapStart() return end DoorAnimUpdate(dt) CheckTriggers() UpdateFocusTile() UpdateJustHurt() UpdatePlrAtk() CheckEntHits() UpdateEnts() MinimapUpdateOff() CheckLevelEnd() Rend() end function TICMinimap() MinimapTick() end function UpdateJustHurt() if not G.justHurt then return end G.justHurt.cd=G.justHurt.cd-G.dt if G.justHurt.cd<0 then PalSet() G.justHurt=nil return end end function UpdatePlrAtk() if G.atk==0 then if btnp(BTN.FIRE) then if G.ammo>0 then G.ammo=G.ammo-1 G.atk=1 G.atke=0 else Say("No shells left!") Snd(SND.FAIL) end end else G.atke=G.atke+G.dt if G.atke>PLR_ATK[G.atk].t then G.atk=(G.atk+1)%(#PLR_ATK+1) G.atke=0 if G.atk>0 and PLR_ATK[G.atk].fire then local dx,dz=PlrFwdVec(4) local arrow=EntAdd(E.ARROW,G.ex+dx,G.ez+dz) arrow.y=G.ey-2 arrow.vx,arrow.vz=dx*200,dz*200 Snd(SND.ARROW) end end end if btnp(BTN.LOB) then if G.grens>0 and G.clk-G.lastGrenT>1 then G.lastGrenT=G.clk local dx,dz=PlrFwdVec(4) local gren=EntAdd(E.GREN,G.ex+dx,G.ez+dz) gren.y=G.ey-2 gren.vx,gren.vz=dx*50,dz*50 G.grens=G.grens-1 elseif G.grens==0 then Say("No flame orbs left!") Snd(SND.FAIL) end end G.weapOver.tid=G.atk==0 and (G.ammo>0 and TID.CBOW_N or TID.CBOW_E) or PLR_ATK[G.atk].tid end function HurtPlr(hp) if D_INVULN then return end G.hp=max(G.hp-hp,0) if G.hp==0 then SetMode(MODE.DEAD) Snd(SND.DIE) PalSet({r=0,g=0,b=0,a=80}) return end G.justHurt={hp=hp,cd=0.7} PalSet({r=255,g=0,b=0,a=40}) Snd(SND.HURT) end function MovePlr(d,vx,vz) local STEP=1 local ex,ez=G.ex,G.ez while d>0 do local l=min(d,STEP) -- how much to move this step d=d-STEP local ax,az=ex+l*vx,ez+l*vz -- full motion local bx,bz=ex,ez+l*vz -- move only in Z direction local cx,cz=ex+l*vx,ez -- move only in X direction if IsPosValid(ax,az) then ex,ez=ax,az elseif IsPosValid(bx,bz) then ex,ez=bx,bz elseif IsPosValid(cx,cz) then ex,ez=cx,cz else break end -- motion completely blocked end G.ex,G.ez=ex,ez end function Say(msg) G.msg=msg G.msgCd=1 end function Snd(snd) assert(snd.sfx) sfx(snd.sfx,snd.note,snd.dur,0,snd.vol,snd.spd) end TICF={ [MODE.TITLE]=TICTitle, [MODE.LVLSEL]=TICLvlSel, [MODE.PLAY]=TICPlay, [MODE.DEAD]=TICDead, [MODE.INSTRUX]=TICInstrux, [MODE.MINIMAP]=TICMinimap, [MODE.PREROLL]=TICPreroll, [MODE.WIN]=TICWin, [MODE.EOL]=TICEol, } Boot() function MinimapStart() SetMode(MODE.MINIMAP) G.mmox,G.mmoy=90-8*G.ex/TSIZE,68-8*G.ez/TSIZE end function MinimapFromWorld(x,z) return G.mmox+8*x/TSIZE,G.mmoy+8*z/TSIZE end function MinimapUpdateOff() local seen=G.mmseen local c0,r0=floor(G.ex/TSIZE),floor(G.ez/TSIZE) for r=r0-3,r0+3 do for c=c0-3,c0+3 do seen[r*240+c]=true end end end function MinimapTick() local mx,my=GetDpad() G.mmox=G.mmox-mx G.mmoy=G.mmoy+my local c0,r0=MapPageStart(G.lvl.pg) local cols,rows=LvlSize() clip(S3.VP_L,S3.VP_T,S3.VP_R-S3.VP_L+1, S3.VP_B-S3.VP_T+1) cls(0) local startx,starty=MinimapFromWorld(0,0) map(c0,r0,cols,rows,S3Round(startx), S3Round(starty),0,1,MinimapRemap) if Blink(0.2,0.1) then local px,py=MinimapFromWorld(G.ex,G.ez) rect(px-1,py-1,3,3,4) local fx,fy=PlrFwdVec(8) line(px,py,px+fx,py+fy,6) end if btnp(BTN.OPEN) then SetMode(MODE.PLAY) end local IX,IY=170,0 local IW,IH=240-IX,110 local X0,Y0=IX+2,IY+2 local R=7 -- row height rect(IX,IY,IW,IH,1) rectb(IX,IY,IW,IH,15) local y=Y0 print("Buttons",X0,y,3) y=y+R print("Z",X0,y,4) print("fire",X0+10,y,6) y=y+R print("X",X0,y,14) print("throw orb",X0+10,y,11) y=y+R print("A",X0,y,3) print("strafe",X0+10,y,2) y=y+R print("S",X0,y,3) print("open",X0+10,y,2) y=y+2*R print("Kills:",X0,y,2) y=y+R print(LVLKil,X0,y,2) y=y+R print("Items:",X0,y,2) y=y+R print(LVLItm,X0,y,2) y=y+R print("Secrets:",X0,y,2) y=y+R print(LVLSec,X0,y,2) y=y+R print("Time:",X0,y,2) y=y+R print(floor(G.clk),X0,y,2) y=y+2*R print("Press S to close map",2,110,2) print("Level "..G.lvlNo..": "..G.lvl.name,2,2,15) clip() end function MinimapRemap(t,c,r) local c0,r0=MapPageStart(G.lvl.pg) c,r=c-c0,r-r0 if not G.mmseen[r*240+c] then return T.VOID end if TD[t] then return t end if MMTILES[t] then return t end return t==T.VOID and T.VOID or T.FLOOR end function table.contains(table,element) for _, value in pairs(table) do if value==element then return true end end return false end