diff --git a/excel/enemy.xlsx b/excel/enemy.xlsx new file mode 100644 index 0000000..212ea3b Binary files /dev/null and b/excel/enemy.xlsx differ diff --git a/excel/equipment.xlsx b/excel/equipment.xlsx index 0464a3e..0697203 100644 Binary files a/excel/equipment.xlsx and b/excel/equipment.xlsx differ diff --git a/excel/excel2json.py b/excel/excel2json.py deleted file mode 100644 index a6ebf7c..0000000 --- a/excel/excel2json.py +++ /dev/null @@ -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}") \ No newline at end of file diff --git a/excel/item.xlsx b/excel/item.xlsx new file mode 100644 index 0000000..fbbbaa4 Binary files /dev/null and b/excel/item.xlsx differ diff --git a/excel/level.xlsx b/excel/level.xlsx new file mode 100644 index 0000000..da1b8b4 Binary files /dev/null and b/excel/level.xlsx differ diff --git a/excel/player.xlsx b/excel/player.xlsx new file mode 100644 index 0000000..987a085 Binary files /dev/null and b/excel/player.xlsx differ diff --git a/export.py b/export.py index 8d1b6c3..f3edd4f 100644 --- a/export.py +++ b/export.py @@ -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)) @@ -87,4 +88,4 @@ for filename in os.listdir(excel_dir): else: f.write(line + '\n') f.write(']') - print(f"导出: {out_path}") + print(f"导出: {out_path}") \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Enemy.json b/src/ReplicatedStorage/Json/Enemy.json new file mode 100644 index 0000000..ce4818a --- /dev/null +++ b/src/ReplicatedStorage/Json/Enemy.json @@ -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"} +] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Equipment.json b/src/ReplicatedStorage/Json/Equipment.json index 77b0b59..2c10059 100644 --- a/src/ReplicatedStorage/Json/Equipment.json +++ b/src/ReplicatedStorage/Json/Equipment.json @@ -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} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/ItemProp.json b/src/ReplicatedStorage/Json/ItemProp.json new file mode 100644 index 0000000..67f4c3c --- /dev/null +++ b/src/ReplicatedStorage/Json/ItemProp.json @@ -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} +] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Level.json b/src/ReplicatedStorage/Json/Level.json new file mode 100644 index 0000000..c4dca4d --- /dev/null +++ b/src/ReplicatedStorage/Json/Level.json @@ -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]} +] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/PlayerLv.json b/src/ReplicatedStorage/Json/PlayerLv.json new file mode 100644 index 0000000..92164e9 --- /dev/null +++ b/src/ReplicatedStorage/Json/PlayerLv.json @@ -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} +] \ No newline at end of file diff --git a/src/ReplicatedStorage/Tools/Utils.luau b/src/ReplicatedStorage/Tools/Utils.luau index 4324d93..9a0ebc7 100644 --- a/src/ReplicatedStorage/Tools/Utils.luau +++ b/src/ReplicatedStorage/Tools/Utils.luau @@ -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 \ No newline at end of file diff --git a/src/Server/DataManager.server.luau b/src/Server/DataManager.server.luau deleted file mode 100644 index f074c4b..0000000 --- a/src/Server/DataManager.server.luau +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/Server/Proxy/PlayerInfoProxy.luau b/src/Server/Proxy/PlayerInfoProxy.luau deleted file mode 100644 index 770a733..0000000 --- a/src/Server/Proxy/PlayerInfoProxy.luau +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/Server/ServerMain/init.server.luau b/src/Server/ServerMain/init.server.luau index f0d9d68..1923ac7 100644 --- a/src/Server/ServerMain/init.server.luau +++ b/src/Server/ServerMain/init.server.luau @@ -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 diff --git a/src/ServerStorage/Modules/ArmorLib/init.luau b/src/ServerStorage/Modules/ArmorLib/init.luau index 535e140..b082db7 100644 --- a/src/ServerStorage/Modules/ArmorLib/init.luau +++ b/src/ServerStorage/Modules/ArmorLib/init.luau @@ -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 \ No newline at end of file diff --git a/src/Server/Proxy/ArchiveProxy.luau b/src/ServerStorage/Proxy/ArchiveProxy.luau similarity index 81% rename from src/Server/Proxy/ArchiveProxy.luau rename to src/ServerStorage/Proxy/ArchiveProxy.luau index c88ea6d..7ca0269 100644 --- a/src/Server/Proxy/ArchiveProxy.luau +++ b/src/ServerStorage/Proxy/ArchiveProxy.luau @@ -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 = {} -------------------------------------------------------------------------------- @@ -47,24 +49,8 @@ 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 = {} + + 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 @@ -114,12 +101,17 @@ local function OnPlayerAdded(Player: Player) if not ArchiveProxy.pData then 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 diff --git a/src/Server/Proxy/EquipmentProxy.luau b/src/ServerStorage/Proxy/EquipmentProxy.luau similarity index 73% rename from src/Server/Proxy/EquipmentProxy.luau rename to src/ServerStorage/Proxy/EquipmentProxy.luau index ff3b050..42a3b62 100644 --- a/src/Server/Proxy/EquipmentProxy.luau +++ b/src/ServerStorage/Proxy/EquipmentProxy.luau @@ -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 \ No newline at end of file diff --git a/src/ServerStorage/Proxy/ItemProxy.luau b/src/ServerStorage/Proxy/ItemProxy.luau new file mode 100644 index 0000000..04678c0 --- /dev/null +++ b/src/ServerStorage/Proxy/ItemProxy.luau @@ -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 \ No newline at end of file diff --git a/src/ServerStorage/Proxy/LevelProxy.luau b/src/ServerStorage/Proxy/LevelProxy.luau new file mode 100644 index 0000000..5f14643 --- /dev/null +++ b/src/ServerStorage/Proxy/LevelProxy.luau @@ -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 \ No newline at end of file diff --git a/src/ServerStorage/Proxy/MobAIProxy.luau b/src/ServerStorage/Proxy/MobAIProxy.luau new file mode 100644 index 0000000..282ec5e --- /dev/null +++ b/src/ServerStorage/Proxy/MobAIProxy.luau @@ -0,0 +1,4 @@ +-- 怪物AI代理 +local MobAIProxy = {} + +return MobAIProxy \ No newline at end of file diff --git a/src/ServerStorage/Proxy/PlayerInfoProxy.luau b/src/ServerStorage/Proxy/PlayerInfoProxy.luau new file mode 100644 index 0000000..b949cf0 --- /dev/null +++ b/src/ServerStorage/Proxy/PlayerInfoProxy.luau @@ -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 \ No newline at end of file diff --git a/src/ServerStorage/Proxy/PlayerLoopProxy/PlayerAI.luau b/src/ServerStorage/Proxy/PlayerLoopProxy/PlayerAI.luau new file mode 100644 index 0000000..e69de29 diff --git a/src/ServerStorage/Proxy/PlayerLoopProxy/init.luau b/src/ServerStorage/Proxy/PlayerLoopProxy/init.luau new file mode 100644 index 0000000..a03e5cd --- /dev/null +++ b/src/ServerStorage/Proxy/PlayerLoopProxy/init.luau @@ -0,0 +1,6 @@ +-- 玩家循环代理 + +local PlayerLoopProxy = {} + + +return PlayerLoopProxy \ No newline at end of file