更新
This commit is contained in:
parent
8f0b963cb9
commit
9900516b10
BIN
excel/enemy.xlsx
Normal file
BIN
excel/enemy.xlsx
Normal file
Binary file not shown.
Binary file not shown.
@ -1,90 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import openpyxl
|
||||
import re
|
||||
|
||||
excel_dir = 'excel'
|
||||
output_dir = 'src/ReplicatedStorage/Data'
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
def parse_array_field(value, elem_type):
|
||||
"""
|
||||
解析数组类型字段,支持[1,2,3]或1,2,3写法,自动去除空格和空字符串。
|
||||
elem_type: 'int', 'float', 'string'等
|
||||
"""
|
||||
if value is None:
|
||||
return []
|
||||
s = str(value).strip()
|
||||
# 支持带中括号写法
|
||||
if s.startswith("[") and s.endswith("]"):
|
||||
s = s[1:-1]
|
||||
# 支持英文逗号和中文逗号
|
||||
items = re.split(r'[,,]', s)
|
||||
result = []
|
||||
for v in items:
|
||||
v = v.strip()
|
||||
if v == "":
|
||||
continue
|
||||
if elem_type == "int":
|
||||
try:
|
||||
result.append(int(v))
|
||||
except Exception:
|
||||
pass
|
||||
elif elem_type == "float":
|
||||
try:
|
||||
result.append(float(v))
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
result.append(v)
|
||||
return result
|
||||
|
||||
for filename in os.listdir(excel_dir):
|
||||
if filename.endswith('.xlsx'):
|
||||
filepath = os.path.join(excel_dir, filename)
|
||||
wb = openpyxl.load_workbook(filepath)
|
||||
for sheet_name in wb.sheetnames:
|
||||
ws = wb[sheet_name]
|
||||
rows = list(ws.iter_rows(values_only=True))
|
||||
if len(rows) < 2:
|
||||
continue
|
||||
headers = rows[0]
|
||||
types = rows[1]
|
||||
# 只保留字段名和类型都不为空的字段
|
||||
valid_indices = [
|
||||
i for i, (h, t) in enumerate(zip(headers, types))
|
||||
if h is not None and str(h).strip() != "" and t is not None and str(t).strip() != ""
|
||||
]
|
||||
valid_headers = [headers[i] for i in valid_indices]
|
||||
valid_types = [types[i] for i in valid_indices]
|
||||
data = []
|
||||
for row in rows[2:]:
|
||||
filtered_row = [row[i] if i < len(row) else None for i in valid_indices]
|
||||
row_dict = {}
|
||||
for h, t, v in zip(valid_headers, valid_types, filtered_row):
|
||||
if t.endswith("[]"):
|
||||
elem_type = t[:-2]
|
||||
row_dict[h] = parse_array_field(v, elem_type)
|
||||
else:
|
||||
row_dict[h] = v
|
||||
id_value = row_dict.get("id")
|
||||
# 只导出id为数字且不为空的行
|
||||
if id_value is not None and isinstance(id_value, (int, float)) and str(int(id_value)).isdigit():
|
||||
data.append(row_dict)
|
||||
if not data:
|
||||
continue
|
||||
out_name = f"{sheet_name}.json"
|
||||
out_path = os.path.join(output_dir, out_name)
|
||||
# 写入json,每个对象单独一行
|
||||
with open(out_path, 'w', encoding='utf-8') as f:
|
||||
f.write('[\n')
|
||||
for i, obj in enumerate(data):
|
||||
line = json.dumps(obj, ensure_ascii=False, separators=(',', ':'))
|
||||
if i < len(data) - 1:
|
||||
f.write(line + ',\n')
|
||||
else:
|
||||
f.write(line + '\n')
|
||||
f.write(']')
|
||||
print(f"导出: {out_path}")
|
BIN
excel/item.xlsx
Normal file
BIN
excel/item.xlsx
Normal file
Binary file not shown.
BIN
excel/level.xlsx
Normal file
BIN
excel/level.xlsx
Normal file
Binary file not shown.
BIN
excel/player.xlsx
Normal file
BIN
excel/player.xlsx
Normal file
Binary file not shown.
@ -44,7 +44,8 @@ def parse_array_field(value, elem_type):
|
||||
for filename in os.listdir(excel_dir):
|
||||
if filename.endswith('.xlsx'):
|
||||
filepath = os.path.join(excel_dir, filename)
|
||||
wb = openpyxl.load_workbook(filepath)
|
||||
# 用 data_only=True 读取公式的值
|
||||
wb = openpyxl.load_workbook(filepath, data_only=True)
|
||||
for sheet_name in wb.sheetnames:
|
||||
ws = wb[sheet_name]
|
||||
rows = list(ws.iter_rows(values_only=True))
|
||||
|
5
src/ReplicatedStorage/Json/Enemy.json
Normal file
5
src/ReplicatedStorage/Json/Enemy.json
Normal file
@ -0,0 +1,5 @@
|
||||
[
|
||||
{"id":1,"type":1,"name":1,"atk":10,"hp":100,"model":"Thief"},
|
||||
{"id":2,"type":1,"name":2,"atk":30,"hp":300,"model":"Thief"},
|
||||
{"id":1000,"type":2,"name":1000,"atk":50,"hp":1000,"model":"Thief"}
|
||||
]
|
@ -1,3 +1,3 @@
|
||||
[
|
||||
{"id":1,"type":1,"name":1,"attributes":[1,20,2,20]}
|
||||
{"id":1,"type":1,"name":1,"attributes":[1,20,2,20],"recycle":10}
|
||||
]
|
4
src/ReplicatedStorage/Json/ItemProp.json
Normal file
4
src/ReplicatedStorage/Json/ItemProp.json
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
{"id":1,"type":1,"typeArgs":[],"quality":4,"iconId":1,"nameId":10001,"textId":20001,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null},
|
||||
{"id":2,"type":1,"typeArgs":[],"quality":4,"iconId":2,"nameId":10002,"textId":20002,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null}
|
||||
]
|
22
src/ReplicatedStorage/Json/Level.json
Normal file
22
src/ReplicatedStorage/Json/Level.json
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
{"id":1,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":2,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":3,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":4,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":5,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[1000]},
|
||||
{"id":6,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":7,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":8,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":9,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":10,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[1000]},
|
||||
{"id":11,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":12,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":13,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":14,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":15,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[1000]},
|
||||
{"id":16,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":17,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":18,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2]},
|
||||
{"id":19,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[1,1,10,2,1,1,10,2,1,1,10,2,1,1,10,2]},
|
||||
{"id":20,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[1000]}
|
||||
]
|
12
src/ReplicatedStorage/Json/PlayerLv.json
Normal file
12
src/ReplicatedStorage/Json/PlayerLv.json
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
{"id":1,"exp":10},
|
||||
{"id":2,"exp":20},
|
||||
{"id":3,"exp":30},
|
||||
{"id":4,"exp":40},
|
||||
{"id":5,"exp":50},
|
||||
{"id":6,"exp":60},
|
||||
{"id":7,"exp":70},
|
||||
{"id":8,"exp":80},
|
||||
{"id":9,"exp":90},
|
||||
{"id":10,"exp":100}
|
||||
]
|
@ -4,7 +4,7 @@ local Utils = {}
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
--> Variables
|
||||
local PlayerDataFolder = ReplicatedStorage.PlayerData
|
||||
local PlayerDataFolder = ReplicatedStorage:WaitForChild("PlayerData")
|
||||
|
||||
--> Constants
|
||||
|
||||
@ -38,15 +38,15 @@ function Utils:GenUniqueId(t: table)
|
||||
return min_id
|
||||
end
|
||||
|
||||
function Utils:GetJsonIdData(JsonName: string, id: number)
|
||||
local JsonData = require(ReplicatedStorage.Json[JsonName])
|
||||
for _, item in ipairs(JsonData) do
|
||||
if item.id == id then
|
||||
return item
|
||||
end
|
||||
end
|
||||
return nil -- 没找到对应id
|
||||
end
|
||||
-- function Utils:GetJsonIdData(JsonName: string, id: number)
|
||||
-- local JsonData = require(ReplicatedStorage.Json[JsonName])
|
||||
-- for _, item in ipairs(JsonData) do
|
||||
-- if item.id == id then
|
||||
-- return item
|
||||
-- end
|
||||
-- end
|
||||
-- return nil -- 没找到对应id
|
||||
-- end
|
||||
|
||||
function Utils:GetIdDataFromJson(JsonData: table, id: number)
|
||||
-- 遍历JsonData,查找id字段等于目标id的项
|
||||
@ -58,6 +58,34 @@ function Utils:GetIdDataFromJson(JsonData: table, id: number)
|
||||
return nil -- 没有找到对应id
|
||||
end
|
||||
|
||||
function Utils:GetMaxIdFromJson(JsonData: table)
|
||||
local maxId = 0
|
||||
for _, item in ipairs(JsonData) do
|
||||
if item.id > maxId then
|
||||
maxId = item.id
|
||||
end
|
||||
end
|
||||
return maxId
|
||||
end
|
||||
|
||||
-- 将字符串数组转化成真正的数组表,"[1,10,2,10]" -> {1,10,2,10}
|
||||
function Utils:StringArrayToTable(StringArray: string)
|
||||
if type(StringArray) ~= "string" then return {} end
|
||||
-- 去除首尾的中括号
|
||||
local content = StringArray:match("%[(.*)%]")
|
||||
if not content or content == "" then return {} end
|
||||
local result = {}
|
||||
for value in string.gmatch(content, "[^,]+") do
|
||||
local num = tonumber(value)
|
||||
if num then
|
||||
table.insert(result, num)
|
||||
else
|
||||
table.insert(result, value)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
return Utils
|
@ -1,303 +0,0 @@
|
||||
--[[
|
||||
Evercyan @ March 2023
|
||||
DataManager
|
||||
|
||||
DataManager handles all user data, like Levels, Tools, Armor, and even misc values that save, such as active armor.
|
||||
I would avoid messing around with the code here unless if you know what you're doing. Always make sure a change works properly
|
||||
before shipping the update out to players to avoid data loss (scary!!).
|
||||
|
||||
"PlayerData" is a reference to the Configuration under ReplicatedStorage that loads during runtime. Make sure to yield for this to exist.
|
||||
This Configuration houses all "pData" Configurations. These are individual player's data that houses many ValueBase objects,
|
||||
such as NumberValues, to easily access on the client & server.
|
||||
Like any instance, trying to change the data on the client will not replicate to the server.
|
||||
|
||||
While a solution like ProfileService & ReplicaService is recommended to avoid instances and lots of FindFirstChild calls, I still believe
|
||||
that this is the best solution for beginners due to the ease of use, and you're relying on Roblox as the source of truth.
|
||||
This makes it easier to edit values in run mode, especially if you aren't an experienced programmer.
|
||||
|
||||
IMPORTANT NOTE: ----
|
||||
'leaderstats' is a folder games use to make stats appear on the player list in-game. Please note that this does exist on the client-side,
|
||||
but attempting to change stats here will do nothing. All player data is actually stored under ReplicatedStorage.PlayerData.
|
||||
|
||||
If you're using third-party scripts that rely on leaderstats to be set to, you may need to alter the code to work with pData configs.
|
||||
]]
|
||||
|
||||
-- 主要加载和保存数据
|
||||
-- 生成对应实例,并把数据加载进去
|
||||
|
||||
--> Services
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local DataStoreService = game:GetService("DataStoreService")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
--> Dependencies
|
||||
local GameConfig = require(ReplicatedStorage.Data.GameConfig)
|
||||
local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary)
|
||||
|
||||
--> Variables
|
||||
local UserData = DataStoreService:GetDataStore("UserData")
|
||||
local SameKeyCooldown = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local PlayerData = Instance.new("Configuration")
|
||||
PlayerData.Name = "PlayerData"
|
||||
PlayerData.Parent = ReplicatedStorage
|
||||
|
||||
local _warn = warn
|
||||
local function warn(warning: string)
|
||||
_warn("DataManager Failure: ".. warning)
|
||||
end
|
||||
|
||||
-- Clamp the passed ValueObject (must be int/number) to the given bounds.
|
||||
local function ClampStat(ValueObject: IntValue, Min: number?, Max: number?)
|
||||
ValueObject.Changed:Connect(function()
|
||||
ValueObject.Value = math.clamp(ValueObject.Value, Min or -math.huge, Max or math.huge)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Create and return a stat value object, used for player stats
|
||||
-- ClampInfo is an optional table for clamping it to a min/max. If you don't need it, simply don't include it.
|
||||
local function CreateStat(ClassName: string, Name: string, DefaultValue, ClampInfo)
|
||||
local Stat = Instance.new(ClassName)
|
||||
Stat.Name = Name
|
||||
Stat.Value = DefaultValue
|
||||
if ClampInfo and (Stat:IsA("NumberValue") or Stat:IsA("IntValue")) then
|
||||
ClampStat(Stat, unpack(ClampInfo))
|
||||
end
|
||||
return Stat
|
||||
end
|
||||
|
||||
-- 这里生成实例,在Configuration里生成一个文件夹,然后生成对应的ValueObject
|
||||
local function CreateDataFolder(Player): Instance
|
||||
local pData = Instance.new("Configuration")
|
||||
pData.Name = Player.UserId
|
||||
|
||||
-- Stats
|
||||
local Stats = Instance.new("Folder")
|
||||
Stats.Name = "Stats"
|
||||
Stats.Parent = pData
|
||||
|
||||
local Level = CreateStat("NumberValue", "Level", 1, {1, GameConfig.MaxLevel})
|
||||
Level.Parent = Stats
|
||||
local XP = CreateStat("NumberValue", "XP", 0, {0})
|
||||
XP.Parent = Stats
|
||||
local Gold = CreateStat("NumberValue", "Gold", 0, {0})
|
||||
Gold.Parent = Stats
|
||||
|
||||
-- Items
|
||||
local Items = Instance.new("Folder")
|
||||
Items.Name = "Items"
|
||||
Items.Parent = pData
|
||||
|
||||
local Tool = Instance.new("Folder")
|
||||
Tool.Name = "Tool"
|
||||
Tool.Parent = Items
|
||||
|
||||
local Armor = Instance.new("Folder")
|
||||
Armor.Name = "Armor"
|
||||
Armor.Parent = Items
|
||||
|
||||
-- Preferences / Misc
|
||||
local ActiveArmor = CreateStat("StringValue", "ActiveArmor", "")
|
||||
ActiveArmor.Parent = pData
|
||||
|
||||
local Hotbar = Instance.new("Folder")
|
||||
Hotbar.Name = "Hotbar"
|
||||
Hotbar.Parent = pData
|
||||
|
||||
for n = 1, 9 do
|
||||
local ValueObject = CreateStat("StringValue", tostring(n), "")
|
||||
ValueObject.Parent = Hotbar
|
||||
end
|
||||
|
||||
return pData
|
||||
end
|
||||
|
||||
-- Unloads loaded user data table into their game session
|
||||
-- 这里把玩家数据加载到实例中
|
||||
local function UnloadData(Player: Player, Data: any, pData: Instance)
|
||||
-- Stats
|
||||
local Stats = pData:FindFirstChild("Stats")
|
||||
for StatName, StatValue in Data.Stats do
|
||||
local Stat = Stats:FindFirstChild(StatName)
|
||||
if Stat then
|
||||
Stat.Value = StatValue
|
||||
end
|
||||
end
|
||||
|
||||
local Items = pData:FindFirstChild("Items")
|
||||
|
||||
-- Tool
|
||||
local ToolFolder = Items:FindFirstChild("Tool")
|
||||
for _, ItemName in Data.Items.Tool do
|
||||
local Tool = ContentLibrary.Tool[ItemName]
|
||||
if Tool then
|
||||
Tool.Instance:Clone().Parent = Player:WaitForChild("StarterGear")
|
||||
Tool.Instance:Clone().Parent = Player:WaitForChild("Backpack")
|
||||
CreateStat("BoolValue", ItemName).Parent = ToolFolder
|
||||
end
|
||||
end
|
||||
|
||||
-- Armor
|
||||
local ArmorFolder = Items:FindFirstChild("Armor")
|
||||
for _, ItemName in Data.Items.Armor do
|
||||
local Armor = ContentLibrary.Armor[ItemName]
|
||||
if Armor then
|
||||
CreateStat("BoolValue", ItemName).Parent = ArmorFolder
|
||||
end
|
||||
end
|
||||
|
||||
-- Preferences / Misc
|
||||
(pData:FindFirstChild("ActiveArmor") :: StringValue).Value = Data.ActiveArmor
|
||||
|
||||
local HotbarFolder = pData:FindFirstChild("Hotbar")
|
||||
for SlotNumber, ItemName in Data.Hotbar do
|
||||
(HotbarFolder:FindFirstChild(SlotNumber) :: StringValue).Value = ItemName
|
||||
end
|
||||
end
|
||||
|
||||
-- Attempt to save user data. Returns whether or not the request was successful.
|
||||
local function SaveData(Player: Player): boolean
|
||||
if not Player:GetAttribute("DataLoaded") then
|
||||
return false
|
||||
end
|
||||
|
||||
local pData = PlayerData:FindFirstChild(Player.UserId)
|
||||
local StarterGear = Player:FindFirstChild("StarterGear")
|
||||
if not pData or not StarterGear then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Same Key Cooldown (can't write to the same key within 6 seconds)
|
||||
if SameKeyCooldown[Player.UserId] then
|
||||
repeat task.wait() until not SameKeyCooldown[Player.UserId]
|
||||
end
|
||||
SameKeyCooldown[Player.UserId] = true
|
||||
task.delay(6, function()
|
||||
SameKeyCooldown[Player.UserId] = nil
|
||||
end)
|
||||
|
||||
-- Compile "DataToSave" table, which we pass to GlobalDataStore:SetAsync --
|
||||
local DataToSave = {}
|
||||
|
||||
DataToSave.Stats = {}
|
||||
DataToSave.Items = {
|
||||
Tool = {},
|
||||
Armor = {}
|
||||
}
|
||||
|
||||
-- Stats
|
||||
local Stats = pData:FindFirstChild("Stats")
|
||||
for _, ValueObject in Stats:GetChildren() do
|
||||
DataToSave.Stats[ValueObject.Name] = ValueObject.Value
|
||||
end
|
||||
|
||||
local Items = pData:FindFirstChild("Items")
|
||||
|
||||
-- Tool
|
||||
local Tool = Items:FindFirstChild("Tool")
|
||||
for _, ValueObject in Tool:GetChildren() do
|
||||
if ContentLibrary.Tool[ValueObject.Name] then
|
||||
table.insert(DataToSave.Items.Tool, ValueObject.Name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Armor
|
||||
local Armor = Items:FindFirstChild("Armor")
|
||||
for _, ValueObject in Armor:GetChildren() do
|
||||
if ContentLibrary.Armor[ValueObject.Name] then
|
||||
table.insert(DataToSave.Items.Armor, ValueObject.Name)
|
||||
end
|
||||
end
|
||||
|
||||
-- Preferences / Misc
|
||||
DataToSave.ActiveArmor = pData.ActiveArmor.Value
|
||||
|
||||
DataToSave.Hotbar = {}
|
||||
for _, ValueObject in pData.Hotbar:GetChildren() do
|
||||
DataToSave.Hotbar[ValueObject.Name] = ValueObject.Value
|
||||
end
|
||||
|
||||
-- Save to DataStore --
|
||||
local Success
|
||||
for i = 1, 3 do
|
||||
Success = xpcall(function()
|
||||
return UserData:SetAsync("user/".. Player.UserId, DataToSave, {Player.UserId})
|
||||
end, warn)
|
||||
|
||||
if Success then
|
||||
break
|
||||
end
|
||||
task.wait(6)
|
||||
end
|
||||
|
||||
if Success then
|
||||
print(("DataManager: User %s's data saved successfully."):format(Player.Name))
|
||||
else
|
||||
warn(("DataManager: User %s's data failed to save."):format(Player.Name))
|
||||
end
|
||||
|
||||
return Success
|
||||
end
|
||||
|
||||
-- Attempt to load user data. Returns whether or not the request was successful, as well as the data if it was.
|
||||
local function LoadData(Player: Player): (boolean, any)
|
||||
local Success, Response = xpcall(function()
|
||||
return UserData:GetAsync("user/".. Player.UserId)
|
||||
end, warn)
|
||||
|
||||
if Success and Response then
|
||||
print(("DataManager: User %s's data loaded into the game with Level '%s'."):format(Player.Name, Response.Stats.Level))
|
||||
else
|
||||
print(("DataManager: User %s had no data to load from."):format(Player.Name))
|
||||
end
|
||||
|
||||
return Success, Response
|
||||
end
|
||||
|
||||
local function OnPlayerAdded(Player: Player)
|
||||
local Success, Data = LoadData(Player)
|
||||
|
||||
if not Success then
|
||||
CollectionService:AddTag(Player, "DataFailed")
|
||||
Player:Kick("Data unable to load. DataStore Service may be down. Please rejoin later.")
|
||||
return
|
||||
end
|
||||
|
||||
local pData = CreateDataFolder(Player)
|
||||
|
||||
if Data then
|
||||
UnloadData(Player, Data, pData)
|
||||
end
|
||||
|
||||
pData.Parent = PlayerData
|
||||
|
||||
Player:SetAttribute("DataLoaded", true)
|
||||
end
|
||||
|
||||
Players.PlayerAdded:Connect(OnPlayerAdded)
|
||||
for _, Player in Players:GetPlayers() do
|
||||
OnPlayerAdded(Player)
|
||||
end
|
||||
|
||||
-- Save on leave
|
||||
Players.PlayerRemoving:Connect(function(Player)
|
||||
SaveData(Player)
|
||||
end)
|
||||
|
||||
-- Server closing (save)
|
||||
game:BindToClose(function()
|
||||
task.wait(RunService:IsStudio() and 1 or 10)
|
||||
end)
|
||||
|
||||
-- Auto-save
|
||||
while true do
|
||||
task.wait(60)
|
||||
for _, Player in Players:GetPlayers() do
|
||||
task.defer(SaveData, Player)
|
||||
end
|
||||
end
|
@ -1,25 +0,0 @@
|
||||
-- 玩家基础信息代理
|
||||
local PlayerInfoProxy = {}
|
||||
|
||||
--> Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
--> Variables
|
||||
|
||||
--> Constants
|
||||
local STORE_NAME = "PlayerInfo"
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function PlayerInfoProxy:InitPlayer(Player: Player)
|
||||
|
||||
end
|
||||
|
||||
|
||||
function PlayerInfoProxy:OnPlayerRemoving(Player: Player)
|
||||
|
||||
end
|
||||
|
||||
|
||||
ReplicatedStorage.Remotes.PlayerRemoving:Connect(PlayerInfoProxy.OnPlayerRemoving)
|
||||
|
||||
return PlayerInfoProxy
|
@ -12,17 +12,38 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
-- 加载Proxy目录下的所有代理
|
||||
local Proxies = {}
|
||||
local ProxyFolder = ServerStorage:FindFirstChild("Proxy")
|
||||
if ProxyFolder then
|
||||
for _, proxyModule in ipairs(ProxyFolder:GetChildren()) do
|
||||
if proxyModule:IsA("ModuleScript") then
|
||||
local success, result = pcall(require, proxyModule)
|
||||
if success then
|
||||
-- 去掉文件名后缀
|
||||
local name = proxyModule.Name
|
||||
Proxies[name] = result
|
||||
else
|
||||
warn("加载代理模块失败: " .. proxyModule.Name, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--> References
|
||||
local PlayerData = ReplicatedStorage:WaitForChild("PlayerData")
|
||||
-- 由ArchiveProxy生成
|
||||
local PlayerDataFolder = ReplicatedStorage:WaitForChild("PlayerData")
|
||||
|
||||
--> Dependencies
|
||||
local HumanoidAttributes = require(script.HumanoidAttributes)
|
||||
local PlayerLeveling = require(script.PlayerLeveling)
|
||||
-- local HumanoidAttributes = require(script.HumanoidAttributes)
|
||||
-- local PlayerLeveling = require(script.PlayerLeveling)
|
||||
|
||||
local GameConfig = require(ReplicatedStorage.Data.GameConfig)
|
||||
local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary)
|
||||
local ToolLib = require(ServerStorage.Modules.ToolLib)
|
||||
local ArmorLib = require(ServerStorage.Modules.ArmorLib)
|
||||
-- local GameConfig = require(ReplicatedStorage.Data.GameConfig)
|
||||
-- local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary)
|
||||
-- local ToolLib = require(ServerStorage.Modules.ToolLib)
|
||||
-- local ArmorLib = require(ServerStorage.Modules.ArmorLib)
|
||||
|
||||
-- local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -39,29 +60,6 @@ local Temporary = CreateFolder("Temporary", workspace)
|
||||
local ProjectileCache = CreateFolder("ProjectileCache", Temporary)
|
||||
local Characters = CreateFolder("Characters", workspace)
|
||||
|
||||
-- 初始化玩家信息存储目录(沟通作用,具体数据还得后端处理)
|
||||
local PlayerDataFolder = Instance.new("Configuration")
|
||||
PlayerDataFolder.Name = "PlayerData"
|
||||
PlayerDataFolder.Parent = ReplicatedStorage
|
||||
|
||||
-- 加载Proxy目录下的所有代理
|
||||
local Proxies = {}
|
||||
local ProxyFolder = script.Parent.Parent:FindFirstChild("Proxy")
|
||||
if ProxyFolder then
|
||||
for _, proxyModule in ipairs(ProxyFolder:GetChildren()) do
|
||||
if proxyModule:IsA("ModuleScript") then
|
||||
local success, result = pcall(require, proxyModule)
|
||||
if success then
|
||||
-- 去掉文件名后缀
|
||||
local name = proxyModule.Name
|
||||
Proxies[name] = result
|
||||
else
|
||||
warn("加载代理模块失败: " .. proxyModule.Name, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Initially require all server-sided & shared modules
|
||||
-- 加载模块 ReplicatedStorage.Modules, ServerStorage.Modules
|
||||
for _, Location in {ReplicatedStorage.Modules, ServerStorage.Modules} do
|
||||
@ -79,16 +77,26 @@ local function OnPlayerAdded(Player: Player)
|
||||
warn("玩家数据未加载: " .. Player.Name)
|
||||
return
|
||||
end
|
||||
-- 玩家数据加载成功
|
||||
print("玩家数据加载成功: ", Proxies.ArchiveProxy.pData[Player.UserId])
|
||||
|
||||
local pData = Instance.new("Configuration")
|
||||
pData.Name = Player.UserId
|
||||
pData.Parent = PlayerDataFolder
|
||||
|
||||
-- 初始化玩家数据目录(其他具体内容在Proxy中处理)
|
||||
if not Proxies.ArchiveProxy.pData[Player.UserId] then
|
||||
Proxies.ArchiveProxy.pData[Player.UserId] = {}
|
||||
end
|
||||
|
||||
-- 加载对应玩家的其他系统代理
|
||||
Proxies.EquipmentProxy:InitPlayer(Player)
|
||||
Proxies.PlayerInfoProxy:InitPlayer(Player)
|
||||
Proxies.LevelProxy:InitPlayer(Player)
|
||||
end
|
||||
|
||||
local function OnPlayerRemoving(Player: Player)
|
||||
-- 清理目录(整体数据在ArchiveProxy中清除)
|
||||
local pData = PlayerDataFolder:FindFirstChild(Player.UserId)
|
||||
if pData then pData:Destroy() end
|
||||
end
|
||||
|
@ -1,165 +1,165 @@
|
||||
--[[
|
||||
Evercyan @ March 2023
|
||||
ArmorLib
|
||||
-- --[[
|
||||
-- Evercyan @ March 2023
|
||||
-- ArmorLib
|
||||
|
||||
ArmorLib is an item library that houses code that can be ran on the server relating
|
||||
to Armor, such as ArmorLib:Give(Player, Armor (ContentLib.Armor[...])), as well
|
||||
as equipping & unequipping armor, which is primarily ran through remotes fired from the
|
||||
client's Inventory Gui.
|
||||
]]
|
||||
-- ArmorLib is an item library that houses code that can be ran on the server relating
|
||||
-- to Armor, such as ArmorLib:Give(Player, Armor (ContentLib.Armor[...])), as well
|
||||
-- as equipping & unequipping armor, which is primarily ran through remotes fired from the
|
||||
-- client's Inventory Gui.
|
||||
-- ]]
|
||||
|
||||
--> Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local Players = game:GetService("Players")
|
||||
-- --> Services
|
||||
-- local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
-- local Players = game:GetService("Players")
|
||||
|
||||
--> References
|
||||
local PlayerData = ReplicatedStorage:WaitForChild("PlayerData")
|
||||
-- --> References
|
||||
-- local PlayerData = ReplicatedStorage:WaitForChild("PlayerData")
|
||||
|
||||
--> Dependencies
|
||||
local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary)
|
||||
local Morph = require(script.Morph)
|
||||
-- --> Dependencies
|
||||
-- local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary)
|
||||
-- local Morph = require(script.Morph)
|
||||
|
||||
--> Variables
|
||||
-- --> Variables
|
||||
local ArmorLib = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- --------------------------------------------------------------------------------
|
||||
|
||||
-- Adds the armor to the player's data
|
||||
function ArmorLib:Give(Player: Player, Armor)
|
||||
local pData = PlayerData:WaitForChild(Player.UserId, 5)
|
||||
-- -- Adds the armor to the player's data
|
||||
-- function ArmorLib:Give(Player: Player, Armor)
|
||||
-- local pData = PlayerData:WaitForChild(Player.UserId, 5)
|
||||
|
||||
if pData then
|
||||
if not pData.Items.Armor:FindFirstChild(Armor.Name) then
|
||||
local ValueObject = Instance.new("BoolValue")
|
||||
ValueObject.Name = Armor.Name
|
||||
ValueObject.Parent = pData.Items.Armor
|
||||
end
|
||||
else
|
||||
warn(("pData for Player '%s' doesn't exist! Did they leave?"):format(Player.Name))
|
||||
end
|
||||
end
|
||||
-- if pData then
|
||||
-- if not pData.Items.Armor:FindFirstChild(Armor.Name) then
|
||||
-- local ValueObject = Instance.new("BoolValue")
|
||||
-- ValueObject.Name = Armor.Name
|
||||
-- ValueObject.Parent = pData.Items.Armor
|
||||
-- end
|
||||
-- else
|
||||
-- warn(("pData for Player '%s' doesn't exist! Did they leave?"):format(Player.Name))
|
||||
-- end
|
||||
-- end
|
||||
|
||||
function ArmorLib:Trash(Player: Player, Armor)
|
||||
local pData = PlayerData:WaitForChild(Player.UserId, 5)
|
||||
-- function ArmorLib:Trash(Player: Player, Armor)
|
||||
-- local pData = PlayerData:WaitForChild(Player.UserId, 5)
|
||||
|
||||
if pData then
|
||||
if pData.Items.Armor:FindFirstChild(Armor.Name) then
|
||||
pData.Items.Armor[Armor.Name]:Destroy()
|
||||
end
|
||||
if pData.ActiveArmor.Value == Armor.Name then
|
||||
pData.ActiveArmor.Value = ""
|
||||
ArmorLib:UnequipArmor(Player)
|
||||
end
|
||||
else
|
||||
warn(("pData for Player '%s' doesn't exist! Did they leave?"):format(Player.Name))
|
||||
end
|
||||
end
|
||||
-- if pData then
|
||||
-- if pData.Items.Armor:FindFirstChild(Armor.Name) then
|
||||
-- pData.Items.Armor[Armor.Name]:Destroy()
|
||||
-- end
|
||||
-- if pData.ActiveArmor.Value == Armor.Name then
|
||||
-- pData.ActiveArmor.Value = ""
|
||||
-- ArmorLib:UnequipArmor(Player)
|
||||
-- end
|
||||
-- else
|
||||
-- warn(("pData for Player '%s' doesn't exist! Did they leave?"):format(Player.Name))
|
||||
-- end
|
||||
-- end
|
||||
|
||||
function ArmorLib:EquipArmor(Player: Player, Armor)
|
||||
local Character = Player.Character
|
||||
local Humanoid = Character and Character:FindFirstChild("Humanoid")
|
||||
local Attributes = Humanoid and Humanoid:WaitForChild("Attributes", 1)
|
||||
if not Attributes or Humanoid.Health <= 0 then return end
|
||||
-- function ArmorLib:EquipArmor(Player: Player, Armor)
|
||||
-- local Character = Player.Character
|
||||
-- local Humanoid = Character and Character:FindFirstChild("Humanoid")
|
||||
-- local Attributes = Humanoid and Humanoid:WaitForChild("Attributes", 1)
|
||||
-- if not Attributes or Humanoid.Health <= 0 then return end
|
||||
|
||||
-- Humanoid changes
|
||||
Attributes.Health:SetAttribute("Armor", Armor.Config.Health)
|
||||
Attributes.WalkSpeed:SetAttribute("Armor", Armor.Config.WalkSpeed)
|
||||
Attributes.JumpPower:SetAttribute("Armor", Armor.Config.JumpPower)
|
||||
-- -- Humanoid changes
|
||||
-- Attributes.Health:SetAttribute("Armor", Armor.Config.Health)
|
||||
-- Attributes.WalkSpeed:SetAttribute("Armor", Armor.Config.WalkSpeed)
|
||||
-- Attributes.JumpPower:SetAttribute("Armor", Armor.Config.JumpPower)
|
||||
|
||||
-- Morph changes
|
||||
Morph:ApplyOutfit(Player, Armor)
|
||||
end
|
||||
-- -- Morph changes
|
||||
-- Morph:ApplyOutfit(Player, Armor)
|
||||
-- end
|
||||
|
||||
function ArmorLib:UnequipArmor(Player: Player)
|
||||
local Character = Player.Character
|
||||
local Humanoid = Character and Character:FindFirstChild("Humanoid")
|
||||
local Attributes = Humanoid and Humanoid:FindFirstChild("Attributes")
|
||||
if not Attributes then return end
|
||||
-- function ArmorLib:UnequipArmor(Player: Player)
|
||||
-- local Character = Player.Character
|
||||
-- local Humanoid = Character and Character:FindFirstChild("Humanoid")
|
||||
-- local Attributes = Humanoid and Humanoid:FindFirstChild("Attributes")
|
||||
-- if not Attributes then return end
|
||||
|
||||
-- Humanoid changes
|
||||
Attributes.Health:SetAttribute("Armor", nil)
|
||||
Attributes.WalkSpeed:SetAttribute("Armor", nil)
|
||||
Attributes.JumpPower:SetAttribute("Armor", nil)
|
||||
-- -- Humanoid changes
|
||||
-- Attributes.Health:SetAttribute("Armor", nil)
|
||||
-- Attributes.WalkSpeed:SetAttribute("Armor", nil)
|
||||
-- Attributes.JumpPower:SetAttribute("Armor", nil)
|
||||
|
||||
-- Morph changes
|
||||
Morph:ClearOutfit(Player)
|
||||
end
|
||||
-- -- Morph changes
|
||||
-- Morph:ClearOutfit(Player)
|
||||
-- end
|
||||
|
||||
---- Remotes -------------------------------------------------------------------
|
||||
-- ---- Remotes -------------------------------------------------------------------
|
||||
|
||||
local ChangeCd = {}
|
||||
-- local ChangeCd = {}
|
||||
|
||||
ReplicatedStorage.Remotes.EquipArmor.OnServerInvoke = function(Player, ArmorName: string)
|
||||
if not ArmorName or typeof(ArmorName) ~= "string" then
|
||||
return
|
||||
end
|
||||
-- ReplicatedStorage.Remotes.EquipArmor.OnServerInvoke = function(Player, ArmorName: string)
|
||||
-- if not ArmorName or typeof(ArmorName) ~= "string" then
|
||||
-- return
|
||||
-- end
|
||||
|
||||
local Armor = ContentLibrary.Armor[ArmorName]
|
||||
-- local Armor = ContentLibrary.Armor[ArmorName]
|
||||
|
||||
if Armor and not ChangeCd[Player.UserId] then
|
||||
ChangeCd[Player.UserId] = true
|
||||
task.delay(0.25, function()
|
||||
ChangeCd[Player.UserId] = nil
|
||||
end)
|
||||
-- if Armor and not ChangeCd[Player.UserId] then
|
||||
-- ChangeCd[Player.UserId] = true
|
||||
-- task.delay(0.25, function()
|
||||
-- ChangeCd[Player.UserId] = nil
|
||||
-- end)
|
||||
|
||||
ArmorLib:EquipArmor(Player, Armor)
|
||||
-- ArmorLib:EquipArmor(Player, Armor)
|
||||
|
||||
local pData = PlayerData:FindFirstChild(Player.UserId)
|
||||
if pData and pData.Items.Armor[ArmorName] and Armor then
|
||||
pData.ActiveArmor.Value = ArmorName
|
||||
end
|
||||
end
|
||||
end
|
||||
-- local pData = PlayerData:FindFirstChild(Player.UserId)
|
||||
-- if pData and pData.Items.Armor[ArmorName] and Armor then
|
||||
-- pData.ActiveArmor.Value = ArmorName
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
ReplicatedStorage.Remotes.UnequipArmor.OnServerInvoke = function(Player)
|
||||
ArmorLib:UnequipArmor(Player)
|
||||
-- ReplicatedStorage.Remotes.UnequipArmor.OnServerInvoke = function(Player)
|
||||
-- ArmorLib:UnequipArmor(Player)
|
||||
|
||||
local pData = PlayerData:FindFirstChild(Player.UserId)
|
||||
if pData then
|
||||
pData.ActiveArmor.Value = ""
|
||||
end
|
||||
end
|
||||
-- local pData = PlayerData:FindFirstChild(Player.UserId)
|
||||
-- if pData then
|
||||
-- pData.ActiveArmor.Value = ""
|
||||
-- end
|
||||
-- end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- --------------------------------------------------------------------------------
|
||||
|
||||
local function OnPlayerAdded(Player: Player)
|
||||
local pData = PlayerData:WaitForChild(Player.UserId)
|
||||
-- local function OnPlayerAdded(Player: Player)
|
||||
-- local pData = PlayerData:WaitForChild(Player.UserId)
|
||||
|
||||
local function OnCharacterAdded(Character)
|
||||
local ActiveArmor = pData:WaitForChild("ActiveArmor")
|
||||
local Armor = ActiveArmor.Value ~= "" and ContentLibrary.Armor[ActiveArmor.Value]
|
||||
-- local function OnCharacterAdded(Character)
|
||||
-- local ActiveArmor = pData:WaitForChild("ActiveArmor")
|
||||
-- local Armor = ActiveArmor.Value ~= "" and ContentLibrary.Armor[ActiveArmor.Value]
|
||||
|
||||
-- Update any incoming accessories (CharacterAppearanceLoaded is really broken lol)
|
||||
local Connection = Character.ChildAdded:Connect(function(Child)
|
||||
if Child:IsA("Accessory") then
|
||||
Morph:UpdateAccessoriesTransparency(Character, ActiveArmor.Value ~= "" and ContentLibrary.Armor[ActiveArmor.Value])
|
||||
end
|
||||
end)
|
||||
Player.CharacterRemoving:Once(function()
|
||||
Connection:Disconnect()
|
||||
end)
|
||||
-- -- Update any incoming accessories (CharacterAppearanceLoaded is really broken lol)
|
||||
-- local Connection = Character.ChildAdded:Connect(function(Child)
|
||||
-- if Child:IsA("Accessory") then
|
||||
-- Morph:UpdateAccessoriesTransparency(Character, ActiveArmor.Value ~= "" and ContentLibrary.Armor[ActiveArmor.Value])
|
||||
-- end
|
||||
-- end)
|
||||
-- Player.CharacterRemoving:Once(function()
|
||||
-- Connection:Disconnect()
|
||||
-- end)
|
||||
|
||||
if Armor then
|
||||
if Player:HasAppearanceLoaded() then
|
||||
ArmorLib:EquipArmor(Player, Armor)
|
||||
else
|
||||
Player.CharacterAppearanceLoaded:Once(function()
|
||||
ArmorLib:EquipArmor(Player, Armor)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- if Armor then
|
||||
-- if Player:HasAppearanceLoaded() then
|
||||
-- ArmorLib:EquipArmor(Player, Armor)
|
||||
-- else
|
||||
-- Player.CharacterAppearanceLoaded:Once(function()
|
||||
-- ArmorLib:EquipArmor(Player, Armor)
|
||||
-- end)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
Player.CharacterAdded:Connect(OnCharacterAdded)
|
||||
if Player.Character then
|
||||
OnCharacterAdded(Player.Character)
|
||||
end
|
||||
end
|
||||
-- Player.CharacterAdded:Connect(OnCharacterAdded)
|
||||
-- if Player.Character then
|
||||
-- OnCharacterAdded(Player.Character)
|
||||
-- end
|
||||
-- end
|
||||
|
||||
for _, Player in Players:GetChildren() do
|
||||
task.defer(OnPlayerAdded, Player)
|
||||
end
|
||||
-- for _, Player in Players:GetChildren() do
|
||||
-- task.defer(OnPlayerAdded, Player)
|
||||
-- end
|
||||
|
||||
Players.PlayerAdded:Connect(OnPlayerAdded)
|
||||
-- Players.PlayerAdded:Connect(OnPlayerAdded)
|
||||
|
||||
return ArmorLib
|
@ -1,6 +1,8 @@
|
||||
-- 数据存储代理
|
||||
local ArchiveProxy = {}
|
||||
|
||||
print("进入")
|
||||
|
||||
--> Services
|
||||
local CollectionService = game:GetService("CollectionService")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
@ -14,7 +16,7 @@ local GameConfig = require(ReplicatedStorage.Data.GameConfig)
|
||||
local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary)
|
||||
|
||||
--> Variables
|
||||
local UserData = DataStoreService:GetDataStore("UserData")
|
||||
local UserData = DataStoreService:GetDataStore("UserData3")
|
||||
local SameKeyCooldown = {}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@ -48,23 +50,7 @@ local function SaveData(Player: Player): boolean
|
||||
return false
|
||||
end
|
||||
|
||||
local pData = PlayerData:FindFirstChild(Player.UserId)
|
||||
local StarterGear = Player:FindFirstChild("StarterGear")
|
||||
if not pData or not StarterGear then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Same Key Cooldown (can't write to the same key within 6 seconds)
|
||||
if SameKeyCooldown[Player.UserId] then
|
||||
repeat task.wait() until not SameKeyCooldown[Player.UserId]
|
||||
end
|
||||
SameKeyCooldown[Player.UserId] = true
|
||||
task.delay(6, function()
|
||||
SameKeyCooldown[Player.UserId] = nil
|
||||
end)
|
||||
|
||||
-- Compile "DataToSave" table, which we pass to GlobalDataStore:SetAsync --
|
||||
local DataToSave = {}
|
||||
local DataToSave = ArchiveProxy.pData[Player.UserId]
|
||||
|
||||
-- Save to DataStore --
|
||||
local Success
|
||||
@ -95,7 +81,8 @@ local function LoadData(Player: Player): (boolean, any)
|
||||
end, warn)
|
||||
|
||||
if Success and Response then
|
||||
print(("DataManager: User %s's data loaded into the game with Level '%s'."):format(Player.Name, Response.Stats.Level))
|
||||
print(("DataManager: User %s's data loaded into the game."):format(Player.Name))
|
||||
print(Response)
|
||||
else
|
||||
print(("DataManager: User %s had no data to load from."):format(Player.Name))
|
||||
end
|
||||
@ -115,11 +102,16 @@ local function OnPlayerAdded(Player: Player)
|
||||
ArchiveProxy.pData = {}
|
||||
end
|
||||
|
||||
-- 如果数据为空,则初始化数据
|
||||
if not Data then
|
||||
Data = {}
|
||||
end
|
||||
|
||||
ArchiveProxy.pData[Player.UserId] = Data
|
||||
Player:SetAttribute("DataLoaded", true)
|
||||
end
|
||||
|
||||
local function OnPlayerRemoving(Player: Player)
|
||||
local function OnPlayerRemoving(Player: string)
|
||||
SaveData(Player)
|
||||
ArchiveProxy.pData[Player.UserId] = nil
|
||||
ReplicatedStorage.Remotes.PlayerRemoving:Fire(Player)
|
||||
@ -139,7 +131,6 @@ game:BindToClose(function()
|
||||
task.wait(RunService:IsStudio() and 1 or 10)
|
||||
end)
|
||||
|
||||
|
||||
-- Auto-save
|
||||
task.spawn(function()
|
||||
while true do
|
@ -3,18 +3,22 @@ local EquipmentProxy = {}
|
||||
|
||||
--> Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
|
||||
--> Variables
|
||||
local Utils = require(ReplicatedStorage.Tools.Utils)
|
||||
local EquipmentJsonData = require(ReplicatedStorage.Json.Equipment)
|
||||
local ArchiveProxy = require(ReplicatedStorage.Modules.ArchiveProxy)
|
||||
local PlayerInfoProxy = require(ReplicatedStorage.Modules.PlayerInfoProxy)
|
||||
local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy)
|
||||
local PlayerInfoProxy = require(ServerStorage.Proxy.PlayerInfoProxy)
|
||||
|
||||
--> Json
|
||||
local JsonEquipment = require(ReplicatedStorage.Json.Equipment)
|
||||
|
||||
--> Constants
|
||||
local STORE_NAME = "Equipment"
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 获取装备文件夹
|
||||
local function GetPlayerEquipmentFolder(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
@ -22,10 +26,10 @@ local function GetPlayerEquipmentFolder(Player: Player)
|
||||
return EquipmentFolder
|
||||
end
|
||||
|
||||
|
||||
-- 创建装备实例
|
||||
local function CreateEquipmentInstance(Player: Player, UniqueId: number, EquipmentData: table)
|
||||
if Player or UniqueId or EquipmentData then
|
||||
warn('创建装备实例失败: ' .. Player.Name .. ' ' .. UniqueId .. ' ' .. EquipmentData)
|
||||
warn('创建装备实例失败: ' , Player.Name, UniqueId, EquipmentData)
|
||||
return
|
||||
end
|
||||
local PlayerEquipmentFolder = GetPlayerEquipmentFolder(Player)
|
||||
@ -40,29 +44,30 @@ end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 初始化玩家
|
||||
function EquipmentProxy:InitPlayer(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
local EquipmentFolder = Utils:CreateFolder("Equipment", pData)
|
||||
Utils:CreateFolder("Equipment", pData)
|
||||
|
||||
-- 初始化数据存储
|
||||
if not ArchiveProxy.pData[Player.UserId] then
|
||||
ArchiveProxy.pData[Player.UserId] = {}
|
||||
-- 新玩家数据初始化
|
||||
if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then
|
||||
ArchiveProxy.pData[Player.UserId][STORE_NAME] = {}
|
||||
end
|
||||
|
||||
-- 初始化装备
|
||||
for uniqueId, EquipmentData in ArchiveProxy.pData[Player.UserId] do
|
||||
for uniqueId, EquipmentData in ArchiveProxy.pData[Player.UserId][STORE_NAME] do
|
||||
CreateEquipmentInstance(Player, uniqueId, EquipmentData)
|
||||
end
|
||||
end
|
||||
|
||||
local EXCEPT_KEYS = { "id", "orgId", "name"}
|
||||
local EXCEPT_KEYS = { "id", "orgId", "name", "attributes"}
|
||||
-- 添加装备到背包
|
||||
function EquipmentProxy:AddEquipment(Player: Player, EquipmentId: number)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
|
||||
local EquipmentData = Utils:GetJsonIdData("Equipment", EquipmentId)
|
||||
local EquipmentData = Utils:GetIdDataFromJson(JsonEquipment, EquipmentId)
|
||||
if not EquipmentData then return end
|
||||
|
||||
local UniqueId = Utils:GenUniqueId(ArchiveProxy.pData[Player.UserId])
|
||||
@ -79,7 +84,7 @@ function EquipmentProxy:AddEquipment(Player: Player, EquipmentId: number)
|
||||
ResultData.wearing = false
|
||||
|
||||
-- 其他随机词条内容添加在下面
|
||||
-- 根据词条内容直接生成回收奖励
|
||||
-- 之后回收修改随机生成
|
||||
|
||||
------------------------------------------------------------
|
||||
|
||||
@ -87,17 +92,16 @@ function EquipmentProxy:AddEquipment(Player: Player, EquipmentId: number)
|
||||
CreateEquipmentInstance(Player, UniqueId, ResultData)
|
||||
end
|
||||
|
||||
-- 回收装备
|
||||
function EquipmentProxy:RecycleEquipment(Player: Player, EquipmentId: number)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
|
||||
local EquipmentData = Utils:GetJsonIdData("Equipment", EquipmentId)
|
||||
local EquipmentData = ArchiveProxy.pData[Player.UserId][STORE_NAME][EquipmentId]
|
||||
if not EquipmentData then return end
|
||||
|
||||
if not ArchiveProxy.pData[Player.UserId][EquipmentId] then return end
|
||||
|
||||
-- 回收装备返回金币
|
||||
-- 调用PlayerInfoProxy来增加货币
|
||||
PlayerInfoProxy:ChangeItem(Player, 1, EquipmentData.recycle)
|
||||
|
||||
ArchiveProxy.pData[Player.UserId][EquipmentId] = nil
|
||||
local EquipmentInstance = GetPlayerEquipmentFolder(Player):FindFirstChild(EquipmentId)
|
||||
@ -106,6 +110,7 @@ function EquipmentProxy:RecycleEquipment(Player: Player, EquipmentId: number)
|
||||
end
|
||||
end
|
||||
|
||||
-- 穿戴装备
|
||||
function EquipmentProxy:WearEquipment(Player: Player, EquipmentId: number)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
@ -119,6 +124,8 @@ function EquipmentProxy:OnPlayerRemoving(Player: Player)
|
||||
end
|
||||
|
||||
|
||||
ReplicatedStorage.Remotes.PlayerRemoving:Connect(EquipmentProxy.OnPlayerRemoving)
|
||||
ReplicatedStorage.Remotes.PlayerRemoving.Event:Connect(function(PlayerUserId: string)
|
||||
EquipmentProxy:OnPlayerRemoving(PlayerUserId)
|
||||
end)
|
||||
|
||||
return EquipmentProxy
|
31
src/ServerStorage/Proxy/ItemProxy.luau
Normal file
31
src/ServerStorage/Proxy/ItemProxy.luau
Normal file
@ -0,0 +1,31 @@
|
||||
-- 道具代理
|
||||
local ItemProxy = {}
|
||||
|
||||
--> Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
|
||||
--> Json
|
||||
local JsonItem = require(ReplicatedStorage.Json.ItemProp)
|
||||
|
||||
--> Variables
|
||||
local Utils = require(ReplicatedStorage.Tools.Utils)
|
||||
-- local ArchiveProxy = require(ReplicatedStorage.Modules.ArchiveProxy)
|
||||
local PlayerInfoProxy = require(ServerStorage.Proxy.PlayerInfoProxy)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function ItemProxy:AddItem(Player: Player, ItemId: number, ItemCount: number)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
|
||||
local ItemData = Utils:GetIdDataFromJson(JsonItem, ItemId)
|
||||
if not ItemData then return end
|
||||
|
||||
-- 之后根据不同类型做处理
|
||||
if ItemData.type == 1 then
|
||||
PlayerInfoProxy:ChangeItemCount(Player, ItemId, ItemCount)
|
||||
end
|
||||
end
|
||||
|
||||
return ItemProxy
|
125
src/ServerStorage/Proxy/LevelProxy.luau
Normal file
125
src/ServerStorage/Proxy/LevelProxy.luau
Normal file
@ -0,0 +1,125 @@
|
||||
-- 关卡代理
|
||||
local LevelProxy = {}
|
||||
|
||||
--> Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
--> Variables
|
||||
local Utils = require(ReplicatedStorage.Tools.Utils)
|
||||
local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy)
|
||||
|
||||
--> Json
|
||||
local JsonLevel = require(ReplicatedStorage.Json.Level)
|
||||
|
||||
--> Constants
|
||||
local STORE_NAME = "Level"
|
||||
local ENUM_LEVEL_TYPE = {
|
||||
Main = 1,
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 初始化生成关卡目录
|
||||
local LevelFolder = Utils:CreateFolder(STORE_NAME, game.Workspace)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 获取玩家关卡文件夹
|
||||
local function GetPlayerLevelFolder(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
local LevelFolder = pData:FindFirstChild("Level")
|
||||
return LevelFolder
|
||||
end
|
||||
|
||||
-- 获取玩家关卡Workspace目录
|
||||
local function GetPlayerLevelWorkspaceFolder(PlayerUserId: string)
|
||||
return LevelFolder:FindFirstChild(PlayerUserId)
|
||||
end
|
||||
|
||||
-- 创建关卡信息实例
|
||||
local function CreateLevelInstance(Player: Player, Folder: Instance, LevelKey: string, LevelValue: number)
|
||||
if not Player or not Folder or not LevelKey or not LevelValue then return end
|
||||
local LevelInstance = Instance.new("NumberValue")
|
||||
LevelInstance.Name = LevelKey
|
||||
LevelInstance.Parent = Folder
|
||||
LevelInstance.Value = LevelValue
|
||||
return LevelInstance
|
||||
end
|
||||
|
||||
-- 初始化玩家关卡信息
|
||||
local function ExtraAddPlayerLevel(Player: Player, LevelData: table)
|
||||
if not Player or not LevelData then return end
|
||||
-- 如果列表中不包含信息就添加到表中
|
||||
for LevelKey, LevelValue in ENUM_LEVEL_TYPE do
|
||||
if not LevelData[LevelKey] then
|
||||
LevelData[LevelKey] = LevelValue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function LevelProxy:InitPlayer(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
local LevelFolder = Utils:CreateFolder(STORE_NAME, pData)
|
||||
local ProgressFolder = Utils:CreateFolder("Progress", LevelFolder)
|
||||
local DungeonFolder = Utils:CreateFolder("Dungeon", LevelFolder)
|
||||
-- 当前关卡状态
|
||||
Utils:CreateFolder("Stats", LevelFolder)
|
||||
|
||||
-- 关卡目录下生成玩家目录
|
||||
local spawnFloder = Utils:CreateFolder(Player.UserId, game.Workspace:FindFirstChild(STORE_NAME))
|
||||
print("spawnFloder: ", spawnFloder.Name)
|
||||
|
||||
-- 新玩家数据初始化
|
||||
if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then
|
||||
ArchiveProxy.pData[Player.UserId][STORE_NAME] = {}
|
||||
ArchiveProxy.pData[Player.UserId][STORE_NAME].Progress = {}
|
||||
-- 副本之后再做
|
||||
ArchiveProxy.pData[Player.UserId][STORE_NAME].Dungeon = {}
|
||||
end
|
||||
|
||||
ExtraAddPlayerLevel(Player, ArchiveProxy.pData[Player.UserId][STORE_NAME].Progress)
|
||||
|
||||
-- 前端变化
|
||||
for LevelKey, LevelValue in ArchiveProxy.pData[Player.UserId][STORE_NAME].Progress do
|
||||
CreateLevelInstance(Player, ProgressFolder, LevelKey, LevelValue)
|
||||
end
|
||||
end
|
||||
|
||||
-- 挑战关卡(挑战副本用另一个函数)
|
||||
function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
|
||||
-- 给前端传数据,做表现
|
||||
|
||||
-- 场景后端生成
|
||||
|
||||
-- 后端生成当前关卡状态数据
|
||||
end
|
||||
|
||||
-- 挑战结束
|
||||
function LevelProxy:ChallengeEnd(Player: Player)
|
||||
-- 判断玩家是否通关
|
||||
-- 通关后,没到最大关卡,关卡进度+1
|
||||
-- 到达最大关卡不做处理
|
||||
end
|
||||
|
||||
function LevelProxy:OnPlayerRemoving(Player: Player)
|
||||
local PlayerLevelFolder = GetPlayerLevelWorkspaceFolder(Player.UserId)
|
||||
if PlayerLevelFolder then
|
||||
PlayerLevelFolder:Destroy()
|
||||
end
|
||||
end
|
||||
|
||||
-- ReplicatedStorage.Remotes.PlayerRemoving.Event:Connect(function(PlayerUserId: string)
|
||||
-- LevelProxy:OnPlayerRemoving(PlayerUserId)
|
||||
-- end)
|
||||
|
||||
Players.PlayerRemoving:Connect(function(Player: Player)
|
||||
LevelProxy:OnPlayerRemoving(Player)
|
||||
end)
|
||||
|
||||
return LevelProxy
|
4
src/ServerStorage/Proxy/MobAIProxy.luau
Normal file
4
src/ServerStorage/Proxy/MobAIProxy.luau
Normal file
@ -0,0 +1,4 @@
|
||||
-- 怪物AI代理
|
||||
local MobAIProxy = {}
|
||||
|
||||
return MobAIProxy
|
172
src/ServerStorage/Proxy/PlayerInfoProxy.luau
Normal file
172
src/ServerStorage/Proxy/PlayerInfoProxy.luau
Normal file
@ -0,0 +1,172 @@
|
||||
-- 玩家基础信息代理
|
||||
local PlayerInfoProxy = {}
|
||||
|
||||
--> Services
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
|
||||
--> Variables
|
||||
local Utils = require(ReplicatedStorage.Tools.Utils)
|
||||
local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy)
|
||||
|
||||
--> Json
|
||||
local JsonPlayerLv = require(ReplicatedStorage.Json.PlayerLv)
|
||||
local JsonItem = require(ReplicatedStorage.Json.ItemProp)
|
||||
|
||||
--> Constants
|
||||
local STORE_NAME = "PlayerInfo"
|
||||
local ENUM_STATE_TYPE = {
|
||||
Number = "NumberValue",
|
||||
String = "StringValue",
|
||||
Bool = "BoolValue",
|
||||
Vector3 = "Vector3Value",
|
||||
Vector2 = "Vector2Value",
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 获取玩家信息文件夹
|
||||
local function GetPlayerInfoFolder(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
local PlayerInfoFolder = pData:FindFirstChild("PlayerInfo")
|
||||
return PlayerInfoFolder
|
||||
end
|
||||
|
||||
-- 创建玩家信息实例
|
||||
local function CreateInfoInstance(Player: Player, Folder: any, StateKey: string, StateType: string, StateValue: any)
|
||||
if not Player and not Folder and not StateKey and not StateType then
|
||||
warn('创建玩家信息实例失败: ' , Player.Name, Folder.Name, StateKey, StateType, StateValue)
|
||||
return
|
||||
end
|
||||
|
||||
local Info = Instance.new(StateType)
|
||||
Info.Name = StateKey
|
||||
Info.Parent = Folder
|
||||
if StateValue then Info.Value = StateValue end
|
||||
return Info
|
||||
end
|
||||
|
||||
-- 改变玩家实例信息
|
||||
local function ChangeInfoInstance(Player: Player, Folder: any, StateKey: string, StateValue: any)
|
||||
if not Folder or not StateKey or not StateValue then return end
|
||||
local Info = Folder:FindFirstChild(StateKey)
|
||||
if not Info then warn('玩家信息实例不存在: ' , Player.Name, StateKey) return end
|
||||
Info.Value = StateValue
|
||||
end
|
||||
|
||||
local STATS_DATA = {
|
||||
name = {type = ENUM_STATE_TYPE.String, value = "PlayerName"},
|
||||
level = {type = ENUM_STATE_TYPE.Number, value = 1},
|
||||
exp = {type = ENUM_STATE_TYPE.Number, value = 0},
|
||||
}
|
||||
|
||||
-- 初始化玩家状态信息
|
||||
local function ExtraAddPlayerStats(Player: Player, StatsData: table)
|
||||
if not Player or not StatsData then return end
|
||||
-- 如果列表中不包含信息就添加到表中
|
||||
for StateKey, StateValue in STATS_DATA do
|
||||
if not StatsData[StateKey] then
|
||||
StatsData[StateKey] = StateValue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- 初始化玩家
|
||||
function PlayerInfoProxy:InitPlayer(Player: Player)
|
||||
local pData = Utils:GetPlayerDataFolder(Player)
|
||||
if not pData then return end
|
||||
local PlayerInfoFolder = Utils:CreateFolder("PlayerInfo", pData)
|
||||
local StatsFolder = Utils:CreateFolder("Stats", PlayerInfoFolder)
|
||||
local ItemsFolder = Utils:CreateFolder("Items", PlayerInfoFolder)
|
||||
|
||||
-- 新玩家数据初始化
|
||||
if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then
|
||||
ArchiveProxy.pData[Player.UserId][STORE_NAME] = {}
|
||||
ArchiveProxy.pData[Player.UserId][STORE_NAME].Stats = {}
|
||||
ArchiveProxy.pData[Player.UserId][STORE_NAME].Items = {}
|
||||
end
|
||||
|
||||
-- 放在外面是为了以后系统新增内容方便(同时不用在初始化数据是做写入了)
|
||||
ExtraAddPlayerStats(Player, ArchiveProxy.pData[Player.UserId][STORE_NAME].Stats)
|
||||
|
||||
-- 创建玩家信息实例
|
||||
for StateKey, StateData in ArchiveProxy.pData[Player.UserId][STORE_NAME].Stats do
|
||||
CreateInfoInstance(Player, StatsFolder, StateKey, StateData.type, StateData.value)
|
||||
end
|
||||
end
|
||||
|
||||
-- 添加经验
|
||||
function PlayerInfoProxy:AddExp(Player: Player, Exp: number)
|
||||
if not Player or not Exp then warn('添加经验失败: ' .. Player.Name .. ' ' .. Exp) return end
|
||||
local playerInfoData = ArchiveProxy.pData[Player.UserId][STORE_NAME]
|
||||
playerInfoData.Stats.exp = playerInfoData.Stats.exp + Exp
|
||||
|
||||
-- 数据变化
|
||||
local currentLevel = playerInfoData.Stats.level
|
||||
local maxLevel = Utils:GetMaxIdFromJson(JsonPlayerLv)
|
||||
if currentLevel < maxLevel then
|
||||
local requireExp = Utils:GetIdDataFromJson(JsonPlayerLv, currentLevel)
|
||||
if playerInfoData.Stats.exp >= requireExp then
|
||||
playerInfoData.Stats.level = currentLevel + 1
|
||||
playerInfoData.Stats.exp = playerInfoData.Stats.exp - requireExp
|
||||
end
|
||||
end
|
||||
|
||||
-- 前端变化
|
||||
local StatsFolder = GetPlayerInfoFolder(Player):FindFirstChild("Stats")
|
||||
ChangeInfoInstance(Player, StatsFolder, "exp", playerInfoData.Stats.exp)
|
||||
ChangeInfoInstance(Player, StatsFolder, "level", playerInfoData.Stats.level)
|
||||
return true, playerInfoData.Stats.level, playerInfoData.Stats.exp
|
||||
end
|
||||
|
||||
-- 改变物品数量记录
|
||||
function PlayerInfoProxy:ChangeItemCount(Player: Player, ItemId: number, ItemCount: number)
|
||||
if not Player or not ItemId or not ItemCount then warn('添加物品失败: ' , Player.Name, ItemId, ItemCount) return end
|
||||
|
||||
local playerInfoData = ArchiveProxy.pData[Player.UserId][STORE_NAME].Items
|
||||
local isNew = false
|
||||
if not playerInfoData[ItemId] then
|
||||
playerInfoData[ItemId] = ItemCount
|
||||
isNew = true
|
||||
else
|
||||
playerInfoData[ItemId] = playerInfoData[ItemId] + ItemCount
|
||||
end
|
||||
|
||||
-- 前端变化
|
||||
local ItemsFolder = GetPlayerInfoFolder(Player):FindFirstChild("Items")
|
||||
if isNew then
|
||||
CreateInfoInstance(Player, ItemsFolder, ItemId, "NumberValue")
|
||||
else
|
||||
ChangeInfoInstance(Player, ItemsFolder, ItemId, playerInfoData[ItemId])
|
||||
end
|
||||
return true, playerInfoData[ItemId]
|
||||
end
|
||||
|
||||
-- 判断是否拥有足够物品
|
||||
function PlayerInfoProxy:HasEnoughItem(Player: Player, ItemId: number, ItemCount: number)
|
||||
if not Player or not ItemId or not ItemCount then warn('添加物品失败: ' .. Player.Name .. ' ' .. ItemId .. ' ' .. ItemCount) return end
|
||||
local playerInfoData = ArchiveProxy.pData[Player.UserId][STORE_NAME].Items
|
||||
if not playerInfoData[ItemId] then return false end
|
||||
return playerInfoData[ItemId] >= ItemCount
|
||||
end
|
||||
|
||||
-- 获取物品数量
|
||||
function PlayerInfoProxy:GetItemCount(Player: Player, ItemId: number)
|
||||
if not Player or not ItemId then warn('获取物品数量失败: ' .. Player.Name .. ' ' .. ItemId) return end
|
||||
local playerInfoData = ArchiveProxy.pData[Player.UserId][STORE_NAME].Items
|
||||
if not playerInfoData[ItemId] then return 0 end
|
||||
return playerInfoData[ItemId]
|
||||
end
|
||||
|
||||
function PlayerInfoProxy:OnPlayerRemoving(Player: Player)
|
||||
|
||||
end
|
||||
|
||||
ReplicatedStorage.Remotes.PlayerRemoving.Event:Connect(function(Player: Player)
|
||||
PlayerInfoProxy:OnPlayerRemoving(Player)
|
||||
end)
|
||||
|
||||
return PlayerInfoProxy
|
6
src/ServerStorage/Proxy/PlayerLoopProxy/init.luau
Normal file
6
src/ServerStorage/Proxy/PlayerLoopProxy/init.luau
Normal file
@ -0,0 +1,6 @@
|
||||
-- 玩家循环代理
|
||||
|
||||
local PlayerLoopProxy = {}
|
||||
|
||||
|
||||
return PlayerLoopProxy
|
Loading…
x
Reference in New Issue
Block a user