local g=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local w=g:WaitForChild("\077\111\100\117\108\101\115")local v=require(w.src.utils.Logger)local A=game:GetService("\077\097\114\107\101\116\112\108\097\099\101\083\101\114\118\105\099\101")local C=game:GetService("\080\108\097\121\101\114\115")local Q=require(w.M)local t=require(w.Rodux)local c=require(4664437268)local U=require(c.ZoneService)local B=require(w.src.thunks.equipPlayer)local S=require(w.src.actions.toClient.clientSlotsCount)local T=require(w.src.actions.toClient.clientStartGame)local R=require(w.src.actions.toClient.clientReset)local O=require(w.src.GlobalConfig)local W=require(w.src.commonReducers)local F=require(w.src.utils.Dict)local H=require(w.src.DeveloperProducts)local N=require(w.src.actions.toClient.clientSendNotification)local s=require(w.src.assets)local X=require(w.src.thunks.connectPlayer)local f=require(w.src.thunks.playerEnteredRoom)local I=require(w.src.thunks.playerFinishedRoom)local x=require(w.src.thunks.startRoomGameLoop)local o=require(w.src.thunks.startInfiniteGame)local n=require(w.src.actions.rooms.removePlayerFromRoom)local l=require(w.src.actions.rooms.playerDied)local j=require(w.src.actions.rooms.addVoteToRoom)local p=require(w.src.actions.inventory.addItemsToPlayerInventory)local L=require(w.src.actions.inventory.removeItemFromPlayerInventory)local u=require(script.Parent.serverReducers)local Y=require(script.Parent.ServerApi)local V=require(script.Parent.networkMiddleware)local q=require(w.src.objects.InventoryObjects)local k=q.BuyObjects local i=require(w.src.GameDatastore)local e=require(w.src.GamePasses)local m=require(w.src.GhostAbility)local E=require(w.src.RoomManager)local z=require(w.src.FallTriggerManager)local a=require(w.src.SoundTriggerManager)local P=require(w.src.LogicTriggerManager)m:initCollisionGroup()E:initCollisionGroups()local M=game.Workspace:WaitForChild("\080\108\097\099\101")local y=require(w.src.Leaderboards)local b=require(w.src.actions.leaderboards.addLeaderboardItems)local h=M:findFirstChild("\082\111\111\109\115")local Z=require(w.src.TagItem)local d=require(w.src.TagItemOnce)return function(g)local w=t.combineReducers(F.join(W,u))local c local function M(g,w,v)local A=F.join(g,{replicated=true})if g.replicateBroadcast then return c:storeAction(Y.AllPlayers,A)end if g.replicateTo~=nil then local w=C:GetPlayerByUserId(g.replicateTo)if w==nil then return end return c:storeAction(w,A)end for g in pairs(W)do if w[g]~=v[g]then return c:storeAction(Y.AllPlayers,A)end end return end local function d(w)return function(v)table.insert(g.savedActions,v)return w(v)end end local r=nil for g,v in ipairs(g.savedActions)do r=w(r,v)end local K={t.thunkMiddleware;d,V(M)}local D=t.Store.new(w,r,K)local G=function(g)D:dispatch(X(g))local w=D:getState()local A={}for g,w in pairs(w)do if W[g]~=nil then A[g]=w end end v:i("\067\108\105\101\110\116 \105\115 \115\116\097\114\116\105\110\103\046 \073\110\105\116\105\097\108\105\122\105\110\103 \115\116\111\114\101 \097\110\100 \068\097\116\097\115\116\111\114\101\046")c:initialStoreState(g,A)local function C(w)v:d("\068\097\116\097\083\116\111\114\101\058\080\101\116 \069\113\117\105\112\112\101\100 \115\116\097\116\117\115 \117\112\100\097\116\101\100 \116\111\058 ",w)D:dispatch(B(g,w))end C(i:getEquippedPets(g))i:onEquippedPetsUpdated(g,C)local function t(w)v:d("\068\097\116\097\083\116\111\114\101\058 \083\108\111\116\115 \117\112\100\097\116\101\100\058 ",w)D:dispatch(S(tostring(g.UserId),w))end t(i:getSlotsCount(g))local function U(w)v:d("\068\097\116\097\083\116\111\114\101\058\073\110\118\101\110\116\111\114\121 \117\112\100\097\116\101\100 \116\111\058",w)local function A(g,w)local A=q.AllObjects[w]if not A then v:d("\078\111 \111\098\106\101\099\116 \102\111\117\110\100 \105\110 \073\110\118\101\110\116\111\114\121\079\098\106\101\099\116\115",w)else g[w]=A end return g end local C=Q.reduce(w,A,{})v:d("\065\100\100 \105\110\118\101\110\116\111\114\121 \105\116\101\109\115\058",C)local t=tostring(g.UserId)D:dispatch(p(t,C))E:addToCharacter(g.Character,C,t)end U(i:getInventory(g))i:onInventoryUpdated(g,U)end c=Y.create({clientStart=G,startRoomGame=function(g,w)warn("\078\079\084 \085\083\069\068\063\063\063\063\063\063\082\111\111\109 "..(w..(" \115\116\097\114\116\101\100 \102\111\114 \112\108\097\121\101\114 "..g.Name)))end;endRoomGame=function(g,w)warn("\078\079\084 \085\083\069\068\063\063\063\063\063\063\082\111\111\109 "..(w..(" \101\110\100\101\100 \102\111\114 \112\108\097\121\101\114 "..g.Name)))D:dispatch(I(g,w))end;pickUpItem=function(g,w)local A=D:getState()v:d("\080\108\097\121\101\114 "..(g.Name..(" \112\105\099\107\101\100 \117\112 \105\116\101\109 "..w)))i:setInventoryItem(g,w)end;pickUpCoin=function(g,w)local A=D:getState()v:d("\080\108\097\121\101\114 "..(g.Name..(" \112\105\099\107\101\100 \117\112 \099\111\105\110 "..w)))local C=q.CoinObjects[w]if C then i:incrementCoins(g,C.value)else v:e("\078\111 \099\111\105\110\073\116\101\109 \102\111\117\110\100",w)end end,dropItem=function(g,w)local A=tostring(g.UserId)local C=D:getState()local Q=C.playerInventories[A]if Q==nil then v:w("\067\111\117\108\100\110\'\116 \102\105\110\100 \112\108\097\121\101\114 \105\110\118\101\110\116\111\114\121 "..A)return end local t=Q[w]if t==nil then v:w("\080\108\097\121\101\114 \099\097\110\'\116 \100\114\111\112 \105\116\101\109 "..w)return end v:d("\080\108\097\121\101\114 "..(g.Name..(" \100\114\111\112\112\101\100 \105\116\101\109 "..w)))local c=g.Character if c==nil then v:w("\067\097\110\'\116 \100\114\111\112 \105\116\101\109 \102\111\114 \112\108\097\121\101\114\044 \110\111 \099\104\097\114\097\099\116\101\114\058 "..A)return end local U=c:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")if U==nil then v:w("\078\111 \072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116 \105\110 \099\104\097\114\097\099\116\101\114 \102\114\111\109 "..A)return end D:dispatch(L(A,w))v:warn("\083\104\111\117\108\100 \100\114\111\112 \105\116\101\109 \116\111 \119\111\114\108\100")i:unsetInventoryItem(g,w)end,buyItem=function(g,w)v:d("\080\108\097\121\101\114 "..(g.Name..(" \098\111\117\103\104\116 \105\116\101\109 "..w)))local A=k[w]if not A then v:w("\078\111 \112\114\111\100\117\099\116")return end local C=A.price local Q=i:decrementCoins(g,C)if Q>=0 then v:d("\066\117\121\105\110\103 \112\114\111\100\117\099\116",w)i:setEquippedPet(g,A.id)i:setInventoryItem(g,w)D:dispatch(p(tostring(g.UserId),{[w]=A}))else v:w("\078\111\116 \101\110\111\117\103\104 \109\111\110\101\121")D:dispatch(N(g,"\078\111\116 \101\110\111\117\103\104 \099\111\105\110\115\046 \077\105\115\115\105\110\103 "..(math.abs(Q).." \099\111\105\110\115"),s.money["\099\111\105\110\115-\112\105\108\101"]))end end,startGhosting=function(g)v:d("\080\108\097\121\101\114 "..(g.Name.." \115\116\097\114\116\101\100 \103\104\111\115\116\105\110\103 "))if e:hasGamePass(g,e.GHOST_MODE_ID)then m:addGhostAbility(g.Character)c:clientStartGhosting(g)else v:e("\080\108\097\121\101\114 "..(g.Name.." \100\111\101\115 \110\111\116 \104\097\118\101 \103\104\111\115\116\105\110\103 \097\098\105\108\105\116\121\058 "))end end;stopGhosting=function(g)v:d("\080\108\097\121\101\114 "..(g.Name.." \115\116\111\112 \103\104\111\115\116\105\110\103 "))if g.Character then g.Character.Humanoid.Health=0 else v:d("\078\111 \067\104\097\114\097\099\116\101\114 \102\111\117\110\100 \102\111\114 \112\108\097\121\101\114\058"..g.Name)end end;equipItem=function(g,w)v:d("\080\108\097\121\101\114 "..(g.Name..(" \101\113\117\105\112\112\101\100 \105\116\101\109 "..w)))local A=(D:getState()).shop.items local C=A[w]if not C then v:w("\078\111 \112\114\111\100\117\099\116")return end i:setEquippedPet(g,C.id)end,roomVote=function(g,w,A)v:d("\080\108\097\121\101\114 "..(g.Name..(" \118\111\116\101\100 \105\110 \114\111\111\109 "..w)),A)local C=(D:getState()).rooms local Q=C[w]if not Q then v:w("\078\111 \114\111\111\109",w)return end D:dispatch(j(g,w,A))c:clientPlaySound(g,"\083\105\109\112\108\101\095\067\108\105\099\107")end,unequipItem=function(g,w)v:d("\080\108\097\121\101\114 "..(g.Name..(" \117\110\101\113\117\105\112\112\101\100 \105\116\101\109 "..w)))local A=(D:getState()).shop.items local C=A[w]if not C then v:w("\078\111 \112\114\111\100\117\099\116")return end i:unsetEquippedPet(g,C.id)end;unequipAll=function(g)v:d("\080\108\097\121\101\114 "..(g.Name.." \117\110\101\113\117\105\112\112\101\100 \097\108\108"))i:unsetAllEquippedPet(g)end})table.insert(g.destructors,function()D:destruct()end)table.insert(g.destructors,function()c:destroy()end)v:i("\083\101\114\118\101\114 \115\116\097\114\116\101\100\033")spawn(function()v:i("\071\101\116\116\105\110\103 \076\101\097\100\101\114\098\111\097\114\100\115\033")while true do local g=y:getMostPlayed()local w=y:getMostVisited()local v=y:getMostCoins()D:dispatch(b("\109\111\115\116\080\108\097\121\101\100",g))D:dispatch(b("\109\111\115\116\086\105\115\105\116\101\100",w))D:dispatch(b("\109\111\115\116\067\111\105\110\115",v))function refreshRoomLeaderboards(g)local w=g.id local v=y:getMostPlayed(w)D:dispatch(b(w,v))end Q.each((D:getState()).rooms,refreshRoomLeaderboards)wait(O.refreshLeaderboards)end end)v:i("\073\110\105\116\105\097\108\105\122\105\110\103 \114\111\111\109\115")if h then for g,w in pairs((D:getState()).rooms)do v:i("\073\110\105\116\105\097\108\105\122\105\110\103 \114\111\111\109\058",g,w)if w.config.noTimer then v:i("\073\110\105\116 \110\111 \116\105\109\101\114 \114\111\111\109\046 \083\116\097\114\116 \105\110\102\105\110\105\116\101 \114\111\111\109")D:dispatch(o(g))else D:dispatch(x(g))end local A=h:findFirstChild(w.modelName)if A then local C=A.placeholders:WaitForChild("\082\111\111\109\080\108\097\099\101\104\111\108\100\101\114")v:w("\067\114\101\097\116\105\110\103 \122\111\110\101",C)local Q=U:createZone("\090\111\110\101"..w.modelName,C)Q.playerAdded:Connect(function(A)v:w(A.Name..(" \101\110\116\101\114\101\100 \114\111\111\109 "..tostring(g)))D:dispatch(f(A,g))if w.name=="\072\111\114\114\111\114\077\097\122\101"then D:dispatch(T(A,g))v:d("\078\111 \112\101\116\115 \097\108\108\111\119\101\100 \105\110 \072\111\114\114\111\114\077\097\122\101\046 \080\108\097\121\101\114 "..(A.Name.." \117\110\101\113\117\105\112\112\101\100 \097\108\108"))i:unsetAllEquippedPet(A)c:clientPlayBackgroundSound(A,"\083\099\097\114\121\095\098\103")end end)Q.playerRemoving:Connect(function(A)v:d(A.Name..(" \108\101\102\116 \114\111\111\109 "..tostring(g)))D:dispatch(n(A,g))if w.name=="\072\111\114\114\111\114\077\097\122\101"then D:dispatch(R(A))c:clientPlayBackgroundSound(A,"\068\101\115\101\114\116\095\083\097\110\100\115")end end)Q:initLoop()else v:w("\082\111\111\109\115 \102\111\108\100\101\114 \105\115 \109\105\115\115\105\110\103 "..(g.." \111\098\106\101\099\116\033\033"))end end else v:w("\082\111\111\109\115 \070\111\108\100\101\114 \100\111\101\115 \110\111\116 \101\120\105\115\116\115\033\033")end v:d("\073\110\105\116\105\097\108\105\122\105\110\103 \084\097\103\073\116\101\109\115")Z.create(nil,"\075\105\108\108\066\114\105\099\107",function(g,w)v:d("\080\108\097\121\101\114 \107\105\108\108\101\100 \119\105\116\104 \107\105\108\108\098\114\105\099\107\046 \072\105\116 \110\097\109\101\058",w.Name)w.parent.Humanoid.Health=0 end)local J={}Z.create(nil,"\067\111\105\110\066\114\105\099\107",function(g,w,A)if A:FindFirstChild("\105\116\101\109\073\100")then v:d("\080\108\097\121\101\114 \103\111\116 \099\111\105\110 \119\105\116\104 \118\097\108\117\101\058 "..A.itemId.Value)local w=tostring(A.itemId.Value)v:d("\080\108\097\121\101\114 "..(g.Name..(" \112\105\099\107\101\100 \117\112 \099\111\105\110 "..w)))local C=q.CoinObjects[w]if C then if C.onePerPlayer then local w=tostring(C.id)..("\095"..tostring(g.UserId))if not J[w]then J[w]=true i:incrementCoins(g,C.value)c:clientPlaySound(g,"\067\111\105\110\095\067\111\108\108\101\099\116")D:dispatch(N(g,"\067\111\108\108\101\099\116\101\100 \116\114\101\097\115\117\114\101 \119\105\116\104 "..(tostring(C.value).." \099\111\105\110\115"),s.money["\099\111\105\110\115-\112\105\108\101"]))end else i:incrementCoins(g,C.value)c:clientPlaySound(g,"\067\111\105\110\095\067\111\108\108\101\099\116")end else v:e("\078\111 \099\111\105\110\073\116\101\109 \102\111\117\110\100",w)end if not C.onePerPlayer then if A.Name=="\080\114\105\109\097\114\121\080\097\114\116"then local g=A:FindFirstAncestorOfClass("\077\111\100\101\108")g:Destroy()else A.Parent=nil wait(10)A.Parent=game.workspace end end else v:w("\078\111 \105\116\101\109\073\100 \086\097\108\117\101 \102\111\114 \099\111\105\110 \112\097\114\116")end end)local gU=10 Z.create(nil,"\083\111\117\110\100\084\114\105\103\103\101\114\066\114\105\099\107",function(g,w,v)function playSound(w,v)c:clientPlaySound(g,w,v)end a:makeSound(v,gU,playSound)end)local wU={}local vU=10 Z.create(nil,"\070\097\108\108\084\114\105\103\103\101\114\066\114\105\099\107",function(g,w,v)if not wU[v]then wU[v]=true spawn(function()z:fallStuff(v,vU)wU[v]=false end)end end,nil)Z.create(nil,"\076\111\103\105\099\084\114\105\103\103\101\114\066\114\105\099\107",function(g,w,v)function playSound(w,v)c:clientPlaySound(g,w,v)end P:trigger(v,g,playSound,D)end,nil)Z.create(nil,"\067\111\108\108\101\099\116\097\098\108\101\066\114\105\099\107",function(g,w,A)if A.itemId then v:d("\080\108\097\121\101\114 \103\111\116 \099\111\108\108\101\099\116\097\098\108\101 \119\105\116\104 \118\097\108\117\101\058 "..A.itemId.Value)local w=tostring(A.itemId.Value)v:d("\080\108\097\121\101\114 "..(g.Name..(" \112\105\099\107\101\100 \117\112 \105\116\101\109 "..w)))i:setInventoryItem(g,w)A.Parent=nil wait(10)A.Parent=game.workspace else v:w("\078\111 \105\116\101\109\073\100 \086\097\108\117\101 \102\111\114 \099\111\108\108\101\099\116\097\098\108\101 \112\097\114\116")end end)A.PromptGamePassPurchaseFinished:Connect(function(g,w,A)local C=tostring(w)if A then v:i(g.Name..(" \112\117\114\099\104\097\115\101\100 \116\104\101 \103\097\109\101 \112\097\115\115 \119\105\116\104 \073\068 "..C))e:addAbility(g,C)D:dispatch(p(tostring(g.UserId),{[C]=q.AllObjects[C]}))else v:d(g.Name..(" \100\105\100 \110\111\116 \112\117\114\099\104\097\115\101 \103\097\109\101 \112\097\115\115 \119\105\116\104 \073\068 "..C))end end)A.ProcessReceipt=H.processReceipt C.PlayerRemoving:Connect(function(g)v:d(g.Name.." \108\101\102\116 \116\104\101 \103\097\109\101\046 \082\101\109\111\118\101 \102\114\111\109 \114\111\111\109\115\033")local w=tostring(g.UserId)D:dispatch(l(g))E:removePlayerCollisionGroup(w)end)end
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local Modules = ReplicatedStorage:WaitForChild('Modules')
local logger = require(Modules.src.utils.Logger)
local MarketplaceService = game:GetService('MarketplaceService')
local Players = game:GetService('Players')

local M = require(Modules.M)
local Rodux = require(Modules.Rodux)

local ZonePlus = require(4664437268) --require(Modules.ZonePlus)
local ZoneService = require(ZonePlus.ZoneService)

local equipPlayer = require(Modules.src.thunks.equipPlayer)
local clientSlotsCount = require(Modules.src.actions.toClient.clientSlotsCount)
local clientStartGame = require(Modules.src.actions.toClient.clientStartGame)
local clientReset = require(Modules.src.actions.toClient.clientReset)
local GlobalConfig = require(Modules.src.GlobalConfig)

-- The Rodux DevTools aren't available yet! Check the README for more details.
-- local RoduxVisualizer = require(Modules.RoduxVisualizer)

local commonReducers = require(Modules.src.commonReducers)
local Dict = require(Modules.src.utils.Dict)
local DeveloperProducts = require(Modules.src.DeveloperProducts)
local clientSendNotification = require(Modules.src.actions.toClient.clientSendNotification)
local assets = require(Modules.src.assets)
-- These imports are pretty darn verbose.
local connectPlayer = require(Modules.src.thunks.connectPlayer)
local playerEnteredRoom = require(Modules.src.thunks.playerEnteredRoom)
local playerFinishedRoom = require(Modules.src.thunks.playerFinishedRoom)
local startRoomGameLoop = require(Modules.src.thunks.startRoomGameLoop)
local startInfiniteGame = require(Modules.src.thunks.startInfiniteGame)
local removePlayerFromRoom = require(Modules.src.actions.rooms.removePlayerFromRoom)
local playerDied = require(Modules.src.actions.rooms.playerDied)
local addVoteToRoom = require(Modules.src.actions.rooms.addVoteToRoom)
local addItemsToPlayerInventory = require(Modules.src.actions.inventory.addItemsToPlayerInventory)
local removeItemFromPlayerInventory =
	require(Modules.src.actions.inventory.removeItemFromPlayerInventory)

local serverReducers = require(script.Parent.serverReducers)
local ServerApi = require(script.Parent.ServerApi)
local networkMiddleware = require(script.Parent.networkMiddleware)

local InventoryObjects = require(Modules.src.objects.InventoryObjects)
local BuyObjects = InventoryObjects.BuyObjects

local GameDatastore = require(Modules.src.GameDatastore)
local GamePasses = require(Modules.src.GamePasses)
local GhostAbility = require(Modules.src.GhostAbility)
local RoomManager = require(Modules.src.RoomManager)
local FallTriggerManager = require(Modules.src.FallTriggerManager)
local SoundTriggerManager = require(Modules.src.SoundTriggerManager)
local LogicTriggerManager = require(Modules.src.LogicTriggerManager)

GhostAbility:initCollisionGroup()
RoomManager:initCollisionGroups()

local Place = game.Workspace:WaitForChild('Place')

local Leaderboards = require(Modules.src.Leaderboards)
local addLeaderboardItems = require(Modules.src.actions.leaderboards.addLeaderboardItems)
local RoomsFolder = Place:findFirstChild('Rooms')

local TagItem = require(Modules.src.TagItem)
local TagItemOnce = require(Modules.src.TagItemOnce)

return function(context)
	local reducer = Rodux.combineReducers(Dict.join(commonReducers, serverReducers))

	local api

	--[[
		This function contains our custom replication logic for Rodux actions.

		Using the Redux pattern as a way to sychronize replicated data is a new
		idea. Vocksel introduced the idea to me, and I used this project partly
		as a test bed to try it out.
	]]
	local function replicate(action, beforeState, afterState)
		-- Create a version of each action that's explicitly flagged as
		-- replicated so that clients can handle them explicitly.
		local replicatedAction = Dict.join(action, { replicated = true })

		-- This is an action that everyone should see!
		if action.replicateBroadcast then
			return api:storeAction(ServerApi.AllPlayers, replicatedAction)
		end

		-- This is an action that we want a specific player to see.
		if action.replicateTo ~= nil then
			local player = Players:GetPlayerByUserId(action.replicateTo)

			if player == nil then return end

			return api:storeAction(player, replicatedAction)
		end

		-- We should probably replicate any actions that modify data shared
		-- between the client and server.
		for key in pairs(commonReducers) do
			if beforeState[key] ~= afterState[key] then
				return api:storeAction(ServerApi.AllPlayers, replicatedAction)
			end
		end

		return
	end

	--[[
		For hot-reloading, we want to save a list of every action that gets run
		through the store. This lets us iterate on our reducers, but otherwise
		keep any state we want across reloads.
	]]
	local function saveActionsMiddleware(nextDispatch)
		return function(action)
			table.insert(context.savedActions, action)

			return nextDispatch(action)
		end
	end

	-- This is a case where the simplicify of reducers shines!
	-- We produce the state that this store should start at based on the actions
	-- that the previous store had executed.
	local initialState = nil
	for _, action in ipairs(context.savedActions) do
		initialState = reducer(initialState, action)
	end

	-- local devTools = RoduxVisualizer.createDevTools({
	-- 	mode = RoduxVisualizer.Mode.Plugin,
	-- })

	local middleware = { -- Our minimal middleware to save actions to our context.
	Rodux.thunkMiddleware, saveActionsMiddleware, networkMiddleware(replicate) }
	--, Rodux.loggerMiddleware
	-- Rodux has a built-in logger middleware to print to the console
	-- whenever actions are dispatched to show the store.
	-- callback defined above.
	-- Middleware to replicate actions to the client, using the replicate

	-- Once the Rodux DevTools are available, this will be revisited!
	-- devTools.middleware,

	local store = Rodux.Store.new(reducer, initialState, middleware)

	local clientStart = function(player)
		store:dispatch(connectPlayer(player))

		local state = store:getState()
		local commonState = {}

		for key, value in pairs(state) do
			if commonReducers[key] ~= nil then
				commonState[key] = value
			end
		end

		logger:i('Client is starting. Initializing store and Datastore.')
		api:initialStoreState(player, commonState)

		----------------------------------------------------------------
		----- Setup DataStore to Store sync
		----------------------------------------------------------------

		local function updetePetsEquippedInState(petIds)
			logger:d('DataStore:Pet Equipped status updated to: ', petIds)
			store:dispatch(equipPlayer(player, petIds))
		end

		updetePetsEquippedInState(GameDatastore:getEquippedPets(player))

		GameDatastore:onEquippedPetsUpdated(player, updetePetsEquippedInState)

		local function slotsCountUpdated(slotsCount)
			logger:d('DataStore: Slots updated: ', slotsCount)
			store:dispatch(clientSlotsCount(tostring(player.UserId), slotsCount))
		end
		slotsCountUpdated(GameDatastore:getSlotsCount(player))

		----
		---- inventory
		----

		local function updateInventoryInState(inventoryItemIds)
			logger:d('DataStore:Inventory updated to:', inventoryItemIds)

			local function getInventoryObject(objects, itemId)
				local object = InventoryObjects.AllObjects[itemId]

				if not object then
					logger:d('No object found in InventoryObjects', itemId)
				else
					objects[itemId] = object
				end
				return objects
			end

			local inventoryItems = M.reduce(inventoryItemIds, getInventoryObject, {})
			logger:d('Add inventory items:', inventoryItems)
			local playerId = tostring(player.UserId)
			store:dispatch(addItemsToPlayerInventory(playerId, inventoryItems))

			RoomManager:addToCharacter(player.Character, inventoryItems, playerId)
		end

		updateInventoryInState(GameDatastore:getInventory(player))

		GameDatastore:onInventoryUpdated(player, updateInventoryInState)
	end

	-- Construct our ServerApi, which creates RemoteEvent objects for our
	-- clients to listen to.
	api = ServerApi.create({
	-- We need to make sure not to replicate anything secret!
		clientStart = clientStart,
		startRoomGame = function(player, roomId)
			warn('NOT USED??????Room ' .. roomId .. ' started for player ' .. player.Name)
		end,
		endRoomGame = function(player, roomId)
			warn('NOT USED??????Room ' .. roomId .. ' ended for player ' .. player.Name)
			store:dispatch(playerFinishedRoom(player, roomId))
		end,
		pickUpItem = function(player, itemId)
			local state = store:getState()

			logger:d('Player ' .. player.Name .. ' picked up item ' .. itemId)

			GameDatastore:setInventoryItem(player, itemId)
		end,
		pickUpCoin = function(player, itemId)
			local state = store:getState()

			logger:d('Player ' .. player.Name .. ' picked up coin ' .. itemId)
			local coinItem = InventoryObjects.CoinObjects[itemId]
			if coinItem then
				GameDatastore:incrementCoins(player, coinItem.value)
			else
				logger:e('No coinItem found', itemId)
			end
		end,
		dropItem = function(player, itemId)
			local playerId = tostring(player.UserId)
			local state = store:getState()
			local inventory = state.playerInventories[playerId]

			if inventory == nil then
				logger:w("Couldn't find player inventory " .. playerId)
				return
			end

			local item = inventory[itemId]

			if item == nil then
				logger:w("Player can't drop item " .. itemId)
				return
			end

			logger:d('Player ' .. player.Name .. ' dropped item ' .. itemId)

			local character = player.Character

			if character == nil then
				logger:w("Can't drop item for player, no character: " .. playerId)
				return
			end

			local root = character:FindFirstChild('HumanoidRootPart')

			if root == nil then
				logger:w('No HumanoidRootPart in character from ' .. playerId)
				return
			end

			store:dispatch(removeItemFromPlayerInventory(playerId, itemId))

			logger:warn('Should drop item to world')
			GameDatastore:unsetInventoryItem(player, itemId)
		end,
		buyItem = function(player, productId)
			logger:d('Player ' .. player.Name .. ' bought item ' .. productId)

			local product = BuyObjects[productId]
			if not product then
				logger:w('No product')
				return
			end

			local productPrice = product.price

			local afterCoins = GameDatastore:decrementCoins(player, productPrice)
			if afterCoins >= 0 then
				logger:d('Buying product', productId)
				GameDatastore:setEquippedPet(player, product.id)
				GameDatastore:setInventoryItem(player, productId)

				store:dispatch(
					addItemsToPlayerInventory(tostring(player.UserId), { [productId] = product })
				)
			else
				logger:w('Not enough money')
				store:dispatch(
					clientSendNotification(
						player,
						'Not enough coins. Missing ' .. math.abs(afterCoins) .. ' coins',
						assets.money['coins-pile']
					)
				)
			end
		end,
		startGhosting = function(player)
			logger:d('Player ' .. player.Name .. ' started ghosting ')

			if GamePasses:hasGamePass(player, GamePasses.GHOST_MODE_ID) then
				GhostAbility:addGhostAbility(player.Character)

				api:clientStartGhosting(player)
			else
				logger:e('Player ' .. player.Name .. ' does not have ghosting ability: ')
			end
		end,
		stopGhosting = function(player)
			logger:d('Player ' .. player.Name .. ' stop ghosting ')
			if player.Character then
				player.Character.Humanoid.Health = 0
			else
				logger:d('No Character found for player:' .. player.Name)
			end
		end,
		equipItem = function(player, productId)
			logger:d('Player ' .. player.Name .. ' equipped item ' .. productId)

			local items = store:getState().shop.items
			local product = items[productId]
			if not product then
				logger:w('No product')
				return
			end

			GameDatastore:setEquippedPet(player, product.id)
		end,
		roomVote = function(player, roomId, vote)
			logger:d('Player ' .. player.Name .. ' voted in room ' .. roomId, vote)

			local items = store:getState().rooms
			local room = items[roomId]
			if not room then
				logger:w('No room', roomId)
				return
			end
			store:dispatch(addVoteToRoom(player, roomId, vote))
			api:clientPlaySound(player, 'Simple_Click')
		end,
		unequipItem = function(player, productId)
			logger:d('Player ' .. player.Name .. ' unequipped item ' .. productId)

			local items = store:getState().shop.items
			local product = items[productId]
			if not product then
				logger:w('No product')
				return
			end

			GameDatastore:unsetEquippedPet(player, product.id)
		end,
		unequipAll = function(player)
			logger:d('Player ' .. player.Name .. ' unequipped all')

			GameDatastore:unsetAllEquippedPet(player)
		end,
	})

	-- The hot-reloading shim has a place for us to stick destructors, since we
	-- need to clean up everything on the server before unloading.
	table.insert(context.destructors, function()
		store:destruct()
	end)

	table.insert(context.destructors, function()
		api:destroy()
	end)

	logger:i('Server started!')

	spawn(function()
		logger:i('Getting Leaderboards!')
		while true do
			local mostPlayed = Leaderboards:getMostPlayed()
			local mostVisited = Leaderboards:getMostVisited()
			local mostCoins = Leaderboards:getMostCoins()

			store:dispatch(addLeaderboardItems('mostPlayed', mostPlayed))
			store:dispatch(addLeaderboardItems('mostVisited', mostVisited))
			store:dispatch(addLeaderboardItems('mostCoins', mostCoins))

			function refreshRoomLeaderboards(roomObject)
				local roomId = roomObject.id
				local mostPlayedRoom = Leaderboards:getMostPlayed(roomId)
				store:dispatch(addLeaderboardItems(roomId, mostPlayedRoom))
			end

			M.each(store:getState().rooms, refreshRoomLeaderboards)

			wait(GlobalConfig.refreshLeaderboards)
		end
	end)

	logger:i('Initializing rooms')
	if RoomsFolder then
		for roomId, room in pairs(store:getState().rooms) do
			logger:i('Initializing room:', roomId, room)

			if room.config.noTimer then
				logger:i('Init no timer room. Start infinite room')

				store:dispatch(startInfiniteGame(roomId))
			else
				store:dispatch(startRoomGameLoop(roomId))
			end

			local roomObj = RoomsFolder:findFirstChild(room.modelName)

			if roomObj then
				local roomPlaceholder = roomObj.placeholders:WaitForChild('RoomPlaceholder')
				logger:w('Creating zone', roomPlaceholder)
				local zone = ZoneService:createZone('Zone' .. room.modelName, roomPlaceholder)

				zone.playerAdded:Connect(function(player) -- Fires when a player enters the zone
					logger:w(player.Name .. ' entered room ' .. tostring(roomId))
					store:dispatch(playerEnteredRoom(player, roomId))

					if room.name == 'HorrorMaze' then
						store:dispatch(clientStartGame(player, roomId))
						logger:d(
							'No pets allowed in HorrorMaze. Player ' .. player.Name .. ' unequipped all'
						)

						GameDatastore:unsetAllEquippedPet(player)
						api:clientPlayBackgroundSound(player, 'Scary_bg')
					end
				end)

				zone.playerRemoving:Connect(function(player) -- Fires when a player exits the zone
					logger:d(player.Name .. ' left room ' .. tostring(roomId))
					store:dispatch(removePlayerFromRoom(player, roomId))
					if room.name == 'HorrorMaze' then
						store:dispatch(clientReset(player))

						api:clientPlayBackgroundSound(player, 'Desert_Sands')
					end
				end)

				zone:initLoop() -- Initiates loop (default 0.5) which enables the events to work
			else
				logger:w('Rooms folder is missing ' .. roomId .. ' object!!')
			end
		end
	else
		logger:w('Rooms Folder does not exists!!')
	end

	logger:d('Initializing TagItems')

	TagItem.create(nil, 'KillBrick', function(player, hit)
		logger:d('Player killed with killbrick. Hit name:', hit.Name)
		hit.parent.Humanoid.Health = 0
	end)

	local collectedByPlayer = {}

	TagItem.create(nil, 'CoinBrick', function(player, hit, part)
		if part:FindFirstChild('itemId') then
			logger:d('Player got coin with value: ' .. part.itemId.Value)

			local itemId = tostring(part.itemId.Value)

			logger:d('Player ' .. player.Name .. ' picked up coin ' .. itemId)
			local coinItem = InventoryObjects.CoinObjects[itemId]

			if coinItem then
				if coinItem.onePerPlayer then
					local key = tostring(coinItem.id) .. '_' .. tostring(player.UserId)

					if not collectedByPlayer[key] then
						collectedByPlayer[key] = true
						GameDatastore:incrementCoins(player, coinItem.value)
						api:clientPlaySound(player, 'Coin_Collect')
						store:dispatch(
							clientSendNotification(
								player,
								'Collected treasure with ' .. tostring(coinItem.value) .. ' coins',
								assets.money['coins-pile']
							)
						)
					end
				else
					GameDatastore:incrementCoins(player, coinItem.value)
					api:clientPlaySound(player, 'Coin_Collect')
				end
			else
				logger:e('No coinItem found', itemId)
			end

			if not coinItem.onePerPlayer then
				if part.Name == 'PrimaryPart' then
					local model = part:FindFirstAncestorOfClass('Model')
					model:Destroy()
				else
					part.Parent = nil
					wait(10)
					part.Parent = game.workspace
				end
			end
		else
			logger:w('No itemId Value for coin part')
		end
	end)

	local soundTriggerWaitFor = 10
	TagItem.create(nil, 'SoundTriggerBrick', function(player, hit, part)
		function playSound(soundName, triggerPart)
			api:clientPlaySound(player, soundName, triggerPart)
		end

		SoundTriggerManager:makeSound(part, soundTriggerWaitFor, playSound)
	end)

	local fallStuffRunner = {}
	local fallStuffWaitFor = 10
	TagItem.create(
		nil,
		'FallTriggerBrick',
		function(player, hit, part)
			if not fallStuffRunner[part] then
				fallStuffRunner[part] = true
				spawn(function()
					FallTriggerManager:fallStuff(part, fallStuffWaitFor)
					fallStuffRunner[part] = false
				end)
			end
		end,
		nil
	)

	TagItem.create(
		nil,
		'LogicTriggerBrick',
		function(player, hit, part)
			function playSound(soundName, triggerPart)
				api:clientPlaySound(player, soundName, triggerPart)
			end

			LogicTriggerManager:trigger(part, player, playSound, store)
		end,
		nil
	)

	TagItem.create(nil, 'CollectableBrick', function(player, hit, part)
		if part.itemId then
			logger:d('Player got collectable with value: ' .. part.itemId.Value)

			local itemId = tostring(part.itemId.Value)
			logger:d('Player ' .. player.Name .. ' picked up item ' .. itemId)

			GameDatastore:setInventoryItem(player, itemId)
			--local coinClone = part:Clone()
			--part:Destroy()

			part.Parent = nil
			wait(10)
			part.Parent = game.workspace
		else
			logger:w('No itemId Value for collectable part')
		end
	end)

	MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, id, purchaseSuccess)
		local productId = tostring(id)
		if purchaseSuccess then
			logger:i(player.Name .. ' purchased the game pass with ID ' .. productId)
			GamePasses:addAbility(player, productId)

			store:dispatch(
				addItemsToPlayerInventory(
					tostring(player.UserId),
					{ [productId] = InventoryObjects.AllObjects[productId] }
				)
			)
		else
			logger:d(player.Name .. ' did not purchase game pass with ID ' .. productId)
		end
	end)

	-- Set Developer Products purchase callback
	MarketplaceService.ProcessReceipt = DeveloperProducts.processReceipt

	Players.PlayerRemoving:Connect(function(player)
		logger:d(player.Name .. ' left the game. Remove from rooms!')
		local playerId = tostring(player.UserId)
		store:dispatch(playerDied(player))
		RoomManager:removePlayerCollisionGroup(playerId)
	end)
end