更新-主要符文后端内容

This commit is contained in:
gechangfu 2025-08-14 17:01:07 +08:00
parent 4ccf2a6709
commit 924b1dccad
12 changed files with 569 additions and 21 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,31 @@
local ScriptModule = {}
-- 将目标类的所有函数全部注入到指定表中
function ScriptModule:InjectScriptFunctions(ParentTable: table, ScriptModule: table)
for key, value in ScriptModule do
if typeof(value) == "function" then
ParentTable[key] = value
end
end
end
-- 注入文件夹下所有模块,到指定表中
function ScriptModule:InjectFolderModules(ParentTable: table, Folder: Instance)
if Folder then
for _, module in ipairs(Folder:GetChildren()) do
if module:IsA("ModuleScript") then
local success, result = pcall(require, module)
if success then
-- 去掉文件名后缀
local name = module.Name
ParentTable[name] = result
else
warn("加载代理模块失败: " .. module.Name, result)
end
end
end
end
end
return ScriptModule

View File

@ -0,0 +1,108 @@
local Rune = {}
Rune.__index = Rune
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
--> Dependencies
local TypeList = require(ServerStorage.Base.TypeList)
local Communicate = require(ServerStorage.Modules.Tools.Communicate)
local Utils = require(ReplicatedStorage.Tools.Utils)
function Rune:Init(PlayerAI: Player, Character: TypeList.Character, ScriptName: string)
local self = {}
setmetatable(self, Rune)
self.PlayerAI = PlayerAI
self.Character = Character
self.ScriptName = ScriptName
self.TriggerSlot = 0
self.WearingSlot = 0
self.TriggerTime = 0
end
-- 触发最开始事件
function Rune:OnStartEvent(runeName: string, triggerSlot: number, AttributesData: table?, BehaviorNameList: table?)
-- runeName: 符文名称
-- triggerSlot: 触发槽位
-- AttributesData: 属性数据,符文可以修改
-- BehaviorNameList: 行为名称列表,符文可以修改
-- 返回值: 可以返回下一个index来控制流程或者nil表示不干预
end
-- 卡牌触发前事件
function Rune:OnTriggerBeginEvent(index: number, AttributesData: table?, BehaviorNameList: table?)
-- index: 当前符文在列表中的位置
-- AttributesData: 属性数据,符文可以修改
-- BehaviorNameList: 行为名称列表,符文可以修改
-- 返回值: 可以返回下一个index来控制流程或者nil表示不干预
end
-- 卡牌触发后事件
function Rune:OnTriggerEndEvent(index: number, AttributesData: table?, BehaviorNameList: table?)
-- index: 当前符文在列表中的位置
-- AttributesData: 属性数据,符文可以修改
-- BehaviorNameList: 行为名称列表,符文可以修改
-- 返回值: 可以返回下一个index来控制流程或者nil表示不干预
end
-- 触发结束事件
function Rune:OnEndEvent(runeName: string, triggerSlot: number, AttributesData: table?, BehaviorNameList: table?)
-- runeName: 符文名称
-- triggerSlot: 触发槽位
-- AttributesData: 属性数据,符文可以修改
-- BehaviorNameList: 行为名称列表,符文可以修改
-- 返回值: 可以返回下一个index来控制流程或者nil表示不干预
end
-- 检查事件
function Rune:Check(index: number, AttributesData: table?, BehaviorNameList: table?)
-- index: 当前符文在列表中的位置
-- AttributesData: 属性数据,符文可以修改
-- BehaviorNameList: 行为名称列表,符文可以修改
return false -- 默认返回false表示不可以执行
end
-- 执行事件
function Rune:Execute(index: number, AttributesData: table?, BehaviorNameList: table?)
-- 使用PlayerAI的专用方法性能更好
local beginNextIndex = self.PlayerAI:TriggerAllRunesExcept(self, "OnTriggerBeginEvent", index, AttributesData, BehaviorNameList)
if type(beginNextIndex) == "number" then
return beginNextIndex
end
-- 执行自己的逻辑
local nextIndex = self:OnExecute(index, AttributesData, BehaviorNameList)
if type(nextIndex) == "number" then
self.PlayerAI:TriggerAllRunesExcept(self, "OnTriggerEndEvent", index, AttributesData, BehaviorNameList)
return nextIndex
end
local endNextIndex = self.PlayerAI:TriggerAllRunesExcept(self, "OnTriggerEndEvent", index, AttributesData, BehaviorNameList)
if type(endNextIndex) == "number" then
return endNextIndex
end
self.TriggerTime = self.TriggerTime + 1
return nextIndex -- 返回下一个要执行的index
end
-- 子类可以重写这个方法
function Rune:OnExecute(index: number, AttributesData: table?, BehaviorNameList: table?)
-- 默认实现,子类可以重写
-- index: 当前符文在列表中的位置
-- AttributesData: 属性数据,符文可以修改
-- BehaviorNameList: 行为名称列表,符文可以修改
-- 返回值: 下一个要执行的index或者nil表示继续下一个
return nil -- 默认继续下一个符文
end
-- 销毁
function Rune:OnDestroy()
self = nil
end
return Rune

View File

@ -0,0 +1,71 @@
-- 冰棺技能:残血后立即回血
--> Services
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Dependencies
local TypeList = require(ServerStorage.Base.TypeList)
local Behaviour = require(ServerStorage.Base.Behaviour)
local MobsProxy = require(ServerStorage.Proxy.MobsProxy)
local DamageProxy = require(ServerStorage.Proxy.DamageProxy)
local Utils = require(ReplicatedStorage.Tools.Utils)
--------------------------------------------------------------------------------
local IceCoffine = {}
IceCoffine.__index = IceCoffine
setmetatable(IceCoffine, {__index = Behaviour})
local CAST_DISTANCE = 50
local PROJECTILE_LENGTH = 50
local PROJECTILE_DURATION = 3
local COOLDOWN = 1000
function IceCoffine:Init(PlayerAI, Character: TypeList.Character, Player: Player)
local self = Behaviour:Init(PlayerAI, Character, script.Name)
self.Player = Player
self.Mobs = nil
setmetatable(self, IceCoffine)
self.OrgCooldown = COOLDOWN
self:StartCooldownTask()
-- 客户端表现
self:SendPerformanceEvent("Init", self.Player, script.Name, true, {})
return self
end
function IceCoffine:Check(CheckInfo: table)
if Behaviour.CheckStat(self) then return -1, self.CheckData end
self:CheckClean()
-- 当前血量<=20%时触发技能
local maxHp = self.Character.Stats.MaxHp
local currentHp = self.Character.Stats.Hp
local recoverHp = maxHp * 0.2
if currentHp <= recoverHp then
self.CheckData = {}
return 300, self.CheckData
end
-- 返回优先级,执行数据
return -1, self.CheckData
end
function IceCoffine:Execute()
self.ExeTask = task.spawn(function ()
self:ChangeExecutingState(true)
-- cd放前面之后发送事件才能正常记录cd
self:StartCooldownTask()
local maxHp = self.Character.Stats.MaxHp
local recoverHp = math.floor(maxHp * (self.PlayerAI:GetSharedData("IceCoffin_MaxRecover") / 100))
DamageProxy:Heal(self.Character, self.Character, recoverHp)
task.wait(0.5)
self:ChangeExecutingState(false)
end)
end
return IceCoffine

View File

@ -0,0 +1,31 @@
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Dependencies
local Utils = require(ReplicatedStorage.Tools.Utils)
local RuneFireDamage = {}
RuneFireDamage.__index = RuneFireDamage
setmetatable(RuneFireDamage, {__index = Rune})
function RuneFireDamage:Init(PlayerAI, Character: TypeList.Character, Player: Player)
local self = Rune:Init(PlayerAI, Character, script.Name)
self.Player = Player
setmetatable(self, RuneFireDamage)
return self
end
function RuneFireDamage:Check(index: number, AttributesData: table, BehaviorNameList: table)
return true
end
function RuneFireDamage:OnExecute(index: number, AttributesData: table, BehaviorNameList: table)
print("RuneFireDamage:OnExecute", index, AttributesData, BehaviorNameList)
Utils:TableSafeAddValue(AttributesData, "fireAtk", 100)
return nil
end
return RuneFireDamage

View File

@ -0,0 +1,36 @@
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Dependencies
local Utils = require(ReplicatedStorage.Tools.Utils)
local RuneIceCoffin = {}
RuneIceCoffin.__index = RuneIceCoffin
setmetatable(RuneIceCoffin, {__index = Rune})
function RuneIceCoffin:Init(PlayerAI, Character: TypeList.Character, Player: Player)
local self = Rune:Init(PlayerAI, Character, script.Name)
self.Player = Player
setmetatable(self, RuneIceCoffin)
return self
end
function RuneIceCoffin:Check(index: number, AttributesData: table, BehaviorNameList: table)
return true
end
function RuneIceCoffin:OnExecute(index: number, AttributesData: table, BehaviorNameList: table)
print("RuneIceCoffin:OnExecute", index, AttributesData, BehaviorNameList)
local maxRecover = self.PlayerAI:GetSharedData("IceCoffin_MaxRecover")
if maxRecover then
self.PlayerAI:SetSharedData("IceCoffin_MaxRecover", maxRecover + 60)
else
self.PlayerAI:SetSharedData("IceCoffin_MaxRecover", 60)
end
return nil
end
return RuneIceCoffin

View File

@ -42,6 +42,12 @@ export type DamageInfo = {
ElementType: ElementType,
}
export type HealInfo = {
Caster: TypeList.Character,
Victim: TypeList.Character,
Amount: number,
}
DamageProxy.DamageType = DamageType
DamageProxy.DamageTag = DamageTag
DamageProxy.ElementType = ElementType
@ -275,4 +281,58 @@ function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Cha
end
end
-- 治疗处理(简化版本)
function DamageProxy:Heal(Caster: TypeList.Character, Victim: TypeList.Character, Amount: number)
-- 基础参数验证
if not Caster or not Victim or not Amount or Amount <= 0 then
warn("Heal: 基础参数无效")
return
end
-- 注册对象到生命周期管理器
globalLifecycleManager:RegisterObject(Caster)
globalLifecycleManager:RegisterObject(Victim)
-- 检查对象是否仍然有效
if not globalLifecycleManager:IsObjectValid(Caster) or not globalLifecycleManager:IsObjectValid(Victim) then
warn("Heal: 施法者或目标已被销毁")
return
end
-- 治疗计算
local VictimHealth = Victim:GetAttributeValue("hp") or 0
local MaxHealth = Victim:GetAttributeValue("maxHp") or 0
-- 计算实际治疗量(不超过最大生命值)
local actualHeal = math.min(Amount, MaxHealth - VictimHealth)
local overheal = math.max(0, Amount - actualHeal)
if actualHeal > 0 then
Victim:ChangeAttributeValue("hp", VictimHealth + actualHeal)
end
-- 给前端发送治疗信息
local clientHealInfo = {
Caster = Caster.Instance,
Victim = Victim.Instance,
ServerTime = os.time(),
HealPosition = Victim.Instance.PrimaryPart.Position,
HealAmount = actualHeal,
Overheal = overheal,
OriginalHealth = VictimHealth,
NewHealth = VictimHealth + actualHeal
}
-- 发送数据到客户端
local success, error = pcall(function()
Communicate:SendToClient(RE_DamagePerformance, "Heal", Caster.Player, clientHealInfo)
end)
if not success then
warn("Heal: 发送治疗数据失败:", error)
end
return actualHeal, overheal
end
return DamageProxy

View File

@ -10,6 +10,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Dependencies
local TypeList = require(ServerStorage.Base.TypeList)
local Utils = require(ReplicatedStorage.Tools.Utils)
local ScriptModule = require(ReplicatedStorage.Tools.ScriptModule)
local Communicate = require(ServerStorage.Modules.Tools.Communicate)
--> Events
@ -21,21 +22,10 @@ local DamageProxy = require(ServerStorage.Proxy.DamageProxy)
local ActivePlayers = {}
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
ScriptModule:InjectFolderModules(Behaviours, ServerStorage:FindFirstChild("Modules"):FindFirstChild("Behaviours"))
local Runes = {}
ScriptModule:InjectFolderModules(Runes, ServerStorage:FindFirstChild("Modules"):FindFirstChild("Runes"))
--------------------------------------------------------------------------------
@ -53,6 +43,8 @@ function PlayerAI.new(Player: Player, PlayerRole: TypeList.Character)
self.Counter = 0
self.BehaviourList = {}
self.RuneList = {}
self.SharedData = {} -- 符文和行为之间的共享数据
self.LoopTask = task.spawn(function()
while task.wait(0.25) do
if self.Character.Stats.Died then continue end
@ -109,11 +101,118 @@ function PlayerAI:AddBehaviour(BehaviourName: string, WearingSlot: number?)
self.BehaviourList[BehaviourName] = newBehaviour
end
-- 动态添加符文
function PlayerAI:AddRune(RuneName: string, WearingSlot: number?)
if not Runes[RuneName] then warn("Rune not found") return end
local newRune = Runes[RuneName]:Init(self, self.Character, self.Player)
newRune.WearingSlot = WearingSlot or 4
self.RuneList[RuneName] = newRune
-- 更新所有符文的TriggerSlot
self:UpdateRuneTriggerSlots()
end
-- 更新所有符文的TriggerSlot
function PlayerAI:UpdateRuneTriggerSlots()
-- 收集所有符文并按WearingSlot排序
local runeArray = {}
for runeName, rune in pairs(self.RuneList) do
table.insert(runeArray, {
name = runeName,
rune = rune,
slot = rune.WearingSlot
})
end
-- 按WearingSlot排序
table.sort(runeArray, function(a, b)
return a.slot < b.slot
end)
-- 分配TriggerSlot从1开始排除空槽位
local currentTriggerSlot = 1
for _, runeData in ipairs(runeArray) do
runeData.rune.TriggerSlot = currentTriggerSlot
currentTriggerSlot = currentTriggerSlot + 1
end
end
-- 获取并记录行为UniqueId
function PlayerAI:GetBehaviourUniqueId()
return Utils:GenUniqueIdPlayerAI(self.LastTime, self.Counter)
end
-- 获取当前符文列表
function PlayerAI:GetRuneList()
local runeArray = {}
for runeName, rune in self.RuneList do
runeArray[rune.TriggerSlot] = runeName
end
return runeArray
end
-- 遍历符文并执行回调
function PlayerAI:ForEachRune(callback: (runeName: string, triggerSlot: number, rune: table) -> ())
for runeName, rune in pairs(self.RuneList) do
local result = callback(runeName, rune.TriggerSlot, rune)
-- 如果回调返回了数字,表示要控制流程
if type(result) == "number" then
return result
end
end
return nil -- 没有符文要控制流程
end
-- 触发所有符文的指定事件(除了指定符文)
function PlayerAI:TriggerAllRunesExcept(excludeRune: table, eventName: string, ...)
for runeName, rune in pairs(self.RuneList) do
if rune ~= excludeRune and rune[eventName] and typeof(rune[eventName]) == "function" then
local result = rune[eventName](rune, ...)
if type(result) == "number" then
return result
end
end
end
return nil
end
-- 触发所有符文的指定事件
function PlayerAI:TriggerAllRunes(eventName: string, ...)
for runeName, rune in pairs(self.RuneList) do
if rune[eventName] and typeof(rune[eventName]) == "function" then
rune[eventName](rune, ...)
end
end
end
-- 触发指定符文的指定事件
function PlayerAI:TriggerRune(runeName: string, eventName: string, ...)
if self.RuneList[runeName] and self.RuneList[runeName][eventName] and typeof(self.RuneList[runeName][eventName]) == "function" then
return self.RuneList[runeName][eventName](self.RuneList[runeName], ...)
end
end
-- 设置共享数据
function PlayerAI:SetSharedData(key: string, value: any)
self.SharedData[key] = value
end
-- 获取共享数据
function PlayerAI:GetSharedData(key: string)
return self.SharedData[key]
end
-- 清除共享数据
function PlayerAI:ClearSharedData(key: string?)
if key then
self.SharedData[key] = nil
else
self.SharedData = {}
end
end
-- 获取客户端行为列表
function PlayerAI:GetClientBehaviourList()
local clientBehaviourList = {
@ -144,6 +243,24 @@ function PlayerAI:RemoveBehaviour(BehaviourName: string)
end
end
-- 动态删除符文
function PlayerAI:RemoveRune(RuneName: string)
if self.RuneList[RuneName] then
self.RuneList[RuneName]:Destroy()
self.RuneList[RuneName] = nil
-- 更新所有符文的TriggerSlot
self:UpdateRuneTriggerSlots()
end
end
-- 清除所有符文
function PlayerAI:ClearAllRune()
for _, rune in self.RuneList do rune:Destroy() end
self.RuneList = {}
-- 清除后不需要更新TriggerSlot因为已经没有符文了
end
-- 清除所有行为
function PlayerAI:ClearAllBehaviour()
for _, behaviour in self.BehaviourList do behaviour:Destroy() end

View File

@ -192,13 +192,17 @@ function PlayerFightProxy:UpdatePlayerFightData(Player: Player)
end
-- 根据技能添加玩家AI行为
local abilityIdList, behaviourNameList = AbilityProxy:GetPlayerWearingAbilityData(Player)
local abilityIdList, behaviorNameList = AbilityProxy:GetPlayerWearingAbilityData(Player)
local playerAI = PlayerFightProxy:GetPlayerAI(Player)
-- 玩家符文影响
local RuneProxy = require(ServerStorage.Proxy.RuneProxy)
RuneProxy:GetRuneAttributes(Player, playerAI, AttributesData, behaviorNameList)
-- TODO设置AI行为临时清除所有行为添加新的玩家行为
playerAI:ClearAllBehaviour()
for _, behaviourName in behaviourNameList do
playerAI:AddBehaviour(behaviourName)
for _, behaviorName in behaviorNameList do
playerAI:AddBehaviour(behaviorName)
end
playerAI:AddBehaviour("Move")
-- playerAI:AddBehaviour("SwordWave")

View File

@ -0,0 +1,79 @@
local RuneCalculation = {}
-- 处理符文机制主函数
function RuneCalculation:GetRuneAttributes(Player: Player, PlayerAI: table, AttributesData: table, BehaviorNameList: table)
if not Player and not PlayerAI then warn("Player or PlayerAI not found") return end
-- 没穿戴符文,直接返回
local wearingRuneUniqueId, wearingRuneName, wearingRuneBehaviorName = self:GetPlayerWearingRuneData(Player)
if #wearingRuneUniqueId == 0 then return AttributesData, BehaviorNameList end
-- 行为名称列表处理
for _, behaviorName in wearingRuneBehaviorName do
if not table.find(BehaviorNameList, behaviorName) then
table.insert(BehaviorNameList, behaviorName)
end
end
-- 符文初始化
PlayerAI:ClearSharedData()
PlayerAI:ClearAllRune()
for wearingSlot, runeName in wearingRuneName do PlayerAI:AddRune(runeName, wearingSlot) end
-- 触发开始事件,并检查是否有符文要控制流程
local startNextIndex = PlayerAI:ForEachRune(function(runeName, triggerSlot, rune)
local result = rune:OnStartEvent(runeName, triggerSlot, AttributesData, BehaviorNameList)
if type(result) == "number" then
return result -- 返回要跳转的index
end
end)
local index = 1
local maxSteps = 1000 -- 防止无限循环
-- 如果开始事件指定了起始位置,使用它
if type(startNextIndex) == "number" then
index = startNextIndex
end
-- 执行符文循环
self:ExecuteRuneLoop(PlayerAI, wearingRuneName, wearingRuneUniqueId, index, maxSteps)
end
-- 执行符文循环的辅助函数
function RuneCalculation:ExecuteRuneLoop(PlayerAI: table, wearingRuneName: table, wearingRuneUniqueId: table, startIndex: number, maxSteps: number, AttributesData: table, BehaviorNameList: table)
local index = startIndex
while index <= #wearingRuneUniqueId and maxSteps > 0 do
local result = PlayerAI:TriggerRune(wearingRuneName[index], "Check", index, AttributesData, BehaviorNameList)
if result then
local nextIndex = PlayerAI:TriggerRune(wearingRuneName[index], "Execute", index, AttributesData, BehaviorNameList)
-- 根据返回值决定下一个index
if type(nextIndex) == "number" then
index = nextIndex -- 符文指定了下一个位置
else
index += 1 -- 默认继续下一个
end
else
index += 1
end
maxSteps -= 1 -- 防止无限循环
end
-- 触发结束事件,并检查是否有符文要控制流程
local endNextIndex = PlayerAI:ForEachRune(function(runeName, triggerSlot, rune)
local result = rune:OnEndEvent(runeName, triggerSlot, AttributesData, BehaviorNameList)
if type(result) == "number" then
return result -- 返回要跳转的index
end
end)
-- 如果结束事件指定了要跳转的位置,重新开始循环
if type(endNextIndex) == "number" and endNextIndex <= #wearingRuneUniqueId and maxSteps > 0 then
self:ExecuteRuneLoop(PlayerAI, wearingRuneName, wearingRuneUniqueId, endNextIndex, maxSteps, AttributesData, BehaviorNameList)
end
end
return RuneCalculation

View File

@ -9,6 +9,7 @@ local ServerStorage = game:GetService("ServerStorage")
local Utils = require(ReplicatedStorage.Tools.Utils)
local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy)
local Rng = require(ReplicatedStorage.Tools.Rng)
local ScriptModule = require(ReplicatedStorage.Tools.ScriptModule)
--> Json
local JsonRune = require(ReplicatedStorage.Json.Rune)
@ -16,6 +17,10 @@ local JsonRune = require(ReplicatedStorage.Json.Rune)
--> Events
local RE_PlayerTip = ReplicatedStorage.Events.RE_PlayerTip
--> SubFunctions
local RuneCalculation = require(script.RuneCalculation)
ScriptModule:InjectScriptFunctions(RuneProxy, RuneCalculation)
--> Constants
local STORE_NAME = "Rune"
@ -225,7 +230,8 @@ end
-- 获取穿戴中的符文
function RuneProxy:GetPlayerWearingRuneData(Player: Player)
local wearingRuneUniqueId = {}
local behaviourNames = {}
local wearingRuneName = {}
local wearingRuneBehaviorName = {}
-- 穿戴中的填入
local EquipmentProxy = require(ServerStorage.Proxy.EquipmentProxy)
local wearingEquipments = EquipmentProxy:GetPlayerWearingEquipmentUniqueId(Player)
@ -233,10 +239,15 @@ function RuneProxy:GetPlayerWearingRuneData(Player: Player)
if RuneData.wearing > 0 and table.find(wearingEquipments, RuneData.wearing) then
table.insert(wearingRuneUniqueId, RuneData.id)
local RuneData = Utils:GetIdDataFromJson(JsonRune, RuneData.orgId)
table.insert(behaviourNames, RuneData.behaviourName)
if RuneData.runeName then
wearingRuneName[RuneData.wearingSlot] = RuneData.runeName
end
if RuneData.behaviorName then
table.insert(wearingRuneBehaviorName, RuneData.behaviorName)
end
end
return wearingRuneUniqueId, behaviourNames
end
return wearingRuneUniqueId, wearingRuneName
end
-- 获取对应装备槽位上的技能