diff --git a/chameleonic.p8 b/chameleonic.p8 index fb75cbe..68be32d 100644 --- a/chameleonic.p8 +++ b/chameleonic.p8 @@ -93,13 +93,13 @@ function _apply(x,ts,a) local t=deli(ts,1) for k,v in pairs(t) do if k=="update" then - if not v(x,a) then - add(ts,t,1) - end + -- else x[k]=v end end + + if (t and t.update and not t.update(x,a)) add(ts,t,1) end function sgn0(x) @@ -112,29 +112,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 @@ -157,7 +134,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 @@ -168,17 +145,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={} @@ -228,6 +201,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={} @@ -289,7 +289,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() @@ -322,16 +322,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 @@ -375,10 +375,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 @@ -403,67 +405,74 @@ function level:recollide() self._crates[mxy]!=nil end end - self.cache_can_stretch:clear() end -function add_adjacent_anchors(tbl,mx,my) - for ax in all{mx*2-1,mx*2+2} do - for ay in all{my*2-1,my*2+2} do - local px,py=level:a2p(ax,ay) - tbl[_amix(ax,ay)]={ax=ax0,ay=ay0,x=px,y=py} - end - end -end +function level:reanchor() + 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 -function level:reanchor(remove) - if remove or not self._anch then - self._anch={} - end - - for ax0=0,31 do - local ax1 = ax0-1+2*(ax0%2) - local mx0,mx1 = ax0\2,ax1\2 - for ay0=0,31 do - local ay1=ay0-1+2*(ay0%2) - local my0,my1=ay0\2,ay1\2 - - if ( - not self:mcoll(mx0,my0) and - not self:mcoll(mx0,my1) and - not self:mcoll(mx1,my0) and - self:mcoll(mx1,my1) - ) then - local px0,py0=level:a2p(ax0,ay0) - self._anch[_amix(ax0,ay0)]={ax=ax0,ay=ay0,x=px0,y=py0} + if ( + self:mcoll(mx0,my0) and not self:get_crate(mx0,my0) and + not self:mcoll(mx0,my1) and + not self:mcoll(mx1,my0) and + not self:mcoll(mx1,my1) + ) then + 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 - for _,cr in pairs(self._crates) do - add_adjacent_anchors(self._anch,cr.mx,cr.my) + local anch_old=self._anch + if (anch_old==nil) anch_old={} + for _,old in pairs(anch_old) do + old.dropped=true end - if (player.rope!=nil) player.rope:make_dirty() + 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) -end -function level:anchors_in(px0,py0,px1,py1) - local anch,ax,xlim,sy,ylim=self._anch,px0\4,(px1+3)\4,py0\4,(py1+3)\4 - local ay=sy-1 - return function() - while true do - ay+=1 - if(ay>ylim)ay,ax=sy,ax+1 - if(ax>xlim)return nil - local anc=anch[_amix(ax,ay)] - if (anc) return anc - end +function level:anchor_points() + keys=all(self._anch_keys) + return function() + local k=keys() + if (k==nil) return nil + return self._anch[k.key] end end @@ -472,12 +481,6 @@ function level:get_open_pit(mx,my) if (pit and pit.contents==nil) return pit end -function level:point_anchor(px,py) - local ax,ay=self:p2a(px,py) - local anc=self._anch[_amix(ax,ay)] - return anc -end - function level:spawn_exit() self._wins={} local spawned=false @@ -491,9 +494,11 @@ function level:spawn_exit() end local win_at=function(x,y) if (self:_mget(x,y)!=18) return - for n in all(neighbors{x=x,y=y}) do - if n.x<0 or n.y<0 or n.x>15 or n.y>15 then - self._wins[_mix(n.x,n.y)]=true + for nx=x-1,x+1 do + for ny=y-1,y+1 do + if nx<0 or ny<0 or nx>15 or ny>15 then + self._wins[_mix(nx,ny)]=true + end end end end @@ -504,16 +509,6 @@ function level:spawn_exit() assert(spawned) end -function level:p2a(px,py) - return px\4,py\4 -end - -function level:a2p(ax,ay) - local px=ax*4+3*(ax%2) - local py=ay*4+3*(ay%2) - return px,py -end - function level:mcoll(mx,my) return self._coll[_mix(mx,my)]!=false end @@ -574,8 +569,8 @@ function level:get_latch(dx,dy,px,py) return { el="crate", dx=dx1,dy=dy1, - px_offset=px-crate.px+dx1, - py_offset=py-crate.py+dy1, + ax_offset=dx1*0.5, + ay_offset=dy1*0.5, rec=crate } end @@ -592,7 +587,9 @@ function level:get_latch(dx,dy,px,py) return { el="eyehook", dx=dx1,dy=dy1, - mx=mx,my=my + ax_offset=dx1*0.5, + ay_offset=dy1*0.5, + rec={mx=mx,my=my,px=mx*8,py=my*8}, } end end @@ -615,16 +612,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 @@ -642,88 +638,16 @@ function level:tug_crate(mx0,my0,dmx,dmy) local mx1,my1=mx0+dmx,my0+dmy local mxy1=_mix(mx1,my1) - existing.mx=mx1 - existing.my=my1 existing.todo={ - {px=mx0*8+dmx*2,py=my0*8+dmy*2}, - {px=mx0*8+dmx*7,py=my0*8+dmy*7}, - {px=mx1*8,py=my1*8,update=function() - self:reanchor(true) + {px=mx1*8+dmx,py=my1*8+dmy,mx=mx1,my=my1,update=function() + self:recollide() + self:reanchor() return true - end} + end}, + {px=mx1*8,py=my1*8} } self._crates[mxy1]=existing - 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 @@ -758,7 +682,6 @@ end function player:any_busy() if (#self.todo>0) return true if (level:busy()) return true - if (self.rope!=nil and self.rope:busy()) return true return false end @@ -826,10 +749,17 @@ function player:update() self.todo=f4({{orienty=1,py=2},{py=7},{y=self.y+1}}) else wrongbleep:bleep() end elseif self.rope==nil and kbd:btnr(4) then - local rx,ry,rx2,ry2=self:_rope_pos() - local dx,dy=12*self.orientx,12*self.orienty + local dx,dy=self.orientx,self.orienty if (dy!=0) dx=0 - self.rope=rope:new(rx,ry,rx2,ry2,dx,dy) + + local x,y=self.x,self.y + while not level:mcoll(x,y) do x+=dx y+=dy end + + self.rope=rope:new( + x+0.5-dx*0.5,y+0.5-dy*0.5, + self.x+0.5,self.y+0.1, + level:get_latch(dx,dy,x*8,y*8) + ) self.todo={{ update=function() @@ -848,8 +778,7 @@ function player:update() if self.rope then self.rope:update() - local rx,ry=self:_rope_pos() - self.rope:drag_dst(rx,ry) + self.rope:drag_dst(self.x+self.px/8+0.5,self.y+self.py/8+0.1) local tdx,tdy=self.rope:tug_orientxy() if (tdx!=0) self.orientx=tdx @@ -866,6 +795,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 @@ -881,14 +811,6 @@ function player:_fall() if (self.fall_frame<10) self.fall_frame+=1 end -function player:_rope_pos() - local px=self.x*8+self.px+4 - local px2=px - local py=self.y*8+self.py+3 - local py2=py - return px,py,px2,py2 -end - function player:draw() local px=self.x*8+self.px local py=self.y*8+self.py @@ -956,18 +878,21 @@ rope={} rope.__index=rope function rope:new( - x,y,src_x,src_y,dx,dy + src_ax,src_ay,dst_ax,dst_ay,latch ) local r={ id=0, - src={x=src_x,y=src_y,todo={}}, - ancs={}, - dst={x=x,y=y,todo={}}, - state={name="cast",dx=dx,dy=dy}, - dirty=true, - latch=nil, - latch_frame=0, + anchors={ + {ax=src_ax,ay=src_ay,prev=nil,next=nil}, + {ax=dst_ax,ay=dst_ay,prev=nil,next=nil} + }, + state={name="cast",frame=0}, + latch=latch, } + r.src=r.anchors[1] + r.dst=r.anchors[2] + r.src.next=r.dst + r.dst.prev=r.src setmetatable(r,rope) return r end @@ -980,30 +905,12 @@ function rope:done() return self.state.name=="done" end -function rope:busy() - for i=0,#self.ancs+1 do - if (#(self:_anc(i).todo)>0) return true - end - return false -end - function rope:update() - local was_busy=self:busy() - for i=0,#self.ancs+1 do - local anc=self:_anc(i) - _apply(anc,anc.todo,i) - end - local is_busy=self:busy() - if (was_busy and not is_busy) self.dirty=true - if self.state.name=="cast" then - self:continue_cast() - elseif self.state.name=="latched" then - self.latch_frame+=1 - if self.latch_frame>=10 then - self.latch_frame=10 - end + self.state.frame+=1 + if (self.state.frame>=3) self.state={name="latched"} + elseif self.state.name=="latched" then if (self.latch==nil) wrongbleep:bleep(3) self:destroy() return if @@ -1011,18 +918,17 @@ function rope:update() self.latch.rec!=nil then self:drag_src( - self.latch.rec.px+self.latch.px_offset, - self.latch.rec.py+self.latch.py_offset + self.latch.rec.mx+0.5+self.latch.ax_offset, + self.latch.rec.my+0.5+self.latch.ay_offset ) - if #self.latch.rec.todo==0 then - if self.latch.rec.dead==true then - self:destroy() - end + if self.latch.rec.dead==true then + self:destroy() end end - if (not is_busy) self:_tidy_up_gen() + 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"} @@ -1036,41 +942,11 @@ function rope:destroy() self.state={name="destroy",frame=0} end -function rope:continue_cast() - local dx,dy=self.state.dx,self.state.dy - local x0=self.src.x - local y0=self.src.y - local x1=x0+dx - local y1=y0+dy - - for x,y in _rast( - x0,y0,x1,y1 - ) do - local latch= - level:get_latch(dx,dy,x,y) - - if latch!=nil or level:pcoll(x,y) then - self.latch=latch - self.state={name="latched"} - break - end - self.src={x=x,y=y,todo={},dirty=true} - end -end - -function rope:_reindex() - self.src.ix=0 - self.dst.ix=#self.ancs - for i,anc in ipairs(self.ancs) do - anc.ix=i - end -end - function rope:draw(artificial_dx,artificial_dy) local points,highlight=self:_tug(true) - if (self:busy()) highlight=nil if (self.state.name=="done") return local perc_to_show=1.0 + if (self.state.name=="cast") perc_to_show=self.state.frame/2 if (self.state.name=="destroy") perc_to_show=(1.0-self.state.frame/5)^2 points[#points]={x=points[#points].x+artificial_dx,y=points[#points].y+artificial_dy} @@ -1108,294 +984,347 @@ function rope:draw(artificial_dx,artificial_dy) end -- draw latch - if self.latch!=nil and perc_to_show>=1.0 then - local x,y=points[1].x,points[1].y + if self.latch!=nil and self.latch.rec and perc_to_show>=1.0 then + local x,y=self.latch.rec.px,self.latch.rec.py local ldx,ldy=self.latch.dx,self.latch.dy local color=8 if (highlight==0) color=12 if self.latch.dx==-1 and self.latch.dy==0 then - rectfill(x+1,y,x+3,y+1,color) + rectfill(x,y+3,x+2,y+4,color) elseif self.latch.dx==1 and self.latch.dy==0 then - rectfill(x-1,y,x-3,y+1,color) + rectfill(x+5,y+3,x+7,y+4,color) elseif self.latch.dx==0 and self.latch.dy==-1 then - rectfill(x,y+1,x-1,y+3,color) + rectfill(x+3,y,x+4,y+2,color) elseif self.latch.dx==0 and self.latch.dy==1 then - rectfill(x,y-1,x-1,y-3,color) + rectfill(x+3,y+5,x+4,y+7,color) end end - for i=0,#self.ancs+1 do - p=self:_anc(i) - local c=12 - if (p.dirty) c=13 - rectfill(p.x-1,p.y-1,p.x+1,p.y+1,c) - print(tostr(p.id)..":"..p.x..","..p.y..","..#p.todo,0,i*8,12) - end + -- debug --[[ - for _,p in pairs(level._anch) do - pset(p.x,p.y,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 + local n1=self.src + local sy=0 + while true do + if (n1==nil) break + local x=n1.ax*8 + local y=n1.ay*8 + if n1.associated_with then + if (n1.associated_with.adx>0) x-=1 + if (n1.associated_with.ady>0) y-=1 + end + rectfill(x-1,y-1,x+1,y+1,12) + print("ax="..n1.ax..",ay="..n1.ay,0,sy) + sy+=7 -function rope:_anc(i) - if (i==0) return self.src - if (i==#self.ancs+1) return self.dst - return self.ancs[i] + local n0=n1.prev + local n2=n1.next + if n0!=nil and n2!=nil then + if n1.associated_with then + local _,_,_,adx,ady=would_stick(n0.ax,n0.ay,n1.associated_with,n2.ax,n2.ay) + assert(adx==-1 or adx==0 or adx==1) + assert(ady==-1 or ady==0 or ady==1) + --assert(not (adx==0 and ady==0)) + + 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 + rectfill(x+2,y+2,x+4,y+4,2) + end + else + rectfill(x+2,y+2,x+4,y+4,4) + end + + n1=n1.next + end + + for _,p in pairs(level._anch) do + local x,y=p.ax*8,p.ay*8 + if (p.adx>0) x-=1 + if (p.ady>0) y-=1 + pset(x,y,11) + pset(x+p.adx,y,11) + pset(x,y+p.ady,11) + end + ]] end function rope:drag_dst(x,y) - self:drag(function() return #self.ancs+1 end,x,y) + self:drag(self.dst,x,y) end function rope:drag_src(x,y) - self:drag(function() return 0 end,x,y) + self:drag(self.src,x,y) end -function rope:drag( - i,x,y -) - local anc=self:_anc(i()) - local busy=self:busy() - - for x,y in _rast( - anc.x,anc.y,x,y - ) do - local a=self:_anc(i()) - if not (_point_eq(a,{x=x,y=y})) then - a.x=x - a.y=y - a.dirty=true - self.dirty=true - if (not busy) self:_tidy_up_gen() - end - end +function rope:drag(n1,ax_new,ay_new) + self:relax() + self:_drag(n1,ax_new,n1.ay) + self:_drag(n1,ax_new,ay_new) + self:relax() end -function rope:make_dirty(only_if_invalid) - local invalid=false - for a=0,#self.ancs do - local a0=self:_anc(a) - local a1=self:_anc(a+1) - if not level:can_stretch(a0,a1) then - a0.dirty=true - a1.dirty=true - invalid=true - end - end - if (invalid or not only_if_invalid) self.dirty=true -end - -function rope:_tidy_up_gen() - self:make_dirty(true) - - local busy=self:busy() - if (not self:latched()) return - if (not self.dirty) return - - local settled=true - local loop=function(f) - local a=0 - while a<=#self.ancs+1 do - local anc=self:_anc(a) - if anc.dirty then - anc.seen=true - if self[f](self,a,busy) then - settled=false anc.changed=true - a=0 - end - end - a+=1 - end - end - - local mark_unseen=function() - touched={} - for a=0,#self.ancs+1,1 do - local anc=self:_anc(a) - anc.seen=false - anc.changed=false - end - end - - local propagate_dirty=function(f) - for a=0,#self.ancs+1,1 do - local a1=self:_anc(a) - if a1.dirty then - local a0=self:_anc(a-1) - if (a0!=nil) a0.dirty=true - - local a2=self:_anc(a+1) - if (a2!=nil) a2.dirty=true - end - end - end +function rope:relax() + local n=self.src while true do - settled=true + if (n==nil) break - mark_unseen() - propagate_dirty() - - loop("_find_needed_anchors") - loop("_find_touched_anchors") - loop("_elide_point") - - for a=0,#self.ancs+1,1 do - local anc=self:_anc(a) - if (anc.seen) anc.dirty=anc.changed - if (anc.dirty) settled=false + if (n.associated_with) then + self:_drag(n,n.associated_with.ax,n.associated_with.ay) end - if (settled) break + n=n.next end - if (self:busy()) return + local n0=self.src + while true do + if (n0==nil) return + local n1=n0.next + if (n1==nil) return + local n2=n1.next + if (n2==nil) return - for i=0,#self.ancs do - local a0=self:_anc(i) - local a1=self:_anc(i+1) - if not level:can_stretch(a0,a1) then - self:destroy() - end - end + if n1.associated_with!=nil then - self.dirty=false -end + local x0,y0=n0.ax,n0.ay + local x1,y1=n1.ax,n1.ay + local x2,y2=n2.ax,n2.ay -function rope:_find_needed_anchors(i,busy) - if (i<=0) return false - if (#self.ancs+1"..tostring(n1.associated_with).."->"..tostring(n2.associated_with)) + self:_drag(n1,x1_new,y1_new) + n0=n1.prev + n2=n1.next + n0.next=n2 + n2.prev=n0 + n1.next=nil + n1.prev=nil + else n0=n0.next end + else n0=n0.next end end end -local ELIDE_POINT=0.01 -function rope:_find_touched_anchors(i) - if (i<=0) return false - if (#self.ancsx2) x0,y0,x2,y2=x2,y2,x0,y0 - local a0=self:_anc(i-1) - local a1=self:_anc(i) - local a2=self:_anc(i+1) + local dx=x2-x0 + local dy=y2-y0 - local level_anc=level:point_anchor(a1.x,a1.y) - if _point_eq(a0,a1) or _point_eq(a1,a2) or (not busy and level_anc==nil) then - -- do it unconditionally + local adx,ady + local x1_new,y1_new + if abs(dx)>abs(dy) then + local dprop=(x1-x0)/dx + x1_new,y1_new=x1,y0+dprop*(y2-y0) + ady=sgn0(y1_new-y1) + adx=0 + if (y0>y2) adx=ady + if (y0y2) ady=adx + if (y0"..tostring(n05.associated_with).."->"..tostring(n1.associated_with)) + n0.next=n05 + n1.prev=n05 + n0=n05 + end + + local n2=n1.next + while true do + if (n2==nil) break + local anch=_sweep_radar(n2.ax,n2.ay,ax1_old,ay1_old,ax1_new,ay1_new) + if (anch==nil) 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 + end end -function _linedist(x0,v,x1) - return 100 * (sum_distance(x0,v,x1)-distance(x0,x1))/distance(x0,x1) +function _stepfrom(x0,x1) + local done=false + if x0==x1 then + return function() + if (done) return nil + done=true return x0 + end + end + + 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) + return function() + if (done) return nil + i+=1 + if i>top then + done = true + if (x1!=flr(x1)) return mul*x1 + return nil + end + return mul*i + end end -function sum_distance(x,y,z) - return distance(x,y) + distance(y,z) +function _which_side(x,y,x0,y0,x1,y1) + return sgn0((x1-x0)*(y-y0) - (y1-y0)*(x-x0)) end function distance_dxy(dx,dy) @@ -1408,17 +1337,34 @@ function distance(p1,p2) return sqrt(dx*dx+dy*dy) end -function rope:collide_rect(x1,y1,x2,y2,exclude_src,exclude_dst) - local last=#self.ancs-exclude_dst - for i=exclude_src,last,1 do - local a0=self:_anc(i) - local a1=self:_anc(i+1) - 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 +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.4 + my0+=0.4 + mx1-=0.4 + my1-=0.4 + + while true do + 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 - return false end function _line_line(x1,y1,x2,y2,x3,y3,x4,y4) @@ -1435,44 +1381,29 @@ function _line_line(x1,y1,x2,y2,x3,y3,x4,y4) return true end -function neighbors(p) - local r={} - for dx=-1,1,1 do - for dy=-1,1,1 do - if dx!=0 or dy!=0 then - add(r,{x=p.x+dx,y=p.y+dy}) - end - end - end - return r -end - -->8 -- moved here because it's complicated function rope:tug_orientxy() - local a1=self:_anc(#self.ancs+1) - local a0=self:_anc(#self.ancs) - local dx=a0.x-a1.x + local a1=self.dst + local a0=self.dst.prev + local dx=a0.ax-a1.ax local tdx=0 - if (dx>3) tdx=1 - if (dx<-3) tdx=-1 + if (dx>3/8) tdx=1 + if (dx<-3/8) tdx=-1 - local dy=a0.y-a1.y + local dy=a0.ay-a1.ay local tdy=0 if abs(dy)>abs(dx)/2 then - if (dy>3) tdy=1 - if (dy<-3) tdy=-1 + if (dy>3/8) tdy=1 + if (dy<-3/8) tdy=-1 end return tdx,tdy end function rope:tug() - self:_tidy_up_gen() if (not self:latched()) return - local rc=self:_tug() - self:_tidy_up_gen() - return rc + return self:_tug() end function rope:_tug(hypothetically) @@ -1480,12 +1411,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 @@ -1495,39 +1426,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 @@ -1568,12 +1469,7 @@ function rope:_tug(hypothetically) then if (hypothetically) return ancs,0 - level:tug_crate( - mx0,my0, - dmx,dmy - ) - -- be busy for 4 ticks while the crate moves - self:_anc(0).todo={{},{},{},{},{}} + level:tug_crate(mx0,my0,dmx,dmy) return true end end @@ -1642,7 +1538,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 @@ -1650,42 +1545,50 @@ 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() -- todo: cache this - self:_reindex() - local points={} local _slope = function(p0,p1) return atan2(p1.y-p0.y,p1.x-p0.x) end - for i=0,#self.ancs+1,1 do - local anc=self:_anc(i) + a=self.src + while a!=nil do + 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,anc) + add(points,point) elseif abs( _slope(points[#points-1],points[#points])- - _slope(points[#points],anc) + _slope(points[#points],point) )==0 then -- epsilon? - points[#points]=anc + points[#points]=point else - add(points,anc) + add(points,point) end + a=a.next end - return points end