--Made By Maticzpl --KEYS: -- CTRL + T Create a new animation / Finish animating -- CTRL + ENTER Edit existing animation -- , (comma) Add new empty frame -- . (dot) Duplicate current frame -- Left / Right Arrow - Navigate frames -- Backspace - Delete current frame if GooAnim then return end GooAnim = { started = false, frameCount = 0, currentFrame = 0, frameInterval = 5, usedType = 0, safeLimit = 0, loadingMode = false, partsPrev = 999999, partsNow = 999999, animSize = {x = 0,y = 0}, offset = {x = 10,y = 10}, frameData = {}, version = 2 } local g = GooAnim function GooAnim.createAnimation(sizex,sizey) if g.started then local res = tpt.confirm("Start new animation?", "Do you want to abandon the current animation and start a new one?","Yes") if not res then return end end tpt.set_pause(1) tpt.decorations_enable(1) g.frameCount = 1 g.frameData = {} g.animSize.x = sizex g.animSize.y = sizey g.started = true sim.createBox( g.offset.x, g.offset.y, g.offset.x + g.animSize.x, g.offset.y + g.animSize.y, g.usedType ) end -- function GooAnim.showFrameCounter() -- print("Frame: "..g.currentFrame.. " / "..(g.frameCount - 1).." (max "..(g.safeLimit - 1)..")") -- end function GooAnim.saveFrame(remove) local frame = {} for x = g.offset.x, g.animSize.x + g.offset.x, 1 do for y = g.offset.y, g.animSize.y + g.offset.y, 1 do local rx = x - g.offset.x local ry = y - g.offset.y local part = sim.partID(x,y) if part == nil then print(x,y) end local deco = sim.partProperty(part,'dcolour') if frame[ry] == nil then frame[ry] = {} end frame[ry][rx] = deco if remove then sim.partKill(part) end end end g.frameData[g.currentFrame] = frame end function GooAnim.loadFrame(frameIndex) for x = g.offset.x, g.animSize.x + g.offset.x, 1 do for y = g.offset.y, g.animSize.y + g.offset.y, 1 do local rx, ry = x - g.offset.x , y - g.offset.y local frame = g.frameData[frameIndex] if frame[ry] == nil or frame[ry][rx] == nil then local part = sim.partCreate(-3,x,y,elem.DEFAULT_PT_BRCK) else local deco = frame[ry][rx] local part = sim.partCreate(-3,x,y,g.usedType) sim.partProperty(part,'dcolour',deco) end end end end -- Thanks to mad-cow for this function! local function GetAllPartsInRegion(x1, y1, x2, y2) -- builts a map of particles in a region. -- WARN: Misbehaves if partile order hasn't been reloaded since it relies on sim.parts() -- Save the returned value and provide it to .GetAllPartsInPos(x,y,region) to reduce computational complexity -- or you can just index the returned value youself, idc local result = {} local width = sim.XRES if x2 < x1 then x1,x2 = x2,x1 end if y2 < y1 then y1,y2 = y2,y1 end for part in sim.parts() do local px, py = sim.partPosition(part) px = math.floor(px+0.5) -- Round pos py = math.floor(py+0.5) local idx = math.floor(px + (py * width)) if px >= x1 and px <= x2 and py >= y1 and py <= y2 then if not result[idx] then result[idx] = {} end table.insert(result[idx], part) end end return result end -- Yes, i did just copy those from my subframe chipmaker script local function ReorderParticles() local particles = {} local width = sim.XRES for part in sim.parts() do local x = math.floor(sim.partProperty(part,'x') + 0.5); local y = math.floor(sim.partProperty(part,'y') + 0.5); local particleData = {} particleData.type = sim.partProperty(part,'type'); particleData.temp = sim.partProperty(part,'temp'); particleData.ctype = sim.partProperty(part,'ctype'); particleData.tmp = sim.partProperty(part,'tmp'); particleData.tmp2 = sim.partProperty(part,'tmp2'); particleData.tmp3 = sim.partProperty(part,'pavg0'); particleData.tmp4 = sim.partProperty(part,'pavg1'); particleData.life = sim.partProperty(part,'life'); particleData.vx = sim.partProperty(part,'vx'); particleData.vy = sim.partProperty(part,'vy'); particleData.dcolour = sim.partProperty(part,'dcolour'); particleData.flags = sim.partProperty(part,'flags'); local index = math.floor(x + (y * width)) if particles[index] == nil then particles[index] = {} end table.insert(particles[index],particleData) --particles[index][#particles[index]] = particleData sim.partKill(part) end for i = sim.XRES * sim.YRES, 0, -1 do local stack = particles[i] if stack ~= nil then for j = #stack, 1, -1 do local part = stack[j] local x = math.floor(i % sim.XRES) local y = math.floor((i - x) / sim.XRES) local id = sim.partCreate(-3,x,y,28) sim.partProperty(id,'type',part.type); sim.partProperty(id,'temp',part.temp); sim.partProperty(id,'ctype',part.ctype); sim.partProperty(id,'tmp',part.tmp); sim.partProperty(id,'tmp2',part.tmp2); sim.partProperty(id,'pavg0',part.tmp3); sim.partProperty(id,'pavg1',part.tmp4); sim.partProperty(id,'life',part.life); sim.partProperty(id,'vx',part.vx); sim.partProperty(id,'vy',part.vy); sim.partProperty(id,'dcolour',part.dcolour) sim.partProperty(id,'flags',part.flags); end end end end local function ArrayGCD(arr) local function gcd(a, b) if (a == 0) then return b; end return gcd(b % a, a); end local result = arr[1]; for i = 2, #arr, 1 do result = gcd(arr[i], result); if(result == 1) then return 1; end end return result; end function GooAnim.toFrame(frameIndex) if frameIndex < 0 or frameIndex >= GooAnim.frameCount then return end GooAnim.saveFrame(true) g.currentFrame = frameIndex GooAnim.loadFrame(frameIndex) end function GooAnim.nextFrame(clone,placeAfterCurrent) GooAnim.saveFrame(not clone) if placeAfterCurrent then g.currentFrame = g.currentFrame + 1 for i = g.frameCount - 1, g.currentFrame, -1 do g.frameData[i+1] = g.frameData[i] end else g.currentFrame = g.frameCount end if not clone then sim.createBox( g.offset.x, g.offset.y, g.offset.x + g.animSize.x, g.offset.y + g.animSize.y, g.usedType ) end g.frameCount = g.frameCount + 1 end function GooAnim.deleteFrame(frameIndex) if g.frameCount < 2 then print("Can't delete this frames") return end if g.currentFrame >= g.frameCount - 1 then g.currentFrame = g.currentFrame - 1 end g.frameCount = g.frameCount - 1 if frameIndex == 0 then for i = 0, g.frameCount - 1, 1 do g.frameData[i] = g.frameData[i+1] end g.frameData[g.frameCount] = nil else table.remove(g.frameData,frameIndex) end sim.clearRect(g.offset.x,g.offset.y,g.animSize.x+1,g.animSize.y+1) g.loadFrame(g.currentFrame) end function GooAnim.finishAnimation() local confirm = tpt.confirm("Finish animation?", "Do you want to finish the animation?","Yes") if not confirm then return end GooAnim.saveFrame() sim.clearSim() sim.createWallBox(g.offset.x, g.offset.y, g.animSize.x + g.offset.x, g.animSize.y + g.offset.y, 12) for y = g.offset.y, g.animSize.y + g.offset.y, 1 do for x = g.offset.x, g.animSize.x + g.offset.x, 1 do for i = g.frameCount - 1, 0 , -1 do local frame = g.frameData[i] if frame ~= nil then local rx = x - g.offset.x local ry = y - g.offset.y local deco = frame[ry][rx] local part = nil local prev = 1 if i-prev >= 0 then while true do while i-prev >= 0 and g.frameData[i-prev] == nil do prev = prev + 1 end if i-prev < 0 then part = sim.partCreate(-3,x,y,g.usedType) sim.partProperty(part,'dcolour',deco) break end if g.frameData[i-prev][ry][rx] == deco then break else part = sim.partCreate(-3,x,y,g.usedType) sim.partProperty(part,'dcolour',deco) break end end else part = sim.partCreate(-3,x,y,g.usedType) sim.partProperty(part,'dcolour',deco) end if part ~= nil then local lasting = 1 if g.frameData[i+lasting] ~= nil then while g.frameData[i+lasting][ry][rx] == deco do lasting = lasting + 1 if g.frameData[i+lasting] == nil then break end end end sim.partProperty(part,'life',(i + lasting) * g.frameInterval); end end end end end GooAnim.started = false sim.takeSnapshot() sim.saveStamp(g.offset.x,g.offset.y,g.animSize.x,g.animSize.y) print("Animation saved in stamps") end function GooAnim.loadAnimation() print("Loading animation") g.offset = nil local furthestX = 0 local furthestY = 0 local maxLife = 0 local allLifeValues = {} for part in sim.parts() do local x,y = sim.partPosition(part) x = math.floor(x) y = math.floor(y) if sim.partProperty(part,'life') > maxLife then maxLife = sim.partProperty(part,'life') end table.insert(allLifeValues,sim.partProperty(part,'life')) if x > furthestX then furthestX = x end if y > furthestY then furthestY = y end if g.offset == nil then g.offset = {x=x,y=y} end end g.frameCount = 0 g.animSize.x = furthestX - g.offset.x g.animSize.y = furthestY - g.offset.y g.started = true g.frameData = {} g.safeLimit = math.floor(235008 / (tonumber(g.animSize.x) * tonumber(g.animSize.y))) ReorderParticles() local stacks = GetAllPartsInRegion(g.offset.x,g.offset.y,furthestX,furthestY) g.frameInterval = ArrayGCD(allLifeValues) g.frameCount = maxLife / g.frameInterval for k, stack in pairs(stacks) do local rx, ry = sim.partPosition(stack[0] or stack[1]) rx = math.floor(rx + 0.5) - g.offset.x ry = math.floor(ry + 0.5) - g.offset.y local frames = 0 local offset = 0 for j, part in pairs(stack) do offset = offset - 1 local part = stack[#stack - j + 1] g.usedType = sim.partProperty(part,'type') local life = sim.partProperty(part,'life') local toSkip = math.floor(life / g.frameInterval) - j - (offset + 1) for s = 0, toSkip, 1 do offset = offset + 1 if g.frameData[j-1+offset] == nil then g.frameData[j-1+offset] = {} end if g.frameData[j-1+offset][ry] == nil then g.frameData[j-1+offset][ry] = {} end if g.frameData[j-1+offset][ry][rx] == nil then g.frameData[j-1+offset][ry][rx] = sim.partProperty(part,'dcolour') end end end if frames > g.frameCount then g.frameCount = frames end end sim.clearSim() local prevInterval = g.frameInterval g.frameInterval = tonumber(tpt.input("Enter New Frame Interval","Do you want to change the frame interval?",g.frameInterval)) if g.frameInterval == nil then g.frameInterval = prevInterval end g.currentFrame = 0 GooAnim.loadFrame(0) end local function bindKeys() event.register(event.keypress, function(key,scan,rpt,shift,ctrl,alt) if shift or alt then return end if GooAnim.started and not ctrl then if key == 44 and not rpt then -- < to next frame local afterCurrent = false if g.currentFrame ~= g.frameCount - 1 then afterCurrent = tpt.confirm("Where to put the frame?","Click 'Cancel' to put the frame as a new frame at the end. Click 'Ok' to insert a new frame after this one.","Ok") end GooAnim.nextFrame(false,afterCurrent) return false end if key == 46 and not rpt then -- > to clone frame local afterCurrent = false if g.currentFrame ~= g.frameCount - 1 then afterCurrent = tpt.confirm("Where to put the frame?","Click 'Cancel' to put the frame as a new frame at the end. Click 'Ok' to insert a new frame after this one.","Ok") end GooAnim.nextFrame(true,afterCurrent) return false end if key == 1073741903 then -- right arrow GooAnim.toFrame(GooAnim.currentFrame + 1 ) end if key == 1073741904 then -- left arrow GooAnim.toFrame(GooAnim.currentFrame - 1 ) end if key == 8 and not rpt then --Backspace GooAnim.deleteFrame(g.currentFrame) end end if key == 13 and ctrl and not GooAnim.started then -- CTRL + ENTER if tpt.confirm("Is the simulation clear?","Make sure the simulation is clear of everything except the animation you want to edit.","Yes") then GooAnim.loadAnimation() end end if key == 116 and ctrl then --CTRL + T to Start / Stop animation if GooAnim.started then GooAnim.finishAnimation() else if not tpt.confirm("New animation", "The whole simulation will be cleared, proceed?","Yes") then return end sim.clearSim() local x = tpt.input("Enter Width","Enter the target resolution for your animation",25) if x == "" then return end local y = tpt.input("Enter Height","Enter the target resolution for your animation",math.floor((x / 16) * 9)) if y == "" then return end GooAnim.frameInterval = tpt.input("Enter Frame Interval","Enter the delay (in game frames) between an animation frame changes",5) local type = tpt.input("Enter Type","What particle type to use (other types than GOO can break the script)","GOO") type = string.upper(type) if elem["DEFAULT_PT_"..type] ~= nil then GooAnim.usedType = elem["DEFAULT_PT_"..type] else tpt.message_box("Wrong Type","The type '"..type.."' is not found") return end -- part limit around 235008 GooAnim.safeLimit = math.floor(235008 / (tonumber(x) * tonumber(y))) if tonumber(x) * tonumber(y) >= 40000 then tpt.message_box("Warning","Big resolution animations may exceed the particle limit") end GooAnim.createAnimation(x,y) end return false end end) event.register(event.tick, function () if g.started then local text = "Frame: "..g.currentFrame.. " / "..(g.frameCount - 1).." (max "..(g.safeLimit - 1).." worst case scenario)" local w,h = gfx.textSize(text) gfx.drawText(sim.XRES / 2 - (w / 2),sim.YRES - 50,text,255,255,255,255) local text = "[,] - New Empty Frame [.] - Clone Frame [CTRL + T] - Finish Animating" local w,h = gfx.textSize(text) gfx.drawText(sim.XRES / 2 - (w / 2),sim.YRES - 35,text,255,255,255,255) local text = "[<- / ->] - Change current frame [Backspace] - Delete current frame" local w,h = gfx.textSize(text) gfx.drawText(sim.XRES / 2 - (w / 2),sim.YRES - 20,text,255,255,255,255) end end) end bindKeys() -- TODO: -- Accurate max frame limit