local i=game:GetService("\080\108\097\121\101\114\115")local M=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local D=game:GetService("\087\111\114\107\115\112\097\099\101")local p=M:WaitForChild("\108\105\098")local x=M:WaitForChild("\099\111\109\109\111\110")local K=require(p:WaitForChild("\082\069\067\083"))local o=require(x:WaitForChild("\082\101\099\115\067\111\109\112\111\110\101\110\116\115"))local j=K.System:extend("\083\112\105\110\110\121\083\121\115\116\101\109")local w=180 local R=128 local U=128 local function e(i,M,D)local p=i-M return(p.X*p.X+p.Y*p.Y)+p.Z*p.Z<=D*D end function j.getTile(D,i,M)if not D.tiles[i]then return end if not D.tiles[i][M]then return end return D.tiles[i][M]end function j.addToTile(x,i,M,D,p)if not x.tiles[i]then x.tiles[i]={}end if not x.tiles[i][M]then x.tiles[i][M]={}end x.spinnyIndex[D]=Vector2.new(i,M)x.tiles[i][M][D]=p end function j.removeFromTile(p,i,M,D)if not p.tiles[i]then return end if not p.tiles[i][M]then return end p.tiles[i][M][D]=nil end function j.addSpinny(D,i,M)M.originalCFrame=i.CFrame M.timeOffset=(math.random()*2)*math.pi local p=i.CFrame.p local x=math.floor(p.X/U)local K=math.floor(p.Z/U)D:addToTile(x,K,i,M)end function j.init(i)i.tiles={}i.spinnyIndex={}i.maid.componentAdded=(i.core:getComponentAddedSignal(o.Spinny)):Connect(function(M,D)i:addSpinny(M,D)end)i.maid.componentRemoving=(i.core:getComponentRemovingSignal(o.Spinny)):Connect(function(M,D)local p=i.spinnyIndex[M]i:removeFromTile(p.X,p.Y,M)end)for M,D in i.core:components(o.Spinny)do i:addSpinny(M,D)end end function j.doAnimationStep(D,i,M)local p=tick()+M.timeOffset local x=math.sin(p%(math.pi*2))local K=(p/2)%(math.pi*2)x=x*x i.CFrame=i.CFrame:lerp((M.originalCFrame*CFrame.new(0,x*1,0))*CFrame.Angles(0,K,0),.1)end function j.step(i)local M=0 local p=D.CurrentCamera local x=p.CFrame.p local K=math.floor(x.X/U)local o=math.floor(x.Z/U)for D=-1,1,1 do for p=-1,1,1 do local j=K+D local U=o+p local d=i:getTile(j,U)if d then for D,p in pairs(d)do local K=p.originalCFrame.p local o=e(x,K,w)if o and M<R then M=M+1 i:doAnimationStep(D,p)elseif M>=R then return end end end end end end return j
-- increments the score of players that touch CoinTrigger instances, destroys those instances when touched.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local lib = ReplicatedStorage:WaitForChild("lib")
local common = ReplicatedStorage:WaitForChild("common")

local RECS = require(lib:WaitForChild("RECS"))
local Components = require(common:WaitForChild("RecsComponents"))

local SpinnySystem = RECS.System:extend("SpinnySystem")

local ANIM_THRESHOLD = 180
local MAX_SPINNY_PER_FRAME = 128
local TILE_SIZE = 128

local function pointsCloserThan(p1,p2,dist)
    local delta = p1-p2

    return ((delta.X*delta.X) + (delta.Y*delta.Y) + (delta.Z*delta.Z)) <= dist*dist
end

function SpinnySystem:getTile(x,y)
    if not self.tiles[x] then
        return
    end
    if not self.tiles[x][y] then
        return
    end

    return self.tiles[x][y]
end

function SpinnySystem:addToTile(x,y,instance,coin)
    if not self.tiles[x] then
        self.tiles[x] = {}
    end
    if not self.tiles[x][y] then
        self.tiles[x][y] = {}
    end
    self.spinnyIndex[instance] = Vector2.new(x,y)
    self.tiles[x][y][instance] = coin
end

function SpinnySystem:removeFromTile(x,y,instance)
    if not self.tiles[x] then
        return
    end
    if not self.tiles[x][y] then
        return
    end

    self.tiles[x][y][instance] = nil
end

function SpinnySystem:addSpinny(instance,coin)
    coin.originalCFrame = instance.CFrame
    coin.timeOffset = math.random() * 2 * math.pi
    local coinPos = instance.CFrame.p
    local tileX = math.floor(coinPos.X/TILE_SIZE)
    local tileY = math.floor(coinPos.Z/TILE_SIZE)

    self:addToTile(tileX,tileY,instance,coin)
end

function SpinnySystem:init()
    self.tiles = {}
    self.spinnyIndex = {}

    self.maid.componentAdded =
        self.core:getComponentAddedSignal(Components.Spinny):Connect(
            function(instance, spinny)
                self:addSpinny(instance,spinny)
            end)

    self.maid.componentRemoving =
    self.core:getComponentRemovingSignal(Components.Spinny):Connect(
        function(instance, spinny)
            local tileCoords = self.spinnyIndex[instance]
            self:removeFromTile(tileCoords.X,tileCoords.Y, instance)
        end)

    for instance, spinny in self.core:components(Components.Spinny) do
        self:addSpinny(instance,spinny)
    end
end

function SpinnySystem:doAnimationStep(instance,spinny)
    local time = tick() + spinny.timeOffset
    local sinVal = math.sin(time % (math.pi*2))
    local rotation = (time/2) % (math.pi*2)

    sinVal = sinVal * sinVal

    instance.CFrame = instance.CFrame:lerp(
        spinny.originalCFrame *
        CFrame.new(0,sinVal*1,0) *
        CFrame.Angles(0,rotation,0),
        0.1
    )
end

function SpinnySystem:step()
    local coinsAnimated = 0
    local camera = Workspace.CurrentCamera
    local camPos = camera.CFrame.p

    local camTileX = math.floor(camPos.X/TILE_SIZE)
    local camTileY = math.floor(camPos.Z/TILE_SIZE)

    for x = -1,1 do
        for y = -1,1 do
            local tileX = camTileX+x
            local tileY = camTileY+y

            local tile = self:getTile(tileX,tileY)
            if tile then
                for instance, spinny in pairs(tile) do
                    local pos = spinny.originalCFrame.p
                    local shouldSpin = pointsCloserThan(camPos, pos, ANIM_THRESHOLD)
                    if shouldSpin and coinsAnimated < MAX_SPINNY_PER_FRAME then
                        coinsAnimated = coinsAnimated + 1
                        self:doAnimationStep(instance, spinny)
                    elseif coinsAnimated >= MAX_SPINNY_PER_FRAME then
                        return
                    end
                end
            end
        end
    end
end

return SpinnySystem