This commit is contained in:
Ggafrik 2025-07-12 05:06:14 +08:00
parent c1c22aa963
commit ac7c83a9bb
28 changed files with 931 additions and 75 deletions

View File

@ -32,6 +32,9 @@
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"Base": {
"$path": "src/ReplicatedStorage/Base"
},
"Data": {
"$path": "src/ReplicatedStorage/Data"
},

BIN
excel/attribute.xlsx Normal file

Binary file not shown.

Binary file not shown.

BIN
excel/gem.xlsx Normal file

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,38 @@
local BehaviourClient = {}
BehaviourClient.__index = BehaviourClient
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Dependencies
local EffectDispatcher = require(ReplicatedStorage.Modules.EffectDispatcher)
--------------------------------------------------------------------------------
-- 刷新时,重新载入,暂时不考虑性能
-- 初始化内容
function BehaviourClient:Init(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean)
local self = {}
self.Player = CasterPlayer
self.Character = CasterPlayer.Character
self.ShowTask = nil
self.EffectDispatcher = EffectDispatcher
return self
end
function BehaviourClient:Show(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean)
end
-- 销毁
function BehaviourClient:Destroy()
if self.ShowTask then
task.cancel(self.ShowTask)
self.ShowTask = nil
end
self = nil
end
return BehaviourClient

View File

@ -0,0 +1,21 @@
[
{"id":1,"type":1,"effectAttribute":"attack","battleValue":[1,10]},
{"id":2,"type":1,"effectAttribute":"hp","battleValue":[1,10]},
{"id":3,"type":1,"effectAttribute":"swordAtk","battleValue":[1,10]},
{"id":4,"type":2,"effectAttribute":"swordWearBase","battleValue":[1,10]},
{"id":5,"type":2,"effectAttribute":"swordWearSpe","battleValue":[1,10]},
{"id":6,"type":1,"effectAttribute":"fireAtk","battleValue":[1,10]},
{"id":7,"type":1,"effectAttribute":"iceAtk","battleValue":[1,10]},
{"id":8,"type":1,"effectAttribute":"lightAtk","battleValue":[1,10]},
{"id":9,"type":1,"effectAttribute":"shadowAtk","battleValue":[1,10]},
{"id":10,"type":1,"effectAttribute":"fireDef","battleValue":[1,10]},
{"id":11,"type":1,"effectAttribute":"iceDef","battleValue":[1,10]},
{"id":12,"type":1,"effectAttribute":"lightDef","battleValue":[1,10]},
{"id":13,"type":1,"effectAttribute":"shadowDef","battleValue":[1,10]},
{"id":50,"type":1,"effectAttribute":"wearNumber","battleValue":[1,10]},
{"id":51,"type":1,"effectAttribute":"skillNumber","battleValue":[1,10]},
{"id":52,"type":1,"effectAttribute":"extraAttributeNumber","battleValue":[1,10]},
{"id":53,"type":1,"effectAttribute":"elementNumber","battleValue":[1,10]},
{"id":54,"type":1,"effectAttribute":"elementDefNumber","battleValue":[1,10]},
{"id":55,"type":1,"effectAttribute":"gemNumber","battleValue":[1,10]}
]

View File

@ -0,0 +1,58 @@
[
{"id":10000,"type":1,"quality":1,"iconId":1,"effectAttribute":"fireAtk","attributeValue":10},
{"id":10001,"type":1,"quality":2,"iconId":2,"effectAttribute":"fireAtk","attributeValue":20},
{"id":10002,"type":1,"quality":3,"iconId":3,"effectAttribute":"fireAtk","attributeValue":30},
{"id":10003,"type":1,"quality":4,"iconId":4,"effectAttribute":"fireAtk","attributeValue":40},
{"id":10004,"type":1,"quality":5,"iconId":5,"effectAttribute":"fireAtk","attributeValue":50},
{"id":10005,"type":1,"quality":6,"iconId":6,"effectAttribute":"fireAtk","attributeValue":60},
{"id":10006,"type":1,"quality":7,"iconId":7,"effectAttribute":"fireAtk","attributeValue":70},
{"id":10100,"type":1,"quality":1,"iconId":1,"effectAttribute":"fireDef","attributeValue":10},
{"id":10101,"type":1,"quality":2,"iconId":2,"effectAttribute":"fireDef","attributeValue":20},
{"id":10102,"type":1,"quality":3,"iconId":3,"effectAttribute":"fireDef","attributeValue":30},
{"id":10103,"type":1,"quality":4,"iconId":4,"effectAttribute":"fireDef","attributeValue":40},
{"id":10104,"type":1,"quality":5,"iconId":5,"effectAttribute":"fireDef","attributeValue":50},
{"id":10105,"type":1,"quality":6,"iconId":6,"effectAttribute":"fireDef","attributeValue":60},
{"id":10106,"type":1,"quality":7,"iconId":7,"effectAttribute":"fireDef","attributeValue":70},
{"id":11000,"type":2,"quality":1,"iconId":1,"effectAttribute":"iceAtk","attributeValue":10},
{"id":11001,"type":2,"quality":2,"iconId":2,"effectAttribute":"iceAtk","attributeValue":20},
{"id":11002,"type":2,"quality":3,"iconId":3,"effectAttribute":"iceAtk","attributeValue":30},
{"id":11003,"type":2,"quality":4,"iconId":4,"effectAttribute":"iceAtk","attributeValue":40},
{"id":11004,"type":2,"quality":5,"iconId":5,"effectAttribute":"iceAtk","attributeValue":50},
{"id":11005,"type":2,"quality":6,"iconId":6,"effectAttribute":"iceAtk","attributeValue":60},
{"id":11006,"type":2,"quality":7,"iconId":7,"effectAttribute":"iceAtk","attributeValue":70},
{"id":11100,"type":2,"quality":1,"iconId":1,"effectAttribute":"iceDef","attributeValue":10},
{"id":11101,"type":2,"quality":2,"iconId":2,"effectAttribute":"iceDef","attributeValue":20},
{"id":11102,"type":2,"quality":3,"iconId":3,"effectAttribute":"iceDef","attributeValue":30},
{"id":11103,"type":2,"quality":4,"iconId":4,"effectAttribute":"iceDef","attributeValue":40},
{"id":11104,"type":2,"quality":5,"iconId":5,"effectAttribute":"iceDef","attributeValue":50},
{"id":11105,"type":2,"quality":6,"iconId":6,"effectAttribute":"iceDef","attributeValue":60},
{"id":11106,"type":2,"quality":7,"iconId":7,"effectAttribute":"iceDef","attributeValue":70},
{"id":12000,"type":2,"quality":1,"iconId":1,"effectAttribute":"lightAtk","attributeValue":10},
{"id":12001,"type":2,"quality":2,"iconId":2,"effectAttribute":"lightAtk","attributeValue":20},
{"id":12002,"type":2,"quality":3,"iconId":3,"effectAttribute":"lightAtk","attributeValue":30},
{"id":12003,"type":2,"quality":4,"iconId":4,"effectAttribute":"lightAtk","attributeValue":40},
{"id":12004,"type":2,"quality":5,"iconId":5,"effectAttribute":"lightAtk","attributeValue":50},
{"id":12005,"type":2,"quality":6,"iconId":6,"effectAttribute":"lightAtk","attributeValue":60},
{"id":12006,"type":2,"quality":7,"iconId":7,"effectAttribute":"lightAtk","attributeValue":70},
{"id":12100,"type":2,"quality":1,"iconId":1,"effectAttribute":"lightDef","attributeValue":10},
{"id":12101,"type":2,"quality":2,"iconId":2,"effectAttribute":"lightDef","attributeValue":20},
{"id":12102,"type":2,"quality":3,"iconId":3,"effectAttribute":"lightDef","attributeValue":30},
{"id":12103,"type":2,"quality":4,"iconId":4,"effectAttribute":"lightDef","attributeValue":40},
{"id":12104,"type":2,"quality":5,"iconId":5,"effectAttribute":"lightDef","attributeValue":50},
{"id":12105,"type":2,"quality":6,"iconId":6,"effectAttribute":"lightDef","attributeValue":60},
{"id":12106,"type":2,"quality":7,"iconId":7,"effectAttribute":"lightDef","attributeValue":70},
{"id":13000,"type":2,"quality":1,"iconId":1,"effectAttribute":"shadowAtk","attributeValue":10},
{"id":13001,"type":2,"quality":2,"iconId":2,"effectAttribute":"shadowAtk","attributeValue":20},
{"id":13002,"type":2,"quality":3,"iconId":3,"effectAttribute":"shadowAtk","attributeValue":30},
{"id":13003,"type":2,"quality":4,"iconId":4,"effectAttribute":"shadowAtk","attributeValue":40},
{"id":13004,"type":2,"quality":5,"iconId":5,"effectAttribute":"shadowAtk","attributeValue":50},
{"id":13005,"type":2,"quality":6,"iconId":6,"effectAttribute":"shadowAtk","attributeValue":60},
{"id":13006,"type":2,"quality":7,"iconId":7,"effectAttribute":"shadowAtk","attributeValue":70},
{"id":13100,"type":2,"quality":1,"iconId":1,"effectAttribute":"shadowDef","attributeValue":10},
{"id":13101,"type":2,"quality":2,"iconId":2,"effectAttribute":"shadowDef","attributeValue":20},
{"id":13102,"type":2,"quality":3,"iconId":3,"effectAttribute":"shadowDef","attributeValue":30},
{"id":13103,"type":2,"quality":4,"iconId":4,"effectAttribute":"shadowDef","attributeValue":40},
{"id":13104,"type":2,"quality":5,"iconId":5,"effectAttribute":"shadowDef","attributeValue":50},
{"id":13105,"type":2,"quality":6,"iconId":6,"effectAttribute":"shadowDef","attributeValue":60},
{"id":13106,"type":2,"quality":7,"iconId":7,"effectAttribute":"shadowDef","attributeValue":70}
]

View File

@ -1,4 +1,61 @@
[
{"id":1,"type":1,"typeArgs":[],"quality":4,"iconId":1,"nameId":10001,"textId":20001,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null},
{"id":2,"type":1,"typeArgs":[],"quality":4,"iconId":2,"nameId":10002,"textId":20002,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null}
{"id":2,"type":1,"typeArgs":[],"quality":4,"iconId":2,"nameId":10002,"textId":20002,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null},
{"id":11,"type":1,"typeArgs":[],"quality":1,"iconId":11,"nameId":10011,"textId":20011,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null},
{"id":10000,"type":4,"typeArgs":[],"quality":1,"iconId":12,"nameId":20000,"textId":30000,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":10001,"type":4,"typeArgs":[],"quality":2,"iconId":13,"nameId":20001,"textId":30001,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":10002,"type":4,"typeArgs":[],"quality":3,"iconId":14,"nameId":20002,"textId":30002,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":10003,"type":4,"typeArgs":[],"quality":4,"iconId":15,"nameId":20003,"textId":30003,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":10004,"type":4,"typeArgs":[],"quality":5,"iconId":16,"nameId":20004,"textId":30004,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":10005,"type":4,"typeArgs":[],"quality":6,"iconId":17,"nameId":20005,"textId":30005,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":10006,"type":4,"typeArgs":[],"quality":7,"iconId":18,"nameId":20006,"textId":30006,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null},
{"id":10100,"type":4,"typeArgs":[],"quality":1,"iconId":19,"nameId":20100,"textId":30100,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":10101,"type":4,"typeArgs":[],"quality":2,"iconId":20,"nameId":20101,"textId":30101,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":10102,"type":4,"typeArgs":[],"quality":3,"iconId":21,"nameId":20102,"textId":30102,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":10103,"type":4,"typeArgs":[],"quality":4,"iconId":22,"nameId":20103,"textId":30103,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":10104,"type":4,"typeArgs":[],"quality":5,"iconId":23,"nameId":20104,"textId":30104,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":10105,"type":4,"typeArgs":[],"quality":6,"iconId":24,"nameId":20105,"textId":30105,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":10106,"type":4,"typeArgs":[],"quality":7,"iconId":25,"nameId":20106,"textId":30106,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null},
{"id":11000,"type":4,"typeArgs":[],"quality":1,"iconId":26,"nameId":21000,"textId":31000,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":11001,"type":4,"typeArgs":[],"quality":2,"iconId":27,"nameId":21001,"textId":31001,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":11002,"type":4,"typeArgs":[],"quality":3,"iconId":28,"nameId":21002,"textId":31002,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":11003,"type":4,"typeArgs":[],"quality":4,"iconId":29,"nameId":21003,"textId":31003,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":11004,"type":4,"typeArgs":[],"quality":5,"iconId":30,"nameId":21004,"textId":31004,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":11005,"type":4,"typeArgs":[],"quality":6,"iconId":31,"nameId":21005,"textId":31005,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":11006,"type":4,"typeArgs":[],"quality":7,"iconId":32,"nameId":21006,"textId":31006,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null},
{"id":11100,"type":4,"typeArgs":[],"quality":1,"iconId":33,"nameId":21100,"textId":31100,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":11101,"type":4,"typeArgs":[],"quality":2,"iconId":34,"nameId":21101,"textId":31101,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":11102,"type":4,"typeArgs":[],"quality":3,"iconId":35,"nameId":21102,"textId":31102,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":11103,"type":4,"typeArgs":[],"quality":4,"iconId":36,"nameId":21103,"textId":31103,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":11104,"type":4,"typeArgs":[],"quality":5,"iconId":37,"nameId":21104,"textId":31104,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":11105,"type":4,"typeArgs":[],"quality":6,"iconId":38,"nameId":21105,"textId":31105,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":11106,"type":4,"typeArgs":[],"quality":7,"iconId":39,"nameId":21106,"textId":31106,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null},
{"id":12000,"type":4,"typeArgs":[],"quality":1,"iconId":40,"nameId":22000,"textId":32000,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":12001,"type":4,"typeArgs":[],"quality":2,"iconId":41,"nameId":22001,"textId":32001,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":12002,"type":4,"typeArgs":[],"quality":3,"iconId":42,"nameId":22002,"textId":32002,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":12003,"type":4,"typeArgs":[],"quality":4,"iconId":43,"nameId":22003,"textId":32003,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":12004,"type":4,"typeArgs":[],"quality":5,"iconId":44,"nameId":22004,"textId":32004,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":12005,"type":4,"typeArgs":[],"quality":6,"iconId":45,"nameId":22005,"textId":32005,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":12006,"type":4,"typeArgs":[],"quality":7,"iconId":46,"nameId":22006,"textId":32006,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null},
{"id":12100,"type":4,"typeArgs":[],"quality":1,"iconId":47,"nameId":22100,"textId":32100,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":12101,"type":4,"typeArgs":[],"quality":2,"iconId":48,"nameId":22101,"textId":32101,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":12102,"type":4,"typeArgs":[],"quality":3,"iconId":49,"nameId":22102,"textId":32102,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":12103,"type":4,"typeArgs":[],"quality":4,"iconId":50,"nameId":22103,"textId":32103,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":12104,"type":4,"typeArgs":[],"quality":5,"iconId":51,"nameId":22104,"textId":32104,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":12105,"type":4,"typeArgs":[],"quality":6,"iconId":52,"nameId":22105,"textId":32105,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":12106,"type":4,"typeArgs":[],"quality":7,"iconId":53,"nameId":22106,"textId":32106,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null},
{"id":13000,"type":4,"typeArgs":[],"quality":1,"iconId":54,"nameId":23000,"textId":33000,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":13001,"type":4,"typeArgs":[],"quality":2,"iconId":55,"nameId":23001,"textId":33001,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":13002,"type":4,"typeArgs":[],"quality":3,"iconId":56,"nameId":23002,"textId":33002,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":13003,"type":4,"typeArgs":[],"quality":4,"iconId":57,"nameId":23003,"textId":33003,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":13004,"type":4,"typeArgs":[],"quality":5,"iconId":58,"nameId":23004,"textId":33004,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":13005,"type":4,"typeArgs":[],"quality":6,"iconId":59,"nameId":23005,"textId":33005,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":13006,"type":4,"typeArgs":[],"quality":7,"iconId":60,"nameId":23006,"textId":33006,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null},
{"id":13100,"type":4,"typeArgs":[],"quality":1,"iconId":61,"nameId":23100,"textId":33100,"buyPrice":[11,10],"sellPrice":[11,10],"use":[],"showPackage":null},
{"id":13101,"type":4,"typeArgs":[],"quality":2,"iconId":62,"nameId":23101,"textId":33101,"buyPrice":[11,20],"sellPrice":[11,20],"use":[],"showPackage":null},
{"id":13102,"type":4,"typeArgs":[],"quality":3,"iconId":63,"nameId":23102,"textId":33102,"buyPrice":[11,30],"sellPrice":[11,30],"use":[],"showPackage":null},
{"id":13103,"type":4,"typeArgs":[],"quality":4,"iconId":64,"nameId":23103,"textId":33103,"buyPrice":[11,40],"sellPrice":[11,40],"use":[],"showPackage":null},
{"id":13104,"type":4,"typeArgs":[],"quality":5,"iconId":65,"nameId":23104,"textId":33104,"buyPrice":[11,50],"sellPrice":[11,50],"use":[],"showPackage":null},
{"id":13105,"type":4,"typeArgs":[],"quality":6,"iconId":66,"nameId":23105,"textId":33105,"buyPrice":[11,60],"sellPrice":[11,60],"use":[],"showPackage":null},
{"id":13106,"type":4,"typeArgs":[],"quality":7,"iconId":67,"nameId":23106,"textId":33106,"buyPrice":[11,70],"sellPrice":[11,70],"use":[],"showPackage":null}
]

View File

@ -0,0 +1,47 @@
-- 剑气
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Dependencies
local BehaviourClient = require(ReplicatedStorage.Base.BehaviourClient)
--> Variables
local PrefabFolder = ReplicatedStorage:WaitForChild("Prefabs")
local Prefab_SwordWave = PrefabFolder:WaitForChild("Projectiles"):WaitForChild("SwordWave")
--------------------------------------------------------------------------------
local SwordWave = {}
SwordWave.__index = SwordWave
setmetatable(SwordWave, {__index = BehaviourClient})
function SwordWave:Init(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean)
local self = BehaviourClient:Init(CasterPlayer, CastInfo, DelayTime, CastState)
setmetatable(self, SwordWave)
return self
end
function SwordWave:Show(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean)
self.ShowTask, self.Projectile, self.Tween = self.EffectDispatcher:ShowProjectile(self.Player, DelayTime,
0, CastInfo.StartPos, CastInfo.EndPos, CastInfo.Duration, Prefab_SwordWave, Enum.EasingStyle.Linear)
end
function SwordWave:Destroy()
if self.ShowTask then
task.cancel(self.ShowTask)
self.ShowTask = nil
end
if self.Tween then
self.Tween:Cancel()
self.Tween = nil
end
if self.Projectile then
self.Projectile:Destroy()
self.Projectile = nil
end
BehaviourClient.Destroy(self)
end
return SwordWave

View File

@ -0,0 +1,46 @@
-- 效果调度器
-- 客户端调用表现全部通过这里,预想效果
-- 1. 方便做功能拓展
-- 2. 便于统一表现逻辑AOE弹道。。。
local EffectDispatcher = {}
--> Services
local TweenService = game:GetService("TweenService")
local function GetPerformanceFolder()
return game.Workspace:FindFirstChild("Performance")
end
function EffectDispatcher:ShowProjectile(Caster: Player, DelayTime: number, PreTime: number, StartPos: Vector3, EndPos: Vector3,
Duration: number, ProjectilePrefab: Part, EasingStyle: Enum.EasingStyle?)
local projectileTask, Projectile, tween
projectileTask = task.spawn(function ()
local PerformanceFolder = GetPerformanceFolder()
if not PerformanceFolder then warn("PerformanceFolder not found") return end
Projectile = ProjectilePrefab:Clone()
Projectile.Parent = PerformanceFolder
Projectile.CFrame = CFrame.new(StartPos, EndPos)
Projectile.CanCollide = false
Projectile.Anchored = true
-- Tween动画
local tweenInfo = TweenInfo.new(Duration, EasingStyle or Enum.EasingStyle.Linear)
local direction = (EndPos - StartPos).Unit
local lookAt = EndPos + direction
local goal = {CFrame = CFrame.new(EndPos, lookAt)}
tween = TweenService:Create(Projectile, tweenInfo, goal)
task.wait(PreTime)
tween:Play()
-- 动画结束后自动销毁
game.Debris:AddItem(Projectile, Duration + PreTime)
end)
return projectileTask, Projectile, tween
end
return EffectDispatcher

View File

@ -38,6 +38,16 @@ function Utils:GenUniqueId(t: table)
return min_id
end
function Utils:GenUniqueIdPlayerAI(LastTime: number, Counter: number)
local now = os.time() -- 或 tick(),精度更高
if now ~= LastTime then
Counter = 0
LastTime = now
end
Counter = Counter + 1
return tostring(now) .. "_" .. tostring(Counter)
end
-- function Utils:GetJsonIdData(JsonName: string, id: number)
-- local JsonData = require(ReplicatedStorage.Json[JsonName])
-- for _, item in ipairs(JsonData) do
@ -86,6 +96,35 @@ function Utils:StringArrayToTable(StringArray: string)
return result
end
function Utils:GetFlatDirectionAndEndPos(startPos: Vector3, targetPos: Vector3, length: number)
local flatStartPos = Vector3.new(startPos.X, 0, startPos.Z)
local flatTarget = Vector3.new(targetPos.X, 0, targetPos.Z)
local direction = (flatTarget - flatStartPos).Unit
local endPos = flatStartPos + direction * length
return flatStartPos, endPos, direction
end
function Utils:CreateDataInstance(Player: Player, UniqueId: number, EquipmentData: table, Folder: Instance)
if Player or UniqueId or EquipmentData or Folder then
warn('创建实例失败: ' , Player.Name, UniqueId, EquipmentData, Folder)
return
end
local Config = Instance.new("Configuration")
Config.Name = UniqueId
Utils:SetAttributesList(Config, EquipmentData)
Config.Parent = Folder
return Config
end
-- 复制表
function Utils:CopyTable(t: table)
local newTable = {}
for k, v in pairs(t) do
newTable[k] = v
end
return newTable
end
--------------------------------------------------------------------------------
return Utils

View File

@ -1,20 +1,34 @@
local Behaviour = {}
Behaviour.__index = Behaviour
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
--> Events
local RE_PerformanceEvent = ReplicatedStorage:FindFirstChild("Events"):FindFirstChild("RE_PerformanceEvent")
local RE_CleanPlayerPerformance = ReplicatedStorage:FindFirstChild("Events"):FindFirstChild("RE_CleanPlayerPerformance")
--> Dependencies
local TypeList = require(script.Parent.TypeList)
local TypeList = require(ServerStorage.Base.TypeList)
local Communicate = require(ServerStorage.Modules.Tools.Communicate)
--------------------------------------------------------------------------------
-- 刷新时,重新载入,暂时不考虑性能
-- 初始化内容
function Behaviour:Init(PlayerAI, Character: TypeList.Character)
function Behaviour:Init(PlayerAI, Character: TypeList.Character, ScriptName: string)
local self = {}
self.PlayerAI = PlayerAI
self.Character = Character
self.CheckData = nil
self.ExeTask = nil
self.UniqueIdList = {}
self.ScriptName = ScriptName
self.Cooldown = 0
self.OrgCooldown = 0
self.CooldownTask = nil
local Humanoid = self.Character.Humanoid
-- 监听属性变化
@ -38,12 +52,28 @@ function Behaviour:Execute()
end
-- 启动冷却时间清除计时
function Behaviour:StartCooldownTask()
self.Cooldown = self.OrgCooldown
self.CooldownTask = task.spawn(function()
task.wait(self.OrgCooldown)
self.Cooldown = 0
end)
end
function Behaviour:SendPerformanceEvent(...)
Communicate:SendToClient(RE_PerformanceEvent, ...)
end
-- 检查当前状态是否可执行
function Behaviour:CheckStat()
if not self.Character then return true end
-- 死亡检查
if self.Character:GetState("Died") then return true end
-- 冷却中检查
if self.Cooldown > 0 then return true end
-- 执行状态中检查
local ExecutingState = self.PlayerAI.ExecutingState
-- 其他内容执行中就false
@ -59,6 +89,20 @@ end
-- 销毁
function Behaviour:Destroy()
-- 清除客户端对应行为表现
for _, UniqueId in self.UniqueIdList do
self:SendPerformanceEvent("Destroy", self.Player, self.ScriptName, true, {UniqueId = UniqueId})
end
if self.CooldownTask then
task.cancel(self.CooldownTask)
self.CooldownTask = nil
end
if self.ExeTask then
task.cancel(self.ExeTask)
self.ExeTask = nil
end
-- 清除数据
self.UniqueIdList = {}
if self.ConAttribtueChanged then
self.ConAttribtueChanged:Disconnect()
self.ConAttribtueChanged = nil
@ -67,6 +111,10 @@ function Behaviour:Destroy()
task.cancel(self.LoopTask)
self.LoopTask = nil
end
for _, UniqueId in self.UniqueIdList do
self.PlayerAI:RemoveBehaviourUniqueId(UniqueId)
end
self.UniqueIdList = {}
self = nil
end

View File

@ -2,8 +2,12 @@
local Character = {}
Character.__index = Character
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Utils = require(ReplicatedStorage.Tools.Utils)
local TypeList = require(script.Parent.TypeList)
local LIMIT_ATTRIBUTE = {
"hp"
}
@ -16,7 +20,7 @@ function Character.new(Player: Player, CharacterModel: Model, CharacterData: tab
-- 生成表格数据
local self = {}
self.Instance = newMobModel
self.Config = CharacterData
self.Config = Utils:CopyTable(CharacterData)
self.Root = HumanoidRootPart
self.Humanoid = mobHumanoid
self.Origin = HumanoidRootPart:GetPivot()
@ -90,9 +94,8 @@ function Character:ChangeAttributeValue(attributeKey: string, value: any)
-- 死亡判断
if attributeKey == "hp" and self.Stats.Died == false then
if self.Config[attributeKey] <= 0 then
self:Died()
end
if self.Config[attributeKey] <= 0 then self:Died() end
end
end
@ -106,6 +109,7 @@ function Character:ChangeState(state: string, value: any)
end
function Character:Died()
print(debug.traceback("Stack trace:"))
self:ChangeState("Died", true)
for _, connection in self.Connections do
connection:Disconnect()

View File

@ -20,4 +20,10 @@ export type Behaviour = {
Destroy: () -> (),
}
export type CastInfo = {
UniqueId: number,
StartPos: Vector3,
EndPos: Vector3,
}
return {}

View File

@ -4,9 +4,9 @@
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 TypeList = require(ServerStorage:WaitForChild("Base").TypeList)
local Behaviour = require(ServerStorage:WaitForChild("Base").Behaviour)
local MobsProxy = require(ServerStorage:WaitForChild("Proxy").MobsProxy)
--------------------------------------------------------------------------------
@ -15,7 +15,7 @@ Move.__index = Move
setmetatable(Move, {__index = Behaviour})
function Move:Init(PlayerAI, Character: TypeList.Character, Player: Player)
local self = Behaviour:Init(PlayerAI, Character)
local self = Behaviour:Init(PlayerAI, Character, script.Name)
self.Player = Player
setmetatable(self, Move)
return self
@ -57,12 +57,4 @@ function Move:Execute()
end)
end
function Move:Destroy()
if self.ExeTask then
task.cancel(self.ExeTask)
self.ExeTask = nil
end
Behaviour.Destroy(self)
end
return Move

View File

@ -0,0 +1,102 @@
-- 移动行为
--> 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 SwordWave = {}
SwordWave.__index = SwordWave
setmetatable(SwordWave, {__index = Behaviour})
local CAST_DISTANCE = 50
local PROJECTILE_LENGTH = 50
local PROJECTILE_DURATION = 3
local COOLDOWN = 1
function SwordWave:Init(PlayerAI, Character: TypeList.Character, Player: Player)
local self = Behaviour:Init(PlayerAI, Character, script.Name)
self.Player = Player
setmetatable(self, SwordWave)
self.OrgCooldown = COOLDOWN
-- 客户端表现
self:SendPerformanceEvent("Init", self.Player, script.Name, true, {})
return self
end
function SwordWave:Check(CheckInfo: table)
if Behaviour.CheckStat(self) then return -1, self.CheckData end
local PlayerMobs = MobsProxy:GetPlayerMobs(self.Player)
if not PlayerMobs then return end
local closestMob, minDistance = nil, CAST_DISTANCE
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 200, self.CheckData
end
-- 返回优先级,执行数据
return -1, self.CheckData
end
function SwordWave:Execute()
self.ExeTask = task.spawn(function ()
self:ChangeExecutingState(true)
task.wait(0.5)
-- 伤害逻辑部分
local StartPos = self.Character.Instance:GetPivot().Position
local EndPos = StartPos + (self.CheckData["ClosestCharacter"].Instance:GetPivot().Position - StartPos).Unit * PROJECTILE_LENGTH
StartPos, EndPos = Utils:GetFlatDirectionAndEndPos(StartPos, self.CheckData["ClosestCharacter"].Instance:GetPivot().Position, PROJECTILE_LENGTH)
DamageProxy:CastFreeProjectile(self.Character, StartPos, EndPos, PROJECTILE_DURATION, 4,
function (Victim: TypeList.Character)
self:OnHit(Victim)
end)
-- 表现部分
self:SendPerformanceEvent("Show", self.Player, self.ScriptName, true, {
{ UniqueId = self.PlayerAI:GetBehaviourUniqueId(),
StartPos = StartPos,
EndPos = EndPos,
Duration = PROJECTILE_DURATION,
Cooldown = self.OrgCooldown,
}
})
self:StartCooldownTask()
self:ChangeExecutingState(false)
end)
end
function SwordWave:OnHit(Victim: TypeList.Character)
DamageProxy:TakeDamage(self.Character, Victim, {
{
Damage = 30,
Type = DamageProxy.DamageType.SKILL,
Tag = DamageProxy.DamageTag.NORMAL,
}
})
return false
end
return SwordWave

View File

@ -0,0 +1,11 @@
local Communicate = {}
function Communicate:SendToClient(Event: RemoteEvent, CastTag: string, CastPlayer: Player, ...)
Event:FireAllClients(tick(), CastTag, CastPlayer, ...)
end
function Communicate:SendToClientFree(Event: RemoteEvent, ...)
Event:FireAllClients(...)
end
return Communicate

View File

@ -4,6 +4,7 @@ local DamageProxy = {}
--> Services
local ServerStorage = game:GetService("ServerStorage")
--> Modules
--> Variables
local TypeList = require(ServerStorage.Base.TypeList)
@ -48,6 +49,65 @@ function DamageProxy:IsDied(Target: Model)
return Humanoid:GetAttribute("hp") <= 0
end
-- 获取范围内敌人
function DamageProxy:GetAoeEnemies(Caster: TypeList.Character, Position: Vector3, Radius: number)
local Enemies = {}
local MobsProxy = require(ServerStorage.Proxy.MobsProxy)
for _, enemy in pairs(MobsProxy:GetPlayerMobs(Caster.Player)) do
if enemy ~= Caster and enemy.Instance then
local enemy_pos = enemy.Instance:GetPivot().Position
if (enemy_pos - Position).Magnitude <= Radius then
table.insert(Enemies, enemy)
end
end
end
return Enemies
end
-- 弹道伤害
function DamageProxy:CastFreeProjectile(Caster: TypeList.Character, StartPos: Vector3, EndPos: Vector3, Duration: number, Range: number, OnHit: (Target: TypeList.Character) -> (boolean?))
local projectileTask = nil
local step_time = 0.05 -- 每帧检测间隔
local elapsed = 0
local hit_targets = {}
local direction = (EndPos - StartPos).Unit
local distance = (EndPos - StartPos).Magnitude
local speed = distance / Duration
local current_pos = StartPos
local cancelled = false
local function cancelTask()
cancelled = true
if projectileTask then
task.cancel(projectileTask)
end
end
projectileTask = task.spawn(function()
while elapsed < Duration and not cancelled do
-- 计算当前位置
current_pos = StartPos + direction * speed * elapsed
-- 检测范围内敌人
local MobsProxy = require(ServerStorage.Proxy.MobsProxy)
for _, enemy in pairs(MobsProxy:GetPlayerMobs(Caster.Player)) do
if enemy ~= Caster and enemy.Instance then
local enemy_pos = enemy.Instance:GetPivot().Position
if (enemy_pos - current_pos).Magnitude <= Range then
if not hit_targets[enemy] then
hit_targets[enemy] = true
local stop = OnHit(enemy)
if stop then cancelTask() break end
end
end
end
end
task.wait(step_time)
elapsed = elapsed + step_time
end
end)
end
function DamageProxy:TakeDamage(Caster: TypeList.Character, Victim: TypeList.Character, DamageInfos: {DamageInfo})
for _, DamageInfo in DamageInfos do
local Damage = DamageInfo.Damage

View File

@ -26,22 +26,6 @@ local function GetPlayerEquipmentFolder(Player: Player)
return EquipmentFolder
end
-- 创建装备实例
local function CreateEquipmentInstance(Player: Player, UniqueId: number, EquipmentData: table)
if Player or UniqueId or EquipmentData then
warn('创建装备实例失败: ' , Player.Name, UniqueId, EquipmentData)
return
end
local PlayerEquipmentFolder = GetPlayerEquipmentFolder(Player)
if not PlayerEquipmentFolder then return end
local Config = Instance.new("Configuration")
Config.Name = UniqueId
Utils:SetAttributesList(Config, PlayerEquipmentFolder)
Config.Parent = PlayerEquipmentFolder
return Config
end
--------------------------------------------------------------------------------
-- 初始化玩家
@ -57,10 +41,11 @@ function EquipmentProxy:InitPlayer(Player: Player)
-- 初始化装备
for uniqueId, EquipmentData in ArchiveProxy.pData[Player.UserId][STORE_NAME] do
CreateEquipmentInstance(Player, uniqueId, EquipmentData)
Utils:CreateDataInstance(Player, uniqueId, EquipmentData, GetPlayerEquipmentFolder(Player))
end
end
-- 一些特殊记录或者不用记录的Key
local EXCEPT_KEYS = { "id", "orgId", "name", "attributes"}
-- 添加装备到背包
function EquipmentProxy:AddEquipment(Player: Player, EquipmentId: number)
@ -81,15 +66,16 @@ function EquipmentProxy:AddEquipment(Player: Player, EquipmentId: number)
ResultData.id = UniqueId
ResultData.orgId = EquipmentId
ResultData.wearing = false
-- 到时候记录穿戴槽位
ResultData.wearing = 0
-- 其他随机词条内容添加在下面
-- 之后回收修改随机生成
-- TODO: 其他随机词条内容添加在下面
-- TODO: 之后回收修改随机生成
------------------------------------------------------------
ArchiveProxy.pData[Player.UserId][UniqueId] = ResultData
CreateEquipmentInstance(Player, UniqueId, ResultData)
Utils:CreateDataInstance(Player, UniqueId, ResultData, GetPlayerEquipmentFolder(Player))
end
-- 回收装备
@ -119,6 +105,12 @@ function EquipmentProxy:WearEquipment(Player: Player, EquipmentId: number)
end
-- 获取装备数据
function EquipmentProxy:GetEquipmentData(Player: Player, EquipmentUniqueId: number)
local EquipmentData = ArchiveProxy.pData[Player.UserId][STORE_NAME][EquipmentUniqueId]
return EquipmentData
end
function EquipmentProxy:OnPlayerRemoving(Player: Player)
end

View File

@ -0,0 +1,204 @@
-- 玩家通用信息
local GemProxy = {}
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
--> Variables
local Utils = require(ReplicatedStorage.Tools.Utils)
local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy)
--> Json
local JsonItem = require(ReplicatedStorage.Json.ItemProp)
local JsonGem = require(ReplicatedStorage.Json.Gem)
--> Events
local RE_PlayerTip = ReplicatedStorage.Events.RE_PlayerTip
local RE_UpgradeAttributes = ReplicatedStorage.Events.RE_UpgradeAttributes
--> Constants
local STORE_NAME = "Gem"
--------------------------------------------------------------------------------
-- 获取玩家信息文件夹
local function GetPlayerGemFolder(Player: Player)
local pData = Utils:GetPlayerDataFolder(Player)
if not pData then return end
local GemFolder = pData:FindFirstChild("Gem")
return GemFolder
end
--------------------------------------------------------------------------------
-- 初始化玩家
function GemProxy:InitPlayer(Player: Player)
local pData = Utils:GetPlayerDataFolder(Player)
if not pData then return end
local GemFolder = Utils:CreateFolder("Gem", pData)
-- 新玩家数据初始化
if not ArchiveProxy.pData[Player.UserId][STORE_NAME] then
ArchiveProxy.pData[Player.UserId][STORE_NAME] = {}
ArchiveProxy.pData[Player.UserId][STORE_NAME].Gems = {}
end
-- 创建玩家信息实例
for GemUniqueId, GemData in ArchiveProxy.pData[Player.UserId][STORE_NAME].Gems do
Utils:CreateDataInstance(Player, GemUniqueId, GemData, GemFolder)
end
end
--------------------------------------------------------------------------------
-- 添加宝石
local EXCEPT_KEYS = {"id", "orgId", "iconId"}
function GemProxy:AddGem(Player: Player, GemId: number)
local pData = Utils:GetPlayerDataFolder(Player)
if not pData then return end
local GemData = Utils:GetIdDataFromJson(JsonGem, GemId)
if not GemData then return end
local UniqueId = Utils:GenUniqueId(ArchiveProxy.pData[Player.UserId])
-- 配置表内容
local ResultData = {}
for key, value in pairs(GemData) do
if not table.find(EXCEPT_KEYS, key) then
ResultData[key] = value
end
end
ResultData.id = UniqueId
ResultData.orgId = GemId
-- 记录穿戴的装备UniqueId
ResultData.wearing = 0
ArchiveProxy.pData[Player.UserId][UniqueId] = ResultData
Utils:CreateDataInstance(Player, UniqueId, ResultData, GetPlayerGemFolder(Player))
end
-- 购买宝石
function GemProxy:BuyGem(Player: Player, GemId: number)
local pData = Utils:GetPlayerDataFolder(Player)
if not pData then return end
local ItemData = Utils:GetIdDataFromJson(JsonItem, GemId)
if not ItemData then warn('无法获取宝石Item数据: ' , Player.Name, GemId) return end
local GemData = Utils:GetIdDataFromJson(JsonGem, GemId)
if not GemData then warn('无法获取宝石配置数据: ' , Player.Name, GemId) return end
local PlayerInfoProxy = require(ServerStorage.Proxy.PlayerInfoProxy)
-- 判断是否花钱
local buyPrice = ItemData.buyPrice
if buyPrice then
if PlayerInfoProxy:HasEnoughItem(Player, buyPrice[1], buyPrice[2]) then
PlayerInfoProxy:ChangeItemCount(Player, buyPrice[1], -buyPrice[2])
else
RE_PlayerTip:FireClient(Player, "钱不够")
return
end
end
-- 添加宝石
GemProxy:AddGem(Player, GemId)
end
-- 回收宝石
function GemProxy:RecycleGem(Player: Player, UniqueId: number)
local GemFolder = GetPlayerGemFolder(Player)
local GemData = ArchiveProxy.pData[Player.UserId][UniqueId]
if not GemData then warn('无法获取宝石数据: ' , Player.Name, UniqueId) return end
local GemInstance = GemFolder:FindFirstChild(UniqueId)
if not GemInstance then warn('宝石实例不存在: ' , Player.Name, UniqueId) return end
local ItemData = Utils:GetIdDataFromJson(JsonItem, GemData.orgId)
if not ItemData then warn('无法获取宝石Item数据: ' , Player.Name, GemData.orgId) return end
-- 判断是否穿戴
if GemData.wearing ~= 0 then
RE_PlayerTip:FireClient(Player, "宝石穿戴中")
return
end
-- 增加货币
local PlayerInfoProxy = require(ServerStorage.Proxy.PlayerInfoProxy)
PlayerInfoProxy:ChangeItemCount(Player, ItemData.sellPrice[1], ItemData.sellPrice[2])
-- 移除内容
ArchiveProxy.pData[Player.UserId][UniqueId] = nil
GemInstance:Destroy()
end
-- 穿戴宝石
function GemProxy:WearGem(Player: Player, GemUniqueId: number, EquipmentUniqueId: number)
-- 检查是否有这个宝石
local GemData = ArchiveProxy.pData[Player.UserId][GemUniqueId]
if not GemData then warn('无法获取宝石数据: ' , Player.Name, GemUniqueId) return end
-- 检查是否有宝石实例
local GemInstance = GetPlayerGemFolder(Player):FindFirstChild(GemUniqueId)
if not GemInstance then warn('宝石实例不存在: ' , Player.Name, GemUniqueId) return end
-- 检查是否有这个装备
local EquipmentProxy = require(ServerStorage.Proxy.EquipmentProxy)
local EquipmentData = EquipmentProxy:GetEquipmentData(Player, EquipmentUniqueId)
if not EquipmentData then warn('无法获取装备数据: ' , Player.Name, EquipmentUniqueId) return end
-- 检查是否正在穿戴中
if GemData.wearing ~= 0 then
RE_PlayerTip:FireClient(Player, "宝石穿戴中, 请先卸下")
return
end
-- TODO: 检查对应装备是否有充足的宝石槽位
-- 穿戴
GemData.wearing = EquipmentUniqueId
GemInstance:SetAttribute("Wearing", EquipmentUniqueId)
end
--------------------------------------------------------------------------------
-- 获取升级加点属性
function GemProxy:GetPlayerGemWearingAttributes(Player: Player)
if not Player then warn('获取玩家属性失败: ', Player.Name) return end
local playerGems = ArchiveProxy.pData[Player.UserId][STORE_NAME].Gems
local attributes = {}
for _, gemData in playerGems do
if gemData.wearing ~= 0 then
if attributes[gemData.effectAttribute] then
attributes[gemData.effectAttribute] = attributes[gemData.effectAttribute] + gemData.attributeValue
else
attributes[gemData.effectAttribute] = gemData.attributeValue
end
end
end
return attributes
end
-- 获取玩家属性
function GemProxy:GetPlayerAttributes(Player: Player)
local attributesList = {}
attributesList.GemWearingAttributes = self:GetPlayerGemWearingAttributes(Player)
return attributesList
end
--------------------------------------------------------------------------------
function GemProxy:OnPlayerRemoving(Player: Player)
end
ReplicatedStorage.Remotes.PlayerRemoving.Event:Connect(function(Player: Player)
GemProxy:OnPlayerRemoving(Player)
end)
return GemProxy

View File

@ -12,11 +12,16 @@ local ArchiveProxy = require(ServerStorage.Proxy.ArchiveProxy)
local MobsProxy = require(ServerStorage.Proxy.MobsProxy)
local TypeList = require(ServerStorage.Base.TypeList)
--> Dependencies
local Communicate = require(ServerStorage.Modules.Tools.Communicate)
--> Json
local JsonLevel = require(ReplicatedStorage.Json.Level)
--> Events
local BD_ChallengeEnd = ReplicatedStorage.Events.BD_ChallengeEnd
local RE_CleanPlayerPerformance = ReplicatedStorage.Events.RE_CleanPlayerPerformance
--> Constants
local STORE_NAME = "Level"
@ -33,11 +38,15 @@ local LevelFolder = Utils:CreateFolder(STORE_NAME, game.Workspace)
--------------------------------------------------------------------------------
-- 获取玩家关卡文件夹
local function GetPlayerLevelFolder(Player: Player)
local pData = Utils:GetPlayerDataFolder(Player)
if not pData then return end
local LevelFolder = pData:FindFirstChild("Level")
return LevelFolder
local function GetPlayerLevelFolder(Player: Player, ChildFolderName: string?)
local PlayerFolder = LevelFolder:FindFirstChild(Player.UserId)
if not PlayerFolder then warn("PlayerFolder not found", Player.UserId) return end
if ChildFolderName then
local ChildFolder = PlayerFolder:FindFirstChild(ChildFolderName)
if not ChildFolder then warn("ChildFolder not found", ChildFolderName) return end
return ChildFolder
end
return PlayerFolder
end
-- 获取玩家关卡Workspace目录
@ -46,9 +55,19 @@ local function GetPlayerLevelWorkspaceFolder(PlayerUserId: string)
end
-- 创建关卡信息实例
local function CreateLevelInstance(Player: Player, Folder: Instance, LevelKey: string, LevelValue: number)
if not Player or not Folder or not LevelKey or not LevelValue then return end
local LevelInstance = Instance.new("NumberValue")
local function CreateLevelInstance(Player: Player, Folder: Instance, LevelKey: string, LevelValue: any)
if not Player or not Folder or not LevelKey then return end
local InstanceType
if type(LevelValue) == "number" then
InstanceType = "NumberValue"
elseif type(LevelValue) == "boolean" then
InstanceType = "BoolValue"
elseif type(LevelValue) == "string" then
InstanceType = "StringValue"
else
InstanceType = "NumberValue"
end
local LevelInstance = Instance.new(InstanceType)
LevelInstance.Name = LevelKey
LevelInstance.Parent = Folder
LevelInstance.Value = LevelValue
@ -68,9 +87,9 @@ end
local EXCEPT_KEY = { "Task", "Mobs"}
local function ChangeValue(Player: Player, Folder: Instance, LevelKey: string, LevelValue: any)
if not Player or not Folder or not LevelKey or not LevelValue then return end
if not Player or not Folder or not LevelKey then warn("LevelProxy ChangeValue", Player.UserId, Folder.Name, LevelKey, LevelValue) return end
local ValueInstance = Folder:FindFirstChild(LevelKey)
if not ValueInstance then return end
if not ValueInstance then warn("ValueInstance not found", LevelKey) return end
local storeTable
if Folder.Name == "Challenge" then
@ -86,17 +105,17 @@ end
-- 怪物死亡,由初始化时传入
local function OnMobDied(Player: Player, Mob: TypeList.Character)
for _, mob in LevelProxy.pData[Player.UserId].Mobs do
if mob ~= Mob then continue end
if mob.Instance ~= Mob.Instance then continue end
table.remove(LevelProxy.pData[Player.UserId].Mobs, table.find(LevelProxy.pData[Player.UserId].Mobs, mob))
-- 怪物清除判断
local LevelData = Utils:GetJsonData(JsonLevel, LevelProxy.pData[Player.UserId].LevelId)
-- 怪物被击杀时做关卡数据处理
local LevelData = Utils:GetIdDataFromJson(JsonLevel, LevelProxy.pData[Player.UserId].LevelId)
if LevelProxy.pData[Player.UserId].SpawnWaveFinish and #LevelProxy.pData[Player.UserId].Mobs == 0 then
if LevelProxy.pData[Player.UserId].NowWave < #LevelData["wave"] then
-- 波数增长
LevelProxy.pData[Player.UserId].NowWave = LevelProxy.pData[Player.UserId].NowWave + 1
-- -- 波数增长
LevelProxy.pData[Player.UserId].ShouldWave = LevelProxy.pData[Player.UserId].ShouldWave + 1
-- 新波次重置怪物生成状态标记
local ChallengeFolder = LevelFolder:FindFirstChild("Challenge")
local ChallengeFolder = GetPlayerLevelFolder(Player, "Challenge")
ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", false)
elseif LevelProxy.pData[Player.UserId].NowWave >= #LevelData["wave"] then
-- 结束判断
@ -110,8 +129,6 @@ end
--------------------------------------------------------------------------------
function LevelProxy:InitPlayer(Player: Player)
local pData = Utils:GetPlayerDataFolder(Player)
if not pData then warn("Level pData not found", Player.UserId) return end
local PlayerLevelFolder = Utils:CreateFolder(Player.UserId, LevelFolder)
local ProgressFolder = Utils:CreateFolder("Progress", PlayerLevelFolder)
local DungeonFolder = Utils:CreateFolder("Dungeon", PlayerLevelFolder)
@ -163,8 +180,7 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
-- 场景后端生成
-- 后端生成当前关卡状态数据
local LevelFolder = GetPlayerLevelWorkspaceFolder(Player.UserId)
local ChallengeFolder = LevelFolder:FindFirstChild("Challenge")
local ChallengeFolder = GetPlayerLevelFolder(Player,"Challenge")
if not ChallengeFolder then warn("ChallengeFolder not found") return end
local levelTask = task.spawn(function()
ChangeValue(Player, ChallengeFolder, "IsBoss", LevelData.type == 2 and true or false)
@ -184,13 +200,13 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
LevelProxy.pData[Player.UserId].NowWave < LevelProxy.pData[Player.UserId].ShouldWave and
LevelProxy.pData[Player.UserId].SpawnWaveFinish == false then
ChangeValue(Player, ChallengeFolder, "NowWave", LevelProxy.pData[Player.UserId].NowWave + 1)
ChangeValue(Player, ChallengeFolder, "SpawnWaveFinish", true)
local waveData = LevelData.wave[LevelProxy.pData[Player.UserId].NowWave]
for i = 1, #waveData, 3 do
local mobId = waveData[i + 1]
local mobCount = waveData[i + 2]
for _ = 1, mobCount do
print("怪物增益", LevelData.atkBonus, LevelData.hpBonus)
local mob = MobsProxy:CreateMob(Player, mobId, LevelData.atkBonus, LevelData.hpBonus, OnMobDied)
table.insert(LevelProxy.pData[Player.UserId].Mobs, mob)
end
@ -204,7 +220,7 @@ function LevelProxy:ChallengeLevel(Player: Player, LevelId: number)
local maxWave = #LevelData.wave
if LevelProxy.pData[Player.UserId].NowWave < maxWave then
-- 下一波
ChangeValue(Player, ChallengeFolder, "ShouldWave", LevelProxy.pData[Player.UserId].ShouldWave + 1)
ChangeValue(Player, ChallengeFolder, "ShouldWave", LevelProxy.pData[Player.UserId].ShouldWave)
else
-- 挑战胜利
self:ChallengeEnd(Player, true)
@ -224,9 +240,7 @@ end
-- 挑战结束
function LevelProxy:ChallengeEnd(Player: Player, result: boolean)
local pData = Utils:GetPlayerDataFolder(Player)
local LevelFolder = Utils:CreateFolder(STORE_NAME, pData)
local ProgressFolder = Utils:CreateFolder("Progress", LevelFolder)
local ProgressFolder = GetPlayerLevelFolder(Player, "Progress")
-- 停止关卡循环
if LevelProxy.pData[Player.UserId].Task then
@ -235,12 +249,16 @@ function LevelProxy:ChallengeEnd(Player: Player, result: boolean)
end
-- 清除剩余怪物
print("清除剩余怪物", LevelProxy.pData[Player.UserId].Mobs)
for _, mob in LevelProxy.pData[Player.UserId].Mobs do mob:Died(true) end
LevelProxy.pData[Player.UserId].Mobs = {}
-- 清除玩家表现
Communicate:SendToClientFree(RE_CleanPlayerPerformance, Player)
-- 判断玩家是否通关
if result then
ChangeValue(Player, ProgressFolder, "LevelId", LevelProxy.pData[Player.UserId].LevelId + 1)
ChangeValue(Player, ProgressFolder, "Main", LevelProxy.pData[Player.UserId].LevelId + 1)
else
local IsBoss = LevelProxy.pData[Player.UserId].IsBoss
if IsBoss then

View File

@ -68,7 +68,7 @@ function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob
newMobModel.Parent = playerMobsFolder
-- 死亡函数
if OnMobDied then Mob.OnDied = OnMobDied end
if OnMobDied then self.OnDied = OnMobDied end
-- -- 接入统一AI
-- self.Humanoid.MoveToFinished:Connect(function()
@ -81,11 +81,13 @@ function Mob.new(Player: Player, MobId: number, OnMobDied: ((Player: Player, Mob
return self
end
-- IsSkinOnDied:暂时意义不明,忘了之前为啥写了
function Mob:Died(IsSkinOnDied: boolean?)
MobsProxy:RemoveMob(self.Player, self.Instance)
if not IsSkinOnDied then
if self.OnDied then self.OnDied(self.Player, self) end
end
-- if not IsSkinOnDied then
-- if self.OnDied then self.OnDied(self.Player, self) end
-- end
if self.OnDied then self.OnDied(self.Player, self) end
Character.Died(self)
end

View File

@ -38,6 +38,9 @@ function LevelLoop.new(Player: Player, PlayerRole: TypeList.Character)
end
function LevelLoop:AutoChallenge()
print("AutoChallenge")
-- TODO: 回退有bug不能一关一关回退
-- 重置玩家状态(先临时调用角色复活,之后复杂的内容再说)
self.PlayerRole:Respawn()
@ -50,11 +53,12 @@ function LevelLoop:AutoChallenge()
if FailBossId == LevelId then
LevelId = LevelId - 1
end
LevelProxy:ChallengeLevel(self.Player, LevelId)
end
function LevelLoop:OnChallengeEnd(Player: Player, LevelId: number, result: boolean)
self.TaskAutoChallenge = task.defer(function()
self.TaskAutoChallenge = task.spawn(function()
task.wait(3)
self:AutoChallenge()
end)

View File

@ -9,6 +9,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Dependencies
local TypeList = require(ServerStorage.Base.TypeList)
local Utils = require(ReplicatedStorage.Tools.Utils)
--> Events
local RE_PlayerAI = ReplicatedStorage.Events.RE_PlayerAI
@ -17,8 +18,6 @@ local RE_PlayerAI = ReplicatedStorage.Events.RE_PlayerAI
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
@ -36,7 +35,6 @@ if BehaviourFolder then
end
end
--------------------------------------------------------------------------------
-- 新建玩家AI
@ -48,6 +46,9 @@ function PlayerAI.new(Player: Player, PlayerRole: TypeList.Character)
self.ExecutingState = false
self.PlayerControling = false
self.LastTime = 0
self.Counter = 0
self.BehaviourList = {}
self.LoopTask = task.spawn(function()
while task.wait(0.25) do
@ -91,6 +92,11 @@ function PlayerAI:AddBehaviour(BehaviourName: string)
self.BehaviourList[BehaviourName] = newBehaviour
end
-- 获取并记录行为UniqueId
function PlayerAI:GetBehaviourUniqueId()
return Utils:GenUniqueIdPlayerAI(self.LastTime, self.Counter)
end
-- 动态删除行为
function PlayerAI:RemoveBehaviour(BehaviourName: string)
if self.BehaviourList[BehaviourName] then

View File

@ -17,6 +17,10 @@ local JsonCharacter = require(ReplicatedStorage.Json.Character)
--> Dependencies
local LevelLoop = require(script.LevelLoop)
local PlayerAI = require(script.PlayerAI)
local Communicate = require(ServerStorage.Modules.Tools.Communicate)
--> Events
local RE_CleanPlayerPerformance = ReplicatedStorage.Events.RE_CleanPlayerPerformance
--------------------------------------------------------------------------------
@ -43,6 +47,9 @@ function PlayerRole.new(Player: Player, CharacterId: number)
-- 调用父类Character的new方法初始化通用属性
local self = Character.new(Player, playerCharacter, CharacterData)
setmetatable(self, PlayerRole)
-- 玩家放到Character目录下
playerCharacter.Parent = game.Workspace.Characters
return self
end
@ -80,6 +87,7 @@ function PlayerFightProxy:InitPlayer(Player: Player)
local PlayerAI = PlayerAI.new(Player, PlayerRole)
PlayerFightProxy.pData[Player.UserId].PlayerAI = PlayerAI
PlayerAI:AddBehaviour("Move")
PlayerAI:AddBehaviour("SwordWave")
end
function PlayerFightProxy:GetPlayerRole(Player: Player)
@ -103,6 +111,9 @@ function PlayerFightProxy:CleanPlayer(Player: Player)
end
function PlayerFightProxy:OnPlayerRemoving(Player: Player)
-- 玩家离开时,通知其他客户端销毁玩家表现内容
Communicate:SendToClientFree(RE_CleanPlayerPerformance, Player)
-- 正常清除玩家该模块下数据
if not PlayerFightProxy.pData[Player.UserId] then warn("PlayerFight Remove Data not found", Player.Name) return end
PlayerFightProxy.pData[Player.UserId] = nil
end

View File

@ -15,7 +15,6 @@ UserInputService.InputBegan:Connect(function(input, gameProcessed)
if input.KeyCode == Enum.KeyCode.H then
RE_PlayerHelper:FireServer("CleanPlayerData")
elseif input.KeyCode == Enum.KeyCode.J then
print("添加物品")
RE_PlayerHelper:FireServer("AddItem", {1, 100})
elseif input.KeyCode == Enum.KeyCode.K then
RE_UpgradeAttributes:FireServer(1)

View File

@ -0,0 +1,88 @@
-- 控制客户端表现管理
local PerformanceClient = {}
PerformanceClient.pData = {}
--> Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--> Events
local EventsFolder = ReplicatedStorage:FindFirstChild("Events")
local RE_PerformanceEvent = EventsFolder:FindFirstChild("RE_PerformanceEvent")
local RE_CleanPlayerPerformance = EventsFolder:FindFirstChild("RE_CleanPlayerPerformance")
--> Variables
local LocalPlayer = game.Players.LocalPlayer
--------------------------------------------------------------------------------
-- 生成本地化表现目录
local PerformanceFolder = Instance.new("Folder")
PerformanceFolder.Name = "Performance"
PerformanceFolder.Parent = game.Workspace
-- 加载所有客户端行为
local Behaviours = {}
local BehaviourFolder = ReplicatedStorage:FindFirstChild("Modules"):FindFirstChild("BehavioursClient")
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
-- 监听表现事件调用
RE_PerformanceEvent.OnClientEvent:Connect(function(ServerTime: number, CastTag: string, CasterPlayer: Player, BehaviourName: string, CastState: boolean, Infos: table)
-- 初始化玩家存储
local UserId = CasterPlayer.UserId
if not PerformanceClient.pData[UserId] then PerformanceClient.pData[UserId] = {} end
local delayTime = ServerTime - tick()
if CastTag == "Init" then
print("Init", BehaviourName)
-- 暂时就新增一个表不调用初始化因为服务端不是多个new做的而是多次调用
if not PerformanceClient.pData[UserId][BehaviourName] then
PerformanceClient.pData[UserId][BehaviourName] = {}
end
elseif CastTag == "Show" then
print("Show", BehaviourName, Behaviours)
-- 直接调用init和对应的show(因为服务端只new一次客户端多次调用)
if not PerformanceClient.pData[UserId][BehaviourName] then return end
for _, CastInfo in pairs(Infos) do
local BehaviourTable = PerformanceClient.pData[UserId][BehaviourName]
BehaviourTable[CastInfo.UniqueId] = Behaviours[BehaviourName]:Init(CasterPlayer, CastInfo, delayTime, CastState)
BehaviourTable[CastInfo.UniqueId]:Show(CasterPlayer, CastInfo, delayTime, CastState)
end
elseif CastTag == "Destroy" then
if not PerformanceClient.pData[UserId][BehaviourName] then return end
for _, CastInfo in pairs(Infos) do
local Behaviour = PerformanceClient.pData[UserId][BehaviourName][CastInfo.UniqueId]
if Behaviour and Behaviour.Destroy then
Behaviour:Destroy()
end
end
end
end)
-- 清理玩家所有表现数据
RE_CleanPlayerPerformance.OnClientEvent:Connect(function(CleanedPlayer: Player)
-- print("CleanPlayerPerformance", CleanedPlayer)
local UserId = CleanedPlayer.UserId
if not PerformanceClient.pData[UserId] then return end
for _, BehaviourList in pairs(PerformanceClient.pData[UserId]) do
for _, Behaviour in pairs(BehaviourList) do
Behaviour:Destroy()
end
end
PerformanceClient.pData[UserId] = nil
end)
return PerformanceClient