local A=game:GetService("\067\111\108\108\101\099\116\105\111\110\083\101\114\118\105\099\101")local v=game:GetService("\080\108\097\121\101\114\115")local Y=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local i=Y:WaitForChild("\099\111\109\109\111\110")local C=Y:WaitForChild("\108\105\098")local R=require(C:WaitForChild("\080\114\111\109\105\115\101"))local M=require(C:WaitForChild("\080\105\122\122\097\065\108\112\097\099\097"))local k=require(i:WaitForChild("\065\099\116\105\111\110\115"))local H=require(i:WaitForChild("\083\101\108\101\099\116\111\114\115"))local c=require(C:WaitForChild("\083\105\103\110\097\108"))local p=M.GameModule:extend("\067\111\105\110\115")local f={default={value=1,respawnTime=60},redcoin={value=5;respawnTime=120};bluecoin={value=10;respawnTime=180};purplecoin={value=25,respawnTime=240},greencoin={value=50;respawnTime=300},silvercoin={value=100,respawnTime=400};crystalcoin={value=250;respawnTime=600};cosmiccoin={value=500;respawnTime=1200}}local P={coin_spawn="\100\101\102\097\117\108\116",redcoin_spawn="\114\101\100\099\111\105\110";bluecoin_spawn="\098\108\117\101\099\111\105\110";greencoin_spawn="\103\114\101\101\110\099\111\105\110";purplecoin_spawn="\112\117\114\112\108\101\099\111\105\110";silvercoin_spawn="\115\105\108\118\101\114\099\111\105\110";crystalcoin_spawn="\099\114\121\115\116\097\108\099\111\105\110";cosmiccoin_spawn="\099\111\115\109\105\099\099\111\105\110"}local function E(v)local Y=A:GetTags(v)for A,v in pairs(Y)do if P[v]then return P[v]end end end function p.create(A)A.coinCollections={}A.coinSpawns={}A.coinsInLast5Secs={}end function p.onPlayerJoin(v,A)v.coinCollections[A]={}end function p.onPlayerRemoving(v,A)v.coinCollections[A]=nil end function p.bindCoinRespawn(i,A,v,Y)spawn(function()wait(Y.respawnTime or 0)if i.coinCollections[A]then i.coinCollections[A][v]=false i.api:coinRespawn(A,v)end end)end function p.requestCoinCollect(Y,A,v)assert(typeof(v)=="\073\110\115\116\097\110\099\101","\097\114\103 \050 \109\117\115\116 \098\101 \097 \112\097\114\116\044 \103\111\116\058"..tostring(v))assert(v:IsA("\066\097\115\101\080\097\114\116"),"\097\114\103 \050 \109\117\115\116 \098\101 \097 \112\097\114\116\044 \103\111\116\058"..tostring(v))assert(Y.coinSpawns[v],"\073\110\118\097\108\105\100 \099\111\105\110 \115\112\097\119\110")if not Y.coinCollections[A][v]then Y.coinCollections[A][v]=true local i=A.Character if not i then return end local C=i:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if not C then return end local R=(C.Position-v.Position).Magnitude if R>40 then return end local M=v:FindFirstChild("\115\112\097\119\110\067\111\110\102\105\103")if M then M=require(M)else local A=E(v)if not A then return end M=f[A or"\100\101\102\097\117\108\116"]end Y.store:dispatch(k.COIN_ADD(A,M.value or 1))local c=Y.store:getState()p.coinCollected:fire(A,H.getCoins(c,A))Y.coinsInLast5Secs[A]=(Y.coinsInLast5Secs[A]or 0)+1 if Y.coinsInLast5Secs[A]>30 then A:Kick("\067\111\108\108\101\099\116\105\110\103 \099\111\105\110\115 \116\111\111 \102\097\115\116 \058\040 \084\104\101 \115\101\114\118\101\114 \109\105\103\104\116 \098\101 \108\097\103\103\105\110\103\046 \079\114 \097\114\101 \121\111\117 \099\104\101\097\116\105\110\103\063")end Y:bindCoinRespawn(A,v,M)end end function p.init(Y)p.coinCollected=c.new()for v,i in pairs(P)do for A,v in pairs(A:GetTagged(v))do Y.coinSpawns[v]=v end end for A,v in pairs(v:GetPlayers())do Y:onPlayerJoin(v)end v.PlayerAdded:Connect(function(A)Y:onPlayerJoin(A)end)v.PlayerRemoving:Connect(function(A)Y:onPlayerRemoving(A)end)spawn(function()while true do Y.coinsInLast5Secs={}wait(5)end end)end function p.postInit(A)local v=A.core:getModule("\083\101\114\118\101\114\065\112\105")local Y=A.core:getModule("\083\116\111\114\101\067\111\110\116\097\105\110\101\114")local function i(v,Y)A.api=v A.store=Y end;(R.all({v:getApi(),Y:getStore()})):andThen(function(A)return R.async(function(v,Y)i(unpack(A))end)end)end return p
-- server side coin tracking

local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

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

local Promise = require(lib:WaitForChild("Promise"))
local PizzaAlpaca = require(lib:WaitForChild("PizzaAlpaca"))
local Actions = require(common:WaitForChild("Actions"))
local Selectors = require(common:WaitForChild("Selectors"))
local Signal = require(lib:WaitForChild("Signal"))

local Coins = PizzaAlpaca.GameModule:extend("Coins")

local coinTypes = {
    default = {
        value = 1,
        respawnTime = 60,
    },
    redcoin = {
        value = 5,
        respawnTime = 120,
    },
    bluecoin = {
        value = 10,
        respawnTime = 180,
    },
    purplecoin = {
        value = 25,
        respawnTime = 240,
    },
    greencoin = {
        value = 50,
        respawnTime = 300,
    },
    silvercoin = {
        value = 100,
        respawnTime = 400,
    },
    crystalcoin = {
        value = 250,
        respawnTime = 600,
    },
    cosmiccoin = {
        value = 500,
        respawnTime = 1200,
    },
}

local tagCoinTypes = {
    coin_spawn = "default",
    redcoin_spawn = "redcoin",
    bluecoin_spawn = "bluecoin",
    greencoin_spawn = "greencoin",
    purplecoin_spawn = "purplecoin",
    silvercoin_spawn = "silvercoin",
    crystalcoin_spawn = "crystalcoin",
    cosmiccoin_spawn = "cosmiccoin",
}

local function getSpawnerType(instance)
    local tags = CollectionService:GetTags(instance)

    for _,tag in pairs(tags) do
        if tagCoinTypes[tag] then
            return tagCoinTypes[tag]
        end
    end
end

function Coins:create()
    self.coinCollections = {}
    self.coinSpawns = {}
    self.coinsInLast5Secs = {}
end

function Coins:onPlayerJoin(player)
    self.coinCollections[player] = {}
end

function Coins:onPlayerRemoving(player)
    self.coinCollections[player] = nil
end

function Coins:bindCoinRespawn(player,coinPart, coinType)
    spawn(function()
        wait(coinType.respawnTime or 0)
        if self.coinCollections[player] then
            self.coinCollections[player][coinPart] = false
            self.api:coinRespawn(player,coinPart)
        end
    end)
end

function Coins:requestCoinCollect(player,coinPart)
    assert(typeof(coinPart) == "Instance", "arg 2 must be a part, got:"..tostring(coinPart))
    assert(coinPart:IsA("BasePart"), "arg 2 must be a part, got:"..tostring(coinPart))
    assert(self.coinSpawns[coinPart], "Invalid coin spawn")

    if not self.coinCollections[player][coinPart] then
        self.coinCollections[player][coinPart] = true

        local char = player.Character
        if not char then return end
        local root = char:FindFirstChild("HumanoidRootPart")
        if not root then return end

        local dist = (root.Position-coinPart.Position).Magnitude

        if dist > 40 then
            return -- too far away :O
        end

        local spawnConfig = coinPart:FindFirstChild("spawnConfig")
        if spawnConfig then
            spawnConfig = require(spawnConfig)
        else
            local spawnTag = getSpawnerType(coinPart)
            if not spawnTag then return end
            spawnConfig = coinTypes[spawnTag or "default"]
        end

        self.store:dispatch(Actions.COIN_ADD(player,spawnConfig.value or 1))
        local state = self.store:getState()
        Coins.coinCollected:fire(player,Selectors.getCoins(state,player))

        self.coinsInLast5Secs[player] = (self.coinsInLast5Secs[player] or 0) + 1
        if self.coinsInLast5Secs[player] > 30 then
            player:Kick("Collecting coins too fast :( The server might be lagging. Or are you cheating?")
        end

        self:bindCoinRespawn(player,coinPart,spawnConfig)
    end
end

function Coins:init()
    Coins.coinCollected = Signal.new()

    for tagname,_ in pairs(tagCoinTypes) do
        for _, instance in pairs(CollectionService:GetTagged(tagname)) do
            self.coinSpawns[instance] = instance
        end
    end

    for _,player in pairs(Players:GetPlayers()) do
		self:onPlayerJoin(player)
	end

    Players.PlayerAdded:Connect(function(player) self:onPlayerJoin(player) end)
    Players.PlayerRemoving:Connect(function(player) self:onPlayerRemoving(player) end)

    -- every 5 secs clear the coin collected table
    spawn(function()
        while true do
            self.coinsInLast5Secs = {}
            wait(5)
        end
    end)
end

function Coins:postInit()
    local apiWrapper = self.core:getModule("ServerApi")
    local storeContainer = self.core:getModule("StoreContainer")

    local function onAll(api, store)
        self.api = api
        self.store = store
    end

    Promise.all({
        apiWrapper:getApi(),
        storeContainer:getStore()
    }):andThen(function(resolved)
        return Promise.async(function(resolve,reject)
            onAll(unpack(resolved))
        end)
    end)
end

return Coins