This commit is contained in:
Ggafrik 2025-07-07 00:45:26 +08:00
parent 42dbc86b3a
commit d4435197ca
2 changed files with 214 additions and 212 deletions

View File

@ -29,222 +29,222 @@ local Random = Random.new()
local MobLib = {} -- Mirror table with the Mob constructor function local MobLib = {} -- Mirror table with the Mob constructor function
local Mob = {} -- Syntax sugar for mob-related functions -- local Mob = {} -- Syntax sugar for mob-related functions
Mob.__index = Mob -- Mob.__index = Mob
local MobsFolder = workspace:FindFirstChild("Mobs") -- local MobsFolder = workspace:FindFirstChild("Mobs")
if not MobsFolder then -- if not MobsFolder then
MobsFolder = Instance.new("Folder") -- MobsFolder = Instance.new("Folder")
MobsFolder.Name = "Mobs" -- MobsFolder.Name = "Mobs"
MobsFolder.Parent = workspace -- MobsFolder.Parent = workspace
end -- end
function MobLib.new(MobInstance: Model): Mobs.Mob -- function MobLib.new(MobInstance: Model): Mobs.Mob
local HumanoidRootPart = MobInstance:FindFirstChild("HumanoidRootPart") :: BasePart -- local HumanoidRootPart = MobInstance:FindFirstChild("HumanoidRootPart") :: BasePart
local Enemy = MobInstance:WaitForChild("Enemy") :: Humanoid -- local Enemy = MobInstance:WaitForChild("Enemy") :: Humanoid
local MobConfig = MobInstance:FindFirstChild("MobConfig") and require(MobInstance:FindFirstChild("MobConfig")) -- local MobConfig = MobInstance:FindFirstChild("MobConfig") and require(MobInstance:FindFirstChild("MobConfig"))
if not HumanoidRootPart or not Enemy or not MobConfig then -- if not HumanoidRootPart or not Enemy or not MobConfig then
print(HumanoidRootPart, Enemy, MobConfig) -- print(HumanoidRootPart, Enemy, MobConfig)
error(("MobLib.new: Passed mob '%s' is missing vital components."):format(MobInstance.Name)) -- error(("MobLib.new: Passed mob '%s' is missing vital components."):format(MobInstance.Name))
end -- end
local Mob = setmetatable({}, Mob) -- local Mob = setmetatable({}, Mob)
Mob.Instance = MobInstance -- Mob.Instance = MobInstance
Mob.Config = MobConfig -- Mob.Config = MobConfig
Mob.Root = HumanoidRootPart -- Mob.Root = HumanoidRootPart
Mob.Enemy = Enemy -- Mob.Enemy = Enemy
Mob.Origin = HumanoidRootPart:GetPivot() -- Mob.Origin = HumanoidRootPart:GetPivot()
Mob._Copy = MobInstance:Clone() -- Mob._Copy = MobInstance:Clone()
-- Initialize -- -- Initialize
Enemy.MaxHealth = MobConfig.Health -- Enemy.MaxHealth = MobConfig.Health
Enemy.Health = MobConfig.Health -- Enemy.Health = MobConfig.Health
Enemy.WalkSpeed = MobConfig.WalkSpeed -- Enemy.WalkSpeed = MobConfig.WalkSpeed
Enemy.JumpPower = MobConfig.JumpPower -- Enemy.JumpPower = MobConfig.JumpPower
HumanoidRootPart.Anchored = true -- HumanoidRootPart.Anchored = true
MobInstance.Parent = MobsFolder -- MobInstance.Parent = MobsFolder
-- Set humanoid states (helps prevent falling down & useless calculations - you're unlikely to have an enemy climbing without pathfinding) -- -- 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 -- for _, EnumName in {"FallingDown", "Seated", "Flying", "Swimming", "Climbing"} do
local HumanoidStateType = Enum.HumanoidStateType[EnumName] -- local HumanoidStateType = Enum.HumanoidStateType[EnumName]
Enemy:SetStateEnabled(HumanoidStateType, false) -- Enemy:SetStateEnabled(HumanoidStateType, false)
if Enemy:GetState() == HumanoidStateType then -- if Enemy:GetState() == HumanoidStateType then
Enemy:ChangeState(Enum.HumanoidStateType.Running) -- Enemy:ChangeState(Enum.HumanoidStateType.Running)
end -- end
end -- end
-- Damage -- -- Damage
local function OnTouched(BasePart) -- local function OnTouched(BasePart)
local Player = Players:GetPlayerFromCharacter(BasePart.Parent) -- local Player = Players:GetPlayerFromCharacter(BasePart.Parent)
local Humanoid = Player and Player.Character:FindFirstChild("Humanoid") -- local Humanoid = Player and Player.Character:FindFirstChild("Humanoid")
if Humanoid and Enemy.Health > 0 then -- if Humanoid and Enemy.Health > 0 then
if DamageCooldown[Player.UserId] then -- if DamageCooldown[Player.UserId] then
return -- return
end -- end
DamageCooldown[Player.UserId] = true -- DamageCooldown[Player.UserId] = true
task.delay(0.5, function() -- task.delay(0.5, function()
DamageCooldown[Player.UserId] = nil -- DamageCooldown[Player.UserId] = nil
end) -- end)
Humanoid.Health = math.clamp(Humanoid.Health - MobConfig.Damage, 0, Humanoid.MaxHealth) -- Humanoid.Health = math.clamp(Humanoid.Health - MobConfig.Damage, 0, Humanoid.MaxHealth)
ReplicatedStorage.Remotes.MobDamagedPlayer:FireClient(Player, MobInstance, MobConfig.Damage) -- ReplicatedStorage.Remotes.MobDamagedPlayer:FireClient(Player, MobInstance, MobConfig.Damage)
end -- end
end -- end
(MobInstance:WaitForChild("Hitbox") :: BasePart).Touched:Connect(OnTouched) -- (MobInstance:WaitForChild("Hitbox") :: BasePart).Touched:Connect(OnTouched)
-- Respawn -- -- Respawn
Enemy.Died:Once(function() -- Enemy.Died:Once(function()
if not Mob.isDead then -- if not Mob.isDead then
Mob.isDead = true -- Mob.isDead = true
Mob:ActivateRagdoll() -- Mob:ActivateRagdoll()
Mob:AwardDrops() -- Mob:AwardDrops()
task.wait(MobConfig.RespawnTime or 5) -- task.wait(MobConfig.RespawnTime or 5)
Mob:Respawn() -- Mob:Respawn()
end -- end
end) -- end)
-- Following has finished. Anchor assembly to optimize. -- -- Following has finished. Anchor assembly to optimize.
Enemy.MoveToFinished:Connect(function() -- Enemy.MoveToFinished:Connect(function()
if not AI:GetClosestPlayer(Mob) and not Mob.isDead then -- if not AI:GetClosestPlayer(Mob) and not Mob.isDead then
HumanoidRootPart.Anchored = true -- HumanoidRootPart.Anchored = true
else -- else
AI:StartTracking(Mob) -- AI:StartTracking(Mob)
end -- end
end) -- end)
Mobs[MobInstance] = Mob -- Mobs[MobInstance] = Mob
return Mob -- return Mob
end -- end
function Mob:Destroy() -- function Mob:Destroy()
if not self.Destroyed then -- if not self.Destroyed then
self.Destroyed = true -- self.Destroyed = true
Mobs[self.Instance] = nil -- Mobs[self.Instance] = nil
self.Instance:Destroy() -- self.Instance:Destroy()
-- Remove instance references -- -- Remove instance references
self.Instance = nil -- self.Instance = nil
self.Root = nil -- self.Root = nil
self.Enemy = nil -- self.Enemy = nil
self._Copy = nil -- self._Copy = nil
end -- end
end -- end
function Mob:TakeDamage(Damage: number) -- function Mob:TakeDamage(Damage: number)
local Enemy = self.Enemy -- local Enemy = self.Enemy
if not self.isDead then -- if not self.isDead then
Enemy.Health = math.clamp(Enemy.Health - Damage, 0, Enemy.MaxHealth) -- Enemy.Health = math.clamp(Enemy.Health - Damage, 0, Enemy.MaxHealth)
end -- end
end -- end
function Mob:Respawn() -- function Mob:Respawn()
if not self.Destroyed then -- if not self.Destroyed then
local NewMob = self._Copy -- local NewMob = self._Copy
self:Destroy() -- self:Destroy()
NewMob.Parent = MobsFolder -- NewMob.Parent = MobsFolder
end -- end
end -- end
function Mob:ActivateRagdoll() -- function Mob:ActivateRagdoll()
for _, Item in self.Instance:GetDescendants() do -- for _, Item in self.Instance:GetDescendants() do
if Item:IsA("Motor6D") then -- if Item:IsA("Motor6D") then
local Attachment0 = Instance.new("Attachment") -- local Attachment0 = Instance.new("Attachment")
Attachment0.CFrame = Item.C0 -- Attachment0.CFrame = Item.C0
Attachment0.Parent = Item.Part0 -- Attachment0.Parent = Item.Part0
local Attachment1 = Instance.new("Attachment") -- local Attachment1 = Instance.new("Attachment")
Attachment1.CFrame = Item.C1 -- Attachment1.CFrame = Item.C1
Attachment1.Parent = Item.Part1 -- Attachment1.Parent = Item.Part1
local Constraint = Instance.new("BallSocketConstraint") -- local Constraint = Instance.new("BallSocketConstraint")
Constraint.Attachment0 = Attachment0 -- Constraint.Attachment0 = Attachment0
Constraint.Attachment1 = Attachment1 -- Constraint.Attachment1 = Attachment1
Constraint.LimitsEnabled = true -- Constraint.LimitsEnabled = true
Constraint.TwistLimitsEnabled = true -- Constraint.TwistLimitsEnabled = true
Constraint.Parent = Item.Parent -- Constraint.Parent = Item.Parent
Item.Enabled = false -- Item.Enabled = false
end -- end
end -- end
end -- end
function Mob:AwardDrops() -- function Mob:AwardDrops()
local PlayerTags = self.Instance:FindFirstChild("PlayerTags") :: Configuration -- local PlayerTags = self.Instance:FindFirstChild("PlayerTags") :: Configuration
if not PlayerTags then return end -- if not PlayerTags then return end
for UserId, Damage: number in PlayerTags:GetAttributes() do -- for UserId, Damage: number in PlayerTags:GetAttributes() do
UserId = tonumber(UserId) -- UserId = tonumber(UserId)
local Player = Players:GetPlayerByUserId(UserId) -- local Player = Players:GetPlayerByUserId(UserId)
if not Player then continue end -- if not Player then continue end
local Percent = Damage / self.Enemy.MaxHealth -- local Percent = Damage / self.Enemy.MaxHealth
if Percent >= 0.25 then -- if Percent >= 0.25 then
local pData = PlayerData:FindFirstChild(Player.UserId) -- local pData = PlayerData:FindFirstChild(Player.UserId)
local Statistics = pData:FindFirstChild("Stats") -- local Statistics = pData:FindFirstChild("Stats")
local Items = pData:FindFirstChild("Items") -- local Items = pData:FindFirstChild("Items")
-- Stats -- -- Stats
if Statistics then -- if Statistics then
for _, StatInfo in self.Config.Drops.Statistics do -- for _, StatInfo in self.Config.Drops.Statistics do
local StatName: string = StatInfo[1] -- local StatName: string = StatInfo[1]
local StatCount: number = StatInfo[2] -- local StatCount: number = StatInfo[2]
local Stat = Statistics:FindFirstChild(StatName) -- local Stat = Statistics:FindFirstChild(StatName)
if Stat then -- if Stat then
Stat.Value += StatCount -- Stat.Value += StatCount
end -- end
end -- end
end -- end
-- Items -- -- Items
if Items then -- if Items then
for _, ItemInfo in self.Config.Drops.Items do -- for _, ItemInfo in self.Config.Drops.Items do
local ItemType: string = ItemInfo[1] -- local ItemType: string = ItemInfo[1]
local ItemName: string = ItemInfo[2] -- local ItemName: string = ItemInfo[2]
local DropChance: number = ItemInfo[3] -- local DropChance: number = ItemInfo[3]
local Item = ContentLibrary[ItemType] and ContentLibrary[ItemType][ItemName] -- local Item = ContentLibrary[ItemType] and ContentLibrary[ItemType][ItemName]
if Item then -- if Item then
local isLucky = Random:NextInteger(1, DropChance) == 1 -- local isLucky = Random:NextInteger(1, DropChance) == 1
if isLucky then -- if isLucky then
require(ServerStorage.Modules[ItemType .."Lib"]):Give(Player, Item) -- require(ServerStorage.Modules[ItemType .."Lib"]):Give(Player, Item)
ReplicatedStorage.Remotes.SendNotification:FireClient(Player, -- ReplicatedStorage.Remotes.SendNotification:FireClient(Player,
"Item Dropped", -- "Item Dropped",
self.Config.Name .." dropped ".. Item.Name .." at a 1/".. FormatNumber(DropChance, "Suffix") .." chance.", -- self.Config.Name .." dropped ".. Item.Name .." at a 1/".. FormatNumber(DropChance, "Suffix") .." chance.",
Item.Config.IconId -- Item.Config.IconId
) -- )
end -- end
else -- else
warn("[Kit/MobLib/AwardDrops]: Item doesn't exist: '".. ItemType .."/".. ItemName ..".") -- warn("[Kit/MobLib/AwardDrops]: Item doesn't exist: '".. ItemType .."/".. ItemName ..".")
end -- end
end -- end
end -- end
-- TeleportLocation (TP) -- -- TeleportLocation (TP)
local TP = self.Config.TeleportLocation and workspace.TP:FindFirstChild(self.Config.TeleportLocation) -- local TP = self.Config.TeleportLocation and workspace.TP:FindFirstChild(self.Config.TeleportLocation)
local Character = Player.Character -- local Character = Player.Character
if TP and Character then -- if TP and Character then
Character:PivotTo(TP.CFrame + Vector3.yAxis*4) -- Character:PivotTo(TP.CFrame + Vector3.yAxis*4)
end -- end
end -- end
end -- end
end -- end
CollectionService:GetInstanceAddedSignal("Mob"):Connect(function(MobInstance) -- 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. -- 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) -- MobLib.new(MobInstance)
end -- end
end) -- end)
for _, MobInstance in CollectionService:GetTagged("Mob") do -- for _, MobInstance in CollectionService:GetTagged("Mob") do
task.defer(MobLib.new, MobInstance) -- task.defer(MobLib.new, MobInstance)
end -- end
return MobLib return MobLib

View File

@ -108,16 +108,13 @@ end
function LevelProxy:InitPlayer(Player: Player) function LevelProxy:InitPlayer(Player: Player)
local pData = Utils:GetPlayerDataFolder(Player) local pData = Utils:GetPlayerDataFolder(Player)
if not pData then return end if not pData then warn("Level pData not found", Player.UserId) return end
local LevelFolder = Utils:CreateFolder(STORE_NAME, pData) local PlayerLevelFolder = Utils:CreateFolder(Player.UserId, LevelFolder)
local ProgressFolder = Utils:CreateFolder("Progress", LevelFolder) local ProgressFolder = Utils:CreateFolder("Progress", PlayerLevelFolder)
local DungeonFolder = Utils:CreateFolder("Dungeon", LevelFolder) local DungeonFolder = Utils:CreateFolder("Dungeon", PlayerLevelFolder)
local ChallengeFolder = Utils:CreateFolder("Challenge", LevelFolder) local ChallengeFolder = Utils:CreateFolder("Challenge", PlayerLevelFolder)
-- 当前关卡状态 -- 当前关卡状态
Utils:CreateFolder("Stats", LevelFolder) Utils:CreateFolder("Stats", PlayerLevelFolder)
-- 关卡目录下生成玩家目录
local spawnFloder = Utils:CreateFolder(Player.UserId, game.Workspace:FindFirstChild(STORE_NAME))
-- 新玩家数据初始化 -- 新玩家数据初始化
if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then
@ -152,11 +149,13 @@ function LevelProxy:InitPlayer(Player: Player)
if key == "Task" or key == "Mobs" then continue end if key == "Task" or key == "Mobs" then continue end
CreateLevelInstance(Player, ChallengeFolder, key, value) CreateLevelInstance(Player, ChallengeFolder, key, value)
end end
self:ChallengeLevel(Player, 1)
end end
-- 挑战关卡(挑战副本用另一个函数) -- 挑战关卡(挑战副本用另一个函数)
function LevelProxy:ChallengeLevel(Player: Player, LevelId: number) function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
local LevelData = Utils:GetJsonData(JsonLevel, LevelId) local LevelData = Utils:GetIdDataFromJson(JsonLevel, LevelId)
if not LevelData then warn("Level Data not found", LevelId) return end if not LevelData then warn("Level Data not found", LevelId) return end
-- 给前端传数据,做表现 -- 给前端传数据,做表现
@ -165,7 +164,7 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
-- 后端生成当前关卡状态数据 -- 后端生成当前关卡状态数据
local LevelFolder = GetPlayerLevelWorkspaceFolder(Player.UserId) local LevelFolder = GetPlayerLevelWorkspaceFolder(Player.UserId)
local ChallengeFolder = LevelFolder:FindFirstChild("Challenge") local ChallengeFolder = LevelFolder:FindFirstChild("Challenge")
if not ChallengeFolder then return end if not ChallengeFolder then warn("ChallengeFolder not found") return end
local levelTask = task.defer(function() local levelTask = task.defer(function()
ChangeValue(Player, ChallengeFolder, "IsBoss", LevelData.type == 1 and true or false) ChangeValue(Player, ChallengeFolder, "IsBoss", LevelData.type == 1 and true or false)
ChangeValue(Player, ChallengeFolder, "LevelId", LevelId) ChangeValue(Player, ChallengeFolder, "LevelId", LevelId)
@ -174,16 +173,18 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
ChangeValue(Player, ChallengeFolder, "ShouldWave", 1) ChangeValue(Player, ChallengeFolder, "ShouldWave", 1)
ChangeValue(Player, ChallengeFolder, "MaxTime", LevelData.timeLimit or 0) ChangeValue(Player, ChallengeFolder, "MaxTime", LevelData.timeLimit or 0)
ChangeValue(Player, ChallengeFolder, "Mobs", {}) ChangeValue(Player, ChallengeFolder, "Mobs", {})
ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", false)
if LevelData.timeLimit then
while task.wait(1) do while task.wait(1) do
ChangeValue(Player, ChallengeFolder, "Time", LevelProxy.pData[Player.UserId].Time + 1) ChangeValue(Player, ChallengeFolder, "Time", LevelProxy.pData[Player.UserId].Time + 1)
-- 关卡生成 -- 关卡生成
if LevelProxy.pData[Player.UserId].Wave < #LevelData.wave then if LevelProxy.pData[Player.UserId].NowWave < #LevelData.wave and
ChangeValue(Player, ChallengeFolder, "Wave", LevelProxy.pData[Player.UserId].Wave + 1) LevelProxy.pData[Player.UserId].NowWave < LevelProxy.pData[Player.UserId].ShouldWave and
end LevelProxy.pData[Player.UserId].SpawnWaveFinish == false then
if LevelProxy.pData[Player.UserId].NowWave < LevelProxy.pData[Player.UserId].ShouldWave then 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) ChangeValue(Player, ChallengeFolder, "NowWave", LevelProxy.pData[Player.UserId].NowWave + 1)
local waveData = LevelData.wave[LevelProxy.pData[Player.UserId].NowWave] local waveData = LevelData.wave[LevelProxy.pData[Player.UserId].NowWave]
for i = 1, #waveData, 3 do for i = 1, #waveData, 3 do
@ -192,14 +193,15 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
for _ = 1, mobCount do for _ = 1, mobCount do
local mob = MobsProxy:CreateMob(Player, mobId, LevelData.atkBonus, LevelData.hpBonus, OnMobDied) local mob = MobsProxy:CreateMob(Player, mobId, LevelData.atkBonus, LevelData.hpBonus, OnMobDied)
table.insert(LevelProxy.pData[Player.UserId].Mobs, mob) table.insert(LevelProxy.pData[Player.UserId].Mobs, mob)
print(mob)
end end
end end
ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", true) ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", true)
end end
if LevelData.timeLimit then
-- 时间结束 -- 时间结束
if LevelProxy.pData[Player.UserId].Time >= LevelData.timeLimit then if LevelProxy.pData[Player.UserId].Time >= LevelData.timeLimit then
self:ChallengeEnd(Player, false) self:ChallengeEnd(Player, false)
break
end end
end end
end end