local I={DiscoveredMapCells={Type="\083\116\114\105\110\103",Default="\091\093"},CollectedBloxkins={Type="\084\097\098\108\101";Default="\091\093"},Inventory={Type="\084\097\098\108\101";Default="\091\093"},PetsOwned={Type="\084\097\098\108\101";Default="\091\093"};ToolsOwned={Type="\084\097\098\108\101",Default="\091\093"},CurrentPet={Type="\083\116\114\105\110\103",Default="\068\111\103"},Currency={Type="\073\110\116\101\103\101\114",Default=0},WarpPurchased={Type="\066\111\111\108\101\097\110",Default=true};Quests={Type="\084\097\098\108\101";Default="\091\093"};XP={Type="\073\110\116\101\103\101\114",Default=0}}local x={String={ValueType="\083\116\114\105\110\103\086\097\108\117\101"};Table={ValueType="\083\116\114\105\110\103\086\097\108\117\101",Serialize=function(I)return(game:GetService("\072\116\116\112\083\101\114\118\105\099\101")):JSONEncode(I)end;Deserialize=function(I)return(game:GetService("\072\116\116\112\083\101\114\118\105\099\101")):JSONDecode(I)end};Number={ValueType="\078\117\109\098\101\114\086\097\108\117\101"},Integer={ValueType="\073\110\116\086\097\108\117\101"},Boolean={ValueType="\066\111\111\108\086\097\108\117\101"}}local u=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local U=require((u:WaitForChild("\080\114\111\106\101\099\116")):WaitForChild("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101"))local k=U:GetResource("\069\120\116\101\114\110\097\108\085\116\105\108\046\078\101\120\117\115\073\110\115\116\097\110\099\101\046\078\101\120\117\115\079\098\106\101\099\116")local R=U:GetResource("\069\120\116\101\114\110\097\108\085\116\105\108\046\078\101\120\117\115\073\110\115\116\097\110\099\101\046\069\118\101\110\116\046\078\101\120\117\115\069\118\101\110\116")local p=k:Extend()p.PlayerDataLayout=I p.CachedPlayerData={}setmetatable(p.CachedPlayerData,{__mode="\107"})p:SetClassName("\080\108\097\121\101\114\068\097\116\097")function p.__new(x,I)x:InitializeSuper()x.PlayerDataFolder=I x.PlayerData={}x.ValueChangedEvents={}x.ValueChanged=R.new()for I,u in pairs(I:GetChildren())do x:ConnectValueObject(u)end I.ChildAdded:Connect(function(I)x:ConnectValueObject(I)end)end function p.GetPlayerData(I)local x=I:WaitForChild("\080\108\097\121\101\114\068\097\116\097")if not p.CachedPlayerData[x]then p.CachedPlayerData[x]=p.new(x)end return p.CachedPlayerData[x]end function p.ConnectValueObject(x,I)local u=I.Name I.Changed:Connect(function()x.PlayerData[u]=I.Value x.ValueChanged:Fire(u)if x.ValueChangedEvents[u]then x.ValueChangedEvents[u]:Fire()end end)x.PlayerData[u]=I.Value x.ValueChanged:Fire(u)if x.ValueChangedEvents[u]then x.ValueChangedEvents[u]:Fire()end end function p.InitializeFromSerializedData(U,u)U.PlayerData=u or{}for I,x in pairs(I)do if not U.PlayerData[I]then U.PlayerData[I]=x.Default end end for I,u in pairs(I)do local k=U.PlayerDataFolder:FindFirstChild(I)or Instance.new(x[u.Type].ValueType)k.Name=I k.Value=U.PlayerData[I]k.Parent=U.PlayerDataFolder end end function p.GetValue(U,u)local k=U.PlayerData[u]local R=I[u]if R then k=k or R.Default local I=x[R.Type]if I and I.Deserialize then k=I.Deserialize(k)end end return k end function p.SetValue(k,u,U)local R=I[u]if R then local I=x[R.Type]if I and I.Serialize then U=I.Serialize(U)end end local p=k.PlayerData[u]if p~=U then k.PlayerData[u]=U local I=k.PlayerDataFolder:FindFirstChild(u)if I then I.Value=U end end end function p.GetValueChangedSignal(x,I)if not x.ValueChangedEvents[I]then x.ValueChangedEvents[I]=R.new()end return x.ValueChangedEvents[I]end return p
--[[
TheNexusAvenger

Reads and manipulates player data.
--]]

local PLAYER_DATA_LAYOUT = {
    DiscoveredMapCells = {
        Type = "String",
        Default = "[]",
    },
    CollectedBloxkins = {
        Type = "Table",
        Default = "[]",
    },
    Inventory = {
        Type = "Table",
        Default = "[]",
    },
    PetsOwned = {
        Type = "Table",
        Default = "[]",
    },
    ToolsOwned = {
        Type = "Table",
        Default = "[]",
    },
    CurrentPet = {
        Type = "String",
        Default = "Dog",
    },
    Currency = {
        Type = "Integer",
        Default = 0,
    },
    WarpPurchased = {
        Type = "Boolean",
        Default = true,
    },
    Quests = {
        Type = "Table",
        Default = "[]",
    },
    XP = {
        Type = "Integer",
        Default = 0,
    },
}
local TYPE_TO_SERIALIZATION = {
    String = {
        ValueType = "StringValue",
    },
    Table = {
        ValueType = "StringValue",
        Serialize = function(Data)
            return game:GetService("HttpService"):JSONEncode(Data)
        end,
        Deserialize = function(String)
            return game:GetService("HttpService"):JSONDecode(String)
        end,
    },
    Number = {
        ValueType = "NumberValue",
    },
    Integer = {
        ValueType = "IntValue",
    },
    Boolean = {
        ValueType = "BoolValue",
    },
}



local ReplicatedStorage = game:GetService("ReplicatedStorage")

local ReplicatedStorageProject = require(ReplicatedStorage:WaitForChild("Project"):WaitForChild("ReplicatedStorage"))

local NexusObject = ReplicatedStorageProject:GetResource("ExternalUtil.NexusInstance.NexusObject")
local NexusEvent = ReplicatedStorageProject:GetResource("ExternalUtil.NexusInstance.Event.NexusEvent")

local PlayerData = NexusObject:Extend()
PlayerData.PlayerDataLayout = PLAYER_DATA_LAYOUT
PlayerData.CachedPlayerData = {}
setmetatable(PlayerData.CachedPlayerData,{__mode="k"})
PlayerData:SetClassName("PlayerData")



--[[
Creates a player data object.
--]]
function PlayerData:__new(Folder)
    self:InitializeSuper()

    --Store the state.
    self.PlayerDataFolder = Folder
    self.PlayerData = {}
    self.ValueChangedEvents = {}
    self.ValueChanged = NexusEvent.new()

    --Connect the value objects.
    for _,ValueObject in pairs(Folder:GetChildren()) do
        self:ConnectValueObject(ValueObject)
    end
    Folder.ChildAdded:Connect(function(ValueObject)
        self:ConnectValueObject(ValueObject)
    end)
end

--[[
Returns a player data object for a
given player.
--]]
function PlayerData.GetPlayerData(Player)
    --Create the cached object if it doesn't exist.
    local Folder = Player:WaitForChild("PlayerData")
    if not PlayerData.CachedPlayerData[Folder] then
        PlayerData.CachedPlayerData[Folder] = PlayerData.new(Folder)
    end

    --Return the cached object.
    return PlayerData.CachedPlayerData[Folder]
end

--[[
Connects a value being added.
--]]
function PlayerData:ConnectValueObject(ValueObject)
    --Connect changes to the object.
    local ValueName = ValueObject.Name
    ValueObject.Changed:Connect(function()
        self.PlayerData[ValueName] = ValueObject.Value
        self.ValueChanged:Fire(ValueName)
        if self.ValueChangedEvents[ValueName] then
            self.ValueChangedEvents[ValueName]:Fire()
        end
    end)

    --Store the current value.
    self.PlayerData[ValueName] = ValueObject.Value

    --Fire the changed event for the initial object.
    --Done in case of a race condition.
    self.ValueChanged:Fire(ValueName)
    if self.ValueChangedEvents[ValueName] then
        self.ValueChangedEvents[ValueName]:Fire()
    end
end

--[[
Initializes the player data from a table
of serialized data.
--]]
function PlayerData:InitializeFromSerializedData(SerializedData)
    --Set the player data.
    self.PlayerData = SerializedData or {}

    --Add the defaults if they don't exist.
    for KeyName,ValueData in pairs(PLAYER_DATA_LAYOUT) do
        if not self.PlayerData[KeyName] then
            self.PlayerData[KeyName] = ValueData.Default
        end
    end

    --Create the object values.
    for KeyName,ValueData in pairs(PLAYER_DATA_LAYOUT) do
        local ValueObject = self.PlayerDataFolder:FindFirstChild(KeyName) or Instance.new(TYPE_TO_SERIALIZATION[ValueData.Type].ValueType)
        ValueObject.Name = KeyName
        ValueObject.Value = self.PlayerData[KeyName]
        ValueObject.Parent = self.PlayerDataFolder
    end
end

--[[
Returns a value.
--]]
function PlayerData:GetValue(Key)
    --Get the value.
    local Value = self.PlayerData[Key]

    --Deserialize the value.
    local ValueData = PLAYER_DATA_LAYOUT[Key]
    if ValueData then
        Value = Value or ValueData.Default
        local ValueTypeData = TYPE_TO_SERIALIZATION[ValueData.Type]
        if ValueTypeData and ValueTypeData.Deserialize then
            Value = ValueTypeData.Deserialize(Value)
        end
    end

    --Return the value.
    return Value
end

--[[
Sets a value.
--]]
function PlayerData:SetValue(Key,Value)
    --Serialize the value if needed.
    local ValueData = PLAYER_DATA_LAYOUT[Key]
    if ValueData then
        local ValueTypeData = TYPE_TO_SERIALIZATION[ValueData.Type]
        if ValueTypeData and ValueTypeData.Serialize then
            Value = ValueTypeData.Serialize(Value)
        end
    end

    --Store the value and invoke the changed events.
    local OldValue = self.PlayerData[Key]
    if OldValue ~= Value then
        self.PlayerData[Key] = Value
        local ValueObject = self.PlayerDataFolder:FindFirstChild(Key)
        if ValueObject then
            ValueObject.Value = Value
        end
    end
end

--[[
Returns the change event for a specific value.
--]]
function PlayerData:GetValueChangedSignal(ValueName)
    --Create the event if it doesn't exist.
    if not self.ValueChangedEvents[ValueName] then
        self.ValueChangedEvents[ValueName] = NexusEvent.new()
    end

    --Return the event.
    return self.ValueChangedEvents[ValueName]
end



return PlayerData