diff --git a/Assets/AddressableAssetsData/link.xml b/Assets/AddressableAssetsData/link.xml deleted file mode 100644 index 62ce7303..00000000 --- a/Assets/AddressableAssetsData/link.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Assets/AddressableAssetsData/link.xml.meta b/Assets/AddressableAssetsData/link.xml.meta deleted file mode 100644 index 19cc8ac3..00000000 --- a/Assets/AddressableAssetsData/link.xml.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 2664c414003fece4d86a470c718331e1 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Editor/PackagingBuildGuard.cs b/Assets/Editor/PackagingBuildGuard.cs index 23f544af..dd00c599 100644 --- a/Assets/Editor/PackagingBuildGuard.cs +++ b/Assets/Editor/PackagingBuildGuard.cs @@ -25,6 +25,7 @@ public sealed class PackagingBuildGuard : IPreprocessBuildWithReport var problems = new List(); CheckPlayers(problems); + CheckPlayerSkills(problems); CheckHeroes(problems); CheckSongs(problems); CheckMails(problems); @@ -85,6 +86,24 @@ public sealed class PackagingBuildGuard : IPreprocessBuildWithReport problems.Add(path + " :: uRankingScore = " + rank.floatValue); } } + + PlayerExperienceRuntimeProbe expProbe; + if (SecureSaveVault.TryLoadJson("player_experience", "runtime", out expProbe) && expProbe != null && expProbe.playerExp != 0) + { + problems.Add("player_experience/runtime :: playerExp = " + expProbe.playerExp); + } + + int backupExp; + if (PlayerProgressBackupService.TryRestorePlayerExperience(out backupExp) && backupExp != 0) + { + problems.Add("player progress backup :: playerExperience = " + backupExp); + } + } + + [System.Serializable] + private sealed class PlayerExperienceRuntimeProbe + { + public int playerExp; } private static void CheckHeroes(List problems) @@ -118,6 +137,62 @@ public sealed class PackagingBuildGuard : IPreprocessBuildWithReport } } + private static void CheckPlayerSkills(List problems) + { + foreach (var guid in AssetDatabase.FindAssets("t:userLevel_skills_SO")) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + var so = AssetDatabase.LoadAssetAtPath(path); + if (so == null || so.skills == null || so.skills.Count == 0) + { + continue; + } + + int enabledCount = 0; + int enabledIndex = -1; + for (int i = 0; i < so.skills.Count; i++) + { + var entry = so.skills[i]; + if (entry != null && entry.isEnabled) + { + enabledCount++; + enabledIndex = i; + } + } + + if (enabledCount != 1 || enabledIndex != 0) + { + problems.Add(path + " :: player skill selection must ship with only index 0 enabled, current enabledIndex=" + enabledIndex + ", enabledCount=" + enabledCount); + } + } + + PlayerSkillSaveData runtimeSkillData; + if (SecureSaveVault.TryLoadJson("player_skills", "runtime", out runtimeSkillData) && runtimeSkillData != null) + { + if (runtimeSkillData.selectedSkillIndex != 0 || + runtimeSkillData.postMatchRewardCounter != 0 || + runtimeSkillData.skillSwitchCooldownRemainingMatches != 0) + { + problems.Add("player_skills/runtime :: selectedSkillIndex=" + runtimeSkillData.selectedSkillIndex + + ", postMatchRewardCounter=" + runtimeSkillData.postMatchRewardCounter + + ", skillSwitchCooldownRemainingMatches=" + runtimeSkillData.skillSwitchCooldownRemainingMatches); + } + } + + PlayerSkillSaveData backupSkillData; + if (PlayerProgressBackupService.TryRestorePlayerSkill(out backupSkillData) && backupSkillData != null) + { + if (backupSkillData.selectedSkillIndex != 0 || + backupSkillData.postMatchRewardCounter != 0 || + backupSkillData.skillSwitchCooldownRemainingMatches != 0) + { + problems.Add("player progress backup :: playerSkill selectedSkillIndex=" + backupSkillData.selectedSkillIndex + + ", postMatchRewardCounter=" + backupSkillData.postMatchRewardCounter + + ", skillSwitchCooldownRemainingMatches=" + backupSkillData.skillSwitchCooldownRemainingMatches); + } + } + } + private static void CheckSongs(List problems) { string[] recordFields = diff --git a/Assets/Editor/PackagingResetTool.cs b/Assets/Editor/PackagingResetTool.cs index e5e0c246..bac024e0 100644 --- a/Assets/Editor/PackagingResetTool.cs +++ b/Assets/Editor/PackagingResetTool.cs @@ -41,7 +41,7 @@ public static class PackagingResetTool "2.5 歌曲:游玩次数、累计时长、各难度成绩记录清零(歌曲解锁/谱面配置不动)\n" + "3. 装备:删除 " + GeneratedEquipmentFolder + " 下所有生成装备,清除英雄已装备引用\n" + "4. 商店:所有物品 purchasedCount、user_has_read 清零(限购配置 itemPurchaseQuota 不动)\n" + - "5. 清理本机测试存档(.cache_bridge 货币/成长/经验瓶/突破材料/装备消耗品、player_rks、player_experience、装备/商店存档,不含拥有权账本)与相关 PlayerPrefs\n\n" + + "5. 清理本机测试存档(.cache_bridge 货币/成长/经验瓶/突破材料/装备消耗品、player_rks、player_experience、player_skills、装备/商店存档,不含拥有权账本)与相关 PlayerPrefs\n\n" + "此操作会修改并保存资产文件,建议在版本控制下执行。是否继续?", "执行重置", "取消"); @@ -65,6 +65,7 @@ public static class PackagingResetTool AssetDatabase.StartAssetEditing(); errors += ResetPlayers(report); + errors += ResetPlayerSkills(report); errors += ResetHeroes(report); errors += ResetSongs(report); errors += DeleteGeneratedEquipmentAssets(report); @@ -129,9 +130,53 @@ public static class PackagingResetTool } report.AppendLine("玩家资产已重置:" + count + " 个 Player_SO。"); + try + { + SecureSaveVault.Delete("player_skills", "runtime"); + PlayerProgressBackupService.ClearPlayerSkillBackup(); + report.AppendLine("已清理 player_skills 运行时存档与备份(player_skills/runtime)。"); + } + catch (Exception ex) + { + errors++; + report.AppendLine("清理 player_skills 存档失败:" + ex.Message); + } + return errors; } + private static int ResetPlayerSkills(StringBuilder report) + { + int count = 0; + foreach (var guid in AssetDatabase.FindAssets("t:userLevel_skills_SO")) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + var so = AssetDatabase.LoadAssetAtPath(path); + if (so == null || so.skills == null || so.skills.Count == 0) + { + continue; + } + + var serialized = new SerializedObject(so); + var skills = serialized.FindProperty("skills"); + if (skills != null && skills.isArray) + { + for (int i = 0; i < skills.arraySize; i++) + { + var entry = skills.GetArrayElementAtIndex(i); + SetChildBool(entry, "isEnabled", i == 0); + } + } + + serialized.ApplyModifiedPropertiesWithoutUndo(); + EditorUtility.SetDirty(so); + count++; + } + + report.AppendLine("玩家技能资产已重置:" + count + " 个 userLevel_skills_SO(仅启用第 0 个技能)。"); + return 0; + } + private static int ResetHeroes(StringBuilder report) { int count = 0; @@ -237,6 +282,15 @@ public static class PackagingResetTool } } + private static void SetChildBool(SerializedProperty parent, string field, bool value) + { + var prop = parent.FindPropertyRelative(field); + if (prop != null) + { + prop.boolValue = value; + } + } + private static int DeleteGeneratedEquipmentAssets(StringBuilder report) { if (!AssetDatabase.IsValidFolder(GeneratedEquipmentFolder)) @@ -383,6 +437,7 @@ public static class PackagingResetTool { string legacyExpPath = Path.Combine(Application.persistentDataPath, "player_experience.json"); SecureSaveVault.Delete("player_experience", "runtime", legacyExpPath); + PlayerProgressBackupService.ClearPlayerExperienceBackup(); report.AppendLine("已清理 player_experience 运行时存档(player_experience/runtime)。"); } catch (Exception ex) diff --git a/Assets/Resources/so/ally/30205_winnie.asset b/Assets/Resources/so/ally/30205_winnie.asset index 279e8bdc..d2a4c1b9 100644 --- a/Assets/Resources/so/ally/30205_winnie.asset +++ b/Assets/Resources/so/ally/30205_winnie.asset @@ -258,6 +258,6 @@ MonoBehaviour: skillDescriptionsText: "\u3010\u8D70\u5411\u672B\u8DEF\u3011\u6D88\u8017\u6CD5\u529B\u503C\u65F6\uFF0C\u83B7\u53D6\u7B49\u540C\u4E8E\u81EA\u8EAB\u6CD5\u529B\u503C\u4E0A\u9650100%\u7684\u5076\u50CF\u5206\u6570\uFF1B\u6D88\u8017\u5168\u90E8\u7684\u6CD5\u529B\u503C\u65F6\uFF0C\u989D\u5916\u83B7\u5F97200%\u3002" thisSkill_levelLimit: 4 isSpecialSkill: 0 - equippedSkillGroupIDs: 4ae4cc01 + equippedSkillGroupIDs: 4ae4cc0150e4cc0152e4cc0153e4cc01 equippedEquipment: {fileID: 0} equippedEquipmentId: diff --git a/Assets/Resources/so/notebook/502idols/502001.asset b/Assets/Resources/so/notebook/502idols/502001.asset index ddd2568a..926fd076 100644 --- a/Assets/Resources/so/notebook/502idols/502001.asset +++ b/Assets/Resources/so/notebook/502idols/502001.asset @@ -13,10 +13,11 @@ MonoBehaviour: m_Name: 502001 m_EditorClassIdentifier: fatherType: 2 - isUnlocked: 1 + isUnlocked: 0 class_id: 502001 class_name: "\u524D\u8A00" class_image: {fileID: 0} + showInBag: 0 itemRarity: 5 usageTag: 0 class_description: "\u540D\u4EE5\u201C\u95EA\u8000\u7684\u5979\u4EEC\u201D" diff --git a/Assets/Resources/so/notebook/502idols/502002.asset b/Assets/Resources/so/notebook/502idols/502002.asset index 3761cf9e..c0ba812f 100644 --- a/Assets/Resources/so/notebook/502idols/502002.asset +++ b/Assets/Resources/so/notebook/502idols/502002.asset @@ -13,10 +13,11 @@ MonoBehaviour: m_Name: 502002 m_EditorClassIdentifier: fatherType: 2 - isUnlocked: 1 + isUnlocked: 0 class_id: 501001 class_name: "\u5B54\u5609\u5F6C" class_image: {fileID: 0} + showInBag: 0 itemRarity: 5 usageTag: 0 class_description: "\u6211\u4FDD\u6301\u7740\u6124\u6012\u4F46\u4F60\u770B\u4E0D\u51FA\u6765\n\u56E0\u4E3A\u6211\u6709\u5FAE\u7B11\u5507" diff --git a/Assets/Resources/so/notebook/502idols/502003.asset b/Assets/Resources/so/notebook/502idols/502003.asset index 09e89cae..95dcfde8 100644 --- a/Assets/Resources/so/notebook/502idols/502003.asset +++ b/Assets/Resources/so/notebook/502idols/502003.asset @@ -13,10 +13,11 @@ MonoBehaviour: m_Name: 502003 m_EditorClassIdentifier: fatherType: 2 - isUnlocked: 1 + isUnlocked: 0 class_id: 501001 class_name: "\u5B54" class_image: {fileID: 0} + showInBag: 0 itemRarity: 5 usageTag: 0 class_description: "\u6211\u4FDD\u6301\u7740\u6124\u6012\u4F46\u4F60\u770B\u4E0D\u51FA\u6765\n\u56E0\u4E3A\u6211\u6709\u5FAE\u7B11\u5507" diff --git a/Assets/Resources/so/storeSO/medicines/78111_eqp_breakthrough_material.asset b/Assets/Resources/so/storeSO/medicines/78111_eqp_breakthrough_material.asset index d7ecacdd..cad4eea6 100644 --- a/Assets/Resources/so/storeSO/medicines/78111_eqp_breakthrough_material.asset +++ b/Assets/Resources/so/storeSO/medicines/78111_eqp_breakthrough_material.asset @@ -17,7 +17,7 @@ MonoBehaviour: itemRarity: 3 itemID: 78111 itemSinglePurchaseQty: 1 - itemPurchaseQuota: -1 + itemPurchaseQuota: 0 itemDescription: "\u4E00\u4E2A\u5DE8\u5927\u7684\u86CB\u7CD5\uFF0C\u5B83\u5C06\u4F60\u5E26\u56DE\u4E86\u4F60\u4E0E\u521D\u604B\u7EA6\u4F1A\u7684\u90A3\u4E2A\u751C\u871C\u590F\u5929\u3002" itemDetailedDescription: "\u6B64\u6D88\u8017\u54C1\u7528\u4E8E\u8BB0\u5FC6\u5DE1\u6F14\u3002\n\n\u4E00\u4E2A\u5DE8\u5927\u768414\u5BF8\u6C34\u679C\u6155\u65AF\u5976\u6CB9\u86CB\u7CD5\uFF0C\u5B83\u5C06\u4F60\u5E26\u56DE\u4E86\u4F60\u4E0E\u521D\u604B\u7EA6\u4F1A\u7684\u90A3\u4E2A\u751C\u871C\u590F\u5929\u3002\n\n\u4F60\u671B\u7740\u5979\u7684\u80CC\u5F71\uFF0C\u5FC3\u4E2D\u90A3\u53E5\u8BDD\u6700\u7EC8\u8FD8\u662F\u6CA1\u6709\u8BF4\u51FA\u53E3\u3002\u5915\u9633\u4E2D\u4F60\u62D9\u52A3\u5730\u62E8\u5F04\u7740\u5409\u4ED6\u7684\u7434\u5F26\uFF0C\u534A\u751F\u4E0D\u719F\u5730\u5F39\u7740\u901F\u6210\u7684\u7B80\u5355\u8C31\u5B50\u3002\u5979\u8010\u5FC3\u5730\u542C\u5B8C\u4E86\uFF0C\u9752\u6DA9\u7684\u8138\u5E9E\u6CDB\u8D77\u7F9E\u6DA9\u817C\u8146\u7684\u7EA2\u6655\u3002\u5FAE\u98CE\u4E2D\uFF0C\u5979\u7684\u957F\u53D1\u5212\u8FC7\u5634\u89D2\uFF0C\u9732\u51FA\u53EA\u6709\u4F60\u80FD\u8BFB\u61C2\u7684\u5F27\u5EA6\u3002\n\n\u2014\u2014\u591A\u5E74\u8FC7\u53BB\uFF0C\u4F60\u518D\u672A\u5F97\u5230\u5979\u7684\u4EFB\u4F55\u6D88\u606F\u3002\u90A3\u628A\u5409\u4ED6\u627F\u8F7D\u7740\u4F60\u7684\u56DE\u5FC6\uFF0C\u6162\u6162\u5728\u5899\u89D2\u72EC\u81EA\u53D1\u9709\u3002\u60C5\u4E0D\u81EA\u7981\u5728\u8111\u6D77\u4E2D\u56DE\u671B\uFF0C\u4F46\u90A3\u91CC\u5DF2\u7ECF\u6CA1\u6709\u5979\u7684\u8EAB\u5F71\u4E86\u3002\n\n\u201C\u5C06\u4F60\u77ED\u6682\u5730\u5E26\u56DE\u4F60\u751F\u547D\u4E2D\u6700\u5E78\u798F\u7684\u65F6\u523B\u3002\u53EA\u662F\u7247\u523B\u4E4B\u95F4\u4E5F\u4F1A\u5F88\u5FEB\u5316\u4F5C\u6CE1\u5F71\u5427\u3002\u201D" itemUsageTag: 0 diff --git a/Assets/Resources/so/storeSO/medicines/78121_eqp_transfer_material.asset b/Assets/Resources/so/storeSO/medicines/78121_eqp_transfer_material.asset index 681337d1..f1015ca6 100644 --- a/Assets/Resources/so/storeSO/medicines/78121_eqp_transfer_material.asset +++ b/Assets/Resources/so/storeSO/medicines/78121_eqp_transfer_material.asset @@ -17,7 +17,7 @@ MonoBehaviour: itemRarity: 4 itemID: 78121 itemSinglePurchaseQty: 1 - itemPurchaseQuota: -1 + itemPurchaseQuota: 0 itemDescription: "\u7528\u4E8E\u8BB0\u5FC6\u5631\u6258\uFF08\u5C5E\u6027\u8F6C\u79FB\uFF09\u7684\u6D88\u8017\u54C1\u3002" itemDetailedDescription: "\u4E00\u4EFD\u7528\u4E8E\u88C5\u5907\u6D17\u70BC\u4E0E\u5C5E\u6027\u8F6C\u79FB\u7684\u6750\u6599\uFF0C\u53EF\u5728\u540E\u7EED\u6D17\u70BC\u7CFB\u7EDF\u4E2D\u6D88\u8017\u4F7F\u7528\u3002" itemUsageTag: 0 diff --git a/Assets/Resources/so/storeSO/medicines/78131_eqp_final_material.asset b/Assets/Resources/so/storeSO/medicines/78131_eqp_final_material.asset index 19c73fb1..4a4c3907 100644 --- a/Assets/Resources/so/storeSO/medicines/78131_eqp_final_material.asset +++ b/Assets/Resources/so/storeSO/medicines/78131_eqp_final_material.asset @@ -17,7 +17,7 @@ MonoBehaviour: itemRarity: 7 itemID: 78131 itemSinglePurchaseQty: 1 - itemPurchaseQuota: -1 + itemPurchaseQuota: 0 itemDescription: "\u7528\u4E8E\u8BB0\u5FC6\u767B\u9876\u5F3A\u5316\u7684\u6D88\u8017\u54C1\u3002" itemDetailedDescription: "\u4E00\u4EFD\u7528\u4E8E\u88C5\u5907\u767B\u9876\u5F3A\u5316\u7684\u6750\u6599\uFF0C\u53EF\u5728\u540E\u7EED\u767B\u9876\u5F3A\u5316\u7CFB\u7EDF\u4E2D\u6D88\u8017\u4F7F\u7528\u3002" itemUsageTag: 0 diff --git a/Assets/Resources/song_songIndex/1002002_arcstar.asset b/Assets/Resources/song_songIndex/1002002_arcstar.asset index 0444f90f..7360510b 100644 --- a/Assets/Resources/song_songIndex/1002002_arcstar.asset +++ b/Assets/Resources/song_songIndex/1002002_arcstar.asset @@ -108,6 +108,6 @@ MonoBehaviour: current_levelProgress: 0 _if_level_is_EASE: 0 max_comboRecord: 0 - thisLevel_selectedDifficultyID: 1 + thisLevel_selectedDifficultyID: 2 thisLevel_addDateString: 2025-12-28 thisLevel_overallDescription: "\u7D22\u5C3C\u5A05\u8BF4\uFF0C\u4E3A\u5979\u627E\u5230\u8BC1\u660E\u8FC7\u53BB\u771F\u5B9E\u5B58\u5728\u7684\u8BC1\u636E\uFF0C\u5979\u4FBF\u80FD\u8FD8\u539F\u4E00\u4E2A\u6765\u81EA\u8FC7\u53BB\u6240\u53D1\u751F\u8FC7\u7684\u6545\u4E8B\uFF0C\u53EF\u6211\u76EE\u524D\u8FD8\u672A\u80FD\u627E\u5230\u8FD9\u6240\u8C13\u7684\u201C\u8BC1\u636E\u201D......\u5B83\u771F\u7684\u5B58\u5728\u5417\uFF1F" diff --git a/Assets/Resources/song_songIndex/1002003_spectrum.asset b/Assets/Resources/song_songIndex/1002003_spectrum.asset index 1c28d50a..5966a06b 100644 --- a/Assets/Resources/song_songIndex/1002003_spectrum.asset +++ b/Assets/Resources/song_songIndex/1002003_spectrum.asset @@ -108,6 +108,6 @@ MonoBehaviour: current_levelProgress: 0 _if_level_is_EASE: 0 max_comboRecord: 0 - thisLevel_selectedDifficultyID: 0 + thisLevel_selectedDifficultyID: 2 thisLevel_addDateString: 2026-01-01 thisLevel_overallDescription: "\u201C\u54D7\u5566\u5566......\u201D\n\u7834\u788E\u7684\u955C\u9762\u6620\u51FA\u5979\u7684\u53CC\u76EE\uFF0C\u5728\u90A3\u4E00\u523B\uFF0C\u89C6\u91CE\u91CC\u51FA\u73B0\u4E86\u591A\u4E2A\u4EE5\u5979\u4E3A\u540D\u7684\u5076\u50CF......" diff --git a/Assets/Resources/song_songIndex/1002004_smile.asset b/Assets/Resources/song_songIndex/1002004_smile.asset index 0b9bbdfb..70cd76e3 100644 --- a/Assets/Resources/song_songIndex/1002004_smile.asset +++ b/Assets/Resources/song_songIndex/1002004_smile.asset @@ -108,6 +108,6 @@ MonoBehaviour: current_levelProgress: 0 _if_level_is_EASE: 0 max_comboRecord: 0 - thisLevel_selectedDifficultyID: 1 + thisLevel_selectedDifficultyID: 2 thisLevel_addDateString: thisLevel_overallDescription: "\u201C\u53F2\u8BD7\u8FDC\u5F81\u201D\u8D5B\u4E8B\u7684\u5F00\u573A\u5E18\u5E55\u88AB\u7F13\u7F13\u62C9\u5F00\uFF0C\u5973\u5B69\u5750\u5728\u8282\u8282\u76F8\u6263\u7684\u9F7F\u8F6E\u8FB9\u4E0A\u3002\n\u800C\u5728\u5979\u7684\u773C\u4E0B\uFF0C\u4FBF\u662F\u65E9\u5DF2\u6253\u5F97\u70ED\u706B\u671D\u5929\u7684\u8D5B\u573A\u3002" diff --git a/Assets/Scenes/Main_main.unity b/Assets/Scenes/Main_main.unity index 3e8d555d..36e7c4d5 100644 --- a/Assets/Scenes/Main_main.unity +++ b/Assets/Scenes/Main_main.unity @@ -2470,6 +2470,85 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 486109075} m_CullTransparentMesh: 1 +--- !u!1 &505976164 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 505976165} + - component: {fileID: 505976167} + - component: {fileID: 505976166} + m_Layer: 5 + m_Name: statusText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &505976165 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505976164} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 615433840} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 64.98, y: 0} + m_SizeDelta: {x: 114, y: 21.44} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &505976166 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505976164} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.8396226, g: 0.21782663, b: 0.40195742, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3} + m_FontSize: 15 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 0 + m_MaxSize: 16 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: "\u672A\u8FBE\u5230\u5F00\u653E\u8981\u6C42" +--- !u!222 &505976167 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 505976164} + m_CullTransparentMesh: 1 --- !u!1 &519420028 GameObject: m_ObjectHideFlags: 0 @@ -2733,6 +2812,82 @@ RectTransform: m_AnchoredPosition: {x: 0, y: -100} m_SizeDelta: {x: 450, y: 300} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &615433839 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 615433840} + - component: {fileID: 615433842} + - component: {fileID: 615433841} + m_Layer: 5 + m_Name: statusBall + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &615433840 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 615433839} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 505976165} + m_Father: {fileID: 1623890023} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 41.6, y: -12.8} + m_SizeDelta: {x: 13.72, y: 13.88} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &615433841 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 615433839} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 1ebd4694ade773448a86c2f663908563, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &615433842 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 615433839} + m_CullTransparentMesh: 1 --- !u!1 &619394800 GameObject: m_ObjectHideFlags: 0 @@ -3774,7 +3929,7 @@ MonoBehaviour: m_HorizontalOverflow: 0 m_VerticalOverflow: 0 m_LineSpacing: 1 - m_Text: WINDOWS_STEAM_publicVer_bansonic_1.0.01 + m_Text: WINDOWS_STEAM_bansonic_1.0.01 --- !u!1 &777304727 GameObject: m_ObjectHideFlags: 0 @@ -4760,6 +4915,82 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 969599515} m_CullTransparentMesh: 1 +--- !u!1 &982753966 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 982753967} + - component: {fileID: 982753969} + - component: {fileID: 982753968} + m_Layer: 5 + m_Name: boarder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &982753967 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 982753966} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1623890023} + m_Father: {fileID: 1750898222} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -72, y: 0} + m_SizeDelta: {x: 53, y: 53} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &982753968 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 982753966} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: a3ab628d6dae8c844bcc5bcfa59c40d8, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &982753969 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 982753966} + m_CullTransparentMesh: 1 --- !u!1 &993808400 GameObject: m_ObjectHideFlags: 0 @@ -4920,7 +5151,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: -9087179407877008718, guid: 3384083cfacddc9478468669e5cae168, type: 3} + m_Sprite: {fileID: 21300000, guid: 95f2af828653163469c9f827bdaaa49c, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 @@ -5469,6 +5700,85 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1122772963} m_CullTransparentMesh: 1 +--- !u!1 &1147968701 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1147968702} + - component: {fileID: 1147968704} + - component: {fileID: 1147968703} + m_Layer: 5 + m_Name: serverName + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1147968702 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1147968701} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1623890023} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 100.01, y: 12.02} + m_SizeDelta: {x: 131.2, y: 22} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1147968703 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1147968701} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.23137255, g: 0.27450982, b: 0.4862745, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: dfa10de3e2846524089b5073175cd151, type: 3} + m_FontSize: 20 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 2 + m_MaxSize: 22 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: "\u516C\u6D4B\u670D\uFF1A\u897F\u6E56" +--- !u!222 &1147968704 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1147968701} + m_CullTransparentMesh: 1 --- !u!1 &1169725025 GameObject: m_ObjectHideFlags: 0 @@ -6063,6 +6373,87 @@ Canvas: m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 +--- !u!1 &1215657711 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1215657712} + - component: {fileID: 1215657713} + m_Layer: 5 + m_Name: serverPrefab (4) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1215657712 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1215657711} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1750898222} + m_Father: {fileID: 1216424339} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 120, y: -135} + m_SizeDelta: {x: 220, y: 75} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1215657713 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1215657711} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 0 + m_TargetGraphic: {fileID: 1750898223} + m_OnClick: + m_PersistentCalls: + m_Calls: [] --- !u!1 &1216424338 GameObject: m_ObjectHideFlags: 0 @@ -6095,6 +6486,7 @@ RectTransform: - {fileID: 1797413297} - {fileID: 1031085824} - {fileID: 962986871} + - {fileID: 1215657712} - {fileID: 75133058} m_Father: {fileID: 309034177} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -6664,7 +7056,7 @@ MonoBehaviour: m_HorizontalOverflow: 0 m_VerticalOverflow: 0 m_LineSpacing: 1 - m_Text: "\u60A8\u4E0D\u5177\u6709\u8BE5\u6743\u9650" + m_Text: "\u672A\u5F00\u653E\u524D\u77BB\u7248\u672C" --- !u!222 &1432043143 CanvasRenderer: m_ObjectHideFlags: 0 @@ -7175,6 +7567,83 @@ Canvas: m_SortingLayerID: 0 m_SortingOrder: 1 m_TargetDisplay: 0 +--- !u!1 &1623890022 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1623890023} + - component: {fileID: 1623890025} + - component: {fileID: 1623890024} + m_Layer: 5 + m_Name: profile + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1623890023 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1623890022} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1147968702} + - {fileID: 615433840} + m_Father: {fileID: 982753967} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 50, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1623890024 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1623890022} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 2100000, guid: 7701c15f96b443d408ac83222c9b869a, type: 2} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: f4d448bbf745cc54095d651af7394bff, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1623890025 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1623890022} + m_CullTransparentMesh: 1 --- !u!1 &1666387300 GameObject: m_ObjectHideFlags: 0 @@ -7749,6 +8218,82 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1746241769} m_CullTransparentMesh: 1 +--- !u!1 &1750898221 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1750898222} + - component: {fileID: 1750898224} + - component: {fileID: 1750898223} + m_Layer: 5 + m_Name: btm + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1750898222 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1750898221} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 982753967} + m_Father: {fileID: 1215657712} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 220, y: 75} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1750898223 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1750898221} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: a3ab628d6dae8c844bcc5bcfa59c40d8, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1750898224 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1750898221} + m_CullTransparentMesh: 1 --- !u!1 &1761920628 GameObject: m_ObjectHideFlags: 0 @@ -7808,7 +8353,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 21300000, guid: f4d448bbf745cc54095d651af7394bff, type: 3} + m_Sprite: {fileID: 21300000, guid: f14b64b175b40214b9e2b4ba8be24697, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 diff --git a/Assets/Scenes/gamePlay_gamePlay.unity b/Assets/Scenes/gamePlay_gamePlay.unity index 25a4a7aa..043c7fd2 100644 --- a/Assets/Scenes/gamePlay_gamePlay.unity +++ b/Assets/Scenes/gamePlay_gamePlay.unity @@ -2722,7 +2722,7 @@ Transform: m_GameObject: {fileID: 133835248} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 1.82, y: 17, z: 0} + m_LocalPosition: {x: 1.82, y: 30, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -14505,14 +14505,14 @@ MonoBehaviour: m_EditorClassIdentifier: startPoint: {fileID: 2067579813} endPoint: {fileID: 1144428590} - particlePrefab: {fileID: 8995107884300426767, guid: 96d352d647a28914c97c8e0732daf67e, type: 3} + particlePrefab: {fileID: 919132149155446097, guid: 0a88ecb14af08a64dae3e5832dff0d28, type: 3} particleMaterials: - material: {fileID: 2100000, guid: fda61a4e48014e24cacbb48c3f50a2f7, type: 2} isEnabled: 1 useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 1, g: 0.15408784, b: 0.15408784, a: 1} + key0: {r: 1, g: 0.5424528, b: 0.5424528, a: 1} key1: {r: 1, g: 0.73624206, b: 0.68867916, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -14545,7 +14545,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 0.5118936, g: 0.8742138, b: 0.21168062, a: 1} + key0: {r: 0.6892934, g: 1, b: 0.4292453, a: 1} key1: {r: 0.9284591, g: 1, b: 0.8459119, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -14578,7 +14578,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 1, g: 0.9086553, b: 0.17924517, a: 1} + key0: {r: 1, g: 0.9486097, b: 0.5330188, a: 1} key1: {r: 1, g: 0.98631495, b: 0.8522012, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -14611,7 +14611,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 0.7577474, g: 0.24842751, b: 1, a: 1} + key0: {r: 0.8827143, g: 0.6367924, b: 1, a: 1} key1: {r: 1, g: 0.75157225, b: 0.99807405, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -14644,7 +14644,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 0.24842751, g: 0.74991274, b: 1, a: 1} + key0: {r: 0.6367924, g: 0.8789309, b: 1, a: 1} key1: {r: 0.80817604, g: 0.93888795, b: 1, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -14672,25 +14672,28 @@ MonoBehaviour: m_ColorSpace: -1 m_NumColorKeys: 2 m_NumAlphaKeys: 2 - minParticleScale: 0.05 - maxParticleScale: 0.2 - enableParticles: 0 + minParticleScale: 0.03 + maxParticleScale: 0.06 + enableParticles: 1 physicsLayerName: particles sortingLayerName: Particles sortingOrder: -667 renderQueueOffset: 0 - baseEmitRate: 500 + baseEmitRate: 10 emitBatchSize: 2 chaosRandomDelay: 0.05 spawnOffsetX: 0.5 spawnOffsetY: 0.5 speedMultiplier: 1 emissionSpeedMultiplier: 1 + perFrameMoveMultiplier: 0.25 + enableRandomRotation: 1 + randomRotationRangeDegrees: 60 shakeIntensity: 0.1 particleParent: {fileID: 885394780} musicSource: {fileID: 845021791} beatmapManager: {fileID: 1261342020} - useAudioAnalysis: 0 + useAudioAnalysis: 1 audioSensitivity: 0.5 audioEmitBoost: 50 --- !u!1 &322497709 @@ -17541,7 +17544,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: 560/560 + m_text: 590/590 m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2} m_sharedMaterial: {fileID: -346136068272202111, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2} @@ -25639,7 +25642,7 @@ Transform: m_GameObject: {fileID: 659610115} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -0.04, y: 17, z: 0} + m_LocalPosition: {x: -0.04, y: 30, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -31642,7 +31645,7 @@ Transform: m_GameObject: {fileID: 706338008} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -3.75, y: 17, z: 0} + m_LocalPosition: {x: -3.75, y: 30, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -41180,7 +41183,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 21300000, guid: 09a8dd532919ad74daabf8e2cd345d26, type: 3} + m_Sprite: {fileID: 21300000, guid: 7df561841c29f7c4495809fa26c0fd87, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 @@ -58443,14 +58446,14 @@ MonoBehaviour: m_EditorClassIdentifier: startPoint: {fileID: 1927940169} endPoint: {fileID: 999995737} - particlePrefab: {fileID: 8995107884300426767, guid: 96d352d647a28914c97c8e0732daf67e, type: 3} + particlePrefab: {fileID: 919132149155446097, guid: 0a88ecb14af08a64dae3e5832dff0d28, type: 3} particleMaterials: - material: {fileID: 2100000, guid: fda61a4e48014e24cacbb48c3f50a2f7, type: 2} isEnabled: 0 useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 1, g: 0.15408784, b: 0.15408784, a: 1} + key0: {r: 1, g: 0.5424528, b: 0.5424528, a: 1} key1: {r: 1, g: 0.73624206, b: 0.68867916, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -58483,7 +58486,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 0.5118936, g: 0.8742138, b: 0.21168062, a: 1} + key0: {r: 0.6892934, g: 1, b: 0.4292453, a: 1} key1: {r: 0.9284591, g: 1, b: 0.8459119, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -58516,7 +58519,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 1, g: 0.9086553, b: 0.17924517, a: 1} + key0: {r: 1, g: 0.9486097, b: 0.5330188, a: 1} key1: {r: 1, g: 0.98631495, b: 0.8522012, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -58549,7 +58552,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 0.7577474, g: 0.24842751, b: 1, a: 1} + key0: {r: 0.8827143, g: 0.6367924, b: 1, a: 1} key1: {r: 1, g: 0.75157225, b: 0.99807405, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -58582,7 +58585,7 @@ MonoBehaviour: useGradientOverride: 1 colorGradient: serializedVersion: 2 - key0: {r: 0.24842751, g: 0.74991274, b: 1, a: 1} + key0: {r: 0.6367924, g: 0.8789309, b: 1, a: 1} key1: {r: 0.80817604, g: 0.93888795, b: 1, a: 1} key2: {r: 0, g: 0, b: 0, a: 0} key3: {r: 0, g: 0, b: 0, a: 0} @@ -58610,20 +58613,23 @@ MonoBehaviour: m_ColorSpace: -1 m_NumColorKeys: 2 m_NumAlphaKeys: 2 - minParticleScale: 0.05 - maxParticleScale: 0.2 - enableParticles: 0 + minParticleScale: 0.03 + maxParticleScale: 0.06 + enableParticles: 1 physicsLayerName: particles sortingLayerName: Particles sortingOrder: -667 renderQueueOffset: 0 - baseEmitRate: 500 + baseEmitRate: 10 emitBatchSize: 2 chaosRandomDelay: 0.05 spawnOffsetX: 0.5 spawnOffsetY: 0.5 speedMultiplier: 1 emissionSpeedMultiplier: 1 + perFrameMoveMultiplier: 0.25 + enableRandomRotation: 1 + randomRotationRangeDegrees: 60 shakeIntensity: 0.1 particleParent: {fileID: 885394780} musicSource: {fileID: 845021791} @@ -60555,14 +60561,14 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: slotIndex: 1 - maxHP: 580 - currentHP: 580 + maxHP: 1630 + currentHP: 1630 maxMana: 300 currentMana: 0 damageResistance: 0 - scoreEfficiency: 0.01 + scoreEfficiency: 0.09 bmm: {fileID: 1261342020} - attack: 8 + attack: 38 baseTrackScore: 1000 perfectRatio: 1 greatRatio: 0.75 @@ -61303,7 +61309,7 @@ Transform: m_GameObject: {fileID: 1638325220} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 3.66, y: 17, z: 0} + m_LocalPosition: {x: 3.66, y: 30, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -62715,7 +62721,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: "\u7231\u7433\u8FBE\u96C5" + m_text: "\u9ECE\u5999\u5999" m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 47babadf44fc728438ae9a924c42227e, type: 2} m_sharedMaterial: {fileID: 6312708493749940865, guid: 47babadf44fc728438ae9a924c42227e, type: 2} @@ -66674,7 +66680,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: 580/580 + m_text: 1630/1630 m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2} m_sharedMaterial: {fileID: -346136068272202111, guid: 7cad3f54ac7b53d4885f00c16f1a6929, type: 2} @@ -67321,14 +67327,14 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: slotIndex: 4 - maxHP: 560 - currentHP: 560 - maxMana: 100 + maxHP: 590 + currentHP: 590 + maxMana: 400 currentMana: 0 damageResistance: 0 scoreEfficiency: 0.01 bmm: {fileID: 1261342020} - attack: 8 + attack: 9 baseTrackScore: 1000 perfectRatio: 1 greatRatio: 0.75 @@ -73845,7 +73851,7 @@ Transform: m_GameObject: {fileID: 2100777313} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -1.9, y: 17, z: 0} + m_LocalPosition: {x: -1.9, y: 30, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] diff --git a/Assets/Scenes/gamePlay_gamePlay/gpGV.asset b/Assets/Scenes/gamePlay_gamePlay/gpGV.asset index 0a18256b..803b79e5 100644 --- a/Assets/Scenes/gamePlay_gamePlay/gpGV.asset +++ b/Assets/Scenes/gamePlay_gamePlay/gpGV.asset @@ -47,14 +47,14 @@ MonoBehaviour: m_Name: gpGV m_EditorClassIdentifier: components: - - {fileID: 2046802184354103511} - {fileID: 2590790448571127267} - - {fileID: -4028999061945789234} - {fileID: 1487594879138804974} - - {fileID: 9136543105356401684} - {fileID: 6346711178776092008} - {fileID: 5914684897888933277} - {fileID: 0} + - {fileID: 0} + - {fileID: 0} + - {fileID: 0} --- !u!114 &1487594879138804974 MonoBehaviour: m_ObjectHideFlags: 3 diff --git a/Assets/_eqpmtSys/eqpmtDesPrefab.cs b/Assets/_eqpmtSys/eqpmtDesPrefab.cs index 7eabdde0..cf6664e6 100644 --- a/Assets/_eqpmtSys/eqpmtDesPrefab.cs +++ b/Assets/_eqpmtSys/eqpmtDesPrefab.cs @@ -793,7 +793,7 @@ public class eqpmtDesPrefab : MonoBehaviour case equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency: return "得分效率"; case equipmentSO.EquipmentSpecialEffectType.HitManaRecovery: return "hit法力恢复"; case equipmentSO.EquipmentSpecialEffectType.HitDamageMultiplier: return "hit伤害倍率"; - case equipmentSO.EquipmentSpecialEffectType.HpLoseBase: return "hplosebase"; + case equipmentSO.EquipmentSpecialEffectType.HpLoseBase: return "基本生命损失"; case equipmentSO.EquipmentSpecialEffectType.Skill: return "技能"; default: return effectType.ToString(); } @@ -848,4 +848,3 @@ public class eqpmtDesPrefab : MonoBehaviour } } - diff --git a/Assets/_eqpmtSys/equipItemPrefab.cs b/Assets/_eqpmtSys/equipItemPrefab.cs index 6a1f66a6..93bd5bdb 100644 --- a/Assets/_eqpmtSys/equipItemPrefab.cs +++ b/Assets/_eqpmtSys/equipItemPrefab.cs @@ -68,6 +68,25 @@ public class equipItemPrefab : MonoBehaviour, IBeginDragHandler, IDragHandler, I } } + private void OnDisable() + { + if (spawnedDescription != null) + { + Destroy(spawnedDescription); + spawnedDescription = null; + } + + if (dragGhost != null) + { + Destroy(dragGhost); + dragGhost = null; + dragGhostRect = null; + } + + suppressNextClick = false; + isDraggingWithLeftButton = false; + } + public void Bind(equipmentSO so, Action onClick = null, Func onRightClick = null) { itemSO = so; diff --git a/Assets/_eqpmtSys/equipmentSO.cs b/Assets/_eqpmtSys/equipmentSO.cs index 00d7e869..75c86555 100644 --- a/Assets/_eqpmtSys/equipmentSO.cs +++ b/Assets/_eqpmtSys/equipmentSO.cs @@ -107,6 +107,11 @@ public class equipmentSO : ScriptableObject return maxLevelEffects != null && maxLevelEffects.Length > 0; } + public bool IsMaxLevelEffectActive() + { + return level >= 20 && HasFinalDreamEffect(); + } + public int GetTierStageIndex() { if (level >= 20) diff --git a/Assets/_mainSpine/op/特邀测试2.png b/Assets/_mainSpine/op/特邀测试2.png new file mode 100644 index 00000000..b2dcdc52 Binary files /dev/null and b/Assets/_mainSpine/op/特邀测试2.png differ diff --git a/Assets/_mainSpine/op/特邀测试2.png.meta b/Assets/_mainSpine/op/特邀测试2.png.meta new file mode 100644 index 00000000..d3159746 --- /dev/null +++ b/Assets/_mainSpine/op/特邀测试2.png.meta @@ -0,0 +1,156 @@ +fileFormatVersion: 2 +guid: 95f2af828653163469c9f827bdaaa49c +TextureImporter: + internalIDToNameTable: + - first: + 213: -2318275076012390624 + second: "\u7279\u9080\u6D4B\u8BD52._0" + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: "\u7279\u9080\u6D4B\u8BD52._0" + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 3600 + height: 5400 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 027c3a2e915d3dfd0800000000000000 + internalID: -2318275076012390624 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + "\u7279\u9080\u6D4B\u8BD52._0": -2318275076012390624 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/artworks/selectYourSongFirst/heroes_level_image/levelBar_controller.cs b/Assets/artworks/selectYourSongFirst/heroes_level_image/levelBar_controller.cs index 5fd65dfc..e0f516f7 100644 --- a/Assets/artworks/selectYourSongFirst/heroes_level_image/levelBar_controller.cs +++ b/Assets/artworks/selectYourSongFirst/heroes_level_image/levelBar_controller.cs @@ -189,9 +189,24 @@ public class levelBar_controller : MonoBehaviour } } - // Documentation text normalized. + // 等级评级改从持久化账本(AllyHeroDeployLedger)取真值,而非 SO 上被打包烘焙的镜像字段。 + // 修复客户端更新后 SO 镜像为出厂 0 值、卡片显示过期等级、需重新给经验才同步的问题。 private string GetRatingFromSO(AllyHero_SO so) { - return so != null ? so.GetDisplayLevelRatingKey() : "C"; + if (so == null) + { + return "C"; + } + + if (so.ally_heroID <= 0) + { + return so.GetDisplayLevelRatingKey(); + } + + AllyHeroDeployLedger ledger = AllyHeroDeployLedger.EnsureInstance(); + int currentExp = ledger.GetCurrentExp(so.ally_heroID); + int unlockedTier = ledger.GetUnlockedTierIndex(so.ally_heroID); + bool levelLocked = ledger.IsLevelLockEnabled(so.ally_heroID); + return so.GetDisplayLevelRatingKeyFromGrowth(currentExp, unlockedTier, levelLocked); } } diff --git a/Assets/artworks/selectYourSongFirst/heroes_level_image/roleCard_prefabController.cs b/Assets/artworks/selectYourSongFirst/heroes_level_image/roleCard_prefabController.cs index 664c2b6a..0d925d2a 100644 --- a/Assets/artworks/selectYourSongFirst/heroes_level_image/roleCard_prefabController.cs +++ b/Assets/artworks/selectYourSongFirst/heroes_level_image/roleCard_prefabController.cs @@ -198,9 +198,25 @@ public class roleCard_prefabController : MonoBehaviour, IDropHandler, IBeginDrag } } + // 等级评级改从持久化账本(AllyHeroDeployLedger)取真值,而非 SO 上被打包烘焙的镜像字段。 + // 修复客户端更新后 SO 镜像为出厂 0 值、卡片显示过期等级、需重新给经验才同步的问题。 private string GetRatingFromSO(AllyHero_SO so) { - return so != null ? so.GetDisplayLevelRatingKey() : "C"; + if (so == null) + { + return "C"; + } + + if (so.ally_heroID <= 0) + { + return so.GetDisplayLevelRatingKey(); + } + + AllyHeroDeployLedger ledger = AllyHeroDeployLedger.EnsureInstance(); + int currentExp = ledger.GetCurrentExp(so.ally_heroID); + int unlockedTier = ledger.GetUnlockedTierIndex(so.ally_heroID); + bool levelLocked = ledger.IsLevelLockEnabled(so.ally_heroID); + return so.GetDisplayLevelRatingKeyFromGrowth(currentExp, unlockedTier, levelLocked); } // ---------------- Drag implementation so roleCard entries can be dragged as source ---------------- diff --git a/Assets/artworks/selectYourSongFirst/heroes_level_image/slots_heroSlots.cs b/Assets/artworks/selectYourSongFirst/heroes_level_image/slots_heroSlots.cs index b39e1e25..0cc60557 100644 --- a/Assets/artworks/selectYourSongFirst/heroes_level_image/slots_heroSlots.cs +++ b/Assets/artworks/selectYourSongFirst/heroes_level_image/slots_heroSlots.cs @@ -948,10 +948,26 @@ public class slots_heroSlots : MonoBehaviour, IDropHandler, IBeginDragHandler, I } } - // Documentation text normalized. + // 等级评级改从持久化账本(AllyHeroDeployLedger)取真值,而非 SO 上被打包烘焙的镜像字段。 + // 修复:客户端更新后 SO 镜像为出厂 0 值,若账本尚未回写 SO 就渲染卡片,会显示过期等级, + // 且需玩家重新给一次经验才同步。这里直接读账本(getter 内部保证 InitializeIfNeeded),从根源消除不一致。 private string GetRatingFromSO(AllyHero_SO so) { - return so != null ? so.GetDisplayLevelRatingKey() : "C"; + if (so == null) + { + return "C"; + } + + if (so.ally_heroID <= 0) + { + return so.GetDisplayLevelRatingKey(); + } + + AllyHeroDeployLedger ledger = AllyHeroDeployLedger.EnsureInstance(); + int currentExp = ledger.GetCurrentExp(so.ally_heroID); + int unlockedTier = ledger.GetUnlockedTierIndex(so.ally_heroID); + bool levelLocked = ledger.IsLevelLockEnabled(so.ally_heroID); + return so.GetDisplayLevelRatingKeyFromGrowth(currentExp, unlockedTier, levelLocked); } // Handle detail button click diff --git a/Assets/idolsDisplay/UI_Idols.cs b/Assets/idolsDisplay/UI_Idols.cs index bbd5ebba..0b68dab5 100644 --- a/Assets/idolsDisplay/UI_Idols.cs +++ b/Assets/idolsDisplay/UI_Idols.cs @@ -91,9 +91,11 @@ public class UI_Idols : MonoBehaviour private int pendingGrowthAnimationHeroId = -1; private Tween idolExpBarTween; private Coroutine startupSkillValidationRoutine; + private bool hasStarted; private void Start() { + hasStarted = true; BindQuitButton(); InitializeSectionToggles(); RebuildCards(); @@ -108,7 +110,10 @@ public class UI_Idols : MonoBehaviour AllyHeroDeployLedger.EnsureInstance().OnHeroGrowthChanged += HandleHeroGrowthChanged; StoreOwnershipLedger.EnsureInstance().OnOwnershipChanged += HandleOwnershipChanged; InitializeSectionToggles(); - RebuildCards(); + if (hasStarted) + { + RebuildCards(); + } HandleOverlayPanelsVisibilityChanged(btmandtopController.CurrentOverlayPanelsVisible); } @@ -399,6 +404,7 @@ public class UI_Idols : MonoBehaviour AllyHero_SO firstDisplayedHero = null; AllyHero_SO preferredHero = currentSelectedHero; bool preferredHeroDisplayed = false; + int visibleCardCount = 0; for (int i = 0; i < heroes.Count; i++) { @@ -414,8 +420,8 @@ public class UI_Idols : MonoBehaviour } IdolLevelSnapshot snapshot = BuildLevelSnapshot(hero); - GameObject instance = Instantiate(idolCardPrefab, idolCardParent); - spawnedCards.Add(instance); + GameObject instance = GetOrCreateCard(visibleCardCount); + visibleCardCount++; idolCardPrefab card = instance.GetComponent(); if (card == null) @@ -686,8 +692,7 @@ public class UI_Idols : MonoBehaviour continue; } - GameObject instance = Instantiate(ssPrefab, ssParent); - spawnedSpecialSkillEntries.Add(instance); + GameObject instance = GetOrCreateSpecialSkillEntry(shown); ssPrefab display = instance.GetComponent(); if (display != null) @@ -760,47 +765,15 @@ public class UI_Idols : MonoBehaviour { for (int i = spawnedCards.Count - 1; i >= 0; i--) { - if (spawnedCards[i] == null) + GameObject card = spawnedCards[i]; + if (card == null) { continue; } -#if UNITY_EDITOR - if (!Application.isPlaying) + if (card.activeSelf) { - DestroyImmediate(spawnedCards[i]); - } - else -#endif - { - Destroy(spawnedCards[i]); - } - } - - spawnedCards.Clear(); - - if (idolCardParent == null) - { - return; - } - - for (int i = idolCardParent.childCount - 1; i >= 0; i--) - { - Transform child = idolCardParent.GetChild(i); - if (child == null || child.GetComponent() == null) - { - continue; - } - -#if UNITY_EDITOR - if (!Application.isPlaying) - { - DestroyImmediate(child.gameObject); - } - else -#endif - { - Destroy(child.gameObject); + card.SetActive(false); } } } @@ -809,49 +782,61 @@ public class UI_Idols : MonoBehaviour { for (int i = spawnedSpecialSkillEntries.Count - 1; i >= 0; i--) { - if (spawnedSpecialSkillEntries[i] == null) + GameObject entry = spawnedSpecialSkillEntries[i]; + if (entry == null) { continue; } -#if UNITY_EDITOR - if (!Application.isPlaying) + if (entry.activeSelf) { - DestroyImmediate(spawnedSpecialSkillEntries[i]); - } - else -#endif - { - Destroy(spawnedSpecialSkillEntries[i]); + entry.SetActive(false); } } + } - spawnedSpecialSkillEntries.Clear(); - - if (ssParent == null) + private GameObject GetOrCreateCard(int index) + { + while (spawnedCards.Count <= index) { - return; + GameObject instance = Instantiate(idolCardPrefab, idolCardParent); + spawnedCards.Add(instance); } - for (int i = ssParent.childCount - 1; i >= 0; i--) + GameObject card = spawnedCards[index]; + if (card != null) { - Transform child = ssParent.GetChild(i); - if (child == null || child.GetComponent() == null) + card.transform.SetParent(idolCardParent, false); + card.transform.SetSiblingIndex(index); + if (!card.activeSelf) { - continue; - } - -#if UNITY_EDITOR - if (!Application.isPlaying) - { - DestroyImmediate(child.gameObject); - } - else -#endif - { - Destroy(child.gameObject); + card.SetActive(true); } } + + return card; + } + + private GameObject GetOrCreateSpecialSkillEntry(int index) + { + while (spawnedSpecialSkillEntries.Count <= index) + { + GameObject instance = Instantiate(ssPrefab, ssParent); + spawnedSpecialSkillEntries.Add(instance); + } + + GameObject entry = spawnedSpecialSkillEntries[index]; + if (entry != null) + { + entry.transform.SetParent(ssParent, false); + entry.transform.SetSiblingIndex(index); + if (!entry.activeSelf) + { + entry.SetActive(true); + } + } + + return entry; } private static void SetText(Text target, string value) @@ -1023,7 +1008,22 @@ public class UI_Idols : MonoBehaviour public void RefreshCurrentHeroDetails() { - ApplyHeroDetails(currentSelectedHero, false); + if (currentSelectedHero == null) + { + ApplyHeroDetails(null, false); + return; + } + + IdolLevelSnapshot snapshot = BuildLevelSnapshot(currentSelectedHero); + SetSliderVisual(idol_expBar, snapshot.sliderValue, snapshot.sliderColor, false); + SetText(idol_expText, snapshot.detailXpText); + SetImageSprite(levelIcon, snapshot.levelIcon); + ApplyLevelDetails(GetEffectiveLevelInfoFromLedger(currentSelectedHero)); + + if (ieqpmt != null) + { + ieqpmt.RefreshCurrentHeroEquipmentState(); + } } private void ApplyBehaviourRadar(AllyHero_SO hero) diff --git a/Assets/idolsDisplay/idolEquipments.cs b/Assets/idolsDisplay/idolEquipments.cs index 5d554374..b8b94705 100644 --- a/Assets/idolsDisplay/idolEquipments.cs +++ b/Assets/idolsDisplay/idolEquipments.cs @@ -73,6 +73,10 @@ public class idolEquipments : MonoBehaviour private float pendingScrollbarValue = 1f; private Coroutine restoreScrollbarRoutine; private bool suppressDropdownCallbacks; + private bool hasStarted; + private bool equipmentOwnershipCacheReady; + private readonly Dictionary ownershipByReference = new Dictionary(); + private readonly Dictionary ownershipByName = new Dictionary(StringComparer.Ordinal); private static readonly (EquipFilterMode mode, string label)[] FilterOptions = { @@ -102,6 +106,7 @@ public class idolEquipments : MonoBehaviour private void Start() { + hasStarted = true; InitializeDropdowns(); InitializeOnlyCanEquipToggle(); BindControls(); @@ -116,8 +121,11 @@ public class idolEquipments : MonoBehaviour InitializeOnlyCanEquipToggle(); BindControls(); RestoreEquippedFromHero(); - RebuildBag(); - RefreshEquippedSlot(); + if (hasStarted) + { + RebuildBag(); + RefreshEquippedSlot(); + } } private void OnDisable() @@ -267,6 +275,8 @@ public class idolEquipments : MonoBehaviour return; } + RefreshEquipmentOwnershipCache(); + List equipments = LoadEquipments() .Where(e => e != null) .Where(e => !equipSmelt.IsEquipmentAssignedToSmeltPool(e) && !equipSmelt.ShouldHideConsumedEquipment(e)) @@ -292,7 +302,7 @@ public class idolEquipments : MonoBehaviour int batchSize = Mathf.Max(1, i_batch); for (int i = 0; i < equipments.Count; i++) { - SpawnBagItem(equipments[i]); + SpawnBagItem(equipments[i], i); if ((i + 1) % batchSize == 0) { yield return null; @@ -307,18 +317,18 @@ public class idolEquipments : MonoBehaviour { for (int i = 0; i < equipments.Count; i++) { - SpawnBagItem(equipments[i]); + SpawnBagItem(equipments[i], i); } } - private void SpawnBagItem(equipmentSO equipment) + private void SpawnBagItem(equipmentSO equipment, int index) { if (equipment == null || eqpmtItemPrefab == null || eqpmtBagParent == null) { return; } - GameObject instance = Instantiate(eqpmtItemPrefab, eqpmtBagParent); + GameObject instance = GetOrCreateBagItem(index); instance.name = string.IsNullOrWhiteSpace(equipment.GetDisplayTierName()) ? equipment.name : equipment.GetDisplayTierName(); equipItemPrefab item = instance.GetComponent(); @@ -332,8 +342,6 @@ public class idolEquipments : MonoBehaviour item.itemButton.interactable = true; } } - - spawnedBagItems.Add(instance); } private bool HandleBagItemRightClicked(equipmentSO equipment) @@ -372,7 +380,8 @@ public class idolEquipments : MonoBehaviour { currentHero.SetEquippedEquipment(equipment); } - RefreshAllIdolPanels(); + equipmentOwnershipCacheReady = false; + RefreshAllIdolPanels(GetComponentInParent(true)); RefreshEquippedSlot(); RebuildBag(); } @@ -384,7 +393,8 @@ public class idolEquipments : MonoBehaviour { currentHero.ClearEquippedEquipment(); } - RefreshAllIdolPanels(); + equipmentOwnershipCacheReady = false; + RefreshAllIdolPanels(GetComponentInParent(true)); RefreshEquippedSlot(); RebuildBag(); } @@ -392,6 +402,15 @@ public class idolEquipments : MonoBehaviour public void SetHero(AllyHero_SO hero) { currentHero = hero; + equipmentOwnershipCacheReady = false; + RestoreEquippedFromHero(); + RefreshEquippedSlot(); + RebuildBag(); + } + + public void RefreshCurrentHeroEquipmentState() + { + equipmentOwnershipCacheReady = false; RestoreEquippedFromHero(); RefreshEquippedSlot(); RebuildBag(); @@ -493,22 +512,21 @@ public class idolEquipments : MonoBehaviour return false; } - AllyHero_SO[] heroes = LoadHeroAssetsForOwnershipCheck(); - for (int i = 0; i < heroes.Length; i++) + if (!equipmentOwnershipCacheReady) { - AllyHero_SO hero = heroes[i]; - if (hero == null || hero == currentHero) - { - continue; - } + RefreshEquipmentOwnershipCache(); + } - hero.LoadEquippedEquipmentFromLocal(); - equipmentSO equipped = hero.GetEquippedEquipmentResolved(); - if (equipped == equipment || (!string.IsNullOrWhiteSpace(equipped?.name) && equipped.name == equipment.name)) - { - occupiedHero = hero; - return true; - } + if (ownershipByReference.TryGetValue(equipment, out occupiedHero) && occupiedHero != null) + { + return true; + } + + if (!string.IsNullOrWhiteSpace(equipment.name) && + ownershipByName.TryGetValue(equipment.name, out occupiedHero) && + occupiedHero != null) + { + return true; } return false; @@ -567,13 +585,13 @@ public class idolEquipments : MonoBehaviour } } - private void RefreshAllIdolPanels() + private void RefreshAllIdolPanels(UI_Idols skipPanel = null) { UI_Idols[] panels = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < panels.Length; i++) { UI_Idols panel = panels[i]; - if (panel != null) + if (panel != null && panel != skipPanel) { panel.RefreshCurrentHeroDetails(); } @@ -582,16 +600,14 @@ public class idolEquipments : MonoBehaviour private void ClearBagItems() { - for (int i = eqpmtBagParent != null ? eqpmtBagParent.childCount - 1 : -1; i >= 0; i--) + for (int i = spawnedBagItems.Count - 1; i >= 0; i--) { - Transform child = eqpmtBagParent.GetChild(i); - if (child != null) + GameObject item = spawnedBagItems[i]; + if (item != null && item.activeSelf) { - Destroy(child.gameObject); + item.SetActive(false); } } - - spawnedBagItems.Clear(); } private void CacheScrollbarValue() @@ -765,7 +781,7 @@ public class idolEquipments : MonoBehaviour else #endif { - results.AddRange(Resources.LoadAll(runtimeEquipmentFolder).Where(e => e != null)); + results.AddRange(RuntimeResourcesCache.LoadAll(runtimeEquipmentFolder).Where(e => e != null)); } for (int i = 0; i < results.Count; i++) @@ -903,4 +919,57 @@ public class idolEquipments : MonoBehaviour string raw = equipment.name.Replace("(Clone)", string.Empty).Replace("(Preview)", string.Empty).Replace("(TourPreview)", string.Empty); return raw.StartsWith("type", StringComparison.OrdinalIgnoreCase) && raw.Length > 4 ? raw.Substring(4) : raw; } + + private GameObject GetOrCreateBagItem(int index) + { + while (spawnedBagItems.Count <= index) + { + GameObject instance = Instantiate(eqpmtItemPrefab, eqpmtBagParent); + spawnedBagItems.Add(instance); + } + + GameObject item = spawnedBagItems[index]; + if (item != null) + { + item.transform.SetParent(eqpmtBagParent, false); + item.transform.SetSiblingIndex(index); + if (!item.activeSelf) + { + item.SetActive(true); + } + } + + return item; + } + + private void RefreshEquipmentOwnershipCache() + { + ownershipByReference.Clear(); + ownershipByName.Clear(); + + AllyHero_SO[] heroes = LoadHeroAssetsForOwnershipCheck(); + for (int i = 0; i < heroes.Length; i++) + { + AllyHero_SO hero = heroes[i]; + if (hero == null || hero == currentHero) + { + continue; + } + + hero.LoadEquippedEquipmentFromLocal(); + equipmentSO equipped = hero.GetEquippedEquipmentResolved(); + if (equipped == null) + { + continue; + } + + ownershipByReference[equipped] = hero; + if (!string.IsNullOrWhiteSpace(equipped.name)) + { + ownershipByName[equipped.name] = hero; + } + } + + equipmentOwnershipCacheReady = true; + } } diff --git a/Assets/idolsDisplay/idolSkillsHub.cs b/Assets/idolsDisplay/idolSkillsHub.cs index 803a6440..171ace0e 100644 --- a/Assets/idolsDisplay/idolSkillsHub.cs +++ b/Assets/idolsDisplay/idolSkillsHub.cs @@ -30,6 +30,7 @@ public class idolSkillsHub : MonoBehaviour [Header("runtime")] public AllyHero_SO currentHero; private int selectedDetailSkillGroupId = -1; + private readonly List spawnedSkillItems = new List(); public void SetHero(AllyHero_SO hero) { @@ -61,6 +62,8 @@ public class idolSkillsHub : MonoBehaviour int currentTierNumber = GetCurrentTierNumber(currentHero); int maxSkillSlots = currentHero.GetEffectiveSkillSlotLimit(); + int equippedCount = GetEquippedCount(); + int visibleIndex = 0; for (int i = 0; i < currentHero.skillGroups.Length; i++) { @@ -72,7 +75,7 @@ public class idolSkillsHub : MonoBehaviour bool unlocked = currentTierNumber >= Mathf.Clamp(group.thisSkill_levelLimit, 1, 4); bool equipped = IsEquipped(group.skillGroupID); - bool slotAvailable = equipped || GetEquippedCount() < maxSkillSlots; + bool slotAvailable = equipped || equippedCount < maxSkillSlots; string enableText; bool enableToggleValue; @@ -106,7 +109,8 @@ public class idolSkillsHub : MonoBehaviour Color bottomColor = equipped ? enabled_btmColor : (unlocked ? disabled_btmColor : unlock_btmColor); Material iconMaterial = unlocked ? null : disgot_grayMtr; - GameObject instance = Instantiate(deSkillPrefab, deSkillContainer); + GameObject instance = GetOrCreateSkillItem(visibleIndex); + visibleIndex++; deSkillPrefab item = instance.GetComponent(); if (item == null) { @@ -286,31 +290,40 @@ public class idolSkillsHub : MonoBehaviour private void ClearItems() { - if (deSkillContainer == null) + for (int i = spawnedSkillItems.Count - 1; i >= 0; i--) { - return; - } - - for (int i = deSkillContainer.childCount - 1; i >= 0; i--) - { - Transform child = deSkillContainer.GetChild(i); - if (child == null) + GameObject item = spawnedSkillItems[i]; + if (item == null) { continue; } -#if UNITY_EDITOR - if (!Application.isPlaying) + if (item.activeSelf) { - DestroyImmediate(child.gameObject); + item.SetActive(false); } - else - { - Destroy(child.gameObject); - } -#else - Destroy(child.gameObject); -#endif } } + + private GameObject GetOrCreateSkillItem(int index) + { + while (spawnedSkillItems.Count <= index) + { + GameObject instance = Instantiate(deSkillPrefab, deSkillContainer); + spawnedSkillItems.Add(instance); + } + + GameObject item = spawnedSkillItems[index]; + if (item != null) + { + item.transform.SetParent(deSkillContainer, false); + item.transform.SetSiblingIndex(index); + if (!item.activeSelf) + { + item.SetActive(true); + } + } + + return item; + } } diff --git a/Assets/playerInfoDisplay/UI_Player.prefab b/Assets/playerInfoDisplay/UI_Player.prefab index 05f0be5c..5e7a5cb1 100644 --- a/Assets/playerInfoDisplay/UI_Player.prefab +++ b/Assets/playerInfoDisplay/UI_Player.prefab @@ -10967,7 +10967,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 8139719423079072813, guid: 16f0765cc15fce04f828ecd44d1dbf91, type: 3} + m_Sprite: {fileID: 21300000, guid: ddce9ea8759b6ca47b6d177982d1ed1e, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 diff --git a/Assets/playerInfoDisplay/uls/uLevel_skills.cs b/Assets/playerInfoDisplay/uls/uLevel_skills.cs index 3a1436f1..f9b805c2 100644 --- a/Assets/playerInfoDisplay/uls/uLevel_skills.cs +++ b/Assets/playerInfoDisplay/uls/uLevel_skills.cs @@ -1,5 +1,6 @@ using UnityEngine; using UnityEngine.UI; +using Bansonic; public class uLevel_skills : MonoBehaviour { @@ -29,6 +30,8 @@ public class uLevel_skills : MonoBehaviour private Color defaultNeedCoinsColor = Color.white; private const int SkillSwitchCooldownMatches = 3; private const int SkillSwitchMinimumCost = 200; + private const int ChallengerModelSkillIndex = 3; + private const string ChallengerModelBlockedMessage = "\u6682\u65f6\u65e0\u6cd5\u5207\u6362\u8fd9\u4e2a\u6280\u80fd"; private void Start() { @@ -101,6 +104,16 @@ public class uLevel_skills : MonoBehaviour if (entry == null || !IsSkillUnlocked(entry)) return; + if (index == ChallengerModelSkillIndex) + { + gNotice.error.display(ChallengerModelBlockedMessage); + pendingSkillIndex = -1; + RefreshDetailPanel(); + RefreshChangeUi(); + RefreshStatusesOnly(); + return; + } + int currentEnabledIndex = GetEnabledSkillIndex(); if (index == currentEnabledIndex) { @@ -255,6 +268,16 @@ public class uLevel_skills : MonoBehaviour return; } + if (pendingSkillIndex == ChallengerModelSkillIndex) + { + gNotice.error.display(ChallengerModelBlockedMessage); + pendingSkillIndex = -1; + RefreshStatusesOnly(); + RefreshDetailPanel(); + RefreshChangeUi(); + return; + } + int switchCost = GetSkillSwitchCost(); bool spent = Application.isPlaying ? PlayerEconomyLedger.EnsureInstance().TrySpendCoins(switchCost) diff --git a/Assets/scripts/Combat/SkillBuilder.cs b/Assets/scripts/Combat/SkillBuilder.cs index 7fdb3cca..447e8ccc 100644 --- a/Assets/scripts/Combat/SkillBuilder.cs +++ b/Assets/scripts/Combat/SkillBuilder.cs @@ -59,7 +59,7 @@ public class SkillBuilder : MonoBehaviour [Tooltip("(Fallback) Damage multiplier applied to ally base attack for Great hits if SO level value not present")] public float damageMultiplierGreat = 0.75f; [Tooltip("(Fallback) Damage multiplier applied to ally base attack for Perfect hits if SO level value not present")] public float damageMultiplierPerfect = 1f; - [Tooltip("(Fallback) Base HP loss on Miss will be (missHpLossBase * (1 - damageResistance)) if SO level value not present")] public float missHpLossBase = 10f; + [Tooltip("(Fallback) 若 SO 等级数据未提供,则 Miss 基本生命损失 = missHpLossBase * (1 - damageResistance)")] public float missHpLossBase = 10f; // --------- Caches to avoid first-hit hitch (Resources.LoadAll) --------- private AllyHero_SO[] _allAllyHeroSOs; diff --git a/Assets/scripts/Data/AllyHero_SO.cs b/Assets/scripts/Data/AllyHero_SO.cs index 3e2c3ebe..13885bf7 100644 --- a/Assets/scripts/Data/AllyHero_SO.cs +++ b/Assets/scripts/Data/AllyHero_SO.cs @@ -9,6 +9,15 @@ using UnityEditor; [CreateAssetMenu(fileName = "NewAllyHero", menuName = "SO_Data/AllyHero")] public class AllyHero_SO : ScriptableObject { + public enum AllyRolePosition + { + 攻击, + 回血, + 回蓝, + 加分, + 综合 + } + private const string EquippedEquipmentSaveCategory = "ally_equipped_equipment"; private const string EquippedEquipmentRecoverySlotPrefix = "ally_equipped_equipment_"; @@ -31,7 +40,13 @@ public class AllyHero_SO : ScriptableObject public string ally_heroDesignation; public int ally_heroID; public bool isUnlocked; + [Header("Role Setup")] + [InspectorName("所属阵营")] + [Tooltip("Uses the same category list as equipment skill types.")] public equipmentSO.EquipmentSkillType allyType; + [InspectorName("角色定位")] + [Tooltip("Primary combat role used for configuration and display.")] + public AllyRolePosition allyRolePosition = AllyRolePosition.综合; [Tooltip("Optional obsession tag used by memory skills such as 30011012. Leave empty to ignore mismatch checks.")] public string obsessionTag; @@ -506,12 +521,57 @@ public class AllyHero_SO : ScriptableObject public string GetDisplayLevelRatingKey() { int displayIndex = GetDisplayLevelIndex(); + return DisplayLevelIndexToRatingKey(displayIndex); + } + + // 用外部传入的成长值(来自持久化账本 AllyHeroDeployLedger)计算等级评级, + // 而不是读取 SO 上被打包烘焙的镜像字段。用于 teamSelector 等直接依赖账本真相的界面, + // 避免"账本已加载但尚未回写 SO 镜像"窗口内读到过期的烘焙值。判级规则与 GetDisplayLevelRatingKey 完全一致。 + public string GetDisplayLevelRatingKeyFromGrowth(int currentExp, int unlockedTierIndex, bool levelLocked) + { + int displayIndex = ResolveDisplayLevelIndexFromGrowth(currentExp, unlockedTierIndex, levelLocked); + return DisplayLevelIndexToRatingKey(displayIndex); + } + + private static string DisplayLevelIndexToRatingKey(int displayIndex) + { if (displayIndex <= 0) return "C"; if (displayIndex == 1) return "B"; if (displayIndex == 2) return "A"; return "S"; } + private int ResolveDisplayLevelIndexFromGrowth(int currentExp, int unlockedTierIndex, bool levelLocked) + { + List sorted = BuildSortedLevelStats(); + if (sorted == null || sorted.Count == 0) + { + return -1; + } + + int safeExp = Mathf.Max(0, currentExp); + int expQualifiedIndex = 0; + for (int i = 0; i < sorted.Count; i++) + { + if (safeExp >= sorted[i].requiredEXP) + { + expQualifiedIndex = i; + } + else + { + break; + } + } + + if (!levelLocked) + { + return expQualifiedIndex; + } + + int unlockedIndex = Mathf.Clamp(unlockedTierIndex, 0, sorted.Count - 1); + return Mathf.Clamp(Mathf.Min(expQualifiedIndex, unlockedIndex), 0, sorted.Count - 1); + } + public AllyLevelInfo GetEffectiveLevelForCurrentEXP() { List sorted = BuildSortedLevelStats(); @@ -656,7 +716,11 @@ public class AllyHero_SO : ScriptableObject total += SumEffectValues(equipment.typeSameEffects, effectType); } - total += SumEffectValues(equipment.maxLevelEffects, effectType); + if (equipment.IsMaxLevelEffectActive()) + { + total += SumEffectValues(equipment.maxLevelEffects, effectType); + } + return total; } diff --git a/Assets/scripts/Team/newTeamSelector.cs b/Assets/scripts/Team/newTeamSelector.cs index e51a616f..80969020 100644 --- a/Assets/scripts/Team/newTeamSelector.cs +++ b/Assets/scripts/Team/newTeamSelector.cs @@ -94,6 +94,9 @@ public class newTeamSelector : MonoBehaviour return; } + // 构建卡片前先确保成长账本已完成加载与镜像同步,消除"账本未初始化就读等级"的时序竞争。 + AllyHeroDeployLedger.EnsureInstance().InitializeIfNeeded(); + // Clear existing for (int i = container.childCount - 1; i >= 0; i--) { @@ -449,7 +452,7 @@ public class newTeamSelector : MonoBehaviour return null; AllyHero_SO hero = FindHeroById(heroId); - return hero != null ? GetRatingFromSO(hero) : null; + return hero != null ? GetRatingFromLedger(hero) : null; } private IEnumerator RefreshSkillsGradually() @@ -519,8 +522,26 @@ public class newTeamSelector : MonoBehaviour } } - private string GetRatingFromSO(AllyHero_SO so) + // 等级评级改从持久化账本(AllyHeroDeployLedger)取真值,而非 SO 上被打包烘焙的镜像字段。 + // 修复:客户端更新后 SO 镜像为出厂 0 值,若账本尚未回写 SO 就构建卡片,会显示过期等级, + // 且需玩家重新给一次经验才同步。这里直接读账本(getter 内部会保证 InitializeIfNeeded),从根源消除不一致。 + private string GetRatingFromLedger(AllyHero_SO so) { - return so != null ? so.GetDisplayLevelRatingKey() : "C"; + if (so == null) + { + return "C"; + } + + if (so.ally_heroID <= 0) + { + // 无有效 heroID 无法查账本,退回 SO 自身判级,保持原行为。 + return so.GetDisplayLevelRatingKey(); + } + + AllyHeroDeployLedger ledger = AllyHeroDeployLedger.EnsureInstance(); + int currentExp = ledger.GetCurrentExp(so.ally_heroID); + int unlockedTier = ledger.GetUnlockedTierIndex(so.ally_heroID); + bool levelLocked = ledger.IsLevelLockEnabled(so.ally_heroID); + return so.GetDisplayLevelRatingKeyFromGrowth(currentExp, unlockedTier, levelLocked); } } diff --git a/Assets/scripts/UI/Panel/dontdestroyonload/btm and top.prefab b/Assets/scripts/UI/Panel/dontdestroyonload/btm and top.prefab index b4808f1e..676da0f5 100644 --- a/Assets/scripts/UI/Panel/dontdestroyonload/btm and top.prefab +++ b/Assets/scripts/UI/Panel/dontdestroyonload/btm and top.prefab @@ -6516,6 +6516,7 @@ MonoBehaviour: notice_display: {fileID: 920723299105391293} market_launch: {fileID: 5379967691629323644} button_userBag: {fileID: 7063352251955770202} + enableGuideDisplay: 0 userGuideButton: {fileID: 6945238783669072533} guideDisplayImage: {fileID: 2438452334951697137} preIMGb: {fileID: 7091982211545804283} diff --git a/Assets/scripts/UI/Panel/dontdestroyonload/btmandtopController.cs b/Assets/scripts/UI/Panel/dontdestroyonload/btmandtopController.cs index fa396004..7ca1b0dc 100644 --- a/Assets/scripts/UI/Panel/dontdestroyonload/btmandtopController.cs +++ b/Assets/scripts/UI/Panel/dontdestroyonload/btmandtopController.cs @@ -14,6 +14,8 @@ using GameServer.Client; public class btmandtopController : MonoBehaviour, ICancelHandler { + private const string MusicLockedMessage = "暂无可选择的音乐"; + public static event System.Action GlobalSettingsVisibilityChanged; public static event System.Action GlobalOverlayPanelVisibilityChanged; public static bool CurrentSettingsVisible { get; private set; } @@ -47,6 +49,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler public Button button_userBag; [Header("User Guide")] + [Tooltip("是否启用指引 sprite 展示。true:点击指引按钮时按场景展示 guideMappings 的 sprite;false:点击无反应,不展示任何指引。")] + [SerializeField] private bool enableGuideDisplay = true; [SerializeField] private Button userGuideButton; [SerializeField] private Image guideDisplayImage; [SerializeField] private Button preIMGb; @@ -227,7 +231,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler EnsureMusicPicRoot(); SetupMusicPicDefault(); if (button_Music != null) - button_Music.onClick.AddListener(ToggleMusicPicLocal); + button_Music.onClick.AddListener(HandleMusicButtonLockedClick); UpdateSteamUserInfo(); InitializeMailRedPot(); @@ -473,6 +477,11 @@ public class btmandtopController : MonoBehaviour, ICancelHandler SetMusicPicVisible(!musicPicVisible, false); } + private void HandleMusicButtonLockedClick() + { + gNotice.error.display(MusicLockedMessage); + } + private void SetMusicPicVisible(bool visible, bool instant) { if (musicPicRoot == null) @@ -578,7 +587,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler closeGuideButton.onClick.RemoveListener(CloseGuideDisplay); if (button_Music != null) - button_Music.onClick.RemoveListener(ToggleMusicPicLocal); + button_Music.onClick.RemoveListener(HandleMusicButtonLockedClick); if (back_navButton != null && navBackAction != null) back_navButton.onClick.RemoveListener(navBackAction); @@ -842,6 +851,16 @@ public class btmandtopController : MonoBehaviour, ICancelHandler private void ToggleGuideDisplay() { + // 关闭指引展示时点击无反应;若指引已在显示中则允许关闭,避免残留面板卡住。 + if (!enableGuideDisplay) + { + if (guideDisplayImage != null && guideDisplayImage.gameObject.activeSelf) + { + CloseGuideDisplay(); + } + return; + } + if (guideDisplayImage == null) { return; diff --git a/Assets/scripts/_runtimecache/PlayerSkillService.cs b/Assets/scripts/_runtimecache/PlayerSkillService.cs index 17c320ea..2f24870e 100644 --- a/Assets/scripts/_runtimecache/PlayerSkillService.cs +++ b/Assets/scripts/_runtimecache/PlayerSkillService.cs @@ -469,11 +469,11 @@ public sealed class PlayerSkillService : MonoBehaviour { bool revealLockedSkills = IsSkillEnabledInternal(RevealLockedIdolSkillsSkillIndex); - idolSkillsHub[] hubs = Resources.FindObjectsOfTypeAll(); + idolSkillsHub[] hubs = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < hubs.Length; i++) { idolSkillsHub hub = hubs[i]; - if (hub == null || !IsSceneInstance(hub)) + if (hub == null) { continue; } @@ -488,19 +488,19 @@ public sealed class PlayerSkillService : MonoBehaviour } } - storeSystem[] stores = Resources.FindObjectsOfTypeAll(); + storeSystem[] stores = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < stores.Length; i++) { - if (stores[i] != null && IsSceneInstance(stores[i])) + if (stores[i] != null) { stores[i].RefreshPlayerSkillItems(); } } - equipSmelt[] smelters = Resources.FindObjectsOfTypeAll(); + equipSmelt[] smelters = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < smelters.Length; i++) { - if (smelters[i] != null && IsSceneInstance(smelters[i])) + if (smelters[i] != null) { smelters[i].RefreshPlayerSkillAdjustedUi(); } @@ -516,7 +516,7 @@ public sealed class PlayerSkillService : MonoBehaviour return; } - uLevel_skills[] skillPanels = Resources.FindObjectsOfTypeAll(); + uLevel_skills[] skillPanels = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); for (int i = 0; i < skillPanels.Length; i++) { if (skillPanels[i] != null && skillPanels[i].ulsSO != null) diff --git a/Assets/scripts/_runtimecache/SecureSaveVault.cs b/Assets/scripts/_runtimecache/SecureSaveVault.cs index 8fa8b0ac..122f933b 100644 --- a/Assets/scripts/_runtimecache/SecureSaveVault.cs +++ b/Assets/scripts/_runtimecache/SecureSaveVault.cs @@ -167,6 +167,9 @@ public static class SecureSaveVault DeleteIfExists(GetFilePath(category, key, ".bak")); DeleteIfExists(GetFilePath(category, key, ".tmp")); DeleteLegacyPlainFile(legacyPlainPath); + // 同时删除恢复镜像(.save_recovery/*),否则下次 TryLoadRawJson 会从镜像 + // 复活刚删掉的数据并回写主存档——删除必须彻底,覆盖所有根目录变体与 .bak。 + DeleteRecoveryCopies(category, key); return true; } catch (Exception ex) @@ -176,6 +179,15 @@ public static class SecureSaveVault } } + private static void DeleteRecoveryCopies(string category, string key) + { + IReadOnlyList candidates = GetRecoveryFilePathVariants(category, key); + for (int i = 0; i < candidates.Count; i++) + { + DeleteIfExists(candidates[i]); + } + } + public static List LoadAllRawJson(string category, string legacyDirectory = null, string legacySearchPattern = "*.json") { var result = new List(); diff --git a/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs b/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs index 5566e958..1aeed7b8 100644 --- a/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs +++ b/Assets/scripts/gamePlay_gameplay/NoteSpawner.cs @@ -86,6 +86,12 @@ public class NoteSpawner : MonoBehaviour // optional: constants for runtime clamping (kept for internal use) private const float SpeedMultiplierMin = 0.5f; private const float SpeedMultiplierMax = 2f; + private const float FlowSpeedGlobalMultiplier = 1.5f; + + public float EffectiveSpeedMultiplier + { + get { return Mathf.Clamp(speedMultiplier, SpeedMultiplierMin, SpeedMultiplierMax) * FlowSpeedGlobalMultiplier; } + } public NoteJudgeConfig judgeConfig; // Documentation text normalized. private Beatmap beatmap; @@ -264,7 +270,7 @@ public class NoteSpawner : MonoBehaviour } // Cache parameters that don't change within the loop - float sm = Mathf.Clamp(speedMultiplier, SpeedMultiplierMin, SpeedMultiplierMax); + float sm = EffectiveSpeedMultiplier; float baseTravelTime = (60f / bpm) * 4f; float baseNoteTravelTime = baseTravelTime / Mathf.Max(0.0001f, sm); float noteSpeed = CalculateSpeed(baseNoteTravelTime); diff --git a/Assets/scripts/gamePlay_gameplay/groundParticularController.cs b/Assets/scripts/gamePlay_gameplay/groundParticularController.cs index 8df4b69f..3418cfa6 100644 --- a/Assets/scripts/gamePlay_gameplay/groundParticularController.cs +++ b/Assets/scripts/gamePlay_gameplay/groundParticularController.cs @@ -60,6 +60,13 @@ public class groundParticularController : MonoBehaviour [SerializeField] private float speedMultiplier = 0.1f; [Tooltip("发射速度倍增器")] [SerializeField] private float emissionSpeedMultiplier = 1f; + [SerializeField] private float perFrameMoveMultiplier = 0.01f; + + [Header("Rotation Randomness")] + [Tooltip("If enabled, each spawned particle gets a random initial rotation offset.")] + [SerializeField] private bool enableRandomRotation = false; + [Tooltip("Random initial rotation range in degrees. Each axis is offset from -range to +range.")] + [SerializeField] private float randomRotationRangeDegrees = 0f; [Tooltip("起点缩放抖动强度 (基于音频电平)")] [SerializeField] private float shakeIntensity = 0.1f; @@ -214,7 +221,7 @@ public class groundParticularController : MonoBehaviour Random.Range(-spawnOffsetY, spawnOffsetY) ); particle.transform.position = startPoint.TransformPoint(finalLocalPos); - particle.transform.rotation = startPoint.rotation; + particle.transform.rotation = GetSpawnRotation(); // 5. 材质均衡选取逻辑 MaterialConfig? selectedConfig = GetBalancedMaterialConfig(); @@ -251,6 +258,24 @@ public class groundParticularController : MonoBehaviour /// /// 轮询式均衡选取材质,确保每种材质数量大致相同 /// + + private Quaternion GetSpawnRotation() + { + if (!enableRandomRotation || randomRotationRangeDegrees <= 0f || startPoint == null) + { + return startPoint != null ? startPoint.rotation : Quaternion.identity; + } + + float range = Mathf.Abs(randomRotationRangeDegrees); + Vector3 randomEuler = new Vector3( + Random.Range(-range, range), + Random.Range(-range, range), + Random.Range(-range, range) + ); + + return startPoint.rotation * Quaternion.Euler(randomEuler); + } + private MaterialConfig? GetBalancedMaterialConfig() { if (particleMaterials == null || particleMaterials.Count == 0) return null; @@ -301,13 +326,13 @@ public class groundParticularController : MonoBehaviour float noteSpawnerSpeedMultiplier = 1.0f; if (beatmapManager != null && beatmapManager.noteSpawner != null) { - noteSpawnerSpeedMultiplier = beatmapManager.noteSpawner.speedMultiplier; + noteSpawnerSpeedMultiplier = beatmapManager.noteSpawner.EffectiveSpeedMultiplier; } float effectiveBPM = Mathf.Max(currentBPM, 60f); float noteSpeed = (totalDist * effectiveBPM * noteSpawnerSpeedMultiplier) / 240f; - float step = noteSpeed * speedMultiplier * emissionSpeedMultiplier * Time.deltaTime; + float step = noteSpeed * speedMultiplier * emissionSpeedMultiplier * perFrameMoveMultiplier * Time.deltaTime; if (step <= 0) step = 0.01f; traveledDist += step; diff --git a/Assets/scripts/gamePlay_gameplay/network_submit/GameServerBridge.cs b/Assets/scripts/gamePlay_gameplay/network_submit/GameServerBridge.cs index 34d85147..5adc4319 100644 --- a/Assets/scripts/gamePlay_gameplay/network_submit/GameServerBridge.cs +++ b/Assets/scripts/gamePlay_gameplay/network_submit/GameServerBridge.cs @@ -16,10 +16,11 @@ public class GameServerBridge : MonoBehaviour [Header("配置")] [SerializeField] private bool autoSubmitOnSettlement = true; - private const string HMAC_SECRET = "your_secret_key_change_this_in_production"; + private const string HMAC_SECRET = "1d95faf15dca25edf0342014428f7dbbd5785ba993113c687b1b86c207b000e0"; private bool _hasSubmitted = false; private float _gameStartTime; + private int _currentRetryCount = 0; private void Awake() { @@ -31,6 +32,7 @@ public class GameServerBridge : MonoBehaviour { _gameStartTime = Time.realtimeSinceStartup; _hasSubmitted = false; + _currentRetryCount = 0; if (autoSubmitOnSettlement) settlementController.OnSettlementCompleted += OnSettlementTriggered; } @@ -141,24 +143,31 @@ public class GameServerBridge : MonoBehaviour Debug.Log("╔══════════════════════════════════════════════════════╗"); Debug.Log("║ ✅✅✅ 上传成功!数据已写入服务器数据库 ✅✅✅ ║"); Debug.Log("╚══════════════════════════════════════════════════════╝"); + _currentRetryCount = 0; } else if (result == "QUEUED") { - Debug.LogWarning("[Bridge] ⏳ 服务器繁忙,3秒后重试..."); + Debug.LogWarning("[Bridge] ⏳ 服务器繁忙,稍后重试..."); _hasSubmitted = false; - await Task.Delay(3000); - SubmitCurrentSettlement(); + await RetrySubmission(songId, difficulty, chartScore, idolScore, totalScore, grade, runtime, playedAt, hmac); } else if (result == "OPT_OUT") { Debug.Log("[Bridge] 当前已关闭排行榜加入选项,未提交世界排行榜。"); + _currentRetryCount = 0; } - else + else if (result == "ERROR" || result == "REJECTED") { Debug.LogWarning($"╔══════════════════════════════════════════════════════╗"); Debug.LogWarning($"║ ❌ 上传失败!服务器返回: {result}"); Debug.LogWarning($"╚══════════════════════════════════════════════════════╝"); _hasSubmitted = false; + await RetrySubmission(songId, difficulty, chartScore, idolScore, totalScore, grade, runtime, playedAt, hmac); + } + else + { + Debug.LogWarning($"[Bridge] 未知返回状态: {result}"); + _hasSubmitted = false; } } catch (Exception ex) @@ -168,6 +177,58 @@ public class GameServerBridge : MonoBehaviour } } + /// + /// 重试上传逻辑:3次重试,每次间隔20秒,失败后缓存到本地 + /// + private async Task RetrySubmission(string songId, string difficulty, int chartScore, int idolScore, + long totalScore, string grade, double runtime, string playedAt, string hmac) + { + _currentRetryCount++; + + if (_currentRetryCount >= 3) + { + Debug.LogWarning($"[Bridge] 已重试 {_currentRetryCount} 次仍失败,缓存到本地待下次启动重试"); + PendingScoreCache.CacheScore(songId, difficulty, chartScore, idolScore, totalScore, grade, runtime, playedAt, hmac); + _currentRetryCount = 0; + return; + } + + Debug.Log($"[Bridge] 等待 20 秒后进行第 {_currentRetryCount} 次重试..."); + await Task.Delay(20000); + + var nm = NetworkManager.Instance; + if (nm == null) + { + Debug.LogWarning("[Bridge] NetworkManager 不存在,缓存到本地"); + PendingScoreCache.CacheScore(songId, difficulty, chartScore, idolScore, totalScore, grade, runtime, playedAt, hmac); + _currentRetryCount = 0; + return; + } + + Debug.Log($"[Bridge] 开始第 {_currentRetryCount} 次重试上传..."); + try + { + string result = await nm.PushSettlement(songId, difficulty, chartScore, idolScore, + totalScore, grade, runtime, playedAt, hmac); + + if (result == "OK") + { + Debug.Log($"[Bridge] ✅ 第 {_currentRetryCount} 次重试成功!"); + _currentRetryCount = 0; + } + else + { + Debug.LogWarning($"[Bridge] 第 {_currentRetryCount} 次重试失败: {result}"); + await RetrySubmission(songId, difficulty, chartScore, idolScore, totalScore, grade, runtime, playedAt, hmac); + } + } + catch (Exception ex) + { + Debug.LogError($"[Bridge] 第 {_currentRetryCount} 次重试异常: {ex.Message}"); + await RetrySubmission(songId, difficulty, chartScore, idolScore, totalScore, grade, runtime, playedAt, hmac); + } + } + private static string ConvertDifficulty(int i) => i switch { 0 => "ez", 1 => "hd", 2 => "in", 3 => "im", _ => "unknown" }; private static string GetGrade(long s) => s >= 960000 ? "SSS" : s >= 920000 ? "SS" : s >= 880000 ? "S" : s >= 820000 ? "A" : s >= 720000 ? "B" : s >= 600000 ? "C" : s >= 400000 ? "D" : "F"; } diff --git a/Assets/scripts/gamePlay_gameplay/network_submit/NetworkManager.cs b/Assets/scripts/gamePlay_gameplay/network_submit/NetworkManager.cs index faf64a1b..6eda8fb9 100644 --- a/Assets/scripts/gamePlay_gameplay/network_submit/NetworkManager.cs +++ b/Assets/scripts/gamePlay_gameplay/network_submit/NetworkManager.cs @@ -18,6 +18,9 @@ namespace GameServer.Client public class NetworkManager : MonoBehaviour { private static readonly bool VerboseLogs = false; + private const int PendingScoreRetryAttemptsPerLaunch = 3; + private const float PendingScoreRetryDelaySeconds = 20f; + private const string LegacyScoreHmacSecret = "1d95faf15dca25edf0342014428f7dbbd5785ba993113c687b1b86c207b000e0"; public static NetworkManager Instance { get; private set; } private static string _startupSteamId = string.Empty; private static string _startupSteamDisplayName = string.Empty; @@ -55,6 +58,8 @@ public class NetworkManager : MonoBehaviour private bool _startupHandshakeCompleted; private Task _avatarUploadTask; private bool _isRefreshingSteamIdentity; + private Coroutine _pendingScoreRetryCoroutine; + private bool _isRetryingPendingScores; private struct CachedRemoteIdentity { @@ -151,6 +156,7 @@ public class NetworkManager : MonoBehaviour } StartCoroutine(StartupHandshakeRoutine()); + _pendingScoreRetryCoroutine = StartCoroutine(RetryPendingScoresRoutine()); if (VerboseLogs) Debug.Log($"[NetworkManager] Startup handshake enabled. Settlement submit will use {BuildApiUrl("/api/submit")}"); } @@ -163,12 +169,137 @@ public class NetworkManager : MonoBehaviour { _requestCts?.Cancel(); OnlineModeSettings.ModeChanged -= HandleOnlineModeChanged; + if (_pendingScoreRetryCoroutine != null) + { + StopCoroutine(_pendingScoreRetryCoroutine); + _pendingScoreRetryCoroutine = null; + } if (!_isApplicationQuitting && Instance == this) { Instance = null; } } + private IEnumerator RetryPendingScoresRoutine() + { + if (_isRetryingPendingScores) + { + yield break; + } + + _isRetryingPendingScores = true; + yield return null; + + List pendingScores = PendingScoreCache.GetPendingScores(); + if (pendingScores == null || pendingScores.Count == 0) + { + _isRetryingPendingScores = false; + _pendingScoreRetryCoroutine = null; + yield break; + } + + if (VerboseLogs) + { + Debug.Log($"[NetworkManager] Found {pendingScores.Count} cached score(s) to retry on startup."); + } + + for (int i = 0; i < pendingScores.Count; i++) + { + if (_isApplicationQuitting || OnlineModeSettings.IsLocalOnlyMode) + { + break; + } + + PendingScoreData score = pendingScores[i]; + if (score == null || string.IsNullOrWhiteSpace(score.songId)) + { + continue; + } + + bool uploaded = false; + for (int attempt = 1; attempt <= PendingScoreRetryAttemptsPerLaunch; attempt++) + { + string hmac = BuildScoreHmac(score); + Task task = PushSettlement( + score.songId, + score.difficulty, + score.chartScore, + score.idolScore, + score.totalScore, + score.grade, + score.runtimeSeconds, + score.playedAt, + hmac); + + while (!task.IsCompleted) + { + if (_isApplicationQuitting) + { + _isRetryingPendingScores = false; + _pendingScoreRetryCoroutine = null; + yield break; + } + + yield return null; + } + + string status = "ERROR"; + if (task.Status == TaskStatus.RanToCompletion && !string.IsNullOrWhiteSpace(task.Result)) + { + status = task.Result; + } + else if (task.IsFaulted) + { + Debug.LogWarning($"[NetworkManager] Pending score retry faulted for songId={score.songId}: {task.Exception?.GetBaseException().Message}"); + } + + if (string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase)) + { + PendingScoreCache.RemoveScore(score.songId, score.totalScore, score.playedAt); + uploaded = true; + break; + } + + if (string.Equals(status, "OPT_OUT", StringComparison.OrdinalIgnoreCase) + || string.Equals(status, "LOCAL_ONLY", StringComparison.OrdinalIgnoreCase)) + { + if (VerboseLogs) + { + Debug.Log($"[NetworkManager] Skip retrying cached score songId={score.songId} because status={status}."); + } + + break; + } + + PendingScoreCache.IncrementRetryCount(score.songId, score.totalScore, score.playedAt); + + if (attempt < PendingScoreRetryAttemptsPerLaunch) + { + yield return new WaitForSecondsRealtime(PendingScoreRetryDelaySeconds); + } + } + + if (!uploaded && VerboseLogs) + { + Debug.Log($"[NetworkManager] Cached score kept for next launch. songId={score.songId} totalScore={score.totalScore}"); + } + } + + _isRetryingPendingScores = false; + _pendingScoreRetryCoroutine = null; + } + + private static string BuildScoreHmac(PendingScoreData score) + { + if (score == null) + { + return string.Empty; + } + + string payload = $"{score.songId}|{score.difficulty}|{score.totalScore}|{score.chartScore}|{score.idolScore}"; + return GameServerSession.ComputeScoreHmac(payload, LegacyScoreHmacSecret); + } + public void Connect() { if (OnlineModeSettings.IsLocalOnlyMode) diff --git a/Assets/scripts/gamePlay_gameplay/network_submit/PendingScoreCache.cs b/Assets/scripts/gamePlay_gameplay/network_submit/PendingScoreCache.cs new file mode 100644 index 00000000..fa01f445 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/network_submit/PendingScoreCache.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using UnityEngine; + +namespace GameServer.Client +{ + /// + /// Stores score submissions that could not be uploaded because of network issues, + /// then retries them on a later startup. + /// + public static class PendingScoreCache + { + [Serializable] + private class CachedScore + { + public string songId; + public string difficulty; + public int chartScore; + public int idolScore; + public long totalScore; + public string grade; + public double runtimeSeconds; + public string playedAt; + public string hmac; + public int retryCount; + public string cachedAt; + } + + [Serializable] + private class CacheContainer + { + public List scores = new List(); + } + + private static readonly string CachePath = Path.Combine(Application.persistentDataPath, "pending_scores.json"); + private static readonly object FileLock = new object(); + + public static void CacheScore( + string songId, + string difficulty, + int chartScore, + int idolScore, + long totalScore, + string grade, + double runtimeSeconds, + string playedAt, + string hmac) + { + lock (FileLock) + { + try + { + CacheContainer container = LoadContainer(); + CachedScore existing = FindScore(container, songId, totalScore, playedAt); + if (existing != null) + { + existing.difficulty = difficulty; + existing.chartScore = chartScore; + existing.idolScore = idolScore; + existing.grade = grade; + existing.runtimeSeconds = runtimeSeconds; + existing.hmac = hmac; + if (string.IsNullOrWhiteSpace(existing.cachedAt)) + { + existing.cachedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + } + } + else + { + container.scores.Add(new CachedScore + { + songId = songId, + difficulty = difficulty, + chartScore = chartScore, + idolScore = idolScore, + totalScore = totalScore, + grade = grade, + runtimeSeconds = runtimeSeconds, + playedAt = playedAt, + hmac = hmac, + retryCount = 0, + cachedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + }); + } + + SaveContainer(container); + Debug.Log($"[PendingScoreCache] Cached score locally. songId={songId} totalScore={totalScore}"); + } + catch (Exception ex) + { + Debug.LogError($"[PendingScoreCache] Failed to cache score: {ex.Message}"); + } + } + } + + public static List GetPendingScores() + { + lock (FileLock) + { + try + { + CacheContainer container = LoadContainer(); + var result = new List(container.scores.Count); + for (int i = 0; i < container.scores.Count; i++) + { + CachedScore cached = container.scores[i]; + if (cached == null) + { + continue; + } + + result.Add(new PendingScoreData + { + songId = cached.songId, + difficulty = cached.difficulty, + chartScore = cached.chartScore, + idolScore = cached.idolScore, + totalScore = cached.totalScore, + grade = cached.grade, + runtimeSeconds = cached.runtimeSeconds, + playedAt = cached.playedAt, + hmac = cached.hmac, + retryCount = cached.retryCount + }); + } + + return result; + } + catch (Exception ex) + { + Debug.LogError($"[PendingScoreCache] Failed to read cache: {ex.Message}"); + return new List(); + } + } + } + + public static void RemoveScore(string songId, long totalScore, string playedAt = null) + { + lock (FileLock) + { + try + { + CacheContainer container = LoadContainer(); + int removed = container.scores.RemoveAll(score => + score != null + && string.Equals(score.songId, songId, StringComparison.Ordinal) + && score.totalScore == totalScore + && (string.IsNullOrWhiteSpace(playedAt) + || string.Equals(score.playedAt, playedAt, StringComparison.Ordinal))); + + if (removed <= 0) + { + return; + } + + SaveContainer(container); + Debug.Log($"[PendingScoreCache] Removed uploaded cached score. songId={songId} totalScore={totalScore}"); + } + catch (Exception ex) + { + Debug.LogError($"[PendingScoreCache] Failed to remove cached score: {ex.Message}"); + } + } + } + + public static void IncrementRetryCount(string songId, long totalScore, string playedAt = null) + { + lock (FileLock) + { + try + { + CacheContainer container = LoadContainer(); + CachedScore score = FindScore(container, songId, totalScore, playedAt); + if (score == null) + { + return; + } + + score.retryCount = Mathf.Max(0, score.retryCount) + 1; + SaveContainer(container); + } + catch (Exception ex) + { + Debug.LogError($"[PendingScoreCache] Failed to update retry count: {ex.Message}"); + } + } + } + + public static void ClearAll() + { + lock (FileLock) + { + try + { + if (File.Exists(CachePath)) + { + File.Delete(CachePath); + Debug.Log("[PendingScoreCache] Cleared all cached scores."); + } + } + catch (Exception ex) + { + Debug.LogError($"[PendingScoreCache] Failed to clear cache: {ex.Message}"); + } + } + } + + public static int GetCachedCount() + { + lock (FileLock) + { + try + { + return LoadContainer().scores.Count; + } + catch + { + return 0; + } + } + } + + private static CacheContainer LoadContainer() + { + if (!File.Exists(CachePath)) + { + return new CacheContainer(); + } + + try + { + string json = File.ReadAllText(CachePath); + return JsonConvert.DeserializeObject(json) ?? new CacheContainer(); + } + catch (Exception ex) + { + Debug.LogWarning($"[PendingScoreCache] Failed to read cache file, creating a fresh container: {ex.Message}"); + return new CacheContainer(); + } + } + + private static void SaveContainer(CacheContainer container) + { + string json = JsonConvert.SerializeObject(container ?? new CacheContainer(), Formatting.Indented); + File.WriteAllText(CachePath, json); + } + + private static CachedScore FindScore(CacheContainer container, string songId, long totalScore, string playedAt) + { + if (container == null || container.scores == null) + { + return null; + } + + for (int i = 0; i < container.scores.Count; i++) + { + CachedScore score = container.scores[i]; + if (score == null) + { + continue; + } + + bool playedAtMatches = string.IsNullOrWhiteSpace(playedAt) + || string.Equals(score.playedAt, playedAt, StringComparison.Ordinal); + if (string.Equals(score.songId, songId, StringComparison.Ordinal) + && score.totalScore == totalScore + && playedAtMatches) + { + return score; + } + } + + return null; + } + } + + [Serializable] + public class PendingScoreData + { + public string songId; + public string difficulty; + public int chartScore; + public int idolScore; + public long totalScore; + public string grade; + public double runtimeSeconds; + public string playedAt; + public string hmac; + public int retryCount; + } +} diff --git a/Assets/scripts/gamePlay_gameplay/network_submit/PendingScoreCache.cs.meta b/Assets/scripts/gamePlay_gameplay/network_submit/PendingScoreCache.cs.meta new file mode 100644 index 00000000..5d6d9b47 --- /dev/null +++ b/Assets/scripts/gamePlay_gameplay/network_submit/PendingScoreCache.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 838755ae207aa0547ae9471fcf9d052f \ No newline at end of file diff --git a/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab.prefab b/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab.prefab index 35c40563..2c2e1ecc 100644 --- a/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab.prefab +++ b/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab.prefab @@ -174,7 +174,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 121, y: -2} + m_AnchoredPosition: {x: 0, y: -2} m_SizeDelta: {x: 113, y: 20} m_Pivot: {x: 0, y: 0.5} --- !u!222 &6919442420098058668 @@ -363,7 +363,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 0, y: 53} + m_AnchoredPosition: {x: 0, y: 63.484863} m_SizeDelta: {x: 40, y: 40} m_Pivot: {x: 0.5, y: 1} --- !u!222 &225269693782171328 @@ -439,7 +439,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 0, y: -2} + m_AnchoredPosition: {x: 118, y: -2} m_SizeDelta: {x: 116, y: 20} m_Pivot: {x: 0, y: 0.5} --- !u!222 &2431985745475878602 @@ -533,7 +533,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 128, y: -53} + m_AnchoredPosition: {x: 128, y: -63.484863} m_SizeDelta: {x: 256, y: 0} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &5490276556566493605 @@ -616,7 +616,7 @@ RectTransform: m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} m_AnchoredPosition: {x: 0, y: -143.99913} - m_SizeDelta: {x: 0, y: 53} + m_SizeDelta: {x: 0, y: 63.484863} m_Pivot: {x: 0.5, y: 1} --- !u!114 &6728628998878729785 MonoBehaviour: @@ -691,7 +691,7 @@ MonoBehaviour: m_MinWidth: -1 m_MinHeight: -1 m_PreferredWidth: -1 - m_PreferredHeight: 53 + m_PreferredHeight: 63.484863 m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 2 @@ -742,8 +742,8 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 8469056815856914825} - {fileID: 423933864671952839} + - {fileID: 8469056815856914825} m_Father: {fileID: 4696069065802349809} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} diff --git a/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab_self.prefab b/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab_self.prefab index 06d294fa..7e96f99c 100644 --- a/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab_self.prefab +++ b/Assets/scripts/roomSystem/playerMessagePrefab/playerMessagePrefab_self.prefab @@ -174,7 +174,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 0, y: -2} + m_AnchoredPosition: {x: 121, y: -2} m_SizeDelta: {x: 113, y: 20} m_Pivot: {x: 0, y: 0.5} --- !u!222 &6919442420098058668 @@ -363,7 +363,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 0, y: 62.501953} + m_AnchoredPosition: {x: 0, y: 125.23433} m_SizeDelta: {x: 40, y: 40} m_Pivot: {x: 0.5, y: 1} --- !u!222 &225269693782171328 @@ -439,7 +439,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 118, y: -2} + m_AnchoredPosition: {x: 0, y: -2} m_SizeDelta: {x: 116, y: 20} m_Pivot: {x: 0, y: 0.5} --- !u!222 &2431985745475878602 @@ -533,7 +533,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 3840, y: -62.501953} + m_AnchoredPosition: {x: 3840, y: -125.234314} m_SizeDelta: {x: 256, y: 0} m_Pivot: {x: 1, y: 0.5} --- !u!114 &5490276556566493605 @@ -616,7 +616,7 @@ RectTransform: m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} m_AnchoredPosition: {x: 0, y: -143.99913} - m_SizeDelta: {x: 0, y: 62.501953} + m_SizeDelta: {x: 0, y: 125.23433} m_Pivot: {x: 0.5, y: 1} --- !u!114 &6728628998878729785 MonoBehaviour: @@ -691,7 +691,7 @@ MonoBehaviour: m_MinWidth: -1 m_MinHeight: -1 m_PreferredWidth: -1 - m_PreferredHeight: 62.501953 + m_PreferredHeight: 125.23433 m_FlexibleWidth: -1 m_FlexibleHeight: -1 m_LayoutPriority: 2 @@ -742,8 +742,8 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 423933864671952839} - {fileID: 8469056815856914825} + - {fileID: 423933864671952839} m_Father: {fileID: 4696069065802349809} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} diff --git a/Assets/scripts/selectYourSongFirst/load_teammatesProfile.cs b/Assets/scripts/selectYourSongFirst/load_teammatesProfile.cs index 2ba06dd7..39f5bf22 100644 --- a/Assets/scripts/selectYourSongFirst/load_teammatesProfile.cs +++ b/Assets/scripts/selectYourSongFirst/load_teammatesProfile.cs @@ -170,10 +170,22 @@ public class load_teammatesProfile : MonoBehaviour return GetRatingKeyFromSO(hero); } + // 等级评级改从持久化账本(AllyHeroDeployLedger)取真值,而非 SO 上被打包烘焙的镜像字段, + // 修复客户端更新后卡片显示过期等级、需重新给经验才同步的问题。 private string GetRatingKeyFromSO(AllyHero_SO so) { if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "Fallback"; - return so.GetDisplayLevelRatingKey(); + + if (so.ally_heroID <= 0) + { + return so.GetDisplayLevelRatingKey(); + } + + AllyHeroDeployLedger ledger = AllyHeroDeployLedger.EnsureInstance(); + int currentExp = ledger.GetCurrentExp(so.ally_heroID); + int unlockedTier = ledger.GetUnlockedTierIndex(so.ally_heroID); + bool levelLocked = ledger.IsLevelLockEnabled(so.ally_heroID); + return so.GetDisplayLevelRatingKeyFromGrowth(currentExp, unlockedTier, levelLocked); } private void ApplyBorderSprite(Image targetImage, string level) diff --git a/Assets/scripts/settings/userSettings.cs b/Assets/scripts/settings/userSettings.cs index 0b07492a..cc111fbc 100644 --- a/Assets/scripts/settings/userSettings.cs +++ b/Assets/scripts/settings/userSettings.cs @@ -91,9 +91,9 @@ public class userSettings : MonoBehaviour if (clearup_saveData_button != null) clearup_saveData_button.onClick.AddListener(OnClearupSaveDataClicked); - if (export_saveData_button != null) export_saveData_button.onClick.AddListener(ResetClearupCounter); - if (import_saveData_button != null) import_saveData_button.onClick.AddListener(ResetClearupCounter); - if (view_detailedHighlight_button != null) view_detailedHighlight_button.onClick.AddListener(ResetClearupCounter); + if (export_saveData_button != null) export_saveData_button.onClick.AddListener(OnUnimplementedFeatureClicked); + if (import_saveData_button != null) import_saveData_button.onClick.AddListener(OnUnimplementedFeatureClicked); + if (view_detailedHighlight_button != null) view_detailedHighlight_button.onClick.AddListener(OnUnimplementedFeatureClicked); if (language_dropdown != null) language_dropdown.onValueChanged.AddListener(OnLanguageDropdownValueChanged); ResetClearupCounter(); @@ -108,9 +108,9 @@ public class userSettings : MonoBehaviour if (clearup_saveData_button != null) clearup_saveData_button.onClick.RemoveListener(OnClearupSaveDataClicked); - if (export_saveData_button != null) export_saveData_button.onClick.RemoveListener(ResetClearupCounter); - if (import_saveData_button != null) import_saveData_button.onClick.RemoveListener(ResetClearupCounter); - if (view_detailedHighlight_button != null) view_detailedHighlight_button.onClick.RemoveListener(ResetClearupCounter); + if (export_saveData_button != null) export_saveData_button.onClick.RemoveListener(OnUnimplementedFeatureClicked); + if (import_saveData_button != null) import_saveData_button.onClick.RemoveListener(OnUnimplementedFeatureClicked); + if (view_detailedHighlight_button != null) view_detailedHighlight_button.onClick.RemoveListener(OnUnimplementedFeatureClicked); if (language_dropdown != null) language_dropdown.onValueChanged.RemoveListener(OnLanguageDropdownValueChanged); ResetClearupCounter(); @@ -129,6 +129,14 @@ public class userSettings : MonoBehaviour clearupClickCount = 0; } + // 导出/导入存档、查看详细高光等按钮已定义 UI 但尚未接入实际逻辑, + // 点击时提示功能未开放。仍复位危险操作确认计数,保持原有副作用。 + private void OnUnimplementedFeatureClicked() + { + ResetClearupCounter(); + gNotice.error.display(LocalizationService.LocalizeLiteral("功能未开放")); + } + private void InitializeLanguageDropdown() { if (language_dropdown == null) diff --git a/Assets/无标题.fbx b/Assets/无标题.fbx new file mode 100644 index 00000000..0b5568da Binary files /dev/null and b/Assets/无标题.fbx differ diff --git a/Assets/无标题.fbx.meta b/Assets/无标题.fbx.meta new file mode 100644 index 00000000..2bc325f5 --- /dev/null +++ b/Assets/无标题.fbx.meta @@ -0,0 +1,107 @@ +fileFormatVersion: 2 +guid: 0a88ecb14af08a64dae3e5832dff0d28 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: