From 2d0690f24f459acb368820d8d4b73f538619a15b Mon Sep 17 00:00:00 2001 From: gechangfu Date: Fri, 15 Aug 2025 17:28:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0-=E5=9F=BA=E7=A1=80=E7=AC=A6?= =?UTF-8?q?=E6=96=87ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel/Rune.xlsx | Bin 10670 -> 10668 bytes excel/cha.xlsx | Bin 15288 -> 15291 bytes excel/item.xlsx | Bin 22865 -> 22981 bytes excel/language.xlsx | Bin 24856 -> 25029 bytes src/ReplicatedStorage/Base/UIWindow.luau | 2 +- src/ReplicatedStorage/Data/SignalEnum.luau | 1 + src/ReplicatedStorage/Json/Attributes.json | 3 +- .../Json/AttributesUpgrade.json | 2 +- src/ReplicatedStorage/Json/ItemProp.json | 4 +- .../Json/Language_En_US.json | 6 +- .../Json/Language_Zh_CN.json | 6 +- src/ReplicatedStorage/Json/Rune.json | 4 +- src/ReplicatedStorage/Tools/Utils.luau | 10 + src/ServerStorage/Proxy/ItemProxy.luau | 3 + src/ServerStorage/Proxy/RuneProxy/init.luau | 35 +- .../ClientMain/Helper.luau | 3 + .../UI/Common/EquipmentModelDetail.luau | 42 +++ .../UI/Windows/ChaWindow/PackageShow.luau | 23 +- .../UI/Windows/ChaWindow/WearingShow.luau | 22 +- .../CombineRuneWindow/PackageShow.luau | 54 ++++ .../CombineRuneWindow/WearingShow.luau | 60 ++++ .../UI/Windows/CombineRuneWindow/init.luau | 300 ++++++++++++++++++ .../UI/Windows/CreateWindow/WeaponItem.luau | 19 +- .../UI/Windows/CreateWindow/init.luau | 19 ++ .../Windows/EquipmentDetailWindow/init.luau | 16 +- .../GetEquipmentsWindow/EquipmentShow.luau | 35 +- .../UI/Windows/MainWindow/init.luau | 5 + .../UI/Windows/RuneWindow/PackageShow.luau | 2 +- .../UI/Windows/RuneWindow/init.luau | 169 +++++++--- .../SelectEquipmentWindow/PackageShow.luau | 81 +++++ .../Windows/SelectEquipmentWindow/init.luau | 78 +++++ 31 files changed, 859 insertions(+), 145 deletions(-) create mode 100644 src/StarterPlayerScripts/UI/Common/EquipmentModelDetail.luau create mode 100644 src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/PackageShow.luau create mode 100644 src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/WearingShow.luau create mode 100644 src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/init.luau create mode 100644 src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/PackageShow.luau create mode 100644 src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/init.luau diff --git a/excel/Rune.xlsx b/excel/Rune.xlsx index 73fe2004fc5cc4821d3cb0b186b322a4341251dc..7198895555795868112892192dbd11651afb30cf 100644 GIT binary patch delta 1854 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 MDI*3LCjbBd0Lnjr7XSbN diff --git a/excel/cha.xlsx b/excel/cha.xlsx index 241945073d312b24a9776a628de064884eb2be0e..bb9ab029a88c82e587a6a9f5c23d0d94126a704b 100644 GIT binary patch delta 1960 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 C-FHv` delta 8492 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 diff --git a/excel/language.xlsx b/excel/language.xlsx index f2a2d9c8f48b3ca31c1275f98d4fbe6ecfe1401d..593768c686e8de346653575f53fac530826ea220 100644 GIT binary patch delta 10077 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 H>+$~p09?9Y diff --git a/src/ReplicatedStorage/Base/UIWindow.luau b/src/ReplicatedStorage/Base/UIWindow.luau index 9db46c1..8510c96 100644 --- a/src/ReplicatedStorage/Base/UIWindow.luau +++ b/src/ReplicatedStorage/Base/UIWindow.luau @@ -70,7 +70,7 @@ function UIWindow:AutoInjectVariables() end if not target then - error("自动注入失败:未找到UI节点 " .. varName) + error("自动注入失败:未找到UI节点 "..self.UIRootName.." "..varName) end self.Variables[varName] = target diff --git a/src/ReplicatedStorage/Data/SignalEnum.luau b/src/ReplicatedStorage/Data/SignalEnum.luau index 92e1547..c5c5217 100644 --- a/src/ReplicatedStorage/Data/SignalEnum.luau +++ b/src/ReplicatedStorage/Data/SignalEnum.luau @@ -3,6 +3,7 @@ local SignalEnum = {} SignalEnum = { SHOW_ABILITY = "SHOW_ABILITY", CHALLENGE_LEVEL_END = "CHALLENGE_LEVEL_END", + SELECT_RUNE_INLAY_EQUIPMENT = "SELECT_RUNE_INLAY_EQUIPMENT", } return SignalEnum \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Attributes.json b/src/ReplicatedStorage/Json/Attributes.json index 2ad9f6e..1786872 100644 --- a/src/ReplicatedStorage/Json/Attributes.json +++ b/src/ReplicatedStorage/Json/Attributes.json @@ -29,5 +29,6 @@ {"id":52,"type":1,"specialType":null,"effectAttribute":"extraAttributeNumber","battleValue":[1,10],"iconId":28,"nameId":252}, {"id":53,"type":1,"specialType":null,"effectAttribute":"elementNumber","battleValue":[1,10],"iconId":29,"nameId":253}, {"id":54,"type":1,"specialType":null,"effectAttribute":"elementDefNumber","battleValue":[1,10],"iconId":30,"nameId":254}, -{"id":55,"type":1,"specialType":null,"effectAttribute":"gemNumber","battleValue":[1,10],"iconId":31,"nameId":255} +{"id":55,"type":1,"specialType":null,"effectAttribute":"gemNumber","battleValue":[1,10],"iconId":31,"nameId":255}, +{"id":56,"type":1,"specialType":null,"effectAttribute":"runeNumber","battleValue":[1,10],"iconId":31,"nameId":256} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/AttributesUpgrade.json b/src/ReplicatedStorage/Json/AttributesUpgrade.json index 5a73d9f..020bcfb 100644 --- a/src/ReplicatedStorage/Json/AttributesUpgrade.json +++ b/src/ReplicatedStorage/Json/AttributesUpgrade.json @@ -9,5 +9,5 @@ {"id":12,"type":2,"effectAttribute":"extraAttributeNumber","cost":[1,300,0],"lvAdd":[1,1],"battleValueLimit":[5,20],"maxLv":3}, {"id":13,"type":2,"effectAttribute":"elementNumber","cost":[1,300,0],"lvAdd":[1,1],"battleValueLimit":[5,20],"maxLv":3}, {"id":14,"type":2,"effectAttribute":"elementDefNumber","cost":[1,300,0],"lvAdd":[1,1],"battleValueLimit":[5,20],"maxLv":3}, -{"id":15,"type":2,"effectAttribute":"gemNumber","cost":[1,300,0],"lvAdd":[1,1],"battleValueLimit":[5,20],"maxLv":3} +{"id":15,"type":2,"effectAttribute":"runeNumber","cost":[1,300,0],"lvAdd":[1,1],"battleValueLimit":[5,20],"maxLv":4} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/ItemProp.json b/src/ReplicatedStorage/Json/ItemProp.json index c5bd145..19221b5 100644 --- a/src/ReplicatedStorage/Json/ItemProp.json +++ b/src/ReplicatedStorage/Json/ItemProp.json @@ -96,5 +96,7 @@ {"id":50013,"type":3,"typeArgs":[],"quality":14,"iconId":14,"nameId":50013,"textId":70013,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null}, {"id":50014,"type":3,"typeArgs":[],"quality":15,"iconId":15,"nameId":50014,"textId":70014,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null}, {"id":50015,"type":3,"typeArgs":[],"quality":16,"iconId":16,"nameId":50015,"textId":70015,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null}, -{"id":60000,"type":7,"typeArgs":[],"quality":1,"iconId":60000,"nameId":60000,"textId":80000,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null} +{"id":60000,"type":7,"typeArgs":[],"quality":4,"iconId":60000,"nameId":60000,"textId":80000,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null}, +{"id":61000,"type":7,"typeArgs":[],"quality":5,"iconId":61000,"nameId":61000,"textId":61000,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null}, +{"id":62000,"type":7,"typeArgs":[],"quality":6,"iconId":62000,"nameId":62000,"textId":62000,"buyPrice":[],"sellPrice":[],"use":[],"showPackage":null} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Language_En_US.json b/src/ReplicatedStorage/Json/Language_En_US.json index 7d76d47..64793e2 100644 --- a/src/ReplicatedStorage/Json/Language_En_US.json +++ b/src/ReplicatedStorage/Json/Language_En_US.json @@ -45,6 +45,7 @@ {"id":1011,"text":"祝福:出现{0}属性的概率+{1}"}, {"id":1012,"text":"祝福:出现{0}技能的概率+{1}"}, {"id":1013,"text":"祝福:出现{0}晶石的概率+{1}"}, +{"id":1200,"text":"该装备无符文槽位"}, {"id":20000,"text":"普攻"}, {"id":20001,"text":"剑气"}, {"id":40000,"text":"测试装备1"}, @@ -62,5 +63,8 @@ {"id":40012,"text":"测试装备13"}, {"id":40013,"text":"测试装备14"}, {"id":40014,"text":"测试装备15"}, -{"id":40015,"text":"测试装备16"} +{"id":40015,"text":"测试装备16"}, +{"id":60000,"text":"火焰伤害符文"}, +{"id":61000,"text":"冰棺符文"}, +{"id":62000,"text":"测试符文"} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Language_Zh_CN.json b/src/ReplicatedStorage/Json/Language_Zh_CN.json index 7d76d47..64793e2 100644 --- a/src/ReplicatedStorage/Json/Language_Zh_CN.json +++ b/src/ReplicatedStorage/Json/Language_Zh_CN.json @@ -45,6 +45,7 @@ {"id":1011,"text":"祝福:出现{0}属性的概率+{1}"}, {"id":1012,"text":"祝福:出现{0}技能的概率+{1}"}, {"id":1013,"text":"祝福:出现{0}晶石的概率+{1}"}, +{"id":1200,"text":"该装备无符文槽位"}, {"id":20000,"text":"普攻"}, {"id":20001,"text":"剑气"}, {"id":40000,"text":"测试装备1"}, @@ -62,5 +63,8 @@ {"id":40012,"text":"测试装备13"}, {"id":40013,"text":"测试装备14"}, {"id":40014,"text":"测试装备15"}, -{"id":40015,"text":"测试装备16"} +{"id":40015,"text":"测试装备16"}, +{"id":60000,"text":"火焰伤害符文"}, +{"id":61000,"text":"冰棺符文"}, +{"id":62000,"text":"测试符文"} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Json/Rune.json b/src/ReplicatedStorage/Json/Rune.json index 2da4fae..320bce9 100644 --- a/src/ReplicatedStorage/Json/Rune.json +++ b/src/ReplicatedStorage/Json/Rune.json @@ -1,3 +1,5 @@ [ -{"id":60000,"type":1,"icon":1,"nameId":20000,"behaviourName":"Attack","recycle":[]} +{"id":60000,"quality":1,"type":1,"icon":1,"nameId":60000,"runeName":"RuneFireDamage","behaviorName":null,"recycle":[],"isInPool":1}, +{"id":61000,"quality":2,"type":1,"icon":1,"nameId":61000,"runeName":"RuneIceCoffin","behaviorName":"IceCoffin","recycle":[],"isInPool":1}, +{"id":62000,"quality":3,"type":1,"icon":1,"nameId":62000,"runeName":null,"behaviorName":null,"recycle":[],"isInPool":1} ] \ No newline at end of file diff --git a/src/ReplicatedStorage/Tools/Utils.luau b/src/ReplicatedStorage/Tools/Utils.luau index 4ed273f..4798de8 100644 --- a/src/ReplicatedStorage/Tools/Utils.luau +++ b/src/ReplicatedStorage/Tools/Utils.luau @@ -50,6 +50,16 @@ function Utils:GenUniqueId(t: table) return min_id end + +function Utils:GenUniqueIdString(t: table) + local min_id = 1 + while t[tostring(min_id)] ~= nil do + min_id = min_id + 1 + end + return tostring(min_id) +end + + function Utils:GenUniqueIdPlayerAI(LastTime: number, Counter: number) local now = os.time() -- 或 tick(),精度更高 if now ~= LastTime then diff --git a/src/ServerStorage/Proxy/ItemProxy.luau b/src/ServerStorage/Proxy/ItemProxy.luau index 971daad..ea20f23 100644 --- a/src/ServerStorage/Proxy/ItemProxy.luau +++ b/src/ServerStorage/Proxy/ItemProxy.luau @@ -28,6 +28,9 @@ function ItemProxy:AddItem(Player: Player, ItemId: number, ItemCount: number) elseif ItemData.type == 3 then local BookProxy = require(script.Parent.BookProxy) BookProxy:UnlockBook(Player, ItemId - 10000) + elseif ItemData.type == 7 then + local RuneProxy = require(script.Parent.RuneProxy) + RuneProxy:AddRune(Player, ItemId) else PlayerInfoProxy:ChangeItemCount(Player, ItemId, ItemCount) end diff --git a/src/ServerStorage/Proxy/RuneProxy/init.luau b/src/ServerStorage/Proxy/RuneProxy/init.luau index d7a5596..e0d43f2 100644 --- a/src/ServerStorage/Proxy/RuneProxy/init.luau +++ b/src/ServerStorage/Proxy/RuneProxy/init.luau @@ -17,6 +17,8 @@ local JsonRune = require(ReplicatedStorage.Json.Rune) --> Events local RE_PlayerTip = ReplicatedStorage.Events.RE_PlayerTip local RE_RuneInlay = ReplicatedStorage.Events.RE_RuneInlay +local RE_RuneCombine = ReplicatedStorage.Events.RE_RuneCombine + --> SubFunctions local RuneCalculation = require(script.RuneCalculation) @@ -65,7 +67,7 @@ function RuneProxy:AddRune(Player: Player, RuneId: number) local RuneData = Utils:GetIdDataFromJson(JsonRune, RuneId) if not RuneData then return end - local UniqueId = Utils:GenUniqueId(ArchiveProxy.pData[Player.UserId][STORE_NAME]) + local UniqueId = Utils:GenUniqueIdString(ArchiveProxy.pData[Player.UserId][STORE_NAME]) -- 配置表内容 local ResultData = {} for key, value in pairs(RuneData) do @@ -86,11 +88,22 @@ function RuneProxy:AddRune(Player: Player, RuneId: number) end -- 合成符文 -function RuneProxy:CombineRune(Player: Player, RuneData: table) +function RuneProxy:CombineRune(Player: Player, RuneIds: table) local pData = Utils:GetPlayerDataFolder(Player) if not pData then return end - if #RuneData ~= 3 then warn('符文合成数量不正确', Player.Name, RuneData) return end + if #RuneIds ~= 3 then warn('符文合成数量不正确', Player.Name, RuneIds) return end + + -- 查看符文id是否相同,相同则返回 + if RuneIds[1] == RuneIds[2] and RuneIds[2] == RuneIds[3] then warn('符文id相同', Player.Name, RuneIds) return end + + local RuneData = {} + for _, RuneId in RuneIds do + RuneId = tostring(RuneId) + local RuneInstance = GetPlayerRuneFolder(Player):FindFirstChild(RuneId) + if not RuneInstance then warn('符文实例不存在', Player.Name, RuneId) return end + table.insert(RuneData, RuneInstance:GetAttributes()) + end -- 获取合成符文数据 local sameQuality = true @@ -110,20 +123,24 @@ function RuneProxy:CombineRune(Player: Player, RuneData: table) -- 添加合成符文 local newRuneId = self:GetRandomRuneIdByQuality(higherQuality) if not newRuneId then warn('合成符文不存在', Player.Name, RuneData) return end - self:AddRune(Player, newRuneId) + local newRuneData, newRuneInstance = self:AddRune(Player, newRuneId) -- 销毁符文 for _, rune in RuneData do self:DestroyRune(Player, rune.id) end + + -- 发送给前端结果信息 + RE_RuneCombine:FireClient(Player, newRuneData.id, newRuneData.orgId) end -- 销毁符文 function RuneProxy:DestroyRune(Player: Player, UniqueId: number) local pData = Utils:GetPlayerDataFolder(Player) if not pData then return end + UniqueId = tostring(UniqueId) -- 获取符文实例存储数据 - local UniqueData = ArchiveProxy.pData[Player.UserId][UniqueId] - if not UniqueData then return end + local UniqueData = ArchiveProxy.pData[Player.UserId][STORE_NAME][UniqueId] + if not UniqueData then warn('UniqueData不存在', Player.Name, UniqueId) return end -- 检查是否有符文实例 local RuneInstance = GetPlayerRuneFolder(Player):FindFirstChild(UniqueId) @@ -131,7 +148,7 @@ function RuneProxy:DestroyRune(Player: Player, UniqueId: number) -- 销毁 RuneInstance:Destroy() - ArchiveProxy.pData[Player.UserId][UniqueId] = nil + ArchiveProxy.pData[Player.UserId][STORE_NAME][UniqueId] = nil end -- 穿戴符文 @@ -292,4 +309,8 @@ RE_RuneInlay.OnServerEvent:Connect(function(Player: Player, RuneUniqueId: number RuneProxy:WearRune(Player, RuneUniqueId, EquipmentUniqueId) end) +RE_RuneCombine.OnServerEvent:Connect(function(Player: Player, RuneData: table) + RuneProxy:CombineRune(Player, RuneData) +end) + return RuneProxy \ No newline at end of file diff --git a/src/StarterPlayerScripts/ClientMain/Helper.luau b/src/StarterPlayerScripts/ClientMain/Helper.luau index a4b0056..0812cca 100644 --- a/src/StarterPlayerScripts/ClientMain/Helper.luau +++ b/src/StarterPlayerScripts/ClientMain/Helper.luau @@ -22,6 +22,9 @@ UserInputService.InputBegan:Connect(function(input, gameProcessed) elseif input.KeyCode == Enum.KeyCode.K then -- RE_UpgradeAttributes:FireServer(1) RE_PlayerHelper:FireServer("AddItem", {2, 1000}) + RE_PlayerHelper:FireServer("AddItem", {60000, 1}) + RE_PlayerHelper:FireServer("AddItem", {61000, 1}) + RE_PlayerHelper:FireServer("AddItem", {62000, 1}) elseif input.KeyCode == Enum.KeyCode.L then RE_UpgradeAttributes:FireServer(2) elseif input.KeyCode == Enum.KeyCode.M then diff --git a/src/StarterPlayerScripts/UI/Common/EquipmentModelDetail.luau b/src/StarterPlayerScripts/UI/Common/EquipmentModelDetail.luau new file mode 100644 index 0000000..790813c --- /dev/null +++ b/src/StarterPlayerScripts/UI/Common/EquipmentModelDetail.luau @@ -0,0 +1,42 @@ +local EquipmentModelDetail = {} + +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Json +local JsonEquipment = require(ReplicatedStorage.Json.Equipment) + +--> Components +local Utils = require(ReplicatedStorage.Tools.Utils) + +--> Prefabs +local FolderEquipmentPrefabs = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") + +function EquipmentModelDetail:ShowDetail(prefab: Instance, orgId: number, isRotation: boolean?) + -- 模型展示 + local equipmentData = Utils:GetIdDataFromJson(JsonEquipment, orgId) + local part = FolderEquipmentPrefabs:FindFirstChild(equipmentData.modelName):Clone() + part.Handle.Position = Vector3.new(0, 0, 0) + part.Handle.CFrame = CFrame.new(0, 0, 0) * CFrame.Angles(math.rad(90), 0, 0) + part.Parent = prefab + + -- 相机 + local viewportCamera = Instance.new("Camera") + prefab.CurrentCamera = viewportCamera + viewportCamera.Parent = prefab + viewportCamera.CFrame = CFrame.new(Vector3.new(0, 0, 6), part.Handle.Position) + + local taskRotation = nil + if isRotation then + taskRotation = task.spawn(function() + while true do + part.Handle.CFrame = part.Handle.CFrame * CFrame.Angles(0, 0, 0.01) + task.wait(0.01) + end + end) + end + + return taskRotation, part, viewportCamera +end + +return EquipmentModelDetail \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/ChaWindow/PackageShow.luau b/src/StarterPlayerScripts/UI/Windows/ChaWindow/PackageShow.luau index 874f2cc..8be550f 100644 --- a/src/StarterPlayerScripts/UI/Windows/ChaWindow/PackageShow.luau +++ b/src/StarterPlayerScripts/UI/Windows/ChaWindow/PackageShow.luau @@ -10,6 +10,13 @@ local JsonItemProp = require(ReplicatedStorage.Json.ItemProp) local FolderEquipment = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") +--> Variables +local LocalPlayer = game.Players.LocalPlayer + +--> Components +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + function PackageShow:Init(data: table) local self = {} self.Data = data @@ -34,20 +41,8 @@ function PackageShow:Refresh() self.Variables._tmpQuality.Text = Localization:GetColoredEquipmentQualityDesc(self.Data.quality) self.Variables._tmpName.Text = Localization:GetLanguageData(itemData.nameId) - local equipmentData = Utils:GetIdDataFromJson(JsonEquipment, self.Data.orgId) - -- 模型 - local part = FolderEquipment:FindFirstChild(equipmentData.modelName):Clone() - part.Handle.Position = Vector3.new(0, 0, 0) - part.Handle.CFrame = CFrame.new(0, 0, 0) * CFrame.Angles(math.rad(90), 0, 0) - part.Parent = self.Variables["_imgView"] - self.Prefab = part - - -- 相机 - local viewportCamera = Instance.new("Camera") - self.Variables["_imgView"].CurrentCamera = viewportCamera - viewportCamera.Parent = self.Variables["_imgView"] - viewportCamera.CFrame = CFrame.new(Vector3.new(0, 0, 6), part.Handle.Position) - self.ViewCamera = viewportCamera + -- 模型展示 + EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], self.Data.orgId, false) end function PackageShow:OnInitFinish() diff --git a/src/StarterPlayerScripts/UI/Windows/ChaWindow/WearingShow.luau b/src/StarterPlayerScripts/UI/Windows/ChaWindow/WearingShow.luau index 0367b0b..a02bda6 100644 --- a/src/StarterPlayerScripts/UI/Windows/ChaWindow/WearingShow.luau +++ b/src/StarterPlayerScripts/UI/Windows/ChaWindow/WearingShow.luau @@ -10,6 +10,11 @@ local JsonItemProp = require(ReplicatedStorage.Json.ItemProp) local FolderEquipment = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") +local LocalPlayer = game.Players.LocalPlayer + +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + function WearingShow:Init(data: table) local self = {} self.Data = data @@ -43,21 +48,8 @@ function WearingShow:Refresh() self.Variables._tmpQuality.Text = Localization:GetColoredEquipmentQualityDesc(self.Data.quality) self.Variables._tmpName.Text = Localization:GetLanguageData(itemData.nameId) - - local equipmentData = Utils:GetIdDataFromJson(JsonEquipment, self.Data.orgId) - -- 模型 - local part = FolderEquipment:FindFirstChild(equipmentData.modelName):Clone() - part.Handle.Position = Vector3.new(0, 0, 0) - part.Handle.CFrame = CFrame.new(0, 0, 0) * CFrame.Angles(math.rad(90), 0, 0) - part.Parent = self.Variables["_imgView"] - self.Prefab = part - - -- 相机 - local viewportCamera = Instance.new("Camera") - self.Variables["_imgView"].CurrentCamera = viewportCamera - viewportCamera.Parent = self.Variables["_imgView"] - viewportCamera.CFrame = CFrame.new(Vector3.new(0, 0, 6), part.Handle.Position) - self.ViewCamera = viewportCamera + -- 模型展示 + EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], self.Data.orgId, false) end end diff --git a/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/PackageShow.luau b/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/PackageShow.luau new file mode 100644 index 0000000..6074749 --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/PackageShow.luau @@ -0,0 +1,54 @@ +local PackageShow = {} +PackageShow.__index = PackageShow + +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Utils = require(ReplicatedStorage.Tools.Utils) +local Localization = require(ReplicatedStorage.Tools.Localization) +local JsonRune = require(ReplicatedStorage.Json.Rune) +local JsonItemProp = require(ReplicatedStorage.Json.ItemProp) + +function PackageShow:Init(data: table) + local self = {} + self.Data = data + self.Variables = { + ["_btnClick"] = 0, + ["_imgIcon"] = 0, + ["_tmpName"] = 0, + ["_imgView"] = 0, + } + self.Connections = {} + + setmetatable(self, PackageShow) + + return self +end + +function PackageShow:Refresh() + local itemData = Utils:GetIdDataFromJson(JsonItemProp, self.Data.orgId) + local runeData = Utils:GetIdDataFromJson(JsonRune, self.Data.orgId) + + self.Variables._imgIcon.Image = Localization:GetImageData(runeData.icon) + self.Variables._imgIcon.BackgroundColor3 = Localization:GetRuneQualityBgColor(runeData.quality) + self.Variables._tmpName.Text = Localization:GetLanguageData(itemData.nameId) +end + +function PackageShow:OnInitFinish() + local con = self.Variables._btnClick.MouseButton1Click:Connect(function() + if self.Data == {} then + -- TODO: 之后做提示弹窗 + else + self.TopUI:WearRefresh(self.Data) + end + end) + table.insert(self.Connections, con) +end + +function PackageShow:Destroy() + for k, v in pairs(self) do + self[k] = nil + end + self = nil +end + +return PackageShow \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/WearingShow.luau b/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/WearingShow.luau new file mode 100644 index 0000000..6883c94 --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/WearingShow.luau @@ -0,0 +1,60 @@ +local WearingShow = {} +WearingShow.__index = WearingShow + +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Utils = require(ReplicatedStorage.Tools.Utils) +local Localization = require(ReplicatedStorage.Tools.Localization) +local JsonRune = require(ReplicatedStorage.Json.Rune) +local JsonItemProp = require(ReplicatedStorage.Json.ItemProp) + +function WearingShow:Init(data: table) + local self = {} + self.Data = data + self.Variables = { + ["_btnClick"] = 0, + ["_imgIcon"] = 0, + ["_tmpName"] = 0, + ["_imgView"] = 0, + } + self.Connections = {} + + setmetatable(self, WearingShow) + + return self +end + +function WearingShow:Refresh() + if self.Data.instance then + local itemData = Utils:GetIdDataFromJson(JsonItemProp, self.Data.orgId) + local runeData = Utils:GetIdDataFromJson(JsonRune, self.Data.orgId) + + self.Variables._imgIcon.Image = Localization:GetImageData(runeData.icon) + self.Variables._imgIcon.BackgroundColor3 = Localization:GetRuneQualityBgColor(runeData.quality) + self.Variables._tmpName.Text = Localization:GetLanguageData(itemData.nameId) + self.Variables._imgIcon.Visible = true + else + self.Variables._imgIcon.Image = "" + self.Variables._tmpName.Text = "" + self.Variables._imgIcon.Visible = false + end +end + +function WearingShow:OnInitFinish() + local con = self.Variables._btnClick.MouseButton1Click:Connect(function() + if self.Data.instance then + self.TopUI:UnwearRefresh(self.Data) + end + end) + table.insert(self.Connections, con) +end + +function WearingShow:Destroy() + print("Destroy WearingShow") + for k, v in pairs(self) do + self[k] = nil + end + self = nil +end + +return WearingShow \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/init.luau new file mode 100644 index 0000000..74f51e1 --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/CombineRuneWindow/init.luau @@ -0,0 +1,300 @@ +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local UIWindow = require(ReplicatedStorage.Base.UIWindow) +local UIEnums = require(ReplicatedStorage.Base.UIEnums) + +--> Components +local WearingShow = require(script.WearingShow) +local PackageShow = require(script.PackageShow) +--> Dependencies +local Utils = require(ReplicatedStorage.Tools.Utils) +local Localization = require(ReplicatedStorage.Tools.Localization) + +--> Json +local JsonAttributesUpgrade = require(ReplicatedStorage.Json.AttributesUpgrade) +local JsonRune = require(ReplicatedStorage.Json.Rune) +local JsonEquipment = require(ReplicatedStorage.Json.Equipment) + +--> Events +local RE_RuneCombine = ReplicatedStorage.Events.RE_RuneCombine + +--> Variables +local LocalPlayer = game.Players.LocalPlayer + +--> Prefabs +local FolderEquipmentPrefabs = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") +local EquipmentFolder = Utils:WaitPlayerDataFolder(LocalPlayer):WaitForChild("Equipment") +local DataFolder = Utils:WaitPlayerDataFolder(LocalPlayer):WaitForChild("Rune") + +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + +-------------------------------------------------------------------------------- + +local CombineRuneWindow = {} +CombineRuneWindow.__index = CombineRuneWindow +setmetatable(CombineRuneWindow, {__index = UIWindow}) + +local MAX_RUNE_COMBINE_NUMBER = 3 + +function CombineRuneWindow:Init(UIManager: table, Data: table?) + local self = UIWindow:Init(UIManager, Data) + setmetatable(self, CombineRuneWindow) + self.Variables = { + ["_goRunePanel"] = 0, + ["__listRunePackage"] = 0, + ["__listRuneCombine"] = 0, + ["_tmpCombatValue"] = 0, + ["_imgIcon"] = 0, + + ["_bgNewSelectFrame"] = 0, + ["_imgNewRune"] = 0, + ["_tmpNewRuneName"] = 0, + + ["_btnClose"] = 0, + ["_btnBgClose"] = 0, + ["_btnCombineRune"] = 0, + } + self.UIRootName = "ui_w_combine_rune" + self.UIParentName = UIEnums.UIParent.UIRoot + + return self +end + +function CombineRuneWindow:ShowCombineRuneResult(uniqueId: number, orgId: number) + if uniqueId then + local runeData = Utils:GetIdDataFromJson(JsonRune, orgId) + self.Variables["_tmpNewRuneName"].Text = Localization:GetLanguageData(runeData.nameId) + self.Variables["_imgNewRune"].Image = Localization:GetImageData(runeData.icon) + self.Variables["_imgNewRune"].BackgroundColor3 = Localization:GetRuneQualityBgColor(runeData.quality) + self.Variables["_imgNewRune"].Transparency = 0 + else + self.NowSelectRuneId = nil + self.Variables["_tmpNewRuneName"].Text = "" + self.Variables["_imgNewRune"].Image = "" + self.Variables["_imgNewRune"].BackgroundColor3 = Color3.fromRGB(0, 0, 0) + self.Variables["_imgNewRune"].Transparency = 1 + end +end + +function CombineRuneWindow:WearRefresh(data: table) + -- 清除之前的显示内容 + self:ShowCombineRuneResult() + + -- 选择符文排查 + local newSlot = self:GetEmptySlot() + if newSlot == 0 then warn("没有空槽位") return end + local isExist, isNotSameQuality = false, false + for i = 1, MAX_RUNE_COMBINE_NUMBER do + if self.Data.SelectRune["slot"..i].instance == data.instance then + isExist = true + break + elseif self.Data.SelectRune["slot"..i].quality then + if self.Data.SelectRune["slot"..i].quality ~= data.quality then + isNotSameQuality = true + break + end + end + end + if isExist then warn("槽位上已经存在该符文") return end + if isNotSameQuality then warn("符文品质不同无法穿戴") return end + + -- 是否为相同品质且品质<3的符文 + if data.quality >= 3 then warn("品质大于等于3的符文不能穿戴") return end + self.Variables["__listRuneCombine"]:RemoveData("slot"..newSlot) + self.Variables["__listRunePackage"]:RemoveData(tostring(data.id)) + + data.wearingSlot = newSlot + self.Variables["__listRuneCombine"]:AddData("slot"..newSlot, data) +end + +function CombineRuneWindow:UnwearRefresh(data: table) + local oldSlot = data.wearingSlot + self.Variables["__listRuneCombine"]:RemoveData("slot"..oldSlot) + self.Variables["__listRunePackage"]:AddData(tostring(data.id), data) + self.Data.SelectRune["slot"..oldSlot] = { + wearingSlot = oldSlot, + } + self.Variables["__listRuneCombine"]:AddData("slot"..oldSlot, self.Data.SelectRune["slot"..oldSlot]) +end + +function CombineRuneWindow:CombineRune() + local combineRuneIds = {} + for i = 1, MAX_RUNE_COMBINE_NUMBER do + if not self.Data.SelectRune["slot"..i].instance then + warn("符文槽位"..i.."为空") + return + else + table.insert(combineRuneIds, self.Data.SelectRune["slot"..i].id) + end + end + RE_RuneCombine:FireServer(combineRuneIds) +end + +function CombineRuneWindow:GetEmptySlot() + for i = 1, MAX_RUNE_COMBINE_NUMBER do + if self.Data.SelectRune["slot"..i].instance == nil then + return i + end + end + return 0 +end + +function CombineRuneWindow:AddInstanceData(configInstance: Instance, Data: table?, isSelect: boolean?) + local data = self.Data + if Data then data = Data end + local attributes = configInstance:GetAttributes() + + -- 归类是否是穿戴的装备 + local parentName, secondName = "PackageRune", configInstance.Name + if attributes.wearing == self.ShowEquipmentId then + parentName = "SelectRune" + secondName = "slot"..attributes.wearingSlot + end + if isSelect then + parentName = "SelectRune" + -- 是否有空槽位判断 + local notHasEmptySlot = true + for i = 1, MAX_RUNE_COMBINE_NUMBER do + if self.Data.SelectRune["slot"..i].instance == nil then + notHasEmptySlot = false + secondName = "slot"..i + break + end + end + if notHasEmptySlot then warn("没有空槽位") return end + end + data[parentName][secondName] = {} + + for attributeKey, attributeValue in attributes do + data[parentName][secondName][attributeKey] = attributeValue + end + data[parentName][secondName].instance = configInstance + return data[parentName][secondName], parentName +end + +function CombineRuneWindow:RemoveInstanceData(configInstance: Instance, Data: table?) + for key, data in pairs(self.Data.SelectRune) do + if data.instance == configInstance then + return tostring(key), "SelectRune" + end + end + for key, data in pairs(self.Data.PackageRune) do + if data.instance == configInstance then + return tostring(key), "PackageRune" + end + end + return nil +end + +function CombineRuneWindow:CleanSelectData() + for i = 1, MAX_RUNE_COMBINE_NUMBER do + self.Data.SelectRune["slot" .. i] = { + wearingSlot = i, + wearing = 0, + } + end +end + +function CombineRuneWindow:ShowWearingSlot() + -- 清除旧数据 + if self.Data then + if self.Data.SelectRune then + self.Data.SelectRune = nil + end + if self.Data.PackageRune then + self.Data.PackageRune = nil + end + end + self.Data = { + SelectRune = {}, + PackageRune = {}, + } + + -- 添加符文数据 + for _, child in DataFolder:GetChildren() do + self:AddInstanceData(child, self.Data) + end + -- 补充空槽位 + for i = 1, MAX_RUNE_COMBINE_NUMBER do + local isExist = false + for k, data in pairs(self.Data.SelectRune) do + if data.wearingSlot == 0 and data.wearing == 0 then + isExist = true + break + end + end + if not isExist then + self.Data.SelectRune["slot" .. i] = { + wearingSlot = i, + wearing = 0, + } + end + end + + self:SetData(self.Data) + self.Variables["__listRunePackage"]:SetData(self.Data.PackageRune) + self.Variables["__listRuneCombine"]:SetData(self.Data.SelectRune) +end + +function CombineRuneWindow:OnOpenWindow() + UIWindow.OnOpenWindow(self) + self.Variables["__listRunePackage"]:AddComponent(PackageShow) + self.Variables["__listRuneCombine"]:AddComponent(WearingShow) + + local bgCloseCon = self.Variables["_btnBgClose"].Activated:Connect(function() + self.UIManager:CloseWindow(script.Name) + end) + local closeCon = self.Variables["_btnClose"].Activated:Connect(function() + self.UIManager:CloseWindow(script.Name) + end) + + table.insert(self.Connections, bgCloseCon) + table.insert(self.Connections, closeCon) + + self:ShowWearingSlot() + + local addCon = DataFolder.ChildAdded:Connect(function(child) + local data, parentName = self:AddInstanceData(child, self.Data) + if parentName == "SelectRune" then + self.Variables["__listRuneCombine"]:AddData("slot"..data.wearingSlot, data) + else + self.Variables["__listRunePackage"]:AddData(child.Name, data) + end + end) + + local removeCon = DataFolder.ChildRemoved:Connect(function(child) + -- TODO: 这里清除逻辑不清晰,之后优化 + local key, parentName = self:RemoveInstanceData(child, self.Data) + if parentName == "SelectRune" then + local removeIndex = self.Variables["__listRuneCombine"]:RemoveData(key) + else + local removeIndex = self.Variables["__listRunePackage"]:RemoveData(key) + end + end) + + table.insert(self.Connections, addCon) + table.insert(self.Connections, removeCon) + + self.Variables["__listRuneCombine"]:SetLayoutOrder("wearingSlot") + + local combineCon = self.Variables["_btnCombineRune"].Activated:Connect(function() + self:CombineRune() + end) + table.insert(self.Connections, combineCon) + + local combineResultCon = RE_RuneCombine.OnClientEvent:Connect(function(uniqueId, orgId) + self:ShowCombineRuneResult(uniqueId, orgId) + end) + table.insert(self.Connections, combineResultCon) +end + +function CombineRuneWindow:OnCloseWindow() + UIWindow.OnCloseWindow(self) +end + + + +return CombineRuneWindow \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/CreateWindow/WeaponItem.luau b/src/StarterPlayerScripts/UI/Windows/CreateWindow/WeaponItem.luau index f5ea701..24e0933 100644 --- a/src/StarterPlayerScripts/UI/Windows/CreateWindow/WeaponItem.luau +++ b/src/StarterPlayerScripts/UI/Windows/CreateWindow/WeaponItem.luau @@ -12,6 +12,10 @@ local JsonEquipment = require(ReplicatedStorage.Json.Equipment) local FolderEquipment = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") +local LocalPlayer = game.Players.LocalPlayer +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + function WeaponItem:Init(data: table) local self = {} self.Data = data @@ -40,21 +44,10 @@ function WeaponItem:Refresh() self.Variables._tmpName.Text = Localization:GetLanguageData(itemData.nameId) self.Variables._tmpQuality.Text = Localization:GetColoredEquipmentQualityDesc(self.Data.Quality) - local equipmentData = Utils:GetIdDataFromJson(JsonEquipment, self.Data.OrgId) - -- 模型 - local part = FolderEquipment:FindFirstChild(equipmentData.modelName):Clone() - part.Handle.Position = Vector3.new(0, 0, 0) - part.Handle.CFrame = CFrame.new(0, 0, 0) * CFrame.Angles(math.rad(90), 0, 0) - part.Parent = self.Variables["_imgView"] + -- 模型展示 + local taskRotation, part = EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], self.Data.OrgId, false) self.Prefab = part - -- 相机 - local viewportCamera = Instance.new("Camera") - self.Variables["_imgView"].CurrentCamera = viewportCamera - viewportCamera.Parent = self.Variables["_imgView"] - viewportCamera.CFrame = CFrame.new(Vector3.new(0, 0, 6), part.Handle.Position) - self.ViewCamera = viewportCamera - -- 如果图鉴没有,就设置成黑色的 if self.Data.Timestamp == 0 then part.Handle.Mesh.TextureId = Localization:GetBlackTexture() diff --git a/src/StarterPlayerScripts/UI/Windows/CreateWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/CreateWindow/init.luau index 067ef97..5ec7f59 100644 --- a/src/StarterPlayerScripts/UI/Windows/CreateWindow/init.luau +++ b/src/StarterPlayerScripts/UI/Windows/CreateWindow/init.luau @@ -22,6 +22,10 @@ local RE_Forge = ReplicatedStorage.Events.RE_Forge local LocalPlayer = game.Players.LocalPlayer +--> Components +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + -------------------------------------------------------------------------------- @@ -70,6 +74,7 @@ function CreateWindow:Init(UIManager: table, Data: table?) -- 详情面板 ["_btnCreate"] = 0, ["_btnMult"] = 0, + ["_imgView"] = 0, ["_imgIcon"] = 0, ["_tmpName"] = 0, ["_tmpQuality"] = 0, @@ -84,6 +89,9 @@ function CreateWindow:Init(UIManager: table, Data: table?) self.UIRootName = "ui_w_create" self.UIParentName = UIEnums.UIParent.UIRoot + self.OldPrefab = nil + self.OldViewportCamera = nil + self.LastActiveItem = nil return self @@ -94,10 +102,21 @@ function CreateWindow:ShowDetailInfo(data: table?) self.Variables["_imgIcon"].Image = Localization:GetImageData(Utils:GetIdDataFromJson(JsonItemProp, data.OrgId).iconId) self.Variables["_tmpName"].Text = Localization:GetLanguageData(Utils:GetIdDataFromJson(JsonItemProp, data.OrgId).nameId) self.Variables["_tmpQuality"].Text = Localization:GetColoredEquipmentQualityDesc(data.Quality) + + local taskRotation, part, viewportCamera = EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], data.OrgId, false) + self.OldPrefab = part + self.OldViewportCamera = viewportCamera else self.Variables["_imgIcon"].Image = "" self.Variables["_tmpName"].Text = "" self.Variables["_tmpQuality"].Text = "" + + if self.OldPrefab then + self.OldPrefab:Destroy() + self.OldViewportCamera:Destroy() + self.OldPrefab = nil + self.OldViewportCamera = nil + end end end diff --git a/src/StarterPlayerScripts/UI/Windows/EquipmentDetailWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/EquipmentDetailWindow/init.luau index be2a9f7..0a0950b 100644 --- a/src/StarterPlayerScripts/UI/Windows/EquipmentDetailWindow/init.luau +++ b/src/StarterPlayerScripts/UI/Windows/EquipmentDetailWindow/init.luau @@ -27,6 +27,7 @@ local LocalPlayer = game.Players.LocalPlayer --> Common local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") local SpecialShow = require(CommonFolder:WaitForChild("SpecialShow")) +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) local FolderEquipment = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") @@ -203,21 +204,12 @@ function EquipmentDetailWindow:OnOpenWindow() self.Variables["_tmpQuality"].Text = Localization:GetColoredEquipmentQualityDesc(equipmentInstance:GetAttribute("quality")) self.Variables["_tmpCombatValue"].Text = 0 - local equipmentData = Utils:GetIdDataFromJson(JsonEquipment, equipmentInstance:GetAttribute("orgId")) - -- 模型 - local part = FolderEquipment:FindFirstChild(equipmentData.modelName):Clone() - part.Handle.Position = Vector3.new(0, 0, 0) - part.Handle.CFrame = CFrame.new(0, 0, 0) * CFrame.Angles(math.rad(90), 0, 0) - part.Parent = self.Variables["_imgView"] + local taskRotation, part, viewportCamera = EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], equipmentInstance:GetAttribute("orgId"), false) + self.TaskRotation = taskRotation self.Prefab = part - - -- 相机 - local viewportCamera = Instance.new("Camera") - self.Variables["_imgView"].CurrentCamera = viewportCamera - viewportCamera.Parent = self.Variables["_imgView"] - viewportCamera.CFrame = CFrame.new(Vector3.new(0, 0, 6), part.Handle.Position) self.ViewCamera = viewportCamera + -- 穿戴状态按钮显示 self.Variables["_btnUnwear"].Visible = self.Data.base.wearing ~= 0 diff --git a/src/StarterPlayerScripts/UI/Windows/GetEquipmentsWindow/EquipmentShow.luau b/src/StarterPlayerScripts/UI/Windows/GetEquipmentsWindow/EquipmentShow.luau index 511dd7a..72a10e5 100644 --- a/src/StarterPlayerScripts/UI/Windows/GetEquipmentsWindow/EquipmentShow.luau +++ b/src/StarterPlayerScripts/UI/Windows/GetEquipmentsWindow/EquipmentShow.luau @@ -11,6 +11,10 @@ local JsonAttributes = require(ReplicatedStorage.Json.Attributes) local FolderEquipment = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") +local LocalPlayer = game.Players.LocalPlayer +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + function EquipmentShow:Init(data: table) local self = {} self.Data = data @@ -25,29 +29,11 @@ function EquipmentShow:Init(data: table) end function EquipmentShow:Refresh() - local equipmentData = Utils:GetIdDataFromJson(JsonEquipment, self.Data["orgId"]) - - -- 模型 - local part = FolderEquipment:FindFirstChild(equipmentData.modelName):Clone() - part.Handle.Position = Vector3.new(0, 0, 0) - part.Handle.CFrame = CFrame.new(0, 0, 0) * CFrame.Angles(math.rad(90), 0, 0) - part.Parent = self.Variables["_imgView"] + local taskRotation, part, viewportCamera = EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], self.Data.orgId, true) + self.TaskRotation = taskRotation self.Prefab = part - - -- 相机 - local viewportCamera = Instance.new("Camera") - self.Variables["_imgView"].CurrentCamera = viewportCamera - viewportCamera.Parent = self.Variables["_imgView"] - viewportCamera.CFrame = CFrame.new(Vector3.new(0, 0, 6), part.Handle.Position) self.ViewCamera = viewportCamera - - -- 旋转 - self.taskRotation = task.spawn(function() - while true do - self.Prefab.Handle.CFrame = self.Prefab.Handle.CFrame * CFrame.Angles(0, 0, 0.01) - task.wait(0.01) - end - end) + print(taskRotation, part, viewportCamera) end function EquipmentShow:OnInitFinish() @@ -64,9 +50,10 @@ function EquipmentShow:Destroy() v:Disconnect() end end - if self.taskRotation then - task.cancel(self.taskRotation) - self.taskRotation = nil + + if self.TaskRotation then + task.cancel(self.TaskRotation) + self.TaskRotation = nil end for k, v in pairs(self) do self[k] = nil diff --git a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau index c9207be..adfc700 100644 --- a/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau +++ b/src/StarterPlayerScripts/UI/Windows/MainWindow/init.luau @@ -88,6 +88,7 @@ function MainWindow:Init(UIManager: table, Data: table?) ["_btnStartChallenge"] = 0, ["_btnBlessing"] = 0, ["_btnRune"] = 0, + ["_btnRuneCombine"] = 0, -- 锻造条 ["_goForgeBar"] = 0, @@ -158,6 +159,9 @@ function MainWindow:OnOpenWindow() local runeCon = self.Variables["_btnRune"].Activated:Connect(function() self.UIManager:OpenWindow("RuneWindow") end) + local combineRuneCon = self.Variables["_btnRuneCombine"].Activated:Connect(function() + self.UIManager:OpenWindow("CombineRuneWindow") + end) table.insert(self.Connections, createCon) table.insert(self.Connections, chaCon) @@ -165,6 +169,7 @@ function MainWindow:OnOpenWindow() table.insert(self.Connections, startChallengeCon) table.insert(self.Connections, blessingCon) table.insert(self.Connections, runeCon) + table.insert(self.Connections, combineRuneCon) local challengeLevelEndCon = challengeLevelEndSignal:Connect(function(result: boolean) self.Variables["_btnStartChallenge"].Visible = true diff --git a/src/StarterPlayerScripts/UI/Windows/RuneWindow/PackageShow.luau b/src/StarterPlayerScripts/UI/Windows/RuneWindow/PackageShow.luau index 8007b7e..e621cc5 100644 --- a/src/StarterPlayerScripts/UI/Windows/RuneWindow/PackageShow.luau +++ b/src/StarterPlayerScripts/UI/Windows/RuneWindow/PackageShow.luau @@ -38,7 +38,7 @@ function PackageShow:OnInitFinish() if self.Data == {} then -- TODO: 之后做提示弹窗 else - self.TopUI:ShowDetailData(self.Data.id) + self.TopUI:ShowDetailData(self.Data.id, self.Data.orgId) end end) table.insert(self.Connections, con) diff --git a/src/StarterPlayerScripts/UI/Windows/RuneWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/RuneWindow/init.luau index 49d4d7f..1d26773 100644 --- a/src/StarterPlayerScripts/UI/Windows/RuneWindow/init.luau +++ b/src/StarterPlayerScripts/UI/Windows/RuneWindow/init.luau @@ -8,22 +8,33 @@ local UIEnums = require(ReplicatedStorage.Base.UIEnums) --> Components local WearingShow = require(script.WearingShow) local PackageShow = require(script.PackageShow) - --> Dependencies local Utils = require(ReplicatedStorage.Tools.Utils) local Localization = require(ReplicatedStorage.Tools.Localization) --> Json -local JsonAttributesUpgrade = require(ReplicatedStorage.Json.AttributesUpgrade) local JsonRune = require(ReplicatedStorage.Json.Rune) +local JsonItem = require(ReplicatedStorage.Json.ItemProp) +local JsonEquipment = require(ReplicatedStorage.Json.Equipment) --> Events local RE_RuneInlay = ReplicatedStorage.Events.RE_RuneInlay - --> Variables local LocalPlayer = game.Players.LocalPlayer +--> Prefabs +local FolderEquipmentPrefabs = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") +local EquipmentFolder = Utils:WaitPlayerDataFolder(LocalPlayer):WaitForChild("Equipment") +local DataFolder = Utils:WaitPlayerDataFolder(LocalPlayer):WaitForChild("Rune") + +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + +--> Signals +local Signal = require(ReplicatedStorage.Tools.Signal) +local selectRuneInlayEquipmentSignal = Signal.new(Signal.ENUM.SELECT_RUNE_INLAY_EQUIPMENT) + -------------------------------------------------------------------------------- local RuneWindow = {} @@ -37,29 +48,35 @@ function RuneWindow:Init(UIManager: table, Data: table?) ["_goRunePanel"] = 0, ["__listRunePackage"] = 0, ["__listRuneWearing"] = 0, - ["_tmpCombatValue"] = 0, + ["_tmpName"] = 0, + ["_tmpQuality"] = 0, ["_imgIcon"] = 0, ["_bgNowSelectFrame"] = 0, ["_imgNowRune"] = 0, ["_tmpNowRuneName"] = 0, + ["_tmpMaxRuneEmpty"] = 0, + ["_imgView"] = 0, ["_btnClose"] = 0, ["_btnBgClose"] = 0, ["_btnInlay"] = 0, + ["_btnChangeEquipment"] = 0, } self.UIRootName = "ui_w_rune" self.UIParentName = UIEnums.UIParent.UIRoot self.ShowEquipmentId = nil self.NowSelectRuneId = nil + self.NowMaxRuneNumber = 0 return self end -function RuneWindow:ShowDetailData(uniqueId: number) +function RuneWindow:ShowDetailData(uniqueId: number, orgId: number) if uniqueId then + if self.NowMaxRuneNumber == 0 then warn("当前最大符文数量为0") return end self.NowSelectRuneId = uniqueId - local runeData = Utils:GetIdDataFromJson(JsonRune, uniqueId) + local runeData = Utils:GetIdDataFromJson(JsonRune, orgId) self.Variables["_tmpNowRuneName"].Text = Localization:GetLanguageData(runeData.nameId) self.Variables["_imgNowRune"].Image = Localization:GetImageData(runeData.icon) self.Variables["_imgNowRune"].BackgroundColor3 = Localization:GetRuneQualityBgColor(runeData.quality) @@ -73,6 +90,18 @@ function RuneWindow:ShowDetailData(uniqueId: number) end end +function RuneWindow:RefreshEquipmentName() + if self.ShowEquipmentId then + local equipmentInstance = EquipmentFolder:FindFirstChild(self.ShowEquipmentId) + local itemData = Utils:GetIdDataFromJson(JsonItem, equipmentInstance:GetAttribute("orgId")) + self.Variables["_tmpName"].Text = Localization:GetLanguageData(itemData.nameId) + self.Variables["_tmpQuality"].Text = Localization:GetColoredEquipmentQualityDesc(equipmentInstance:GetAttribute("quality")) + else + self.Variables["_tmpName"].Text = "" + self.Variables["_tmpQuality"].Text = "" + end +end + function RuneWindow:WearRefresh(data: table) local newSlot = data.instance:GetAttribute("wearing") self.Variables["__listRuneWearing"]:RemoveData("slot"..newSlot) @@ -101,7 +130,7 @@ function RuneWindow:InlayRune() if self.NowSelectRuneId and self.ShowEquipmentId then RE_RuneInlay:FireClient(self.NowSelectRuneId, self.ShowEquipmentId) end -end +end function RuneWindow:AddInstanceData(configInstance: Instance, Data: table?) local data = self.Data @@ -137,6 +166,65 @@ function RuneWindow:RemoveInstanceData(configInstance: Instance, Data: table?) return nil end +function RuneWindow:ShowWearingSlot() + -- 清除旧数据 + if self.Data then + if self.Data.WearingRune then + self.Data.WearingRune = nil + end + if self.Data.PackageRune then + self.Data.PackageRune = nil + end + end + self.Data = { + WearingRune = {}, + PackageRune = {}, + } + + local equipmentInstance = EquipmentFolder:FindFirstChild(self.ShowEquipmentId) + local maxRuneNumber = equipmentInstance:GetAttribute("maxRuneNumber") + self.NowMaxRuneNumber = maxRuneNumber + self:RefreshEquipmentName() + + -- 模型展示 + EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], equipmentInstance:GetAttribute("orgId"), false) + + -- 如果当前最大槽位数为0,刷新至空显示 + if maxRuneNumber <= 0 then + self:ShowDetailData() + self.Variables["_bgNowSelectFrame"].Visible = false + self.Variables["_tmpMaxRuneEmpty"].Text = Localization:GetLanguageData(1200) + self.Variables["_tmpMaxRuneEmpty"].Visible = true + else + self.Variables["_bgNowSelectFrame"].Visible = true + self.Variables["_tmpMaxRuneEmpty"].Visible = false + end + + -- 补充空槽位 + for i = 1, self.NowMaxRuneNumber do + local isExist = false + for k, data in pairs(self.Data.WearingRune) do + if data.wearingSlot == i then + isExist = true + break + end + end + if not isExist then + self.Data.WearingRune["slot" .. i] = { + wearingSlot = i, + } + end + end + + -- 添加符文数据 + for _, child in DataFolder:GetChildren() do + self:AddInstanceData(child, self.Data) + end + self:SetData(self.Data) + self.Variables["__listRunePackage"]:SetData(self.Data.PackageRune) + self.Variables["__listRuneWearing"]:SetData(self.Data.WearingRune) +end + function RuneWindow:OnOpenWindow() UIWindow.OnOpenWindow(self) self.Variables["__listRunePackage"]:AddComponent(PackageShow) @@ -151,16 +239,8 @@ function RuneWindow:OnOpenWindow() table.insert(self.Connections, bgCloseCon) table.insert(self.Connections, closeCon) - - -- 自己进行数据处理 - local DataFolder = Utils:WaitPlayerDataFolder(LocalPlayer):FindFirstChild("Rune") - local data = { - WearingRune = {}, - PackageRune = {}, - } -- 找到当前穿戴的装备 - local EquipmentFolder = Utils:WaitPlayerDataFolder(LocalPlayer):FindFirstChild("Equipment") for _, child in EquipmentFolder:GetChildren() do if child:GetAttribute("wearing") ~= 0 then self.ShowEquipmentId = child.Name @@ -170,6 +250,7 @@ function RuneWindow:OnOpenWindow() -- 如果当前没有穿戴的装备,并且没有装备,则报错提示, 并返回内容 if not self.ShowEquipmentId and #EquipmentFolder:GetChildren() == 0 then warn("没有找到当前穿戴的装备") + self.NowMaxRuneNumber = 0 return end @@ -177,41 +258,10 @@ function RuneWindow:OnOpenWindow() if not self.ShowEquipmentId then self.ShowEquipmentId = EquipmentFolder:GetChildren()[1].Name end - - local equipmentInstance = EquipmentFolder:FindFirstChild(self.ShowEquipmentId) - local maxRuneNumber = equipmentInstance:GetAttribute("maxRuneNumber") - - -- 如果当前最大槽位数为0,刷新至空显示 - if maxRuneNumber <= 0 then - self.Variables["__listRuneWearing"]:SetData({}) - self:ShowDetailData() - return - end - - -- 添加符文数据 - for _, child in DataFolder:GetChildren() do - self:AddInstanceData(child, data) - end - self:SetData(data) - - -- 补充空槽位 - for i = 1, maxRuneNumber do - local isExist = false - for k, data in pairs(self.Data.WearingRune) do - if data.wearingSlot == i then - isExist = true - break - end - end - if not isExist then - self.Data.WearingRune["slot" .. i] = { - wearingSlot = i, - } - end - end + self:ShowWearingSlot() local addCon = DataFolder.ChildAdded:Connect(function(child) - local data, parentName = self:AddInstanceData(child, data) + local data, parentName = self:AddInstanceData(child, self.Data) if parentName == "WearingRune" then self.Variables["__listRuneWearing"]:AddData("slot"..data.wearingSlot, data) else @@ -229,14 +279,29 @@ function RuneWindow:OnOpenWindow() end end) - table.insert(self.Connections, addCon) table.insert(self.Connections, removeCon) - print(self.Variables["__listRunePackage"], self.Variables["__listRuneWearing"]) - self.Variables["__listRunePackage"]:SetData(self.Data.PackageRune) - self.Variables["__listRuneWearing"]:SetData(self.Data.WearingRune) self.Variables["__listRuneWearing"]:SetLayoutOrder("wearingSlot") + + local inlayCon = self.Variables["_btnInlay"].Activated:Connect(function() + self:InlayRune() + end) + table.insert(self.Connections, inlayCon) + + local changeEquipmentCon = self.Variables["_btnChangeEquipment"].Activated:Connect(function() + self.UIManager:OpenWindow("SelectEquipmentWindow", { + ExpectIds = {tostring(self.ShowEquipmentId)}, + }) + end) + table.insert(self.Connections, changeEquipmentCon) + + local selectRuneInlayEquipmentCon = selectRuneInlayEquipmentSignal:Connect(function(id) + self.ShowEquipmentId = id + self:RefreshEquipmentName() + self:ShowWearingSlot() + end) + table.insert(self.Connections, selectRuneInlayEquipmentCon) end function RuneWindow:OnCloseWindow() diff --git a/src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/PackageShow.luau b/src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/PackageShow.luau new file mode 100644 index 0000000..83f1765 --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/PackageShow.luau @@ -0,0 +1,81 @@ +local PackageShow = {} +PackageShow.__index = PackageShow + +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Utils = require(ReplicatedStorage.Tools.Utils) +local Localization = require(ReplicatedStorage.Tools.Localization) +local JsonEquipment = require(ReplicatedStorage.Json.Equipment) +local JsonItemProp = require(ReplicatedStorage.Json.ItemProp) + +local FolderEquipment = ReplicatedStorage:WaitForChild("Prefabs"):WaitForChild("Equipments") + +--> Variables +local LocalPlayer = game.Players.LocalPlayer + +--> Components +local CommonFolder = LocalPlayer:WaitForChild("PlayerScripts"):WaitForChild("UI"):WaitForChild("Common") +local EquipmentModelDetail = require(CommonFolder:WaitForChild("EquipmentModelDetail")) + +function PackageShow:Init(data: table) + local self = {} + self.Data = data + self.Variables = { + ["_btnClick"] = 0, + ["_imgIcon"] = 0, + ["_tmpQuality"] = 0, + ["_tmpName"] = 0, + ["_imgView"] = 0, + } + self.Connections = {} + + setmetatable(self, PackageShow) + + return self +end + +function PackageShow:Refresh() + local itemData = Utils:GetIdDataFromJson(JsonItemProp, self.Data.orgId) + self.Variables._imgIcon.Image = Localization:GetImageData(itemData.iconId) + self.Variables._imgIcon.Visible = false + self.Variables._tmpQuality.Text = Localization:GetColoredEquipmentQualityDesc(self.Data.quality) + self.Variables._tmpName.Text = Localization:GetLanguageData(itemData.nameId) + + -- 模型展示 + EquipmentModelDetail:ShowDetail(self.Variables["_imgView"], self.Data.orgId, false) +end + +function PackageShow:OnInitFinish() + local con = self.Variables._btnClick.MouseButton1Click:Connect(function() + if self.Data == {} then + -- TODO: 之后做提示弹窗 + else + self.TopUI:SetSelectId(self.Data.id) + end + end) + table.insert(self.Connections, con) + + if self.Data.instance then + local wearingCon = self.Data.instance:GetAttributeChangedSignal("wearing"):Connect(function() + local oldWearing = self.Data.wearing + local newWearing = self.Data.instance:GetAttribute("wearing") + if oldWearing ~= newWearing then + if newWearing > 0 then + self.TopUI:WearRefresh(self.Data) + else + self.TopUI:UnwearRefresh(self.Data) + end + end + end) + table.insert(self.Connections, wearingCon) + end +end + +function PackageShow:Destroy() + for k, v in pairs(self) do + self[k] = nil + end + self = nil +end + +return PackageShow \ No newline at end of file diff --git a/src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/init.luau b/src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/init.luau new file mode 100644 index 0000000..cf35dc3 --- /dev/null +++ b/src/StarterPlayerScripts/UI/Windows/SelectEquipmentWindow/init.luau @@ -0,0 +1,78 @@ +--> Services +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +--> Dependencies +local UIWindow = require(ReplicatedStorage.Base.UIWindow) +local UIEnums = require(ReplicatedStorage.Base.UIEnums) + +--> Components +local PackageShow = require(script.PackageShow) + +--> Dependencies +local Utils = require(ReplicatedStorage.Tools.Utils) + +--> Variables +local LocalPlayer = game.Players.LocalPlayer +local Signal = require(ReplicatedStorage.Tools.Signal) +local selectRuneInlayEquipmentSignal = Signal.new(Signal.ENUM.SELECT_RUNE_INLAY_EQUIPMENT) + +-------------------------------------------------------------------------------- + +local SelectEquipmentWindow = {} +SelectEquipmentWindow.__index = SelectEquipmentWindow +setmetatable(SelectEquipmentWindow, {__index = UIWindow}) + +function SelectEquipmentWindow:Init(UIManager: table, Data: table?) + local self = UIWindow:Init(UIManager, Data) + setmetatable(self, SelectEquipmentWindow) + self.Variables = { + ["__listWeaponPackage"] = 0, + ["_btnClose"] = 0, + ["_btnBgClose"] = 0, + } + self.UIRootName = "ui_w_select_equipment" + self.UIParentName = UIEnums.UIParent.UIRoot + + return self +end + +function SelectEquipmentWindow:SetSelectId(id: number) + selectRuneInlayEquipmentSignal:Fire(id) + self.UIManager:CloseWindow(script.Name) +end + +function SelectEquipmentWindow:OnOpenWindow() + UIWindow.OnOpenWindow(self) + + -- 自己进行数据处理 + local DataFolder = Utils:WaitPlayerDataFolder(LocalPlayer):FindFirstChild("Equipment") + self.Data.ShowData = {} + for _, child in DataFolder:GetChildren() do + if not table.find(self.Data.ExpectIds, child.Name) then + local newData = child:GetAttributes() + newData.instance = child + table.insert(self.Data.ShowData, newData) + end + end + print(self.Data) + + local bgCloseCon = self.Variables["_btnBgClose"].Activated:Connect(function() + self.UIManager:CloseWindow(script.Name) + end) + local closeCon = self.Variables["_btnClose"].Activated:Connect(function() + self.UIManager:CloseWindow(script.Name) + end) + table.insert(self.Connections, bgCloseCon) + table.insert(self.Connections, closeCon) + + self.Variables["__listWeaponPackage"]:AddComponent(PackageShow) + self.Variables["__listWeaponPackage"]:SetData(self.Data.ShowData) +end + +function SelectEquipmentWindow:OnCloseWindow() + UIWindow.OnCloseWindow(self) +end + + + +return SelectEquipmentWindow \ No newline at end of file