From 42dbc86b3adc5733d86a5468563089884fcd3bf0 Mon Sep 17 00:00:00 2001 From: Ggafrik <906823881@qq.com> Date: Mon, 7 Jul 2025 00:03:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/level.xlsx | Bin 9397 -> 9395 bytes export.py | 36 +++++- src/ReplicatedStorage/Json/Level.json | 40 +++--- src/ServerStorage/Base/Character.luau | 2 + src/ServerStorage/Modules/MobLib/init.luau | 3 +- src/ServerStorage/Proxy/LevelProxy.luau | 130 +++++++++++++++++++- src/ServerStorage/Proxy/MobsProxy/AI.luau | 16 ++- src/ServerStorage/Proxy/MobsProxy/init.luau | 16 ++- 8 files changed, 206 insertions(+), 37 deletions(-) diff --git a/excel/level.xlsx b/excel/level.xlsx index da1b8b4b75006e8a26ea5093dba60f51a735bc07..2186fd1f1b99df5a962f8490d1fa19e8e5ef360d 100644 GIT binary patch delta 2572 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 Y^#}ajO<`bQE#O^DGFY5JSnO};Kb&R1NB{r; diff --git a/export.py b/export.py index f3edd4f..0c7d731 100644 --- a/export.py +++ b/export.py @@ -11,16 +11,13 @@ if not os.path.exists(output_dir): def parse_array_field(value, elem_type): """ - 解析数组类型字段,支持[1,2,3]或1,2,3写法,自动去除空格和空字符串。 - elem_type: 'int', 'float', 'string'等 + 解析一维数组类型字段,支持[1,2,3]或1,2,3写法。 """ 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: @@ -41,6 +38,32 @@ def parse_array_field(value, elem_type): result.append(v) return result +def parse_2d_array_field(value, elem_type): + """ + 解析二维数组类型字段,支持[1,2],[3,4]或[[1,2],[3,4]]等写法。 + 保证导出始终为双数组结构。 + """ + if value is None or str(value).strip() == "": + return [] + s = str(value).strip() + # 去除最外层中括号 + if s.startswith("[[") and s.endswith("]]"): + s = s[1:-1] + # 按 '],[' 拆分 + parts = re.split(r'\]\s*,\s*\[', s) + result = [] + for part in parts: + part = part.strip("[] ") + arr = parse_array_field(part, elem_type) + result.append(arr) + # 如果内容其实是一维数组(如单元格内容为1,2,3),也包一层 + if len(result) == 1 and not isinstance(result[0], list): + result = [parse_array_field(s, elem_type)] + # 如果内容为空但原始字符串非空,也包一层 + if not result and s: + result = [parse_array_field(s, elem_type)] + return result + for filename in os.listdir(excel_dir): if filename.endswith('.xlsx'): filepath = os.path.join(excel_dir, filename) @@ -65,7 +88,10 @@ for filename in os.listdir(excel_dir): 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("[]"): + if t.endswith("[][]"): + elem_type = t[:-4] + row_dict[h] = parse_2d_array_field(v, elem_type) + elif t.endswith("[]"): elem_type = t[:-2] row_dict[h] = parse_array_field(v, elem_type) else: diff --git a/src/ReplicatedStorage/Json/Level.json b/src/ReplicatedStorage/Json/Level.json index c4dca4d..162b75c 100644 --- a/src/ReplicatedStorage/Json/Level.json +++ b/src/ReplicatedStorage/Json/Level.json @@ -1,22 +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]} +{"id":1,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":2,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":3,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":4,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":5,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1000,1]]}, +{"id":6,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":7,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":8,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":9,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":10,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1000,1]]}, +{"id":11,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":12,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":13,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":14,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":15,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1000,1]]}, +{"id":16,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":17,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":18,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":19,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1],[10,1,1,10,2,1]]}, +{"id":20,"type":2,"timeLimit":60,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1000,1]]} ] \ No newline at end of file diff --git a/src/ServerStorage/Base/Character.luau b/src/ServerStorage/Base/Character.luau index 27ed32c..4a9297d 100644 --- a/src/ServerStorage/Base/Character.luau +++ b/src/ServerStorage/Base/Character.luau @@ -110,3 +110,5 @@ function Character:Died() self.Instance:Destroy() self = nil end + +return Character \ No newline at end of file diff --git a/src/ServerStorage/Modules/MobLib/init.luau b/src/ServerStorage/Modules/MobLib/init.luau index f0e9093..3521715 100644 --- a/src/ServerStorage/Modules/MobLib/init.luau +++ b/src/ServerStorage/Modules/MobLib/init.luau @@ -41,9 +41,10 @@ end function MobLib.new(MobInstance: Model): Mobs.Mob local HumanoidRootPart = MobInstance:FindFirstChild("HumanoidRootPart") :: BasePart - local Enemy = MobInstance:FindFirstChild("Enemy") :: Humanoid + local Enemy = MobInstance:WaitForChild("Enemy") :: Humanoid local MobConfig = MobInstance:FindFirstChild("MobConfig") and require(MobInstance:FindFirstChild("MobConfig")) if not HumanoidRootPart or not Enemy or not MobConfig then + print(HumanoidRootPart, Enemy, MobConfig) error(("MobLib.new: Passed mob '%s' is missing vital components."):format(MobInstance.Name)) end diff --git a/src/ServerStorage/Proxy/LevelProxy.luau b/src/ServerStorage/Proxy/LevelProxy.luau index 0064e33..27f55a4 100644 --- a/src/ServerStorage/Proxy/LevelProxy.luau +++ b/src/ServerStorage/Proxy/LevelProxy.luau @@ -9,6 +9,8 @@ local Players = game:GetService("Players") --> Variables local Utils = require(ReplicatedStorage.Tools.Utils) local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy) +local MobsProxy = require(ServerStorage.Proxy.MobsProxy) +local TypeList = require(ServerStorage.Base.TypeList) --> Json local JsonLevel = require(ReplicatedStorage.Json.Level) @@ -60,6 +62,48 @@ local function ExtraAddPlayerLevel(Player: Player, LevelData: table) end end +local EXCEPT_KEY = { "Task", "Mobs"} +local function ChangeValue(Player: Player, Folder: Instance, LevelKey: string, LevelValue: any) + if not Player or not Folder or not LevelKey or not LevelValue then return end + local ValueInstance = Folder:FindFirstChild(LevelKey) + if not ValueInstance then return end + + local storeTable + if Folder.Name == "Challenge" then + storeTable = LevelProxy.pData[Player.UserId] + else + storeTable = ArchiveProxy.pData[Player.UserId][STORE_NAME].Progress + end + + storeTable[LevelKey] = LevelValue + if not table.find(EXCEPT_KEY, LevelKey) then ValueInstance.Value = LevelValue end +end + +-- 怪物死亡,由初始化时传入 +local function OnMobDied(Player: Player, Mob: TypeList.Character) + for _, mob in LevelProxy.pData[Player.UserId].Mobs do + if mob ~= Mob then continue end + + table.remove(LevelProxy.pData[Player.UserId].Mobs, mob) + + -- 怪物清除判断 + local LevelData = Utils:GetJsonData(JsonLevel, LevelProxy.pData[Player.UserId].LevelId) + if LevelProxy.pData[Player.UserId].SpawnWaveFinish and #LevelProxy.pData[Player.UserId].Mobs == 0 then + if LevelProxy.pData[Player.UserId].NowWave < #LevelData["wave"] then + -- 波数增长 + LevelProxy.pData[Player.UserId].NowWave = LevelProxy.pData[Player.UserId].NowWave + 1 + -- 新波次重置怪物生成状态标记 + local ChallengeFolder = LevelFolder:FindFirstChild("Challenge") + ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", false) + elseif LevelProxy.pData[Player.UserId].NowWave >= #LevelData["wave"] then + -- 结束判断 + LevelProxy:ChallengeEnd(Player, true) + end + end + break + end +end + -------------------------------------------------------------------------------- function LevelProxy:InitPlayer(Player: Player) @@ -68,6 +112,7 @@ function LevelProxy:InitPlayer(Player: Player) local LevelFolder = Utils:CreateFolder(STORE_NAME, pData) local ProgressFolder = Utils:CreateFolder("Progress", LevelFolder) local DungeonFolder = Utils:CreateFolder("Dungeon", LevelFolder) + local ChallengeFolder = Utils:CreateFolder("Challenge", LevelFolder) -- 当前关卡状态 Utils:CreateFolder("Stats", LevelFolder) @@ -88,29 +133,108 @@ function LevelProxy:InitPlayer(Player: Player) for LevelKey, LevelValue in ArchiveProxy.pData[Player.UserId][STORE_NAME].Progress do CreateLevelInstance(Player, ProgressFolder, LevelKey, LevelValue) end + + -- 本地内容初始化(关卡挑战信息,不存储) + if not LevelProxy.pData then LevelProxy.pData = {} end + if not LevelProxy.pData[Player.UserId] then LevelProxy.pData[Player.UserId] = {} end + LevelProxy.pData[Player.UserId].Task = nil + LevelProxy.pData[Player.UserId].Time = 0 + LevelProxy.pData[Player.UserId].LevelId = 0 + LevelProxy.pData[Player.UserId].MaxTime = 0 + LevelProxy.pData[Player.UserId].IsBoss = false + LevelProxy.pData[Player.UserId].NowWave = 0 + LevelProxy.pData[Player.UserId].ShouldWave = 0 + LevelProxy.pData[Player.UserId].SpawnWaveFinish = false + LevelProxy.pData[Player.UserId].Mobs = {} + + -- 关卡挑战信息前端 + for key, value in LevelProxy.pData[Player.UserId] do + if key == "Task" or key == "Mobs" then continue end + CreateLevelInstance(Player, ChallengeFolder, key, value) + end end -- 挑战关卡(挑战副本用另一个函数) function LevelProxy:ChallengeLevel(Player: Player, LevelId: number) + local LevelData = Utils:GetJsonData(JsonLevel, LevelId) + if not LevelData then warn("Level Data not found", LevelId) return end -- 给前端传数据,做表现 -- 场景后端生成 -- 后端生成当前关卡状态数据 + local LevelFolder = GetPlayerLevelWorkspaceFolder(Player.UserId) + local ChallengeFolder = LevelFolder:FindFirstChild("Challenge") + if not ChallengeFolder then return end + local levelTask = task.defer(function() + ChangeValue(Player, ChallengeFolder, "IsBoss", LevelData.type == 1 and true or false) + ChangeValue(Player, ChallengeFolder, "LevelId", LevelId) + ChangeValue(Player, ChallengeFolder, "Time", 0) + ChangeValue(Player, ChallengeFolder, "NowWave", 0) + ChangeValue(Player, ChallengeFolder, "ShouldWave", 1) + ChangeValue(Player, ChallengeFolder, "MaxTime", LevelData.timeLimit or 0) + ChangeValue(Player, ChallengeFolder, "Mobs", {}) + + if LevelData.timeLimit then + while task.wait(1) do + ChangeValue(Player, ChallengeFolder, "Time", LevelProxy.pData[Player.UserId].Time + 1) + + -- 关卡生成 + if LevelProxy.pData[Player.UserId].Wave < #LevelData.wave then + ChangeValue(Player, ChallengeFolder, "Wave", LevelProxy.pData[Player.UserId].Wave + 1) + end + if LevelProxy.pData[Player.UserId].NowWave < LevelProxy.pData[Player.UserId].ShouldWave then + ChangeValue(Player, ChallengeFolder, "NowWave", LevelProxy.pData[Player.UserId].NowWave + 1) + local waveData = LevelData.wave[LevelProxy.pData[Player.UserId].NowWave] + for i = 1, #waveData, 3 do + local mobId = waveData[i + 1] + local mobCount = waveData[i + 2] + for _ = 1, mobCount do + local mob = MobsProxy:CreateMob(Player, mobId, LevelData.atkBonus, LevelData.hpBonus, OnMobDied) + table.insert(LevelProxy.pData[Player.UserId].Mobs, mob) + end + end + ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", true) + end + -- 时间结束 + if LevelProxy.pData[Player.UserId].Time >= LevelData.timeLimit then + self:ChallengeEnd(Player, false) + break + end + end + end + end) + ChangeValue(Player, ChallengeFolder, "Task", levelTask) end -- 挑战结束 -function LevelProxy:ChallengeEnd(Player: Player) +function LevelProxy:ChallengeEnd(Player: Player, result: boolean) + local pData = Utils:GetPlayerDataFolder(Player) + local LevelFolder = Utils:CreateFolder(STORE_NAME, pData) + local ProgressFolder = Utils:CreateFolder("Progress", LevelFolder) + -- 清除剩余怪物 + for _, mob in LevelProxy.pData[Player.UserId].Mobs do + mob:Died() + end + LevelProxy.pData[Player.UserId].Mobs = {} + -- 判断玩家是否通关 - -- 通关后,没到最大关卡,关卡进度+1 - -- 到达最大关卡不做处理 + if result then + ChangeValue(Player, ProgressFolder, "LevelId", LevelProxy.pData[Player.UserId].LevelId + 1) + end end function LevelProxy:OnPlayerRemoving(Player: Player) + -- 关卡文件夹清除 local PlayerLevelFolder = GetPlayerLevelWorkspaceFolder(Player.UserId) if PlayerLevelFolder then PlayerLevelFolder:Destroy() end + -- 关卡存储数据清除 + if LevelProxy.pData[Player.UserId].Task then + LevelProxy.pData[Player.UserId].Task:Cancel() + end + LevelProxy.pData[Player.UserId] = nil end -- ReplicatedStorage.Remotes.PlayerRemoving.Event:Connect(function(PlayerUserId: string) diff --git a/src/ServerStorage/Proxy/MobsProxy/AI.luau b/src/ServerStorage/Proxy/MobsProxy/AI.luau index 7ccab98..bdbc1ba 100644 --- a/src/ServerStorage/Proxy/MobsProxy/AI.luau +++ b/src/ServerStorage/Proxy/MobsProxy/AI.luau @@ -10,22 +10,24 @@ --> Services local Players = game:GetService("Players") +local ServerStorage = game:GetService("ServerStorage") --> Dependencies -local MobList = require(script.Parent.MobList) +local TypeList = require(ServerStorage.Base.TypeList) --> Variables +local DamageProxy = require(ServerStorage.Proxy.DamageProxy) local ActiveMobs = {} local AI = {} -------------------------------------------------------------------------------- -- 获取两个单位之间的距离 -function AI:GetModelDistance(Unit1: Model, Unit2: Model): number +function AI:GetModelDistance(Unit1: TypeList.Character, Unit2: TypeList.Character): number return (Unit1:GetPivot().Position - Unit2:GetPivot().Position).Magnitude end -function AI:GetClosestPlayer(Mob: any): (Player?, number?) +function AI:GetClosestPlayer(Mob: TypeList.Character): (Player?, number?) local Closest = {Player = nil, Magnitude = math.huge} local ActivePlayer -- We retain a reference to this, so they have to get further away from the mob to stop following, instead of the usual distance. @@ -97,7 +99,13 @@ task.defer(function() if not Player then return end if AI:GetModelDistance(MobInstance, Player.Character) <= 5 then -- 调用伤害模块 - + DamageProxy:TakeDamage(MobInstance, Player.Character, { + { + Damage = 10, + DamageType = DamageProxy.DamageType.PHYSICAL, + DamageTag = DamageProxy.DamageTag.NORMAL + } + }) end end end diff --git a/src/ServerStorage/Proxy/MobsProxy/init.luau b/src/ServerStorage/Proxy/MobsProxy/init.luau index e86ab1c..650f15a 100644 --- a/src/ServerStorage/Proxy/MobsProxy/init.luau +++ b/src/ServerStorage/Proxy/MobsProxy/init.luau @@ -11,9 +11,10 @@ local Utils = require(ReplicatedStorage.Tools.Utils) local PlayerInfoProxy = require(ServerStorage.Proxy.PlayerInfoProxy) local AI = require(script.AI) local Character = require(ServerStorage.Base.Character) +local TypeList = require(ServerStorage.Base.TypeList) --> Json -local JsonMob = require(ReplicatedStorage.Json.Mob) +local JsonMob = require(ReplicatedStorage.Json.Enemy) --> Constants @@ -42,7 +43,7 @@ end local Mob = {} Mob.__index = Mob -function Mob.new(Player: Player, MobId: number) +function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) -- 获取玩家怪物目录 local playerMobsFolder = GetPlayerMobsFolder(Player) if not playerMobsFolder then return end @@ -64,6 +65,9 @@ function Mob.new(Player: Player, MobId: number) -- 放入关卡中 newMobModel.Parent = playerMobsFolder + + -- 死亡函数 + if OnMobDied then Mob.OnDied = OnMobDied end -- 接入统一AI self.Humanoid.MoveToFinished:Connect(function() @@ -78,14 +82,18 @@ end function Mob:Died() MobsProxy:RemoveMob(self.Player, self.Instance) + if self.OnDied then self.OnDied(self.Player, self) end Character.Died(self) end -------------------------------------------------------------------------------- -- 给玩家创建怪物 -function MobsProxy:CreateMob(Player: Player, MobId: number) - local Mob = Mob.new(Player, MobId) +function MobsProxy:CreateMob(Player: Player, MobId: number, AtkBonus: number?, HpBonus: number?, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) + local Mob = Mob.new(Player, MobId, OnMobDied) + -- 关卡系数 + if AtkBonus then Mob:ChangeValue("attack", math.floor(Mob.attack * (AtkBonus / 1000))) end + if HpBonus then Mob:ChangeValue("hp", math.floor(Mob.hp * (HpBonus / 1000))) end MobsProxy.pData[Player.UserId][Mob.Instance] = Mob return Mob end