UI update 02
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using Bansonic;
|
||||
|
||||
public class BootLoader : MonoBehaviour
|
||||
{
|
||||
@@ -19,6 +20,8 @@ public class BootLoader : MonoBehaviour
|
||||
[Tooltip("Optional Text to show loading percentage.")]
|
||||
[SerializeField] private Text progressText;
|
||||
|
||||
private bool transitionStarted;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Start the asynchronous loading process
|
||||
@@ -61,8 +64,16 @@ public class BootLoader : MonoBehaviour
|
||||
float elapsedTime = Time.time - startTime;
|
||||
if (elapsedTime >= minSplashTime)
|
||||
{
|
||||
// Allow the scene to activate
|
||||
asyncLoad.allowSceneActivation = true;
|
||||
if (!transitionStarted)
|
||||
{
|
||||
transitionStarted = true;
|
||||
if (gTransition.Run(AllowSceneActivationRoutine(asyncLoad)))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
asyncLoad.allowSceneActivation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +83,18 @@ public class BootLoader : MonoBehaviour
|
||||
// Restore screen sleep timeout
|
||||
Screen.sleepTimeout = SleepTimeout.SystemSetting;
|
||||
}
|
||||
|
||||
private IEnumerator AllowSceneActivationRoutine(AsyncOperation asyncLoad)
|
||||
{
|
||||
if (asyncLoad == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
asyncLoad.allowSceneActivation = true;
|
||||
while (!asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1925,30 +1925,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: check primary group
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != when) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Final fallback: iterate availableSkills
|
||||
if (so.availableSkills != null)
|
||||
{
|
||||
foreach (var def in so.availableSkills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != when) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryCastOnFullMana()
|
||||
@@ -2005,10 +1981,8 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
|
||||
if (so == null)
|
||||
{
|
||||
// Can't locate SO for this slot; attempt best-effort: call UsePrimarySkillForSlot which will log details
|
||||
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1}: AllyHero_SO not found for slot. Falling back to UsePrimarySkillForSlot.");
|
||||
SkillBuilder.Instance.UsePrimarySkillForSlot(slotIndex, -1f, null);
|
||||
return true;
|
||||
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1}: AllyHero_SO not found for slot. Skip OnManaFull cast.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool anyTriggered = false;
|
||||
@@ -2033,47 +2007,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
}
|
||||
|
||||
// Second: primary group
|
||||
if (!anyTriggered)
|
||||
{
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
foreach (var skill in fallbackGroup.skills)
|
||||
{
|
||||
if (skill == null) continue;
|
||||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||||
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1} cast skill from primary group: {skill.skillId} (OnManaFull)");
|
||||
anyTriggered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Third: availableSkills / primary skill fallback
|
||||
if (!anyTriggered)
|
||||
{
|
||||
// try primary skill specifically
|
||||
var def = so.GetPrimarySkill();
|
||||
if (def != null && def.triggerCondition == SkillDefinition.SkillTrigger.OnManaFull)
|
||||
{
|
||||
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
|
||||
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1} cast primary skill '{def.skillId}' due to ManaFull.");
|
||||
anyTriggered = true;
|
||||
}
|
||||
else if (so.availableSkills != null)
|
||||
{
|
||||
foreach (var skill in so.availableSkills)
|
||||
{
|
||||
if (skill == null) continue;
|
||||
if (skill.triggerCondition != SkillDefinition.SkillTrigger.OnManaFull) continue;
|
||||
SkillBuilder.Instance.UseSkillDefinition(skill, slotIndex, -1f, null);
|
||||
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1} cast available skill: {skill.skillId} (OnManaFull)");
|
||||
anyTriggered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyTriggered)
|
||||
{
|
||||
LogVerbose($"[AllyCombatant] Slot {slotIndex + 1}: no skills configured for OnManaFull.");
|
||||
@@ -2184,26 +2117,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null && fallbackGroup.skills != null)
|
||||
{
|
||||
for (int i = 0; i < fallbackGroup.skills.Length; i++)
|
||||
{
|
||||
var def = fallbackGroup.skills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
else if (so.availableSkills != null)
|
||||
{
|
||||
for (int i = 0; i < so.availableSkills.Length; i++)
|
||||
{
|
||||
var def = so.availableSkills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var vars = BuildFormulaVars(so);
|
||||
|
||||
@@ -2344,26 +2257,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null && fallbackGroup.skills != null)
|
||||
{
|
||||
for (int i = 0; i < fallbackGroup.skills.Length; i++)
|
||||
{
|
||||
var def = fallbackGroup.skills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
else if (so.availableSkills != null)
|
||||
{
|
||||
for (int i = 0; i < so.availableSkills.Length; i++)
|
||||
{
|
||||
var def = so.availableSkills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var vars = BuildFormulaVars(so);
|
||||
|
||||
@@ -2446,26 +2339,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null && fallbackGroup.skills != null)
|
||||
{
|
||||
for (int i = 0; i < fallbackGroup.skills.Length; i++)
|
||||
{
|
||||
var def = fallbackGroup.skills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
else if (so.availableSkills != null)
|
||||
{
|
||||
for (int i = 0; i < so.availableSkills.Length; i++)
|
||||
{
|
||||
var def = so.availableSkills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the most restrictive threshold that is still satisfied (smallest attackTriggerValue such that attack < value).
|
||||
string selected = null;
|
||||
@@ -2567,26 +2440,6 @@ public class AllyCombatant : MonoBehaviour, ICombatant
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null && fallbackGroup.skills != null)
|
||||
{
|
||||
for (int i = 0; i < fallbackGroup.skills.Length; i++)
|
||||
{
|
||||
var def = fallbackGroup.skills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
else if (so.availableSkills != null)
|
||||
{
|
||||
for (int i = 0; i < so.availableSkills.Length; i++)
|
||||
{
|
||||
var def = so.availableSkills[i];
|
||||
if (def != null) defs.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pick the closest satisfied threshold from below (largest attackTriggerValue such that attack > value).
|
||||
string selected = null;
|
||||
|
||||
@@ -3104,18 +3104,7 @@ ResolvedGroup:
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: prefer any defined primary group (SO-level) via GetPrimarySkillGroup(), otherwise use primarySkillIndex
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
UseSkillGroupForSlot(fallbackGroup, slotIndex, inputValue, specificTarget);
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = so.primarySkillIndex;
|
||||
if (idx < 0) { Debug.LogWarning($"UsePrimarySkillForSlot: primarySkillIndex not set for slot {slotIndex}"); return; }
|
||||
UseSelectedSkillForSlot(slotIndex, idx, inputValue, specificTarget);
|
||||
Debug.LogWarning($"UsePrimarySkillForSlot: no equipped skill groups for slot {slotIndex}");
|
||||
}
|
||||
|
||||
// Cast all non-null skills in a SkillGroup for a given slotIndex. Each skill is invoked via UseSkillDefinition.
|
||||
@@ -3219,30 +3208,6 @@ ResolvedGroup:
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallback: check SO-level primary group via GetPrimarySkillGroup()
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
foreach (var sk in fallbackGroup.skills)
|
||||
{
|
||||
if (sk == null) continue;
|
||||
if (sk.triggerCondition == SkillDefinition.SkillTrigger.OnGameStart)
|
||||
{
|
||||
LogVerbose($"[SkillBuilder] TriggerOnGameStart: casting group skill {sk.skillId} for slot {i}");
|
||||
UseSkillDefinition(sk, i, -1f, null);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var def = so.GetPrimarySkill();
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnGameStart)
|
||||
{
|
||||
LogVerbose($"[SkillBuilder] TriggerOnGameStart: casting primary skill {def.skillId} for slot {i}");
|
||||
UsePrimarySkillForSlot(i, -1f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3283,31 +3248,6 @@ ResolvedGroup:
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnEnemyDead) continue;
|
||||
LogVerbose($"[SkillBuilder] TriggerOnEnemyDead: slot {i+1} casting skill {def.skillId}");
|
||||
GameObject ctxTarget = null;
|
||||
if (deadEnemy != null && (def.requiresSpecificTarget || def.defaultSelector == Selector.CurrentEnemies)) ctxTarget = deadEnemy.gameObject;
|
||||
UseSkillDefinition(def, i, -1f, ctxTarget);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var primary = so.GetPrimarySkill();
|
||||
if (primary != null && primary.triggerCondition == SkillDefinition.SkillTrigger.OnEnemyDead)
|
||||
{
|
||||
LogVerbose($"[SkillBuilder] TriggerOnEnemyDead: slot {i+1} casting primary skill {primary.skillId}");
|
||||
// Only pass deadEnemy as specificTarget when the skill truly expects a specific target (e.g. targets CurrentEnemies)
|
||||
GameObject ctxTarget = null;
|
||||
if (deadEnemy != null && (primary.requiresSpecificTarget || primary.defaultSelector == Selector.CurrentEnemies)) ctxTarget = deadEnemy.gameObject;
|
||||
UsePrimarySkillForSlot(i, -1f, ctxTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3344,26 +3284,6 @@ ResolvedGroup:
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var fallbackGroup2 = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup2 != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup2.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnEnemyRevive) continue;
|
||||
LogVerbose($"[SkillBuilder] TriggerOnEnemyRevive: slot {i+1} casting skill {def.skillId} (from primary group)");
|
||||
UseSkillDefinition(def, i, -1f, enemy != null ? enemy.gameObject : null);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var primary2 = so.GetPrimarySkill();
|
||||
if (primary2 != null && primary2.triggerCondition == SkillDefinition.SkillTrigger.OnEnemyRevive)
|
||||
{
|
||||
LogVerbose($"[SkillBuilder] TriggerOnEnemyRevive: slot {i+1} casting primary skill {primary2.skillId}");
|
||||
UsePrimarySkillForSlot(i, -1f, enemy != null ? enemy.gameObject : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3395,24 +3315,6 @@ ResolvedGroup:
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var fallbackGroup3 = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup3 != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup3.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAllEnemiesDefeated) continue;
|
||||
UseSkillDefinition(def, i, -1f, null);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var primary3 = so.GetPrimarySkill();
|
||||
if (primary3 != null && primary3.triggerCondition == SkillDefinition.SkillTrigger.OnAllEnemiesDefeated)
|
||||
{
|
||||
UsePrimarySkillForSlot(i, -1f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3474,24 +3376,6 @@ ResolvedGroup:
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var fallbackGroup = so.GetPrimarySkillGroup();
|
||||
if (fallbackGroup != null && fallbackGroup.skills != null)
|
||||
{
|
||||
foreach (var def in fallbackGroup.skills)
|
||||
{
|
||||
if (def == null) continue;
|
||||
if (def.triggerCondition != SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast) continue;
|
||||
UseSkillDefinition(def, i, -1f, null);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var primary = so.GetPrimarySkill();
|
||||
if (primary != null && primary.triggerCondition == SkillDefinition.SkillTrigger.OnAdjacentAllySkillCast)
|
||||
{
|
||||
UsePrimarySkillForSlot(i, -1f, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3644,57 +3528,7 @@ ResolvedGroup:
|
||||
}
|
||||
return anyTriggered;
|
||||
}
|
||||
|
||||
// Fallback to previous behavior using primary skill (availableSkills)
|
||||
var defPrimary = so.GetPrimarySkill();
|
||||
if (defPrimary == null) { LogVerbose($"[SkillBuilder] NotifyNoteHit: no primary skill for slot {trackIndex}"); return false; }
|
||||
if (defPrimary.triggerCondition != SkillDefinition.SkillTrigger.OnNoteHit)
|
||||
{
|
||||
LogVerbose($"[SkillBuilder] NotifyNoteHit: primary skill {defPrimary.skillId} for slot {trackIndex} is not set to OnNoteHit (actual={defPrimary.triggerCondition}), rejecting");
|
||||
return false;
|
||||
}
|
||||
|
||||
// previous checks preserved
|
||||
var effectiveNoteTypePrimary = noteType;
|
||||
if (noteType == SkillDefinition.NoteTypeTrigger.Hold) effectiveNoteTypePrimary = SkillDefinition.NoteTypeTrigger.Tap;
|
||||
switch (defPrimary.noteTriggerType)
|
||||
{
|
||||
case SkillDefinition.NoteTypeTrigger.Tap:
|
||||
if (effectiveNoteTypePrimary != SkillDefinition.NoteTypeTrigger.Tap) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} requires Tap but event is {noteType}, rejecting"); return false; }
|
||||
break;
|
||||
case SkillDefinition.NoteTypeTrigger.Hold:
|
||||
if (noteType != SkillDefinition.NoteTypeTrigger.Hold) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} requires Hold but event is {noteType}, rejecting"); return false; }
|
||||
break;
|
||||
case SkillDefinition.NoteTypeTrigger.Either:
|
||||
break;
|
||||
}
|
||||
|
||||
int qualityPrimary = JudgeQualityFromString(judgeResult);
|
||||
if (defPrimary.onNoteHitMinThreshold == SkillDefinition.NoteTriggerThreshold.Miss)
|
||||
{
|
||||
if (qualityPrimary != 0) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} is Miss-only but quality={qualityPrimary}, rejecting"); return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
int required = (int)defPrimary.onNoteHitMinThreshold;
|
||||
if (qualityPrimary < required) { LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} requires quality>={required} but got {qualityPrimary}, rejecting"); return false; }
|
||||
}
|
||||
|
||||
string keyPrimary = $"{trackIndex}:{defPrimary.skillId}";
|
||||
float nowPrimary = Time.time;
|
||||
if (defPrimary.onNoteHitCooldown > 0f && _lastOnNoteHitTriggerTime.TryGetValue(keyPrimary, out float lastPrimary))
|
||||
{
|
||||
if (nowPrimary - lastPrimary < defPrimary.onNoteHitCooldown)
|
||||
{
|
||||
LogVerbose($"[SkillBuilder] NotifyNoteHit: skill {defPrimary.skillId} on cooldown for slot {trackIndex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LogVerbose($"[SkillBuilder] NotifyNoteHit: triggering primary skill {defPrimary.skillId} for slot {trackIndex} due to note hit {judgeResult} (noteType={noteType})");
|
||||
UsePrimarySkillForSlot(trackIndex, -1f, null);
|
||||
_lastOnNoteHitTriggerTime[keyPrimary] = nowPrimary;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -32,6 +32,10 @@ public class AllyHero_SO : ScriptableObject
|
||||
[Tooltip("Optional obsession tag used by memory skills such as 30011012. Leave empty to ignore mismatch checks.")]
|
||||
public string obsessionTag;
|
||||
|
||||
[Header("DLC")]
|
||||
[Tooltip("DLC key that owns this hero. Leave empty for base-game heroes that are always available. When set, the hero is only accessible when the matching DLC is owned.")]
|
||||
public string sourceDlcId;
|
||||
|
||||
[Header("Inspector")]
|
||||
public Sprite ally_heroImage;
|
||||
[Header("Inspector")]
|
||||
@@ -120,6 +124,8 @@ public class AllyHero_SO : ScriptableObject
|
||||
[Header("Inspector")]
|
||||
public int ally_currentEXP;
|
||||
public int ally_growthUnlockedTierIndex;
|
||||
[Tooltip("When enabled, level display/effective level resolution is clamped to the previous tier while this hero is breakthrough-locked, even if EXP has already reached the next tier threshold.")]
|
||||
public bool level_lock;
|
||||
public bool ally_autoBreakthroughEnabled;
|
||||
public int ally_battleDeployCount;
|
||||
public int ally_finishCount;
|
||||
@@ -165,14 +171,6 @@ public class AllyHero_SO : ScriptableObject
|
||||
}
|
||||
}
|
||||
|
||||
if (skillGroups != null)
|
||||
{
|
||||
foreach (SkillGroup group in skillGroups)
|
||||
{
|
||||
if (group != null) return group;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -365,11 +363,53 @@ public class AllyHero_SO : ScriptableObject
|
||||
result.Add(groupId);
|
||||
}
|
||||
|
||||
public int GetUnlockedLevelIndex()
|
||||
{
|
||||
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
|
||||
return ResolveUnlockedLevelIndex(sorted);
|
||||
}
|
||||
|
||||
public int GetExpQualifiedLevelIndex()
|
||||
{
|
||||
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
|
||||
return ResolveExpQualifiedLevelIndex(sorted);
|
||||
}
|
||||
|
||||
public int GetDisplayLevelIndex()
|
||||
{
|
||||
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
|
||||
return ResolveDisplayLevelIndex(sorted);
|
||||
}
|
||||
|
||||
public string GetDisplayLevelRatingKey()
|
||||
{
|
||||
int displayIndex = GetDisplayLevelIndex();
|
||||
if (displayIndex <= 0) return "C";
|
||||
if (displayIndex == 1) return "B";
|
||||
if (displayIndex == 2) return "A";
|
||||
return "S";
|
||||
}
|
||||
|
||||
public AllyLevelInfo GetEffectiveLevelForCurrentEXP()
|
||||
{
|
||||
if (levelStats == null || levelStats.Count == 0) return null;
|
||||
List<AllyLevelInfo> sorted = BuildSortedLevelStats();
|
||||
int unlockedIndex = ResolveUnlockedLevelIndex(sorted);
|
||||
if (unlockedIndex < 0 || unlockedIndex >= sorted.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return sorted[unlockedIndex];
|
||||
}
|
||||
|
||||
private List<AllyLevelInfo> BuildSortedLevelStats()
|
||||
{
|
||||
var sorted = new List<AllyLevelInfo>();
|
||||
if (levelStats == null || levelStats.Count == 0)
|
||||
{
|
||||
return sorted;
|
||||
}
|
||||
|
||||
List<AllyLevelInfo> sorted = new List<AllyLevelInfo>();
|
||||
for (int i = 0; i < levelStats.Count; i++)
|
||||
{
|
||||
if (levelStats[i] != null)
|
||||
@@ -384,8 +424,57 @@ public class AllyHero_SO : ScriptableObject
|
||||
}
|
||||
|
||||
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
|
||||
int unlockedTierIndex = Mathf.Clamp(ally_growthUnlockedTierIndex, 0, sorted.Count - 1);
|
||||
return sorted[unlockedTierIndex];
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private int ResolveUnlockedLevelIndex(List<AllyLevelInfo> sorted)
|
||||
{
|
||||
if (sorted == null || sorted.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Mathf.Clamp(ally_growthUnlockedTierIndex, 0, sorted.Count - 1);
|
||||
}
|
||||
|
||||
private int ResolveExpQualifiedLevelIndex(List<AllyLevelInfo> sorted)
|
||||
{
|
||||
if (sorted == null || sorted.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int expQualifiedIndex = 0;
|
||||
for (int i = 0; i < sorted.Count; i++)
|
||||
{
|
||||
if (ally_currentEXP >= sorted[i].requiredEXP)
|
||||
{
|
||||
expQualifiedIndex = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Mathf.Clamp(expQualifiedIndex, 0, sorted.Count - 1);
|
||||
}
|
||||
|
||||
private int ResolveDisplayLevelIndex(List<AllyLevelInfo> sorted)
|
||||
{
|
||||
if (sorted == null || sorted.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int expQualifiedIndex = ResolveExpQualifiedLevelIndex(sorted);
|
||||
if (!level_lock)
|
||||
{
|
||||
return expQualifiedIndex;
|
||||
}
|
||||
|
||||
int unlockedIndex = ResolveUnlockedLevelIndex(sorted);
|
||||
return Mathf.Clamp(Mathf.Min(expQualifiedIndex, unlockedIndex), 0, sorted.Count - 1);
|
||||
}
|
||||
|
||||
private AllyLevelInfo CloneLevelInfo(AllyLevelInfo source)
|
||||
|
||||
@@ -404,26 +404,7 @@ class AllyHeroGraphView : GraphView
|
||||
|
||||
private int GetCurrentLevelIndex(AllyHero_SO so)
|
||||
{
|
||||
if (so.levelStats == null || so.levelStats.Count == 0) return -1;
|
||||
int bestIndex = -1;
|
||||
int currentExp = so.ally_currentEXP;
|
||||
for (int i = 0; i < so.levelStats.Count; i++)
|
||||
{
|
||||
var lvl = so.levelStats[i];
|
||||
if (lvl == null) continue;
|
||||
if (bestIndex < 0)
|
||||
{
|
||||
bestIndex = i;
|
||||
if (currentExp < lvl.requiredEXP) continue;
|
||||
}
|
||||
var best = so.levelStats[bestIndex];
|
||||
if (best == null) continue;
|
||||
if (currentExp >= lvl.requiredEXP && lvl.requiredEXP >= best.requiredEXP)
|
||||
{
|
||||
bestIndex = i;
|
||||
}
|
||||
}
|
||||
return bestIndex < 0 ? 0 : bestIndex;
|
||||
return so != null ? so.GetDisplayLevelIndex() : -1;
|
||||
}
|
||||
|
||||
private string FormatNumber(float value)
|
||||
|
||||
@@ -337,12 +337,6 @@ public static class HeroSkinResolver
|
||||
}
|
||||
#endif
|
||||
|
||||
HeroSkinSO[] loaded = RuntimeResourcesCache.LoadAllHeroSkins();
|
||||
if (loaded != null && loaded.Length > 0)
|
||||
{
|
||||
return loaded;
|
||||
}
|
||||
|
||||
return Resources.LoadAll<HeroSkinSO>(string.Empty) ?? Array.Empty<HeroSkinSO>();
|
||||
return RuntimeResourcesCache.LoadAllHeroSkins() ?? Array.Empty<HeroSkinSO>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using Bansonic;
|
||||
|
||||
public class pressStart : MonoBehaviour
|
||||
{
|
||||
@@ -301,7 +302,10 @@ public class pressStart : MonoBehaviour
|
||||
// 3. 切换场景
|
||||
Debug.Log("[pressStart] Transition sequence complete. Loading UI_UI scene.");
|
||||
Time.timeScale = 1f;
|
||||
SceneManager.LoadScene("UI_UI");
|
||||
if (!gTransition.LoadScene("UI_UI", LoadSceneMode.Single))
|
||||
{
|
||||
SceneManager.LoadScene("UI_UI");
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator PollForBanflagAndProceed()
|
||||
@@ -316,8 +320,17 @@ public class pressStart : MonoBehaviour
|
||||
UpdateStatus(LocalizationService.Get("press_start.loading_test_mode", "Banflag detected. Loading test mode..."), Color.green);
|
||||
// small delay to show status
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
if (gTransition.LoadScene(testModeSceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(testModeSceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
@@ -334,8 +347,17 @@ public class pressStart : MonoBehaviour
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@@ -340,7 +340,10 @@ public class UI_SongsSelect_RuntimeFeatures : MonoBehaviour
|
||||
BeatmapManager.SetPendingSong(song, difficulty);
|
||||
|
||||
Time.timeScale = 1f;
|
||||
SceneManager.LoadScene(gameplaySceneName, LoadSceneMode.Single);
|
||||
if (!gTransition.LoadScene(gameplaySceneName, LoadSceneMode.Single))
|
||||
{
|
||||
SceneManager.LoadScene(gameplaySceneName, LoadSceneMode.Single);
|
||||
}
|
||||
}
|
||||
|
||||
private SongData ResolveSelectedSong()
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Bansonic;
|
||||
|
||||
public class back_toPreviousPage : MonoBehaviour
|
||||
{
|
||||
@@ -32,8 +33,17 @@ public class back_toPreviousPage : MonoBehaviour
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ public class CharacterCardView : MonoBehaviour, IBeginDragHandler, IDragHandler,
|
||||
return false;
|
||||
}
|
||||
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>("");
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
|
||||
@@ -1035,7 +1035,7 @@ public static class TeamShareCodec
|
||||
return;
|
||||
}
|
||||
|
||||
cachedHeroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
cachedHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
cachedHeroById = new Dictionary<int, AllyHero_SO>();
|
||||
if (cachedHeroes == null)
|
||||
{
|
||||
|
||||
@@ -494,9 +494,7 @@ public class newTeamSelector : MonoBehaviour
|
||||
|
||||
private static void RebuildHeroCache()
|
||||
{
|
||||
cachedHeroes = Resources.LoadAll<AllyHero_SO>("so/ally");
|
||||
if (cachedHeroes == null || cachedHeroes.Length == 0)
|
||||
cachedHeroes = Resources.LoadAll<AllyHero_SO>("");
|
||||
cachedHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
|
||||
cachedHeroesById = new Dictionary<int, AllyHero_SO>();
|
||||
if (cachedHeroes == null) return;
|
||||
@@ -511,30 +509,6 @@ public class newTeamSelector : MonoBehaviour
|
||||
|
||||
private string GetRatingFromSO(AllyHero_SO so)
|
||||
{
|
||||
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "C";
|
||||
|
||||
List<AllyHero_SO.AllyLevelInfo> sorted = new List<AllyHero_SO.AllyLevelInfo>();
|
||||
foreach (var level in so.levelStats)
|
||||
{
|
||||
if (level != null)
|
||||
sorted.Add(level);
|
||||
}
|
||||
|
||||
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
|
||||
|
||||
int currentExp = so.ally_currentEXP;
|
||||
int selectedIndex = 0;
|
||||
for (int i = 0; i < sorted.Count; i++)
|
||||
{
|
||||
if (currentExp >= sorted[i].requiredEXP)
|
||||
selectedIndex = i;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (selectedIndex <= 0) return "C";
|
||||
if (selectedIndex == 1) return "B";
|
||||
if (selectedIndex == 2) return "A";
|
||||
return "S";
|
||||
return so != null ? so.GetDisplayLevelRatingKey() : "C";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public class loadSettlementTeamPrefab : MonoBehaviour
|
||||
AllyHero_SO resolvedHeroSO = null;
|
||||
if (allyId > 0)
|
||||
{
|
||||
var arr = Resources.LoadAll<AllyHero_SO>("");
|
||||
var arr = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
foreach (var a in arr)
|
||||
{
|
||||
if (a != null && a.ally_heroID == allyId) { resolvedHeroSO = a; break; }
|
||||
|
||||
@@ -264,7 +264,7 @@ public class teamSettingPanel : MonoBehaviour
|
||||
return false;
|
||||
}
|
||||
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>("");
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[AddComponentMenu("Layout/Flow Layout Group")]
|
||||
public class FlowLayoutGroup : LayoutGroup
|
||||
{
|
||||
[SerializeField] private float spacingX = 10f;
|
||||
[SerializeField] private float spacingY = 10f;
|
||||
[SerializeField] private bool expandChildHeight = false;
|
||||
[SerializeField] private float forcedChildHeight = 120f;
|
||||
|
||||
private readonly List<RowInfo> rows = new List<RowInfo>();
|
||||
private float calculatedPreferredHeight;
|
||||
|
||||
private struct RowInfo
|
||||
{
|
||||
public int startIndex;
|
||||
public int endIndex;
|
||||
public float width;
|
||||
public float height;
|
||||
}
|
||||
|
||||
public float SpacingX
|
||||
{
|
||||
get => spacingX;
|
||||
set => SetProperty(ref spacingX, value);
|
||||
}
|
||||
|
||||
public float SpacingY
|
||||
{
|
||||
get => spacingY;
|
||||
set => SetProperty(ref spacingY, value);
|
||||
}
|
||||
|
||||
public bool ExpandChildHeight
|
||||
{
|
||||
get => expandChildHeight;
|
||||
set => SetProperty(ref expandChildHeight, value);
|
||||
}
|
||||
|
||||
public float ForcedChildHeight
|
||||
{
|
||||
get => forcedChildHeight;
|
||||
set => SetProperty(ref forcedChildHeight, value);
|
||||
}
|
||||
|
||||
public override void CalculateLayoutInputHorizontal()
|
||||
{
|
||||
base.CalculateLayoutInputHorizontal();
|
||||
CalculateRows();
|
||||
float minWidth = padding.horizontal;
|
||||
float preferredWidth = rectTransform.rect.width > 0f ? rectTransform.rect.width : minWidth;
|
||||
SetLayoutInputForAxis(minWidth, preferredWidth, -1f, 0);
|
||||
}
|
||||
|
||||
public override void CalculateLayoutInputVertical()
|
||||
{
|
||||
CalculateRows();
|
||||
SetLayoutInputForAxis(calculatedPreferredHeight, calculatedPreferredHeight, -1f, 1);
|
||||
}
|
||||
|
||||
public override void SetLayoutHorizontal()
|
||||
{
|
||||
CalculateRows();
|
||||
SetChildrenAlongAxis();
|
||||
}
|
||||
|
||||
public override void SetLayoutVertical()
|
||||
{
|
||||
CalculateRows();
|
||||
SetChildrenAlongAxis();
|
||||
}
|
||||
|
||||
private void CalculateRows()
|
||||
{
|
||||
rows.Clear();
|
||||
|
||||
float availableWidth = GetAvailableWidth();
|
||||
|
||||
float currentRowWidth = 0f;
|
||||
float currentRowHeight = 0f;
|
||||
int currentRowStart = 0;
|
||||
bool hasRow = false;
|
||||
|
||||
for (int i = 0; i < rectChildren.Count; i++)
|
||||
{
|
||||
RectTransform child = rectChildren[i];
|
||||
if (child == null) continue;
|
||||
|
||||
float childWidth = Mathf.Max(LayoutUtility.GetPreferredSize(child, 0), LayoutUtility.GetMinSize(child, 0), child.rect.width);
|
||||
float childHeight = expandChildHeight
|
||||
? forcedChildHeight
|
||||
: Mathf.Max(LayoutUtility.GetPreferredSize(child, 1), LayoutUtility.GetMinSize(child, 1), child.rect.height);
|
||||
|
||||
float requiredWidth = hasRow ? currentRowWidth + spacingX + childWidth : childWidth;
|
||||
bool shouldWrap = hasRow && requiredWidth > availableWidth;
|
||||
|
||||
if (shouldWrap)
|
||||
{
|
||||
rows.Add(new RowInfo
|
||||
{
|
||||
startIndex = currentRowStart,
|
||||
endIndex = i - 1,
|
||||
width = currentRowWidth,
|
||||
height = currentRowHeight
|
||||
});
|
||||
|
||||
currentRowStart = i;
|
||||
currentRowWidth = childWidth;
|
||||
currentRowHeight = childHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRowWidth = hasRow ? requiredWidth : childWidth;
|
||||
currentRowHeight = Mathf.Max(currentRowHeight, childHeight);
|
||||
hasRow = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRow)
|
||||
{
|
||||
rows.Add(new RowInfo
|
||||
{
|
||||
startIndex = currentRowStart,
|
||||
endIndex = rectChildren.Count - 1,
|
||||
width = currentRowWidth,
|
||||
height = currentRowHeight
|
||||
});
|
||||
}
|
||||
|
||||
calculatedPreferredHeight = padding.vertical;
|
||||
for (int i = 0; i < rows.Count; i++)
|
||||
{
|
||||
calculatedPreferredHeight += rows[i].height;
|
||||
if (i < rows.Count - 1)
|
||||
{
|
||||
calculatedPreferredHeight += spacingY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetChildrenAlongAxis()
|
||||
{
|
||||
float availableWidth = GetAvailableWidth();
|
||||
float y = padding.top;
|
||||
|
||||
for (int rowIndex = 0; rowIndex < rows.Count; rowIndex++)
|
||||
{
|
||||
RowInfo row = rows[rowIndex];
|
||||
float startX = GetRowStartX(availableWidth, row.width);
|
||||
float x = startX;
|
||||
|
||||
for (int i = row.startIndex; i <= row.endIndex; i++)
|
||||
{
|
||||
RectTransform child = rectChildren[i];
|
||||
if (child == null) continue;
|
||||
|
||||
float childWidth = Mathf.Max(LayoutUtility.GetPreferredSize(child, 0), LayoutUtility.GetMinSize(child, 0), child.rect.width);
|
||||
float childHeight = expandChildHeight
|
||||
? forcedChildHeight
|
||||
: Mathf.Max(LayoutUtility.GetPreferredSize(child, 1), LayoutUtility.GetMinSize(child, 1), child.rect.height);
|
||||
|
||||
float offsetY = GetChildVerticalOffset(row.height, childHeight);
|
||||
SetChildAlongAxis(child, 0, x, childWidth);
|
||||
SetChildAlongAxis(child, 1, y + offsetY, childHeight);
|
||||
x += childWidth + spacingX;
|
||||
}
|
||||
|
||||
y += row.height + spacingY;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetRowStartX(float availableWidth, float rowWidth)
|
||||
{
|
||||
TextAnchor anchor = childAlignment;
|
||||
switch (anchor)
|
||||
{
|
||||
case TextAnchor.UpperCenter:
|
||||
case TextAnchor.MiddleCenter:
|
||||
case TextAnchor.LowerCenter:
|
||||
return padding.left + Mathf.Max(0f, (availableWidth - rowWidth) * 0.5f);
|
||||
|
||||
case TextAnchor.UpperRight:
|
||||
case TextAnchor.MiddleRight:
|
||||
case TextAnchor.LowerRight:
|
||||
return padding.left + Mathf.Max(0f, availableWidth - rowWidth);
|
||||
|
||||
default:
|
||||
return padding.left;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetChildVerticalOffset(float rowHeight, float childHeight)
|
||||
{
|
||||
TextAnchor anchor = childAlignment;
|
||||
switch (anchor)
|
||||
{
|
||||
case TextAnchor.MiddleLeft:
|
||||
case TextAnchor.MiddleCenter:
|
||||
case TextAnchor.MiddleRight:
|
||||
return Mathf.Max(0f, (rowHeight - childHeight) * 0.5f);
|
||||
|
||||
case TextAnchor.LowerLeft:
|
||||
case TextAnchor.LowerCenter:
|
||||
case TextAnchor.LowerRight:
|
||||
return Mathf.Max(0f, rowHeight - childHeight);
|
||||
|
||||
default:
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetAvailableWidth()
|
||||
{
|
||||
float availableWidth = rectTransform.rect.width - padding.horizontal;
|
||||
if (availableWidth > 0f)
|
||||
{
|
||||
return availableWidth;
|
||||
}
|
||||
|
||||
RectTransform parentRect = rectTransform.parent as RectTransform;
|
||||
if (parentRect != null)
|
||||
{
|
||||
availableWidth = parentRect.rect.width - padding.horizontal;
|
||||
if (availableWidth > 0f)
|
||||
{
|
||||
return availableWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return float.PositiveInfinity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24c5b6ddd04e710409080eb450a8a928
|
||||
@@ -7,9 +7,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using DG.Tweening;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Bansonic;
|
||||
@@ -17,26 +15,43 @@ using Bansonic;
|
||||
class UI_Panel_Character : MonoBehaviour
|
||||
{
|
||||
[SerializeField] float anim_Time = 0.5f;
|
||||
[SerializeField, Range(0f, 1f)] float illustrationFadeStartAlpha = 0.25f;
|
||||
float Anim_Speed => 1f / anim_Time;
|
||||
|
||||
[SerializeField] List<Animator> ui_Anim;
|
||||
[SerializeField] Transform content_Character_Slot;
|
||||
[SerializeField] Button button_Character_Slot_Prefab;
|
||||
[SerializeField] GameObject button_Character_Slot_Prefab;
|
||||
[SerializeField] Text text_Character_Name;
|
||||
[SerializeField] Text text_char_name;
|
||||
[SerializeField] Image Image_Character_Illustration;
|
||||
[SerializeField] Image Image_Character_Illustration_BG;
|
||||
|
||||
//[Header("")]
|
||||
|
||||
[Header("buttons")]
|
||||
public Button change_thisHero_skin;
|
||||
public Button thisHero_detail;
|
||||
public Button confirm_thisHero;
|
||||
|
||||
[Header("quit")]
|
||||
[SerializeField] Button quitButton;
|
||||
|
||||
[Header("Data Paths")]
|
||||
[Tooltip("Path relative to Resources folder for Editor mode")]
|
||||
public string editorResourcePath = "so/ally";
|
||||
[Tooltip("Path relative to Resources folder for Runtime mode")]
|
||||
public string runtimeResourcePath = "so/ally";
|
||||
|
||||
private List<AllyHero_SO> allyHeroList = new List<AllyHero_SO>();
|
||||
private readonly List<AllyHero_SO> allyHeroList = new List<AllyHero_SO>();
|
||||
private readonly List<uiui_character_displayPrefab> heroDisplayPrefabs = new List<uiui_character_displayPrefab>();
|
||||
private Vector2 originalIllustrationPos;
|
||||
private Vector2 originalIllustrationBGPos;
|
||||
private bool hasCachedPositions;
|
||||
private Coroutine c_Character_Illustration_Anim;
|
||||
private int previewCharacterIndex = -1;
|
||||
private CanvasGroup illustrationCanvasGroup;
|
||||
|
||||
private const string SAVED_HERO_ID_KEY = "SelectedMainHeroID";
|
||||
|
||||
private void Update()
|
||||
{
|
||||
@@ -45,135 +60,129 @@ class UI_Panel_Character : MonoBehaviour
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
private Vector2 originalIllustrationPos;
|
||||
private Vector2 originalIllustrationBGPos;
|
||||
private bool hasCachedPositions = false;
|
||||
private const string SAVED_HERO_ID_KEY = "SelectedMainHeroID";
|
||||
|
||||
void Start()
|
||||
{
|
||||
UnityEngine.Debug.Log($"[UI_Panel_Character] Start called. Illustration: {Image_Character_Illustration}");
|
||||
|
||||
// 初始化按钮监听
|
||||
if (change_thisHero_skin != null)
|
||||
change_thisHero_skin.onClick.AddListener(() => gNotice.warning.display("此版本未开放皮肤切换功能"));
|
||||
|
||||
change_thisHero_skin.onClick.AddListener(() => gNotice.warning.display("\u6b64\u7248\u672c\u672a\u5f00\u653e\u76ae\u80a4\u5207\u6362\u529f\u80fd"));
|
||||
|
||||
if (thisHero_detail != null)
|
||||
thisHero_detail.onClick.AddListener(() => gNotice.warning.display("此版本未开放详情查看功能"));
|
||||
|
||||
thisHero_detail.onClick.AddListener(() => gNotice.warning.display("\u6b64\u7248\u672c\u672a\u5f00\u653e\u8be6\u60c5\u67e5\u770b\u529f\u80fd"));
|
||||
|
||||
if (confirm_thisHero != null)
|
||||
confirm_thisHero.onClick.AddListener(OnConfirmHeroClicked);
|
||||
|
||||
if (quitButton != null)
|
||||
quitButton.onClick.AddListener(() => gameObject.SetActive(false));
|
||||
|
||||
if (Image_Character_Illustration != null)
|
||||
{
|
||||
// 设置为图示的数值:Pos X: 376, Pos Y: -306
|
||||
Image_Character_Illustration.rectTransform.anchoredPosition = new Vector2(376, -306);
|
||||
|
||||
originalIllustrationPos = Image_Character_Illustration.rectTransform.anchoredPosition;
|
||||
if (Image_Character_Illustration.transform.parent.TryGetComponent(out CanvasGroup canvasGroup))
|
||||
|
||||
if (Image_Character_Illustration.transform.parent != null &&
|
||||
Image_Character_Illustration.transform.parent.TryGetComponent(out CanvasGroup canvasGroup))
|
||||
{
|
||||
canvasGroup.alpha = 0;
|
||||
illustrationCanvasGroup = canvasGroup;
|
||||
illustrationCanvasGroup.alpha = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Image_Character_Illustration_BG != null)
|
||||
{
|
||||
// 设置运行时 X 值为 376
|
||||
Vector2 pos = Image_Character_Illustration_BG.rectTransform.anchoredPosition;
|
||||
pos.x = 376;
|
||||
Image_Character_Illustration_BG.rectTransform.anchoredPosition = pos;
|
||||
|
||||
originalIllustrationBGPos = Image_Character_Illustration_BG.rectTransform.anchoredPosition;
|
||||
}
|
||||
|
||||
hasCachedPositions = true;
|
||||
|
||||
foreach (var item in ui_Anim)
|
||||
{
|
||||
if (item != null) item.speed = Anim_Speed;
|
||||
if (item != null)
|
||||
item.speed = Anim_Speed;
|
||||
}
|
||||
|
||||
|
||||
if (content_Character_Slot != null)
|
||||
content_Character_Slot.Get_Childrens_Component<UI_Button_Character_Head_Slot>(true, true);
|
||||
|
||||
// Load AllyHero_SO data
|
||||
string path = Application.isEditor ? editorResourcePath : runtimeResourcePath;
|
||||
var loadedData = Resources.LoadAll<AllyHero_SO>(path);
|
||||
allyHeroList = new List<AllyHero_SO>(loadedData);
|
||||
|
||||
UnityEngine.Debug.Log($"[UI_Panel_Character] Loaded {allyHeroList.Count} heroes from {path}");
|
||||
var loadedData = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
allyHeroList.Clear();
|
||||
allyHeroList.AddRange(loadedData);
|
||||
heroDisplayPrefabs.Clear();
|
||||
|
||||
UnityEngine.Debug.Log($"[UI_Panel_Character] Loaded {allyHeroList.Count} heroes");
|
||||
|
||||
// Ensure consistent order with UI_Panel_Main
|
||||
allyHeroList.Sort((a, b) => a.ally_heroID.CompareTo(b.ally_heroID));
|
||||
|
||||
// 读取保存的角色 ID
|
||||
int savedHeroID = PlayerPrefs.GetInt(SAVED_HERO_ID_KEY, -1);
|
||||
int initialIndex = 0;
|
||||
|
||||
for (int i = 0; i < allyHeroList.Count; i++)
|
||||
{
|
||||
var obj = Instantiate(button_Character_Slot_Prefab, content_Character_Slot);
|
||||
|
||||
// Register UI sounds for the newly instantiated button
|
||||
UISystemBootstrap.RegisterHierarchy(obj.gameObject);
|
||||
var slotObject = Instantiate(button_Character_Slot_Prefab, content_Character_Slot);
|
||||
var displayPrefab = slotObject.GetComponent<uiui_character_displayPrefab>();
|
||||
var slotButton = displayPrefab != null ? displayPrefab.characterProfileButton : slotObject.GetComponent<Button>();
|
||||
if (slotButton == null && slotObject.transform.parent != null)
|
||||
{
|
||||
slotButton = slotObject.transform.parent.GetComponent<Button>();
|
||||
}
|
||||
|
||||
UISystemBootstrap.RegisterHierarchy(slotObject);
|
||||
|
||||
var data = allyHeroList[i];
|
||||
|
||||
// 如果 ID 匹配,设置初始索引
|
||||
if (savedHeroID != -1 && data.ally_heroID == savedHeroID)
|
||||
{
|
||||
initialIndex = i;
|
||||
}
|
||||
|
||||
if (obj.image != null)
|
||||
if (displayPrefab != null)
|
||||
{
|
||||
obj.image.sprite = data.ally_heroSelectIcon;
|
||||
obj.image.preserveAspect = true; // 保持比例,防止拉伸变形
|
||||
obj.image.type = Image.Type.Simple; // 确保是普通显示模式
|
||||
|
||||
// 确保 RectTransform 撑满父物体并居中
|
||||
obj.image.rectTransform.anchorMin = Vector2.zero;
|
||||
obj.image.rectTransform.anchorMax = Vector2.one;
|
||||
obj.image.rectTransform.sizeDelta = Vector2.zero;
|
||||
obj.image.rectTransform.anchoredPosition = Vector2.zero;
|
||||
displayPrefab.SetDisplay(data.ally_heroSelectIcon, data.ally_heroID);
|
||||
}
|
||||
heroDisplayPrefabs.Add(displayPrefab);
|
||||
|
||||
if (obj.TryGetComponent(out UI_Button_Character_Head_Slot head_Slot))
|
||||
if (slotObject.TryGetComponent(out UI_Button_Character_Head_Slot head_Slot))
|
||||
{
|
||||
head_Slot.index = i;
|
||||
int capturedIndex = i;
|
||||
obj.onClick.AddListener(() => Set_Character_Index(capturedIndex));
|
||||
if (slotButton != null)
|
||||
{
|
||||
slotButton.onClick.AddListener(() => Set_Character_Index(capturedIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 应用初始索引(来自保存的 ID 或默认 0)
|
||||
|
||||
previewCharacterIndex = initialIndex;
|
||||
UI_Panel_Main.Singleton.Character_Index = initialIndex;
|
||||
|
||||
// Initial character display
|
||||
Set_Character();
|
||||
RefreshSelectedState(false);
|
||||
}
|
||||
|
||||
public void Set_Character_Index(int index)
|
||||
{
|
||||
if (UI_Panel_Main.Singleton.Character_Index == index) return;
|
||||
UI_Panel_Main.Singleton.Character_Index = index;
|
||||
if (previewCharacterIndex == index)
|
||||
return;
|
||||
|
||||
previewCharacterIndex = index;
|
||||
Set_Character();
|
||||
|
||||
// 人性化逻辑:点击头像即自动设为看板并保存,并显示提示内容
|
||||
SaveCurrentCharacter(true);
|
||||
RefreshSelectedState(true);
|
||||
}
|
||||
|
||||
private void OnConfirmHeroClicked()
|
||||
{
|
||||
if (SaveCurrentCharacter(true))
|
||||
{
|
||||
// 确认按钮逻辑:保存后禁用自身物体(隐藏面板)
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool SaveCurrentCharacter(bool showNotice)
|
||||
{
|
||||
int currentIndex = UI_Panel_Main.Singleton.Character_Index;
|
||||
int currentIndex = GetCurrentPreviewIndex();
|
||||
if (allyHeroList != null && currentIndex >= 0 && currentIndex < allyHeroList.Count)
|
||||
{
|
||||
int heroID = allyHeroList[currentIndex].ally_heroID;
|
||||
@@ -182,57 +191,104 @@ class UI_Panel_Character : MonoBehaviour
|
||||
|
||||
if (showNotice)
|
||||
{
|
||||
gNotice.alarm.display($"已将 {allyHeroList[currentIndex].ally_heroName} 设置为记录对象。");
|
||||
gNotice.alarm.display($"\u5df2\u5c06 {allyHeroList[currentIndex].ally_heroName} \u8bbe\u7f6e\u4e3a\u770b\u677f\u5bf9\u8c61\u3002");
|
||||
}
|
||||
|
||||
// 同步更新主面板的看板图
|
||||
previewCharacterIndex = currentIndex;
|
||||
UI_Panel_Main.Singleton.Character_Index = currentIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
previewCharacterIndex = UI_Panel_Main.Singleton != null ? UI_Panel_Main.Singleton.Character_Index : previewCharacterIndex;
|
||||
Set_Character();
|
||||
RefreshSelectedState(false);
|
||||
}
|
||||
Coroutine c_Character_Illustration_Anim;
|
||||
|
||||
private void RefreshSelectedState(bool animated)
|
||||
{
|
||||
int selectedIndex = GetCurrentPreviewIndex();
|
||||
for (int i = 0; i < heroDisplayPrefabs.Count; i++)
|
||||
{
|
||||
if (heroDisplayPrefabs[i] != null)
|
||||
{
|
||||
heroDisplayPrefabs[i].SetSelected(i == selectedIndex, animated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetCurrentPreviewIndex()
|
||||
{
|
||||
if (previewCharacterIndex >= 0 && previewCharacterIndex < allyHeroList.Count)
|
||||
{
|
||||
return previewCharacterIndex;
|
||||
}
|
||||
|
||||
if (UI_Panel_Main.Singleton != null)
|
||||
{
|
||||
return UI_Panel_Main.Singleton.Character_Index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void Set_Character()
|
||||
{
|
||||
if (Image_Character_Illustration == null || illustrationCanvasGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (c_Character_Illustration_Anim != null)
|
||||
{
|
||||
StopCoroutine(c_Character_Illustration_Anim);
|
||||
c_Character_Illustration_Anim = null;
|
||||
}
|
||||
if (Image_Character_Illustration.transform.parent.TryGetComponent(out CanvasGroup canvasGroup))
|
||||
{
|
||||
c_Character_Illustration_Anim = StartCoroutine(C_Character_Illustration_Anim(canvasGroup, Set_Character_Show));
|
||||
}
|
||||
|
||||
illustrationCanvasGroup.DOKill();
|
||||
Set_Character_Show();
|
||||
illustrationCanvasGroup.alpha = illustrationFadeStartAlpha;
|
||||
illustrationCanvasGroup.DOFade(1f, anim_Time * 0.6f)
|
||||
.SetEase(Ease.OutCubic)
|
||||
.SetLink(illustrationCanvasGroup.gameObject);
|
||||
}
|
||||
|
||||
public void Set_Character_Show()
|
||||
{
|
||||
int index = UI_Panel_Main.Singleton.Character_Index;
|
||||
if (allyHeroList == null || index < 0 || index >= allyHeroList.Count) return;
|
||||
int index = GetCurrentPreviewIndex();
|
||||
if (allyHeroList.Count == 0 || index < 0 || index >= allyHeroList.Count)
|
||||
return;
|
||||
|
||||
// 停止之前的 Tween 以防冲突
|
||||
Image_Character_Illustration.rectTransform.DOKill();
|
||||
Image_Character_Illustration_BG.rectTransform.DOKill();
|
||||
text_Character_Name.DOKill();
|
||||
if (text_char_name != null)
|
||||
{
|
||||
text_char_name.DOKill();
|
||||
}
|
||||
|
||||
var data = allyHeroList[index];
|
||||
|
||||
// 使用初始缓存的坐标,防止频繁点击导致的偏移累积
|
||||
Vector2 targetPos1 = hasCachedPositions ? originalIllustrationPos : Image_Character_Illustration.rectTransform.anchoredPosition;
|
||||
|
||||
Vector2 targetPos1 = hasCachedPositions
|
||||
? originalIllustrationPos
|
||||
: Image_Character_Illustration.rectTransform.anchoredPosition;
|
||||
Image_Character_Illustration.sprite = data.ally_hero_HD_image;
|
||||
|
||||
// 动画:从左侧 200 像素滑入到目标位置
|
||||
|
||||
float startX1 = targetPos1.x - 200;
|
||||
Image_Character_Illustration.rectTransform.anchoredPosition = new Vector2(startX1, targetPos1.y);
|
||||
Image_Character_Illustration.rectTransform.DOAnchorPos(targetPos1, anim_Time)
|
||||
.SetEase(Ease.OutCubic)
|
||||
.SetLink(Image_Character_Illustration.gameObject);
|
||||
|
||||
Vector2 targetPosBG = hasCachedPositions ? originalIllustrationBGPos : Image_Character_Illustration_BG.rectTransform.anchoredPosition;
|
||||
Vector2 targetPosBG = hasCachedPositions
|
||||
? originalIllustrationBGPos
|
||||
: Image_Character_Illustration_BG.rectTransform.anchoredPosition;
|
||||
Image_Character_Illustration_BG.sprite = data.ally_hero_HD_image;
|
||||
|
||||
|
||||
float startXBG = targetPosBG.x - 200;
|
||||
Image_Character_Illustration_BG.rectTransform.anchoredPosition = new Vector2(startXBG, targetPosBG.y);
|
||||
Image_Character_Illustration_BG.rectTransform.DOAnchorPos(targetPosBG, anim_Time)
|
||||
@@ -246,19 +302,13 @@ class UI_Panel_Character : MonoBehaviour
|
||||
text_Character_Name.DOText(text, anim_Time)
|
||||
.SetEase(Ease.Linear)
|
||||
.SetLink(text_Character_Name.gameObject);
|
||||
}
|
||||
IEnumerator C_Character_Illustration_Anim(CanvasGroup canvasGroup, Action onHide)
|
||||
{
|
||||
while (canvasGroup.alpha > 0)
|
||||
|
||||
if (text_char_name != null)
|
||||
{
|
||||
canvasGroup.alpha -= Anim_Speed * Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
onHide?.Invoke();
|
||||
while (canvasGroup.alpha < 1)
|
||||
{
|
||||
canvasGroup.alpha += Anim_Speed * Time.deltaTime;
|
||||
yield return null;
|
||||
text_char_name.text = string.Empty;
|
||||
text_char_name.DOText(text, anim_Time)
|
||||
.SetEase(Ease.Linear)
|
||||
.SetLink(text_char_name.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,12 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
if (slot.mailTitle != null) slot.mailTitle.text = mail.mail_title;
|
||||
if (slot.mailSender != null) slot.mailSender.text = mail.mail_sender;
|
||||
if (slot.mailTime != null) slot.mailTime.text = mail.mail_date;
|
||||
if (slot.mailImage != null) slot.mailImage.sprite = mail.mail_image;
|
||||
if (slot.mailImage != null)
|
||||
{
|
||||
slot.mailImage.sprite = mail.mail_image;
|
||||
}
|
||||
slot.RefreshRewardsWithMailText(mail);
|
||||
slot.RefreshRewardPreviews(mail);
|
||||
TryBindServerMailImage(mail, slot);
|
||||
UpdateSlotVisuals(slot, mail);
|
||||
SetSelectedState(slot, false);
|
||||
@@ -295,7 +300,8 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
reward_description = rewardEntry.reward_description ?? string.Empty,
|
||||
reward_key = rewardEntry.reward_key ?? string.Empty,
|
||||
reward_store_item_id = rewardEntry.reward_store_item_id,
|
||||
reward_image = null
|
||||
reward_image = null,
|
||||
reward_icon_url = rewardEntry.reward_icon_url ?? string.Empty
|
||||
};
|
||||
MailRewardGrantService.PopulateRewardDisplay(reward);
|
||||
mail.rewardList.Add(reward);
|
||||
@@ -418,10 +424,7 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
var go = Instantiate(rewardSlotPrefab, content_Reward_Slot);
|
||||
var slot = go.GetComponent<rewardSlotPrefab>();
|
||||
if (slot == null) continue;
|
||||
if (slot.rewardName != null) slot.rewardName.text = reward.rewardName;
|
||||
if (slot.rewardAmount != null) slot.rewardAmount.text = reward.reward_ammount == 1 ? string.Empty : reward.reward_ammount.ToString();
|
||||
if (slot.rewardIcon != null) slot.rewardIcon.sprite = reward.reward_image;
|
||||
if (slot.detailText != null) slot.detailText.text = reward.reward_description;
|
||||
slot.BindReward(reward);
|
||||
if (slot.detailBtm != null) slot.detailBtm.SetActive(false);
|
||||
}
|
||||
|
||||
@@ -486,17 +489,20 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
if (mail.mail_image != null)
|
||||
{
|
||||
slot.mailImage.sprite = mail.mail_image;
|
||||
SetMailImageVisible(slot, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_serverMailImageUrls.TryGetValue(mail.mail_id, out string url) || string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
SetMailImageVisible(slot, false);
|
||||
return;
|
||||
}
|
||||
|
||||
string normalizedUrl = NormalizeMailImageUrl(url);
|
||||
if (string.IsNullOrWhiteSpace(normalizedUrl))
|
||||
{
|
||||
SetMailImageVisible(slot, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -504,6 +510,7 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
{
|
||||
mail.mail_image = cachedSprite;
|
||||
slot.mailImage.sprite = cachedSprite;
|
||||
SetMailImageVisible(slot, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -512,6 +519,7 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
SetMailImageVisible(slot, false);
|
||||
StartCoroutine(LoadServerMailImageRoutine(normalizedUrl, mail, slot));
|
||||
}
|
||||
|
||||
@@ -547,6 +555,7 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
if (slot != null && slot.mailImage != null)
|
||||
{
|
||||
slot.mailImage.sprite = sprite;
|
||||
SetMailImageVisible(slot, true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -556,6 +565,20 @@ public class UI_Panel_Mail : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetMailImageVisible(mailSlotPrefab slot, bool visible)
|
||||
{
|
||||
if (slot == null || slot.mailImage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject imageObject = slot.mailImage.gameObject;
|
||||
if (imageObject != null && imageObject.activeSelf != visible)
|
||||
{
|
||||
imageObject.SetActive(visible);
|
||||
}
|
||||
}
|
||||
|
||||
string NormalizeMailImageUrl(string imageUrl)
|
||||
{
|
||||
string trimmed = imageUrl?.Trim() ?? string.Empty;
|
||||
@@ -1287,6 +1310,53 @@ public static class MailRewardGrantService
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetRewardRarity(mail_so.rewardItem reward, out ItemRarity rarity)
|
||||
{
|
||||
rarity = ItemRarity.None;
|
||||
if (reward == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureAssetsLoaded();
|
||||
int amount = Mathf.Max(1, reward.reward_ammount);
|
||||
string rewardKey = string.IsNullOrWhiteSpace(reward.reward_key) ? string.Empty : reward.reward_key.Trim();
|
||||
string rewardName = string.IsNullOrWhiteSpace(reward.rewardName) ? string.Empty : reward.rewardName.Trim();
|
||||
|
||||
if (TryFindStoreItemReference(reward, rewardKey, rewardName, out storeItemSO storeItem) && storeItem != null)
|
||||
{
|
||||
rarity = storeItem.itemRarity;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (reward.reward_Type == mail_so.reward_type.expBottles_allies)
|
||||
{
|
||||
if (TryResolveExpBottle(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.ExpBottleAsset != null)
|
||||
{
|
||||
rarity = resolved.ExpBottleAsset.itemRarity;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (reward.reward_Type == mail_so.reward_type.growth_material)
|
||||
{
|
||||
if (TryResolveGrowthMaterial(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.GrowthMaterialAsset != null)
|
||||
{
|
||||
rarity = resolved.GrowthMaterialAsset.itemRarity;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (reward.reward_Type == mail_so.reward_type.equipment_consumable)
|
||||
{
|
||||
if (TryResolveEquipmentConsumable(reward, rewardKey, rewardName, amount, out ResolvedReward resolved) && resolved != null && resolved.EquipmentConsumableAsset != null)
|
||||
{
|
||||
rarity = resolved.EquipmentConsumableAsset.itemRarity;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGrantAll(mail_so mail, out string failureMessage)
|
||||
{
|
||||
failureMessage = string.Empty;
|
||||
@@ -1482,6 +1552,21 @@ public static class MailRewardGrantService
|
||||
{
|
||||
StoreItemsByName[item.name] = item;
|
||||
}
|
||||
|
||||
// expBottlesSO / growthMaterialSO / equipmentConsumableSO live outside Resources,
|
||||
// so Resources.LoadAll returns nothing. Backfill from storeItemSO direct references instead.
|
||||
if (item.associatedExpBottle != null && !ExpBottleAssets.ContainsKey(item.associatedExpBottle.bottleKind))
|
||||
{
|
||||
ExpBottleAssets[item.associatedExpBottle.bottleKind] = item.associatedExpBottle;
|
||||
}
|
||||
if (item.associatedGrowthMaterial != null && !GrowthMaterialAssets.ContainsKey(item.associatedGrowthMaterial.materialKind))
|
||||
{
|
||||
GrowthMaterialAssets[item.associatedGrowthMaterial.materialKind] = item.associatedGrowthMaterial;
|
||||
}
|
||||
if (item.associatedEquipmentConsumable != null && !EquipmentConsumableAssets.ContainsKey(item.associatedEquipmentConsumable.consumableKind))
|
||||
{
|
||||
EquipmentConsumableAssets[item.associatedEquipmentConsumable.consumableKind] = item.associatedEquipmentConsumable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
|
||||
|
||||
private IEnumerator InitDataRoutine()
|
||||
{
|
||||
var rawData = Resources.LoadAll<AllyHero_SO>("so/ally");
|
||||
var rawData = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
data_List = new List<AllyHero_SO>(rawData);
|
||||
|
||||
data_List.Sort((a, b) => a.ally_heroID.CompareTo(b.ally_heroID));
|
||||
@@ -318,7 +318,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
|
||||
button_Story.onClick.AddListener(
|
||||
() =>
|
||||
{
|
||||
gNotice.warning.display("此版本未开放该功能");
|
||||
gNotice.error.display("功能未开放");
|
||||
//Try_Open_Panel(ui_Panel_Story);
|
||||
});
|
||||
if (button_Idol != null)
|
||||
@@ -486,10 +486,19 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
|
||||
|
||||
IEnumerator LoadSelectSceneAsync()
|
||||
{
|
||||
if (gTransition.LoadScene(ui_Select_Music_Scene_Name, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(ui_Select_Music_Scene_Name, LoadSceneMode.Single);
|
||||
|
||||
// Documentation text normalized.
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@@ -1538,7 +1538,7 @@ MonoBehaviour:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 8cdc81ed9eb4f4a41bdad444afbea4c2, type: 3}
|
||||
m_Type: 3
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 1
|
||||
@@ -5956,7 +5956,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_IgnoreReversedGraphics: 1
|
||||
m_IgnoreReversedGraphics: 0
|
||||
m_BlockingObjects: 0
|
||||
m_BlockingMask:
|
||||
serializedVersion: 2
|
||||
|
||||
@@ -124,12 +124,15 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
private Coroutine musicPicFade;
|
||||
private bool musicPicVisible = true;
|
||||
private bool navSceneLoading = false;
|
||||
private Coroutine deferredUiRefreshRoutine;
|
||||
private bool lastSettingsVisibilityState;
|
||||
private bool lastOverlayPanelsVisibilityState;
|
||||
private readonly Dictionary<string, int> guideIndexByScene = new Dictionary<string, int>();
|
||||
private string currentGuideScene = string.Empty;
|
||||
private static btmandtopController activeInstance;
|
||||
private RectTransform topNavigationRoot;
|
||||
private bool topNavigationGeometryDirty = true;
|
||||
private int lastTopNavigationParentChildCount = -1;
|
||||
|
||||
private static readonly List<string> sceneHistory = new List<string>();
|
||||
private static bool sceneHistoryHooked = false;
|
||||
@@ -203,6 +206,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
|
||||
BindNewBackButtons();
|
||||
EnsureTopNavigationFront();
|
||||
ScheduleDeferredUiRefresh();
|
||||
|
||||
EnsureMusicPicRoot();
|
||||
SetupMusicPicDefault();
|
||||
@@ -279,14 +283,11 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
if (userInfoInstance == null)
|
||||
{
|
||||
userInfoInstance = Instantiate(userInfo_prefab, putPrefabsHere.transform);
|
||||
TryAssignCanvasCamera(userInfoInstance);
|
||||
PlacePanelBelowSettings(userInfoInstance);
|
||||
userInfoInstance.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
userInfoInstance.transform.SetParent(putPrefabsHere.transform, false);
|
||||
PlacePanelBelowSettings(userInfoInstance);
|
||||
userInfoInstance.SetActive(false);
|
||||
}
|
||||
}
|
||||
@@ -874,43 +875,16 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
|
||||
private void ToggleUserInfoPrefab()
|
||||
{
|
||||
if (userInfo_prefab == null || putPrefabsHere == null) return;
|
||||
|
||||
if (userInfoInstance == null)
|
||||
if (ToggleIfAlreadyOpen(ref userInfoInstance))
|
||||
{
|
||||
userInfoInstance = Instantiate(userInfo_prefab, putPrefabsHere.transform);
|
||||
TryAssignCanvasCamera(userInfoInstance);
|
||||
PlacePanelBelowSettings(userInfoInstance);
|
||||
userInfoInstance.SetActive(true);
|
||||
BroadcastOverlayPanelsVisibility();
|
||||
RegisterManagedPanel(userInfoInstance, () =>
|
||||
{
|
||||
if (userInfoInstance != null)
|
||||
{
|
||||
userInfoInstance.SetActive(false);
|
||||
BroadcastOverlayPanelsVisibility();
|
||||
}
|
||||
});
|
||||
EnsureTopNavigationFront();
|
||||
return;
|
||||
}
|
||||
|
||||
userInfoInstance.transform.SetParent(putPrefabsHere.transform, false);
|
||||
userInfoInstance.SetActive(!userInfoInstance.activeSelf);
|
||||
if (userInfoInstance.activeSelf)
|
||||
if (OpenPrefab(userInfo_prefab, ref userInfoInstance))
|
||||
{
|
||||
PlacePanelBelowSettings(userInfoInstance);
|
||||
RegisterManagedPanel(userInfoInstance, () =>
|
||||
{
|
||||
if (userInfoInstance != null)
|
||||
{
|
||||
userInfoInstance.SetActive(false);
|
||||
BroadcastOverlayPanelsVisibility();
|
||||
}
|
||||
});
|
||||
CloseInfoPanels(userInfoInstance);
|
||||
RegisterManagedPanel(userInfoInstance, () => CloseManagedOverlay(userInfoInstance));
|
||||
}
|
||||
BroadcastOverlayPanelsVisibility();
|
||||
EnsureTopNavigationFront();
|
||||
}
|
||||
|
||||
private void BroadcastSettingsVisibility(bool visible)
|
||||
@@ -951,6 +925,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
lastOverlayPanelsVisibilityState = visible;
|
||||
CurrentOverlayPanelsVisible = visible;
|
||||
GlobalOverlayPanelVisibilityChanged?.Invoke(visible);
|
||||
topNavigationGeometryDirty = true;
|
||||
EnsureTopNavigationFront();
|
||||
}
|
||||
|
||||
@@ -1079,7 +1054,6 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
if (instance == null)
|
||||
{
|
||||
instance = Instantiate(prefab, putPrefabsHere.transform);
|
||||
TryAssignCanvasCamera(instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1097,7 +1071,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
instance.SetActive(true);
|
||||
}
|
||||
|
||||
PlacePanelBelowSettings(instance);
|
||||
TryAssignCanvasCamera(instance);
|
||||
BroadcastOverlayPanelsVisibility();
|
||||
EnsureTopNavigationFront();
|
||||
return true;
|
||||
@@ -1114,30 +1088,9 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
settingsInstance.transform.SetAsLastSibling();
|
||||
}
|
||||
|
||||
private void PlacePanelBelowSettings(GameObject instance)
|
||||
{
|
||||
if (instance == null || putPrefabsHere == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
instance.transform.SetParent(putPrefabsHere.transform, false);
|
||||
|
||||
if (settingsInstance == null || instance == settingsInstance)
|
||||
{
|
||||
instance.transform.SetAsLastSibling();
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureSettingsLastSibling();
|
||||
int settingsIndex = settingsInstance.transform.GetSiblingIndex();
|
||||
int targetIndex = Mathf.Clamp(settingsIndex, 0, putPrefabsHere.transform.childCount - 1);
|
||||
instance.transform.SetSiblingIndex(targetIndex);
|
||||
EnsureSettingsLastSibling();
|
||||
}
|
||||
|
||||
private void CloseInfoPanels(GameObject keep)
|
||||
{
|
||||
CloseInstance(ref userInfoInstance, keep);
|
||||
CloseInstance(ref storeInstance, keep);
|
||||
CloseInstance(ref showLevelInstance, keep);
|
||||
CloseInstance(ref emailInstance, keep);
|
||||
@@ -1292,9 +1245,27 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
if (topNavigationRoot.GetSiblingIndex() != parent.childCount - 1)
|
||||
{
|
||||
topNavigationRoot.SetAsLastSibling();
|
||||
topNavigationGeometryDirty = true;
|
||||
}
|
||||
|
||||
RefreshTopNavigationGeometry();
|
||||
// The parent's child count changing means a panel was shown/hidden, which can
|
||||
// shift the top-bar layout. Use it as a cheap heuristic to re-settle geometry.
|
||||
if (parent.childCount != lastTopNavigationParentChildCount)
|
||||
{
|
||||
lastTopNavigationParentChildCount = parent.childCount;
|
||||
topNavigationGeometryDirty = true;
|
||||
}
|
||||
|
||||
if (topNavigationGeometryDirty)
|
||||
{
|
||||
topNavigationGeometryDirty = false;
|
||||
RefreshTopNavigationGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
public void MarkTopNavigationGeometryDirty()
|
||||
{
|
||||
topNavigationGeometryDirty = true;
|
||||
}
|
||||
|
||||
private void ResolveTopNavigationRoot()
|
||||
@@ -1411,6 +1382,40 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
}
|
||||
}
|
||||
trackedCurrentScene = incoming;
|
||||
|
||||
if (activeInstance != null)
|
||||
{
|
||||
activeInstance.topNavigationGeometryDirty = true;
|
||||
activeInstance.EnsureTopNavigationFront();
|
||||
activeInstance.ScheduleDeferredUiRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleDeferredUiRefresh()
|
||||
{
|
||||
if (!isActiveAndEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (deferredUiRefreshRoutine != null)
|
||||
{
|
||||
StopCoroutine(deferredUiRefreshRoutine);
|
||||
}
|
||||
|
||||
deferredUiRefreshRoutine = StartCoroutine(DeferredUiRefreshRoutine());
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator DeferredUiRefreshRoutine()
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
yield return null;
|
||||
topNavigationGeometryDirty = true;
|
||||
EnsureTopNavigationFront();
|
||||
}
|
||||
|
||||
deferredUiRefreshRoutine = null;
|
||||
}
|
||||
|
||||
private static string PopPreviousSceneName(string currentScene)
|
||||
@@ -1490,11 +1495,23 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
|
||||
navSceneLoading = true;
|
||||
Time.timeScale = 1f;
|
||||
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
navSceneLoading = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
|
||||
while (op != null && !op.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
navSceneLoading = false;
|
||||
}
|
||||
|
||||
@@ -1570,4 +1587,3 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using GameServer.Client;
|
||||
using Bansonic;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
@@ -613,8 +614,15 @@ public class PauseManager : MonoBehaviour
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
yield return null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
yield return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
|
||||
private readonly Dictionary<int, int> currentExpByHeroId = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, int> unlockedTierByHeroId = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, bool> levelLockByHeroId = new Dictionary<int, bool>();
|
||||
private readonly Dictionary<int, bool> autoBreakthroughEnabledByHeroId = new Dictionary<int, bool>();
|
||||
private readonly Dictionary<int, int> deployCountsByHeroId = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, int> finishCountsByHeroId = new Dictionary<int, int>();
|
||||
@@ -153,6 +154,13 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
return autoBreakthroughEnabledByHeroId.TryGetValue(heroId, out value) && value;
|
||||
}
|
||||
|
||||
public bool IsLevelLockEnabled(int heroId)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
bool value;
|
||||
return levelLockByHeroId.TryGetValue(heroId, out value) && value;
|
||||
}
|
||||
|
||||
public void SetCurrentExp(AllyHero_SO hero, int value)
|
||||
{
|
||||
if (hero == null || hero.ally_heroID <= 0)
|
||||
@@ -164,6 +172,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
int safeValue = Mathf.Max(0, value);
|
||||
currentExpByHeroId[hero.ally_heroID] = safeValue;
|
||||
hero.ally_currentEXP = safeValue;
|
||||
int unlockedTier = GetUnlockedTierIndex(hero.ally_heroID);
|
||||
bool levelLock = ResolveLevelLockState(hero, safeValue, unlockedTier);
|
||||
levelLockByHeroId[hero.ally_heroID] = levelLock;
|
||||
hero.level_lock = levelLock;
|
||||
SyncLegacySelectedSlotExpKeys(hero.ally_heroID, safeValue);
|
||||
MarkDirty(hero);
|
||||
SaveNow();
|
||||
if (OnHeroGrowthChanged != null)
|
||||
@@ -183,7 +196,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
int safeValue = Mathf.Clamp(value, 0, 3);
|
||||
unlockedTierByHeroId[hero.ally_heroID] = safeValue;
|
||||
hero.ally_growthUnlockedTierIndex = safeValue;
|
||||
hero.ally_currentEXP = GetCurrentExp(hero.ally_heroID);
|
||||
int currentExp = GetCurrentExp(hero.ally_heroID);
|
||||
hero.ally_currentEXP = currentExp;
|
||||
bool levelLock = ResolveLevelLockState(hero, currentExp, safeValue);
|
||||
levelLockByHeroId[hero.ally_heroID] = levelLock;
|
||||
hero.level_lock = levelLock;
|
||||
MarkDirty(hero);
|
||||
SaveNow();
|
||||
if (OnHeroGrowthChanged != null)
|
||||
@@ -292,6 +309,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
}
|
||||
|
||||
SyncAllMirrorFlags();
|
||||
SyncAllLegacySelectedSlotExpKeys();
|
||||
AllyHeroDeployLedgerStorage.TrySave(BuildPayload());
|
||||
}
|
||||
|
||||
@@ -300,6 +318,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
InitializeIfNeeded();
|
||||
currentExpByHeroId.Clear();
|
||||
unlockedTierByHeroId.Clear();
|
||||
levelLockByHeroId.Clear();
|
||||
autoBreakthroughEnabledByHeroId.Clear();
|
||||
ResetGrowthStateToDefaults();
|
||||
ClearAllPendingDebtKeys();
|
||||
@@ -311,6 +330,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
{
|
||||
currentExpByHeroId.Clear();
|
||||
unlockedTierByHeroId.Clear();
|
||||
levelLockByHeroId.Clear();
|
||||
autoBreakthroughEnabledByHeroId.Clear();
|
||||
deployCountsByHeroId.Clear();
|
||||
finishCountsByHeroId.Clear();
|
||||
@@ -341,6 +361,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
|
||||
currentExpByHeroId[entry.heroId] = currentExp;
|
||||
unlockedTierByHeroId[entry.heroId] = unlockedTier;
|
||||
levelLockByHeroId[entry.heroId] = entry.levelLock;
|
||||
autoBreakthroughEnabledByHeroId[entry.heroId] = entry.autoBreakthroughEnabled;
|
||||
deployCountsByHeroId[entry.heroId] = Mathf.Max(0, entry.deployCount);
|
||||
finishCountsByHeroId[entry.heroId] = Mathf.Max(0, entry.finishCount);
|
||||
@@ -356,8 +377,9 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
{
|
||||
currentExpByHeroId.Clear();
|
||||
unlockedTierByHeroId.Clear();
|
||||
levelLockByHeroId.Clear();
|
||||
autoBreakthroughEnabledByHeroId.Clear();
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -368,6 +390,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
|
||||
currentExpByHeroId[hero.ally_heroID] = 0;
|
||||
unlockedTierByHeroId[hero.ally_heroID] = 0;
|
||||
levelLockByHeroId[hero.ally_heroID] = false;
|
||||
autoBreakthroughEnabledByHeroId[hero.ally_heroID] = false;
|
||||
|
||||
if (hero.ally_currentEXP != 0)
|
||||
@@ -387,12 +410,18 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
hero.ally_autoBreakthroughEnabled = false;
|
||||
MarkDirty(hero);
|
||||
}
|
||||
|
||||
if (hero.level_lock)
|
||||
{
|
||||
hero.level_lock = false;
|
||||
MarkDirty(hero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncAllMirrorFlags()
|
||||
{
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -421,6 +450,12 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
unlockedTierByHeroId[hero.ally_heroID] = unlockedTier;
|
||||
}
|
||||
|
||||
bool levelLock;
|
||||
if (!levelLockByHeroId.TryGetValue(hero.ally_heroID, out levelLock))
|
||||
{
|
||||
levelLock = false;
|
||||
}
|
||||
|
||||
bool autoBreakthroughEnabled;
|
||||
if (!autoBreakthroughEnabledByHeroId.TryGetValue(hero.ally_heroID, out autoBreakthroughEnabled))
|
||||
{
|
||||
@@ -431,6 +466,8 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
NormalizeGrowthState(hero, ref currentExp, ref unlockedTier);
|
||||
currentExpByHeroId[hero.ally_heroID] = currentExp;
|
||||
unlockedTierByHeroId[hero.ally_heroID] = unlockedTier;
|
||||
levelLock = ResolveLevelLockState(hero, currentExp, unlockedTier);
|
||||
levelLockByHeroId[hero.ally_heroID] = levelLock;
|
||||
|
||||
int finishCount;
|
||||
if (!finishCountsByHeroId.TryGetValue(hero.ally_heroID, out finishCount))
|
||||
@@ -469,6 +506,12 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (hero.level_lock != levelLock)
|
||||
{
|
||||
hero.level_lock = levelLock;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (hero.ally_battleDeployCount != count)
|
||||
{
|
||||
hero.ally_battleDeployCount = count;
|
||||
@@ -503,7 +546,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
private static Dictionary<int, AllyHero_SO> BuildHeroLookup()
|
||||
{
|
||||
Dictionary<int, AllyHero_SO> heroById = new Dictionary<int, AllyHero_SO>();
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -564,9 +607,41 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
unlockedTier = Mathf.Min(unlockedTier, reachableTier);
|
||||
}
|
||||
|
||||
private static bool ResolveLevelLockState(AllyHero_SO hero, int currentExp, int unlockedTier)
|
||||
{
|
||||
if (hero == null || hero.levelStats == null || hero.levelStats.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<AllyHero_SO.AllyLevelInfo> levels = new List<AllyHero_SO.AllyLevelInfo>();
|
||||
for (int i = 0; i < hero.levelStats.Count; i++)
|
||||
{
|
||||
if (hero.levelStats[i] != null)
|
||||
{
|
||||
levels.Add(hero.levelStats[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (levels.Count < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
levels.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
|
||||
int clampedTier = Mathf.Clamp(unlockedTier, 0, levels.Count - 1);
|
||||
if (clampedTier >= levels.Count - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentCap = Mathf.Max(levels[clampedTier].requiredEXP, levels[clampedTier + 1].requiredEXP);
|
||||
return Mathf.Max(0, currentExp) >= currentCap;
|
||||
}
|
||||
|
||||
private static void ClearAllPendingDebtKeys()
|
||||
{
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -581,6 +656,63 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private static void SyncLegacySelectedSlotExpKeys(int heroId, int expValue)
|
||||
{
|
||||
if (heroId <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int safeExp = Mathf.Max(0, expValue);
|
||||
bool changed = false;
|
||||
for (int slot = 1; slot <= 5; slot++)
|
||||
{
|
||||
string heroKey = $"selected_heroSlot0{slot}_heroID";
|
||||
if (PlayerPrefs.GetInt(heroKey, 0) != heroId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string expKey = $"selected_heroSlot0{slot}_exp";
|
||||
if (PlayerPrefs.GetInt(expKey, int.MinValue) == safeExp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerPrefs.SetInt(expKey, safeExp);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncAllLegacySelectedSlotExpKeys()
|
||||
{
|
||||
bool changed = false;
|
||||
for (int slot = 1; slot <= 5; slot++)
|
||||
{
|
||||
string heroKey = $"selected_heroSlot0{slot}_heroID";
|
||||
int heroId = PlayerPrefs.GetInt(heroKey, 0);
|
||||
string expKey = $"selected_heroSlot0{slot}_exp";
|
||||
int expValue = heroId > 0 ? GetCurrentExp(heroId) : 0;
|
||||
if (PlayerPrefs.GetInt(expKey, int.MinValue) == expValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerPrefs.SetInt(expKey, expValue);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private AllyHeroDeployLedgerPayload BuildPayload()
|
||||
{
|
||||
AllyHeroDeployLedgerPayload payload = AllyHeroDeployLedgerStorage.CreateDefaultPayload();
|
||||
@@ -595,6 +727,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
allHeroIds.Add(pair.Key);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, bool> pair in levelLockByHeroId)
|
||||
{
|
||||
allHeroIds.Add(pair.Key);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, bool> pair in autoBreakthroughEnabledByHeroId)
|
||||
{
|
||||
allHeroIds.Add(pair.Key);
|
||||
@@ -627,6 +764,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
heroId = heroId,
|
||||
currentExp = Mathf.Max(0, GetCurrentExp(heroId)),
|
||||
unlockedTierIndex = Mathf.Clamp(GetUnlockedTierIndex(heroId), 0, 3),
|
||||
levelLock = IsLevelLockEnabled(heroId),
|
||||
autoBreakthroughEnabled = IsAutoBreakthroughEnabled(heroId),
|
||||
deployCount = Mathf.Max(0, GetDeployCount(heroId)),
|
||||
finishCount = Mathf.Max(0, GetFinishCount(heroId)),
|
||||
|
||||
@@ -7,6 +7,7 @@ public class AllyHeroDeployEntry
|
||||
public int heroId;
|
||||
public int currentExp;
|
||||
public int unlockedTierIndex;
|
||||
public bool levelLock;
|
||||
public bool autoBreakthroughEnabled;
|
||||
public int deployCount;
|
||||
public int finishCount;
|
||||
|
||||
@@ -14,6 +14,21 @@ public static class DlcContentAccess
|
||||
return owningDlc == null || DlcOwnershipService.IsDlcOwned(owningDlc);
|
||||
}
|
||||
|
||||
public static bool IsHeroAccessible(AllyHero_SO hero)
|
||||
{
|
||||
if (hero == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(hero.sourceDlcId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return DlcOwnershipService.IsDlcOwned(hero.sourceDlcId);
|
||||
}
|
||||
|
||||
public static bool IsSkinAccessible(HeroSkinResolvedData skin)
|
||||
{
|
||||
if (skin == null)
|
||||
|
||||
@@ -24,6 +24,7 @@ public class DlcManifestEntry
|
||||
public string[] songLabels;
|
||||
public string[] songContentLabels;
|
||||
public string[] heroSkinLabels;
|
||||
public string[] heroLabels;
|
||||
|
||||
public string GetSafeDlcKey()
|
||||
{
|
||||
@@ -50,6 +51,11 @@ public class DlcManifestEntry
|
||||
return GetResolvedLabels(heroSkinLabels, "dlc:" + GetSafeDlcKey() + ":heroskins");
|
||||
}
|
||||
|
||||
public string[] GetHeroLabels()
|
||||
{
|
||||
return GetResolvedLabels(heroLabels, "dlc:" + GetSafeDlcKey() + ":heroes");
|
||||
}
|
||||
|
||||
public string[] GetDependencyKeys()
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
@@ -58,6 +64,7 @@ public class DlcManifestEntry
|
||||
AppendUnique(result, GetSongLabels());
|
||||
AppendUnique(result, GetSongContentLabels());
|
||||
AppendUnique(result, GetHeroSkinLabels());
|
||||
AppendUnique(result, GetHeroLabels());
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ public static class DlcManifestService
|
||||
|
||||
public static List<DlcManifestRuntimeEntry> LoadInstalledEntries()
|
||||
{
|
||||
DlcPackageArchiveService.PrepareInstalledPackages();
|
||||
|
||||
List<DlcManifestRuntimeEntry> result = new List<DlcManifestRuntimeEntry>();
|
||||
HashSet<string> visitedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
string[] roots = GetManifestSearchRoots();
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
public static class DlcPackageArchiveService
|
||||
{
|
||||
[Serializable]
|
||||
private sealed class InstallState
|
||||
{
|
||||
public string sourcePath;
|
||||
public long sourceFileLength;
|
||||
public long sourceWriteTicksUtc;
|
||||
}
|
||||
|
||||
private const string RuntimeFolderName = "DLC";
|
||||
private const string PackageFolderName = "packages";
|
||||
private const string InstalledFolderName = "installed_packages";
|
||||
private const string PackageExtension = ".bsnkdlc";
|
||||
private const string StateFileName = ".bsnkdlc.installstate.json";
|
||||
|
||||
private static readonly HashSet<string> PreparedPackagesThisSession =
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetRuntimeState()
|
||||
{
|
||||
PreparedPackagesThisSession.Clear();
|
||||
}
|
||||
|
||||
public static void PrepareInstalledPackages()
|
||||
{
|
||||
string[] searchRoots = GetPackageSearchRoots();
|
||||
HashSet<string> visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (int i = 0; i < searchRoots.Length; i++)
|
||||
{
|
||||
string root = searchRoots[i];
|
||||
if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] packageFiles;
|
||||
try
|
||||
{
|
||||
packageFiles = Directory.GetFiles(root, "*" + PackageExtension, SearchOption.AllDirectories);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to enumerate package files in '" + root + "': " + ex.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int packageIndex = 0; packageIndex < packageFiles.Length; packageIndex++)
|
||||
{
|
||||
string packagePath = packageFiles[packageIndex];
|
||||
if (string.IsNullOrWhiteSpace(packagePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string fullPackagePath;
|
||||
try
|
||||
{
|
||||
fullPackagePath = Path.GetFullPath(packagePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!visited.Add(fullPackagePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TryInstallPackage(fullPackagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] GetPackageSearchRoots()
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName, PackageFolderName));
|
||||
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName));
|
||||
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName, PackageFolderName));
|
||||
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName));
|
||||
|
||||
string playerRoot = GetPlayerRootDirectory();
|
||||
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName, PackageFolderName));
|
||||
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName));
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static void TryInstallPackage(string packagePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(packagePath) || !File.Exists(packagePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string installDirectory = GetInstallDirectory(packagePath);
|
||||
if (string.IsNullOrWhiteSpace(installDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool needsInstall = NeedsInstall(packagePath, installDirectory);
|
||||
if (!needsInstall && PreparedPackagesThisSession.Contains(packagePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!needsInstall)
|
||||
{
|
||||
PreparedPackagesThisSession.Add(packagePath);
|
||||
return;
|
||||
}
|
||||
|
||||
string installRoot = Path.GetDirectoryName(installDirectory) ?? string.Empty;
|
||||
string stagingDirectory = installDirectory + ".staging";
|
||||
if (!string.IsNullOrWhiteSpace(installRoot))
|
||||
{
|
||||
Directory.CreateDirectory(installRoot);
|
||||
}
|
||||
|
||||
if (Directory.Exists(stagingDirectory))
|
||||
{
|
||||
Directory.Delete(stagingDirectory, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(stagingDirectory);
|
||||
ZipFile.ExtractToDirectory(packagePath, stagingDirectory);
|
||||
WriteInstallState(packagePath, stagingDirectory);
|
||||
|
||||
if (Directory.Exists(installDirectory))
|
||||
{
|
||||
Directory.Delete(installDirectory, true);
|
||||
}
|
||||
|
||||
Directory.Move(stagingDirectory, installDirectory);
|
||||
PreparedPackagesThisSession.Add(packagePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to install package '" + packagePath + "': " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool NeedsInstall(string packagePath, string installDirectory)
|
||||
{
|
||||
if (!Directory.Exists(installDirectory))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
InstallState state = ReadInstallState(installDirectory);
|
||||
if (state == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
FileInfo info;
|
||||
try
|
||||
{
|
||||
info = new FileInfo(packagePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !string.Equals(state.sourcePath ?? string.Empty, packagePath, StringComparison.OrdinalIgnoreCase)
|
||||
|| state.sourceFileLength != info.Length
|
||||
|| state.sourceWriteTicksUtc != info.LastWriteTimeUtc.Ticks;
|
||||
}
|
||||
|
||||
private static string GetInstallDirectory(string packagePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(packagePath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(packagePath) ?? "package";
|
||||
string safeName = SanitizeFileName(fileNameWithoutExtension);
|
||||
string hash = ComputeShortHash(packagePath);
|
||||
string installRoot = Path.Combine(Application.persistentDataPath, RuntimeFolderName, InstalledFolderName);
|
||||
return Path.Combine(installRoot, safeName + "_" + hash);
|
||||
}
|
||||
|
||||
private static void WriteInstallState(string packagePath, string directory)
|
||||
{
|
||||
FileInfo info = new FileInfo(packagePath);
|
||||
InstallState state = new InstallState
|
||||
{
|
||||
sourcePath = packagePath,
|
||||
sourceFileLength = info.Exists ? info.Length : 0L,
|
||||
sourceWriteTicksUtc = info.Exists ? info.LastWriteTimeUtc.Ticks : 0L
|
||||
};
|
||||
|
||||
string json = JsonUtility.ToJson(state, true);
|
||||
File.WriteAllText(Path.Combine(directory, StateFileName), json, Encoding.UTF8);
|
||||
}
|
||||
|
||||
private static InstallState ReadInstallState(string directory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string statePath = Path.Combine(directory, StateFileName);
|
||||
if (!File.Exists(statePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(statePath, Encoding.UTF8);
|
||||
return JsonUtility.FromJson<InstallState>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlayerRootDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
string dataPath = Application.dataPath;
|
||||
if (string.IsNullOrWhiteSpace(dataPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
DirectoryInfo parent = Directory.GetParent(dataPath);
|
||||
return parent != null ? parent.FullName : string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendIfValid(List<string> result, string path)
|
||||
{
|
||||
if (result == null || string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
if (!result.Contains(fullPath))
|
||||
{
|
||||
result.Add(fullPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static string ComputeShortHash(string value)
|
||||
{
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);
|
||||
using (SHA1 sha1 = SHA1.Create())
|
||||
{
|
||||
byte[] hash = sha1.ComputeHash(bytes);
|
||||
StringBuilder builder = new StringBuilder(8);
|
||||
for (int i = 0; i < 4 && i < hash.Length; i++)
|
||||
{
|
||||
builder.Append(hash[i].ToString("x2"));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return "package";
|
||||
}
|
||||
|
||||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||
StringBuilder builder = new StringBuilder(value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
char current = value[i];
|
||||
builder.Append(Array.IndexOf(invalidChars, current) >= 0 ? '_' : current);
|
||||
}
|
||||
|
||||
return builder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bf72431ab28c4640b1b960e8ca98168
|
||||
@@ -19,6 +19,11 @@ public static class DlcRemoteContentService
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
if (DlcRemoteManifestSyncService.ShouldDeferInitialRefresh())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (DlcRemoteContentService.autoRefreshOnStart)
|
||||
{
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
@@ -144,42 +149,56 @@ public static class DlcRemoteContentService
|
||||
|
||||
state.retainedCatalogHandles.Add(catalogHandle);
|
||||
|
||||
if (manifest.entry.autoDownloadDependencies)
|
||||
{
|
||||
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
|
||||
for (int i = 0; i < dependencyKeys.Length; i++)
|
||||
{
|
||||
string dependencyKey = dependencyKeys[i];
|
||||
if (string.IsNullOrWhiteSpace(dependencyKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
|
||||
yield return downloadHandle;
|
||||
|
||||
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
state.retainedAssetHandles.Add(downloadHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
localSuccess = false;
|
||||
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
|
||||
Addressables.Release(downloadHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<dlcData> loadedDlcs = new List<dlcData>();
|
||||
List<SongData> loadedSongs = new List<SongData>();
|
||||
List<SongDlcContentSO> loadedSongContents = new List<SongDlcContentSO>();
|
||||
List<HeroSkinSO> loadedHeroSkins = new List<HeroSkinSO>();
|
||||
List<AllyHero_SO> loadedHeroes = new List<AllyHero_SO>();
|
||||
|
||||
// Load the lightweight DLC metadata first so we can decide ownership
|
||||
// before pulling (or even downloading) any heavy content.
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetDlcDataLabels(), loadedDlcs, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
|
||||
bool owned = IsManifestEntryOwned(manifest, loadedDlcs);
|
||||
|
||||
if (owned)
|
||||
{
|
||||
if (manifest.entry.autoDownloadDependencies)
|
||||
{
|
||||
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
|
||||
for (int i = 0; i < dependencyKeys.Length; i++)
|
||||
{
|
||||
string dependencyKey = dependencyKeys[i];
|
||||
if (string.IsNullOrWhiteSpace(dependencyKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
|
||||
yield return downloadHandle;
|
||||
|
||||
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
state.retainedAssetHandles.Add(downloadHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
localSuccess = false;
|
||||
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
|
||||
Addressables.Release(downloadHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetHeroLabels(), loadedHeroes, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[DLC] Skipping content load for unowned DLC '" + manifest.entry.GetSafeDlcKey() + "'. Only metadata is registered.");
|
||||
}
|
||||
|
||||
DlcRuntimePackage package = new DlcRuntimePackage
|
||||
{
|
||||
@@ -190,13 +209,54 @@ public static class DlcRemoteContentService
|
||||
dlcs = loadedDlcs.ToArray(),
|
||||
songs = loadedSongs.ToArray(),
|
||||
songContents = loadedSongContents.ToArray(),
|
||||
heroSkins = loadedHeroSkins.ToArray()
|
||||
heroSkins = loadedHeroSkins.ToArray(),
|
||||
heroes = loadedHeroes.ToArray()
|
||||
};
|
||||
|
||||
state.packages.Add(package);
|
||||
reportResult?.Invoke(localSuccess);
|
||||
}
|
||||
|
||||
private static bool IsManifestEntryOwned(DlcManifestRuntimeEntry manifest, List<dlcData> loadedDlcs)
|
||||
{
|
||||
// If any loaded dlcData asset reports owned, the package is owned. A DLC that
|
||||
// does not enforce entitlement (builtInContent / requiresOwnership == false)
|
||||
// is always owned via DlcOwnershipService.
|
||||
if (loadedDlcs != null)
|
||||
{
|
||||
bool anyEnforced = false;
|
||||
for (int i = 0; i < loadedDlcs.Count; i++)
|
||||
{
|
||||
dlcData dlc = loadedDlcs[i];
|
||||
if (dlc == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
anyEnforced = true;
|
||||
if (DlcOwnershipService.IsDlcOwned(dlc))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyEnforced)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// No dlcData asset was found for this manifest entry. Fall back to the manifest
|
||||
// key so a remote/local ownership flag can still gate the content.
|
||||
string key = manifest != null && manifest.entry != null ? manifest.entry.GetSafeDlcKey() : null;
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return DlcOwnershipService.IsDlcOwned(key);
|
||||
}
|
||||
|
||||
private static IEnumerator LoadAssetsByLabels<T>(
|
||||
string[] labels,
|
||||
List<T> output,
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using GameServer.Client;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
public static class DlcRemoteManifestSyncService
|
||||
{
|
||||
[Serializable]
|
||||
private sealed class RemoteManifestEnvelope
|
||||
{
|
||||
public bool success;
|
||||
public string message;
|
||||
public RemoteManifestItem[] dlcs;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private sealed class RemoteManifestItem
|
||||
{
|
||||
public string dlc_key;
|
||||
public string display_name;
|
||||
public string version;
|
||||
public string manifest_file_name;
|
||||
public string manifest_json;
|
||||
public string uploaded_at;
|
||||
public string updated_at;
|
||||
}
|
||||
|
||||
private sealed class DlcRemoteManifestSyncRunner : MonoBehaviour
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
if (autoSyncOnStart)
|
||||
{
|
||||
SyncPublishedDlcs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const string DefaultServerUrl = "http://47.112.187.172:8080";
|
||||
private const string RemoteManifestFilePrefix = "remote_";
|
||||
private const string ManifestApiPath = "/api/dlcs/manifests";
|
||||
|
||||
private static DlcRemoteManifestSyncRunner runner;
|
||||
private static bool autoSyncOnStart = true;
|
||||
private static bool syncRequestedWhileBusy;
|
||||
private static bool initialSyncPending = true;
|
||||
|
||||
public static bool IsSyncing { get; private set; }
|
||||
public static event Action<bool> SyncCompleted;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void EnsureRunner()
|
||||
{
|
||||
if (runner != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject go = new GameObject(nameof(DlcRemoteManifestSyncService));
|
||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
||||
runner = go.AddComponent<DlcRemoteManifestSyncRunner>();
|
||||
}
|
||||
|
||||
public static void SetAutoSyncOnStart(bool enabled)
|
||||
{
|
||||
autoSyncOnStart = enabled;
|
||||
if (!enabled)
|
||||
{
|
||||
initialSyncPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShouldDeferInitialRefresh()
|
||||
{
|
||||
return autoSyncOnStart && initialSyncPending && !OnlineModeSettings.IsLocalOnlyMode;
|
||||
}
|
||||
|
||||
public static void SyncPublishedDlcs()
|
||||
{
|
||||
EnsureRunner();
|
||||
if (runner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSyncing)
|
||||
{
|
||||
syncRequestedWhileBusy = true;
|
||||
return;
|
||||
}
|
||||
|
||||
runner.StartCoroutine(SyncPublishedDlcsRoutine());
|
||||
}
|
||||
|
||||
private static IEnumerator SyncPublishedDlcsRoutine()
|
||||
{
|
||||
if (IsSyncing)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (OnlineModeSettings.IsLocalOnlyMode)
|
||||
{
|
||||
initialSyncPending = false;
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
SyncCompleted?.Invoke(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string url = BuildManifestApiUrl();
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
initialSyncPending = false;
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
SyncCompleted?.Invoke(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
IsSyncing = true;
|
||||
syncRequestedWhileBusy = false;
|
||||
bool success = false;
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 10;
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
string responseText = request.downloadHandler != null ? request.downloadHandler.text : string.Empty;
|
||||
success = TryApplyRemoteManifestPayload(responseText);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[DLC] Remote manifest sync failed: " + request.error);
|
||||
}
|
||||
}
|
||||
|
||||
initialSyncPending = false;
|
||||
|
||||
if (success)
|
||||
{
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
}
|
||||
else
|
||||
{
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
}
|
||||
|
||||
IsSyncing = false;
|
||||
SyncCompleted?.Invoke(success);
|
||||
|
||||
if (syncRequestedWhileBusy)
|
||||
{
|
||||
SyncPublishedDlcs();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryApplyRemoteManifestPayload(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoteManifestEnvelope envelope;
|
||||
try
|
||||
{
|
||||
envelope = JsonUtility.FromJson<RemoteManifestEnvelope>(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to parse remote manifest payload: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (envelope == null || !envelope.success)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Remote manifest payload reported failure.");
|
||||
return false;
|
||||
}
|
||||
|
||||
string manifestRoot = Path.Combine(Application.persistentDataPath, "DLC", "manifests");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(manifestRoot);
|
||||
DeleteExistingRemoteManifestFiles(manifestRoot);
|
||||
|
||||
RemoteManifestItem[] items = envelope.dlcs ?? Array.Empty<RemoteManifestItem>();
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
RemoteManifestItem item = items[i];
|
||||
if (item == null || string.IsNullOrWhiteSpace(item.manifest_json))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string safeKey = SanitizeFileName(item.dlc_key);
|
||||
if (string.IsNullOrWhiteSpace(safeKey))
|
||||
{
|
||||
safeKey = "package_" + i;
|
||||
}
|
||||
|
||||
string fileName = RemoteManifestFilePrefix + safeKey + ".json";
|
||||
string path = Path.Combine(manifestRoot, fileName);
|
||||
File.WriteAllText(path, item.manifest_json);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to write remote manifests: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteExistingRemoteManifestFiles(string manifestRoot)
|
||||
{
|
||||
string[] files = Directory.GetFiles(manifestRoot, RemoteManifestFilePrefix + "*.json", SearchOption.TopDirectoryOnly);
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(files[i]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to delete old remote manifest '" + files[i] + "': " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildManifestApiUrl()
|
||||
{
|
||||
string baseUrl = DefaultServerUrl;
|
||||
NetworkManager manager = NetworkManager.Instance;
|
||||
if (manager != null && !string.IsNullOrWhiteSpace(manager.ServerUrl))
|
||||
{
|
||||
baseUrl = manager.ServerUrl.Trim();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return baseUrl.TrimEnd('/') + ManifestApiPath;
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||
string result = value.Trim();
|
||||
for (int i = 0; i < invalidChars.Length; i++)
|
||||
{
|
||||
result = result.Replace(invalidChars[i], '_');
|
||||
}
|
||||
|
||||
return result.Replace(' ', '_');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08474a42ea631b04b982f1e7e67fd96a
|
||||
@@ -12,6 +12,7 @@ public sealed class DlcRuntimePackage
|
||||
public SongData[] songs = Array.Empty<SongData>();
|
||||
public SongDlcContentSO[] songContents = Array.Empty<SongDlcContentSO>();
|
||||
public HeroSkinSO[] heroSkins = Array.Empty<HeroSkinSO>();
|
||||
public AllyHero_SO[] heroes = Array.Empty<AllyHero_SO>();
|
||||
}
|
||||
|
||||
public static class DlcRuntimeRegistry
|
||||
@@ -25,11 +26,14 @@ public static class DlcRuntimeRegistry
|
||||
new Dictionary<string, SongDlcContentSO>(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly Dictionary<string, HeroSkinSO> HeroSkinsById =
|
||||
new Dictionary<string, HeroSkinSO>(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly Dictionary<int, AllyHero_SO> HeroesById =
|
||||
new Dictionary<int, AllyHero_SO>();
|
||||
|
||||
private static dlcData[] cachedDlcs = Array.Empty<dlcData>();
|
||||
private static SongData[] cachedSongs = Array.Empty<SongData>();
|
||||
private static SongDlcContentSO[] cachedSongContents = Array.Empty<SongDlcContentSO>();
|
||||
private static HeroSkinSO[] cachedHeroSkins = Array.Empty<HeroSkinSO>();
|
||||
private static AllyHero_SO[] cachedHeroes = Array.Empty<AllyHero_SO>();
|
||||
|
||||
public static event Action RuntimeContentChanged;
|
||||
|
||||
@@ -69,6 +73,11 @@ public static class DlcRuntimeRegistry
|
||||
return cachedHeroSkins;
|
||||
}
|
||||
|
||||
public static AllyHero_SO[] GetAllHeroes()
|
||||
{
|
||||
return cachedHeroes;
|
||||
}
|
||||
|
||||
public static void ReplaceAll(IList<DlcRuntimePackage> packages)
|
||||
{
|
||||
ClearSilently();
|
||||
@@ -97,10 +106,12 @@ public static class DlcRuntimeRegistry
|
||||
SongsById.Clear();
|
||||
SongContentsById.Clear();
|
||||
HeroSkinsById.Clear();
|
||||
HeroesById.Clear();
|
||||
cachedDlcs = Array.Empty<dlcData>();
|
||||
cachedSongs = Array.Empty<SongData>();
|
||||
cachedSongContents = Array.Empty<SongDlcContentSO>();
|
||||
cachedHeroSkins = Array.Empty<HeroSkinSO>();
|
||||
cachedHeroes = Array.Empty<AllyHero_SO>();
|
||||
}
|
||||
|
||||
private static void RegisterPackageInternal(DlcRuntimePackage package)
|
||||
@@ -185,6 +196,20 @@ public static class DlcRuntimeRegistry
|
||||
HeroSkinsById[skinId] = skin;
|
||||
}
|
||||
}
|
||||
|
||||
if (package.heroes != null)
|
||||
{
|
||||
for (int i = 0; i < package.heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = package.heroes[i];
|
||||
if (hero == null || hero.ally_heroID <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HeroesById[hero.ally_heroID] = hero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RebuildSnapshots()
|
||||
@@ -200,5 +225,8 @@ public static class DlcRuntimeRegistry
|
||||
|
||||
cachedHeroSkins = new HeroSkinSO[HeroSkinsById.Count];
|
||||
HeroSkinsById.Values.CopyTo(cachedHeroSkins, 0);
|
||||
|
||||
cachedHeroes = new AllyHero_SO[HeroesById.Count];
|
||||
HeroesById.Values.CopyTo(cachedHeroes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public static class RuntimeResourcesCache
|
||||
|
||||
public static AllyHero_SO[] LoadAllAllyHeroes()
|
||||
{
|
||||
return LoadAll<AllyHero_SO>(string.Empty);
|
||||
return MergeArrays(LoadAll<AllyHero_SO>(string.Empty), DlcRuntimeRegistry.GetAllHeroes());
|
||||
}
|
||||
|
||||
public static storeItemSO[] LoadAllStoreItems()
|
||||
|
||||
@@ -16,6 +16,7 @@ public class DailyTaskSaveData
|
||||
public string dateKey;
|
||||
public int dateStamp;
|
||||
public int refreshUsedCount;
|
||||
public bool loginReportedToday;
|
||||
public int trustedDateStamp;
|
||||
public int lastRefreshDateStamp;
|
||||
public long lastRefreshLocalTicks;
|
||||
|
||||
@@ -17,6 +17,8 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
}
|
||||
|
||||
private const float OnlineDurationFlushStepSeconds = 5f;
|
||||
private const string GameplaySceneName = "gameplay_gameplay";
|
||||
private const string MainUiSceneName = "UI_UI";
|
||||
|
||||
private readonly Queue<DailyTaskEventData> pendingEvents = new Queue<DailyTaskEventData>();
|
||||
private readonly Dictionary<string, userTasksPool.TaskDefinition> definitionById = new Dictionary<string, userTasksPool.TaskDefinition>(StringComparer.Ordinal);
|
||||
@@ -28,6 +30,8 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
private bool initialized;
|
||||
private bool appFocused = true;
|
||||
private float pendingOnlineDurationSeconds;
|
||||
private bool isGameplayDurationTracking;
|
||||
private float gameplayDurationRealtimeStart;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Bootstrap()
|
||||
@@ -90,6 +94,7 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
if (!hasFocus)
|
||||
{
|
||||
FlushPendingOnlineDuration();
|
||||
FlushGameplayDuration();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,12 +103,14 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
if (pauseStatus)
|
||||
{
|
||||
FlushPendingOnlineDuration();
|
||||
FlushGameplayDuration();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
FlushPendingOnlineDuration();
|
||||
FlushGameplayDuration();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@@ -111,6 +118,7 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
if (Instance == this)
|
||||
{
|
||||
FlushPendingOnlineDuration();
|
||||
FlushGameplayDuration();
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
}
|
||||
@@ -327,9 +335,18 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
if (string.Equals(scene.name, "UI_UI", StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(scene.name, MainUiSceneName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DailyTaskEventHub.ReportLogin();
|
||||
TryReportDailyLogin();
|
||||
}
|
||||
|
||||
if (string.Equals(scene.name, GameplaySceneName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
BeginGameplayDurationTracking();
|
||||
}
|
||||
else
|
||||
{
|
||||
FlushGameplayDuration();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +399,7 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
saveData.dateKey = todayKey;
|
||||
saveData.dateStamp = effectiveTodayStamp;
|
||||
saveData.refreshUsedCount = 0;
|
||||
saveData.loginReportedToday = false;
|
||||
saveData.accumulatedProgress = new List<DailyTaskAccumulatedProgress>();
|
||||
saveData.uniqueIntProgress = new List<DailyTaskUniqueIntProgress>();
|
||||
GenerateDailyTasks();
|
||||
@@ -524,6 +542,57 @@ public sealed class DailyTaskService : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void TryReportDailyLogin()
|
||||
{
|
||||
EnsureTodayTasks();
|
||||
if (saveData == null || saveData.loginReportedToday)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
saveData.loginReportedToday = true;
|
||||
ReportEvent(new DailyTaskEventData
|
||||
{
|
||||
taskType = userTasksPool.TaskType.Login,
|
||||
amount = 1f
|
||||
});
|
||||
}
|
||||
|
||||
private void BeginGameplayDurationTracking()
|
||||
{
|
||||
if (isGameplayDurationTracking)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
isGameplayDurationTracking = true;
|
||||
gameplayDurationRealtimeStart = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
private void FlushGameplayDuration()
|
||||
{
|
||||
if (!isGameplayDurationTracking)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float now = Time.realtimeSinceStartup;
|
||||
float elapsed = Mathf.Max(0f, now - gameplayDurationRealtimeStart);
|
||||
isGameplayDurationTracking = false;
|
||||
gameplayDurationRealtimeStart = 0f;
|
||||
|
||||
if (elapsed <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReportEvent(new DailyTaskEventData
|
||||
{
|
||||
taskType = userTasksPool.TaskType.GameDuration,
|
||||
amount = elapsed
|
||||
});
|
||||
}
|
||||
|
||||
private bool ApplyEventToTrackedProgress(DailyTaskEventData eventData)
|
||||
{
|
||||
switch (eventData.taskType)
|
||||
|
||||
@@ -284,7 +284,7 @@ public class dailyTaskManager : MonoBehaviour
|
||||
{
|
||||
if (taskService.GetRemainingRefreshCount() <= 0)
|
||||
{
|
||||
gNotice.warning.display(LocalizationService.Get("daily.refresh_limit_reached", "已达到今日刷新上限"));
|
||||
gNotice.warning.display("今日刷新次数已用尽");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -295,6 +295,7 @@ public class dailyTaskManager : MonoBehaviour
|
||||
return;
|
||||
}
|
||||
|
||||
gNotice.message.display($"刷新成功,剩余刷新次数:{taskService.GetRemainingRefreshCount()}");
|
||||
RefreshTaskUi();
|
||||
}
|
||||
|
||||
|
||||
@@ -262,19 +262,28 @@ public class eula_and_warnings : MonoBehaviour
|
||||
{
|
||||
if (_mainScenePreloadReady)
|
||||
{
|
||||
StartCoroutine(ActivatePreloadedMainSceneRoutine());
|
||||
if (!gTransition.Run(ActivatePreloadedMainSceneRoutineWithoutFade()))
|
||||
{
|
||||
StartCoroutine(ActivatePreloadedMainSceneRoutineWithoutFade());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
|
||||
if (!gTransition.LoadScene(MainSceneName, LoadSceneMode.Single))
|
||||
{
|
||||
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
|
||||
}
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator ActivatePreloadedMainSceneRoutine()
|
||||
private System.Collections.IEnumerator ActivatePreloadedMainSceneRoutineWithoutFade()
|
||||
{
|
||||
Scene sourceScene = gameObject.scene;
|
||||
if (_mainScenePreloadOperation == null)
|
||||
{
|
||||
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
|
||||
if (!gTransition.LoadScene(MainSceneName, LoadSceneMode.Single))
|
||||
{
|
||||
SceneManager.LoadScene(MainSceneName, LoadSceneMode.Single);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class gTransBlack : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Canvas b_Canvas;
|
||||
[SerializeField] private CanvasGroup b_CG;
|
||||
|
||||
public Canvas Canvas => b_Canvas;
|
||||
public CanvasGroup CanvasGroup => b_CG;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureReferences();
|
||||
ApplySceneCamera();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
SceneManager.sceneLoaded -= HandleSceneLoaded;
|
||||
SceneManager.sceneLoaded += HandleSceneLoaded;
|
||||
ApplySceneCamera();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
SceneManager.sceneLoaded -= HandleSceneLoaded;
|
||||
}
|
||||
|
||||
private void HandleSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
ApplySceneCamera();
|
||||
}
|
||||
|
||||
public void RefreshCanvasCamera()
|
||||
{
|
||||
ApplySceneCamera();
|
||||
}
|
||||
|
||||
private void EnsureReferences()
|
||||
{
|
||||
if (b_Canvas == null)
|
||||
{
|
||||
b_Canvas = GetComponentInChildren<Canvas>(true);
|
||||
}
|
||||
|
||||
if (b_CG == null)
|
||||
{
|
||||
b_CG = GetComponent<CanvasGroup>();
|
||||
if (b_CG == null)
|
||||
{
|
||||
b_CG = GetComponentInChildren<CanvasGroup>(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySceneCamera()
|
||||
{
|
||||
EnsureReferences();
|
||||
|
||||
if (b_Canvas == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (b_Canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
||||
{
|
||||
b_Canvas.worldCamera = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (b_Canvas.transform.localScale == Vector3.zero)
|
||||
{
|
||||
b_Canvas.transform.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
Camera targetCamera = ResolveFirstSceneCamera();
|
||||
if (targetCamera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
b_Canvas.worldCamera = targetCamera;
|
||||
}
|
||||
|
||||
private static Camera ResolveFirstSceneCamera()
|
||||
{
|
||||
Camera mainCam = Camera.main;
|
||||
if (mainCam != null && mainCam.gameObject.scene.IsValid() && mainCam.gameObject.scene.isLoaded)
|
||||
{
|
||||
return mainCam;
|
||||
}
|
||||
|
||||
Camera[] cameras = Object.FindObjectsByType<Camera>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < cameras.Length; i++)
|
||||
{
|
||||
Camera camera = cameras[i];
|
||||
if (camera == null || !camera.isActiveAndEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!camera.gameObject.scene.IsValid() || !camera.gameObject.scene.isLoaded)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return camera;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e8dcec2fcbc0a24e966c83902aad73a
|
||||
@@ -1578,8 +1578,17 @@ public class GameManager : MonoBehaviour
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
@@ -1588,10 +1597,19 @@ public class GameManager : MonoBehaviour
|
||||
private IEnumerator FadeToBlackAndLoad(string sceneName, float duration)
|
||||
{
|
||||
RecordTotalPlayTime();
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single, duration, duration))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (blackMaskImage == null)
|
||||
{
|
||||
AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncOp.isDone) yield return null;
|
||||
while (asyncOp != null && !asyncOp.isDone) yield return null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
@@ -1620,7 +1638,7 @@ public class GameManager : MonoBehaviour
|
||||
|
||||
// load scene
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@@ -3787,7 +3787,10 @@ namespace GameServer.Client
|
||||
}
|
||||
|
||||
Time.timeScale = 1f;
|
||||
SceneManager.LoadScene(GameplaySceneName, LoadSceneMode.Single);
|
||||
if (!gTransition.LoadScene(GameplaySceneName, LoadSceneMode.Single))
|
||||
{
|
||||
SceneManager.LoadScene(GameplaySceneName, LoadSceneMode.Single);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureRoomAutoPlayDisabled()
|
||||
|
||||
@@ -363,6 +363,7 @@ namespace GameServer.Client
|
||||
[JsonProperty("reward_description")] public string reward_description;
|
||||
[JsonProperty("reward_key")] public string reward_key;
|
||||
[JsonProperty("reward_store_item_id")] public int reward_store_item_id;
|
||||
[JsonProperty("reward_icon_url")] public string reward_icon_url;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -456,4 +457,46 @@ namespace GameServer.Client
|
||||
[JsonProperty("requests")] public List<SocialFriendRequestEntry> requests;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 邮件附件发放参考常量。
|
||||
/// 服务器配置 rewards 时按此填写 reward_type + reward_key(或 reward_store_item_id)。
|
||||
/// </summary>
|
||||
public static class MailRewardKeys
|
||||
{
|
||||
// ── reward_type 字符串 ──────────────────────────────────────────────────
|
||||
public const string TypeCoins = "money";
|
||||
public const string TypePlayerExp = "exp_user";
|
||||
public const string TypeMaterial = "metarial";
|
||||
public const string TypeExpBottle = "exp_bottle";
|
||||
public const string TypeGrowthMaterial = "growth_material";
|
||||
public const string TypeEquipConsumable = "equipment_consumable";
|
||||
public const string TypeStoreItem = "store_item";
|
||||
|
||||
// ── 经验瓶 reward_key ─────────────────────────────────────────────────
|
||||
public const string ExpBottleCommon = "exp_common"; // 凡品经验瓶 (itemID 78001)
|
||||
public const string ExpBottleMedium = "exp_medium"; // 中品经验瓶 (78002)
|
||||
public const string ExpBottleSuperior = "exp_superior"; // 上品经验瓶 (78003)
|
||||
public const string ExpBottleSupreme = "exp_supreme"; // 极品经验瓶 (78004)
|
||||
public const string ExpBottleExtraordinary = "exp_extraordinary"; // 绝品经验瓶 (78005)
|
||||
public const string ExpBottleCelestial = "exp_celestial"; // 仙品经验瓶 (78006)
|
||||
public const string ExpBottleRainAll = "exp_rain_all"; // 雨露均沾 (78011)
|
||||
public const string ExpBottleRainAllAdv = "exp_rain_all_advanced"; // 高级雨露均沾 (78012)
|
||||
public const string ExpBottleRainAllSuper = "exp_rain_all_super"; // 超级雨露均沾 (78013)
|
||||
|
||||
// ── 突破材料 reward_key ────────────────────────────────────────────────
|
||||
public const string GrowthMaterial78021 = "dush_78021";
|
||||
public const string GrowthMaterial78022 = "dush_78022";
|
||||
public const string GrowthMaterial78023 = "dush_78023";
|
||||
public const string GrowthMaterial78024 = "dush_78024";
|
||||
|
||||
// ── 装备消耗品 reward_key ─────────────────────────────────────────────
|
||||
public const string EquipUpgrade = "eqc_78101"; // 装备升级材料
|
||||
public const string EquipBreakthrough = "eqc_78111"; // 装备突破材料
|
||||
public const string EquipTransfer = "eqc_78121"; // 装备洗炼材料
|
||||
public const string EquipFinal = "eqc_78131"; // 装备登顶材料
|
||||
|
||||
// ── 商店物品:直接用 reward_store_item_id 填 itemID,或 reward_key 填 itemID 字符串 ──
|
||||
// itemID 77001 = 角色 30206;77003 = 歌曲1;77004 = 歌曲2(见 Resources/so/storeSO/)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Collections;
|
||||
using UnityEngine.Audio;
|
||||
using DG.Tweening;
|
||||
using GameServer.Client;
|
||||
using Bansonic;
|
||||
|
||||
public class settlementController : MonoBehaviour
|
||||
{
|
||||
@@ -1912,8 +1913,17 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
@@ -2006,10 +2016,19 @@ public class settlementController : MonoBehaviour
|
||||
// Coroutine that will be started on the GameManager instance so that the gm's MonoBehaviour runs it
|
||||
private IEnumerator FadeToBlackAndLoadOnGM(string sceneName, float duration)
|
||||
{
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single, duration, duration))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (gm == null || gm.blackMaskImage == null)
|
||||
{
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone) yield return null;
|
||||
while (asyncLoad != null && !asyncLoad.isDone) yield return null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
@@ -2040,7 +2059,7 @@ public class settlementController : MonoBehaviour
|
||||
|
||||
// load target scene
|
||||
AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncOp.isDone)
|
||||
while (asyncOp != null && !asyncOp.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
[InitializeOnLoad]
|
||||
public static class UIBlurBehindInstaller
|
||||
{
|
||||
private const string RendererDataPath = "Assets/Settings/Renderer2D.asset";
|
||||
private const string MaterialFolderPath = "Assets/Materials";
|
||||
private const string UIMaterialFolderPath = "Assets/Materials/UI";
|
||||
private const string MaterialPath = "Assets/Materials/UI/Bansonic_UIBlurBehind.mat";
|
||||
private const string ShaderName = "UI/Bansonic/Blur Behind";
|
||||
|
||||
private static bool installScheduled;
|
||||
|
||||
static UIBlurBehindInstaller()
|
||||
{
|
||||
ScheduleInstall();
|
||||
}
|
||||
|
||||
[MenuItem("Bansonic/Rendering/Install UI Blur Behind")]
|
||||
public static void EnsureInstalledMenu()
|
||||
{
|
||||
EnsureInstalled();
|
||||
}
|
||||
|
||||
private static void ScheduleInstall()
|
||||
{
|
||||
if (installScheduled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
installScheduled = true;
|
||||
EditorApplication.delayCall += RunScheduledInstall;
|
||||
}
|
||||
|
||||
private static void RunScheduledInstall()
|
||||
{
|
||||
installScheduled = false;
|
||||
EnsureInstalled();
|
||||
}
|
||||
|
||||
private static void EnsureInstalled()
|
||||
{
|
||||
if (EditorApplication.isCompiling || BuildPipeline.isBuildingPlayer)
|
||||
{
|
||||
ScheduleInstall();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureRendererFeatureInstalled())
|
||||
{
|
||||
ScheduleInstall();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureMaterialAsset())
|
||||
{
|
||||
ScheduleInstall();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool EnsureRendererFeatureInstalled()
|
||||
{
|
||||
ScriptableRendererData rendererData = AssetDatabase.LoadAssetAtPath<ScriptableRendererData>(RendererDataPath);
|
||||
if (rendererData == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rendererData.TryGetRendererFeature<UIBlurBehindRendererFeature>(out UIBlurBehindRendererFeature feature))
|
||||
{
|
||||
feature = ScriptableObject.CreateInstance<UIBlurBehindRendererFeature>();
|
||||
feature.name = "UI Blur Behind Renderer Feature";
|
||||
feature.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
|
||||
feature.captureDownsample = 2;
|
||||
|
||||
AssetDatabase.AddObjectToAsset(feature, rendererData);
|
||||
rendererData.rendererFeatures.Add(feature);
|
||||
rendererData.SetDirty();
|
||||
EditorUtility.SetDirty(feature);
|
||||
EditorUtility.SetDirty(rendererData);
|
||||
TryValidateRendererFeatures(rendererData);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.ImportAsset(RendererDataPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if (feature.renderPassEvent != RenderPassEvent.AfterRenderingPostProcessing)
|
||||
{
|
||||
feature.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (feature.captureDownsample != 2)
|
||||
{
|
||||
feature.captureDownsample = 2;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
rendererData.SetDirty();
|
||||
EditorUtility.SetDirty(feature);
|
||||
EditorUtility.SetDirty(rendererData);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool EnsureMaterialAsset()
|
||||
{
|
||||
Shader shader = Shader.Find(ShaderName);
|
||||
if (shader == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureFolder("Assets", "Materials");
|
||||
EnsureFolder(MaterialFolderPath, "UI");
|
||||
|
||||
Material material = AssetDatabase.LoadAssetAtPath<Material>(MaterialPath);
|
||||
if (material == null)
|
||||
{
|
||||
material = new Material(shader);
|
||||
material.name = "Bansonic_UIBlurBehind";
|
||||
material.SetFloat("_BlurRadius", 0.95f);
|
||||
material.SetFloat("_BlurSpread", 0.28f);
|
||||
material.SetFloat("_BackgroundOpacity", 1.0f);
|
||||
material.SetFloat("_TintStrength", 0.0f);
|
||||
AssetDatabase.CreateAsset(material, MaterialPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if (material.shader != shader)
|
||||
{
|
||||
material.shader = shader;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
EditorUtility.SetDirty(material);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void EnsureFolder(string parentPath, string folderName)
|
||||
{
|
||||
string combined = Path.Combine(parentPath, folderName).Replace("\\", "/");
|
||||
if (AssetDatabase.IsValidFolder(combined))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AssetDatabase.CreateFolder(parentPath, folderName);
|
||||
}
|
||||
|
||||
private static void TryValidateRendererFeatures(ScriptableRendererData rendererData)
|
||||
{
|
||||
MethodInfo validateMethod = typeof(ScriptableRendererData).GetMethod("ValidateRendererFeatures", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
validateMethod?.Invoke(rendererData, null);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e36a19a876cc674aa18f8f1f76d423d
|
||||
@@ -0,0 +1,162 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.RenderGraphModule;
|
||||
using UnityEngine.Rendering.RenderGraphModule.Util;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
public class UIBlurBehindRendererFeature : ScriptableRendererFeature
|
||||
{
|
||||
public const string GlobalTextureName = "_BansonicUIBlurSourceTex";
|
||||
private static readonly int GlobalTextureId = Shader.PropertyToID(GlobalTextureName);
|
||||
|
||||
[Tooltip("When the UI blur background texture should be captured.")]
|
||||
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
|
||||
|
||||
[Tooltip("Downsample factor for the captured screen texture. Higher values are cheaper and blurrier.")]
|
||||
[Range(1, 4)]
|
||||
public int captureDownsample = 2;
|
||||
|
||||
private UIBlurBehindPass pass;
|
||||
|
||||
public override void Create()
|
||||
{
|
||||
if (pass == null)
|
||||
{
|
||||
pass = new UIBlurBehindPass();
|
||||
}
|
||||
|
||||
pass.renderPassEvent = renderPassEvent;
|
||||
pass.SetDownsample(captureDownsample);
|
||||
}
|
||||
|
||||
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
||||
{
|
||||
if (pass == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Camera camera = renderingData.cameraData.camera;
|
||||
if (camera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CameraType cameraType = renderingData.cameraData.cameraType;
|
||||
if (cameraType == CameraType.Preview || cameraType == CameraType.Reflection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pass.SetDownsample(captureDownsample);
|
||||
renderer.EnqueuePass(pass);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (pass != null)
|
||||
{
|
||||
pass.Dispose();
|
||||
pass = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class UIBlurBehindPass : ScriptableRenderPass
|
||||
{
|
||||
private int downsample = 2;
|
||||
private RTHandle blurSourceTexture;
|
||||
|
||||
public void SetDownsample(int value)
|
||||
{
|
||||
downsample = Mathf.Clamp(value, 1, 4);
|
||||
requiresIntermediateTexture = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
blurSourceTexture?.Release();
|
||||
blurSourceTexture = null;
|
||||
}
|
||||
|
||||
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
||||
{
|
||||
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
|
||||
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
|
||||
if (resourceData.isActiveTargetBackBuffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RenderTextureDescriptor descriptor = cameraData.cameraTargetDescriptor;
|
||||
descriptor.msaaSamples = 1;
|
||||
descriptor.depthBufferBits = 0;
|
||||
descriptor.width = Mathf.Max(1, descriptor.width / downsample);
|
||||
descriptor.height = Mathf.Max(1, descriptor.height / downsample);
|
||||
descriptor.useMipMap = false;
|
||||
descriptor.autoGenerateMips = false;
|
||||
RenderingUtils.ReAllocateHandleIfNeeded(
|
||||
ref blurSourceTexture,
|
||||
descriptor,
|
||||
FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp,
|
||||
name: GlobalTextureName);
|
||||
|
||||
if (blurSourceTexture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Shader.SetGlobalTexture(GlobalTextureId, blurSourceTexture);
|
||||
|
||||
TextureHandle source = resourceData.activeColorTexture;
|
||||
TextureHandle destination = renderGraph.ImportTexture(blurSourceTexture);
|
||||
if (!source.IsValid() || !destination.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RenderGraphUtils.BlitMaterialParameters parameters = new(
|
||||
source,
|
||||
destination,
|
||||
Blitter.GetBlitMaterial(TextureDimension.Tex2D),
|
||||
0);
|
||||
renderGraph.AddBlitPass(parameters, "UI Blur Behind Capture Copy");
|
||||
}
|
||||
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
|
||||
descriptor.msaaSamples = 1;
|
||||
descriptor.depthBufferBits = 0;
|
||||
descriptor.width = Mathf.Max(1, descriptor.width / downsample);
|
||||
descriptor.height = Mathf.Max(1, descriptor.height / downsample);
|
||||
descriptor.useMipMap = false;
|
||||
descriptor.autoGenerateMips = false;
|
||||
|
||||
RenderingUtils.ReAllocateHandleIfNeeded(
|
||||
ref blurSourceTexture,
|
||||
descriptor,
|
||||
FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp,
|
||||
name: GlobalTextureName);
|
||||
|
||||
CommandBuffer cmd = CommandBufferPool.Get("UI Blur Behind Capture");
|
||||
try
|
||||
{
|
||||
if (blurSourceTexture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Blitter.BlitCameraTexture(cmd, renderingData.cameraData.renderer.cameraColorTargetHandle, blurSourceTexture);
|
||||
cmd.SetGlobalTexture(GlobalTextureId, blurSourceTexture.nameID);
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmd.Clear();
|
||||
CommandBufferPool.Release(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9ac8248c066e1440a110a8b1cf5b019
|
||||
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using DG.Tweening;
|
||||
using GameServer.Client;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
@@ -62,6 +63,7 @@ public class globalChatSystem : MonoBehaviour
|
||||
[SerializeField] Button back_to_worldwideChatButton;
|
||||
[SerializeField] Toggle globalChatToggle;
|
||||
[SerializeField] Toggle friendChatToggle;
|
||||
[SerializeField] private CanvasGroup chatCanvasGroup;
|
||||
|
||||
private ArenaRoomService _service;
|
||||
private ScrollRect _scrollRect;
|
||||
@@ -94,6 +96,7 @@ public class globalChatSystem : MonoBehaviour
|
||||
private bool _suppressModeToggleEvents;
|
||||
private bool _modeTogglesAutoCreated;
|
||||
private ToggleGroup _chatModeToggleGroup;
|
||||
private Tween _visibilityTween;
|
||||
public static globalChatSystem Instance { get; private set; }
|
||||
public static event Action<string> ActivePrivateConversationChanged;
|
||||
|
||||
@@ -148,6 +151,7 @@ public class globalChatSystem : MonoBehaviour
|
||||
{
|
||||
Instance = this;
|
||||
_lastPrivatePartnerId = PlayerPrefs.GetString(LastPrivatePartnerPrefKey, string.Empty);
|
||||
EnsureCanvasGroupReference();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
@@ -212,6 +216,9 @@ public class globalChatSystem : MonoBehaviour
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EnsureCanvasGroupReference();
|
||||
PlayOpenFadeIfNeeded();
|
||||
|
||||
if (_service == null)
|
||||
{
|
||||
_service = ArenaRoomService.Instance;
|
||||
@@ -283,6 +290,8 @@ public class globalChatSystem : MonoBehaviour
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
KillVisibilityTween();
|
||||
|
||||
if (closeButton != null)
|
||||
{
|
||||
closeButton.onClick.RemoveListener(CloseSelf);
|
||||
@@ -1200,7 +1209,80 @@ public class globalChatSystem : MonoBehaviour
|
||||
|
||||
private void CloseSelf()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
HideWithFade();
|
||||
}
|
||||
|
||||
private void EnsureCanvasGroupReference()
|
||||
{
|
||||
if (chatCanvasGroup == null)
|
||||
{
|
||||
chatCanvasGroup = GetComponent<CanvasGroup>();
|
||||
}
|
||||
}
|
||||
|
||||
private void KillVisibilityTween()
|
||||
{
|
||||
if (_visibilityTween != null && _visibilityTween.IsActive())
|
||||
{
|
||||
_visibilityTween.Kill(false);
|
||||
}
|
||||
|
||||
_visibilityTween = null;
|
||||
}
|
||||
|
||||
private void PlayOpenFadeIfNeeded()
|
||||
{
|
||||
if (chatCanvasGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
KillVisibilityTween();
|
||||
chatCanvasGroup.gameObject.SetActive(true);
|
||||
chatCanvasGroup.alpha = 0f;
|
||||
chatCanvasGroup.interactable = false;
|
||||
chatCanvasGroup.blocksRaycasts = false;
|
||||
_visibilityTween = chatCanvasGroup.DOFade(1f, 0.25f)
|
||||
.SetEase(Ease.Linear)
|
||||
.SetUpdate(true)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
if (chatCanvasGroup == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
chatCanvasGroup.alpha = 1f;
|
||||
chatCanvasGroup.interactable = true;
|
||||
chatCanvasGroup.blocksRaycasts = true;
|
||||
_visibilityTween = null;
|
||||
});
|
||||
}
|
||||
|
||||
private void HideWithFade()
|
||||
{
|
||||
if (chatCanvasGroup == null)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
KillVisibilityTween();
|
||||
chatCanvasGroup.interactable = false;
|
||||
chatCanvasGroup.blocksRaycasts = false;
|
||||
_visibilityTween = chatCanvasGroup.DOFade(0f, 0.25f)
|
||||
.SetEase(Ease.Linear)
|
||||
.SetUpdate(true)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
if (chatCanvasGroup != null)
|
||||
{
|
||||
chatCanvasGroup.alpha = 0f;
|
||||
}
|
||||
|
||||
gameObject.SetActive(false);
|
||||
_visibilityTween = null;
|
||||
});
|
||||
}
|
||||
|
||||
public static bool IsViewingPrivateConversation(string steamId)
|
||||
|
||||
@@ -13,102 +13,30 @@ public class UI_SelectSong_AutoPlayToggle : MonoBehaviour
|
||||
[Header("Tween")]
|
||||
[SerializeField] private float tweenDuration = 0.25f;
|
||||
[SerializeField] private Ease tweenEase = Ease.OutCubic;
|
||||
|
||||
private static readonly Color32 OnBg = new Color32(0x3A, 0x3A, 0x3A, 0xFF);
|
||||
private static readonly Color32 OffBg = new Color32(0xFF, 0xFF, 0xFF, 0xFF);
|
||||
private static readonly Color32 OnText = new Color32(0xFF, 0xFF, 0xFF, 0xFF);
|
||||
private static readonly Color32 OffText = new Color32(0x3A, 0x3A, 0x3A, 0xFF);
|
||||
private Color initialButtonColor = Color.white;
|
||||
private Color initialTextColor = Color.white;
|
||||
private bool hasInitialButtonColor;
|
||||
private bool hasInitialTextColor;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
TryAutoWire();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
TryAutoWire();
|
||||
|
||||
if (button != null)
|
||||
{
|
||||
button.onClick.RemoveListener(OnClick);
|
||||
button.onClick.AddListener(OnClick);
|
||||
}
|
||||
if (toggle != null)
|
||||
{
|
||||
toggle.onValueChanged.RemoveListener(OnToggleChanged);
|
||||
toggle.onValueChanged.AddListener(OnToggleChanged);
|
||||
// Keep UI state consistent with global setting.
|
||||
toggle.SetIsOnWithoutNotify(GameConfig.autoPlayEnabled);
|
||||
}
|
||||
|
||||
ApplyVisual(GameConfig.autoPlayEnabled, instant: true);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
button.onClick.RemoveListener(OnClick);
|
||||
}
|
||||
if (toggle != null)
|
||||
{
|
||||
toggle.onValueChanged.RemoveListener(OnToggleChanged);
|
||||
}
|
||||
KillTweens();
|
||||
}
|
||||
|
||||
public void TryAutoWire()
|
||||
{
|
||||
button = button != null ? button : GetComponent<Button>();
|
||||
toggle = toggle != null ? toggle : GetComponent<Toggle>();
|
||||
buttonImage = buttonImage != null ? buttonImage : GetComponent<Image>();
|
||||
if (buttonImage == null && toggle != null && toggle.targetGraphic is Image tgImage)
|
||||
{
|
||||
buttonImage = tgImage;
|
||||
}
|
||||
labelText = labelText != null ? labelText : GetComponentInChildren<Text>(true);
|
||||
}
|
||||
|
||||
private void OnClick()
|
||||
{
|
||||
bool next = !GameConfig.autoPlayEnabled;
|
||||
GameConfig.SetAutoPlayEnabled(next);
|
||||
if (toggle != null)
|
||||
{
|
||||
toggle.SetIsOnWithoutNotify(next);
|
||||
}
|
||||
ApplyVisual(next, instant: false);
|
||||
}
|
||||
|
||||
private void OnToggleChanged(bool isOn)
|
||||
{
|
||||
GameConfig.SetAutoPlayEnabled(isOn);
|
||||
ApplyVisual(isOn, instant: false);
|
||||
}
|
||||
|
||||
private void ApplyVisual(bool enabled, bool instant)
|
||||
{
|
||||
Color targetBg = enabled ? OnBg : OffBg;
|
||||
Color targetText = enabled ? OnText : OffText;
|
||||
|
||||
if (instant || !Application.isPlaying || tweenDuration <= 0f)
|
||||
{
|
||||
if (buttonImage != null) buttonImage.color = targetBg;
|
||||
if (labelText != null) labelText.color = targetText;
|
||||
return;
|
||||
}
|
||||
|
||||
KillTweens();
|
||||
|
||||
if (buttonImage != null)
|
||||
buttonImage.DOColor(targetBg, tweenDuration).SetEase(tweenEase);
|
||||
if (labelText != null)
|
||||
labelText.DOColor(targetText, tweenDuration).SetEase(tweenEase);
|
||||
// Legacy component retained only for scene compatibility.
|
||||
// Autoplay UI is now driven exclusively by selected_songInfo.autoplayButton.
|
||||
}
|
||||
|
||||
private void KillTweens()
|
||||
{
|
||||
if (buttonImage != null) buttonImage.DOKill();
|
||||
if (labelText != null) labelText.DOKill();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,59 +3,10 @@ using UnityEngine.SceneManagement;
|
||||
|
||||
public static class UI_SelectSong_AutoPlayToggleBinder
|
||||
{
|
||||
private const string TargetSceneName = "selectYourSongFirst";
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Init()
|
||||
{
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
|
||||
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
if (scene.name != TargetSceneName)
|
||||
return;
|
||||
|
||||
TryBind(scene);
|
||||
}
|
||||
|
||||
private static void TryBind(Scene scene)
|
||||
{
|
||||
// Ensure we have the latest persisted value (important when entering from editor play).
|
||||
GameConfig.LoadPrefs();
|
||||
|
||||
// Prefer the user's specified hierarchy if present (active only).
|
||||
GameObject go =
|
||||
SceneObjectLookupCache.Find("rightInfos/difficultyBtnInfos/difficultyBtnInfos/select_Auto") ??
|
||||
SceneObjectLookupCache.Find("rightInfos/difficultyBtnInfos/select_Auto");
|
||||
|
||||
if (go == null)
|
||||
{
|
||||
// Fallback: search by name in this scene (includes inactive objects).
|
||||
var transforms = Object.FindObjectsByType<Transform>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < transforms.Length; i++)
|
||||
{
|
||||
var t = transforms[i];
|
||||
if (t == null) continue;
|
||||
if (t.gameObject.scene != scene) continue;
|
||||
if (t.name != "select_Auto") continue;
|
||||
go = t.gameObject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (go == null)
|
||||
{
|
||||
if (GameConfig.verboseLogs)
|
||||
Debug.LogWarning("[AutoPlay] select_Auto button not found in selectYourSongFirst scene. Autoplay toggle UI will be unavailable.");
|
||||
return;
|
||||
}
|
||||
|
||||
var toggle = go.GetComponent<UI_SelectSong_AutoPlayToggle>();
|
||||
if (toggle == null)
|
||||
toggle = go.AddComponent<UI_SelectSong_AutoPlayToggle>();
|
||||
|
||||
toggle.TryAutoWire();
|
||||
// Legacy autoplay binding has been retired.
|
||||
// selectYourSongFirst now uses selected_songInfo.autoplayButton as the single source of truth.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class UI_SelectSong_EnterAnim : MonoBehaviour
|
||||
for (int i = 0; i < difficultyGroup.childCount; i++)
|
||||
{
|
||||
RectTransform child = difficultyGroup.GetChild(i) as RectTransform;
|
||||
if (child != null)
|
||||
if (child != null && child.gameObject.activeSelf)
|
||||
{
|
||||
difficultyItems.Add(child);
|
||||
}
|
||||
@@ -317,7 +317,7 @@ public class UI_SelectSong_EnterAnim : MonoBehaviour
|
||||
void SnapVisible(RectTransform rect, bool applyScale)
|
||||
{
|
||||
if (rect == null) return;
|
||||
if (!rect.gameObject.activeSelf) rect.gameObject.SetActive(true);
|
||||
if (!rect.gameObject.activeSelf) return;
|
||||
|
||||
CacheBase(rect);
|
||||
if (basePos.TryGetValue(rect, out Vector2 pos))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -23,11 +24,20 @@ public class load_teammatesProfile : MonoBehaviour
|
||||
public Image teammate_profile_boarder_04;
|
||||
public Image teammate_profile_boarder_05;
|
||||
|
||||
[Header("Level Colors")]
|
||||
public Color levelColor_C;
|
||||
public Color levelColor_B;
|
||||
public Color levelColor_A;
|
||||
public Color levelColor_S;
|
||||
[Header("Level Border Sprites")]
|
||||
public Sprite[] levelBorderSprites;
|
||||
|
||||
[Header("Hover Details")]
|
||||
public loadDetailsPrefab hoverDetailsLoader;
|
||||
public GameObject hoverDetailsPrefab;
|
||||
public GameObject hoverDetailsParent;
|
||||
|
||||
private bool hoverTargetsBound;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsureHoverTargetsBound();
|
||||
}
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
IEnumerator Start()
|
||||
@@ -36,6 +46,38 @@ public class load_teammatesProfile : MonoBehaviour
|
||||
update_teammates_profile();
|
||||
}
|
||||
|
||||
private void EnsureHoverTargetsBound()
|
||||
{
|
||||
if (hoverTargetsBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BindHoverTarget(teammate_profile_01, 1);
|
||||
BindHoverTarget(teammate_profile_02, 2);
|
||||
BindHoverTarget(teammate_profile_03, 3);
|
||||
BindHoverTarget(teammate_profile_04, 4);
|
||||
BindHoverTarget(teammate_profile_05, 5);
|
||||
|
||||
hoverTargetsBound = true;
|
||||
}
|
||||
|
||||
private void BindHoverTarget(Image targetImage, int slotIndex)
|
||||
{
|
||||
if (targetImage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TeammateProfileHoverTarget hoverTarget = targetImage.GetComponent<TeammateProfileHoverTarget>();
|
||||
if (hoverTarget == null)
|
||||
{
|
||||
hoverTarget = targetImage.gameObject.AddComponent<TeammateProfileHoverTarget>();
|
||||
}
|
||||
|
||||
hoverTarget.Bind(this, slotIndex);
|
||||
}
|
||||
|
||||
private IEnumerator EnsureHeroCache()
|
||||
{
|
||||
if (heroCacheReady) yield break;
|
||||
@@ -49,7 +91,7 @@ public class load_teammatesProfile : MonoBehaviour
|
||||
}
|
||||
|
||||
heroCacheBuilding = true;
|
||||
var allHeroes = Resources.LoadAll<AllyHero_SO>("");
|
||||
var allHeroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
heroById = new Dictionary<int, AllyHero_SO>();
|
||||
if (allHeroes != null)
|
||||
{
|
||||
@@ -79,6 +121,8 @@ public class load_teammatesProfile : MonoBehaviour
|
||||
|
||||
public void update_teammates_profile()
|
||||
{
|
||||
EnsureHoverTargetsBound();
|
||||
|
||||
if (!heroCacheReady)
|
||||
{
|
||||
StartCoroutine(EnsureHeroCacheThenUpdate());
|
||||
@@ -98,12 +142,12 @@ public class load_teammatesProfile : MonoBehaviour
|
||||
teammate_profile_04.sprite = GetHeroSquareProfile(heroId4);
|
||||
teammate_profile_05.sprite = GetHeroSquareProfile(heroId5);
|
||||
|
||||
// Set border colors based on hero level
|
||||
teammate_profile_boarder_01.color = GetBorderColor(GetHeroLevel(heroId1, 1));
|
||||
teammate_profile_boarder_02.color = GetBorderColor(GetHeroLevel(heroId2, 2));
|
||||
teammate_profile_boarder_03.color = GetBorderColor(GetHeroLevel(heroId3, 3));
|
||||
teammate_profile_boarder_04.color = GetBorderColor(GetHeroLevel(heroId4, 4));
|
||||
teammate_profile_boarder_05.color = GetBorderColor(GetHeroLevel(heroId5, 5));
|
||||
// Set border sprites based on hero level
|
||||
ApplyBorderSprite(teammate_profile_boarder_01, GetHeroLevelKey(heroId1, 1));
|
||||
ApplyBorderSprite(teammate_profile_boarder_02, GetHeroLevelKey(heroId2, 2));
|
||||
ApplyBorderSprite(teammate_profile_boarder_03, GetHeroLevelKey(heroId3, 3));
|
||||
ApplyBorderSprite(teammate_profile_boarder_04, GetHeroLevelKey(heroId4, 4));
|
||||
ApplyBorderSprite(teammate_profile_boarder_05, GetHeroLevelKey(heroId5, 5));
|
||||
}
|
||||
|
||||
private Sprite GetHeroSquareProfile(int heroId)
|
||||
@@ -117,58 +161,153 @@ public class load_teammatesProfile : MonoBehaviour
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetHeroLevel(int heroId, int slotIndex)
|
||||
private string GetHeroLevelKey(int heroId, int slotIndex)
|
||||
{
|
||||
if (heroId == 0) return "C";
|
||||
if (heroById == null) return "C";
|
||||
if (heroId == 0) return "Fallback";
|
||||
if (heroById == null) return "Fallback";
|
||||
AllyHero_SO hero;
|
||||
if (!heroById.TryGetValue(heroId, out hero) || hero == null) return "C";
|
||||
int exp = hero.ally_currentEXP;
|
||||
int keySlot = Mathf.Clamp(slotIndex, 1, 5);
|
||||
string expKey = $"selected_heroSlot0{keySlot}_exp";
|
||||
if (PlayerPrefs.HasKey(expKey))
|
||||
exp = PlayerPrefs.GetInt(expKey, exp);
|
||||
return GetRatingFromSO(hero, exp);
|
||||
if (!heroById.TryGetValue(heroId, out hero) || hero == null) return "Fallback";
|
||||
return GetRatingKeyFromSO(hero);
|
||||
}
|
||||
|
||||
private string GetRatingFromSO(AllyHero_SO so, int currentExp)
|
||||
private string GetRatingKeyFromSO(AllyHero_SO so)
|
||||
{
|
||||
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "C";
|
||||
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return "Fallback";
|
||||
return so.GetDisplayLevelRatingKey();
|
||||
}
|
||||
|
||||
List<AllyHero_SO.AllyLevelInfo> sorted = new List<AllyHero_SO.AllyLevelInfo>();
|
||||
foreach (var l in so.levelStats) if (l != null) sorted.Add(l);
|
||||
sorted.Sort((a, b) => a.requiredEXP.CompareTo(b.requiredEXP));
|
||||
int selectedIndex = 0;
|
||||
for (int i = 0; i < sorted.Count; i++)
|
||||
private void ApplyBorderSprite(Image targetImage, string level)
|
||||
{
|
||||
if (targetImage == null)
|
||||
{
|
||||
if (currentExp >= sorted[i].requiredEXP)
|
||||
{
|
||||
selectedIndex = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedIndex <= 0) return "C";
|
||||
if (selectedIndex == 1) return "B";
|
||||
if (selectedIndex == 2) return "A";
|
||||
return "S";
|
||||
Sprite borderSprite = GetBorderSprite(level);
|
||||
targetImage.sprite = borderSprite;
|
||||
|
||||
targetImage.color = Color.white;
|
||||
}
|
||||
|
||||
private Color GetBorderColor(string level)
|
||||
private Sprite GetBorderSprite(string level)
|
||||
{
|
||||
Color color;
|
||||
if (levelBorderSprites == null || levelBorderSprites.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int index;
|
||||
switch (level)
|
||||
{
|
||||
case "C": color = levelColor_C; break;
|
||||
case "B": color = levelColor_B; break;
|
||||
case "A": color = levelColor_A; break;
|
||||
case "S": color = levelColor_S; break;
|
||||
default: color = Color.white; break;
|
||||
case "Fallback": index = 4; break;
|
||||
case "C": index = 0; break;
|
||||
case "B": index = 1; break;
|
||||
case "A": index = 2; break;
|
||||
case "S": index = 3; break;
|
||||
default: index = 4; break;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= levelBorderSprites.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return levelBorderSprites[index];
|
||||
}
|
||||
|
||||
public void ShowHeroDetailsForSlot(int slotIndex)
|
||||
{
|
||||
loadDetailsPrefab detailsLoader = ResolveDetailsLoader();
|
||||
if (detailsLoader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!heroCacheReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int heroId = GetSelectedHeroId(slotIndex);
|
||||
if (heroId == 0 || heroById == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AllyHero_SO hero;
|
||||
if (!heroById.TryGetValue(heroId, out hero) || hero == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string type = "\u89d2\u8272";
|
||||
string name = string.IsNullOrEmpty(hero.ally_heroName) ? "\u672a\u77e5\u89d2\u8272" : hero.ally_heroName;
|
||||
string status = "\u53ef\u7528";
|
||||
string description = string.IsNullOrEmpty(hero.ally_heroDescription) ? "\u65e0\u63cf\u8ff0" : hero.ally_heroDescription;
|
||||
|
||||
if (hoverDetailsPrefab != null)
|
||||
{
|
||||
detailsLoader.detailsPrefab = hoverDetailsPrefab;
|
||||
}
|
||||
|
||||
if (hoverDetailsParent != null)
|
||||
{
|
||||
detailsLoader.detailsParent = hoverDetailsParent;
|
||||
}
|
||||
|
||||
detailsLoader.ShowDetails(type, name, status, description, Input.mousePosition);
|
||||
}
|
||||
|
||||
public void HideHeroDetails()
|
||||
{
|
||||
loadDetailsPrefab detailsLoader = ResolveDetailsLoader();
|
||||
if (detailsLoader != null)
|
||||
{
|
||||
detailsLoader.HideDetails();
|
||||
}
|
||||
}
|
||||
|
||||
private loadDetailsPrefab ResolveDetailsLoader()
|
||||
{
|
||||
if (hoverDetailsLoader != null)
|
||||
{
|
||||
return hoverDetailsLoader;
|
||||
}
|
||||
|
||||
return loadDetailsPrefab.Instance;
|
||||
}
|
||||
|
||||
private int GetSelectedHeroId(int slotIndex)
|
||||
{
|
||||
int keySlot = Mathf.Clamp(slotIndex, 1, 5);
|
||||
return PlayerPrefs.GetInt($"selected_heroSlot0{keySlot}_heroID", 0);
|
||||
}
|
||||
|
||||
private sealed class TeammateProfileHoverTarget : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
private load_teammatesProfile owner;
|
||||
private int slotIndex;
|
||||
|
||||
public void Bind(load_teammatesProfile targetOwner, int targetSlotIndex)
|
||||
{
|
||||
owner = targetOwner;
|
||||
slotIndex = targetSlotIndex;
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
if (owner != null)
|
||||
{
|
||||
owner.ShowHeroDetailsForSlot(slotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
if (owner != null)
|
||||
{
|
||||
owner.HideHeroDetails();
|
||||
}
|
||||
}
|
||||
color.a = 1f;
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Bansonic;
|
||||
|
||||
public class returnMainMenu : MonoBehaviour
|
||||
{
|
||||
@@ -27,8 +28,17 @@ public class returnMainMenu : MonoBehaviour
|
||||
|
||||
private IEnumerator LoadSceneAsync(string sceneName)
|
||||
{
|
||||
if (gTransition.LoadScene(sceneName, LoadSceneMode.Single))
|
||||
{
|
||||
while (gTransition.IsBusy)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
|
||||
while (!asyncLoad.isDone)
|
||||
while (asyncLoad != null && !asyncLoad.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ public class userSettings : MonoBehaviour
|
||||
{
|
||||
Dictionary<int, SongData> songs = new Dictionary<int, SongData>();
|
||||
|
||||
SongData[] runtimeSongs = Resources.LoadAll<SongData>(RuntimeResourcesPath);
|
||||
SongData[] runtimeSongs = RuntimeResourcesCache.LoadSongsFromPath(RuntimeResourcesPath);
|
||||
if (runtimeSongs != null)
|
||||
{
|
||||
for (int i = 0; i < runtimeSongs.Length; i++)
|
||||
@@ -350,6 +350,9 @@ public class userSettings : MonoBehaviour
|
||||
AssetDatabase.SaveAssets();
|
||||
#endif
|
||||
|
||||
SceneManager.LoadScene("Main_main");
|
||||
if (!gTransition.LoadScene("Main_main", LoadSceneMode.Single))
|
||||
{
|
||||
SceneManager.LoadScene("Main_main");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user