diff --git a/chameleonic.p8 b/chameleonic.p8 index abd61e5..abc25a6 100644 --- a/chameleonic.p8 +++ b/chameleonic.p8 @@ -116,29 +116,6 @@ function _mnmx(x,y) return x,y end -function _rastn( - x0,y0,x1,y1,dx,dy -) - -- todo: more optimized implementation? - local iter=_rast(x0,y0,x1,y1) - local prevx,prevy=nil,nil - - local done=false - return function() - while not done do - local x,y=iter() - - if (x==nil) done=true return x1, y1 - - local x8 = x\dx - local y8 = y\dy - if not (x8==prevx and y8==prevy) then - prevx,prevy=x8,y8 - return x,y - end - end - end -end function _rast( x0,y0,x1,y1 @@ -161,7 +138,7 @@ function _rast( if (x==x1) done=true return x1,y1 local oldx,oldy=x,y err-=dy - if (err<0) y+=sy err+=dx+dy return oldx,y + if (err<0) y+=sy err+=dx x+=sx return oldx,oldy end @@ -172,17 +149,13 @@ function _rast( if (y==y1) done=true return x1,y1 local oldx,oldy=x,y err-=dx - if (err<0) x+=sx err+=dy+dx return x,oldy + if (err<0) x+=sx err+=dy y+=sy return oldx,oldy end end end -function _point_eq(p1,p2) - return p1.x==p2.x and p1.y==p2.y -end - -->8 -- input kbd={} @@ -232,6 +205,33 @@ function kbd:release(i) self.down&=~(1<"..tostring(v).." " + end + return str.."}" + end + if type(any)=="number" then + return ""..any + end + return "unknown" -- should never show +end + -->8 -- title screen title={} @@ -293,7 +293,7 @@ function level:reinit(n) self.todo={} self.bigx=(n%8) self.bigy=(n\8) - self.cache_can_stretch=dcache:new() + self.next_crate_id=1 self:load_dynobjs() self:recollide() @@ -326,16 +326,16 @@ function level:draw() spr(pit.contents,pit.px,pit.py) pal() pal(1,0) + end end for _,crate in pairs(self._crates) do spr(crate.s,crate.px,crate.py) - end end pal() end function level:busy() - for _,crate in pairs(self.crates) do + for _,crate in pairs(self._crates) do if (#crate.todo>0) return true end return false @@ -379,10 +379,12 @@ function level:load_dynobjs() if def then self._crates[mxy]={ s=s,def=def, + id=self.next_crate_id, mx=mx,my=my, px=px,py=py, todo={} } + self.next_crate_id+=1 end if s==28 then -- pit @@ -399,15 +401,17 @@ end function level:recollide() self._coll={} + self._coll_nocrate={} for mx=0,15 do for my=0,15 do local mxy=_mix(mx,my) + self._coll_nocrate[mxy]= + fget(self:_mget(mx,my),7) self._coll[mxy]= - fget(self:_mget(mx,my),7) or + self._coll_nocrate[mxy] or self._crates[mxy]!=nil end end - self.cache_can_stretch:clear() end function add_adjacent_anchors(tbl,mx,my) @@ -420,33 +424,72 @@ function add_adjacent_anchors(tbl,mx,my) end function level:reanchor() - self._anch={} + local anch_new={} for dxy in all{{-1,-1},{1,-1},{-1,1},{1,1}} do local dx,dy=unpack(dxy) assert(dx!=0 and dy!=0) for mx0=0,15 do for my0=0,15 do local mx1,my1=mx0+dx,my0+dy + if ( - self:mcoll(mx0,my0) and + self:mcoll_nocrate(mx0,my0) and not self:mcoll(mx0,my1) and not self:mcoll(mx1,my0) and not self:mcoll(mx1,my1) ) then - add(self._anch, { + local key="GEOM"..mx0..","..my0..","..dx..","..dy + anch_new[key]= { ax=max(mx0,mx1),ay=max(my0,my1),adx=-dx,ady=-dy - }) + } end end end + + for _,cr in pairs(self._crates) do + local key="CRATE"..cr.id..","..dx..","..dy + local mx0,my0=cr.mx,cr.my + local mx1,my1=mx0+dx,my0+dy + anch_new[key]={ + ax=max(mx0,mx1),ay=max(my0,my1),adx=-dx,ady=-dy + } + end end + + local anch_old=self._anch + if (anch_old==nil) anch_old={} + for _,old in pairs(anch_old) do + old.dropped=true + end + + for k,new in pairs(anch_new) do + local old=anch_old[k] + if old then + anch_new[k]=old + old.ax,old.ay,old.adx,old.ady=new.ax,new.ay,new.adx,new.ady + old.dropped=nil + end + end + self._anch=anch_new + self._anch_keys={} + for k,_ in pairs(self._anch) do + add(self._anch_keys,{key=k}) + end + + if (player.rope!=nil) player.rope:relax() end function level:win_at(mx,my) return self._wins[_mix(mx,my)] end + function level:anchor_points() - return pairs(self._anch) + keys=all(self._anch_keys) + return function() + local k=keys() + if (k==nil) return nil + return self._anch[k.key] + end end function level:get_open_pit(mx,my) @@ -499,6 +542,9 @@ end function level:mcoll(mx,my) return self._coll[_mix(mx,my)]!=false end +function level:mcoll_nocrate(mx,my) + return self._coll_nocrate[_mix(mx,my)]!=false +end function level:pcoll(px,py) return self:mcoll(px\8,py\8) @@ -574,6 +620,8 @@ function level:get_latch(dx,dy,px,py) return { el="eyehook", dx=dx1,dy=dy1, + ax_offset=dx1*0.5, + ay_offset=dy1*0.5, mx=mx,my=my } end @@ -597,16 +645,15 @@ function level:can_move( -- todo: check tongue collision if player.rope then - local px,py=mx0*8,my0*8 local chk=false if dmx==0 and dmy==-1 then - chk=player.rope:collide_rect(px+3,py-5,px+4,py+5,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0,my0-1,1,2,exclude_src,exclude_dst) elseif dmx==0 and dmy==1 then - chk=player.rope:collide_rect(px+3,py+3,px+4,py+13,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0,my0,1,2,exclude_src,exclude_dst) elseif dmx==-1 and dmy==0 then - chk=player.rope:collide_rect(px-5,py+3,px+5,py+4,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0-1,my0,2,1,exclude_src,exclude_dst) elseif dmx==1 and dmy==0 then - chk=player.rope:collide_rect(px+3,py+3,px+13,py+4,exclude_src,exclude_dst) + chk=player.rope:collide_mrect(mx0,my0,2,1,exclude_src,exclude_dst) end if (chk) return false @@ -639,74 +686,6 @@ function level:tug_crate(mx0,my0,dmx,dmy) self:recollide() self:reanchor(false) end --->8 --- collision checks -function level:can_stretch( - p1,p2 -) - local key=p1.x..","..p1.y..","..p2.x..","..p2.y - return self.cache_can_stretch:wrap(key,function() - return self:_can_stretch(p1,p2) - end) -end - -function level:_can_stretch( - p1,p2 -) - -- faster implementation for straight lines - if p1.y\8==p2.y\8 then - local my=p2.y\8 - for mx=p1.x\8,p2.x\8 do - if (level:mcoll(mx,my)) return false - end - end - - if p1.x\8==p2.x\8 then - local mx=p2.x\8 - for my=p1.y\8,p2.y\8 do - if (level:mcoll(mx,my)) return false - end - end - - if (level:pcoll(p1.x,p1.y)) return false - if (level:pcoll(p2.x,p2.y)) return false - - local res=true - for x,y in _rastn(p1.x,p1.y,p2.x,p2.y,8,8) do - if level:pcoll(x,y) then - res=false - break - end - end - - return res -end - --->8 ---cache impl for collision checks -dcache={} -dcache.__index=dcache - -function dcache:new() - local d={} - setmetatable(d,dcache) - d:clear() - return d -end -function dcache:clear() - self.old,self.new,self.new_n={},{},0 -end - -function dcache:wrap(key,f) - local el=self.new[key] - if (el!=nil) return el - local el=self.old[key] - if (el==nil) el=f() - self.new[key]=el - self.new_n+=1 - if (self.new_n>1000) self.old,self.new,self.new_n=self.new,{},0 - return el -end -->8 --player handling @@ -815,7 +794,7 @@ function player:update() self.rope=rope:new( x+0.5-dx*0.5,y+0.5-dy*0.5, - self.x+0.5,self.y+0.5, + self.x+0.5,self.y+0.1, level:get_latch(dx,dy,x*8,y*8) ) @@ -836,7 +815,7 @@ function player:update() if self.rope then self.rope:update() - self.rope:drag_dst(self.x+0.5,self.y+0.5) + self.rope:drag_dst(self.x+0.5,self.y+0.1) local tdx,tdy=self.rope:tug_orientxy() if (tdx!=0) self.orientx=tdx @@ -853,6 +832,7 @@ function player:_vanish_if_requested() self.vanish_frame+=1 if (self.fall_frame>0 or self.vanish_frame>20) then + self.rope=nil level:restart() kbd:release(5) self.vanish_frame=20 @@ -975,8 +955,8 @@ function rope:update() self.latch.rec!=nil then self:drag_src( - self.latch.rec.mx+0.5+self.latch.ax_offset, - self.latch.rec.my+0.5+self.latch.ay_offset + self.latch.rec.px/8+0.5+self.latch.ax_offset, + self.latch.rec.py/8+0.5+self.latch.ay_offset ) if #self.latch.rec.todo==0 then @@ -986,6 +966,8 @@ function rope:update() end end + if (not self:_check_sane()) self:destroy() + elseif self.state.name=="destroy" then -- destroy self.state.frame+=1 if (self.state.frame>=5) self.state={name="done"} @@ -1057,13 +1039,14 @@ function rope:draw(artificial_dx,artificial_dy) end end local n1=self.src + --[[ local sy=0 while true do if (n1==nil) break local x=n1.ax*8 local y=n1.ay*8 rectfill(x-1,y-1,x+1,y+1,12) - --print("ax="..n1.ax..",ay="..n1.ay,0,sy) + print("ax="..n1.ax..",ay="..n1.ay,0,sy) sy+=7 local n0=n1.prev @@ -1075,7 +1058,9 @@ function rope:draw(artificial_dx,artificial_dy) assert(ady==-1 or ady==0 or ady==1) --assert(not (adx==0 and ady==0)) - rectfill(x+2,y+2,x+4,y+4,3) + local c=3 + if (n1.associated_with.dropped) c=8 + rectfill(x+2,y+2,x+4,y+4,c) pset(x+adx*2,y,9) pset(x,y+ady*2,9) else @@ -1093,17 +1078,6 @@ function rope:draw(artificial_dx,artificial_dy) pset(p.ax*8+p.adx,p.ay*8,11) pset(p.ax*8,p.ay*8+p.ady,11) end - - --[[ - print("dirty:"..tostr(self.dirty),32,0,9) - print("busy:"..tostr(self:busy()),32,7,9) - print("state:"..tostr(self.state.name),32,14,9) - if self.all_ops!=nil then - for i,o in ipairs(self.all_ops) do - rect(o.mx*8,o.my*8,o.mx*8+7,o.my*8+7,4) - --print(o.mx..","..o.my,0,i*8,3) - end - end ]]-- end @@ -1116,13 +1090,25 @@ function rope:drag_src(x,y) end function rope:drag(n1,ax_new,ay_new) - -- TODO: stepwise? - self:_relax() + self:relax() + self:_drag(n1,ax_new,n1.ay) self:_drag(n1,ax_new,ay_new) - self:_relax() + self:relax() end -function rope:_relax() +function rope:relax() + local n=self.src + + while true do + if (n==nil) break + + if (n.associated_with) then + self:_drag(n,n.associated_with.ax,n.associated_with.ay) + end + + n=n.next + end + local n0=self.src while true do if (n0==nil) return @@ -1132,12 +1118,14 @@ function rope:_relax() if (n2==nil) return if n1.associated_with!=nil then + local x0,y0=n0.ax,n0.ay local x1,y1=n1.ax,n1.ay local x2,y2=n2.ax,n2.ay local would,x1_new,y1_new=would_stick(x0,y0,n1.associated_with,x2,y2) - if not would then + if not would and not (n1.ax==x1_new and n1.ay==y1_new) then + printh("relaxing: "..tostring(n0.associated_with).."->"..tostring(n1.associated_with).."->"..tostring(n2.associated_with)) self:_drag(n1,x1_new,y1_new) n0=n1.prev n2=n1.next @@ -1145,11 +1133,56 @@ function rope:_relax() n2.prev=n0 n1.next=nil n1.prev=nil + --n0=n0.next else n0=n0.next end else n0=n0.next end end end +function rope:_check_sane() + if (self.state.name!="latched") return true + if (level:busy()) return true + + printh("start") + local n0=self.src + + local qxs,qys={},{} + while true do + local n1=n0.next + if (n1==nil) break + + for qx,qy in _rast(flr(n0.ax*2),flr(n0.ay*2),flr(n1.ax*2),flr(n1.ay*2)) do + add(qxs,qx) + add(qys,qy) + end + n0=n1 + end + + local function _blocked(qx,qy) + local mx0=(qx-1)\2 + local mx1=qx\2 + local my0=(qy-1)\2 + local my1=qy\2 + + return level:mcoll(mx0,my0) and level:mcoll(mx1,my1) + end + for i=1,#qxs do + if (_blocked(qxs[i],qys[i])) printh("blocked"..qxs[i]..","..qys[i]) return false + end + + for i=3,#qxs do + local qx1,qy1=qxs[i-1],qys[i-1] + if qx1%2==0 and qy1%2==0 then + local qx0,qy0=qxs[i-2],qys[i-2] + local qx2,qy2=qxs[i],qys[i] + local mx0,my0=qx0\2,qy0\2 + local mx2,my2=qx2\2,qy2\2 + if (level:mcoll(mx0,my2) and level:mcoll(mx2,my0)) printh("not traversable") return false + end + end + return true +end + function would_stick(x0,y0,anchor,x2,y2) local x1,y1=anchor.ax,anchor.ay if (x0>x2) x0,y0,x2,y2=x2,y2,x0,y0 @@ -1175,7 +1208,9 @@ function would_stick(x0,y0,anchor,x2,y2) if (y0"..tostring(n05.associated_with).."->"..tostring(n1.associated_with)) n0.next=n05 n1.prev=n05 n0=n05 @@ -1258,9 +1294,8 @@ function rope:_drag(n1,ax1_new,ay1_new) if (n2==nil) break local anch=_sweep_radar(n2.ax,n2.ay,ax1_old,ay1_old,ax1_new,ay1_new) if (anch==nil) break - if (anch.ax==n1.ax and anch.y==n1.ay) break - if (anch.ax==n2.ax and anch.y==n2.ay) break local n15={ax=anch.ax,ay=anch.ay,associated_with=anch,prev=n1,next=n2} + printh("creating post: "..tostring(n1.associated_with).."->"..tostring(n15.associated_with).."->"..tostring(n2.associated_with)) n1.next=n15 n2.prev=n15 n2=n15 @@ -1276,7 +1311,9 @@ function _stepfrom(x0,x1) end end - local mul=1 + local mul=0.5 + x0*=2 + x1*=2 if (x0>x1) x0,x1,mul=-x0,-x1,-mul local i=flr(x0) local top=flr(x1) @@ -1320,18 +1357,33 @@ function distance(p1,p2) return sqrt(dx*dx+dy*dy) end -function rope:collide_rect(x1,y1,x2,y2,exclude_src,exclude_dst) - local a0=self.src +function rope:collide_mrect(mx0,my0,mw,mh,exclude_src,exclude_dst) + local mx1,my1=mx0+mw,my0+mh + local n0=self.src + + mx0+=0.1 + my0+=0.1 + mx1-=0.1 + my1-=0.1 + while true do - local a1=a0.next - if (a1==nil) return false - --[[ - if (_line_line(a0.x,a0.y,a1.x,a1.y,x1,y1,x2,y1)) return true - if (_line_line(a0.x,a0.y,a1.x,a1.y,x1,y1,x1,y2)) return true - if (_line_line(a0.x,a0.y,a1.x,a1.y,x1,y2,x2,y2)) return true - if (_line_line(a0.x,a0.y,a1.x,a1.y,x2,y1,x2,y2)) return true - ]]-- - a0=a0.next + local n1=n0.next + if (n1==nil) return false + + local nd=n0 + for i=1,exclude_dst do + nd=nd.next + if (nd==nil) return false + end + + if exclude_src<=0 then + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx0,my0,mx1,my0)) return true + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx0,my0,mx0,my1)) return true + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx0,my1,mx1,my1)) return true + if (_line_line(n0.ax,n0.ay,n1.ax,n1.ay,mx1,my0,mx1,my1)) return true + end + exclude_src-=1 + n0=n1 end end @@ -1391,12 +1443,12 @@ function rope:_tug(hypothetically) local touched={} for i=#ancs-1,2,-1 do - local ops_before_trash,hit_end1=self:_calc_push(ancs[i+1],ancs[i],ancs[i-1],ancs[i-2]) + local ops_before_trash=self:_calc_push(ancs[i+1],ancs[i],ancs[i-1],ancs[i-2]) local ops_to_do,corners={} if #ops_before_trash>0 then ops_to_do=ops_before_trash else - local ops_after_trash,hit_end2=self:_calc_push(ancs[i-2],ancs[i-1],ancs[i],ancs[i+1]) + local ops_after_trash=self:_calc_push(ancs[i-2],ancs[i-1],ancs[i],ancs[i+1]) ops_to_do=ops_after_trash end @@ -1406,39 +1458,9 @@ function rope:_tug(hypothetically) if (hypothetically) return ancs,i-1 local dmx,dmy=ops[1].dmx,ops[1].dmy - local adjacent_ancs={} for o in all(ops) do - add_adjacent_anchors(adjacent_ancs,o.mx,o.my) level:tug_crate(o.mx,o.my,o.dmx,o.dmy) end - for node=ancs[i-1].ix,ancs[i].ix do - local anc=self:_anc(node) - local x0,y0=anc.x,anc.y - - local upd=function(x,y,force) - return {update=function(s,i) - if force or not level:pcoll(x,y) then - s.x=x - s.y=y - end - s.dirty=true - self.dirty=true - return true - end} - end - local dmxh,dmyh=dmx,dmy - local ax,ay=level:p2a(x0,y0) - if (adjacent_ancs[_amix(ax,ay)]==nil) dmxh,dmyh=0,0 - anc.todo={ - {}, - upd(x0+dmxh*2,y0+dmyh*2), - upd(x0+dmxh*7,y0+dmyh*7), - upd(x0+dmxh*8,y0+dmyh*8), - } - end - for node=ancs[i-1].ix-1,ancs[i].ix+1 do - local anc=self:_anc(node) - end return true end end @@ -1548,7 +1570,6 @@ function rope:_calc_push( end end - local hit_end=true local ops2={} for o in all(ops) do if not level:mcoll(o.mx,o.my) then @@ -1556,18 +1577,16 @@ function rope:_calc_push( else local crate=level:get_crate(o.mx,o.my) if crate==nil then - hit_end=false break else if not level:can_move(false,o.mx,o.my,o.dmx,o.dmy,0,0) then - hit_end=false break end end add(ops2,o) end end - return ops2,hit_end + return ops2 end function rope:_anchors_simplified() @@ -1578,7 +1597,18 @@ function rope:_anchors_simplified() end a=self.src while a!=nil do - local point={x=a.ax*8,y=a.ay*8,ax=a.ax,ay=a.ay} + local point={ + x=flr(a.ax*8+0.5),y=flr(a.ay*8+0.5), + ax=a.ax,ay=a.ay + } + if a.associated_with then + if (a.associated_with.adx==1) point.x-=1 + if (a.associated_with.ady==1) point.y-=1 + elseif a.prev==nil and self.latch then + if (self.latch.ax_offset<0) point.x-=1 + if (self.latch.ay_offset<0) point.y-=1 + end + if #points<=1 then add(points,point) elseif abs( @@ -1590,7 +1620,6 @@ function rope:_anchors_simplified() add(points,point) end a=a.next - assert(#points<100) end return points end