diff --git a/excel/ability.xlsx b/excel/ability.xlsx index 5cd47c1..0328664 100644 Binary files a/excel/ability.xlsx and b/excel/ability.xlsx differ diff --git a/excel/anim.xlsx b/excel/anim.xlsx index 52ebc72..e880a3c 100644 Binary files a/excel/anim.xlsx and b/excel/anim.xlsx differ diff --git a/excel/attribute.xlsx b/excel/attribute.xlsx index 371cd91..abf4721 100644 Binary files a/excel/attribute.xlsx and b/excel/attribute.xlsx differ diff --git a/excel/enemy.xlsx b/excel/enemy.xlsx index 32f8ea2..acac085 100644 Binary files a/excel/enemy.xlsx and b/excel/enemy.xlsx differ diff --git a/excel/level.xlsx b/excel/level.xlsx index e9f07bc..bbbc993 100644 Binary files a/excel/level.xlsx and b/excel/level.xlsx differ diff --git a/src/ReplicatedStorage/Json/Ability.json b/src/ReplicatedStorage/Json/Ability.json index 60dfbfc..d0bac1e 100644 --- a/src/ReplicatedStorage/Json/Ability.json +++ b/src/ReplicatedStorage/Json/Ability.json @@ -1,3 +1,4 @@ [ -{"id":20000,"type":1,"icon":1,"behaviourName":"SwordWave","upgradeCost":[30000,5,10],"upgradeValue":[10,200],"recycle":[30000,100]} +{"id":20000,"type":1,"icon":1,"behaviourName":"Attack","upgradeCost":[30000,5,0],"upgradeValue":[10,0],"recycle":[30000,0]}, +{"id":20001,"type":1,"icon":1,"behaviourName":"SwordWave","upgradeCost":[30000,5,10],"upgradeValue":[10,200],"recycle":[30000,100]} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Animation.json b/src/ReplicatedStorage/Json/Animation.json index 7f1d70f..4fa1d14 100644 --- a/src/ReplicatedStorage/Json/Animation.json +++ b/src/ReplicatedStorage/Json/Animation.json @@ -1,3 +1,4 @@ [ -{"id":1,"name":"SwordWave","rbxId":"133335766636767","priority":"Action"} +{"id":1,"name":"SwordWave","rbxId":"133335766636767","priority":"Action"}, +{"id":2,"name":"Attack","rbxId":"17283773476","priority":"Action"} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Attributes.json b/src/ReplicatedStorage/Json/Attributes.json index 419a1fa..68a54b0 100644 --- a/src/ReplicatedStorage/Json/Attributes.json +++ b/src/ReplicatedStorage/Json/Attributes.json @@ -14,7 +14,7 @@ {"id":13,"type":1,"effectAttribute":"shadowDef","battleValue":[1,10],"iconId":13}, {"id":14,"type":2,"effectAttribute":"attackRate","battleValue":[1,10],"iconId":14}, {"id":15,"type":2,"effectAttribute":"hpRate","battleValue":[1,10],"iconId":15}, -{"id":16,"type":2,"effectAttribute":"atkSpeed","battleValue":[1,10],"iconId":16}, +{"id":16,"type":1,"effectAttribute":"atkSpeed","battleValue":[1,10],"iconId":16}, {"id":20,"type":2,"effectAttribute":"critRate","battleValue":[1,10],"iconId":17}, {"id":21,"type":2,"effectAttribute":"critDamageRate","battleValue":[1,10],"iconId":18}, {"id":22,"type":2,"effectAttribute":"atkSpeedRate","battleValue":[1,10],"iconId":19}, diff --git a/src/ReplicatedStorage/Json/Enemy.json b/src/ReplicatedStorage/Json/Enemy.json index e29a0d2..3a16439 100644 --- a/src/ReplicatedStorage/Json/Enemy.json +++ b/src/ReplicatedStorage/Json/Enemy.json @@ -1,5 +1,5 @@ [ -{"id":1,"type":1,"name":1,"attack":83,"hp":400,"walkSpeed":10,"attackSpeed":1,"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":240,"hp":2000,"walkSpeed":20,"attackSpeed":1,"model":"Thief"} +{"id":1,"type":1,"name":1,"attack":83,"hp":400,"walkSpeed":8,"attackSpeed":1,"model":"Thief"}, +{"id":2,"type":1,"name":2,"attack":30,"hp":300,"walkSpeed":8,"attackSpeed":1,"model":"Thief"}, +{"id":1000,"type":2,"name":1000,"attack":240,"hp":2000,"walkSpeed":4,"attackSpeed":1,"model":"Thief"} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Level.json b/src/ReplicatedStorage/Json/Level.json index 5dc7665..9905731 100644 --- a/src/ReplicatedStorage/Json/Level.json +++ b/src/ReplicatedStorage/Json/Level.json @@ -1,52 +1,52 @@ [ -{"id":1,"type":1,"timeLimit":null,"atkBonus":500,"hpBonus":500,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":2,"type":1,"timeLimit":null,"atkBonus":520,"hpBonus":520,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":3,"type":1,"timeLimit":null,"atkBonus":540,"hpBonus":540,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":4,"type":1,"timeLimit":null,"atkBonus":560,"hpBonus":560,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":1,"type":1,"timeLimit":null,"atkBonus":500,"hpBonus":500,"wave":[[10,1,1,50,1,1]]}, +{"id":2,"type":1,"timeLimit":null,"atkBonus":520,"hpBonus":520,"wave":[[10,1,1,50,1,1]]}, +{"id":3,"type":1,"timeLimit":null,"atkBonus":540,"hpBonus":540,"wave":[[10,1,1,50,1,1]]}, +{"id":4,"type":1,"timeLimit":null,"atkBonus":560,"hpBonus":560,"wave":[[10,1,1,50,1,1]]}, {"id":5,"type":2,"timeLimit":60,"atkBonus":1050,"hpBonus":1050,"wave":[[10,1000,1]]}, -{"id":6,"type":1,"timeLimit":null,"atkBonus":600,"hpBonus":600,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":7,"type":1,"timeLimit":null,"atkBonus":620,"hpBonus":620,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":8,"type":1,"timeLimit":null,"atkBonus":640,"hpBonus":640,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":9,"type":1,"timeLimit":null,"atkBonus":660,"hpBonus":660,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":6,"type":1,"timeLimit":null,"atkBonus":600,"hpBonus":600,"wave":[[10,1,1,50,1,1]]}, +{"id":7,"type":1,"timeLimit":null,"atkBonus":620,"hpBonus":620,"wave":[[10,1,1,50,1,1]]}, +{"id":8,"type":1,"timeLimit":null,"atkBonus":640,"hpBonus":640,"wave":[[10,1,1,50,1,1]]}, +{"id":9,"type":1,"timeLimit":null,"atkBonus":660,"hpBonus":660,"wave":[[10,1,1,50,1,1]]}, {"id":10,"type":2,"timeLimit":60,"atkBonus":1100,"hpBonus":1100,"wave":[[10,1000,1]]}, -{"id":11,"type":1,"timeLimit":null,"atkBonus":700,"hpBonus":700,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":12,"type":1,"timeLimit":null,"atkBonus":720,"hpBonus":720,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":13,"type":1,"timeLimit":null,"atkBonus":740,"hpBonus":740,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":14,"type":1,"timeLimit":null,"atkBonus":760,"hpBonus":760,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":11,"type":1,"timeLimit":null,"atkBonus":700,"hpBonus":700,"wave":[[10,1,1,50,1,1]]}, +{"id":12,"type":1,"timeLimit":null,"atkBonus":720,"hpBonus":720,"wave":[[10,1,1,50,1,1]]}, +{"id":13,"type":1,"timeLimit":null,"atkBonus":740,"hpBonus":740,"wave":[[10,1,1,50,1,1]]}, +{"id":14,"type":1,"timeLimit":null,"atkBonus":760,"hpBonus":760,"wave":[[10,1,1,50,1,1]]}, {"id":15,"type":2,"timeLimit":60,"atkBonus":1150,"hpBonus":1150,"wave":[[10,1000,1]]}, -{"id":16,"type":1,"timeLimit":null,"atkBonus":800,"hpBonus":800,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":17,"type":1,"timeLimit":null,"atkBonus":820,"hpBonus":820,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":18,"type":1,"timeLimit":null,"atkBonus":840,"hpBonus":840,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":19,"type":1,"timeLimit":null,"atkBonus":860,"hpBonus":860,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":16,"type":1,"timeLimit":null,"atkBonus":800,"hpBonus":800,"wave":[[10,1,1,50,1,1]]}, +{"id":17,"type":1,"timeLimit":null,"atkBonus":820,"hpBonus":820,"wave":[[10,1,1,50,1,1]]}, +{"id":18,"type":1,"timeLimit":null,"atkBonus":840,"hpBonus":840,"wave":[[10,1,1,50,1,1]]}, +{"id":19,"type":1,"timeLimit":null,"atkBonus":860,"hpBonus":860,"wave":[[10,1,1,50,1,1]]}, {"id":20,"type":2,"timeLimit":60,"atkBonus":1250,"hpBonus":1250,"wave":[[10,1000,1]]}, -{"id":21,"type":1,"timeLimit":null,"atkBonus":900,"hpBonus":900,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":22,"type":1,"timeLimit":null,"atkBonus":920,"hpBonus":920,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":23,"type":1,"timeLimit":null,"atkBonus":940,"hpBonus":940,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":24,"type":1,"timeLimit":null,"atkBonus":960,"hpBonus":960,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":21,"type":1,"timeLimit":null,"atkBonus":900,"hpBonus":900,"wave":[[10,1,1,50,1,1]]}, +{"id":22,"type":1,"timeLimit":null,"atkBonus":920,"hpBonus":920,"wave":[[10,1,1,50,1,1]]}, +{"id":23,"type":1,"timeLimit":null,"atkBonus":940,"hpBonus":940,"wave":[[10,1,1,50,1,1]]}, +{"id":24,"type":1,"timeLimit":null,"atkBonus":960,"hpBonus":960,"wave":[[10,1,1,50,1,1]]}, {"id":25,"type":2,"timeLimit":60,"atkBonus":1350,"hpBonus":1350,"wave":[[10,1000,1]]}, -{"id":26,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":27,"type":1,"timeLimit":null,"atkBonus":1020,"hpBonus":1020,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":28,"type":1,"timeLimit":null,"atkBonus":1040,"hpBonus":1040,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":29,"type":1,"timeLimit":null,"atkBonus":1060,"hpBonus":1060,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":26,"type":1,"timeLimit":null,"atkBonus":1000,"hpBonus":1000,"wave":[[10,1,1,50,1,1]]}, +{"id":27,"type":1,"timeLimit":null,"atkBonus":1020,"hpBonus":1020,"wave":[[10,1,1,50,1,1]]}, +{"id":28,"type":1,"timeLimit":null,"atkBonus":1040,"hpBonus":1040,"wave":[[10,1,1,50,1,1]]}, +{"id":29,"type":1,"timeLimit":null,"atkBonus":1060,"hpBonus":1060,"wave":[[10,1,1,50,1,1]]}, {"id":30,"type":2,"timeLimit":60,"atkBonus":1500,"hpBonus":1500,"wave":[[10,1000,1]]}, -{"id":31,"type":1,"timeLimit":null,"atkBonus":1100,"hpBonus":1100,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":32,"type":1,"timeLimit":null,"atkBonus":1120,"hpBonus":1120,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":33,"type":1,"timeLimit":null,"atkBonus":1140,"hpBonus":1140,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":34,"type":1,"timeLimit":null,"atkBonus":1160,"hpBonus":1160,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":31,"type":1,"timeLimit":null,"atkBonus":1100,"hpBonus":1100,"wave":[[10,1,1,50,1,1]]}, +{"id":32,"type":1,"timeLimit":null,"atkBonus":1120,"hpBonus":1120,"wave":[[10,1,1,50,1,1]]}, +{"id":33,"type":1,"timeLimit":null,"atkBonus":1140,"hpBonus":1140,"wave":[[10,1,1,50,1,1]]}, +{"id":34,"type":1,"timeLimit":null,"atkBonus":1160,"hpBonus":1160,"wave":[[10,1,1,50,1,1]]}, {"id":35,"type":2,"timeLimit":60,"atkBonus":2000,"hpBonus":2000,"wave":[[10,1000,1]]}, -{"id":36,"type":1,"timeLimit":null,"atkBonus":1200,"hpBonus":1200,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":37,"type":1,"timeLimit":null,"atkBonus":1220,"hpBonus":1220,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":38,"type":1,"timeLimit":null,"atkBonus":1240,"hpBonus":1240,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":39,"type":1,"timeLimit":null,"atkBonus":1260,"hpBonus":1260,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":36,"type":1,"timeLimit":null,"atkBonus":1200,"hpBonus":1200,"wave":[[10,1,1,50,1,1]]}, +{"id":37,"type":1,"timeLimit":null,"atkBonus":1220,"hpBonus":1220,"wave":[[10,1,1,50,1,1]]}, +{"id":38,"type":1,"timeLimit":null,"atkBonus":1240,"hpBonus":1240,"wave":[[10,1,1,50,1,1]]}, +{"id":39,"type":1,"timeLimit":null,"atkBonus":1260,"hpBonus":1260,"wave":[[10,1,1,50,1,1]]}, {"id":40,"type":2,"timeLimit":60,"atkBonus":2500,"hpBonus":2500,"wave":[[10,1000,1]]}, -{"id":41,"type":1,"timeLimit":null,"atkBonus":1300,"hpBonus":1300,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":42,"type":1,"timeLimit":null,"atkBonus":1320,"hpBonus":1320,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":43,"type":1,"timeLimit":null,"atkBonus":1340,"hpBonus":1340,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":44,"type":1,"timeLimit":null,"atkBonus":1360,"hpBonus":1360,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":41,"type":1,"timeLimit":null,"atkBonus":1300,"hpBonus":1300,"wave":[[10,1,1,50,1,1]]}, +{"id":42,"type":1,"timeLimit":null,"atkBonus":1320,"hpBonus":1320,"wave":[[10,1,1,50,1,1]]}, +{"id":43,"type":1,"timeLimit":null,"atkBonus":1340,"hpBonus":1340,"wave":[[10,1,1,50,1,1]]}, +{"id":44,"type":1,"timeLimit":null,"atkBonus":1360,"hpBonus":1360,"wave":[[10,1,1,50,1,1]]}, {"id":45,"type":2,"timeLimit":60,"atkBonus":3000,"hpBonus":3000,"wave":[[10,1000,1]]}, -{"id":46,"type":1,"timeLimit":null,"atkBonus":1400,"hpBonus":1400,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":47,"type":1,"timeLimit":null,"atkBonus":1420,"hpBonus":1420,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":48,"type":1,"timeLimit":null,"atkBonus":1440,"hpBonus":1440,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, -{"id":49,"type":1,"timeLimit":null,"atkBonus":1460,"hpBonus":1460,"wave":[[10,1,1,50,1,1,100,1,1,150,1,1,200,1,1,250,1,1]]}, +{"id":46,"type":1,"timeLimit":null,"atkBonus":1400,"hpBonus":1400,"wave":[[10,1,1,50,1,1]]}, +{"id":47,"type":1,"timeLimit":null,"atkBonus":1420,"hpBonus":1420,"wave":[[10,1,1,50,1,1]]}, +{"id":48,"type":1,"timeLimit":null,"atkBonus":1440,"hpBonus":1440,"wave":[[10,1,1,50,1,1]]}, +{"id":49,"type":1,"timeLimit":null,"atkBonus":1460,"hpBonus":1460,"wave":[[10,1,1,50,1,1]]}, {"id":50,"type":2,"timeLimit":60,"atkBonus":3500,"hpBonus":3500,"wave":[[10,1000,1]]} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Modules/BehavioursClient/Attack.luau b/src/ReplicatedStorage/Modules/BehavioursClient/Attack.luau new file mode 100644 index 0000000..077c237 --- /dev/null +++ b/src/ReplicatedStorage/Modules/BehavioursClient/Attack.luau @@ -0,0 +1,48 @@ +-- 剑气 + +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local BehaviourClient = require(ReplicatedStorage.Base.BehaviourClient) + +--> Variables +local PrefabFolder = ReplicatedStorage:WaitForChild("Prefabs") +-- local Prefab_Attack = PrefabFolder:WaitForChild("Projectiles"):WaitForChild("Attack") + +-------------------------------------------------------------------------------- + +local Attack = {} +Attack.__index = Attack +setmetatable(Attack, {__index = BehaviourClient}) + +function Attack:Init(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean) + local self = BehaviourClient:Init(CasterPlayer, CastInfo, DelayTime, CastState) + setmetatable(self, Attack) + + -- 加载动画 + self:LoadAnimationByName("Attack") + return self +end + +function Attack:Show(CasterPlayer: Player, CastInfo: table, DelayTime: number, CastState: boolean) + self.EffectDispatcher:ShowAnimation(self.Player, DelayTime, self:GetAnimationByName("Attack")) +end + +function Attack: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 Attack \ No newline at end of file diff --git a/src/ServerStorage/Base/Behaviour.luau b/src/ServerStorage/Base/Behaviour.luau index a75d6bb..c96cb84 100644 --- a/src/ServerStorage/Base/Behaviour.luau +++ b/src/ServerStorage/Base/Behaviour.luau @@ -12,6 +12,16 @@ local RE_CleanPlayerPerformance = ReplicatedStorage:FindFirstChild("Events"):Fin --> Dependencies local TypeList = require(ServerStorage.Base.TypeList) local Communicate = require(ServerStorage.Modules.Tools.Communicate) +local Utils = require(ReplicatedStorage.Tools.Utils) + +--> Json +local JsonAttributes = require(ReplicatedStorage.Json.Attributes) + +--> 临时维护一个属性数据表,用于记录属性类型 +local AttributesNameData = {} +for _, Attribute in JsonAttributes do + AttributesNameData[Attribute.effectAttribute] = Utils:DeepCopyTable(Attribute) +end -------------------------------------------------------------------------------- @@ -54,7 +64,6 @@ end -- 检查行为前先清理之前遗留的引用数据 function Behaviour:CheckClean() - if self.Mobs then for _, Mob in self.Mobs do self.Mobs[Mob] = nil @@ -69,6 +78,21 @@ function Behaviour:CheckClean() end end +function Behaviour:GetAttributeValue(AttributeName: string) + local AttributeValue = self.Character.Instance:FindFirstChild("Attributes"):GetAttribute(AttributeName) + if not AttributeValue then return nil end + -- 处理对应的值 + local AttributeData = AttributesNameData[AttributeName] + if not AttributeData then return nil end + + local typeValue = AttributeData.type + if typeValue == 1 then + return AttributeValue + elseif typeValue == 2 then + return AttributeValue / 100 + end +end + -- 启动冷却时间清除计时 function Behaviour:StartCooldownTask() self.Cooldown = self.OrgCooldown diff --git a/src/ServerStorage/Modules/Behaviours/Attack.luau b/src/ServerStorage/Modules/Behaviours/Attack.luau new file mode 100644 index 0000000..092c555 --- /dev/null +++ b/src/ServerStorage/Modules/Behaviours/Attack.luau @@ -0,0 +1,121 @@ +-- 移动行为 + +--> 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 Attack = {} +Attack.__index = Attack +setmetatable(Attack, {__index = Behaviour}) + +local CAST_DISTANCE = 8 +local COOLDOWN = 1 +local ATTRIBUTE_LIST = { + {Name = "attack", ElementType = DamageProxy.ElementType.NONE}, + {Name = "fireAtk", ElementType = DamageProxy.ElementType.FIRE}, + {Name = "iceAtk", ElementType = DamageProxy.ElementType.ICE}, + {Name = "lightAtk", ElementType = DamageProxy.ElementType.LIGHT}, + {Name = "shadowAtk", ElementType = DamageProxy.ElementType.SHADOW}, + +} + +function Attack:Init(PlayerAI, Character: TypeList.Character, Player: Player) + local self = Behaviour:Init(PlayerAI, Character, script.Name) + self.Player = Player + self.Mobs = nil + setmetatable(self, Attack) + self.OrgCooldown = COOLDOWN + -- self:StartCooldownTask() + + -- 客户端表现 + self:SendPerformanceEvent("Init", self.Player, script.Name, true, {}) + return self +end + +function Attack:Check(CheckInfo: table) + if Behaviour.CheckStat(self) then return -1, self.CheckData end + self:CheckClean() + + self.Mobs = MobsProxy:GetPlayerMobs(self.Player) + if not self.Mobs then return end + + + local closestMob, minDistance = nil, CAST_DISTANCE + for _, Mob in self.Mobs 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 50, self.CheckData + end + + -- 返回优先级,执行数据 + return -1, self.CheckData +end + +function Attack:Execute() + self.ExeTask = task.spawn(function () + self:ChangeExecutingState(true) + -- cd放前面之后发送事件才能正常记录cd + -- self:StartCooldownTask() + + -- 停止移动 + self.Character.Humanoid:MoveTo(self.Character.Root.Position) + -- 朝向目标 + local HumanoidRootPart = self.Character.Root + local TargetPosition = self.CheckData["ClosestCharacter"].Instance.PrimaryPart.Position + + if HumanoidRootPart then + HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, TargetPosition) + end + + local atkSpeed = self:GetAttributeValue("atkSpeed") + if not atkSpeed then warn("atkSpeed not found") return end + + -- 表现部分 + self:SendPerformanceEvent("Show", self.Player, self.ScriptName, true, { + { UniqueId = self.PlayerAI:GetBehaviourUniqueId() } + }) + -- 攻击前摇 + task.wait(atkSpeed) + + -- TODO: 之后这里可以提前做暴击判定 + + -- 伤害逻辑计算部分 + local damageData = {} + for _, attribute in ATTRIBUTE_LIST do + local attributeValue = self:GetAttributeValue(attribute.Name) + if attributeValue then + table.insert(damageData, { + Damage = attributeValue, + Type = DamageProxy.DamageType.NORMAL, + Tag = DamageProxy.DamageTag.NORMAL, + ElementType = attribute.ElementType, + }) + end + end + DamageProxy:TakeDamage(self.Character, self.CheckData["ClosestCharacter"], damageData) + + -- task.wait(atkSpeed / 2) + self:ChangeExecutingState(false) + end) +end + +return Attack \ No newline at end of file diff --git a/src/ServerStorage/Modules/Behaviours/Move.luau b/src/ServerStorage/Modules/Behaviours/Move.luau index a8989d0..94d0f37 100644 --- a/src/ServerStorage/Modules/Behaviours/Move.luau +++ b/src/ServerStorage/Modules/Behaviours/Move.luau @@ -14,6 +14,8 @@ local Move = {} Move.__index = Move setmetatable(Move, {__index = Behaviour}) +local ATTACK_DISTANCE = 8 + function Move:Init(PlayerAI, Character: TypeList.Character, Player: Player) local self = Behaviour:Init(PlayerAI, Character, script.Name) self.Player = Player @@ -41,8 +43,11 @@ function Move:Check(CheckInfo: table) self.CheckData = {} if closestMob then - self.CheckData["ClosestCharacter"] = closestMob - return 10, self.CheckData + local distance = (closestMob.Instance.PrimaryPart.Position - self.Character.Instance.PrimaryPart.Position).Magnitude + if distance > ATTACK_DISTANCE then + self.CheckData["ClosestCharacter"] = closestMob + return 10, self.CheckData + end end -- 返回优先级,执行数据 @@ -50,10 +55,22 @@ function Move:Check(CheckInfo: table) end function Move:Execute() + if self.ExeTask then task.cancel(self.ExeTask) end self.ExeTask = task.spawn(function () self:ChangeExecutingState(true) - self.Character.Humanoid:MoveTo(self.CheckData["ClosestCharacter"].Instance:GetPivot().Position) - task.wait(0.5) + + -- 新移动朝向 + local TargetCharacter = self.CheckData["ClosestCharacter"].Instance + local TargetPosition = TargetCharacter:GetPivot().Position + local CharacterPosition = self.Character.Instance:GetPivot().Position + local Direction = (TargetPosition - CharacterPosition).Unit + local MoveToPosition = TargetPosition - (Direction * ATTACK_DISTANCE) + + self.Character.Humanoid:MoveTo(MoveToPosition) + + -- 旧内容 + -- self.Character.Humanoid:MoveTo(self.CheckData["ClosestCharacter"].Instance:GetPivot().Position) + task.wait(0.2) self:ChangeExecutingState(false) end) end diff --git a/src/ServerStorage/Proxy/ArchiveProxy.luau b/src/ServerStorage/Proxy/ArchiveProxy.luau index 9a89920..687dbb0 100644 --- a/src/ServerStorage/Proxy/ArchiveProxy.luau +++ b/src/ServerStorage/Proxy/ArchiveProxy.luau @@ -86,7 +86,6 @@ local function LoadData(Player: Player): (boolean, any) if Success and Response then print(("DataManager: User %s's data loaded into the game."):format(Player.Name)) - print(Response) else print(("DataManager: User %s had no data to load from."):format(Player.Name)) end diff --git a/src/ServerStorage/Proxy/MobsProxy/AI.luau b/src/ServerStorage/Proxy/MobsProxy/AI.luau index cc77bf9..04a760b 100644 --- a/src/ServerStorage/Proxy/MobsProxy/AI.luau +++ b/src/ServerStorage/Proxy/MobsProxy/AI.luau @@ -67,9 +67,7 @@ task.defer(function() local Enemy = Mob.Humanoid -- Simulation - if Mob.Root.Anchored then - Mob.Root.Anchored = false - end + 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 @@ -78,9 +76,12 @@ task.defer(function() -- Tracking if Player and Enemy and PlayerRole then if PlayerRole.Stats.Died then return end - if distance > 5 then + if distance > 8 then + Enemy:SetAttribute("AttackState", "Idle") Enemy:MoveTo(Player.Character:GetPivot().Position, Player.Character.PrimaryPart) else + Enemy:SetAttribute("AttackState", "Hit") + Mob.Root.Anchored = true Mob.ExecutingState = true -- 停止移动 Enemy:MoveTo(MobInstance:GetPivot().Position) @@ -92,7 +93,7 @@ task.defer(function() if not Player then return end if PlayerRole.Stats.Died then return end - if AI:GetModelDistance(MobInstance, Player.Character) <= 5 then + if AI:GetModelDistance(MobInstance, Player.Character) <= 8 then -- 调用伤害模块 DamageProxy:TakeDamage(Mob, Mob.PlayerRole, { { @@ -104,6 +105,7 @@ task.defer(function() }) end Mob.ExecutingState = false + Enemy:SetAttribute("AttackState", "Idle") end end else diff --git a/src/ServerStorage/Proxy/MobsProxy/init.luau b/src/ServerStorage/Proxy/MobsProxy/init.luau index 94db091..0f3f4be 100644 --- a/src/ServerStorage/Proxy/MobsProxy/init.luau +++ b/src/ServerStorage/Proxy/MobsProxy/init.luau @@ -99,7 +99,10 @@ function MobsProxy:CreateMob(Player: Player, MobId: number, AtkBonus: number?, H AI:StartTracking(Mob) -- 关卡系数 if AtkBonus then Mob:ChangeAttributeValue("attack", math.floor(Mob.Config.attack * (AtkBonus / 1000))) end - if HpBonus then Mob:ChangeAttributeValue("hp", math.floor(Mob.Config.hp * (HpBonus / 1000))) end + if HpBonus then + Mob:ChangeAttributeValue("hp", math.floor(Mob.Config.hp * (HpBonus / 1000))) + Mob:ChangeAttributeValue("maxhp", math.floor(Mob.Config.maxhp * (HpBonus / 1000))) + end MobsProxy.pData[Player.UserId][Mob.Instance] = Mob return Mob end diff --git a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau index c9b8af4..8bf3b1a 100644 --- a/src/ServerStorage/Proxy/PlayerFightProxy/init.luau +++ b/src/ServerStorage/Proxy/PlayerFightProxy/init.luau @@ -65,6 +65,7 @@ function PlayerRole.new(Player: Player, CharacterId: number) return self end + function PlayerRole:Died() self:ChangeState("Died", true) self.Humanoid.WalkSpeed = 0 @@ -119,7 +120,6 @@ function PlayerFightProxy:UpdatePlayerFightData(Player: Player) local AbilityProxy = require(ServerStorage.Proxy.AbilityProxy) local GemProxy = require(ServerStorage.Proxy.GemProxy) - local AttributesData = {} -- 计算角色基础属性值 + 装备属性值 + 宝石属性值,赋值属性 @@ -178,8 +178,9 @@ function PlayerFightProxy:UpdatePlayerFightData(Player: Player) for _, behaviourName in behaviourNameList do playerAI:AddBehaviour(behaviourName) end - -- playerAI:AddBehaviour("Move") - playerAI:AddBehaviour("SwordWave") + playerAI:AddBehaviour("Move") + -- playerAI:AddBehaviour("SwordWave") + playerAI:AddBehaviour("Attack") -- 给前端发送技能信息 diff --git a/src/StarterPlayerScripts/ClientMain/Camera.client.luau b/src/StarterPlayerScripts/ClientMain/Camera.client.luau index c9fea3a..fb9d04d 100644 --- a/src/StarterPlayerScripts/ClientMain/Camera.client.luau +++ b/src/StarterPlayerScripts/ClientMain/Camera.client.luau @@ -5,7 +5,7 @@ local RunService = game:GetService("RunService") local player = Players.LocalPlayer local camera = workspace.CurrentCamera -local CAMERA_DISTANCE = 20 +local CAMERA_DISTANCE = 30 local CAMERA_HEIGHT = 40 local CAMERA_ANGLE = math.rad(-45) local SCREEN_OFFSET = 5 diff --git a/src/StarterPlayerScripts/ClientMain/MobAnim.luau b/src/StarterPlayerScripts/ClientMain/MobAnim.luau new file mode 100644 index 0000000..d439f1c --- /dev/null +++ b/src/StarterPlayerScripts/ClientMain/MobAnim.luau @@ -0,0 +1,159 @@ +--[[ + Evercyan @ March 2023 + MobClient + + Unlike MobLib which handles server-sided code, and most of it in general, we run + some code on the client for things such as custom health overlays & overwriting humanoid state types. + + If you want to edit mob code to add new behavior or edit existing behavior, you likely want to refer + to the server-sided code under ServerStorage. +]] + +--> Services +local CollectionService = game:GetService("CollectionService") +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local Players = game:GetService("Players") + +--> Player +local Player = Players.LocalPlayer + +--> Dependencies +local Tween = require(ReplicatedStorage.Modules.Tween) +local Maid = require(ReplicatedStorage.Modules.Maid) + +--> Variables +local Mobs = {} + +-- Folder +local ClientMainPrefabs = Player.PlayerScripts.ClientMainPrefabs + +-------------------------------------------------------------------------------- + +-- WaitForChild keeps yielding, even if the Instance is removed. +-- Using this for safe yielding with StreamingEnabled! +local function safeWait(Item: Instance, Name: string): Instance? + if not Item then + return + elseif Item:FindFirstChild(Name) then + return Item:FindFirstChild(Name) + end + + local ItemAdded = Instance.new("BindableEvent") + local Maid = Maid.new() + + Maid:Add(Item.ChildAdded:Connect(function(Child) + if Child.Name == Name then + ItemAdded:Fire(Child) + ItemAdded:Destroy() + Maid:Destroy() + end + end)) + Maid:Add(Item.Destroying:Connect(function() + ItemAdded:Fire() + ItemAdded:Destroy() + Maid:Destroy() + end)) + + return ItemAdded.Event:Wait() +end + +-- Creates an "AnimationTrack" instance which is stored on the client for playing +local function LoadAnimationTrack(MobInstance: Model, Name: string, Priority: string) : AnimationTrack? + local Humanoid = MobInstance:FindFirstChild("Humanoid") :: Humanoid + local Animator = Humanoid and Humanoid:FindFirstChild("Animator") :: Animator + if not Animator then return nil end + + local Animation + Animation = ClientMainPrefabs.MobClient.DefaultAnimations[Name] + + local AnimationTrack = Animator:LoadAnimation(Animation) + AnimationTrack.Priority = Enum.AnimationPriority[Priority or "Core"] + + return AnimationTrack +end + +local function PerMob(MobInstance: Model) + if Mobs[MobInstance] then return end + + local Humanoid = safeWait(MobInstance, "Humanoid") :: Humanoid + local Root = safeWait(MobInstance, "HumanoidRootPart") :: BasePart + if not Humanoid or not Root then return end + + local Maid = Maid.new() + + -- 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] + Humanoid:SetStateEnabled(HumanoidStateType, false) + if Humanoid:GetState() == HumanoidStateType then + Humanoid:ChangeState(Enum.HumanoidStateType.Running) + end + end + + -- Animations / Behavior --------------------------------------------------- + + local AnimationTracks = { + Running = LoadAnimationTrack(MobInstance, "Running", "Core"), + Jumping = LoadAnimationTrack(MobInstance, "Jumping", "Movement"), + Hit = LoadAnimationTrack(MobInstance, "Hit", "Action") + } + + Maid:Add(Humanoid.Running:Connect(function(Speed) + if Speed > 0.01 then + local Percent = Speed/Humanoid.WalkSpeed + if not AnimationTracks.Running.IsPlaying then + AnimationTracks.Running:Play() + end + AnimationTracks.Running:AdjustSpeed(Percent) + else + AnimationTracks.Running:Stop() + end + end)) + + Maid:Add(Humanoid.Jumping:Connect(function() + AnimationTracks.Jumping.TimePosition = 0 + if not AnimationTracks.Jumping.IsPlaying then + AnimationTracks.Jumping:Play() + end + end)) + + -- Maid:Add(Humanoid.Died:Once(function() + -- Root:ApplyImpulse(-Root.CFrame.LookVector * Root.AssemblyMass*50) -- Ragdoll Impulse + -- end)) + + Maid:Add(Root:GetPropertyChangedSignal("Anchored"):Connect(function() + if Root.Anchored then + AnimationTracks.Running:Stop() + end + end)) + + -- TODO: 攻击动画之后要做延迟处理和攻速处理 + Maid:Add(Humanoid:GetAttributeChangedSignal("AttackState"):Connect(function() + if Humanoid:GetAttribute("AttackState") == "Hit" then + if AnimationTracks.Hit.IsPlaying then + AnimationTracks.Hit.TimePosition = 0 + else + AnimationTracks.Hit:Play() + end + else + AnimationTracks.Hit:Stop() + end + end)) + + local Mob = {} + Mob.Instance = MobInstance + Mob.AnimationTracks = AnimationTracks + Mobs[MobInstance] = Mob + + Maid:Add(MobInstance.Destroying:Connect(function() + Mobs[MobInstance] = nil + Maid:Destroy() + end)) +end + +CollectionService:GetInstanceAddedSignal("Mob"):Connect(PerMob) +for _, MobInstance in CollectionService:GetTagged("Mob") do + task.spawn(PerMob, MobInstance) +end + +return {} \ No newline at end of file diff --git a/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau b/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau index acf16c2..bff133f 100644 --- a/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau +++ b/src/StarterPlayerScripts/ClientMain/PerformanceClient/DamageBoard.luau @@ -35,8 +35,8 @@ end function DamageBoard:CreateNormalDamageBoard(DamageDetail: table) local DamageInitPos = DamageDetail.DamagePosition - local BiasPos = Vector3.new(0, 4, 0) - local MultPos = Vector3.new(0, 1.5, 0) + local BiasPos = Vector3.new(0, 6, 0) + local MultPos = Vector3.new(0, 1, 0) -- 根据元素类型排序 local DamageInfos = DamageDetail["DamageInfos"] diff --git a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau index 7ddccf5..f62a98c 100644 --- a/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau +++ b/src/StarterPlayerScripts/ClientMain/PerformanceClient/init.luau @@ -56,7 +56,6 @@ RE_PerformanceEvent.OnClientEvent:Connect(function(ServerTime: number, CastTag: local delayTime = ServerTime - tick() if CastTag == "Init" then - -- print("Init", BehaviourName) -- 暂时就新增一个表,不调用初始化,因为服务端不是多个new做的,而是多次调用 if not PerformanceClient.pData[UserId][BehaviourName] then PerformanceClient.pData[UserId][BehaviourName] = {} diff --git a/src/StarterPlayerScripts/ClientMain/PlayerControl.luau b/src/StarterPlayerScripts/ClientMain/PlayerControl.luau index 42588c4..464eb66 100644 --- a/src/StarterPlayerScripts/ClientMain/PlayerControl.luau +++ b/src/StarterPlayerScripts/ClientMain/PlayerControl.luau @@ -2,30 +2,57 @@ local UserInputService = game:GetService("UserInputService") local PlayerControl = {} --- 监听按键按下 -function PlayerControl.ListenMoveKeyDown(onKeyDown) - UserInputService.InputBegan:Connect(function(input, gameProcessed) - if gameProcessed then return end - if input.UserInputType == Enum.UserInputType.Keyboard then - local key = input.KeyCode - if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then - onKeyDown(key) - end - end - end) -end +-- 添加按键状态跟踪 +local pressedKeys = {} --- 监听按键松开 function PlayerControl.ListenMoveKeyUp(onKeyUp) UserInputService.InputEnded:Connect(function(input, gameProcessed) if gameProcessed then return end if input.UserInputType == Enum.UserInputType.Keyboard then local key = input.KeyCode if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then - onKeyUp(key) + pressedKeys[key] = nil -- 移除当前按键状态 + + -- 检查是否还有其他移动按键在按下 + local hasOtherKeysPressed = false + for k, v in pairs(pressedKeys) do + if k == Enum.KeyCode.W or k == Enum.KeyCode.A or k == Enum.KeyCode.S or k == Enum.KeyCode.D then + hasOtherKeysPressed = true + break + end + end + + -- 只有当没有其他移动按键按下时才调用onKeyUp + if not hasOtherKeysPressed then + onKeyUp(key) + end end end end) end +-- 添加按键按下监听 +function PlayerControl.ListenMoveKeyDown(onKeyDown) + UserInputService.InputBegan:Connect(function(input, gameProcessed) + if gameProcessed then return end + if input.UserInputType == Enum.UserInputType.Keyboard then + local key = input.KeyCode + if key == Enum.KeyCode.W or key == Enum.KeyCode.A or key == Enum.KeyCode.S or key == Enum.KeyCode.D then + pressedKeys[key] = true + onKeyDown(key) + end + end + end) +end + +-- 添加获取当前按下按键的函数 +function PlayerControl.GetPressedKeys() + return pressedKeys +end + +-- 添加检查特定按键是否按下的函数 +function PlayerControl.IsKeyPressed(keyCode) + return pressedKeys[keyCode] == true +end + return PlayerControl \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/UIManager.luau b/src/StarterPlayerScripts/UI/UIManager.luau index 0fb6a88..708b3f0 100644 --- a/src/StarterPlayerScripts/UI/UIManager.luau +++ b/src/StarterPlayerScripts/UI/UIManager.luau @@ -128,6 +128,14 @@ function UIManager:IsOpened(WindowName: string) return UIManager.Instances[WindowName] ~= nil end +function UIManager:SetData(WindowName: string, Data: table) + if not UIManager.Instances[WindowName] then + warn("UIManager:SetData() 窗口不存在:" .. WindowName) + return + end + UIManager.Instances[WindowName]:SetData(Data) +end + -- 获取当前窗口堆栈信息(用于调试) function UIManager:GetWindowStackInfo() local info = {} diff --git a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau index 050451d..ddbb2fe 100644 --- a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau +++ b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau @@ -62,14 +62,16 @@ function MainWindow:OnOpenWindow() table.insert(self.Connections, chaCon) table.insert(self.Connections, attributeUpgradeCon) - local playerDataFolder = Utils:WaitPlayerDataFolder(LocalPlayer) - local StatsFolder = playerDataFolder:WaitForChild("PlayerInfo"):WaitForChild("Stats") - local levelCon = StatsFolder.level.Changed:Connect(function(newValue) + -- TODO: 暂时用主关卡数显示,我记得之前这里主要是记录的,Challenge中才是正在挑战的内容 + -- TODO: 之后LevelProxy也应该挪到ReplicatedStorage下,之前可能是因为觉得Challenge是临时的内容所以放在workspace下,但是逻辑做的不统一 + local playerDataFolder = game.Workspace:WaitForChild("Level"):WaitForChild(LocalPlayer.UserId) + local StatsFolder = playerDataFolder:WaitForChild("Progress") + local levelCon = StatsFolder.Main.Changed:Connect(function(newValue) self:SetShowLevel(newValue) end) -- 初始值设置 - self:SetShowLevel(StatsFolder.level.Value) + self:SetShowLevel(StatsFolder.Main.Value) table.insert(self.Connections, levelCon) end