装备系统完结,修复并加入很多

This commit is contained in:
FloatGaming
2026-04-03 19:15:02 +08:00
parent 538085b64f
commit d9376833b6
378 changed files with 62464 additions and 16756 deletions
+88 -54
View File
@@ -146,6 +146,15 @@ public class AllyCombatant : MonoBehaviour, ICombatant
private int _lastManaFullCastFrame = -1;
private bool _pendingManaFullCastRetry = false;
// Last event payloads used by custom equipment skill handlers.
public int lastHealOverflowAmount { get; private set; }
public int lastHealActualAmount { get; private set; }
public int lastDamageTakenAmount { get; private set; }
public int lastAdjacentAllyDamageTakenAmount { get; private set; }
public int lastMaxHPIncreaseAmount { get; private set; }
public int lastManaSpentAmount { get; private set; }
public bool lastManaSpendWasFullBar { get; private set; }
// Reusable buffers to reduce allocations during skill evaluation
private readonly List<SkillDefinition> _tmpSkillDefs = new List<SkillDefinition>(16);
private readonly Dictionary<string, float> _formulaVarsBuffer = new Dictionary<string, float>(16);
@@ -1001,20 +1010,22 @@ public class AllyCombatant : MonoBehaviour, ICombatant
var eff = GetEffectiveLevelForExp(a, slotExp);
if (eff != null)
{
maxHP = eff.maxHP;
maxMana = eff.maxMana;
damageResistance = eff.damageResistance;
scoreEfficiency = eff.scoreEfficiency;
SetAttack(eff.attack);
var resolved = a.GetEffectiveLevelInfoWithEquipment(eff);
maxHP = resolved.maxHP;
maxMana = resolved.maxMana;
damageResistance = resolved.damageResistance;
scoreEfficiency = resolved.scoreEfficiency;
SetAttack(resolved.attack);
}
else
{
var lvl = a.levelStats[0];
maxHP = lvl.maxHP;
maxMana = lvl.maxMana;
damageResistance = lvl.damageResistance;
scoreEfficiency = lvl.scoreEfficiency;
SetAttack(lvl.attack);
var resolved = a.GetEffectiveLevelInfoWithEquipment(lvl);
maxHP = resolved.maxHP;
maxMana = resolved.maxMana;
damageResistance = resolved.damageResistance;
scoreEfficiency = resolved.scoreEfficiency;
SetAttack(resolved.attack);
}
}
@@ -1263,6 +1274,12 @@ public class AllyCombatant : MonoBehaviour, ICombatant
UpdateUIImmediate();
lastMaxHPIncreaseAmount = Mathf.Max(0, maxHP - oldMax);
if (lastMaxHPIncreaseAmount > 0)
{
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnMaxHPIncreased);
}
// Max HP change affects percentage triggers
EvaluateHPPercentageTriggers(oldHP);
}
@@ -1309,6 +1326,8 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
else if (delta < 0)
{
lastManaSpentAmount = -delta;
lastManaSpendWasFullBar = old >= maxMana && currentMana <= 0;
if (_isTriggeringManaLost) return;
_isTriggeringManaLost = true;
try
@@ -1320,6 +1339,11 @@ public class AllyCombatant : MonoBehaviour, ICombatant
_isTriggeringManaLost = false;
}
}
else
{
lastManaSpentAmount = 0;
lastManaSpendWasFullBar = false;
}
int finalMana = currentMana;
if (requestedMana != old || incomingOverflowGainAtFull)
@@ -1648,6 +1672,7 @@ public class AllyCombatant : MonoBehaviour, ICombatant
ModifyHP(-delta, true, true);
int actual = oldHP - currentHP;
lastDamageTakenAmount = actual;
GameplaySkillLogger.RecordAllyResourceEvent(
allyName,
slotIndex,
@@ -1671,6 +1696,12 @@ public class AllyCombatant : MonoBehaviour, ICombatant
iNumberPrefabController.SpawnForAllyStatic(slotIndex, iNumberPrefabController.InstantNumberType.Damage, -actual);
}
}
if (actual > 0)
{
TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnDamageTaken);
NotifyAdjacentAlliesDamaged(actual);
}
}
public void ReceiveHeal(float amount, GameObject source, bool deferPopup = false)
@@ -1679,8 +1710,11 @@ public class AllyCombatant : MonoBehaviour, ICombatant
int oldHP = currentHP;
float mult = GetTotalHealReceivedMultiplier();
int delta = Mathf.CeilToInt(amount * mult);
int rawTargetHp = oldHP + delta;
ModifyHP(delta, true, true);
int actual = currentHP - oldHP;
lastHealActualAmount = actual;
lastHealOverflowAmount = Mathf.Max(0, rawTargetHp - maxHP);
GameplaySkillLogger.RecordAllyResourceEvent(
allyName,
slotIndex,
@@ -1706,6 +1740,26 @@ public class AllyCombatant : MonoBehaviour, ICombatant
}
}
private void NotifyAdjacentAlliesDamaged(int actualDamage)
{
if (actualDamage <= 0) return;
var ui = teamUIController.Instance;
if (ui == null) return;
int[] adjacent = ui.GetAdjacentAllyIndices(slotIndex);
if (adjacent == null || adjacent.Length == 0) return;
for (int i = 0; i < adjacent.Length; i++)
{
var allyObj = ui.GetAllyObjectBySlot(adjacent[i]);
if (allyObj == null) continue;
var ally = allyObj.GetComponent<AllyCombatant>();
if (ally == null || ally == this || ally.IsDead) continue;
ally.lastAdjacentAllyDamageTakenAmount = actualDamage;
ally.TryTriggerSkillsOnEvent(SkillDefinition.SkillTrigger.OnAdjacentAllyDamaged);
}
}
public void ApplyBuff(Buff buff, GameObject source)
{
ApplyBuffInternal(buff, source, true);
@@ -1764,17 +1818,13 @@ public class AllyCombatant : MonoBehaviour, ICombatant
if (so == null) return;
// If equipped groups exist, iterate them first
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (var gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
foreach (var def in group.skills)
{
@@ -1874,13 +1924,13 @@ public class AllyCombatant : MonoBehaviour, ICombatant
bool anyTriggered = false;
// First: equipped groups
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
foreach (var skill in group.skills)
{
@@ -2029,17 +2079,13 @@ public class AllyCombatant : MonoBehaviour, ICombatant
// Collect candidate skills (same priority order as other triggers)
var defs = _tmpSkillDefs;
defs.Clear();
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (var gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null || group.skills == null) continue;
for (int i = 0; i < group.skills.Length; i++)
{
@@ -2193,17 +2239,13 @@ public class AllyCombatant : MonoBehaviour, ICombatant
var defs = _tmpSkillDefs;
defs.Clear();
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (var gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null || group.skills == null) continue;
for (int i = 0; i < group.skills.Length; i++)
{
@@ -2299,17 +2341,13 @@ public class AllyCombatant : MonoBehaviour, ICombatant
var defs = _tmpSkillDefs;
defs.Clear();
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (var gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null || group.skills == null) continue;
for (int i = 0; i < group.skills.Length; i++)
{
@@ -2424,17 +2462,13 @@ public class AllyCombatant : MonoBehaviour, ICombatant
var defs = _tmpSkillDefs;
defs.Clear();
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (var gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null || group.skills == null) continue;
for (int i = 0; i < group.skills.Length; i++)
{
+5 -1
View File
@@ -47,7 +47,11 @@ public enum EffectType
// While active, redirects damage that would be taken by this ally to an adjacent ally.
RedirectSelfDamageToAdjacent,
// While active, any non-Miss note judgement on the ally's track is rewritten to Perfect.
RewriteNonMissToPerfect
RewriteNonMissToPerfect,
// Equipment/passive specific: restore current mana directly.
RestoreMana,
// Equipment/passive specific: effect is handled entirely by custom code path.
CustomPassive
}
public interface ICombatant
+455 -57
View File
@@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@@ -73,11 +73,34 @@ public class SkillBuilder : MonoBehaviour
private readonly Dictionary<string, RefreshOnlyTimedState> _refreshOnlyTimedEffectStates = new Dictionary<string, RefreshOnlyTimedState>(32);
private readonly Dictionary<string, Coroutine> _refreshOnlyOverTimeCoroutines = new Dictionary<string, Coroutine>(16);
private readonly Dictionary<int, ManaFullLongingState> _manaFullLongingStateBySlot = new Dictionary<int, ManaFullLongingState>(8);
private readonly Dictionary<int, EquipDuelState> _equipDuelStatesBySlot = new Dictionary<int, EquipDuelState>(8);
private readonly Dictionary<int, int> _equipManaFullSpendCountBySlot = new Dictionary<int, int>(8);
private readonly HashSet<int> _equipGhoulSchoolActiveSlots = new HashSet<int>();
private readonly Dictionary<int, HashSet<string>> _equipGhoulSchoolSeenSkillIdsBySlot = new Dictionary<int, HashSet<string>>(8);
private int _cachedAlliesFrame = -1;
private readonly List<GameObject> _cachedAllies = new List<GameObject>(8);
private const string SkillIdYuetaoLongingDamage = "44109";
private const string SkillIdYuetaoLongingCost = "44109_2";
private const string EquipSkillLostMaster = "420001_overheal_mana";
private const string EquipSkillDuelStart = "420002_duel_start";
private const string EquipSkillDuelDouble = "420003_duel_double";
private const string EquipSkillDuelDisable = "420004_duel_disable";
private const string EquipSkillGhoulSchool = "420005_ghoul_school";
private const string EquipSkillFifthManaRestore = "420006_fifth_mana_restore";
private const string EquipSkillMaxHpToAttack = "420007_maxhp_gain_attack";
private const string EquipSkillSelfDamagedGrowHp = "420008_self_damage_grow_hp";
private const string EquipSkillAdjacentDamagedAddScore = "420009_adjacent_damage_add_score";
private const string EquipSkillStartSetMana = "420010_start_set_mana";
private const string EquipSkillKillRefillMana = "420011_kill_refill_mana";
private const string EquipSkillManaSpendReduceCap = "420012_mana_spend_reduce_cap";
private const string IllusionSkillBole14StartMaxHp = "421001_bole14_start_maxhp";
private const string IllusionSkillBole6SpendFullMaxHp = "421002_bole6_spend_full_maxhp";
private const string IllusionSkillScarborough28StartAttack = "421003_scarborough28_start_attack";
private const string IllusionSkillScarborough33SpendFullScore = "421004_scarborough33_spend_full_score";
private const string IllusionSkillBole7KillHeal = "421005_bole7_kill_heal";
private const string IllusionSkillCraft9SpendFullMana = "421006_craft9_spend_full_mana";
private const string IllusionSkillTeraExtraSlot = "421007_tera_extra_slot";
private struct ManaFullLongingState
{
@@ -85,6 +108,14 @@ public class SkillBuilder : MonoBehaviour
public int consumedHp;
}
private sealed class EquipDuelState
{
public bool disabled;
public int hpLossPerSecond = 12;
public int scorePerSecond = 6;
public Coroutine routine;
}
private static readonly HashSet<string> s_refreshOnlyTimedSkillIds = new HashSet<string>
{
// Lock
@@ -1200,26 +1231,9 @@ public class SkillBuilder : MonoBehaviour
heroSoForName = GetAllyHeroSOBySlot(slotIndex);
// Documentation text normalized.
if (heroSoForName != null && heroSoForName.skillGroups != null)
if (heroSoForName != null)
{
foreach (var g in heroSoForName.skillGroups)
{
if (g == null || g.skills == null) continue;
foreach (var sd in g.skills)
{
if (sd == null) continue;
if (sd == def)
{
groupId = g.skillGroupID;
if (!string.IsNullOrWhiteSpace(g.groupName)) smallSkillName = g.groupName;
smallSkillIcon = g.groupIcon;
goto ResolvedGroup;
}
}
}
// Fallback: match by skillId when the SkillDefinition instance isn't the same reference.
if (!string.IsNullOrWhiteSpace(def.skillId))
if (heroSoForName.skillGroups != null)
{
foreach (var g in heroSoForName.skillGroups)
{
@@ -1227,7 +1241,7 @@ public class SkillBuilder : MonoBehaviour
foreach (var sd in g.skills)
{
if (sd == null) continue;
if (!string.IsNullOrWhiteSpace(sd.skillId) && sd.skillId == def.skillId)
if (sd == def)
{
groupId = g.skillGroupID;
if (!string.IsNullOrWhiteSpace(g.groupName)) smallSkillName = g.groupName;
@@ -1236,6 +1250,51 @@ public class SkillBuilder : MonoBehaviour
}
}
}
// Fallback: match by skillId when the SkillDefinition instance isn't the same reference.
if (!string.IsNullOrWhiteSpace(def.skillId))
{
foreach (var g in heroSoForName.skillGroups)
{
if (g == null || g.skills == null) continue;
foreach (var sd in g.skills)
{
if (sd == null) continue;
if (!string.IsNullOrWhiteSpace(sd.skillId) && sd.skillId == def.skillId)
{
groupId = g.skillGroupID;
if (!string.IsNullOrWhiteSpace(g.groupName)) smallSkillName = g.groupName;
smallSkillIcon = g.groupIcon;
goto ResolvedGroup;
}
}
}
}
}
if (!string.IsNullOrWhiteSpace(def.skillId))
{
int[] runtimeGroupIds = heroSoForName.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null)
{
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
var g = heroSoForName.GetSkillGroupByID(gid);
if (g == null || g.skills == null) continue;
foreach (var sd in g.skills)
{
if (sd == null) continue;
if (!string.IsNullOrWhiteSpace(sd.skillId) && sd.skillId == def.skillId)
{
groupId = g.skillGroupID;
if (!string.IsNullOrWhiteSpace(g.groupName)) smallSkillName = g.groupName;
smallSkillIcon = g.groupIcon;
goto ResolvedGroup;
}
}
}
}
}
}
ResolvedGroup:
@@ -1325,6 +1384,13 @@ ResolvedGroup:
return;
}
NotifyGhoulSchoolSkillTriggered(def, slotIndex, caster);
if (TryHandleEquipmentSkill(def, slotIndex, caster, heroSoForName, specificTarget))
{
return;
}
float amount = 0f;
// If caller provided explicit inputValue use it
if (inputValue != -1f)
@@ -1913,6 +1979,334 @@ ResolvedGroup:
return finalCost;
}
private bool TryHandleEquipmentSkill(SkillDefinition def, int slotIndex, GameObject caster, AllyHero_SO heroSoForName, GameObject specificTarget)
{
if (def == null || string.IsNullOrWhiteSpace(def.skillId)) return false;
var casterAlly = caster != null ? caster.GetComponent<AllyCombatant>() : null;
if (casterAlly == null) return false;
switch (def.skillId)
{
case EquipSkillLostMaster:
return HandleEquipSkillLostMaster(casterAlly);
case EquipSkillDuelStart:
return HandleEquipSkillDuelStart(slotIndex, casterAlly);
case EquipSkillDuelDouble:
return HandleEquipSkillDuelDouble(slotIndex);
case EquipSkillDuelDisable:
return HandleEquipSkillDuelDisable(slotIndex);
case EquipSkillGhoulSchool:
return HandleEquipSkillGhoulSchool(slotIndex);
case EquipSkillFifthManaRestore:
return HandleEquipSkillFifthManaRestore(slotIndex, casterAlly);
case EquipSkillMaxHpToAttack:
return HandleEquipSkillMaxHpToAttack(casterAlly);
case EquipSkillSelfDamagedGrowHp:
return HandleEquipSkillSelfDamagedGrowHp(casterAlly);
case EquipSkillAdjacentDamagedAddScore:
return HandleEquipSkillAdjacentDamagedAddScore(casterAlly);
case EquipSkillStartSetMana:
return HandleEquipSkillStartSetMana(casterAlly);
case EquipSkillKillRefillMana:
return HandleEquipSkillKillRefillMana(casterAlly);
case EquipSkillManaSpendReduceCap:
return HandleEquipSkillManaSpendReduceCap(casterAlly);
case IllusionSkillBole14StartMaxHp:
return HandleIllusionSkillBole14StartMaxHp(casterAlly);
case IllusionSkillBole6SpendFullMaxHp:
return HandleIllusionSkillBole6SpendFullMaxHp(casterAlly);
case IllusionSkillScarborough28StartAttack:
return HandleIllusionSkillScarborough28StartAttack(casterAlly);
case IllusionSkillScarborough33SpendFullScore:
return HandleIllusionSkillScarborough33SpendFullScore(casterAlly);
case IllusionSkillBole7KillHeal:
return HandleIllusionSkillBole7KillHeal(casterAlly);
case IllusionSkillCraft9SpendFullMana:
return HandleIllusionSkillCraft9SpendFullMana(casterAlly);
case IllusionSkillTeraExtraSlot:
return true;
default:
return false;
}
}
private bool HandleEquipSkillGhoulSchool(int slotIndex)
{
_equipGhoulSchoolActiveSlots.Add(slotIndex);
if (!_equipGhoulSchoolSeenSkillIdsBySlot.ContainsKey(slotIndex))
{
_equipGhoulSchoolSeenSkillIdsBySlot[slotIndex] = new HashSet<string>();
}
return true;
}
private void NotifyGhoulSchoolSkillTriggered(SkillDefinition def, int slotIndex, GameObject caster)
{
if (def == null || slotIndex < 0) return;
if (def.skillId == EquipSkillGhoulSchool) return;
if (!_equipGhoulSchoolActiveSlots.Contains(slotIndex)) return;
if (string.IsNullOrWhiteSpace(def.skillId)) return;
if (!_equipGhoulSchoolSeenSkillIdsBySlot.TryGetValue(slotIndex, out HashSet<string> seen))
{
seen = new HashSet<string>();
_equipGhoulSchoolSeenSkillIdsBySlot[slotIndex] = seen;
}
if (!seen.Add(def.skillId))
{
return;
}
AllyCombatant ally = caster != null ? caster.GetComponent<AllyCombatant>() : GetAllyObjectBySlot(slotIndex)?.GetComponent<AllyCombatant>();
if (ally == null || ally.IsDead) return;
ally.scoreEfficiency += 0.01f;
iBudeffPrefabController.Instance?.RegisterTimedEffect(ally, PlayerBudeffIconType.ot_scoreEfficiency_up, 0.01f, 0f);
}
private bool HandleEquipSkillLostMaster(AllyCombatant ally)
{
int overflow = Mathf.Max(0, ally.lastHealOverflowAmount);
if (overflow <= 0) return true;
int manaGain = Mathf.FloorToInt(overflow * 0.07f);
if (manaGain > 0)
{
ally.ModifyMana(manaGain, true, true);
}
return true;
}
private bool HandleEquipSkillDuelStart(int slotIndex, AllyCombatant ally)
{
if (!_equipDuelStatesBySlot.TryGetValue(slotIndex, out EquipDuelState state))
{
state = new EquipDuelState();
_equipDuelStatesBySlot[slotIndex] = state;
}
if (state.disabled)
{
return true;
}
state.hpLossPerSecond = 12;
state.scorePerSecond = 6;
if (state.routine != null)
{
StopCoroutine(state.routine);
}
state.routine = StartCoroutine(EquipDuelRoutine(slotIndex, ally));
return true;
}
private bool HandleEquipSkillDuelDouble(int slotIndex)
{
if (_equipDuelStatesBySlot.TryGetValue(slotIndex, out EquipDuelState state) && !state.disabled)
{
state.hpLossPerSecond *= 2;
state.scorePerSecond *= 2;
}
return true;
}
private bool HandleEquipSkillDuelDisable(int slotIndex)
{
if (_equipDuelStatesBySlot.TryGetValue(slotIndex, out EquipDuelState state))
{
state.disabled = true;
if (state.routine != null)
{
StopCoroutine(state.routine);
state.routine = null;
}
}
return true;
}
private IEnumerator EquipDuelRoutine(int slotIndex, AllyCombatant ally)
{
while (ally != null && !ally.IsDead)
{
if (!_equipDuelStatesBySlot.TryGetValue(slotIndex, out EquipDuelState state) || state.disabled)
{
yield break;
}
ally.ModifyHP(-Mathf.Max(0, state.hpLossPerSecond), true);
if (ally == null || ally.IsDead)
{
yield break;
}
ally.AddScoreDirect(Mathf.Max(0, state.scorePerSecond));
yield return new WaitForSeconds(1f);
}
}
private bool HandleEquipSkillFifthManaRestore(int slotIndex, AllyCombatant ally)
{
if (!ally.lastManaSpendWasFullBar || ally.lastManaSpentAmount <= 0)
{
return true;
}
int count = 0;
_equipManaFullSpendCountBySlot.TryGetValue(slotIndex, out count);
count++;
_equipManaFullSpendCountBySlot[slotIndex] = count;
if (count % 5 == 0)
{
ally.SetCurrentMana(ally.maxMana, true, true);
}
return true;
}
private bool HandleEquipSkillMaxHpToAttack(AllyCombatant ally)
{
int hpIncrease = Mathf.Max(0, ally.lastMaxHPIncreaseAmount);
int times = hpIncrease / 200;
if (times <= 0)
{
return true;
}
int attackGainPerStep = Mathf.CeilToInt(ally.maxHP * 0.03f);
int totalAttackGain = Mathf.Max(0, attackGainPerStep * times);
if (totalAttackGain > 0)
{
ally.ModifyAttack(totalAttackGain);
}
return true;
}
private bool HandleEquipSkillSelfDamagedGrowHp(AllyCombatant ally)
{
if (ally.lastDamageTakenAmount <= 0)
{
return true;
}
int increase = Mathf.CeilToInt(ally.maxHP * 0.02f);
if (increase > 0)
{
ally.SetMaxHP(ally.maxHP + increase, false);
}
return true;
}
private bool HandleEquipSkillAdjacentDamagedAddScore(AllyCombatant ally)
{
int scoreGain = Mathf.Max(0, ally.lastAdjacentAllyDamageTakenAmount);
if (scoreGain > 0)
{
ally.AddScoreDirect(scoreGain);
}
return true;
}
private bool HandleEquipSkillStartSetMana(AllyCombatant ally)
{
int newMaxMana = Mathf.Max(1, Mathf.CeilToInt(ally.maxHP * 0.5f));
ally.SetMaxMana(newMaxMana, false);
return true;
}
private bool HandleEquipSkillKillRefillMana(AllyCombatant ally)
{
ally.SetCurrentMana(ally.maxMana, true, true);
return true;
}
private bool HandleEquipSkillManaSpendReduceCap(AllyCombatant ally)
{
if (ally.lastManaSpentAmount <= 0)
{
return true;
}
ally.SetMaxMana(Mathf.Max(1, ally.maxMana - 100), false);
return true;
}
private bool HandleIllusionSkillBole14StartMaxHp(AllyCombatant ally)
{
int increase = Mathf.CeilToInt(ally.maxHP * 0.02f);
if (increase > 0)
{
ally.SetMaxHP(ally.maxHP + increase, false);
}
return true;
}
private bool HandleIllusionSkillBole6SpendFullMaxHp(AllyCombatant ally)
{
if (!ally.lastManaSpendWasFullBar || ally.lastManaSpentAmount <= 0)
{
return true;
}
int increase = Mathf.CeilToInt(ally.maxHP * 0.01f);
if (increase > 0)
{
ally.SetMaxHP(ally.maxHP + increase, false);
}
return true;
}
private bool HandleIllusionSkillScarborough28StartAttack(AllyCombatant ally)
{
ally.ModifyAttack(2);
return true;
}
private bool HandleIllusionSkillScarborough33SpendFullScore(AllyCombatant ally)
{
if (!ally.lastManaSpendWasFullBar || ally.lastManaSpentAmount <= 0)
{
return true;
}
ally.AddScoreDirect(Mathf.Max(0, ally.attack));
return true;
}
private bool HandleIllusionSkillBole7KillHeal(AllyCombatant ally)
{
int healAmount = Mathf.CeilToInt(ally.maxHP * 0.03f);
if (healAmount > 0)
{
ally.ModifyHP(healAmount, true);
}
return true;
}
private bool HandleIllusionSkillCraft9SpendFullMana(AllyCombatant ally)
{
if (!ally.lastManaSpendWasFullBar || ally.lastManaSpentAmount <= 0)
{
return true;
}
ally.ModifyMana(10, true, true);
return true;
}
// Use the selected skill index from an AllyHero_SO for a given slot (calls UseSkillDefinition)
public void UseSelectedSkillForSlot(int slotIndex, int selectedSkillIndex, float inputValue = -1f, GameObject specificTarget = null)
{
@@ -1937,17 +2331,13 @@ ResolvedGroup:
}
// If SO defines equipped group IDs, cast skills from those groups (these represent active/owned skills at runtime)
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (var gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
UseSkillGroupForSlot(group, slotIndex, inputValue, specificTarget);
}
@@ -1984,6 +2374,18 @@ ResolvedGroup:
public void TriggerOnGameStart()
{
if (teamUIController.Instance == null || teamUIController.Instance.allySlotIds == null) return;
foreach (var kv in _equipDuelStatesBySlot)
{
if (kv.Value != null && kv.Value.routine != null)
{
StopCoroutine(kv.Value.routine);
kv.Value.routine = null;
}
}
_equipDuelStatesBySlot.Clear();
_equipManaFullSpendCountBySlot.Clear();
_equipGhoulSchoolActiveSlots.Clear();
_equipGhoulSchoolSeenSkillIdsBySlot.Clear();
int slots = teamUIController.Instance.allySlotIds.Count;
for (int i = 0; i < slots; i++)
{
@@ -1991,13 +2393,13 @@ ResolvedGroup:
if (so == null) continue;
// If equipped groups exist, iterate them and cast group skills whose triggerCondition == OnGameStart
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
foreach (var sk in group.skills)
{
@@ -2054,13 +2456,13 @@ ResolvedGroup:
var so = GetAllyHeroSOBySlot(i);
if (so == null) continue;
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
foreach (var def in group.skills)
{
@@ -2118,13 +2520,13 @@ ResolvedGroup:
var so = GetAllyHeroSOBySlot(i);
if (so == null) continue;
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
foreach (var def in group.skills)
{
@@ -2170,13 +2572,13 @@ ResolvedGroup:
var so = GetAllyHeroSOBySlot(i);
if (so == null) continue;
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
foreach (var def in group.skills)
{
@@ -2248,17 +2650,13 @@ ResolvedGroup:
var so = GetAllyHeroSOBySlot(i);
if (so == null) continue;
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIds = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++)
{
var g = so.skillGroups[k];
if (g != null && g.skillGroupID == gid) { group = g; break; }
}
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null || group.skills == null) continue;
foreach (var def in group.skills)
@@ -2388,13 +2786,13 @@ ResolvedGroup:
bool anyTriggered = false;
// If equipped groups exist, iterate equipped groups and check each skill for OnNoteHit
if (so.equippedSkillGroupIDs != null && so.equippedSkillGroupIDs.Length > 0 && so.skillGroups != null)
int[] runtimeGroupIdsForNotes = so.GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIdsForNotes != null && runtimeGroupIdsForNotes.Length > 0)
{
foreach (int gid in so.equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIdsForNotes)
{
if (gid == 0) continue;
SkillGroup group = null;
for (int k = 0; k < so.skillGroups.Length; k++) { var g = so.skillGroups[k]; if (g != null && g.skillGroupID == gid) { group = g; break; } }
SkillGroup group = so.GetSkillGroupByID(gid);
if (group == null) continue;
foreach (var def in group.skills)
{
+6
View File
@@ -28,6 +28,12 @@ public class SkillDefinition : ScriptableObject
OnManaGained,
// Documentation text normalized.
OnManaLost,
// New: triggers only when this ally takes direct damage (not generic HP loss).
OnDamageTaken,
// New: triggers when an adjacent ally takes direct damage.
OnAdjacentAllyDamaged,
// New: triggers when this ally's max HP increases.
OnMaxHPIncreased,
// Documentation text normalized.
OnHPAbovePercent,
// Documentation text normalized.
+431 -10
View File
@@ -1,6 +1,9 @@
using System;
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
[CreateAssetMenu(fileName = "NewAllyHero", menuName = "SO_Data/AllyHero")]
public class AllyHero_SO : ScriptableObject
@@ -24,6 +27,7 @@ public class AllyHero_SO : ScriptableObject
public string ally_heroDesignation;
public int ally_heroID;
public bool isUnlocked;
public equipmentSO.EquipmentSkillType allyType;
[Header("Inspector")]
public Sprite ally_heroImage;
@@ -129,6 +133,10 @@ public class AllyHero_SO : ScriptableObject
[Tooltip("Equipped skill group IDs for this ally. Each int references a skillGroupID defined in skillGroups. Use 0 to indicate empty.")]
public int[] equippedSkillGroupIDs = new int[0];
[Header("Equipments")]
public equipmentSO equippedEquipment;
public string equippedEquipmentId;
public SkillDefinition GetPrimarySkill()
{
if (availableSkills == null || primarySkillIndex < 0 || primarySkillIndex >= availableSkills.Length) return null;
@@ -137,11 +145,10 @@ public class AllyHero_SO : ScriptableObject
public SkillGroup GetPrimarySkillGroup()
{
if (skillGroups == null || skillGroups.Length == 0) return null;
if (equippedSkillGroupIDs != null && equippedSkillGroupIDs.Length > 0)
int[] runtimeGroupIds = GetEffectiveEquippedSkillGroupIDs();
if (runtimeGroupIds != null && runtimeGroupIds.Length > 0)
{
foreach (int gid in equippedSkillGroupIDs)
foreach (int gid in runtimeGroupIds)
{
if (gid == 0) continue;
SkillGroup group = GetSkillGroupByID(gid);
@@ -149,9 +156,12 @@ public class AllyHero_SO : ScriptableObject
}
}
foreach (SkillGroup group in skillGroups)
if (skillGroups != null)
{
if (group != null) return group;
foreach (SkillGroup group in skillGroups)
{
if (group != null) return group;
}
}
return null;
@@ -159,12 +169,191 @@ public class AllyHero_SO : ScriptableObject
public SkillGroup GetSkillGroupByID(int groupID)
{
if (skillGroups == null || skillGroups.Length == 0) return null;
foreach (SkillGroup group in skillGroups)
if (skillGroups != null && skillGroups.Length > 0)
{
if (group != null && group.skillGroupID == groupID) return group;
foreach (SkillGroup group in skillGroups)
{
if (group != null && group.skillGroupID == groupID) return group;
}
}
return null;
return EquipmentSkillGroupLibrary.ResolveSkillGroup(groupID);
}
public int[] GetEffectiveEquippedSkillGroupIDs()
{
var result = new List<int>();
var dedupe = new HashSet<int>();
int heroSkillSlotsRemaining = GetEffectiveSkillSlotLimit();
if (equippedSkillGroupIDs != null)
{
for (int i = 0; i < equippedSkillGroupIDs.Length; i++)
{
if (heroSkillSlotsRemaining <= 0)
{
break;
}
int groupId = equippedSkillGroupIDs[i];
if (groupId <= 0 || !dedupe.Add(groupId))
{
continue;
}
result.Add(groupId);
heroSkillSlotsRemaining--;
}
}
equipmentSO equipment = GetEquippedEquipmentResolved();
if (equipment != null)
{
TryAddEquipmentSkillGroupId(result, dedupe, equipment.sa_skillID);
TryAddEquipmentSkillGroupId(result, dedupe, equipment.ia_skillID);
}
return result.ToArray();
}
public int GetEffectiveSkillSlotLimit()
{
AllyLevelInfo currentLevelInfo = GetEffectiveLevelForCurrentEXP();
int slotLimit = currentLevelInfo != null ? Mathf.Max(0, currentLevelInfo.skill_slot_limited) : 0;
if (HasExclusiveIllusionExtraSlotBonus())
{
slotLimit += 1;
}
return slotLimit;
}
public AllyLevelInfo GetEffectiveLevelInfoWithEquipment(AllyLevelInfo baseInfo)
{
if (baseInfo == null)
{
return null;
}
AllyLevelInfo resolved = CloneLevelInfo(baseInfo);
resolved.skill_slot_limited = GetEffectiveSkillSlotLimit();
equipmentSO equipment = GetEquippedEquipmentResolved();
if (equipment == null)
{
return resolved;
}
resolved.maxHP = ApplyIntEquipmentBonus(
resolved.maxHP,
equipment.maxHp,
equipment,
equipmentSO.EquipmentSpecialEffectType.MaxHp);
resolved.attack = ApplyIntEquipmentBonus(
resolved.attack,
equipment.attack,
equipment,
equipmentSO.EquipmentSpecialEffectType.Attack);
resolved.maxMana = ApplyIntEquipmentBonus(
resolved.maxMana,
equipment.maxMana,
equipment,
equipmentSO.EquipmentSpecialEffectType.MaxMana);
resolved.damageResistance = ApplyFloatEquipmentBonus(
resolved.damageResistance,
equipment.damageResistance,
equipment,
equipmentSO.EquipmentSpecialEffectType.DamageResistance);
resolved.scoreEfficiency = ApplyFloatEquipmentBonus(
resolved.scoreEfficiency,
equipment.scoreEfficiency,
equipment,
equipmentSO.EquipmentSpecialEffectType.ScoreEfficiency);
return resolved;
}
private bool HasExclusiveIllusionExtraSlotBonus()
{
equipmentSO equipment = GetEquippedEquipmentResolved();
if (equipment == null || equipment.ia_skillID != 30021007)
{
return false;
}
AllyHero_SO winner = ResolveExclusiveIllusionExtraSlotOwner();
return winner != null && winner.ally_heroID == ally_heroID;
}
private static AllyHero_SO ResolveExclusiveIllusionExtraSlotOwner()
{
AllyHero_SO[] heroes = LoadAllHeroAssetsForEquipmentChecks();
if (heroes == null || heroes.Length == 0)
{
return null;
}
AllyHero_SO selected = null;
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero == null)
{
continue;
}
hero.LoadEquippedEquipmentFromLocal();
equipmentSO equipped = hero.GetEquippedEquipmentResolved();
if (equipped == null || equipped.ia_skillID != 30021007)
{
continue;
}
if (selected == null
|| hero.ally_heroID < selected.ally_heroID
|| (hero.ally_heroID == selected.ally_heroID
&& string.CompareOrdinal(hero.ally_heroName ?? string.Empty, selected.ally_heroName ?? string.Empty) < 0))
{
selected = hero;
}
}
return selected;
}
private static AllyHero_SO[] LoadAllHeroAssetsForEquipmentChecks()
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
string[] guids = AssetDatabase.FindAssets("t:AllyHero_SO", new[] { "Assets/Resources/so/ally" });
var heroes = new List<AllyHero_SO>(guids.Length);
for (int i = 0; i < guids.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
AllyHero_SO hero = AssetDatabase.LoadAssetAtPath<AllyHero_SO>(path);
if (hero != null)
{
heroes.Add(hero);
}
}
return heroes.ToArray();
}
#endif
return Resources.LoadAll<AllyHero_SO>("so/ally");
}
private static void TryAddEquipmentSkillGroupId(List<int> result, HashSet<int> dedupe, int groupId)
{
if (groupId <= 0 || result == null || dedupe == null || !dedupe.Add(groupId))
{
return;
}
result.Add(groupId);
}
public AllyLevelInfo GetEffectiveLevelForCurrentEXP()
@@ -190,17 +379,119 @@ public class AllyHero_SO : ScriptableObject
return sorted[unlockedTierIndex];
}
private AllyLevelInfo CloneLevelInfo(AllyLevelInfo source)
{
return new AllyLevelInfo
{
levelName = source.levelName,
levelID = source.levelID,
attack = source.attack,
maxHP = source.maxHP,
damageResistance = source.damageResistance,
maxMana = source.maxMana,
scoreEfficiency = source.scoreEfficiency,
requiredEXP = source.requiredEXP,
skill_slot_limited = source.skill_slot_limited,
manaGainGood = source.manaGainGood,
manaGainGreat = source.manaGainGreat,
manaGainPerfect = source.manaGainPerfect,
manaGainOnMiss = source.manaGainOnMiss,
damageMultiplierGood = source.damageMultiplierGood,
damageMultiplierGreat = source.damageMultiplierGreat,
damageMultiplierPerfect = source.damageMultiplierPerfect,
missHpLossBase = source.missHpLossBase
};
}
private int ApplyIntEquipmentBonus(int baseValue, equipmentSO.EquipmentStatTuning tuning, equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float percent = GetEquipmentPercentBonus(tuning, equipment, effectType);
float flat = GetEquipmentFlatBonus(equipment, effectType);
return Mathf.Max(0, Mathf.FloorToInt(baseValue * (1f + percent) + flat));
}
private float ApplyFloatEquipmentBonus(float baseValue, equipmentSO.EquipmentStatTuning tuning, equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float percent = GetEquipmentPercentBonus(tuning, equipment, effectType);
float flat = GetEquipmentFlatBonus(equipment, effectType);
return Mathf.Max(0f, (baseValue * (1f + percent)) + flat);
}
private float GetEquipmentPercentBonus(equipmentSO.EquipmentStatTuning tuning, equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float total = 0f;
if (tuning != null)
{
if (!Mathf.Approximately(tuning.basicGain, 0f))
{
total += tuning.basicGain;
total += Mathf.Max(0, equipment.level) * tuning.cultivationInterval;
}
}
if (equipment.enableTypeSameEffects && equipment.skillType == allyType)
{
total += SumEffectValues(equipment.typeSameEffects, effectType);
}
total += SumEffectValues(equipment.maxLevelEffects, effectType);
return total;
}
private float GetEquipmentFlatBonus(equipmentSO equipment, equipmentSO.EquipmentSpecialEffectType effectType)
{
float total = 0f;
total += SumEffectValues(equipment.specialEffects, effectType);
equipmentSO.EquipmentSpecialEffect[] normalizedIllusionEffects = equipment.GetNormalizedIllusionEffects();
total += SumEffectValues(normalizedIllusionEffects, effectType);
return total;
}
private static float SumEffectValues(equipmentSO.EquipmentSpecialEffect[] effects, equipmentSO.EquipmentSpecialEffectType effectType)
{
if (effects == null || effects.Length == 0)
{
return 0f;
}
float total = 0f;
for (int i = 0; i < effects.Length; i++)
{
equipmentSO.EquipmentSpecialEffect effect = effects[i];
if (effect == null || effect.effectType != effectType)
{
continue;
}
total += effect.value;
}
return total;
}
[System.Serializable]
private class EquippedSkillGroupIDsPayload
{
public int[] equippedSkillGroupIDs;
}
[System.Serializable]
private class EquippedEquipmentPayload
{
public string equippedEquipmentId;
}
private string GetEquippedSkillsPrefsKey()
{
return "ally_equippedSkillGroupIDs_" + ally_heroID;
}
private string GetEquippedEquipmentPrefsKey()
{
return "ally_equippedEquipment_" + ally_heroID;
}
public void SaveEquippedSkillsToLocal()
{
var payload = new EquippedSkillGroupIDsPayload { equippedSkillGroupIDs = equippedSkillGroupIDs ?? new int[0] };
@@ -220,6 +511,136 @@ public class AllyHero_SO : ScriptableObject
equippedSkillGroupIDs = payload.equippedSkillGroupIDs ?? new int[0];
}
public void SaveEquippedEquipmentToLocal()
{
equippedEquipmentId = equippedEquipment != null ? equippedEquipment.name : string.Empty;
var payload = new EquippedEquipmentPayload { equippedEquipmentId = equippedEquipmentId ?? string.Empty };
string json = JsonUtility.ToJson(payload);
PlayerPrefs.SetString(GetEquippedEquipmentPrefsKey(), json);
PlayerPrefs.Save();
}
public void LoadEquippedEquipmentFromLocal()
{
string key = GetEquippedEquipmentPrefsKey();
if (!PlayerPrefs.HasKey(key))
{
return;
}
string json = PlayerPrefs.GetString(key, string.Empty);
if (string.IsNullOrEmpty(json))
{
return;
}
EquippedEquipmentPayload payload = JsonUtility.FromJson<EquippedEquipmentPayload>(json);
if (payload == null)
{
return;
}
equippedEquipmentId = payload.equippedEquipmentId ?? string.Empty;
equippedEquipment = ResolveEquippedEquipmentById(equippedEquipmentId);
}
public void SetEquippedEquipment(equipmentSO equipment, bool persist = true)
{
equippedEquipment = equipment;
equippedEquipmentId = equipment != null ? equipment.name : string.Empty;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
if (persist)
{
UnityEditor.AssetDatabase.SaveAssets();
}
}
#endif
if (persist)
{
SaveEquippedEquipmentToLocal();
}
}
public equipmentSO GetEquippedEquipmentResolved()
{
if (equippedEquipment != null)
{
return equippedEquipment;
}
if (string.IsNullOrWhiteSpace(equippedEquipmentId))
{
return null;
}
equippedEquipment = ResolveEquippedEquipmentById(equippedEquipmentId);
return equippedEquipment;
}
public void ClearEquippedEquipment()
{
equippedEquipment = null;
equippedEquipmentId = string.Empty;
PlayerPrefs.DeleteKey(GetEquippedEquipmentPrefsKey());
PlayerPrefs.Save();
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
UnityEditor.AssetDatabase.SaveAssets();
}
#endif
}
private equipmentSO ResolveEquippedEquipmentById(string equipmentId)
{
if (string.IsNullOrWhiteSpace(equipmentId))
{
return null;
}
#if UNITY_EDITOR
if (!Application.isPlaying)
{
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:equipmentSO", new[] { "Assets/Resources/so/uEquip" });
for (int i = 0; i < guids.Length; i++)
{
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
equipmentSO equipment = UnityEditor.AssetDatabase.LoadAssetAtPath<equipmentSO>(path);
if (equipment != null && equipment.name == equipmentId)
{
return equipment;
}
}
}
#endif
equipmentSO[] runtimeEquipments = Resources.LoadAll<equipmentSO>("so/uEquip");
for (int i = 0; i < runtimeEquipments.Length; i++)
{
if (runtimeEquipments[i] != null && runtimeEquipments[i].name == equipmentId)
{
return runtimeEquipments[i];
}
}
var generatedEquipments = Bansonic.equipmentGenerator.GetRuntimeGeneratedEquipments();
for (int i = 0; i < generatedEquipments.Count; i++)
{
if (generatedEquipments[i] != null && generatedEquipments[i].name == equipmentId)
{
return generatedEquipments[i];
}
}
return null;
}
public void ClearEquippedSkills()
{
equippedSkillGroupIDs = Array.Empty<int>();
+5 -1
View File
@@ -1,4 +1,5 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "PlayerDefaultSO", menuName = "SO_Data/PlayerSO")]
public class Player_SO : ScriptableObject
@@ -10,6 +11,9 @@ public class Player_SO : ScriptableObject
[SerializeField] private int player_coins;
[SerializeField] private int player_material;
[Header("levels")]
[SerializeField] private int player_currentLevel;
[Header("exp bottles")]
[SerializeField] private int commonExpBottle78001;
[SerializeField] private int mediumExpBottle78002;
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ddf7e9a712979354b814ca04dc00c587
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
using UnityEngine;
using UnityEngine.UI;
public class player_level_skills : MonoBehaviour
{
//[Header("player level skill list")]
//[SerializeField]
enum userSKILL
{
cameraHeight,
display_all_unlockSkills,
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0114995ee6f6bf44db3c8dd8fb1ed615
+34 -9
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@@ -47,6 +47,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
[SerializeField] Ease prefab_Enter_Ease = Ease.OutCubic;
[SerializeField] Button button_Idol;
[SerializeField] GameObject ui_Panel_Idol;
[SerializeField] Button button_Select_Music;
[SerializeField] string ui_Select_Music_Scene_Name= "selectYourSongFirst";
@@ -275,7 +276,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
yield return null;
// 读取保存的角色 ID 并初始化索引
// 璇诲彇淇濆瓨鐨勮鑹?ID 骞跺垵濮嬪寲绱㈠紩
int savedHeroID = PlayerPrefs.GetInt("SelectedMainHeroID", -1);
if (savedHeroID != -1)
{
@@ -314,14 +315,14 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
button_Story.onClick.AddListener(
() =>
{
gNotice.warning.display("此版本未开放该功能");
gNotice.warning.display("姝ょ増鏈湭寮€鏀捐鍔熻兘");
//Try_Open_Panel(ui_Panel_Story);
});
if (button_Idol != null)
button_Idol.onClick.AddListener(
() =>
{
gNotice.warning.display("此版本未开放该功能");
Toggle_Panel(ui_Panel_Idol);
});
if (music_Player != null && music_Player.Button_List != null)
music_Player.Button_List.onClick.AddListener(
@@ -356,6 +357,30 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
return true;
}
void Toggle_Panel(GameObject panel)
{
if (panel == null)
{
return;
}
if (panel.activeSelf)
{
panel.SetActive(false);
return;
}
if (panel == ui_Panel_Setting)
{
UI_SettingsPanelEnterAnim enterAnim = panel.GetComponent<UI_SettingsPanelEnterAnim>();
if (enterAnim == null)
enterAnim = panel.AddComponent<UI_SettingsPanelEnterAnim>();
enterAnim.enabled = true;
}
panel.SetActive(true);
}
GameObject Try_Open_Prefab(GameObject prefab, ref GameObject instance)
{
if (prefab == null) return null;
@@ -636,7 +661,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
{
hover = startButton.gameObject.AddComponent<UI_StartButtonHover>();
}
hover.ApplyTexts("执行任务", "Start", "意志复演", "REITERATION OF WILL");
hover.ApplyTexts("鎵ц浠诲姟", "Start", "鎰忓織澶嶆紨", "REITERATION OF WILL");
}
void Setup_RightButtons_Hover()
{
@@ -771,7 +796,7 @@ public class UI_Panel_Main : Singleton_Mono<UI_Panel_Main>
c_Character_Illustration_Anim = StartCoroutine(C_Character_Illustration_Anim(canvasGroup, () =>
{
image_Character.sprite = sprite;
// image_Character.SetNativeSize(); // 注释掉 SetNativeSize 以防改变目标图片大小
// image_Character.SetNativeSize(); // 娉ㄩ噴鎺?SetNativeSize 浠ラ槻鏀瑰彉鐩爣鍥剧墖澶у皬
}));
}
@@ -3562,9 +3587,9 @@ public class UI_JiantouHover : MonoBehaviour, IPointerEnterHandler, IPointerExit
public class UI_StartButtonHover : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
string offCn = "执行任务";
string offCn = "鎵ц浠诲姟";
string offEn = "Start";
string onCn = "意志复演";
string onCn = "鎰忓織澶嶆紨";
string onEn = "REITERATION OF WILL";
float charInterval = 0.03f;
Text cnText;
@@ -3719,7 +3744,7 @@ public class UI_StartButtonHover : MonoBehaviour, IPointerEnterHandler, IPointer
char[] filler = new char[remain];
for (int r = 0; r < remain; r++)
{
filler[r] = useDot ? '' : randomChars[(i + r) % randomChars.Length];
filler[r] = useDot ? '.' : randomChars[(i + r) % randomChars.Length];
}
suffix = new string(filler);
}
@@ -1810,6 +1810,126 @@ MonoBehaviour:
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &1712207995943947140
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8719722111862639519}
- component: {fileID: 5301586153983064217}
- component: {fileID: 8236121141998032047}
- component: {fileID: 7063352251955770202}
m_Layer: 5
m_Name: Button_bag
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8719722111862639519
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712207995943947140}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3304862671657828245}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5301586153983064217
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712207995943947140}
m_CullTransparentMesh: 1
--- !u!114 &8236121141998032047
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712207995943947140}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 687b777ccb26c814f81a7c11655dd300, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 3
--- !u!114 &7063352251955770202
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1712207995943947140}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 8236121141998032047}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &1747558998138543452
GameObject:
m_ObjectHideFlags: 0
@@ -4554,7 +4674,7 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 521.45, y: -43}
m_AnchoredPosition: {x: 589.88, y: -43}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0, y: 0}
--- !u!114 &786756074167547563
@@ -4948,6 +5068,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
player_SO: {fileID: 11400000, guid: a59c019c71199384eaac0703299047c8, type: 2}
playerCoins_legacy: {fileID: 7376147910533281946}
player_mmrFragment: {fileID: 1718535194614905540}
putPrefabsHere: {fileID: 6773918330215714056}
settings_launch: {fileID: 865676738076373517}
userInfo_launch: {fileID: 1502937460034745443}
@@ -4956,6 +5077,7 @@ MonoBehaviour:
email_display: {fileID: 7520904819348953829}
notice_display: {fileID: 920723299105391293}
market_launch: {fileID: 5379967691629323644}
button_userBag: {fileID: 7063352251955770202}
userGuideButton: {fileID: 6945238783669072533}
guideDisplayImage: {fileID: 2438452334951697137}
preIMGb: {fileID: 7091982211545804283}
@@ -4989,6 +5111,7 @@ MonoBehaviour:
userInfo_prefab: {fileID: 5155954718781159675, guid: 18f4d2367ef2dff429599f1580fd6bad, type: 3}
email_prefab: {fileID: 3756075665884320709, guid: 41f4ea31b64f18d48aeef21b79930a6c, type: 3}
notice_prefab: {fileID: 8527835049849151044, guid: 06bb6d3a45697374ab4f5ff36356d138, type: 3}
userBag_prefab: {fileID: 2826375232361138358, guid: 9e0039d3a46af1c458d7f6e2fc72bab2, type: 3}
back_navButton: {fileID: 0}
home_navButton: {fileID: 0}
settings_navButton: {fileID: 0}
@@ -6816,6 +6939,7 @@ RectTransform:
- {fileID: 5227902933222757190}
- {fileID: 1387828356511308751}
- {fileID: 263900830868465045}
- {fileID: 8719722111862639519}
m_Father: {fileID: 6434860682258445909}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -6846,7 +6970,7 @@ MonoBehaviour:
m_CellSize: {x: 50, y: 50}
m_Spacing: {x: 20, y: 20}
m_Constraint: 1
m_ConstraintCount: 5
m_ConstraintCount: 6
--- !u!1 &7164294745953147232
GameObject:
m_ObjectHideFlags: 0
@@ -7180,14 +7304,14 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.24705882, g: 0.2901961, b: 0.3372549, a: 1}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 8442432492524575577, guid: 3a26cd9302ce6db45a1139fc2bb26b66, type: 3}
m_Sprite: {fileID: 21300000, guid: 27adfaee3f8bee14280e634021049f50, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
@@ -20,6 +20,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
[Header("player unique so")]
public Player_SO player_SO;
public Text playerCoins_legacy;
public Text player_mmrFragment;
public Text player_rks;
[Header("put prefabs here")]
public GameObject putPrefabsHere;
[Header("son buttons")]
@@ -30,6 +32,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
public Button email_display;
public Button notice_display;
public Button market_launch;
public Button button_userBag;
[Header("User Guide")]
[SerializeField] private Button userGuideButton;
@@ -62,6 +65,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
public GameObject userInfo_prefab;
public GameObject email_prefab;
public GameObject notice_prefab;
public GameObject userBag_prefab;
[Header("New Back Buttons")]
[SerializeField] private Button back_navButton;
@@ -102,6 +106,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private UnityAction navSettingsAction;
private UnityAction navGuideAction;
private UnityAction instantiateMarket;
private UnityAction instantiateUserBag;
// cached reference to the settings instance to ensure only one exists
private GameObject settingsInstance;
@@ -110,6 +115,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private GameObject showLevelInstance;
private GameObject emailInstance;
private GameObject noticeInstance;
private GameObject userBagInstance;
private CanvasGroup musicPicGroup;
private Coroutine musicPicFade;
private bool musicPicVisible = true;
@@ -118,6 +124,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private bool lastOverlayPanelsVisibilityState;
private readonly Dictionary<string, int> guideIndexByScene = new Dictionary<string, int>();
private string currentGuideScene = string.Empty;
private static btmandtopController activeInstance;
private static readonly List<string> sceneHistory = new List<string>();
private static bool sceneHistoryHooked = false;
@@ -125,6 +132,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
void Awake()
{
activeInstance = this;
EnsureMusicPicRoot();
InitializeSceneHistoryIfNeeded();
}
@@ -133,7 +141,12 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
{
PlayerEconomyLedger.EnsureInstance().AttachPlayerData(player_SO);
PlayerEconomyLedger.EnsureInstance().OnCoinsChanged += HandleCoinsChanged;
PlayerEconomyLedger.EnsureInstance().OnMaterialChanged += HandleMaterialChanged;
PlayerRksService.EnsureLoaded(player_SO);
PlayerRksService.OnRksChanged += HandleRksChanged;
UpdatePlayerCoinsLegacyText(PlayerEconomyLedger.EnsureInstance().GetCoins());
UpdatePlayerMmrFragmentText(PlayerEconomyLedger.EnsureInstance().GetMaterial());
UpdatePlayerRksText(PlayerRksService.GetBestOverallRks(player_SO));
if (button_Music == null)
{
@@ -156,6 +169,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
// instantiateNotice = () => ShowNoticePrefab();
navGuideAction = () => ToggleGuideDisplay();
instantiateMarket = () => ShowStorePrefab();
instantiateUserBag = () => ShowUserBagPrefab();
if (settings_launch != null)
settings_launch.onClick.AddListener(instantiateSettings);
@@ -171,6 +185,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
notice_display.onClick.AddListener(instantiateNotice);
if (market_launch != null)
market_launch.onClick.AddListener(instantiateMarket);
if (button_userBag != null)
button_userBag.onClick.AddListener(instantiateUserBag);
if (userGuideButton != null)
userGuideButton.onClick.AddListener(navGuideAction);
if (preIMGb != null)
@@ -473,6 +489,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
notice_display.onClick.RemoveListener(instantiateNotice);
if (market_launch != null)
market_launch.onClick.RemoveListener(instantiateMarket);
if (button_userBag != null)
button_userBag.onClick.RemoveListener(instantiateUserBag);
if (userGuideButton != null)
userGuideButton.onClick.RemoveListener(navGuideAction);
if (preIMGb != null)
@@ -493,7 +511,17 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
settings_navButton.onClick.RemoveListener(navSettingsAction);
if (PlayerEconomyLedger.Instance != null)
{
PlayerEconomyLedger.Instance.OnCoinsChanged -= HandleCoinsChanged;
PlayerEconomyLedger.Instance.OnMaterialChanged -= HandleMaterialChanged;
}
PlayerRksService.OnRksChanged -= HandleRksChanged;
if (ReferenceEquals(activeInstance, this))
{
activeInstance = null;
}
}
private void HandleCoinsChanged(int coinAmount)
@@ -501,6 +529,16 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
UpdatePlayerCoinsLegacyText(coinAmount);
}
private void HandleMaterialChanged(int materialAmount)
{
UpdatePlayerMmrFragmentText(materialAmount);
}
private void HandleRksChanged(float rksAmount)
{
UpdatePlayerRksText(rksAmount);
}
private void UpdatePlayerCoinsLegacyText(int coinAmount)
{
if (playerCoins_legacy != null)
@@ -509,6 +547,46 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
}
}
private void UpdatePlayerMmrFragmentText(int fragmentAmount)
{
if (player_mmrFragment != null)
{
player_mmrFragment.text = fragmentAmount.ToString();
}
}
private void UpdatePlayerRksText(float rksAmount)
{
if (player_rks == null)
{
player_rks = FindLegacyTextByLiteral("player_rks");
}
if (player_rks != null)
{
player_rks.text = rksAmount.ToString("F2");
}
}
private Text FindLegacyTextByLiteral(string literal)
{
if (string.IsNullOrEmpty(literal))
{
return null;
}
Text[] texts = GetComponentsInChildren<Text>(true);
for (int i = 0; i < texts.Length; i++)
{
if (texts[i] != null && texts[i].text == literal)
{
return texts[i];
}
}
return null;
}
private void ToggleGuideDisplay()
{
if (guideDisplayImage == null)
@@ -751,7 +829,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
|| (storeInstance != null && storeInstance.activeInHierarchy)
|| (showLevelInstance != null && showLevelInstance.activeInHierarchy)
|| (emailInstance != null && emailInstance.activeInHierarchy)
|| (noticeInstance != null && noticeInstance.activeInHierarchy);
|| (noticeInstance != null && noticeInstance.activeInHierarchy)
|| (userBagInstance != null && userBagInstance.activeInHierarchy);
}
private void BroadcastOverlayPanelsVisibility()
@@ -764,8 +843,12 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private void ShowLevelPrefab()
{
bool opened = ShowPrefab(showLevel_prefab, ref showLevelInstance);
if (opened)
if (ToggleIfAlreadyOpen(ref showLevelInstance))
{
return;
}
if (OpenPrefab(showLevel_prefab, ref showLevelInstance))
{
CloseInfoPanels(showLevelInstance);
}
@@ -773,8 +856,12 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private void ShowStorePrefab()
{
bool opened = ShowPrefab(store_prefab, ref storeInstance);
if (opened)
if (ToggleIfAlreadyOpen(ref storeInstance))
{
return;
}
if (OpenPrefab(store_prefab, ref storeInstance))
{
CloseInfoPanels(storeInstance);
}
@@ -782,8 +869,12 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private void ShowEmailPrefab()
{
bool opened = ShowPrefab(email_prefab, ref emailInstance);
if (opened)
if (ToggleIfAlreadyOpen(ref emailInstance))
{
return;
}
if (OpenPrefab(email_prefab, ref emailInstance))
{
CloseInfoPanels(emailInstance);
}
@@ -791,14 +882,48 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private void ShowNoticePrefab()
{
bool opened = ShowPrefab(notice_prefab, ref noticeInstance);
if (opened)
if (ToggleIfAlreadyOpen(ref noticeInstance))
{
return;
}
if (OpenPrefab(notice_prefab, ref noticeInstance))
{
CloseInfoPanels(noticeInstance);
}
}
private bool ShowPrefab(GameObject prefab, ref GameObject instance)
private void ShowUserBagPrefab()
{
if (ToggleIfAlreadyOpen(ref userBagInstance))
{
return;
}
if (OpenPrefab(userBag_prefab, ref userBagInstance))
{
CloseInfoPanels(userBagInstance);
}
}
private bool ToggleIfAlreadyOpen(ref GameObject instance)
{
if (instance == null)
{
return false;
}
if (!instance.activeInHierarchy)
{
return false;
}
instance.SetActive(false);
BroadcastOverlayPanelsVisibility();
return true;
}
private bool OpenPrefab(GameObject prefab, ref GameObject instance)
{
if (prefab == null || putPrefabsHere == null) return false;
@@ -816,14 +941,16 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
}
}
bool shouldOpen = !instance.activeSelf;
instance.SetActive(shouldOpen);
if (!shouldOpen)
if (instance == null)
{
BroadcastOverlayPanelsVisibility();
return false;
}
if (!instance.activeSelf)
{
instance.SetActive(true);
}
PlacePanelBelowSettings(instance);
BroadcastOverlayPanelsVisibility();
return true;
@@ -868,6 +995,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
CloseInstance(ref showLevelInstance, keep);
CloseInstance(ref emailInstance, keep);
CloseInstance(ref noticeInstance, keep);
CloseInstance(ref userBagInstance, keep);
}
private void CloseInstance(ref GameObject instance, GameObject keep)
@@ -915,6 +1043,18 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
catch { }
}
public static void RefreshOverlayVisibilityState()
{
if (activeInstance != null)
{
activeInstance.BroadcastOverlayPanelsVisibility();
return;
}
CurrentOverlayPanelsVisible = false;
GlobalOverlayPanelVisibilityChanged?.Invoke(false);
}
private void InstantiatePrefab(GameObject prefab)
{
if (prefab != null && putPrefabsHere != null)
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
public static class EquipmentConsumableCatalog
@@ -6,7 +6,9 @@ public static class EquipmentConsumableCatalog
private static readonly EquipmentConsumableDescriptor[] Descriptors =
{
new EquipmentConsumableDescriptor(EquipmentConsumableKind.Material78101, "eqc_78101", "装备升级材料78101"),
new EquipmentConsumableDescriptor(EquipmentConsumableKind.Material78111, "eqc_78111", "装备突破材料78111")
new EquipmentConsumableDescriptor(EquipmentConsumableKind.Material78111, "eqc_78111", "装备突破材料78111"),
new EquipmentConsumableDescriptor(EquipmentConsumableKind.Material78121, "eqc_78121", "装备洗炼材料78121"),
new EquipmentConsumableDescriptor(EquipmentConsumableKind.Material78131, "eqc_78131", "装备登顶材料78131")
};
private static readonly Dictionary<EquipmentConsumableKind, EquipmentConsumableDescriptor> ByKind = BuildByKind();
@@ -1,5 +1,7 @@
public enum EquipmentConsumableKind
{
Material78101,
Material78111
Material78111,
Material78121,
Material78131
}
@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class PlayerRksSaveData
{
public float bestOverallRks;
}
public static class PlayerRksService
{
private const string SaveCategory = "player_rks";
private const string SaveKey = "overall";
private const float MaxScorePerChart = 1000000f;
private const int TopChartCount = 20;
public static event Action<float> OnRksChanged;
private static bool loaded;
private static float bestOverallRks;
public static void EnsureLoaded(Player_SO player = null)
{
if (loaded)
{
SyncPlayerSo(player);
return;
}
loaded = true;
bestOverallRks = 0f;
PlayerRksSaveData saveData;
if (SecureSaveVault.TryLoadJson(SaveCategory, SaveKey, out saveData) && saveData != null)
{
bestOverallRks = Mathf.Max(0f, saveData.bestOverallRks);
}
else if (player != null)
{
bestOverallRks = Mathf.Max(0f, player.URankingScore);
}
SyncPlayerSo(player);
}
public static float GetBestOverallRks(Player_SO player = null)
{
EnsureLoaded(player);
return bestOverallRks;
}
public static float GetNormalizedOverallRks100(Player_SO player = null)
{
EnsureLoaded(player);
float maxConstant = Mathf.Max(1f, GetMaxChartConstant());
return Mathf.Clamp(bestOverallRks / maxConstant * 100f, 0f, 100f);
}
public static float RefreshAndPersist(Player_SO player = null)
{
EnsureLoaded(player);
float calculated = CalculateOverallRksRaw();
if (calculated > bestOverallRks + 0.0001f)
{
bestOverallRks = calculated;
SecureSaveVault.SaveJson(SaveCategory, SaveKey, new PlayerRksSaveData { bestOverallRks = bestOverallRks });
SyncPlayerSo(player);
OnRksChanged?.Invoke(bestOverallRks);
}
else
{
SyncPlayerSo(player);
}
return bestOverallRks;
}
public static void ClearPersistentState(Player_SO player = null)
{
loaded = true;
bestOverallRks = 0f;
SecureSaveVault.Delete(SaveCategory, SaveKey);
SyncPlayerSo(player);
OnRksChanged?.Invoke(bestOverallRks);
}
private static void SyncPlayerSo(Player_SO player)
{
Player_SO target = player != null ? player : LoadDefaultPlayerSo();
if (target != null && !Mathf.Approximately(target.URankingScore, bestOverallRks))
{
target.SetURankingScore(bestOverallRks);
}
}
private static Player_SO LoadDefaultPlayerSo()
{
Player_SO[] players = Resources.LoadAll<Player_SO>(string.Empty);
if (players == null)
{
return null;
}
for (int i = 0; i < players.Length; i++)
{
if (players[i] != null)
{
return players[i];
}
}
return null;
}
private static float CalculateOverallRksRaw()
{
SongData[] songs = Resources.LoadAll<SongData>(string.Empty);
if (songs == null || songs.Length == 0)
{
return 0f;
}
List<float> chartRksValues = new List<float>(64);
for (int i = 0; i < songs.Length; i++)
{
SongData song = songs[i];
if (song == null || song.chartFiles == null)
{
continue;
}
for (int j = 0; j < song.chartFiles.Count; j++)
{
ChartFileEntry entry = song.chartFiles[j];
if (entry == null)
{
continue;
}
int bestTotal = entry.totalPersonalRecordForThisDifficulty;
if (bestTotal <= 0)
{
bestTotal = song.GetPersonalRecord(entry.difficulty) + song.GetIdolRecord(entry.difficulty);
}
float chartRks = CalculateChartRks(entry.difficultyLEVEL, bestTotal);
if (chartRks > 0f)
{
chartRksValues.Add(chartRks);
}
}
}
if (chartRksValues.Count == 0)
{
return 0f;
}
chartRksValues.Sort((a, b) => b.CompareTo(a));
int count = Mathf.Min(TopChartCount, chartRksValues.Count);
float sum = 0f;
for (int i = 0; i < count; i++)
{
sum += chartRksValues[i];
}
return sum / Mathf.Max(1, count);
}
private static float GetMaxChartConstant()
{
SongData[] songs = Resources.LoadAll<SongData>(string.Empty);
float maxConstant = 0f;
if (songs == null)
{
return 1f;
}
for (int i = 0; i < songs.Length; i++)
{
SongData song = songs[i];
if (song == null || song.chartFiles == null)
{
continue;
}
for (int j = 0; j < song.chartFiles.Count; j++)
{
ChartFileEntry entry = song.chartFiles[j];
if (entry != null && entry.difficultyLEVEL > maxConstant)
{
maxConstant = entry.difficultyLEVEL;
}
}
}
return Mathf.Max(1f, maxConstant);
}
private static float CalculateChartRks(float chartConstant, int totalScore)
{
if (chartConstant <= 0f || totalScore <= 0)
{
return 0f;
}
float scoreRate = Mathf.Clamp01(totalScore / MaxScorePerChart);
float quality = Mathf.Pow(scoreRate, 1.35f);
return chartConstant * quality;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: de7dfff9eb545b94b906ac8a9690132f
@@ -53,7 +53,13 @@ public static class StoreExpBottlePurchaseService
}
var primaryCost = itemSO.costRequirements[0];
if (primaryCost.currencyType != storeItemSO.CurrencyType.coins)
if (itemSO.associatedSelectableEquipmentRewardSource != null)
{
failureMessage = "该物品需要通过选择器购买";
return false;
}
if (!IsSupportedCurrency(primaryCost.currencyType))
{
failureMessage = "不可购买";
return false;
@@ -89,13 +95,13 @@ public static class StoreExpBottlePurchaseService
PlayerEconomyLedger.EnsureInstance().AttachPlayerData(playerData);
StoreOwnershipLedger.EnsureInstance().InitializeIfNeeded();
if (!PlayerEconomyLedger.EnsureInstance().HasEnoughCoins(totalCost))
if (!HasEnoughCurrency(primaryCost.currencyType, totalCost))
{
failureMessage = "货币不足";
return false;
}
if (!PlayerEconomyLedger.EnsureInstance().TrySpendCoins(totalCost))
if (!TrySpendCurrency(primaryCost.currencyType, totalCost))
{
failureMessage = "货币不足";
return false;
@@ -103,7 +109,7 @@ public static class StoreExpBottlePurchaseService
if (!GrantPurchasedItem(playerData, itemSO, grantedCount, out failureMessage))
{
PlayerEconomyLedger.EnsureInstance().AddCoins(totalCost);
RefundCurrency(primaryCost.currencyType, totalCost);
return false;
}
@@ -328,4 +334,49 @@ public static class StoreExpBottlePurchaseService
return false;
}
private static bool IsSupportedCurrency(storeItemSO.CurrencyType currencyType)
{
return currencyType == storeItemSO.CurrencyType.coins
|| currencyType == storeItemSO.CurrencyType.material;
}
private static bool HasEnoughCurrency(storeItemSO.CurrencyType currencyType, int amount)
{
switch (currencyType)
{
case storeItemSO.CurrencyType.coins:
return PlayerEconomyLedger.EnsureInstance().HasEnoughCoins(amount);
case storeItemSO.CurrencyType.material:
return PlayerEconomyLedger.EnsureInstance().HasEnoughMaterial(amount);
default:
return false;
}
}
private static bool TrySpendCurrency(storeItemSO.CurrencyType currencyType, int amount)
{
switch (currencyType)
{
case storeItemSO.CurrencyType.coins:
return PlayerEconomyLedger.EnsureInstance().TrySpendCoins(amount);
case storeItemSO.CurrencyType.material:
return PlayerEconomyLedger.EnsureInstance().TrySpendMaterial(amount);
default:
return false;
}
}
private static void RefundCurrency(storeItemSO.CurrencyType currencyType, int amount)
{
switch (currencyType)
{
case storeItemSO.CurrencyType.coins:
PlayerEconomyLedger.EnsureInstance().AddCoins(amount);
break;
case storeItemSO.CurrencyType.material:
PlayerEconomyLedger.EnsureInstance().AddMaterial(amount);
break;
}
}
}
@@ -492,6 +492,7 @@ public class settlementController : MonoBehaviour
settlementHistoryRecorded = false;
settlementHeroStatsRecorded = false;
OnSettlementCompleted?.Invoke();
equipSmelt.NotifySettlementCompleted();
// Documentation text normalized.
InitializeSettlementCanvas();
@@ -663,6 +664,7 @@ public class settlementController : MonoBehaviour
int pm = sm != null ? sm.allSum_pmScore : 0;
int idol = sm != null ? sm.allSum_idolScore : 0;
thisSong_so.ApplySettlementResult(diff, pm, idol, _1000000);
PlayerRksService.RefreshAndPersist();
}
if (personalRecord_Text != null)
@@ -708,6 +710,11 @@ public class settlementController : MonoBehaviour
private void TryRecordRecentPlayHistory(int noteCountRaw)
{
if (GameConfig.autoPlayEnabled)
{
return;
}
if (settlementHistoryRecorded)
{
return;
+12
View File
@@ -202,6 +202,18 @@ public class userSettings : MonoBehaviour
AllyHero_SO.ClearAllEquippedSkills();
yield return null;
equipSmelt.ClearPersistentState();
yield return null;
PlayerPrefs.DeleteKey("bansonic_equipment_next_id_v1");
yield return null;
Bansonic.equipmentGenerator.ClearRuntimeGeneratedPersistence();
yield return null;
PlayerRksService.ClearPersistentState();
yield return null;
#if UNITY_EDITOR
AssetDatabase.SaveAssets();
#endif