From 962aedcb0dd3d9588dd0cfcd045090460b67be4d Mon Sep 17 00:00:00 2001 From: Ggafrik <906823881@qq.com> Date: Mon, 7 Jul 2025 23:52:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0-=E7=8E=A9=E5=AE=B6ai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 需调试bug --- excel/cha.xlsx | Bin 0 -> 8795 bytes excel/enemy.xlsx | Bin 9002 -> 9025 bytes src/ReplicatedStorage/Json/Character.json | 3 + src/ReplicatedStorage/Json/Enemy.json | 6 +- src/Server/ServerMain/init.server.luau | 2 + src/ServerStorage/Base/Behaviour.luau | 83 ++++++ src/ServerStorage/Base/Character.luau | 11 +- src/ServerStorage/Base/TypeList.luau | 11 + src/ServerStorage/Modules/ArmorLib/Morph.luau | 119 --------- src/ServerStorage/Modules/ArmorLib/init.luau | 165 ------------ .../Modules/Behaviours/Move.luau | 67 +++++ src/ServerStorage/Modules/DamageLib.luau | 103 -------- src/ServerStorage/Modules/MobLib/AI.luau | 173 ------------ src/ServerStorage/Modules/MobLib/MobList.luau | 10 - src/ServerStorage/Modules/MobLib/init.luau | 250 ------------------ src/ServerStorage/Modules/ToolLib.luau | 72 ----- src/ServerStorage/Proxy/DamageProxy.luau | 10 +- src/ServerStorage/Proxy/LevelProxy.luau | 37 ++- src/ServerStorage/Proxy/MobsProxy/AI.luau | 12 +- src/ServerStorage/Proxy/MobsProxy/init.luau | 41 +-- .../Proxy/PlayerFightProxy/LevelLoop.luau | 73 +++++ .../Proxy/PlayerFightProxy/PlayerAI.luau | 101 +++++++ .../Proxy/PlayerFightProxy/init.luau | 104 ++++++++ src/ServerStorage/Proxy/PlayerInfoProxy.luau | 2 +- .../Proxy/PlayerLoopProxy/PlayerAI.luau | 0 .../Proxy/PlayerLoopProxy/init.luau | 6 - 26 files changed, 509 insertions(+), 952 deletions(-) create mode 100644 excel/cha.xlsx create mode 100644 src/ReplicatedStorage/Json/Character.json create mode 100644 src/ServerStorage/Base/Behaviour.luau delete mode 100644 src/ServerStorage/Modules/ArmorLib/Morph.luau delete mode 100644 src/ServerStorage/Modules/ArmorLib/init.luau create mode 100644 src/ServerStorage/Modules/Behaviours/Move.luau delete mode 100644 src/ServerStorage/Modules/DamageLib.luau delete mode 100644 src/ServerStorage/Modules/MobLib/AI.luau delete mode 100644 src/ServerStorage/Modules/MobLib/MobList.luau delete mode 100644 src/ServerStorage/Modules/MobLib/init.luau delete mode 100644 src/ServerStorage/Modules/ToolLib.luau create mode 100644 src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau create mode 100644 src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau create mode 100644 src/ServerStorage/Proxy/PlayerFightProxy/init.luau delete mode 100644 src/ServerStorage/Proxy/PlayerLoopProxy/PlayerAI.luau delete mode 100644 src/ServerStorage/Proxy/PlayerLoopProxy/init.luau diff --git a/excel/cha.xlsx b/excel/cha.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5552c548f402960aa74623bd7938f4251f066eb1 GIT binary patch literal 8795 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 literal 0 HcmV?d00001 diff --git a/excel/enemy.xlsx b/excel/enemy.xlsx index bc9b0adf11d644daef797fed5c72f449fd7239b1..6332a8c9f21efdbf77a5b0d536eebfcccf4e80c1 100644 GIT binary patch delta 2450 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 z02BZK00000000000002ylYk;88}1QkUq%4{09OJ401*HH00000000000001BlfoiC Q0o{`UBO?Z@AOHXW03myB!2kdN delta 2408 zcmV-u377W4Myf`z$_5201!U{ulg|bmf8TGLFc5y9wEqG5oq`D=1eI1zLbXWU6m?zu zQmJx`Q@n!BY^R~B`oHfCA^nk;t{noKB63nn!BX_VG&(-|`sHvV)!kCcI|l%SPElb@op_$61*?cYkTnzFe_2UY z5d)^W^>nQm$#ub)sY);K{m82b7pOx>)O#@GYQ-s=NxH6>Fdc|uB{4u>7rfRzT1DRj zR}po$u3ajt8X%UuXu}=1 z0398!c*!n1={lsYe~`*nszgpnw8?UASe~L0m~z9OA3#)UT~F4$1aA<>!3cRrJ2@{D z7*P9@(l8~6VN)p#NNr2~v$ATjr-cL&JLl}rnkxq14oC>XfsxRJEcJqzf5KU-GDXwm zdZFZRMvcB!%VwUJ?23_Eifh(TR$jl*AlVoEKd6vUn*+ z^Vu0>`zN`F+(U))?WZ}1dJ z4WlOrMnwsgWNnIVSuJ#me?q9?p6feldJp!KHP4@EJo?+iwey>y_dVHwZ3DaHY@?qA zU`?mF;v$!u6uH>S_Tja;X`NR*Hw7qY7zePVd-jd-+X5Kl!PvKQSAk`gqF0P&9ivk? zTsypCp1oe5SwKufk=w9PT7Ik8))d5+r}_HR*AU($tg?9?udf-xT0 z6|6C5+O#vMi~~Cy`X?iQ9J+os7`kCR4%|2#1nxAP1=(*y~4laT`yv)2hT0tvZhES#wW003;0 z6AU4L$!^;)5Qgso@(u*=7A4zBYQb_4GzjXRilj}>G&Qmab5Tjk3yQvbhjhIFz8DcC za>V8PXT)FA$E{Xm4bDqzDq3VYC18ZDrCC<=*Uy*17fO8Krsm2TsOSzneVpCiOgGl8 zd9NHvR(u0RR6000960l$BdC zm#;tGv~_dqLgsvUEDCCgeo8iEJ7x*J73wo`0{a;ScHI;4lGsrqSrU6n@Fj7eM7ku7 zl*pEZ>r*7xAke4KATFJy91vT$ARHZS6(+rHt7NII&?Pn!iILz&A~h13k;siip&=k6 z>+^ety& z$g=nEhbxw>JmnZre?vh1*ICCGm0 ziJONFP8?oNz!$_y1%DDP%a!V(6N8LS^izYJYdaQVCF@Sii)XzP#|=&^991S92Y&&o zfY<&S&Q-xjKi2rHpB@eAd3taQv7mKBNO77^Wy7BSl>Y#;Zx4kE2~HL+97X{E0PBci6JUvbB-uhxM4bs#Y?0g;Hormh^f%%t#HC zWOfRe(qx;GDd@AQ8vIVgyHs$e1xKNC&QS2G@cX0Aj%qTpfQPwHO9{>@9}4Wi!lnqy z1ZQhvn$kYZ{qGnGnF%hHj zt!eKV5lYim+5Wu+HpI;Mc<(v=?zuNjSNo<8zQEY3RT-iYK>;Yy%1V_PdMVc74GOH| zO7gl@kf8%uw8}3pQlXh>4Lq4vgK-tuz>}0^LT6~_oMsqXv4e)&$UBr5x2y z{q??&H8z^#?0<<1PrMFET#%S0gwbS1JOT0)pM8b=8b*lN%yH0g{tkA|M-1 z7A_n{0RRB&1ONaO0000000000000000Me6}A}1S%*!EaO0RRA30ssIJ0000000000 a0000007{eDB0d4elNci-29zKG0002^PGD&O diff --git a/src/ReplicatedStorage/Json/Character.json b/src/ReplicatedStorage/Json/Character.json new file mode 100644 index 0000000..9b0036b --- /dev/null +++ b/src/ReplicatedStorage/Json/Character.json @@ -0,0 +1,3 @@ +[ +{"id":1,"name":1,"attack":10,"hp":100,"walkSpeed":10,"attackSpeed":2} +] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Enemy.json b/src/ReplicatedStorage/Json/Enemy.json index cd2d6ad..05a2143 100644 --- a/src/ReplicatedStorage/Json/Enemy.json +++ b/src/ReplicatedStorage/Json/Enemy.json @@ -1,5 +1,5 @@ [ -{"id":1,"type":1,"name":1,"atk":10,"hp":100,"walkSpeed":10,"atkSpeed":2,"model":"Thief"}, -{"id":2,"type":1,"name":2,"atk":30,"hp":300,"walkSpeed":10,"atkSpeed":1,"model":"Thief"}, -{"id":1000,"type":2,"name":1000,"atk":50,"hp":1000,"walkSpeed":20,"atkSpeed":1,"model":"Thief"} +{"id":1,"type":1,"name":1,"attack":10,"hp":100,"walkSpeed":10,"attackSpeed":2,"model":"Thief"}, +{"id":2,"type":1,"name":2,"attack":30,"hp":300,"walkSpeed":10,"attackSpeed":1,"model":"Thief"}, +{"id":1000,"type":2,"name":1000,"attack":50,"hp":1000,"walkSpeed":20,"attackSpeed":1,"model":"Thief"} ] \ No newline at end of file diff --git a/src/Server/ServerMain/init.server.luau b/src/Server/ServerMain/init.server.luau index 1923ac7..b236511 100644 --- a/src/Server/ServerMain/init.server.luau +++ b/src/Server/ServerMain/init.server.luau @@ -92,7 +92,9 @@ local function OnPlayerAdded(Player: Player) -- 加载对应玩家的其他系统代理 Proxies.EquipmentProxy:InitPlayer(Player) Proxies.PlayerInfoProxy:InitPlayer(Player) + Proxies.MobsProxy:InitPlayer(Player) Proxies.LevelProxy:InitPlayer(Player) + Proxies.PlayerFightProxy:InitPlayer(Player) end local function OnPlayerRemoving(Player: Player) diff --git a/src/ServerStorage/Base/Behaviour.luau b/src/ServerStorage/Base/Behaviour.luau new file mode 100644 index 0000000..fa4d8e1 --- /dev/null +++ b/src/ServerStorage/Base/Behaviour.luau @@ -0,0 +1,83 @@ +local Behaviour = {} +Behaviour.__index = Behaviour + +--> Dependencies +local TypeList = require(script.Parent.TypeList) + +-------------------------------------------------------------------------------- + +-- 刷新时,重新载入,暂时不考虑性能 + +-- 初始化内容 +function Behaviour:Init(Character: TypeList.Character) + local self = {} + self.Character = Character + self.CheckData = nil + self.ExeTask = nil + + local Humanoid = self.Character.Humanoid + -- 监听属性变化 + self.ConAttribtueChanged = Humanoid.AttributeChanged:Connect(function(attributeKey: string, attributeValue: any) + -- 以后这里要是有其他状态也可以加打断 + if attributeKey == "Died" and attributeValue == true then + self:OnDied() + end + end) + return self +end + +-- 执行检查(主要重写部分) return 优先级, 执行数据 +function Behaviour:Check(CheckInfo: table) + -- 返回优先级,执行数据 + return -1, self.CheckData +end + +-- 具体执行内容(主要重写部分) +function Behaviour:Execute() + +end + +-- 检查当前状态是否可执行 +function Behaviour:CheckStat() + if not self.Character then warn("Behaviour Character not found") return false end + -- 死亡检查 + if self.Character:GetState("Died") then return false end + + -- 执行状态中检查 + local FightingFolder = self.Character:FindFirstChild("Fighting") + local ExecutingState = FightingFolder:FindFirstChild("ExecutingState") + -- 其他内容执行中,就false + if ExecutingState.Value == true then return false end + return true +end + +-- 改变当前执行状态标记 +function Behaviour:ChangeExecutingState(State: boolean) + if not self.Character then warn("Behaviour Character not found") return end + local FightingFolder = self.Character:FindFirstChild("Fighting") + local ExecutingState = FightingFolder:FindFirstChild("ExecutingState") + ExecutingState.Value = State +end + +-- 销毁 +function Behaviour:Destroy() + if self.ConAttribtueChanged then + self.ConAttribtueChanged:Disconnect() + self.ConAttribtueChanged = nil + end + if self.LoopTask then + task.cancel(self.LoopTask) + self.LoopTask = nil + end + self = nil +end + +-- 死亡时,主要打断当前执行状态 +function Behaviour:OnDied() + if self.ExeTask then + task.cancel(self.ExeTask) + self.ExeTask = nil + end +end + +return Behaviour \ No newline at end of file diff --git a/src/ServerStorage/Base/Character.luau b/src/ServerStorage/Base/Character.luau index 4a9297d..b8394df 100644 --- a/src/ServerStorage/Base/Character.luau +++ b/src/ServerStorage/Base/Character.luau @@ -35,12 +35,11 @@ function Character.new(Player: Player, CharacterModel: Model, CharacterData: tab self.Config["max" .. attributeKey] = attributeValue Attributes:SetAttribute("max" .. attributeKey, attributeValue) end - - local conAttribute = self.AttributeChanged:Connect(function(attributeKey: string, attributeValue: number) - self:ChangeAttribute(attributeKey, attributeValue) - end) - table.insert(self.Connections, conAttribute) end + local conAttribute = Attributes.AttributeChanged:Connect(function(attributeKey: string, attributeValue: number) + self:ChangeAttribute(attributeKey, attributeValue) + end) + table.insert(self.Connections, conAttribute) -- 配置角色状态数据 local statsData = { @@ -68,7 +67,7 @@ function Character.new(Player: Player, CharacterModel: Model, CharacterData: tab return self end -function Character:GetAttribute(attributeKey: string) +function Character:GetAttributeValue(attributeKey: string) return self.Config[attributeKey], self.Instance.Attributes:GetAttribute(attributeKey) end diff --git a/src/ServerStorage/Base/TypeList.luau b/src/ServerStorage/Base/TypeList.luau index 0bb7234..0f7a4ff 100644 --- a/src/ServerStorage/Base/TypeList.luau +++ b/src/ServerStorage/Base/TypeList.luau @@ -9,4 +9,15 @@ export type Character = { Stats: table, } +export type Behaviour = { + Character: Character, + CheckData: any, + ExeTask: TaskScheduler, + Init: (Character: Character) -> Behaviour, + Check: (CheckInfo: table) -> (number, any), + ExecutingBehaviour: () -> boolean, + Execute: () -> (), + Destroy: () -> (), +} + return {} \ No newline at end of file diff --git a/src/ServerStorage/Modules/ArmorLib/Morph.luau b/src/ServerStorage/Modules/ArmorLib/Morph.luau deleted file mode 100644 index 947c49a..0000000 --- a/src/ServerStorage/Modules/ArmorLib/Morph.luau +++ /dev/null @@ -1,119 +0,0 @@ ---[[ - Evercyan @ March 2023 - Morph - - Morph is a module used to house the functions for armor purely related to morphing (changing physical appearance) - This includes creating & welding the armor model to the character, as well as visual changes like hiding limbs or accessories. -]] - -local Morph = {} - -function Morph:UpdateLimbTransparency(Character: Model, Armor) - for _, Instance in Character:GetChildren() do - local Transparency = if Armor then Armor.Config.BodyPartsVisible[Instance.Name] and 0 or 1 else Instance:GetAttribute("OriginalTransparency") - if Instance:IsA("BasePart") and Transparency then - if not Instance:GetAttribute("OriginalTransparency") then - Instance:SetAttribute("OriginalTransparency", Instance.Transparency) - end - - Instance.Transparency = Transparency - end - end -end - -function Morph:UpdateAccessoriesTransparency(Character: Model, Armor) - for _, Instance in Character:GetChildren() do - if Instance:IsA("Accessory") then - - for _, Piece in Instance:GetDescendants() do - local Transparency = if Armor then Armor.Config.AccessoriesVisible and 0 or 1 else Piece:GetAttribute("OriginalTransparency") - if Piece:IsA("BasePart") and Transparency then - if not Piece:GetAttribute("OriginalTransparency") then - Piece:SetAttribute("OriginalTransparency", Piece.Transparency) - end - Piece.Transparency = Transparency - end - end - end - end -end - --- Welds an armor's limb (Model) to the character's body limb. -local function WeldArmorLimb(ArmorLimb: Model, BodyLimb: BasePart) - local Middle = ArmorLimb:FindFirstChild("Middle") - - -- Weld the entire armor limb together (to Middle) - for _, Piece: Instance in ArmorLimb:GetDescendants() do - if Piece:IsA("BasePart") then - Piece.Anchored = false - Piece.CanCollide = false - Piece.CanQuery = false - Piece.Massless = true - - if Piece.Name ~= "Middle" then - local Weld = Instance.new("Weld") - Weld.Name = Piece.Name .."/".. Middle.Name - Weld.Part0 = Middle - Weld.Part1 = Piece - Weld.C1 = Piece.CFrame:Inverse() * Middle.CFrame - Weld.Parent = Middle - end - end - end - - -- Weld the armor limb base (Middle) to the body limb - local Weld = Instance.new("Weld") - Weld.Name = BodyLimb.Name .."/".. Middle.Name - Weld.Part0 = BodyLimb - Weld.Part1 = Middle - Weld.Parent = Middle -end - -function Morph:ApplyOutfit(Player: Player, Armor) - local Character = Player.Character - if not Character then return end - - if Character:FindFirstChild("ArmorGroup") then - self:ClearOutfit(Player) - end - - -- Update limb & accessory transparency - self:UpdateLimbTransparency(Character, Armor) - self:UpdateAccessoriesTransparency(Character, Armor) - - -- Create armor group - local ArmorGroup = Instance.new("Folder") - ArmorGroup.Name = "ArmorGroup" - - for _, ArmorLimb in Armor.Instance:GetChildren() do - if not ArmorLimb:IsA("Model") then continue end - ArmorLimb = ArmorLimb:Clone() - - local Middle = ArmorLimb:FindFirstChild("Middle") - local BodyLimb = Character:FindFirstChild(ArmorLimb.Name) - - if Middle and BodyLimb then - WeldArmorLimb(ArmorLimb, BodyLimb) - ArmorLimb.Parent = ArmorGroup - elseif not Middle then - warn(("Armor limb %s/%s is missing a \"Middle\" BasePart!"):format(Armor.Name, ArmorLimb.Name)) - end - end - - ArmorGroup.Parent = Character -end - -function Morph:ClearOutfit(Player: Player) - local Character = Player.Character - if not Character then return end - - self:UpdateLimbTransparency(Character) - self:UpdateAccessoriesTransparency(Character) - - local ArmorGroup = Character:FindFirstChild("ArmorGroup") - if ArmorGroup then - ArmorGroup:Destroy() - end -end - -return Morph \ No newline at end of file diff --git a/src/ServerStorage/Modules/ArmorLib/init.luau b/src/ServerStorage/Modules/ArmorLib/init.luau deleted file mode 100644 index b082db7..0000000 --- a/src/ServerStorage/Modules/ArmorLib/init.luau +++ /dev/null @@ -1,165 +0,0 @@ --- --[[ --- 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. --- ]] - --- --> Services --- local ReplicatedStorage = game:GetService("ReplicatedStorage") --- local Players = game:GetService("Players") - --- --> References --- local PlayerData = ReplicatedStorage:WaitForChild("PlayerData") - --- --> Dependencies --- local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary) --- local Morph = require(script.Morph) - --- --> Variables -local ArmorLib = {} - --- -------------------------------------------------------------------------------- - --- -- 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 - --- 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 - --- 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) - --- -- 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 - --- -- Humanoid changes --- Attributes.Health:SetAttribute("Armor", nil) --- Attributes.WalkSpeed:SetAttribute("Armor", nil) --- Attributes.JumpPower:SetAttribute("Armor", nil) - --- -- Morph changes --- Morph:ClearOutfit(Player) --- end - --- ---- Remotes ------------------------------------------------------------------- - --- local ChangeCd = {} - --- ReplicatedStorage.Remotes.EquipArmor.OnServerInvoke = function(Player, ArmorName: string) --- if not ArmorName or typeof(ArmorName) ~= "string" then --- return --- end - --- 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) - --- 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 - --- ReplicatedStorage.Remotes.UnequipArmor.OnServerInvoke = function(Player) --- ArmorLib:UnequipArmor(Player) - --- 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 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) - --- 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 - --- for _, Player in Players:GetChildren() do --- task.defer(OnPlayerAdded, Player) --- end - --- Players.PlayerAdded:Connect(OnPlayerAdded) - -return ArmorLib \ No newline at end of file diff --git a/src/ServerStorage/Modules/Behaviours/Move.luau b/src/ServerStorage/Modules/Behaviours/Move.luau new file mode 100644 index 0000000..dbff437 --- /dev/null +++ b/src/ServerStorage/Modules/Behaviours/Move.luau @@ -0,0 +1,67 @@ +-- 移动行为 + +--> Services +local ServerStorage = game:GetService("ServerStorage") + +--> Dependencies +local TypeList = require(ServerStorage.Base.TypeList) +local Behaviour = require(ServerStorage.Base.Behaviour) +local MobsProxy = require(ServerStorage.Proxy.MobsProxy) + +-------------------------------------------------------------------------------- + +local Move = {} +Move.__index = Move + +function Move:Init(Character: TypeList.Character, Player: Player) + local self = Behaviour:Init(Character) + self.Player = Player + setmetatable(self, Move) + return self +end + +function Move:Check(CheckInfo: table) + if Behaviour.CheckStat(self) then return -1, self.CheckData end + + local PlayerMobs = MobsProxy:GetPlayerMobs(self.Player) + if not PlayerMobs then warn("PlayerMobs not found") return end + + local closestMob, minDistance = nil, math.huge + for _, Mob in PlayerMobs do + if Mob.Instance and Mob.Instance.PrimaryPart then + local dist = (Mob.Instance.PrimaryPart.Position - self.Character.Instance.PrimaryPart.Position).Magnitude + if dist < minDistance then + minDistance = dist + closestMob = Mob + end + end + end + + self.CheckData = {} + if closestMob then + self.CheckData["ClosestCharacter"] = closestMob + return 10, self.CheckData + end + + -- 返回优先级,执行数据 + return -1, self.CheckData +end + +function Move:Execute() + self.ExeTask = task.spawn(function () + self:ChangeExecutingState(true) + self.Character.Humanoid.WalkToPart.CFrame = self.CheckData["ClosestCharacter"].Instance.PrimaryPart.CFrame + task.wait(0.5) + self:ChangeExecutingState(false) + end) +end + +function Move:Destroy() + if self.ExeTask then + task.cancel(self.ExeTask) + self.ExeTask = nil + end + Behaviour.Destroy(self) +end + +return Move \ No newline at end of file diff --git a/src/ServerStorage/Modules/DamageLib.luau b/src/ServerStorage/Modules/DamageLib.luau deleted file mode 100644 index b63ef43..0000000 --- a/src/ServerStorage/Modules/DamageLib.luau +++ /dev/null @@ -1,103 +0,0 @@ ---[[ - Evercyan @ March 2023 - DamageLib - - DamageLib houses code relating to damage for weapons, and any additional sources of damage - you may add to your game. It is recommended to have all code for damage run through here for consistency. - - If you're looking to adjust crit multiplier, gamepass rewards (ex. x3 damage), etc, you can do this under DamageLib:DamageMob(). -]] - ---> Services -local CollectionService = game:GetService("CollectionService") -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local ServerStorage = game:GetService("ServerStorage") - ---> Dependencies -local Mobs = require(ServerStorage.Modules.MobLib.MobList) - ---> Variables -local DamageCd = {} -local Random = Random.new() - --------------------------------------------------------------------------------- - -local DamageLib = {} - -function DamageLib:TagMobForDamage(Player: Player, Mob, Damage: number) - if Mob.isDead then return end - - local PlayerTags = Mob.Instance:FindFirstChild("PlayerTags") - if not PlayerTags then - PlayerTags = Instance.new("Configuration") - PlayerTags.Name = "PlayerTags" - PlayerTags.Parent = Mob.Instance - end - - local ExistingTag = PlayerTags:GetAttribute(Player.UserId) - if ExistingTag then - PlayerTags:SetAttribute(Player.UserId, ExistingTag + Damage) - else - PlayerTags:SetAttribute(Player.UserId, Damage) - end -end - -function DamageLib:DamageMob(Player: Player, Mob): number? - if Mob.isDead then return end - - local pData = ReplicatedStorage.PlayerData:FindFirstChild(Player.UserId) - local Level = pData and pData:FindFirstChild("Stats") and pData.Stats:FindFirstChild("Level") - if not Level or (Level.Value < Mob.Config.Level[2]) then - return - end - - -- Make sure the equipped tool can be found, so we can safely grab the damage from it. - -- Never pass damage as a number through a remote, as the client can manipulate this data. - local Character = Player.Character - local Tool = Character and Character:FindFirstChildOfClass("Tool") - local ItemConfig = Tool and Tool:FindFirstChild("ItemConfig") and require(Tool.ItemConfig) - if not ItemConfig or not ItemConfig.Damage then return end - - -- Damage Cooldown - if DamageCd[Player.UserId] then return end - DamageCd[Player.UserId] = true - task.delay(ItemConfig.Cooldown - 0.03, function() -- We subtract just a tad so inconsistencies with timing on the client (ie. time to raycast) is less likely to stop a hit from going through - DamageCd[Player.UserId] = nil - end) - - -- Calculate damage - local Damage = typeof(ItemConfig.Damage) == "table" and Random:NextInteger(unpack(ItemConfig.Damage)) or ItemConfig.Damage - local isCrit = Random:NextInteger(1, 10) == 1 - - if isCrit then - Damage *= 2 - end - - self:TagMobForDamage(Player, Mob, Damage) - Mob.Enemy.Health = math.clamp(Mob.Enemy.Health - Damage, 0, Mob.Enemy.MaxHealth) - ReplicatedStorage.Remotes.PlayerDamagedMob:FireClient(Player, Mob.Instance, Damage) - - return Damage -end - -ReplicatedStorage.Remotes.DamageMob.OnServerInvoke = function(Player, MobInstance: Model) - if MobInstance and typeof(MobInstance) == "Instance" and CollectionService:HasTag(MobInstance, "Mob") then - local Mob = Mobs[MobInstance] - - if Mob then - -- Follow - local Enemy = Mob.Instance:FindFirstChild("Enemy") - if Enemy and (not Enemy.WalkToPart or not Enemy.WalkToPart:IsDescendantOf(Player.Character)) then - local HumanoidRootPart = Mob.Instance:FindFirstChild("HumanoidRootPart") - if HumanoidRootPart then - HumanoidRootPart.Anchored = false - HumanoidRootPart:SetNetworkOwner(Player) - Enemy:MoveTo(Player.Character:GetPivot().Position) - end - end - return DamageLib:DamageMob(Player, Mob) - end - end -end - -return DamageLib \ No newline at end of file diff --git a/src/ServerStorage/Modules/MobLib/AI.luau b/src/ServerStorage/Modules/MobLib/AI.luau deleted file mode 100644 index 991142c..0000000 --- a/src/ServerStorage/Modules/MobLib/AI.luau +++ /dev/null @@ -1,173 +0,0 @@ ---[[ - Evercyan @ March 2023 - AI - - This script handles the physical behavior of mobs, notably Following and Jumping. - - Mobs automatically follow any player within range, but once attacked, they will no longer follow the closest - player within range for the follow session, and will instead start attacking the player which recently attacked it. -]] - ---> Services -local Players = game:GetService("Players") - ---> Dependencies -local MobList = require(script.Parent.MobList) - ---> Variables -local ActiveMobs = {} -local AI = {} - ----- UTILITY FUNCTIONS --------------------------------------------------------- - -local RaycastParams = RaycastParams.new() -RaycastParams.FilterDescendantsInstances = { - workspace:WaitForChild("Mobs"), - workspace:WaitForChild("Characters") -} -RaycastParams.RespectCanCollide = true -RaycastParams.IgnoreWater = true - -local function GetTableLength(t): number - local n = 0 - for _ in t do - n += 1 - end - return n -end - --------------------------------------------------------------------------------- - --- Gets the closest player within range to the given mob. If the closest player is the player the mob is already following, distance is 2x. --- Note: If streaming is enabled, MoveTo may produce undesired results if the max distance is ever higher than streaming minimum, such as the enemy --- appearing to 'idle' if the current distance/magnitude is between the streaming minimum and the max follow distance (including 2x possibility) -function AI:GetClosestPlayer(Mob: MobList.Mob): (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. - if Mob.Enemy.WalkToPart then - local Player = Players:GetPlayerFromCharacter(Mob.Enemy.WalkToPart.Parent) - if Player then - ActivePlayer = Player - end - end - - for _, Player in Players:GetPlayers() do - local Character = Player.Character - if not Character then continue end - - local Magnitude = (Character:GetPivot().Position - Mob.Instance:GetPivot().Position).Magnitude - local MaxDistance = (ActivePlayer == Player and (Mob.Config.FollowDistance or 32) * 2) or Mob.Config.FollowDistance or 32 - - if Magnitude <= MaxDistance and Magnitude < Closest.Magnitude then - Closest.Player = Player - Closest.Magnitude = Magnitude - end - end - - return Closest.Player, Closest.Player and Closest.Magnitude -end - --- Adds the mob to the ActiveMobs table. This table runs a few times per second and updates follow & jump code for active mobs. --- Active mobs are unanchored, and anchored back when they're not active (not within follow range of any player) -function AI:StartTracking(Mob) - if not Mob.Destroyed then - ActiveMobs[Mob.Instance] = Mob - end -end - --- Requests the mob to perform a jump if there's an obstacle in the way of it. -function AI:RequestJump(Mob) - if not Mob.Instance then - return - end - - local Root: BasePart = Mob.Root - local JumpHeight: number = (Mob.Enemy.JumpPower ^ 2) / (2 * workspace.Gravity) * .8 - local HipPoint: CFrame = Root.CFrame + (Root.CFrame.LookVector*(Root.Size.Z/2-0.2)) + (Root.CFrame.UpVector/-Root.Size.Y/2) - - local RaycastResult = workspace:Raycast( - HipPoint.Position, - HipPoint.LookVector * Mob.Enemy.WalkSpeed/4, - RaycastParams - ) - - if RaycastResult then - -- There is an obstacle, but they should be able to jump over it! - if RaycastResult.Instance ~= workspace.Terrain then - local PartHeight: number = (RaycastResult.Instance.Position + Vector3.new(0, RaycastResult.Instance.Size.Y/2, 0)).Y - if (HipPoint.Position.Y + JumpHeight) > PartHeight then - Mob.Enemy.Jump = true - end - else - Mob.Enemy.Jump = true - end - end -end - --------------------------------------------------------------------------------- - --- Code to begin tracking, evenly spread out -task.defer(function() - local iterationsPerSec = 4 - while true do - local Mobn = GetTableLength(MobList) - local Quota = Mobn / (60/iterationsPerSec) - - for _, Mob in MobList do - if not Mob.isDead and not Mob.Destroyed then - local Player = AI:GetClosestPlayer(Mob) - - if Player and not ActiveMobs[Mob.Instance] then - task.defer(AI.StartTracking, AI, Mob) - Quota -= 1 - if Quota < 1 then - Quota += Mobn / (60/iterationsPerSec) - task.wait() - end - end - end - end - task.wait() - end -end) - --- Code to continue tracking & jumping on Active Mobs -task.defer(function() - while true do - task.wait(1/4) - for MobInstance, Mob in ActiveMobs do - task.spawn(function() - if not Mob.isDead and not Mob.Destroyed then - local Player, m = AI:GetClosestPlayer(Mob) - local Enemy = Mob.Enemy - - -- Simulation - if Mob.Root.Anchored then - Mob.Root.Anchored = false - end - if not Mob.Root:GetNetworkOwner() then - Mob.Root:SetNetworkOwner(Player) - task.wait(0.05) -- Give physics more time so it doesn't appear as choppy - end - - -- Tracking - if Player and Enemy then - Enemy:MoveTo(Player.Character:GetPivot().Position, Player.Character.PrimaryPart) - else - ActiveMobs[MobInstance] = nil - Mob.Root:SetNetworkOwner() - Enemy:MoveTo(Mob.Origin.Position) - end - - -- Jumping - AI:RequestJump(Mob) - else - ActiveMobs[MobInstance] = nil - end - end) - end - end -end) - -return AI \ No newline at end of file diff --git a/src/ServerStorage/Modules/MobLib/MobList.luau b/src/ServerStorage/Modules/MobLib/MobList.luau deleted file mode 100644 index 9cf0100..0000000 --- a/src/ServerStorage/Modules/MobLib/MobList.luau +++ /dev/null @@ -1,10 +0,0 @@ -export type Mob = { - Instance: Model, - Config: any, - Root: BasePart, - Enemy: Humanoid, - Origin: CFrame, - Respawn: (Mob) -> () -} - -return {} \ No newline at end of file diff --git a/src/ServerStorage/Modules/MobLib/init.luau b/src/ServerStorage/Modules/MobLib/init.luau deleted file mode 100644 index 3b5d03a..0000000 --- a/src/ServerStorage/Modules/MobLib/init.luau +++ /dev/null @@ -1,250 +0,0 @@ ---[[ - Evercyan @ March 2023 - MobLib - - MobLib is where the server-sided code for mobs is ran. This manages all of the logic, excluding the - minor logic on the client, like disabling certain humanoid state types for optimization. -]] - ---> Services -local CollectionService = game:GetService("CollectionService") -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local ServerStorage = game:GetService("ServerStorage") -local Players = game:GetService("Players") - ---> References -local PlayerData = ReplicatedStorage:WaitForChild("PlayerData") - ---> Dependencies -local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary) -local FormatNumber = require(ReplicatedStorage.Modules.FormatNumber) -local AI = require(script.AI) - ---> Variables -local Mobs = require(script.MobList) -- Dictionary which stores a reference to every Mob -local DamageCooldown = {} -local Random = Random.new() - --------------------------------------------------------------------------------- - -local MobLib = {} -- Mirror table with the Mob constructor function - --- local Mob = {} -- Syntax sugar for mob-related functions --- Mob.__index = Mob - --- local MobsFolder = workspace:FindFirstChild("Mobs") --- if not MobsFolder then --- MobsFolder = Instance.new("Folder") --- MobsFolder.Name = "Mobs" --- MobsFolder.Parent = workspace --- end - --- function MobLib.new(MobInstance: Model): Mobs.Mob --- local HumanoidRootPart = MobInstance:FindFirstChild("HumanoidRootPart") :: BasePart --- 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 - --- local Mob = setmetatable({}, Mob) --- Mob.Instance = MobInstance --- Mob.Config = MobConfig --- Mob.Root = HumanoidRootPart --- Mob.Enemy = Enemy --- Mob.Origin = HumanoidRootPart:GetPivot() --- Mob._Copy = MobInstance:Clone() - --- -- Initialize --- Enemy.MaxHealth = MobConfig.Health --- Enemy.Health = MobConfig.Health --- Enemy.WalkSpeed = MobConfig.WalkSpeed --- Enemy.JumpPower = MobConfig.JumpPower --- HumanoidRootPart.Anchored = true --- MobInstance.Parent = MobsFolder - --- -- Set humanoid states (helps prevent falling down & useless calculations - you're unlikely to have an enemy climbing without pathfinding) --- for _, EnumName in {"FallingDown", "Seated", "Flying", "Swimming", "Climbing"} do --- local HumanoidStateType = Enum.HumanoidStateType[EnumName] --- Enemy:SetStateEnabled(HumanoidStateType, false) --- if Enemy:GetState() == HumanoidStateType then --- Enemy:ChangeState(Enum.HumanoidStateType.Running) --- end --- end - --- -- Damage --- local function OnTouched(BasePart) --- local Player = Players:GetPlayerFromCharacter(BasePart.Parent) --- local Humanoid = Player and Player.Character:FindFirstChild("Humanoid") - --- if Humanoid and Enemy.Health > 0 then --- if DamageCooldown[Player.UserId] then --- return --- end --- DamageCooldown[Player.UserId] = true --- task.delay(0.5, function() --- DamageCooldown[Player.UserId] = nil --- end) - --- Humanoid.Health = math.clamp(Humanoid.Health - MobConfig.Damage, 0, Humanoid.MaxHealth) --- ReplicatedStorage.Remotes.MobDamagedPlayer:FireClient(Player, MobInstance, MobConfig.Damage) --- end --- end - --- (MobInstance:WaitForChild("Hitbox") :: BasePart).Touched:Connect(OnTouched) - --- -- Respawn --- Enemy.Died:Once(function() --- if not Mob.isDead then --- Mob.isDead = true --- Mob:ActivateRagdoll() --- Mob:AwardDrops() --- task.wait(MobConfig.RespawnTime or 5) --- Mob:Respawn() --- end --- end) - --- -- Following has finished. Anchor assembly to optimize. --- Enemy.MoveToFinished:Connect(function() --- if not AI:GetClosestPlayer(Mob) and not Mob.isDead then --- HumanoidRootPart.Anchored = true --- else --- AI:StartTracking(Mob) --- end --- end) - - --- Mobs[MobInstance] = Mob - --- return Mob --- end - --- function Mob:Destroy() --- if not self.Destroyed then --- self.Destroyed = true - --- Mobs[self.Instance] = nil --- self.Instance:Destroy() - --- -- Remove instance references --- self.Instance = nil --- self.Root = nil --- self.Enemy = nil --- self._Copy = nil --- end --- end - --- function Mob:TakeDamage(Damage: number) --- local Enemy = self.Enemy --- if not self.isDead then --- Enemy.Health = math.clamp(Enemy.Health - Damage, 0, Enemy.MaxHealth) --- end --- end - --- function Mob:Respawn() --- if not self.Destroyed then --- local NewMob = self._Copy --- self:Destroy() --- NewMob.Parent = MobsFolder --- end --- end - --- function Mob:ActivateRagdoll() --- for _, Item in self.Instance:GetDescendants() do --- if Item:IsA("Motor6D") then --- local Attachment0 = Instance.new("Attachment") --- Attachment0.CFrame = Item.C0 --- Attachment0.Parent = Item.Part0 - --- local Attachment1 = Instance.new("Attachment") --- Attachment1.CFrame = Item.C1 --- Attachment1.Parent = Item.Part1 - --- local Constraint = Instance.new("BallSocketConstraint") --- Constraint.Attachment0 = Attachment0 --- Constraint.Attachment1 = Attachment1 --- Constraint.LimitsEnabled = true --- Constraint.TwistLimitsEnabled = true --- Constraint.Parent = Item.Parent - --- Item.Enabled = false --- end --- end --- end - --- function Mob:AwardDrops() --- local PlayerTags = self.Instance:FindFirstChild("PlayerTags") :: Configuration --- if not PlayerTags then return end - --- for UserId, Damage: number in PlayerTags:GetAttributes() do --- UserId = tonumber(UserId) - --- local Player = Players:GetPlayerByUserId(UserId) --- if not Player then continue end - --- local Percent = Damage / self.Enemy.MaxHealth - --- if Percent >= 0.25 then --- local pData = PlayerData:FindFirstChild(Player.UserId) --- local Statistics = pData:FindFirstChild("Stats") --- local Items = pData:FindFirstChild("Items") - --- -- Stats --- if Statistics then --- for _, StatInfo in self.Config.Drops.Statistics do --- local StatName: string = StatInfo[1] --- local StatCount: number = StatInfo[2] - --- local Stat = Statistics:FindFirstChild(StatName) --- if Stat then --- Stat.Value += StatCount --- end --- end --- end - --- -- Items --- if Items then --- for _, ItemInfo in self.Config.Drops.Items do --- local ItemType: string = ItemInfo[1] --- local ItemName: string = ItemInfo[2] --- local DropChance: number = ItemInfo[3] - --- local Item = ContentLibrary[ItemType] and ContentLibrary[ItemType][ItemName] --- if Item then --- local isLucky = Random:NextInteger(1, DropChance) == 1 --- if isLucky then --- require(ServerStorage.Modules[ItemType .."Lib"]):Give(Player, Item) --- ReplicatedStorage.Remotes.SendNotification:FireClient(Player, --- "Item Dropped", --- self.Config.Name .." dropped ".. Item.Name .." at a 1/".. FormatNumber(DropChance, "Suffix") .." chance.", --- Item.Config.IconId --- ) --- end --- else --- warn("[Kit/MobLib/AwardDrops]: Item doesn't exist: '".. ItemType .."/".. ItemName ..".") --- end --- end --- end - --- -- TeleportLocation (TP) --- local TP = self.Config.TeleportLocation and workspace.TP:FindFirstChild(self.Config.TeleportLocation) --- local Character = Player.Character --- if TP and Character then --- Character:PivotTo(TP.CFrame + Vector3.yAxis*4) --- end --- end --- end --- end - --- CollectionService:GetInstanceAddedSignal("Mob"):Connect(function(MobInstance) --- if MobInstance:IsDescendantOf(workspace) then -- For some reason, HD Admin saves a copy of the map under ServerStorage (if you happen to use that), and the MobLib will attempt to clone its copy into workspace.Mobs. --- MobLib.new(MobInstance) --- end --- end) - --- for _, MobInstance in CollectionService:GetTagged("Mob") do --- task.defer(MobLib.new, MobInstance) --- end - -return MobLib \ No newline at end of file diff --git a/src/ServerStorage/Modules/ToolLib.luau b/src/ServerStorage/Modules/ToolLib.luau deleted file mode 100644 index b100195..0000000 --- a/src/ServerStorage/Modules/ToolLib.luau +++ /dev/null @@ -1,72 +0,0 @@ ---[[ - Evercyan @ March 2023 - ToolLib - - ToolLib is an item library that houses code that can be ran on the server relating - to Tools, such as ToolLib:Give(Player, Tool (ContentLib.Tool[...])) -]] - ---> Services -local ReplicatedStorage = game:GetService("ReplicatedStorage") -local Players = game:GetService("Players") - ---> References -local PlayerData = ReplicatedStorage:WaitForChild("PlayerData") - ---> Dependencies -local ContentLibrary = require(ReplicatedStorage.Modules.ContentLibrary) - ---> Variables -local ToolLib = {} - --------------------------------------------------------------------------------- - --- Adds the tool to the player's data, as well as the ToolInstance under StarterGear & Backpack. -function ToolLib:Give(Player: Player, Tool) - local pData = PlayerData:WaitForChild(Player.UserId, 5) - - if pData then - if not pData.Items.Tool:FindFirstChild(Tool.Name) then - local ValueObject = Instance.new("BoolValue") - ValueObject.Name = Tool.Name - ValueObject.Parent = pData.Items.Tool - - local StarterGear = Player:FindFirstChild("StarterGear") - local Backpack = Player:WaitForChild("Backpack") - - if StarterGear and not StarterGear:FindFirstChild(Tool.Name) then - Tool.Instance:Clone().Parent = StarterGear - end - - if Backpack and not Backpack:FindFirstChild(Tool.Name) then - Tool.Instance:Clone().Parent = Backpack - end - end - else - warn(("pData for Player '%s' doesn't exist! Did they leave?"):format(Player.Name)) - end -end - -function ToolLib:Trash(Player: Player, Tool) - local pData = PlayerData:WaitForChild(Player.UserId, 5) - - if pData then - if pData.Items.Tool:FindFirstChild(Tool.Name) then - pData.Items.Tool[Tool.Name]:Destroy() - end - - if Player.StarterGear:FindFirstChild(Tool.Name) then - Player.StarterGear[Tool.Name]:Destroy() - end - if Player.Backpack:FindFirstChild(Tool.Name) then - Player.Backpack[Tool.Name]:Destroy() - end - if Player.Character and Player.Character:FindFirstChild(Tool.Name) then - Player.Character[Tool.Name]:Destroy() - end - else - warn(("pData for Player '%s' doesn't exist! Did they leave?"):format(Player.Name)) - end -end - -return ToolLib \ No newline at end of file diff --git a/src/ServerStorage/Proxy/DamageProxy.luau b/src/ServerStorage/Proxy/DamageProxy.luau index d3a1ece..c0669ac 100644 --- a/src/ServerStorage/Proxy/DamageProxy.luau +++ b/src/ServerStorage/Proxy/DamageProxy.luau @@ -19,13 +19,15 @@ local DamageTag = { export type DamageTag = "Normal" | "Critical" export type DamageType = "Normal" | "Skill" - export type DamageInfo = { Damage: number, Type: DamageType, Tag: DamageTag, } +DamageProxy.DamageType = DamageType +DamageProxy.DamageTag = DamageTag + -------------------------------------------------------------------------------- function DamageProxy:GetHumanoid(Target: Model) @@ -43,7 +45,7 @@ end function DamageProxy:IsDied(Target: Model) local Humanoid = self:GetHumanoid(Target) - return Humanoid:GetAttribute("Health") <= 0 + return Humanoid:GetAttribute("hp") <= 0 end function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Character, DamageInfos: {DamageInfo}) @@ -53,8 +55,8 @@ function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Cha local DamageTag = DamageInfo.DamageTag -- 伤害计算 - local VictimHealth = Victim:GetAttribute("health") - Victim:ChangeAttribute("health", math.max(0, VictimHealth - Damage)) + local VictimHealth = Victim:GetAttributeValue("hp") + Victim:ChangeAttribute("hp", math.max(0, VictimHealth - Damage)) end end diff --git a/src/ServerStorage/Proxy/LevelProxy.luau b/src/ServerStorage/Proxy/LevelProxy.luau index d261ed2..78bb243 100644 --- a/src/ServerStorage/Proxy/LevelProxy.luau +++ b/src/ServerStorage/Proxy/LevelProxy.luau @@ -19,6 +19,7 @@ local JsonLevel = require(ReplicatedStorage.Json.Level) local STORE_NAME = "Level" local ENUM_LEVEL_TYPE = { Main = 1, + BossFail = 0, } -------------------------------------------------------------------------------- @@ -83,7 +84,6 @@ 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) -- 怪物清除判断 @@ -112,9 +112,9 @@ function LevelProxy:InitPlayer(Player: Player) local PlayerLevelFolder = Utils:CreateFolder(Player.UserId, LevelFolder) local ProgressFolder = Utils:CreateFolder("Progress", PlayerLevelFolder) local DungeonFolder = Utils:CreateFolder("Dungeon", PlayerLevelFolder) + -- 当前关卡状态 不存储 local ChallengeFolder = Utils:CreateFolder("Challenge", PlayerLevelFolder) - -- 当前关卡状态 - Utils:CreateFolder("Stats", PlayerLevelFolder) + -- Utils:CreateFolder("Stats", PlayerLevelFolder) -- 新玩家数据初始化 if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then @@ -150,7 +150,7 @@ function LevelProxy:InitPlayer(Player: Player) CreateLevelInstance(Player, ChallengeFolder, key, value) end - self:ChallengeLevel(Player, 1) + -- self:ChallengeLevel(Player, 1) end -- 挑战关卡(挑战副本用另一个函数) @@ -166,7 +166,7 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number) local ChallengeFolder = LevelFolder:FindFirstChild("Challenge") if not ChallengeFolder then warn("ChallengeFolder not found") return end local levelTask = task.defer(function() - ChangeValue(Player, ChallengeFolder, "IsBoss", LevelData.type == 1 and true or false) + ChangeValue(Player, ChallengeFolder, "IsBoss", LevelData.type == 2 and true or false) ChangeValue(Player, ChallengeFolder, "LevelId", LevelId) ChangeValue(Player, ChallengeFolder, "Time", 0) ChangeValue(Player, ChallengeFolder, "NowWave", 0) @@ -185,7 +185,6 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number) ChangeValue(Player, ChallengeFolder, "NowWave", LevelProxy.pData[Player.UserId].NowWave + 1) ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", true) - 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] @@ -193,13 +192,26 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number) for _ = 1, mobCount do local mob = MobsProxy:CreateMob(Player, mobId, LevelData.atkBonus, LevelData.hpBonus, OnMobDied) table.insert(LevelProxy.pData[Player.UserId].Mobs, mob) - print(mob) end end ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", true) end + + if LevelProxy.pData[Player.UserId].NowWave >= LevelProxy.pData[Player.UserId].ShouldWave and + LevelProxy.pData[Player.UserId].SpawnWaveFinish == true and + #LevelProxy.pData[Player.UserId].Mobs == 0 then + local maxWave = #LevelData.wave + if LevelProxy.pData[Player.UserId].NowWave < maxWave then + -- 下一波 + ChangeValue(Player, ChallengeFolder, "ShouldWave", LevelProxy.pData[Player.UserId].ShouldWave + 1) + else + -- 挑战胜利 + self:ChallengeEnd(Player, true) + end + end + + -- 时间结束 if LevelData.timeLimit then - -- 时间结束 if LevelProxy.pData[Player.UserId].Time >= LevelData.timeLimit then self:ChallengeEnd(Player, false) end @@ -215,14 +227,17 @@ function LevelProxy:ChallengeEnd(Player: Player, result: boolean) 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 + for _, mob in LevelProxy.pData[Player.UserId].Mobs do mob:Died() end LevelProxy.pData[Player.UserId].Mobs = {} -- 判断玩家是否通关 if result then ChangeValue(Player, ProgressFolder, "LevelId", LevelProxy.pData[Player.UserId].LevelId + 1) + else + local IsBoss = LevelProxy.pData[Player.UserId].IsBoss + if IsBoss then + ChangeValue(Player, ProgressFolder, "BossFail", LevelProxy.pData[Player.UserId].LevelId) + end end end diff --git a/src/ServerStorage/Proxy/MobsProxy/AI.luau b/src/ServerStorage/Proxy/MobsProxy/AI.luau index bdbc1ba..92b946f 100644 --- a/src/ServerStorage/Proxy/MobsProxy/AI.luau +++ b/src/ServerStorage/Proxy/MobsProxy/AI.luau @@ -1,12 +1,4 @@ ---[[ - Evercyan @ March 2023 - AI - - This script handles the physical behavior of mobs, notably Following and Jumping. - - Mobs automatically follow any player within range, but once attacked, they will no longer follow the closest - player within range for the follow session, and will instead start attacking the player which recently attacked it. -]] +-- 怪物ai 通用维护版 --> Services local Players = game:GetService("Players") @@ -23,7 +15,7 @@ local AI = {} -------------------------------------------------------------------------------- -- 获取两个单位之间的距离 -function AI:GetModelDistance(Unit1: TypeList.Character, Unit2: TypeList.Character): number +function AI:GetModelDistance(Unit1: Model, Unit2: Model): number return (Unit1:GetPivot().Position - Unit2:GetPivot().Position).Magnitude end diff --git a/src/ServerStorage/Proxy/MobsProxy/init.luau b/src/ServerStorage/Proxy/MobsProxy/init.luau index 650f15a..4e23130 100644 --- a/src/ServerStorage/Proxy/MobsProxy/init.luau +++ b/src/ServerStorage/Proxy/MobsProxy/init.luau @@ -28,10 +28,8 @@ local MobsFolder = Utils:CreateFolder("Mobs", game.Workspace) -------------------------------------------------------------------------------- local function GetPlayerMobsFolder(Player: Player) - local pData = Utils:GetPlayerDataFolder(Player) - if not pData then return end - local MobsFolder = pData:FindFirstChild("Mobs"):FindFirstChild(Player.UserId) - return MobsFolder + if not Player then warn("GetPlayerMobsFolder Player not found", Player.Name) return end + return MobsFolder:FindFirstChild(Player.UserId) end local function FindMobPrefab(MobModelName: string) @@ -41,7 +39,7 @@ end -------------------------------------------------------------------------------- local Mob = {} -Mob.__index = Mob +Mob.__index = Character function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) -- 获取玩家怪物目录 @@ -49,12 +47,12 @@ function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob if not playerMobsFolder then return end -- 获取怪物数据 - local MobData = Utils:GetJsonData(JsonMob, MobId) + local MobData = Utils:GetIdDataFromJson(JsonMob, MobId) if not MobData then warn("Mob Data not found", MobId) return end -- 获取怪物模型 - local MobInstance = FindMobPrefab(MobData.Model) - if not MobInstance then warn("Mob Prefab not found", MobData.Model) return end + local MobInstance = FindMobPrefab(MobData.model) + if not MobInstance then warn("Mob Prefab not found", MobData.model) return end -- 克隆怪物模型 local newMobModel = MobInstance:Clone() @@ -69,14 +67,14 @@ function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob -- 死亡函数 if OnMobDied then Mob.OnDied = OnMobDied end - -- 接入统一AI - self.Humanoid.MoveToFinished:Connect(function() - if not AI:GetClosestPlayer(Mob) and not Mob.isDead then - self.Root.Anchored = true - else - AI:StartTracking(Mob) - end - end) + -- -- 接入统一AI + -- self.Humanoid.MoveToFinished:Connect(function() + -- if not AI:GetClosestPlayer(Mob) and not Mob.isDead then + -- self.Root.Anchored = true + -- else + -- AI:StartTracking(Mob) + -- end + -- end) return self end @@ -91,9 +89,10 @@ end -- 给玩家创建怪物 function MobsProxy:CreateMob(Player: Player, MobId: number, AtkBonus: number?, HpBonus: number?, OnMobDied: ((Player: Player, Mob: TypeList.Character) -> ())?) local Mob = Mob.new(Player, MobId, OnMobDied) + AI:StartTracking(Mob) -- 关卡系数 - 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 + if AtkBonus then Mob:ChangeAttribute("attack", math.floor(Mob.Config.attack * (AtkBonus / 1000))) end + if HpBonus then Mob:ChangeAttribute("hp", math.floor(Mob.Config.hp * (HpBonus / 1000))) end MobsProxy.pData[Player.UserId][Mob.Instance] = Mob return Mob end @@ -118,12 +117,16 @@ end function MobsProxy:InitPlayer(Player: Player) local pData = Utils:GetPlayerDataFolder(Player) - if not pData then return end + if not pData then warn("Player Data not found", Player.Name) return end Utils:CreateFolder(Player.UserId, MobsFolder) MobsProxy.pData[Player.UserId] = {} end +function MobsProxy:GetPlayerMobs(Player: Player) + return MobsProxy.pData[Player.UserId] +end + function MobsProxy:OnPlayerRemoving(Player: Player) local pData = Utils:GetPlayerDataFolder(Player) if not pData then return end diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau b/src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau new file mode 100644 index 0000000..2542b06 --- /dev/null +++ b/src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau @@ -0,0 +1,73 @@ +-- 玩家关卡循环 + +--> Services +local ServerStorage = game:GetService("ServerStorage") +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Events +local BD_ChallengeEnd = ReplicatedStorage.Events.BD_ChallengeEnd + +--> Dependencies +local LevelProxy = require(ServerStorage.Proxy.LevelProxy) +local TypeList = require(ServerStorage.Base.TypeList) +local Utils = require(ReplicatedStorage.Tools.Utils) + +--> Json +local JsonLevel = require(ReplicatedStorage.Json.Level) + +-------------------------------------------------------------------------------- + +local LevelLoop = {} +LevelLoop.__index = LevelLoop + +function LevelLoop.new(Player: Player, Character: TypeList.Character) + print("LevelLoop:Init") + local self = {} + setmetatable(self, LevelLoop) + self.Player = Player + self.Character = Character + self.TaskAutoChallenge = nil + + self.ConChallengeEnd = BD_ChallengeEnd.Event:Connect(function(Player: Player, LevelId: number) + if Player ~= self.Player then return end + self:OnChallengeEnd(Player, LevelId) + end) + + self:AutoChallenge() + return self +end + +function LevelLoop:AutoChallenge() + print("AutoChallenge") + local LevelFolder = game.Workspace:WaitForChild("Level"):WaitForChild(self.Player.UserId) + local ProgressFolder = LevelFolder:FindFirstChild("Progress") + + local LevelId = ProgressFolder:FindFirstChild("Main").Value + local FailBossId = ProgressFolder:FindFirstChild("BossFail").Value + -- Boss失败了就去挑战上一个关卡 + if FailBossId == LevelId then + LevelId = LevelId - 1 + end + LevelProxy:ChallengeLevel(self.Player, LevelId) +end + +function LevelLoop:OnChallengeEnd(Player: Player, LevelId: number) + self.TaskAutoChallenge = task.defer(function() + task.wait(3) + self:AutoChallenge() + end) +end + +function LevelLoop:Destroy() + if self.TaskAutoChallenge then + task.cancel(self.TaskAutoChallenge) + self.TaskAutoChallenge = nil + end + if self.ConChallengeEnd then + self.ConChallengeEnd:Disconnect() + self.ConChallengeEnd = nil + end + self = nil +end + +return LevelLoop \ No newline at end of file diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau new file mode 100644 index 0000000..02cb310 --- /dev/null +++ b/src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau @@ -0,0 +1,101 @@ +-- 玩家AI +local PlayerAI = {} +PlayerAI.__index = PlayerAI + +--> Services +local Players = game:GetService("Players") +local ServerStorage = game:GetService("ServerStorage") + +--> Dependencies +local TypeList = require(ServerStorage.Base.TypeList) + +--> Variables +local DamageProxy = require(ServerStorage.Proxy.DamageProxy) +local ActivePlayers = {} + +local BehavioursFolder = ServerStorage:FindFirstChild("Modules"):FindFirstChild("Behaviours") + +local Behaviours = {} +local BehaviourFolder = ServerStorage:FindFirstChild("Modules"):FindFirstChild("Behaviours") +if BehaviourFolder then + for _, behaviourModule in ipairs(BehaviourFolder:GetChildren()) do + if behaviourModule:IsA("ModuleScript") then + local success, result = pcall(require, behaviourModule) + if success then + -- 去掉文件名后缀 + local name = behaviourModule.Name + Behaviours[name] = result + else + warn("加载代理模块失败: " .. behaviourModule.Name, result) + end + end + end +end + + +-------------------------------------------------------------------------------- + +-- 新建玩家AI +function PlayerAI.new(Player: Player, PlayerRole: TypeList.Character) + local self = {} + setmetatable(self, PlayerAI) + self.Character = PlayerRole + self.Player = Player + + self.BehaviourList = {} + self.LoopTask = task.spawn(function() + while task.wait(0.25) do + self:Update() + end + end) + + return self +end + +-- 核心循环 +function PlayerAI:Update() + local maxPriority = -math.huge + local bestBehaviour = nil + local bestCheckData = nil + + for _, behaviour in self.BehaviourList do + local priority, checkData = behaviour:Check(self.Character) + if priority > maxPriority then + maxPriority = priority + bestBehaviour = behaviour + bestCheckData = checkData + end + end + + if bestBehaviour and maxPriority > 0 then + bestBehaviour:Execute(bestCheckData) + end +end + +-- 动态添加行为 +function PlayerAI:AddBehaviour(BehaviourName: string) + if not Behaviours[BehaviourName] then warn("Behaviour not found") return end + local newBehaviour = Behaviours[BehaviourName]:Init(self.Character, self.Player) + self.BehaviourList[BehaviourName] = newBehaviour +end + +-- 动态删除行为 +function PlayerAI:RemoveBehaviour(BehaviourName: string) + if self.BehaviourList[BehaviourName] then + self.BehaviourList[BehaviourName]:Destroy() + self.BehaviourList[BehaviourName] = nil + end +end + +-- 销毁AI +function PlayerAI:Destroy() + for _, behaviour in self.BehaviourList do behaviour:Destroy() end + self.BehaviourList = nil + if self.LoopTask then + task.cancel(self.LoopTask) + self.LoopTask = nil + end + self = nil +end + +return PlayerAI \ No newline at end of file diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau new file mode 100644 index 0000000..15c679d --- /dev/null +++ b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau @@ -0,0 +1,104 @@ +-- 玩家循环代理 +local PlayerFightProxy = {} + +--> Services +local Players = game:GetService("Players") +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local ServerStorage = game:GetService("ServerStorage") + +--> Variables +local Character = require(ServerStorage.Base.Character) +local Utils = require(ReplicatedStorage.Tools.Utils) +local LevelProxy = require(ServerStorage.Proxy.LevelProxy) + +--> Json +local JsonCharacter = require(ReplicatedStorage.Json.Character) + +--> Dependencies +local LevelLoop = require(script.LevelLoop) +local PlayerAI = require(script.PlayerAI) + +-------------------------------------------------------------------------------- + +local function WaitForCharacter(Player: Player) + while not Player.Character or not Player.Character.Parent do + Player.CharacterAdded:Wait() + end + return Player.Character +end + +-------------------------------------------------------------------------------- + +local PlayerRole = {} +PlayerRole.__index = Character + +function PlayerRole.new(Player: Player, CharacterId: number) + local playerCharacter = WaitForCharacter(Player) + + -- 获取怪物数据 + local CharacterData = Utils:GetIdDataFromJson(JsonCharacter, CharacterId) + if not CharacterData then warn("CharacterId Data not found", CharacterId) return end + + -- 调用父类Character的new方法,初始化通用属性 + local self = Character.new(Player, playerCharacter, CharacterData) + setmetatable(self, PlayerRole) + return self +end + +function PlayerRole:Died() + self:ChangeState("Died", true) + LevelProxy:ChallengeEnd(self.Player, false) +end + +function PlayerRole:Respawn() + self:ChangeState("Died", false) + self:ChangeAttribute("hp", self.Config.maxhp) + -- 重置玩家位置 + +end + +-------------------------------------------------------------------------------- + +function PlayerFightProxy:InitPlayer(Player: Player) + if not PlayerFightProxy.pData then PlayerFightProxy.pData = {} end + if not PlayerFightProxy.pData[Player.UserId] then + PlayerFightProxy.pData[Player.UserId] = {} + end + + -- 生成玩家状态 + local PlayerRole = PlayerRole.new(Player, 1) + PlayerFightProxy.pData[Player.UserId].PlayerRole = PlayerRole + + -- 生成关卡循环 + local LevelLoop = LevelLoop.new(Player, PlayerRole) + PlayerFightProxy.pData[Player.UserId].LevelLoop = LevelLoop + + -- 生成玩家AI + local PlayerAI = PlayerAI.new(Player, PlayerRole) + PlayerFightProxy.pData[Player.UserId].PlayerAI = PlayerAI + PlayerAI:AddBehaviour("Move") +end + + +-- 重置玩家状态,但是不删除 +function PlayerFightProxy:CleanPlayer(Player: Player) + -- 玩家角色重生 + PlayerFightProxy.pData[Player.UserId].PlayerRole:Respawn() + -- AI重新生成 + PlayerFightProxy.pData[Player.UserId].PlayerAI:Destroy() + PlayerFightProxy.pData[Player.UserId].PlayerAI = nil + + local PlayerAI = PlayerAI.new(PlayerRole) + PlayerFightProxy.pData[Player.UserId].PlayerAI = PlayerAI +end + +function PlayerFightProxy:OnPlayerRemoving(Player: Player) + if not PlayerFightProxy.pData[Player.UserId] then warn("PlayerFight Remove Data not found", Player.Name) return end + PlayerFightProxy.pData[Player.UserId] = nil +end + +Players.PlayerRemoving:Connect(function(Player: Player) + PlayerFightProxy:OnPlayerRemoving(Player) +end) + +return PlayerFightProxy \ No newline at end of file diff --git a/src/ServerStorage/Proxy/PlayerInfoProxy.luau b/src/ServerStorage/Proxy/PlayerInfoProxy.luau index b949cf0..0489abe 100644 --- a/src/ServerStorage/Proxy/PlayerInfoProxy.luau +++ b/src/ServerStorage/Proxy/PlayerInfoProxy.luau @@ -1,4 +1,4 @@ --- 玩家基础信息代理 +-- 玩家通用信息 local PlayerInfoProxy = {} --> Services diff --git a/src/ServerStorage/Proxy/PlayerLoopProxy/PlayerAI.luau b/src/ServerStorage/Proxy/PlayerLoopProxy/PlayerAI.luau deleted file mode 100644 index e69de29..0000000 diff --git a/src/ServerStorage/Proxy/PlayerLoopProxy/init.luau b/src/ServerStorage/Proxy/PlayerLoopProxy/init.luau deleted file mode 100644 index a03e5cd..0000000 --- a/src/ServerStorage/Proxy/PlayerLoopProxy/init.luau +++ /dev/null @@ -1,6 +0,0 @@ --- 玩家循环代理 - -local PlayerLoopProxy = {} - - -return PlayerLoopProxy \ No newline at end of file