Files
bansonic_beta_main/Assets/scripts/_runtimecache/PlayerSkillService.cs
T

653 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using Bansonic;
using UnityEngine;
using UnityEngine.SceneManagement;
[Serializable]
public class PlayerSkillSaveData
{
public int selectedSkillIndex = -1;
public int postMatchRewardCounter = 0;
public int skillSwitchCooldownRemainingMatches = 0;
}
public sealed class PlayerSkillService : MonoBehaviour
{
private const string SaveCategory = "player_skills";
private const string SaveKey = "runtime";
private const string SkillAssetEditorPath = "Assets/playerInfoDisplay/uls/ulsSO.asset";
private const int MemoryBuySkillIndex = 1;
private const int MemorySellSkillIndex = 2;
private const int PostMatchEquipmentSkillIndex = 4;
private const int SmeltDiscountSkillIndex = 5;
private const int ScoreToMaterialSkillIndex = 6;
private const int RevealLockedIdolSkillsSkillIndex = 7;
private const int SpendRefundSkillIndex = 8;
private const int ScoreToCoinsSkillIndex = 9;
private const int CameraAngleSkillIndex = 10;
private const int MemoryBuyStoreItemId = 799001;
private const int MemorySellStoreItemId = 799002;
private const int RandomMemoryStoreItemId = 78141;
private const float SpendRefundPerOwnedHero = 0.005f;
private const float MaxSpendRefundRate = 0.05f;
public static PlayerSkillService Instance { get; private set; }
private bool initialized;
private PlayerSkillSaveData saveData;
private userLevel_skills_SO registeredSkillAsset;
private storeItemSO cachedRandomMemoryRewardItem;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Bootstrap()
{
EnsureInstance();
}
public static PlayerSkillService EnsureInstance()
{
if (Instance != null)
{
return Instance;
}
GameObject host = new GameObject("__runtime_player_skill_bridge");
host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
DontDestroyOnLoad(host);
Instance = host.AddComponent<PlayerSkillService>();
return Instance;
}
public static void RegisterSkillAsset(userLevel_skills_SO skillAsset)
{
EnsureInstance().RegisterAssetInternal(skillAsset);
}
public static bool IsSkillEnabled(int skillIndex)
{
return EnsureInstance().IsSkillEnabledInternal(skillIndex);
}
public static int GetSelectedSkillIndex()
{
return EnsureInstance().GetSelectedSkillIndexInternal();
}
public static int GetSkillSwitchCooldownRemainingMatches()
{
return EnsureInstance().GetSkillSwitchCooldownRemainingMatchesInternal();
}
public static void ConfirmSkillSelection(userLevel_skills_SO skillAsset, int selectedIndex)
{
EnsureInstance().ConfirmSkillSelectionInternal(skillAsset, selectedIndex);
}
public static int ModifySmeltCoinCost(int baseCost)
{
if (!IsSkillEnabled(SmeltDiscountSkillIndex))
{
return Mathf.Max(0, baseCost);
}
return Mathf.CeilToInt(Mathf.Max(0, baseCost) * 0.5f);
}
public static int GetCoinRefundAmount(int spentCoins)
{
return EnsureInstance().GetRefundAmount(spentCoins);
}
public static int GetMaterialRefundAmount(int spentMaterial)
{
return EnsureInstance().GetRefundAmount(spentMaterial);
}
public static void NotifySettlementCompleted(int idolScore)
{
EnsureInstance().HandleSettlementCompletedInternal(idolScore);
}
public static void AppendDynamicStoreItems(List<storeItemSO> target, Sprite coinSprite, Sprite materialSprite)
{
EnsureInstance().AppendDynamicStoreItemsInternal(target, coinSprite, materialSprite);
}
public static bool IsPlayerSkillStoreItem(storeItemSO item)
{
return item != null && (item.itemID == MemoryBuyStoreItemId || item.itemID == MemorySellStoreItemId);
}
public static bool TryPurchasePlayerSkillStoreItem(storeItemSO item, int amount, out string failureMessage, out gItemGet.ItemEntry rewardEntry)
{
return EnsureInstance().TryPurchasePlayerSkillStoreItemInternal(item, amount, out failureMessage, out rewardEntry);
}
public static void ClearPersistentState()
{
EnsureInstance().ClearPersistentStateInternal();
}
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeIfNeeded();
}
private void OnEnable()
{
SceneManager.sceneLoaded += HandleSceneLoaded;
}
private void OnDisable()
{
SceneManager.sceneLoaded -= HandleSceneLoaded;
}
private void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
SaveNow();
}
}
private void OnApplicationQuit()
{
SaveNow();
}
private void InitializeIfNeeded()
{
if (initialized)
{
return;
}
initialized = true;
if (!SecureSaveVault.TryLoadJson(SaveCategory, SaveKey, out saveData) || saveData == null)
{
saveData = new PlayerSkillSaveData();
}
ResolveSkillAssetIfNeeded();
SyncSelectionFromAsset();
ApplySceneBindings();
}
private void RegisterAssetInternal(userLevel_skills_SO skillAsset)
{
InitializeIfNeeded();
if (skillAsset == null)
{
return;
}
registeredSkillAsset = skillAsset;
SyncSelectionFromAsset();
ApplySceneBindings();
}
private bool IsSkillEnabledInternal(int skillIndex)
{
InitializeIfNeeded();
ResolveSkillAssetIfNeeded();
SyncSelectionFromAsset();
return saveData != null && saveData.selectedSkillIndex == skillIndex;
}
private int GetSelectedSkillIndexInternal()
{
InitializeIfNeeded();
ResolveSkillAssetIfNeeded();
SyncSelectionFromAsset();
return saveData != null ? saveData.selectedSkillIndex : -1;
}
private int GetSkillSwitchCooldownRemainingMatchesInternal()
{
InitializeIfNeeded();
return saveData != null ? Mathf.Max(0, saveData.skillSwitchCooldownRemainingMatches) : 0;
}
private int GetRefundAmount(int spendAmount)
{
if (spendAmount <= 0 || !IsSkillEnabledInternal(SpendRefundSkillIndex))
{
return 0;
}
float refundRate = Mathf.Min(GetOwnedHeroCount() * SpendRefundPerOwnedHero, MaxSpendRefundRate);
if (refundRate <= 0f)
{
return 0;
}
return Mathf.FloorToInt(spendAmount * refundRate);
}
private void HandleSettlementCompletedInternal(int idolScore)
{
InitializeIfNeeded();
ResolveSkillAssetIfNeeded();
SyncSelectionFromAsset();
if (saveData.skillSwitchCooldownRemainingMatches > 0)
{
saveData.skillSwitchCooldownRemainingMatches = Mathf.Max(0, saveData.skillSwitchCooldownRemainingMatches - 1);
}
List<gItemGet.ItemEntry> grantedEntries = new List<gItemGet.ItemEntry>();
if (IsSkillEnabledInternal(PostMatchEquipmentSkillIndex))
{
saveData.postMatchRewardCounter = Mathf.Max(0, saveData.postMatchRewardCounter) + 1;
if (saveData.postMatchRewardCounter >= 10)
{
saveData.postMatchRewardCounter = 0;
equipmentSO generated = GrantRandomMemoryFromConfiguredSource();
if (generated != null)
{
grantedEntries.Add(gItemGet.FromEquipment(generated, 1));
}
}
}
if (idolScore > 0 && IsSkillEnabledInternal(ScoreToMaterialSkillIndex))
{
int grantedMaterial = Mathf.FloorToInt(idolScore * 0.001f);
if (grantedMaterial > 0)
{
PlayerEconomyLedger.EnsureInstance().AddMaterial(grantedMaterial);
grantedEntries.Add(gItemGet.Create("\u8bb0\u5fc6\u788e\u7247", null, grantedMaterial, ItemRarity.Uncommon));
}
}
if (idolScore > 0 && IsSkillEnabledInternal(ScoreToCoinsSkillIndex))
{
int grantedCoins = Mathf.FloorToInt(idolScore * 0.002f);
if (grantedCoins > 0)
{
PlayerEconomyLedger.EnsureInstance().AddCoins(grantedCoins);
grantedEntries.Add(gItemGet.Create("\u91d1\u5e01", null, grantedCoins, new Color32(240, 196, 74, 255)));
}
}
SaveNow();
if (grantedEntries.Count > 0)
{
gItemGet.display(grantedEntries);
}
}
private void AppendDynamicStoreItemsInternal(List<storeItemSO> target, Sprite coinSprite, Sprite materialSprite)
{
InitializeIfNeeded();
ResolveSkillAssetIfNeeded();
SyncSelectionFromAsset();
if (target == null)
{
return;
}
if (IsSkillEnabledInternal(MemoryBuySkillIndex))
{
target.Add(CreateMemoryBuyStoreItem(materialSprite));
}
if (IsSkillEnabledInternal(MemorySellSkillIndex))
{
target.Add(CreateMemorySellStoreItem(coinSprite));
}
}
private bool TryPurchasePlayerSkillStoreItemInternal(storeItemSO item, int amount, out string failureMessage, out gItemGet.ItemEntry rewardEntry)
{
failureMessage = string.Empty;
rewardEntry = default;
if (item == null)
{
failureMessage = "\u5546\u54c1\u6570\u636e\u4e22\u5931";
return false;
}
int purchaseAmount = Mathf.Max(1, amount);
if (item.itemID == MemoryBuyStoreItemId)
{
int totalCost = purchaseAmount * 10;
if (!PlayerEconomyLedger.EnsureInstance().TrySpendCoins(totalCost))
{
failureMessage = "\u8d27\u5e01\u4e0d\u8db3";
return false;
}
PlayerEconomyLedger.EnsureInstance().AddMaterial(purchaseAmount);
rewardEntry = gItemGet.Create("\u8bb0\u5fc6\u788e\u7247", item.itemIcon, purchaseAmount, ItemRarity.Uncommon);
return true;
}
if (item.itemID == MemorySellStoreItemId)
{
if (!PlayerEconomyLedger.EnsureInstance().TrySpendMaterial(purchaseAmount))
{
failureMessage = "\u8bb0\u5fc6\u788e\u7247\u4e0d\u8db3";
return false;
}
int grantedCoins = purchaseAmount * 4;
PlayerEconomyLedger.EnsureInstance().AddCoins(grantedCoins);
rewardEntry = gItemGet.Create("\u91d1\u5e01", item.itemIcon, grantedCoins, new Color32(240, 196, 74, 255));
return true;
}
failureMessage = "\u5f53\u524d\u672a\u914d\u7f6e\u53d1\u653e\u903b\u8f91";
return false;
}
private void ConfirmSkillSelectionInternal(userLevel_skills_SO skillAsset, int selectedIndex)
{
InitializeIfNeeded();
if (skillAsset == null || skillAsset.skills == null || selectedIndex < 0 || selectedIndex >= skillAsset.skills.Count)
{
return;
}
registeredSkillAsset = skillAsset;
for (int i = 0; i < skillAsset.skills.Count; i++)
{
userLevel_skills_SO.UserLevelSkillEntry entry = skillAsset.skills[i];
if (entry == null)
{
continue;
}
entry.isEnabled = (i == selectedIndex);
}
if (saveData.selectedSkillIndex != selectedIndex)
{
saveData.postMatchRewardCounter = 0;
saveData.skillSwitchCooldownRemainingMatches = 3;
}
saveData.selectedSkillIndex = selectedIndex;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(skillAsset);
}
#endif
SaveNow();
ApplySceneBindings();
}
private void ClearPersistentStateInternal()
{
saveData = new PlayerSkillSaveData();
SecureSaveVault.Delete(SaveCategory, SaveKey);
ApplySceneBindings();
}
private void HandleSceneLoaded(Scene scene, LoadSceneMode mode)
{
ApplySceneBindings();
}
private void ApplySceneBindings()
{
bool revealLockedSkills = IsSkillEnabledInternal(RevealLockedIdolSkillsSkillIndex);
idolSkillsHub[] hubs = Resources.FindObjectsOfTypeAll<idolSkillsHub>();
for (int i = 0; i < hubs.Length; i++)
{
idolSkillsHub hub = hubs[i];
if (hub == null || !IsSceneInstance(hub))
{
continue;
}
if (hub.display_ungotten != revealLockedSkills)
{
hub.display_ungotten = revealLockedSkills;
if (hub.gameObject.activeInHierarchy)
{
hub.Rebuild();
}
}
}
storeSystem[] stores = Resources.FindObjectsOfTypeAll<storeSystem>();
for (int i = 0; i < stores.Length; i++)
{
if (stores[i] != null && IsSceneInstance(stores[i]))
{
stores[i].RefreshPlayerSkillItems();
}
}
equipSmelt[] smelters = Resources.FindObjectsOfTypeAll<equipSmelt>();
for (int i = 0; i < smelters.Length; i++)
{
if (smelters[i] != null && IsSceneInstance(smelters[i]))
{
smelters[i].RefreshPlayerSkillAdjustedUi();
}
}
effectEventController.RefreshPlayerSkillCameraOffset();
}
private void ResolveSkillAssetIfNeeded()
{
if (registeredSkillAsset != null)
{
return;
}
uLevel_skills[] skillPanels = Resources.FindObjectsOfTypeAll<uLevel_skills>();
for (int i = 0; i < skillPanels.Length; i++)
{
if (skillPanels[i] != null && skillPanels[i].ulsSO != null)
{
registeredSkillAsset = skillPanels[i].ulsSO;
return;
}
}
userLevel_skills_SO[] loadedAssets = Resources.FindObjectsOfTypeAll<userLevel_skills_SO>();
for (int i = 0; i < loadedAssets.Length; i++)
{
if (loadedAssets[i] != null)
{
registeredSkillAsset = loadedAssets[i];
return;
}
}
#if UNITY_EDITOR
registeredSkillAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<userLevel_skills_SO>(SkillAssetEditorPath);
#endif
}
private void SyncSelectionFromAsset()
{
if (saveData == null || registeredSkillAsset == null || registeredSkillAsset.skills == null || registeredSkillAsset.skills.Count == 0)
{
return;
}
int enabledIndex = -1;
for (int i = 0; i < registeredSkillAsset.skills.Count; i++)
{
userLevel_skills_SO.UserLevelSkillEntry entry = registeredSkillAsset.skills[i];
if (entry != null && entry.isEnabled)
{
enabledIndex = i;
break;
}
}
if (enabledIndex == saveData.selectedSkillIndex)
{
return;
}
if (saveData.selectedSkillIndex >= 0)
{
saveData.postMatchRewardCounter = 0;
}
saveData.selectedSkillIndex = enabledIndex;
SaveNow();
}
private storeItemSO CreateMemoryBuyStoreItem(Sprite materialSprite)
{
storeItemSO item = ScriptableObject.CreateInstance<storeItemSO>();
item.hideFlags = HideFlags.DontSave;
item.itemID = MemoryBuyStoreItemId;
item.itemName = "1\u8bb0\u5fc6\u788e\u7247";
item.itemIcon = materialSprite;
item.itemRarity = ItemRarity.Uncommon;
item.itemSinglePurchaseQty = 1;
item.itemPurchaseQuota = -1;
item.itemDescription = "\u82b1\u8d3910\u91d1\u5e01\u8d2d\u4e701\u4e2a\u8bb0\u5fc6\u788e\u7247\u3002";
item.itemDetailedDescription = "\u73a9\u5bb6\u6280\u80fd\u201c\u8bb0\u5fc6\u8d2d\u4e70\u8ba1\u5212\u201d\u751f\u6548\u4e2d\u3002\u82b1\u8d3910\u91d1\u5e01\u8d2d\u4e701\u4e2a\u8bb0\u5fc6\u788e\u7247\u3002";
item.isOnShelf = true;
item.canbepurchased = true;
item.itemType = storeItemSO.ItemType.consumable;
item.costRequirements = new List<storeItemSO.CostRequirement>
{
new storeItemSO.CostRequirement
{
currencyType = storeItemSO.CurrencyType.coins,
amount = 10
}
};
item.unlockRequirements = new List<storeItemSO.UnlockRequirement>();
return item;
}
private storeItemSO CreateMemorySellStoreItem(Sprite coinSprite)
{
storeItemSO item = ScriptableObject.CreateInstance<storeItemSO>();
item.hideFlags = HideFlags.DontSave;
item.itemID = MemorySellStoreItemId;
item.itemName = "4\u91d1\u5e01";
item.itemIcon = coinSprite;
item.itemRarity = ItemRarity.Common;
item.itemSinglePurchaseQty = 1;
item.itemPurchaseQuota = -1;
item.itemDescription = "\u6d88\u80171\u4e2a\u8bb0\u5fc6\u788e\u7247\uff0c\u6362\u53d64\u91d1\u5e01\u3002";
item.itemDetailedDescription = "\u73a9\u5bb6\u6280\u80fd\u201c\u8bb0\u5fc6\u5f52\u8fd8\u8ba1\u5212\u201d\u751f\u6548\u4e2d\u3002\u6d88\u80171\u4e2a\u8bb0\u5fc6\u788e\u7247\uff0c\u6362\u53d64\u91d1\u5e01\u3002";
item.isOnShelf = true;
item.canbepurchased = true;
item.itemType = storeItemSO.ItemType.consumable;
item.costRequirements = new List<storeItemSO.CostRequirement>
{
new storeItemSO.CostRequirement
{
currencyType = storeItemSO.CurrencyType.material,
amount = 1
}
};
item.unlockRequirements = new List<storeItemSO.UnlockRequirement>();
return item;
}
private equipmentSO GrantRandomMemoryFromConfiguredSource()
{
storeItemSO randomMemoryItem = ResolveRandomMemoryRewardItem();
if (randomMemoryItem == null || randomMemoryItem.associatedSelectableEquipmentRewardSource == null)
{
return null;
}
smeltStageRewardSO rewardSource = randomMemoryItem.associatedSelectableEquipmentRewardSource;
int rewardIndex = Mathf.Max(0, randomMemoryItem.associatedSelectableEquipmentRewardIndex);
if (rewardSource.rewardRequirements == null || rewardIndex >= rewardSource.rewardRequirements.Length)
{
return null;
}
return equipmentGenerator.GenerateFromSmeltReward(rewardSource, rewardSource.rewardRequirements[rewardIndex]);
}
private storeItemSO ResolveRandomMemoryRewardItem()
{
if (cachedRandomMemoryRewardItem != null)
{
return cachedRandomMemoryRewardItem;
}
storeItemSO[] items = Resources.LoadAll<storeItemSO>("so/storeSO");
for (int i = 0; i < items.Length; i++)
{
if (items[i] != null && items[i].itemID == RandomMemoryStoreItemId)
{
cachedRandomMemoryRewardItem = items[i];
break;
}
}
return cachedRandomMemoryRewardItem;
}
private int GetOwnedHeroCount()
{
HashSet<int> heroIds = new HashSet<int>();
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
if (heroes == null)
{
return 0;
}
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero != null && hero.isUnlocked)
{
heroIds.Add(hero.ally_heroID);
}
}
return heroIds.Count;
}
private bool IsSceneInstance(Component component)
{
if (component == null || component.gameObject == null)
{
return false;
}
Scene scene = component.gameObject.scene;
return scene.IsValid() && scene.isLoaded;
}
private void SaveNow()
{
if (!initialized)
{
return;
}
SecureSaveVault.SaveJson(SaveCategory, SaveKey, saveData);
}
}