更新-玩家ai
需调试bug
This commit is contained in:
parent
d4435197ca
commit
962aedcb0d
BIN
excel/cha.xlsx
Normal file
BIN
excel/cha.xlsx
Normal file
Binary file not shown.
BIN
excel/enemy.xlsx
BIN
excel/enemy.xlsx
Binary file not shown.
3
src/ReplicatedStorage/Json/Character.json
Normal file
3
src/ReplicatedStorage/Json/Character.json
Normal file
@ -0,0 +1,3 @@
|
||||
[
|
||||
{"id":1,"name":1,"attack":10,"hp":100,"walkSpeed":10,"attackSpeed":2}
|
||||
]
|
@ -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"}
|
||||
]
|
@ -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)
|
||||
|
83
src/ServerStorage/Base/Behaviour.luau
Normal file
83
src/ServerStorage/Base/Behaviour.luau
Normal file
@ -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
|
@ -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
|
||||
|
||||
|
@ -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 {}
|
@ -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
|
@ -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
|
67
src/ServerStorage/Modules/Behaviours/Move.luau
Normal file
67
src/ServerStorage/Modules/Behaviours/Move.luau
Normal file
@ -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
|
@ -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
|
@ -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
|
@ -1,10 +0,0 @@
|
||||
export type Mob = {
|
||||
Instance: Model,
|
||||
Config: any,
|
||||
Root: BasePart,
|
||||
Enemy: Humanoid,
|
||||
Origin: CFrame,
|
||||
Respawn: (Mob) -> ()
|
||||
}
|
||||
|
||||
return {}
|
@ -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
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
73
src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau
Normal file
73
src/ServerStorage/Proxy/PlayerFightProxy/LevelLoop.luau
Normal file
@ -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
|
101
src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau
Normal file
101
src/ServerStorage/Proxy/PlayerFightProxy/PlayerAI.luau
Normal file
@ -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
|
104
src/ServerStorage/Proxy/PlayerFightProxy/init.luau
Normal file
104
src/ServerStorage/Proxy/PlayerFightProxy/init.luau
Normal file
@ -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
|
@ -1,4 +1,4 @@
|
||||
-- 玩家基础信息代理
|
||||
-- 玩家通用信息
|
||||
local PlayerInfoProxy = {}
|
||||
|
||||
--> Services
|
||||
|
@ -1,6 +0,0 @@
|
||||
-- 玩家循环代理
|
||||
|
||||
local PlayerLoopProxy = {}
|
||||
|
||||
|
||||
return PlayerLoopProxy
|
Loading…
x
Reference in New Issue
Block a user