Commit eea6cdcb authored by ElderLich's avatar ElderLich

Feature: Implemented Deluxe Mates like in MD

parent 2046e5d8
...@@ -954,7 +954,7 @@ MonoBehaviour: ...@@ -954,7 +954,7 @@ MonoBehaviour:
- id: 1003003 - id: 1003003
m_name: m_name:
m_description: m_description:
path: Mate/M13670_Model path: Mate/M13671_Model
secondFace: 1 secondFace: 1
diy: 0 diy: 0
notReady: 0 notReady: 0
...@@ -965,6 +965,13 @@ MonoBehaviour: ...@@ -965,6 +965,13 @@ MonoBehaviour:
secondFace: 1 secondFace: 1
diy: 0 diy: 0
notReady: 0 notReady: 0
- id: 1003203
m_name:
m_description:
path: Mate/M13670_Model
secondFace: 1
diy: 0
notReady: 0
- id: 1003004 - id: 1003004
m_name: m_name:
m_description: m_description:
...@@ -8133,4 +8140,4 @@ MonoBehaviour: ...@@ -8133,4 +8140,4 @@ MonoBehaviour:
path: Wallpaper/88177324 path: Wallpaper/88177324
secondFace: 0 secondFace: 0
diy: 1 diy: 1
notReady: 0 notReady: 0
\ No newline at end of file
...@@ -10,6 +10,7 @@ using System.Collections.Generic; ...@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Drawing.Text; using System.Drawing.Text;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets;
...@@ -26,7 +27,6 @@ namespace MDPro3.Duel ...@@ -26,7 +27,6 @@ namespace MDPro3.Duel
{ {
public class DuelBGManager public class DuelBGManager
{ {
#region Parameters #region Parameters
private OcgCore Core => Program.instance.ocgcore; private OcgCore Core => Program.instance.ocgcore;
...@@ -34,6 +34,8 @@ namespace MDPro3.Duel ...@@ -34,6 +34,8 @@ namespace MDPro3.Duel
private bool mate0Random = true; private bool mate0Random = true;
private bool mate1Random = true; private bool mate1Random = true;
private Tween mate0RandomCooldownTween;
private Tween mate1RandomCooldownTween;
private int bgPhase0 = 0; private int bgPhase0 = 0;
private int bgPhase1 = 0; private int bgPhase1 = 0;
private bool backgroundFieldInitialize = false; private bool backgroundFieldInitialize = false;
...@@ -70,6 +72,8 @@ namespace MDPro3.Duel ...@@ -70,6 +72,8 @@ namespace MDPro3.Duel
private BgEffectManager stand1Manager; private BgEffectManager stand1Manager;
private Mate mate0; private Mate mate0;
private Mate mate1; private Mate mate1;
private PremiumMateState premiumMate0;
private PremiumMateState premiumMate1;
private GameObject phaseButton; private GameObject phaseButton;
private TimerHandler timerHandler; private TimerHandler timerHandler;
...@@ -83,11 +87,43 @@ namespace MDPro3.Duel ...@@ -83,11 +87,43 @@ namespace MDPro3.Duel
#endregion #endregion
private sealed class PremiumMateState
{
public int Side;
public PremiumMateRule Rule;
public PremiumMateSwapEffect SwapEffect;
public Transform Anchor;
public readonly Dictionary<int, Mate> Forms = new();
public int ActiveMateId;
public bool IsTransitioning;
public int QueuedMateId;
public bool IsPermanentSub;
public bool InBattlePhaseSub;
public bool PendingDirectAttack;
public IReadOnlyList<string> OverrideTriggerPriority;
public int PendingTurnSwapToken;
public int RayeEngageMateId;
public int RayeBattleMateId;
}
private readonly struct SwapEffectLabelPlaybackResult
{
public bool Consumed { get; }
public SwapEffectLabelPlaybackResult(bool consumed)
{
Consumed = consumed;
}
public static SwapEffectLabelPlaybackResult None => new(false);
}
#region Public #region Public
public async UniTask LoadAssetsAsync() public async UniTask LoadAssetsAsync()
{ {
loaded = false; loaded = false;
ResetPremiumMateStates();
deck = null; deck = null;
var deckName = Config.GetConfigDeckName(); var deckName = Config.GetConfigDeckName();
...@@ -266,30 +302,12 @@ namespace MDPro3.Duel ...@@ -266,30 +302,12 @@ namespace MDPro3.Duel
int mateCode = int.Parse(mateConfig); int mateCode = int.Parse(mateConfig);
if (deck != null && !overrideDeckAppearance) if (deck != null && !overrideDeckAppearance)
mateCode = deck.Mate; mateCode = deck.Mate;
if (mateCode != Items.CODE_NONE) await LoadMateForSideAsync(0, mateCode, pos_Avatar_near);
{
var mate = await ABLoader.LoadMateAsync(mateCode);
if (mate != null)
{
mate0 = mate;
mate0.parent = pos_Avatar_near;
mate0.gameObject.SetActive(false);
}
}
mateConfig = Config.Get(condition.ToString() + "Mate1", Program.items.mates[0].id.ToString()); mateConfig = Config.Get(condition.ToString() + "Mate1", Program.items.mates[0].id.ToString());
if (hasSide1Appearance) if (hasSide1Appearance)
mateConfig = side1Appearance.Mate.ToString(); mateConfig = side1Appearance.Mate.ToString();
if (mateConfig != Items.CODE_NONE.ToString()) await LoadMateForSideAsync(1, int.Parse(mateConfig), pos_Avatar_far);
{
var mate = await ABLoader.LoadMateAsync(int.Parse(mateConfig));
if (mate != null)
{
mate1 = mate;
mate1.parent = pos_Avatar_far;
mate1.gameObject.SetActive(false);
}
}
#endregion #endregion
...@@ -442,196 +460,1367 @@ namespace MDPro3.Duel ...@@ -442,196 +460,1367 @@ namespace MDPro3.Duel
}; };
CreatePlaceSelector(gps); CreatePlaceSelector(gps);
} }
for (uint s = 0; s < 6; s++) for (uint s = 0; s < 6; s++)
{
gps = new()
{
controller = c,
location = (uint)CardLocation.SpellZone,
sequence = s
};
CreatePlaceSelector(gps);
}
}
#endregion
#region Quit Load
await processor.PreloadPlayerNames();
if (Core.NeedVoice())
{
Core.GetUI<OcgCoreUI>().CG.alpha = 1;
Core.GetUI<OcgCoreUI>().CG.blocksRaycasts = true;
}
Core.GetUI<OcgCoreUI>().Buttons.SetActive(false);
UIManager.ShowFPSLeft();
UIManager.HideBlackBack(0f);
UIManager.UIBlackOut(Core.TransitionTime);
await UniTask.WaitForSeconds(Core.TransitionTime);
backgroundFieldInitialize = false;
BackgroundFieldInitialize();
if (condition == Condition.Duel && Config.GetBool("DuelAutoAcc", false)
|| condition == Condition.Watch && Config.GetBool("WatchAutoAcc", false)
|| condition == Condition.Replay && Config.GetBool("ReplayAutoAcc", false))
Core.GetUI<OcgCoreUI>().OnAcc();
#endregion
loaded = true;
}
public async UniTask ExitDuelAsync()
{
ClearResponse();
CameraManager.BlackOut(0f, 0.3f);
UIManager.UIBlackIn(Core.TransitionTime);
Core.GetUI<OcgCoreUI>().CloseHint();
HideAttackLine();
HideDuelFinalBlowText();
await UniTask.WaitForSeconds(Core.TransitionTime);
Core.servantUI.ShutDown();
NoMoreWait = true;
packages.Clear();
allPackages.Clear();
AudioManager.ResetSESource();
mycardDuel = false;
Core.CloseCharaFace();
Dispose();
foreach (var card in cards)
card.Dispose();
cards.Clear();
pause = false;
nextMoveAction = null;
Core.cachedCharaFaces.Clear();
CameraManager.ShiftTo2D();
Core.GetUI<OcgCoreUI>().Buttons.SetActive(false);
Core.GetUI<OcgCoreUI>().DuelLog.ClearLog();
Program.instance.ui_.chatPanel.Hide();
await UniTask.WaitForSeconds(0.3f);
UIManager.UIBlackOut(Core.TransitionTime);
await UniTask.WaitForSeconds(Core.TransitionTime);
UIManager.ShowFPSRight();
AudioManager.PlayBGM(AudioManager.BGM_MENU_MAIN);
}
private void InitializeDeckModel(ElementObjectManager deckManager, int player, CardLocation location)
{
deckManager.transform.SetParent(player == 0 ? field0Manager.transform : field1Manager.transform, false);
deckManager.transform.localPosition = _positionMap[(player, location)];
deckManager.transform.localEulerAngles = _angleMap[(player, location)];
allGameObjects.Add(deckManager.gameObject);
var mat = player == 0 ? myProtector : opProtector;
deckManager.GetNestedElement<MeshRenderer>("DummyDeck/DummyCardModel_back").material = mat;
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel01_back").material = mat;
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel02_back").material = mat;
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel03_back").material = mat;
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel04_back").material = mat;
deckManager.gameObject.SetActive(false);
}
private static readonly Dictionary<(int, CardLocation), Vector3> _positionMap = new()
{
[(0, CardLocation.Deck)] = new(26.6f, 1.5f, -23.5f),
[(0, CardLocation.Extra)] = new(-26.6f, 1.5f, -23.5f),
[(1, CardLocation.Deck)] = new(-26.6f, 1.5f, 23.5f),
[(1, CardLocation.Extra)] = new(26.6f, 1.5f, 23.5f)
};
private static readonly Dictionary<(int, CardLocation), Vector3> _angleMap = new()
{
[(0, CardLocation.Deck)] = new(0f, -20f, 0f),
[(0, CardLocation.Extra)] = new(0f, 20f, 0f),
[(1, CardLocation.Deck)] = new(0f, 160f, 0f),
[(1, CardLocation.Extra)] = new(0f, -160f, 0f)
};
public async UniTask ShowDecksAsync()
{
if (myDeck == null || myExtra == null
|| opDeck == null || opExtra == null)
return;
await ShowAllDeckModelsAsync();
Core.GetUI<OcgCoreUI>().CG.alpha = 1;
Core.GetUI<OcgCoreUI>().CG.blocksRaycasts = true;
Core.GetUI<OcgCoreUI>().Buttons.SetActive(true);
AudioManager.PlayBgmNormal(Config.GetBool("BGMbyMySide", true) ? field0Manager.name : field1Manager.name);
}
public async UniTask ShowDecksWithDuelStartTextAsync()
{
if (myDeck == null || myExtra == null
|| opDeck == null || opExtra == null)
return;
await ShowAllDeckModelsAsync();
var effect = ABLoader.LoadMasterDuelGameObject("DuelTextStart");
var director = effect.GetComponent<PlayableDirector>();
await director.WaitAsync();
UnityEngine.Object.Destroy(effect);
Core.GetUI<OcgCoreUI>().CG.alpha = 1;
Core.GetUI<OcgCoreUI>().CG.blocksRaycasts = true;
Core.GetUI<OcgCoreUI>().Buttons.SetActive(true);
AudioManager.PlayBgmNormal(Config.GetBool("BGMbyMySide", true) ? field0Manager.name : field1Manager.name);
}
public void BackgroundFieldInitialize()
{
if (field0Manager == null || field1Manager == null)
return;
if (bgPhase0 == 1 && bgPhase1 == 1) return;
if (backgroundFieldInitialize)
{
field0Manager.gameObject.SetActive(false);
field1Manager.gameObject.SetActive(false);
field0Manager.gameObject.SetActive(true);
field1Manager.gameObject.SetActive(true);
}
field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1);
grave0Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1);
bgPhase0 = 1;
field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1);
grave1Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1);
bgPhase1 = 1;
InitializeMateEntry(0);
InitializeMateEntry(1);
if (timerHandler != null)
timerHandler.DuelStart();
RestartMateRandomCooldown(0);
RestartMateRandomCooldown(1);
backgroundFieldInitialize = true;
}
public void OnNewTurn(bool myTurn, int turn)
{
ApplyPremiumNewTurn(GetPremiumMateState(0), myTurn, turn);
ApplyPremiumNewTurn(GetPremiumMateState(1), !myTurn, turn);
}
public void OnNewPhase(int turnPlayer, DuelPhase phase)
{
ApplyPremiumPhase(GetPremiumMateState(0), turnPlayer == 0, phase);
ApplyPremiumPhase(GetPremiumMateState(1), turnPlayer == 1, phase);
}
public void OnSpecialSummonFromExtra(int ownerPlayer)
{
var state = GetPremiumMateState(ownerPlayer);
if (state == null)
return;
if (state.Rule.Behavior == PremiumMateBehavior.GaiaExtraDeckPermanent
|| state.Rule.Behavior == PremiumMateBehavior.FiendsmithExtraDeckOrEquipPermanent)
{
if (state.IsPermanentSub)
return;
state.IsPermanentSub = true;
RequestPremiumMateForm(state, state.Rule.SubId);
}
}
public void OnEquipApplied(int ownerPlayer)
{
var state = GetPremiumMateState(ownerPlayer);
if (state == null)
return;
if (state.Rule.Behavior != PremiumMateBehavior.FiendsmithExtraDeckOrEquipPermanent)
return;
if (state.IsPermanentSub)
return;
state.IsPermanentSub = true;
RequestPremiumMateForm(state, state.Rule.SubId);
}
public void OnLifePointsChanged(int player, int lifePoint)
{
var state = GetPremiumMateState(player);
if (state == null)
return;
if (state.Rule.Behavior != PremiumMateBehavior.ShuraigLpThreshold)
return;
if (state.IsPermanentSub)
return;
if (lifePoint > state.Rule.LpThreshold)
return;
state.IsPermanentSub = true;
RequestPremiumMateForm(state, state.Rule.SubId);
}
public void OnDirectAttack(int attackerPlayer)
{
var state = GetPremiumMateState(attackerPlayer);
if (state == null)
return;
if (state.Rule.Behavior != PremiumMateBehavior.RayeBattlePhaseAndDirectAttack)
return;
var battleMateId = GetRayeBattleMateId(state);
if (state.ActiveMateId != battleMateId)
return;
state.PendingDirectAttack = true;
}
public void OnPlayerDamaged(int defenderPlayer, int amount)
{
if (amount <= 0)
return;
var state0 = GetPremiumMateState(0);
var state1 = GetPremiumMateState(1);
TryResolveRayeDirectAttack(state0, defenderPlayer);
TryResolveRayeDirectAttack(state1, defenderPlayer);
}
private void TryResolveRayeDirectAttack(PremiumMateState state, int defenderPlayer)
{
if (state == null)
return;
if (state.Rule.Behavior != PremiumMateBehavior.RayeBattlePhaseAndDirectAttack)
return;
if (!state.PendingDirectAttack)
return;
if (state.Side == defenderPlayer)
return;
state.PendingDirectAttack = false;
state.InBattlePhaseSub = false;
RequestPremiumMateForm(state, GetRayeEngageMateId(state));
}
private void ApplyPremiumNewTurn(PremiumMateState state, bool ownerTurn, int turn)
{
if (state == null)
return;
_ = ownerTurn;
state.PendingDirectAttack = false;
switch (state.Rule.Behavior)
{
case PremiumMateBehavior.LaundryBattlePhaseRoundTrip:
state.InBattlePhaseSub = false;
if (!state.IsPermanentSub)
RequestPremiumMateForm(state, state.Rule.BaseId);
break;
case PremiumMateBehavior.RayeBattlePhaseAndDirectAttack:
state.InBattlePhaseSub = false;
break;
case PremiumMateBehavior.IpSpTurnParity:
var target = (turn % 2 != 0) ? state.Rule.BaseId : state.Rule.SubId;
const float ipSpPreSwapDelaySeconds = 0.80f;
var turnSwapToken = ++state.PendingTurnSwapToken;
_ = RequestPremiumMateFormDelayedAsync(state, target, ipSpPreSwapDelaySeconds, turnSwapToken);
break;
}
}
private async UniTask RequestPremiumMateFormDelayedAsync(PremiumMateState state, int targetMateId, float delaySeconds, int token)
{
if (state == null)
return;
if (delaySeconds > 0f)
await UniTask.WaitForSeconds(delaySeconds);
if (state.PendingTurnSwapToken != token)
return;
if (GetPremiumMateState(state.Side) != state)
return;
RequestPremiumMateForm(state, targetMateId);
}
private void ApplyPremiumPhase(PremiumMateState state, bool ownerTurn, DuelPhase phase)
{
if (state == null)
return;
switch (state.Rule.Behavior)
{
case PremiumMateBehavior.LaundryBattlePhaseRoundTrip:
if (ownerTurn && phase == DuelPhase.BattleStart)
{
state.InBattlePhaseSub = true;
RequestPremiumMateForm(state, state.Rule.SubId);
}
else if ((!ownerTurn || phase == DuelPhase.Main2 || phase == DuelPhase.End) && state.InBattlePhaseSub)
{
state.InBattlePhaseSub = false;
RequestPremiumMateForm(state, state.Rule.BaseId);
}
break;
case PremiumMateBehavior.RayeBattlePhaseAndDirectAttack:
var battleMateId = GetRayeBattleMateId(state);
if (ownerTurn && phase == DuelPhase.BattleStart)
{
state.PendingDirectAttack = false;
state.InBattlePhaseSub = true;
if (state.ActiveMateId != battleMateId)
RequestPremiumMateForm(state, battleMateId);
}
else if ((phase == DuelPhase.Main2 || phase == DuelPhase.End) && state.InBattlePhaseSub)
{
state.PendingDirectAttack = false;
state.InBattlePhaseSub = false;
}
break;
}
}
private void InitializeMateEntry(int side)
{
var state = GetPremiumMateState(side);
if (state != null)
{
foreach (var pair in state.Forms)
if (pair.Value != null)
pair.Value.gameObject.SetActive(false);
if (!state.Forms.TryGetValue(state.ActiveMateId, out var activeMate) || activeMate == null)
activeMate = state.Forms.Values.FirstOrDefault(m => m != null);
if (activeMate == null)
{
SetMateForSide(side, null);
return;
}
state.ActiveMateId = activeMate.code;
ResetMateTransform(activeMate, state.Anchor);
SetExclusivePremiumMateActiveForm(state, activeMate);
activeMate.PrepareForPremiumSwapActivation();
activeMate.Play(Mate.MateAction.Entry);
SetMateForSide(side, activeMate);
return;
}
var mate = GetMateForSide(side);
if (mate != null)
{
mate.gameObject.SetActive(true);
mate.Play(Mate.MateAction.Entry);
}
}
private async UniTask LoadMateForSideAsync(int side, int mateCode, Transform anchor)
{
SetPremiumMateState(side, null);
SetMateForSide(side, null);
if (mateCode == Items.CODE_NONE)
return;
var normalizedMateCode = PremiumMateRules.GetBaseMateId(mateCode);
if (PremiumMateRules.TryGetRuleByBaseId(normalizedMateCode, out var rule))
{
var formIds = new List<int> { rule.BaseId };
foreach (var variantId in rule.VariantIds)
if (!formIds.Contains(variantId))
formIds.Add(variantId);
var loadedForms = new Dictionary<int, Mate>();
foreach (var formId in formIds)
{
var formMate = await ABLoader.LoadMateAsync(formId);
if (formMate == null)
continue;
ConfigureLoadedMate(formMate, anchor);
loadedForms[formId] = formMate;
}
if (loadedForms.TryGetValue(rule.BaseId, out var baseMate)
&& loadedForms.TryGetValue(rule.SubId, out _))
{
var state = new PremiumMateState
{
Side = side,
Rule = rule,
SwapEffect = PremiumMateSwapEffects.GetOrDefault(rule.BaseId),
Anchor = anchor,
ActiveMateId = rule.BaseId
};
foreach (var pair in loadedForms)
state.Forms[pair.Key] = pair.Value;
if (rule.Behavior == PremiumMateBehavior.RayeBattlePhaseAndDirectAttack)
{
// The premium Raye mate's item IDs do not match the visual form order:
// 1003003 = Engage, 1003103 = Kagari, 1003203 = Raye.
// The opening Raye-only startup is disabled; start directly on Engage.
state.RayeEngageMateId = rule.BaseId;
state.RayeBattleMateId = loadedForms.ContainsKey(1003103) ? 1003103 : rule.SubId;
state.ActiveMateId = state.RayeEngageMateId;
}
SetPremiumMateState(side, state);
if (!loadedForms.TryGetValue(state.ActiveMateId, out var initialMate) || initialMate == null)
initialMate = baseMate;
SetMateForSide(side, initialMate);
return;
}
var fallbackMate = loadedForms.Values.FirstOrDefault();
if (fallbackMate != null)
{
SetMateForSide(side, fallbackMate);
return;
}
}
var mate = await ABLoader.LoadMateAsync(normalizedMateCode);
if (mate == null)
return;
ConfigureLoadedMate(mate, anchor);
SetMateForSide(side, mate);
}
private static void ConfigureLoadedMate(Mate mate, Transform anchor)
{
if (mate == null)
return;
ResetMateTransform(mate, anchor);
mate.gameObject.SetActive(false);
}
private static void ResetMateTransform(Mate mate, Transform anchor)
{
if (mate == null || anchor == null)
return;
mate.parent = anchor;
mate.transform.SetParent(anchor, false);
mate.transform.localPosition = Vector3.zero;
mate.transform.localRotation = Quaternion.identity;
}
private static int GetRayeEngageMateId(PremiumMateState state)
{
if (state == null)
return 0;
if (state.RayeEngageMateId > 0)
return state.RayeEngageMateId;
return state.Rule.BaseId;
}
private static int GetRayeBattleMateId(PremiumMateState state)
{
if (state == null)
return 0;
return state.RayeBattleMateId > 0 ? state.RayeBattleMateId : state.Rule.SubId;
}
private static int ResolvePremiumMateTargetId(PremiumMateState state, int targetMateId)
{
if (state == null)
return targetMateId;
if (state.Rule.Behavior == PremiumMateBehavior.RayeBattlePhaseAndDirectAttack
&& targetMateId == GetRayeBattleMateId(state)
&& !state.InBattlePhaseSub)
{
return GetRayeEngageMateId(state);
}
return targetMateId;
}
private void ActivatePremiumMateFormImmediate(PremiumMateState state, int targetMateId)
{
if (state == null)
return;
if (!state.Forms.TryGetValue(targetMateId, out var targetMate) || targetMate == null)
return;
ResetMateTransform(targetMate, state.Anchor);
SetExclusivePremiumMateActiveForm(state, targetMate);
targetMate.PrepareForPremiumSwapActivation();
state.ActiveMateId = targetMateId;
SetMateForSide(state.Side, targetMate);
RestartMateRandomCooldown(state.Side);
}
private static void SetExclusivePremiumMateActiveForm(PremiumMateState state, Mate activeMate, Mate additionalActiveMate = null)
{
if (state == null)
return;
foreach (var pair in state.Forms)
{
var mate = pair.Value;
if (mate == null)
continue;
mate.gameObject.SetActive(mate == activeMate || mate == additionalActiveMate);
}
}
private static bool ShouldSuppressAmbientPremiumMateActions(PremiumMateState state)
{
return state != null && state.Rule.Behavior == PremiumMateBehavior.RayeBattlePhaseAndDirectAttack;
}
private static bool HasNonChangeTrigger(IReadOnlyList<string> triggerPriority)
{
if (triggerPriority == null || triggerPriority.Count == 0)
return false;
for (var i = 0; i < triggerPriority.Count; i++)
{
var trigger = triggerPriority[i];
if (string.IsNullOrEmpty(trigger))
continue;
if (!trigger.StartsWith("Change", StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
private static bool IsChangeTrigger(string triggerName)
{
return !string.IsNullOrEmpty(triggerName) && triggerName.StartsWith("Change", StringComparison.OrdinalIgnoreCase);
}
private static bool HasConfiguredChangeTrigger(IReadOnlyList<string> triggerPriority)
{
if (triggerPriority == null || triggerPriority.Count == 0)
return false;
for (var i = 0; i < triggerPriority.Count; i++)
{
if (IsChangeTrigger(triggerPriority[i]))
return true;
}
return false;
}
private static IReadOnlyList<string> BuildNonChangeFirstPriority(IReadOnlyList<string> triggerPriority)
{
if (triggerPriority == null || triggerPriority.Count <= 1)
return triggerPriority;
var hasLeadingChange = false;
var hasFollowingNonChange = false;
for (var i = 0; i < triggerPriority.Count; i++)
{
var trigger = triggerPriority[i];
if (string.IsNullOrEmpty(trigger))
continue;
if (IsChangeTrigger(trigger))
{
hasLeadingChange = true;
continue;
}
if (hasLeadingChange)
{
hasFollowingNonChange = true;
break;
}
}
if (!hasFollowingNonChange)
return triggerPriority;
var reordered = new List<string>(triggerPriority.Count);
for (var i = 0; i < triggerPriority.Count; i++)
{
var trigger = triggerPriority[i];
if (string.IsNullOrEmpty(trigger) || IsChangeTrigger(trigger))
continue;
reordered.Add(trigger);
}
for (var i = 0; i < triggerPriority.Count; i++)
{
var trigger = triggerPriority[i];
if (string.IsNullOrEmpty(trigger) || !IsChangeTrigger(trigger))
continue;
reordered.Add(trigger);
}
return reordered.Count > 0 ? reordered : triggerPriority;
}
private static bool MateHasAnyChangeTriggerParameter(Mate mate, IReadOnlyList<string> triggerPriority)
{
if (mate == null || triggerPriority == null || triggerPriority.Count == 0)
return false;
var checkedSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < triggerPriority.Count; i++)
{
var trigger = triggerPriority[i];
if (!IsChangeTrigger(trigger))
continue;
if (!checkedSet.Add(trigger))
continue;
if (mate.HasMasterDuelTriggerParameter(trigger))
return true;
}
return false;
}
private static bool MateHasAnyTransitionCandidate(Mate mate, IReadOnlyList<string> triggerPriority)
{
if (mate == null || triggerPriority == null || triggerPriority.Count == 0)
return false;
return mate.TryDescribeMasterDuelTransitionCandidates(triggerPriority, out _);
}
private static UniTask WaitForSecondsUnscaledAsync(float seconds)
{
if (seconds <= 0f)
return UniTask.CompletedTask;
return UniTask.Delay(TimeSpan.FromSeconds(seconds), DelayType.UnscaledDeltaTime);
}
private static UniTask WaitForSecondsSwapTimingAsync(float seconds, bool useUnscaledTiming)
{
if (seconds <= 0f)
return UniTask.CompletedTask;
return useUnscaledTiming
? WaitForSecondsUnscaledAsync(seconds)
: UniTask.WaitForSeconds(seconds);
}
private static async UniTask<float> MeasureCurrentTransitionQueueDelayAsync(
Mate mate,
int beforeStateHash,
bool beforeInTransition,
float maxWaitSeconds)
{
if (mate == null || maxWaitSeconds <= 0f)
return 0f;
var startRealtime = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup - startRealtime < maxWaitSeconds)
{
await UniTask.Yield(PlayerLoopTiming.Update);
if (!mate.TryGetMasterDuelPrimaryAnimatorSnapshot(
out var stateHash,
out _,
out _,
out var inTransition,
out var activeInHierarchy)
|| !activeInHierarchy)
{
return 0f;
}
if ((!beforeInTransition && inTransition) || stateHash != beforeStateHash)
return Time.realtimeSinceStartup - startRealtime;
}
return 0f;
}
private void RequestPremiumMateForm(PremiumMateState state, int targetMateId)
{
if (state == null)
return;
targetMateId = ResolvePremiumMateTargetId(state, targetMateId);
if (!state.Forms.ContainsKey(targetMateId))
return;
if (state.IsTransitioning && state.QueuedMateId == targetMateId)
return;
if (!state.IsTransitioning && state.ActiveMateId == targetMateId)
return;
state.QueuedMateId = targetMateId;
if (state.IsTransitioning)
return;
_ = ProcessPremiumMateSwapQueueAsync(state);
}
private async UniTask ProcessPremiumMateSwapQueueAsync(PremiumMateState state)
{
if (state == null)
return;
state.IsTransitioning = true;
try
{
while (state.QueuedMateId != 0)
{
if (state.QueuedMateId == state.ActiveMateId)
{
state.QueuedMateId = 0;
break;
}
var nextMateId = state.QueuedMateId;
state.QueuedMateId = 0;
await SwapPremiumMateAsync(state, nextMateId);
}
}
finally
{
state.IsTransitioning = false;
}
}
private async UniTask SwapPremiumMateAsync(PremiumMateState state, int targetMateId)
{
if (state == null)
return;
targetMateId = ResolvePremiumMateTargetId(state, targetMateId);
if (!state.Forms.TryGetValue(targetMateId, out var nextMate) || nextMate == null)
return;
var currentMate = GetMateForSide(state.Side);
if (currentMate == null && state.Forms.TryGetValue(state.ActiveMateId, out var fromState))
currentMate = fromState;
if (currentMate == nextMate)
{
SetExclusivePremiumMateActiveForm(state, nextMate);
state.ActiveMateId = targetMateId;
SetMateForSide(state.Side, nextMate);
return;
}
var toSub = state.Rule.Behavior == PremiumMateBehavior.RayeBattlePhaseAndDirectAttack
? targetMateId == GetRayeBattleMateId(state)
: targetMateId == state.Rule.SubId;
var swapDelay = toSub ? state.SwapEffect.ToSubDelaySeconds : state.SwapEffect.ToBaseDelaySeconds;
var effectAssetPath = state.SwapEffect.GetEffectAssetPath(toSub);
var useUnscaledSwapTiming = state.SwapEffect.UseUnscaledSwapTiming;
var changeOnTargetMate = state.SwapEffect.PlayChangeOnTargetMate;
var changeOnBothMates = state.SwapEffect.PlayChangeOnBothMates;
var playChangeOnCurrentMate = !changeOnTargetMate || changeOnBothMates;
var playChangeOnNextMate = changeOnTargetMate || changeOnBothMates;
var triggerPriorityOverride = state.OverrideTriggerPriority;
var currentTriggerPriority = triggerPriorityOverride ?? (toSub
? state.SwapEffect.ToSubCurrentTriggerPriority
: state.SwapEffect.ToBaseCurrentTriggerPriority);
var nextTriggerPriority = triggerPriorityOverride ?? (toSub
? state.SwapEffect.ToSubNextTriggerPriority
: state.SwapEffect.ToBaseNextTriggerPriority);
IReadOnlyList<string> effectiveNextTriggerPriority = nextTriggerPriority;
if (state.SwapEffect.UseChangeMotion && currentMate != null && playChangeOnCurrentMate
&& !MateHasAnyTransitionCandidate(currentMate, currentTriggerPriority))
playChangeOnCurrentMate = false;
state.OverrideTriggerPriority = null;
var playedChangeOnCurrent = false;
var playedChangeOnNext = false;
var currentTransitionDelay = 0f;
var nextTransitionDelay = 0f;
var currentTriggerQueueDelay = 0f;
var currentTriggerQueueProbeStarted = false;
var currentTriggerQueueDelayTask = UniTask.FromResult(0f);
var currentBeforeStateHash = 0;
var currentBeforeInTransition = false;
var hasCurrentSnapshot = false;
if (state.SwapEffect.CompensateCurrentTriggerQueueDelay && currentMate != null)
{
hasCurrentSnapshot = currentMate.TryGetMasterDuelPrimaryAnimatorSnapshot(
out currentBeforeStateHash,
out _,
out _,
out currentBeforeInTransition,
out _);
}
void StartCurrentTriggerQueueProbeIfNeeded()
{
if (currentTriggerQueueProbeStarted || !playedChangeOnCurrent || !hasCurrentSnapshot)
return;
if (state.SwapEffect.MaxCurrentTriggerQueueDelaySeconds <= 0f)
return;
currentTriggerQueueProbeStarted = true;
currentTriggerQueueDelayTask = MeasureCurrentTransitionQueueDelayAsync(
currentMate,
currentBeforeStateHash,
currentBeforeInTransition,
state.SwapEffect.MaxCurrentTriggerQueueDelaySeconds);
}
var effectPosition = currentMate != null ? currentMate.transform.position : state.Anchor.position;
var effectLabel = toSub ? state.SwapEffect.ToSubEffectLabel : state.SwapEffect.ToBaseEffectLabel;
var preferEffectLabelPlayback = state.SwapEffect.PreferEffectLabelPlayback && !string.IsNullOrEmpty(effectLabel);
if (!preferEffectLabelPlayback && state.SwapEffect.UseChangeMotion && currentMate != null && playChangeOnCurrentMate)
{
playedChangeOnCurrent = currentMate.PlayChangeTransition(currentTriggerPriority, out currentTransitionDelay);
StartCurrentTriggerQueueProbeIfNeeded();
}
if (state.SwapEffect.SourceToEffectDelaySeconds > 0f)
await WaitForSecondsSwapTimingAsync(state.SwapEffect.SourceToEffectDelaySeconds, useUnscaledSwapTiming);
var effectPlayback = await SpawnPremiumSwapEffectAsync(state, effectAssetPath, effectPosition, effectLabel, toSub);
if (preferEffectLabelPlayback && state.SwapEffect.UseChangeMotion && currentMate != null && playChangeOnCurrentMate)
{
if (!effectPlayback.Consumed)
{
playedChangeOnCurrent = currentMate.PlayChangeTransition(currentTriggerPriority, out currentTransitionDelay);
StartCurrentTriggerQueueProbeIfNeeded();
}
}
var delay = swapDelay;
if (currentTriggerQueueProbeStarted)
{
currentTriggerQueueDelay = await currentTriggerQueueDelayTask;
if (currentTriggerQueueDelay > 0f)
delay += currentTriggerQueueDelay;
}
if (playedChangeOnCurrent)
{
if (state.SwapEffect.UseTransitionDelayAsMinimum)
delay = Mathf.Max(delay, currentTransitionDelay);
else if (delay <= 0f)
delay = currentTransitionDelay;
}
var keepCurrentAliveForOverlap = currentMate != null
&& playedChangeOnCurrent
&& changeOnBothMates
&& currentTransitionDelay > delay;
if (delay > 0f)
await WaitForSecondsSwapTimingAsync(delay, useUnscaledSwapTiming);
targetMateId = ResolvePremiumMateTargetId(state, targetMateId);
if (!state.Forms.TryGetValue(targetMateId, out nextMate) || nextMate == null)
return;
ResetMateTransform(nextMate, state.Anchor);
if (currentMate != null && !keepCurrentAliveForOverlap)
currentMate.gameObject.SetActive(false);
SetExclusivePremiumMateActiveForm(state, nextMate, keepCurrentAliveForOverlap ? currentMate : null);
nextMate.PrepareForPremiumSwapActivation();
state.ActiveMateId = targetMateId;
SetMateForSide(state.Side, nextMate);
if (state.SwapEffect.PreferNonChangeNextWhenChangeTriggerMissing
&& triggerPriorityOverride == null
&& HasConfiguredChangeTrigger(nextTriggerPriority)
&& !MateHasAnyChangeTriggerParameter(nextMate, nextTriggerPriority))
{
var reorderedPriority = BuildNonChangeFirstPriority(nextTriggerPriority);
if (!ReferenceEquals(reorderedPriority, nextTriggerPriority))
effectiveNextTriggerPriority = reorderedPriority;
}
if (state.SwapEffect.NextMotionLeadInSeconds > 0f)
await WaitForSecondsUnscaledAsync(state.SwapEffect.NextMotionLeadInSeconds);
var hasNextActivationTrigger = HasNonChangeTrigger(effectiveNextTriggerPriority);
var shouldPlayNextMotion = state.SwapEffect.UseChangeMotion
&& (playChangeOnNextMate || !playedChangeOnCurrent || hasNextActivationTrigger);
if (shouldPlayNextMotion)
{
playedChangeOnNext = nextMate.PlayChangeTransition(effectiveNextTriggerPriority, out nextTransitionDelay);
var nextStageWait = nextTransitionDelay;
if (state.SwapEffect.NextMotionMinDurationSeconds > 0f)
nextStageWait = Mathf.Max(nextStageWait, state.SwapEffect.NextMotionMinDurationSeconds);
if (nextStageWait > 0f)
await WaitForSecondsUnscaledAsync(nextStageWait);
if (state.SwapEffect.NextMotionDelaySeconds > 0f)
await WaitForSecondsUnscaledAsync(state.SwapEffect.NextMotionDelaySeconds);
}
if (keepCurrentAliveForOverlap && currentMate != null)
currentMate.gameObject.SetActive(false);
RestartMateRandomCooldown(state.Side);
}
private async UniTask<SwapEffectLabelPlaybackResult> SpawnPremiumSwapEffectAsync(PremiumMateState state, string effectPath, Vector3 position, string effectLabel = null, bool toSub = true)
{
if (state == null || state.SwapEffect == null)
return SwapEffectLabelPlaybackResult.None;
if (!state.SwapEffect.HasChangeEffectAsset(toSub))
return SwapEffectLabelPlaybackResult.None;
var fullPath = Path.Combine(Program.root, effectPath);
GameObject effect = null;
var selectedScore = int.MinValue;
void ConsiderEffectCandidate(GameObject candidate)
{
if (candidate == null)
return;
var score = GetSwapEffectCandidateScore(candidate);
if (effect == null || score > selectedScore)
{
if (effect != null && effect != candidate)
UnityEngine.Object.Destroy(effect);
effect = candidate;
selectedScore = score;
}
else
{
UnityEngine.Object.Destroy(candidate);
}
}
if (Directory.Exists(fullPath))
{
var leafName = Path.GetFileName(effectPath);
var directBundlePath = Path.Combine(fullPath, leafName);
if (!string.IsNullOrEmpty(leafName) && File.Exists(directBundlePath))
{
var loadPath = effectPath + "/" + leafName;
ConsiderEffectCandidate(await ABLoader.LoadFromFileAsync(loadPath, false, true));
}
ConsiderEffectCandidate(await ABLoader.LoadFromFolderAsync<PlayableDirector>(effectPath, false, true));
ConsiderEffectCandidate(await ABLoader.LoadFromFolderAsync<ParticleSystem>(effectPath, false, true));
}
else
{
ConsiderEffectCandidate(await ABLoader.LoadFromFileAsync(effectPath, false, true));
}
if (effect == null)
return SwapEffectLabelPlaybackResult.None;
effect.transform.SetParent(Program.instance.container_3D, false);
effect.transform.position = position;
var labelResult = await PlayPremiumSwapEffectLabelAsync(effect, effectLabel);
UnityEngine.Object.Destroy(effect, 5f);
return labelResult;
}
private async UniTask<SwapEffectLabelPlaybackResult> PlayPremiumSwapEffectLabelAsync(GameObject effect, string effectLabel)
{
if (effect == null)
return SwapEffectLabelPlaybackResult.None;
var labelPlayed = false;
var fallbackPlayed = false;
var animators = effect.GetComponentsInChildren<Animator>(true);
for (var i = 0; i < animators.Length; i++)
{
var animator = animators[i];
if (animator == null)
continue;
if (TryPlayAnimatorEffectLabel(animator, effectLabel))
labelPlayed = true;
}
var directors = effect.GetComponentsInChildren<PlayableDirector>(true);
for (var i = 0; i < directors.Length; i++)
{
var director = directors[i];
if (director == null)
continue;
if (TryPlayDirectorEffectLabel(director, effectLabel, out var startTime))
{
director.time = startTime;
director.Play();
labelPlayed = true;
}
}
var changeEffects = effect.GetComponentsInChildren<BgAvatarChangeEffect>(true);
if (!labelPlayed && !string.IsNullOrEmpty(effectLabel))
{
for (var i = 0; i < changeEffects.Length; i++)
{
if (!TryPlayBgAvatarChangeEffectLabel(changeEffects[i], effectLabel))
continue;
labelPlayed = true;
break;
}
}
if (!labelPlayed && !string.IsNullOrEmpty(effectLabel))
{
if (TryPlayElementObjectLabel(effect, effectLabel))
labelPlayed = true;
else if (TryPlayAnyComponentLabel(effect, effectLabel))
labelPlayed = true;
}
var labeledControllers = effect.GetComponentsInChildren<LabeledPlayableController>(true);
if (!labelPlayed && !string.IsNullOrEmpty(effectLabel) && labeledControllers.Length > 0)
{
for (var i = 0; i < directors.Length; i++)
{
var director = directors[i];
if (director == null || director.state == PlayState.Playing)
continue;
director.Play();
}
const int maxFramesToWait = 3;
for (var frame = 0; frame <= maxFramesToWait && !labelPlayed; frame++)
{ {
gps = new() for (var i = 0; i < labeledControllers.Length; i++)
{ {
controller = c, var controller = labeledControllers[i];
location = (uint)CardLocation.SpellZone, if (controller == null || controller.loopMixerBehaviour == null)
sequence = s continue;
};
CreatePlaceSelector(gps);
}
}
#endregion controller.PlayLabel(effectLabel, (TimelineClip)null);
labelPlayed = true;
break;
}
#region Quit Load if (!labelPlayed && frame < maxFramesToWait)
await UniTask.Yield();
}
}
await processor.PreloadPlayerNames(); if (!labelPlayed && !string.IsNullOrEmpty(effectLabel) && directors.Length == 1)
if (Core.NeedVoice())
{ {
Core.GetUI<OcgCoreUI>().CG.alpha = 1; var director = directors[0];
Core.GetUI<OcgCoreUI>().CG.blocksRaycasts = true; if (director != null)
{
director.time = 0d;
director.Play();
fallbackPlayed = true;
}
} }
Core.GetUI<OcgCoreUI>().Buttons.SetActive(false);
UIManager.ShowFPSLeft(); return new SwapEffectLabelPlaybackResult(labelPlayed || fallbackPlayed);
UIManager.HideBlackBack(0f); }
UIManager.UIBlackOut(Core.TransitionTime);
await UniTask.WaitForSeconds(Core.TransitionTime);
backgroundFieldInitialize = false; private static bool TryPlayBgAvatarChangeEffectLabel(BgAvatarChangeEffect changeEffect, string effectLabel)
BackgroundFieldInitialize(); {
if (changeEffect == null || string.IsNullOrEmpty(effectLabel))
return false;
if (condition == Condition.Duel && Config.GetBool("DuelAutoAcc", false) var toMain = LabelEquals(changeEffect.toMainLabel, effectLabel);
|| condition == Condition.Watch && Config.GetBool("WatchAutoAcc", false) var toSub = LabelEquals(changeEffect.toSubLabel, effectLabel);
|| condition == Condition.Replay && Config.GetBool("ReplayAutoAcc", false)) if (!toMain && !toSub)
Core.GetUI<OcgCoreUI>().OnAcc(); return false;
#endregion var fieldName = toMain ? "toMainObj" : "toSubObj";
var field = typeof(BgAvatarChangeEffect).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var particle = field != null ? field.GetValue(changeEffect) as ParticleSystem : null;
if (particle != null)
{
PlayTargetObject(particle.gameObject);
return true;
}
loaded = true; if (TryPlayElementObjectLabel(changeEffect.gameObject, effectLabel))
return true;
return TryPlayAnyComponentLabel(changeEffect.gameObject, effectLabel);
} }
public async UniTask ExitDuelAsync() private static bool TryPlayElementObjectLabel(GameObject root, string effectLabel)
{ {
ClearResponse(); if (root == null || string.IsNullOrEmpty(effectLabel))
CameraManager.BlackOut(0f, 0.3f); return false;
UIManager.UIBlackIn(Core.TransitionTime); var elements = root.GetComponentsInChildren<ElementObject>(true);
Core.GetUI<OcgCoreUI>().CloseHint(); var played = false;
HideAttackLine(); for (var i = 0; i < elements.Length; i++)
HideDuelFinalBlowText(); {
await UniTask.WaitForSeconds(Core.TransitionTime); var element = elements[i];
Core.servantUI.ShutDown(); if (element == null || !LabelEquals(element.label, effectLabel))
continue;
NoMoreWait = true; PlayTargetObject(element.gameObject);
packages.Clear(); played = true;
allPackages.Clear(); }
AudioManager.ResetSESource();
mycardDuel = false;
Core.CloseCharaFace();
Dispose();
foreach (var card in cards) return played;
card.Dispose();
cards.Clear();
pause = false;
nextMoveAction = null;
Core.cachedCharaFaces.Clear();
CameraManager.ShiftTo2D();
Core.GetUI<OcgCoreUI>().Buttons.SetActive(false);
Core.GetUI<OcgCoreUI>().DuelLog.ClearLog();
Program.instance.ui_.chatPanel.Hide();
await UniTask.WaitForSeconds(0.3f);
UIManager.UIBlackOut(Core.TransitionTime);
await UniTask.WaitForSeconds(Core.TransitionTime);
UIManager.ShowFPSRight();
AudioManager.PlayBGM(AudioManager.BGM_MENU_MAIN);
} }
private void InitializeDeckModel(ElementObjectManager deckManager, int player, CardLocation location) private static bool TryPlayAnyComponentLabel(GameObject root, string effectLabel)
{ {
deckManager.transform.SetParent(player == 0 ? field0Manager.transform : field1Manager.transform, false); if (root == null || string.IsNullOrEmpty(effectLabel))
deckManager.transform.localPosition = _positionMap[(player, location)]; return false;
deckManager.transform.localEulerAngles = _angleMap[(player, location)];
allGameObjects.Add(deckManager.gameObject);
var mat = player == 0 ? myProtector : opProtector; var behaviours = root.GetComponentsInChildren<MonoBehaviour>(true);
deckManager.GetNestedElement<MeshRenderer>("DummyDeck/DummyCardModel_back").material = mat; var played = false;
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel01_back").material = mat; for (var i = 0; i < behaviours.Length; i++)
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel02_back").material = mat; {
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel03_back").material = mat; var behaviour = behaviours[i];
deckManager.GetNestedElement<MeshRenderer>("CardShuffleTop/CardModel04_back").material = mat; if (behaviour == null)
continue;
deckManager.gameObject.SetActive(false); var field = behaviour.GetType().GetField("label", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field == null || field.FieldType != typeof(string))
continue;
var value = field.GetValue(behaviour) as string;
if (!LabelEquals(value, effectLabel))
continue;
PlayTargetObject(behaviour.gameObject);
played = true;
}
return played;
} }
private static readonly Dictionary<(int, CardLocation), Vector3> _positionMap = new() private static void PlayTargetObject(GameObject target)
{ {
[(0, CardLocation.Deck)] = new(26.6f, 1.5f, -23.5f), if (target == null)
[(0, CardLocation.Extra)] = new(-26.6f, 1.5f, -23.5f), return;
[(1, CardLocation.Deck)] = new(-26.6f, 1.5f, 23.5f),
[(1, CardLocation.Extra)] = new(26.6f, 1.5f, 23.5f)
};
private static readonly Dictionary<(int, CardLocation), Vector3> _angleMap = new() target.SetActive(true);
var particles = target.GetComponentsInChildren<ParticleSystem>(true);
for (var i = 0; i < particles.Length; i++)
{
var particle = particles[i];
if (particle == null)
continue;
particle.gameObject.SetActive(true);
particle.Clear(true);
particle.Play(true);
}
var animators = target.GetComponentsInChildren<Animator>(true);
for (var i = 0; i < animators.Length; i++)
{
var animator = animators[i];
if (animator == null)
continue;
animator.gameObject.SetActive(true);
animator.Rebind();
animator.Update(0f);
animator.Play(0, 0, 0f);
}
var directors = target.GetComponentsInChildren<PlayableDirector>(true);
for (var i = 0; i < directors.Length; i++)
{
var director = directors[i];
if (director == null)
continue;
director.gameObject.SetActive(true);
director.time = 0d;
director.Play();
}
}
private static bool LabelEquals(string a, string b)
{ {
[(0, CardLocation.Deck)] = new(0f, -20f, 0f), return !string.IsNullOrEmpty(a)
[(0, CardLocation.Extra)] = new(0f, 20f, 0f), && !string.IsNullOrEmpty(b)
[(1, CardLocation.Deck)] = new(0f, 160f, 0f), && a.Equals(b, StringComparison.OrdinalIgnoreCase);
[(1, CardLocation.Extra)] = new(0f, -160f, 0f) }
};
public async UniTask ShowDecksAsync() private static bool TryPlayDirectorEffectLabel(PlayableDirector director, string effectLabel, out double startTime)
{ {
if(myDeck == null || myExtra == null startTime = 0d;
|| opDeck == null || opExtra == null) if (director == null || string.IsNullOrEmpty(effectLabel))
return; return false;
await ShowAllDeckModelsAsync(); var playableName = director.playableAsset != null ? director.playableAsset.name : string.Empty;
var nameMatch =
(!string.IsNullOrEmpty(playableName) && playableName.IndexOf(effectLabel, StringComparison.OrdinalIgnoreCase) >= 0)
|| director.name.IndexOf(effectLabel, StringComparison.OrdinalIgnoreCase) >= 0
|| director.gameObject.name.IndexOf(effectLabel, StringComparison.OrdinalIgnoreCase) >= 0;
if (nameMatch)
return true;
Core.GetUI<OcgCoreUI>().CG.alpha = 1; return TryGetDirectorLabelStartTime(director, effectLabel, out startTime);
Core.GetUI<OcgCoreUI>().CG.blocksRaycasts = true;
Core.GetUI<OcgCoreUI>().Buttons.SetActive(true);
AudioManager.PlayBgmNormal(Config.GetBool("BGMbyMySide", true) ? field0Manager.name : field1Manager.name);
} }
public async UniTask ShowDecksWithDuelStartTextAsync() private static bool TryGetDirectorLabelStartTime(PlayableDirector director, string effectLabel, out double startTime)
{ {
if (myDeck == null || myExtra == null startTime = 0d;
|| opDeck == null || opExtra == null) if (director == null || string.IsNullOrEmpty(effectLabel) || director.playableAsset == null)
return; return false;
await ShowAllDeckModelsAsync(); var outputs = director.playableAsset.outputs;
foreach (var output in outputs)
{
var track = output.sourceObject as TrackAsset;
if (track == null)
continue;
var effect = ABLoader.LoadMasterDuelGameObject("DuelTextStart"); foreach (var clip in track.GetClips())
var director = effect.GetComponent<PlayableDirector>(); {
await director.WaitAsync(); if (clip == null)
UnityEngine.Object.Destroy(effect); continue;
Core.GetUI<OcgCoreUI>().CG.alpha = 1; var displayName = clip.displayName;
Core.GetUI<OcgCoreUI>().CG.blocksRaycasts = true; if (!string.IsNullOrEmpty(displayName)
Core.GetUI<OcgCoreUI>().Buttons.SetActive(true); && displayName.Equals(effectLabel, StringComparison.OrdinalIgnoreCase))
AudioManager.PlayBgmNormal(Config.GetBool("BGMbyMySide", true) ? field0Manager.name : field1Manager.name); {
startTime = clip.start;
return true;
}
}
}
return false;
} }
public void BackgroundFieldInitialize() private static int GetSwapEffectCandidateScore(GameObject candidate)
{ {
if (field0Manager == null || field1Manager == null) if (candidate == null)
return; return int.MinValue;
if (bgPhase0 == 1 && bgPhase1 == 1) return;
var labeledControllers = candidate.GetComponentsInChildren<LabeledPlayableController>(true).Length;
var directors = candidate.GetComponentsInChildren<PlayableDirector>(true).Length;
var animators = candidate.GetComponentsInChildren<Animator>(true).Length;
var particles = candidate.GetComponentsInChildren<ParticleSystem>(true).Length;
var changeEffects = candidate.GetComponentsInChildren<BgAvatarChangeEffect>(true).Length;
var elementLabels = candidate.GetComponentsInChildren<ElementObject>(true).Length;
return labeledControllers * 100 + changeEffects * 60 + directors * 20 + animators * 8 + elementLabels * 4 + particles * 2;
}
if (backgroundFieldInitialize) private static bool TryPlayAnimatorEffectLabel(Animator animator, string effectLabel)
{
if (animator == null || string.IsNullOrEmpty(effectLabel))
return false;
var played = false;
var parameters = animator.parameters;
for (var i = 0; i < parameters.Length; i++)
{ {
field0Manager.gameObject.SetActive(false); var parameter = parameters[i];
field1Manager.gameObject.SetActive(false); if (parameter.type != AnimatorControllerParameterType.Trigger)
field0Manager.gameObject.SetActive(true); continue;
field1Manager.gameObject.SetActive(true); if (!parameter.name.Equals(effectLabel, StringComparison.OrdinalIgnoreCase))
continue;
animator.SetTrigger(parameter.name);
played = true;
} }
if (played)
return true;
field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1); for (var layer = 0; layer < animator.layerCount; layer++)
grave0Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1);
bgPhase0 = 1;
field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1);
grave1Manager.PlayAnimatorTrigger(TriggerLabelDefine.StartToPhase1);
bgPhase1 = 1;
if (mate0 != null)
{ {
mate0.gameObject.SetActive(true); var layerName = animator.GetLayerName(layer);
mate0.Play(Mate.MateAction.Entry); var shortHash = Animator.StringToHash(effectLabel);
var fullHash = Animator.StringToHash($"{layerName}.{effectLabel}");
if (animator.HasState(layer, fullHash))
{
animator.Play(fullHash, layer, 0f);
played = true;
continue;
}
if (animator.HasState(layer, shortHash))
{
animator.Play(shortHash, layer, 0f);
played = true;
}
} }
if (mate1 != null) if (played)
return true;
var controller = animator.runtimeAnimatorController;
if (controller == null)
return false;
var clips = controller.animationClips;
for (var i = 0; i < clips.Length; i++)
{ {
mate1.gameObject.SetActive(true); var clip = clips[i];
mate1.Play(Mate.MateAction.Entry); if (clip == null)
continue;
if (!clip.name.Equals(effectLabel, StringComparison.OrdinalIgnoreCase))
continue;
animator.Play(clip.name, 0, 0f);
return true;
} }
if (timerHandler != null)
timerHandler.DuelStart();
mate0Random = false; return false;
mate1Random = false; }
DOTween.To(v => { }, 0, 0, UnityEngine.Random.Range(8, 16)).OnComplete(() =>
{
mate0Random = true;
});
DOTween.To(v => { }, 0, 0, UnityEngine.Random.Range(8, 16)).OnComplete(() =>
{
mate1Random = true;
});
backgroundFieldInitialize = true; private Mate GetMateForSide(int side)
{
return side == 0 ? mate0 : mate1;
}
private void SetMateForSide(int side, Mate mate)
{
if (side == 0)
mate0 = mate;
else
mate1 = mate;
}
private PremiumMateState GetPremiumMateState(int side)
{
return side == 0 ? premiumMate0 : premiumMate1;
}
private void SetPremiumMateState(int side, PremiumMateState state)
{
if (side == 0)
premiumMate0 = state;
else
premiumMate1 = state;
}
private void ResetPremiumMateStates()
{
premiumMate0 = null;
premiumMate1 = null;
mate0RandomCooldownTween?.Kill();
mate0RandomCooldownTween = null;
mate1RandomCooldownTween?.Kill();
mate1RandomCooldownTween = null;
} }
public void RefreshBgState() public void RefreshBgState()
...@@ -741,7 +1930,7 @@ namespace MDPro3.Duel ...@@ -741,7 +1930,7 @@ namespace MDPro3.Duel
{ {
field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.PhaseToDamagePhaseAll); field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.PhaseToDamagePhaseAll);
//field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhaseToNextPhaseAll); //field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhaseToNextPhaseAll);
if (mate0 != null && !first) if (!first && CanPlayMateAction(0, mate0))
mate0.Play(Mate.MateAction.GetDamage); mate0.Play(Mate.MateAction.GetDamage);
if (bgPhase0 == 1 && life0 < (lpLimit * 0.75f)) if (bgPhase0 == 1 && life0 < (lpLimit * 0.75f))
{ {
...@@ -790,7 +1979,7 @@ namespace MDPro3.Duel ...@@ -790,7 +1979,7 @@ namespace MDPro3.Duel
{ {
field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.PhaseToDamagePhaseAll); field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.PhaseToDamagePhaseAll);
//field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhaseToNextPhaseAll); //field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhaseToNextPhaseAll);
if (mate1 != null && !first) if (!first && CanPlayMateAction(1, mate1))
mate1.Play(Mate.MateAction.GetDamage); mate1.Play(Mate.MateAction.GetDamage);
if (bgPhase1 == 1 && life1 < (lpLimit * 0.75f)) if (bgPhase1 == 1 && life1 < (lpLimit * 0.75f))
{ {
...@@ -1085,6 +2274,9 @@ namespace MDPro3.Duel ...@@ -1085,6 +2274,9 @@ namespace MDPro3.Duel
foreach (var go in allGameObjects) foreach (var go in allGameObjects)
UnityEngine.Object.Destroy(go); UnityEngine.Object.Destroy(go);
allGameObjects.Clear(); allGameObjects.Clear();
ResetPremiumMateStates();
mate0 = null;
mate1 = null;
} }
public async UniTask ShowDuelResultText(string text) public async UniTask ShowDuelResultText(string text)
...@@ -1148,7 +2340,7 @@ namespace MDPro3.Duel ...@@ -1148,7 +2340,7 @@ namespace MDPro3.Duel
void HeroWin() void HeroWin()
{ {
field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.EndWin); field0Manager.PlayAnimatorTrigger(TriggerLabelDefine.EndWin);
if (mate0 != null) if (CanPlayMateAction(0, mate0))
mate0.Play(Mate.MateAction.Victory); mate0.Play(Mate.MateAction.Victory);
} }
...@@ -1164,14 +2356,14 @@ namespace MDPro3.Duel ...@@ -1164,14 +2356,14 @@ namespace MDPro3.Duel
if (stand0Manager != null) if (stand0Manager != null)
stand0Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhase4ToEnd); stand0Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhase4ToEnd);
if (mate0 != null) if (CanPlayMateAction(0, mate0))
mate0.Play(Mate.MateAction.Defeat); mate0.Play(Mate.MateAction.Defeat);
} }
void RivalWin() void RivalWin()
{ {
field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.EndWin); field1Manager.PlayAnimatorTrigger(TriggerLabelDefine.EndWin);
if (mate1 != null) if (CanPlayMateAction(1, mate1))
mate1.Play(Mate.MateAction.Victory); mate1.Play(Mate.MateAction.Victory);
} }
...@@ -1187,7 +2379,7 @@ namespace MDPro3.Duel ...@@ -1187,7 +2379,7 @@ namespace MDPro3.Duel
if (stand1Manager != null) if (stand1Manager != null)
stand1Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhase4ToEnd); stand1Manager.PlayAnimatorTrigger(TriggerLabelDefine.DamagePhase4ToEnd);
if (mate1 != null) if (CanPlayMateAction(1, mate1))
mate1.Play(Mate.MateAction.Defeat); mate1.Play(Mate.MateAction.Defeat);
} }
...@@ -2470,10 +3662,12 @@ namespace MDPro3.Duel ...@@ -2470,10 +3662,12 @@ namespace MDPro3.Duel
public bool HoveringMate0() public bool HoveringMate0()
{ {
if(mate0 == null) if (mate0 == null)
return false; return false;
if (UserInput.HoverObject == mate0.gameObject) if (UserInput.HoverObject == mate0.gameObject)
return true; return true;
if (UserInput.HoverObject != null && UserInput.HoverObject.transform.IsChildOf(mate0.transform))
return true;
return false; return false;
} }
...@@ -2483,12 +3677,14 @@ namespace MDPro3.Duel ...@@ -2483,12 +3677,14 @@ namespace MDPro3.Duel
return false; return false;
if (UserInput.HoverObject == mate1.gameObject) if (UserInput.HoverObject == mate1.gameObject)
return true; return true;
if (UserInput.HoverObject != null && UserInput.HoverObject.transform.IsChildOf(mate1.transform))
return true;
return false; return false;
} }
public void TapMate0() public void TapMate0()
{ {
if (mate0 == null) if (!CanPlayMateAction(0, mate0, false))
return; return;
if (Time.time - mate0TapTime < 1f) if (Time.time - mate0TapTime < 1f)
return; return;
...@@ -2498,7 +3694,7 @@ namespace MDPro3.Duel ...@@ -2498,7 +3694,7 @@ namespace MDPro3.Duel
public void TapMate1() public void TapMate1()
{ {
if (mate1 == null) if (!CanPlayMateAction(1, mate1, false))
return; return;
if (Time.time - mate1TapTime < 1f) if (Time.time - mate1TapTime < 1f)
return; return;
...@@ -2508,28 +3704,84 @@ namespace MDPro3.Duel ...@@ -2508,28 +3704,84 @@ namespace MDPro3.Duel
public void PlayMate0Random() public void PlayMate0Random()
{ {
if (mate0 == null) if (!CanPlayMateAction(0, mate0))
return;
if (ShouldSuppressAmbientPremiumMateActions(GetPremiumMateState(0)))
return;
if (!mate0Random)
return;
mate0.Play(Mate.MateAction.Random);
RestartMateRandomCooldown(0);
}
public void PlayMate1Random()
{
if (!CanPlayMateAction(1, mate1))
return; return;
if (mate0Random) if (ShouldSuppressAmbientPremiumMateActions(GetPremiumMateState(1)))
return;
if (!mate1Random)
return;
mate1.Play(Mate.MateAction.Random);
RestartMateRandomCooldown(1);
}
private bool CanPlayMateAction(int side, Mate mate, bool skipWhileTransitioning = true)
{
if (mate == null)
return false;
if (skipWhileTransitioning && IsPremiumMateTransitioning(side))
return false;
return true;
}
private bool IsPremiumMateTransitioning(int side)
{
var state = GetPremiumMateState(side);
return state != null && state.IsTransitioning;
}
private void RestartMateRandomCooldown(int side)
{
var premiumState = GetPremiumMateState(side);
if (ShouldSuppressAmbientPremiumMateActions(premiumState))
{
if (side == 0)
{
mate0Random = false;
mate0RandomCooldownTween?.Kill();
mate0RandomCooldownTween = null;
}
else
{
mate1Random = false;
mate1RandomCooldownTween?.Kill();
mate1RandomCooldownTween = null;
}
return;
}
var mate = side == 0 ? mate0 : mate1;
var delay = mate != null && mate.type == Mate.MateType.MasterDuel
? UnityEngine.Random.Range(20f, 70f)
: UnityEngine.Random.Range(8f, 16f);
if (side == 0)
{ {
mate0Random = false; mate0Random = false;
mate0.Play(Mate.MateAction.Random); mate0RandomCooldownTween?.Kill();
DOTween.To(v => { }, 0, 0, UnityEngine.Random.Range(8, 16)).OnComplete(() => mate0RandomCooldownTween = DOTween.To(v => { }, 0, 0, delay).OnComplete(() =>
{ {
mate0Random = true; mate0Random = true;
}); });
} }
} else
public void PlayMate1Random()
{
if (mate1 == null)
return;
if (mate1Random)
{ {
mate1Random = false; mate1Random = false;
mate1.Play(Mate.MateAction.Random); mate1RandomCooldownTween?.Kill();
DOTween.To(v => { }, 0, 0, UnityEngine.Random.Range(8, 16)).OnComplete(() => mate1RandomCooldownTween = DOTween.To(v => { }, 0, 0, delay).OnComplete(() =>
{ {
mate1Random = true; mate1Random = true;
}); });
......
using System.Collections.Generic;
using System.Linq;
namespace MDPro3.Duel
{
public enum PremiumMateBehavior
{
LaundryBattlePhaseRoundTrip,
GaiaExtraDeckPermanent,
ShuraigLpThreshold,
RayeBattlePhaseAndDirectAttack,
FiendsmithExtraDeckOrEquipPermanent,
IpSpTurnParity
}
public sealed class PremiumMateRule
{
public int BaseId { get; }
public int SubId { get; }
public IReadOnlyList<int> VariantIds { get; }
public PremiumMateBehavior Behavior { get; }
public int LpThreshold { get; }
public PremiumMateRule(
int baseId,
int subId,
PremiumMateBehavior behavior,
int lpThreshold = 0,
IReadOnlyList<int> extraVariantIds = null)
{
BaseId = baseId;
SubId = subId;
Behavior = behavior;
LpThreshold = lpThreshold;
var variants = new List<int> { subId };
if (extraVariantIds != null)
foreach (var variantId in extraVariantIds)
if (variantId > 0 && variantId != baseId && !variants.Contains(variantId))
variants.Add(variantId);
VariantIds = variants;
}
}
public static class PremiumMateRules
{
private static readonly List<PremiumMateRule> _rules = new()
{
new PremiumMateRule(1000020, 1000021, PremiumMateBehavior.LaundryBattlePhaseRoundTrip),
new PremiumMateRule(1003001, 1003101, PremiumMateBehavior.GaiaExtraDeckPermanent),
new PremiumMateRule(1003002, 1003102, PremiumMateBehavior.ShuraigLpThreshold, 3000),
new PremiumMateRule(1003003, 1003203, PremiumMateBehavior.RayeBattlePhaseAndDirectAttack, 0, new[] { 1003103 }),
new PremiumMateRule(1003004, 1003104, PremiumMateBehavior.FiendsmithExtraDeckOrEquipPermanent),
new PremiumMateRule(1003005, 1003105, PremiumMateBehavior.IpSpTurnParity),
};
private static readonly Dictionary<int, PremiumMateRule> _ruleByAnyId = _rules
.SelectMany(rule => rule.VariantIds
.Select(id => new KeyValuePair<int, PremiumMateRule>(id, rule))
.Prepend(new KeyValuePair<int, PremiumMateRule>(rule.BaseId, rule)))
.ToDictionary(pair => pair.Key, pair => pair.Value);
private static readonly Dictionary<int, PremiumMateRule> _ruleByBaseId = _rules
.ToDictionary(rule => rule.BaseId, rule => rule);
public static IReadOnlyList<PremiumMateRule> All => _rules;
public static bool TryGetRule(int mateId, out PremiumMateRule rule)
{
return _ruleByAnyId.TryGetValue(mateId, out rule);
}
public static bool TryGetRuleByBaseId(int mateId, out PremiumMateRule rule)
{
return _ruleByBaseId.TryGetValue(mateId, out rule);
}
public static bool IsPremiumMateId(int mateId)
{
return _ruleByAnyId.ContainsKey(mateId);
}
public static bool IsPremiumBaseId(int mateId)
{
return _ruleByBaseId.ContainsKey(mateId);
}
public static bool IsPremiumVariantId(int mateId)
{
return TryGetRule(mateId, out var rule) && rule.VariantIds.Contains(mateId);
}
public static int GetBaseMateId(int mateId)
{
return TryGetRule(mateId, out var rule) ? rule.BaseId : mateId;
}
public static int GetSubMateId(int mateId)
{
return TryGetRule(mateId, out var rule) ? rule.SubId : mateId;
}
public static List<Items.Item> FilterAppearanceMateItems(IEnumerable<Items.Item> source)
{
return source.Where(item => !IsPremiumVariantId(item.id)).ToList();
}
}
}
fileFormatVersion: 2
guid: 555a55c03fd6e3d45bc341e9808a721c
\ No newline at end of file
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
namespace MDPro3.Duel
{
public sealed class PremiumMateSwapEffect
{
public int BaseId { get; }
public float DelaySeconds { get; }
public float ToSubDelaySeconds { get; }
public float ToBaseDelaySeconds { get; }
public bool UseChangeMotion { get; }
public bool UseTransitionDelayAsMinimum { get; }
public string EffectAssetPath { get; }
public string ToSubEffectAssetPath { get; }
public string ToBaseEffectAssetPath { get; }
public string ToSubEffectLabel { get; }
public string ToBaseEffectLabel { get; }
public bool PreferEffectLabelPlayback { get; }
public bool PlayChangeOnTargetMate { get; }
public bool PlayChangeOnBothMates { get; }
public bool PreferNonChangeNextWhenChangeTriggerMissing { get; }
public bool CompensateCurrentTriggerQueueDelay { get; }
public float MaxCurrentTriggerQueueDelaySeconds { get; }
public bool UseUnscaledSwapTiming { get; }
public float NextMotionDelaySeconds { get; }
public float SourceToEffectDelaySeconds { get; }
public float NextMotionLeadInSeconds { get; }
public float NextMotionMinDurationSeconds { get; }
public IReadOnlyList<string> ToSubTriggerPriority { get; }
public IReadOnlyList<string> ToBaseTriggerPriority { get; }
public IReadOnlyList<string> ToSubCurrentTriggerPriority { get; }
public IReadOnlyList<string> ToBaseCurrentTriggerPriority { get; }
public IReadOnlyList<string> ToSubNextTriggerPriority { get; }
public IReadOnlyList<string> ToBaseNextTriggerPriority { get; }
public IReadOnlyList<string> DuelStartTriggerPriority { get; }
private static readonly string[] DefaultToSubTriggers = { "Change", "Change1", "ChangePreHide", "Change2" };
private static readonly string[] DefaultToBaseTriggers = { "Change2", "ChangeBack", "ChangePreHide", "Change" };
public PremiumMateSwapEffect(
int baseId,
float delaySeconds,
bool useChangeMotion,
string effectAssetPath,
bool useTransitionDelayAsMinimum = true,
float toSubDelaySeconds = -1f,
float toBaseDelaySeconds = -1f,
IReadOnlyList<string> toSubTriggerPriority = null,
IReadOnlyList<string> toBaseTriggerPriority = null,
IReadOnlyList<string> toSubCurrentTriggerPriority = null,
IReadOnlyList<string> toBaseCurrentTriggerPriority = null,
IReadOnlyList<string> toSubNextTriggerPriority = null,
IReadOnlyList<string> toBaseNextTriggerPriority = null,
IReadOnlyList<string> duelStartTriggerPriority = null,
string toSubEffectAssetPath = null,
string toBaseEffectAssetPath = null,
string toSubEffectLabel = null,
string toBaseEffectLabel = null,
bool preferEffectLabelPlayback = false,
bool playChangeOnTargetMate = false,
bool playChangeOnBothMates = false,
bool preferNonChangeNextWhenChangeTriggerMissing = false,
bool compensateCurrentTriggerQueueDelay = false,
float maxCurrentTriggerQueueDelaySeconds = 0f,
bool useUnscaledSwapTiming = false,
float nextMotionDelaySeconds = 0f,
float sourceToEffectDelaySeconds = 0f,
float nextMotionLeadInSeconds = 0f,
float nextMotionMinDurationSeconds = 0f)
{
BaseId = baseId;
DelaySeconds = Mathf.Max(0f, delaySeconds);
ToSubDelaySeconds = toSubDelaySeconds >= 0f ? toSubDelaySeconds : DelaySeconds;
ToBaseDelaySeconds = toBaseDelaySeconds >= 0f ? toBaseDelaySeconds : DelaySeconds;
UseChangeMotion = useChangeMotion;
UseTransitionDelayAsMinimum = useTransitionDelayAsMinimum;
EffectAssetPath = effectAssetPath;
ToSubEffectAssetPath = toSubEffectAssetPath ?? effectAssetPath;
ToBaseEffectAssetPath = toBaseEffectAssetPath ?? effectAssetPath;
ToSubEffectLabel = toSubEffectLabel;
ToBaseEffectLabel = toBaseEffectLabel;
PreferEffectLabelPlayback = preferEffectLabelPlayback;
PlayChangeOnTargetMate = playChangeOnTargetMate;
PlayChangeOnBothMates = playChangeOnBothMates;
PreferNonChangeNextWhenChangeTriggerMissing = preferNonChangeNextWhenChangeTriggerMissing;
CompensateCurrentTriggerQueueDelay = compensateCurrentTriggerQueueDelay;
MaxCurrentTriggerQueueDelaySeconds = Mathf.Max(0f, maxCurrentTriggerQueueDelaySeconds);
UseUnscaledSwapTiming = useUnscaledSwapTiming;
NextMotionDelaySeconds = Mathf.Max(0f, nextMotionDelaySeconds);
SourceToEffectDelaySeconds = Mathf.Max(0f, sourceToEffectDelaySeconds);
NextMotionLeadInSeconds = Mathf.Max(0f, nextMotionLeadInSeconds);
NextMotionMinDurationSeconds = Mathf.Max(0f, nextMotionMinDurationSeconds);
ToSubTriggerPriority = toSubTriggerPriority != null && toSubTriggerPriority.Count > 0
? toSubTriggerPriority.ToArray()
: DefaultToSubTriggers;
ToBaseTriggerPriority = toBaseTriggerPriority != null && toBaseTriggerPriority.Count > 0
? toBaseTriggerPriority.ToArray()
: DefaultToBaseTriggers;
ToSubCurrentTriggerPriority = toSubCurrentTriggerPriority != null && toSubCurrentTriggerPriority.Count > 0
? toSubCurrentTriggerPriority.ToArray()
: ToSubTriggerPriority;
ToBaseCurrentTriggerPriority = toBaseCurrentTriggerPriority != null && toBaseCurrentTriggerPriority.Count > 0
? toBaseCurrentTriggerPriority.ToArray()
: ToBaseTriggerPriority;
ToSubNextTriggerPriority = toSubNextTriggerPriority != null && toSubNextTriggerPriority.Count > 0
? toSubNextTriggerPriority.ToArray()
: ToSubTriggerPriority;
ToBaseNextTriggerPriority = toBaseNextTriggerPriority != null && toBaseNextTriggerPriority.Count > 0
? toBaseNextTriggerPriority.ToArray()
: ToBaseTriggerPriority;
DuelStartTriggerPriority = duelStartTriggerPriority != null && duelStartTriggerPriority.Count > 0
? duelStartTriggerPriority.ToArray()
: ToSubTriggerPriority;
}
public string GetEffectAssetPath(bool toSub)
{
return toSub ? ToSubEffectAssetPath : ToBaseEffectAssetPath;
}
public bool HasChangeEffectAsset(bool toSub)
{
var effectAssetPath = GetEffectAssetPath(toSub);
if (string.IsNullOrEmpty(effectAssetPath))
return false;
var fullPath = Path.Combine(Program.root, effectAssetPath);
return File.Exists(fullPath) || Directory.Exists(fullPath);
}
}
public static class PremiumMateSwapEffects
{
private static readonly PremiumMateSwapEffect _default = new(0, 0.35f, true, string.Empty);
private static readonly Dictionary<int, PremiumMateSwapEffect> _effectsByBaseId = new()
{
{ 1000020, new PremiumMateSwapEffect(
1000020,
0.45f,
true,
"MasterDuel/Mate/fxp_14759_change_001",
toSubEffectLabel: "HumanToDragon",
toBaseEffectLabel: "DragonToHuman",
useUnscaledSwapTiming: true) },
{ 1003001, new PremiumMateSwapEffect(
1003001,
1.30f,
true,
"MasterDuel/Mate/fxp_04044_change_001",
useTransitionDelayAsMinimum: false,
toSubTriggerPriority: new[] { "HorseToDragon", "Change", "Change2", "Change1", "ChangePreHide" },
toBaseTriggerPriority: new[] { "DragonToHorse", "Change", "ChangeBack", "Change2", "ChangePreHide" },
toSubEffectLabel: "HorseToDragon",
toBaseEffectLabel: "DragonToHorse",
preferEffectLabelPlayback: true) },
{ 1003002, new PremiumMateSwapEffect(
1003002,
0.75f,
true,
string.Empty,
useTransitionDelayAsMinimum: false,
toSubTriggerPriority: new[] { "Change", "Change2", "Change1", "ChangePreHide" },
toBaseTriggerPriority: new[] { "Change", "ChangeBack", "Change2", "ChangePreHide" },
toSubCurrentTriggerPriority: new[] { "ChangePreHide", "Change1", "Change2", "ChangeBack", "Change" },
toSubNextTriggerPriority: new[] { "Change", "Change2", "Change1", "ChangePreHide" },
playChangeOnTargetMate: true,
playChangeOnBothMates: true) },
{ 1003003, new PremiumMateSwapEffect(
1003003,
1.60f,
true,
"MasterDuel/Mate/fxp_M13679_gate_02",
toSubDelaySeconds: 0.90f,
toBaseDelaySeconds: 1.95f,
toBaseEffectAssetPath: string.Empty,
toSubEffectLabel: "EngageToKagari",
toBaseEffectLabel: "KagariToEngage",
useUnscaledSwapTiming: true,
toSubTriggerPriority: new[] { "Change2", "Change1", "Change", "ChangePreHide" },
toBaseTriggerPriority: new[] { "Change2", "ChangeBack", "ChangePreHide", "Change" },
toBaseCurrentTriggerPriority: new[] { "Change2", "ChangeBack", "ChangePreHide", "Change" },
toSubNextTriggerPriority: new[] { "Change2", "Change1", "Change", "Entry", "Normal", "ChangePreHide" },
toBaseNextTriggerPriority: new[] { "Entry", "Normal", "Change2", "ChangeBack", "ChangePreHide", "Change" },
duelStartTriggerPriority: new[] { "Change", "Change2", "Change1", "ChangePreHide" },
playChangeOnTargetMate: false,
preferNonChangeNextWhenChangeTriggerMissing: true) },
{ 1003004, new PremiumMateSwapEffect(
1003004,
2.80f,
true,
"MasterDuel/Mate/fxp_M20196_flash",
useTransitionDelayAsMinimum: false,
toSubEffectLabel: "M20196ToM20215",
toBaseEffectLabel: "M20215ToM20196",
preferEffectLabelPlayback: false,
playChangeOnTargetMate: true,
playChangeOnBothMates: true,
preferNonChangeNextWhenChangeTriggerMissing: false,
compensateCurrentTriggerQueueDelay: true,
maxCurrentTriggerQueueDelaySeconds: 1.50f,
useUnscaledSwapTiming: true,
nextMotionDelaySeconds: 0.45f,
sourceToEffectDelaySeconds: 0.18f,
nextMotionLeadInSeconds: 0.22f,
nextMotionMinDurationSeconds: 1.85f,
toSubNextTriggerPriority: new[] { "Change", "Entry", "Normal", "Change1", "ChangePreHide", "Change2" },
toBaseNextTriggerPriority: new[] { "Change2", "Change", "Entry", "Normal", "ChangeBack", "ChangePreHide" }) },
{ 1003005, new PremiumMateSwapEffect(
1003005,
1.35f,
true,
string.Empty,
useTransitionDelayAsMinimum: false,
toSubTriggerPriority: new[] { "Change", "Change1", "ChangePreHide", "Change2" },
toBaseTriggerPriority: new[] { "Change2", "ChangeBack", "ChangePreHide", "Change" },
toSubCurrentTriggerPriority: new[] { "Change", "Change1", "ChangePreHide", "Change2" },
toBaseCurrentTriggerPriority: new[] { "Change2", "ChangeBack", "ChangePreHide", "Change" },
toSubNextTriggerPriority: new[] { "Entry", "Normal", "Change", "Change1", "ChangePreHide", "Change2" },
toBaseNextTriggerPriority: new[] { "Entry", "Normal", "Change2", "ChangeBack", "ChangePreHide", "Change" },
playChangeOnTargetMate: false,
playChangeOnBothMates: false) },
};
public static PremiumMateSwapEffect GetOrDefault(int mateId)
{
var baseId = PremiumMateRules.GetBaseMateId(mateId);
return _effectsByBaseId.TryGetValue(baseId, out var effect) ? effect : _default;
}
public static bool TryGet(int mateId, out PremiumMateSwapEffect effect)
{
var baseId = PremiumMateRules.GetBaseMateId(mateId);
return _effectsByBaseId.TryGetValue(baseId, out effect);
}
}
}
fileFormatVersion: 2
guid: fd603fb2afd594547a10c88eda27f8dd
\ No newline at end of file
...@@ -920,6 +920,13 @@ namespace MDPro3.Duel ...@@ -920,6 +920,13 @@ namespace MDPro3.Duel
card.AnimationPositon(); card.AnimationPositon();
ES_hint = InterString.Get("「[?]」特殊召唤宣言时", card.GetData().Name); ES_hint = InterString.Get("「[?]」特殊召唤宣言时", card.GetData().Name);
var isExtraDeckMonster = card.GetData().HasType(CardType.Fusion)
|| card.GetData().HasType(CardType.Synchro)
|| card.GetData().HasType(CardType.Xyz)
|| card.GetData().HasType(CardType.Link);
if (isExtraDeckMonster)
duelBGManager.OnSpecialSummonFromExtra(gps.InMyControl() ? 0 : 1);
if(materialCards.Count > 0) if(materialCards.Count > 0)
{ {
if (card.GetData().HasType(CardType.Link)) if (card.GetData().HasType(CardType.Link))
...@@ -1315,6 +1322,9 @@ namespace MDPro3.Duel ...@@ -1315,6 +1322,9 @@ namespace MDPro3.Duel
else else
attackedPosition = attackedCard.model.transform.position; attackedPosition = attackedCard.model.transform.position;
if (directAttack != 0)
duelBGManager.OnDirectAttack(from.InMyControl() ? 0 : 1);
var isFinalAttack = duelBGManager.IsFinalBlow(); var isFinalAttack = duelBGManager.IsFinalBlow();
duelBGManager.HideAttackLine(); duelBGManager.HideAttackLine();
duelBGManager.HideDuelFinalBlowText(); duelBGManager.HideDuelFinalBlowText();
...@@ -1516,7 +1526,12 @@ namespace MDPro3.Duel ...@@ -1516,7 +1526,12 @@ namespace MDPro3.Duel
if(life0 <= 0 || life1 <= 0) if(life0 <= 0 || life1 <= 0)
duelBGManager.FinishDamageEffect(); duelBGManager.FinishDamageEffect();
if (currentMessage == GameMessage.Damage)
duelBGManager.OnPlayerDamaged(player, Mathf.Max(value, 0));
duelBGManager.UpdateBgEffects(player); duelBGManager.UpdateBgEffects(player);
duelBGManager.OnLifePointsChanged(0, life0);
duelBGManager.OnLifePointsChanged(1, life1);
AudioManager.PlaySE("SE_COST_DAMAGE"); AudioManager.PlaySE("SE_COST_DAMAGE");
Core.SetLP(player, -value); Core.SetLP(player, -value);
await UniTask.WaitForSeconds(0.5f); await UniTask.WaitForSeconds(0.5f);
...@@ -1543,6 +1558,8 @@ namespace MDPro3.Duel ...@@ -1543,6 +1558,8 @@ namespace MDPro3.Duel
ES_hint = InterString.Get("对方生命值回复时"); ES_hint = InterString.Get("对方生命值回复时");
} }
duelBGManager.OnLifePointsChanged(0, life0);
duelBGManager.OnLifePointsChanged(1, life1);
Core.SetLP(player, value); Core.SetLP(player, value);
await UniTask.WaitForSeconds(0.5f); await UniTask.WaitForSeconds(0.5f);
} }
...@@ -1568,6 +1585,10 @@ namespace MDPro3.Duel ...@@ -1568,6 +1585,10 @@ namespace MDPro3.Duel
duelBGManager.FinishDamageEffect(); duelBGManager.FinishDamageEffect();
duelBGManager.UpdateBgEffects(player); duelBGManager.UpdateBgEffects(player);
if (diff < 0)
duelBGManager.OnPlayerDamaged(player, -diff);
duelBGManager.OnLifePointsChanged(0, life0);
duelBGManager.OnLifePointsChanged(1, life1);
if(diff < 0) if(diff < 0)
AudioManager.PlaySE("SE_COST_DAMAGE"); AudioManager.PlaySE("SE_COST_DAMAGE");
Core.SetLP(player, diff); Core.SetLP(player, diff);
...@@ -1947,6 +1968,7 @@ namespace MDPro3.Duel ...@@ -1947,6 +1968,7 @@ namespace MDPro3.Duel
opSpSummonCount = 0; opSpSummonCount = 0;
turns++; turns++;
myTurn = isFirst ? (turns % 2 != 0) : (turns % 2 == 0); myTurn = isFirst ? (turns % 2 != 0) : (turns % 2 == 0);
duelBGManager.OnNewTurn(myTurn, turns);
PhaseButtonHandler.TurnChange(myTurn, turns); PhaseButtonHandler.TurnChange(myTurn, turns);
PhaseButtonHandler.SetTextMain(string.Empty); PhaseButtonHandler.SetTextMain(string.Empty);
...@@ -1997,6 +2019,7 @@ namespace MDPro3.Duel ...@@ -1997,6 +2019,7 @@ namespace MDPro3.Duel
else if (duelPhase == DuelPhase.End) else if (duelPhase == DuelPhase.End)
PhaseButtonHandler.SetTextMain("End"); PhaseButtonHandler.SetTextMain("End");
duelBGManager.OnNewPhase(player, duelPhase);
await duelBGManager.ShowPhaseBanner(player, duelPhase); await duelBGManager.ShowPhaseBanner(player, duelPhase);
} }
...@@ -2296,7 +2319,10 @@ namespace MDPro3.Duel ...@@ -2296,7 +2319,10 @@ namespace MDPro3.Duel
if (currentMessage == GameMessage.CardTarget) if (currentMessage == GameMessage.CardTarget)
cardFrom.AddTarget(cardTo); cardFrom.AddTarget(cardTo);
else if (currentMessage == GameMessage.Equip) else if (currentMessage == GameMessage.Equip)
{
cardFrom.equipedCard = cardTo; cardFrom.equipedCard = cardTo;
duelBGManager.OnEquipApplied(from.InMyControl() ? 0 : 1);
}
return UniTask.CompletedTask; return UniTask.CompletedTask;
} }
......
using MDPro3.UI;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System;
using UnityEngine; using UnityEngine;
using UnityEngine.Playables; using UnityEngine.Playables;
using Willow; using Willow;
using MDPro3.Servant; using MDPro3.Servant;
using MDPro3.UI;
namespace MDPro3 namespace MDPro3
{ {
...@@ -46,6 +47,37 @@ namespace MDPro3 ...@@ -46,6 +47,37 @@ namespace MDPro3
public PlayableDirector directorO;//o_attack NA BOSS轻攻击 public PlayableDirector directorO;//o_attack NA BOSS轻攻击
BoxCollider m_collider; BoxCollider m_collider;
SkinnedMeshRenderer mesh; SkinnedMeshRenderer mesh;
private readonly Dictionary<Animator, HashSet<string>> masterDuelTriggersByAnimator = new();
private Animator[] masterDuelAnimators = Array.Empty<Animator>();
private Animator masterDuelPrimaryAnimator;
private bool masterDuelInitialized;
private bool masterDuelHasAnyTriggers;
private static readonly string[] masterDuelPrimaryTriggerHints =
{
"Entry", "Attack", "Damage", "Victory", "Defeat",
"Tap", "Tap1", "Tap2", "Random1", "Random2",
"Wait2", "Wait3", "Change", "Change1", "Change2", "ChangeBack", "ChangePreHide"
};
private static readonly string[] masterDuelTapFallbackStates = { "Tap", "Tap1", "Tap2" };
private static readonly string[] masterDuelRandomFallbackStates = { "Random1", "Random2", "Wait2", "Wait3", "Normal" };
private static readonly string[] masterDuelChangeFallbackStates = { "Change", "Change1", "Change2", "ChangeBack", "ChangePreHide" };
private static readonly string[] ipEntryVoice = { "SE_M14676_c", "SE_M14676_d" };
private static readonly string[] ipTapVoice = { "SE_M14676_i", "SE_M14676_j", "SE_M14676_k", "SE_M14676_d" };
private static readonly string[] ipAttackVoice = { "SE_M14676_e", "SE_M14676_f", "SE_M14676_d1", "SE_M14676_d" };
private static readonly string[] ipDamageVoice = { "SE_M14676_h2", "SE_M14676_h1", "SE_M14676_h", "SE_M14676_d1", "SE_M14676_d" };
private static readonly string[] ipRandomVoice = { "SE_M14676_f", "SE_M14676_g", "SE_M14676_e", "SE_M14676_j" };
private static readonly string[] ipVictoryVoice = { "SE_M14676_k", "SE_M14676_g", "SE_M14676_i" };
private static readonly string[] ipDefeatVoice = { "SE_M14676_d1", "SE_M14676_h2", "SE_M14676_d" };
private static readonly string[] spEntryVoice = { "SE_M15702_c", "SE_M15702_f" };
private static readonly string[] spTapVoice = { "SE_M15702_i", "SE_M15702_j", "SE_M15702_k" };
private static readonly string[] spAttackVoice = { "SE_M15702_d", "SE_M15702_d1", "SE_M15702_g" };
private static readonly string[] spDamageVoice = { "SE_M15702_h2", "SE_M15702_h1", "SE_M15702_h" };
private static readonly string[] spRandomVoice = { "SE_M15702_f", "SE_M15702_g", "SE_M15702_e", "SE_M15702_j" };
private static readonly string[] spVictoryVoice = { "SE_M15702_k", "SE_M15702_i", "SE_M15702_e" };
private static readonly string[] spDefeatVoice = { "SE_M15702_d1", "SE_M15702_h2", "SE_M15702_d" };
private Coroutine masterDuelVoiceFallbackCoroutine;
private float masterDuelVoiceFallbackStartTime;
bool Playing() bool Playing()
{ {
if (directorA != null && directorA.state == PlayState.Playing) if (directorA != null && directorA.state == PlayState.Playing)
...@@ -59,8 +91,6 @@ namespace MDPro3 ...@@ -59,8 +91,6 @@ namespace MDPro3
if (directorI != null && directorI.state == PlayState.Playing) if (directorI != null && directorI.state == PlayState.Playing)
return true; return true;
if (directorI != null && directorI.state == PlayState.Playing)
return true;
if (directorJ != null && directorJ.state == PlayState.Playing) if (directorJ != null && directorJ.state == PlayState.Playing)
return true; return true;
if (directorK != null && directorK.state == PlayState.Playing) if (directorK != null && directorK.state == PlayState.Playing)
...@@ -75,17 +105,129 @@ namespace MDPro3 ...@@ -75,17 +105,129 @@ namespace MDPro3
return false; return false;
} }
private void EnsureMasterDuelInitialized(bool forceRefresh = false)
{
if (type != MateType.MasterDuel)
return;
if (forceRefresh)
masterDuelInitialized = false;
if (masterDuelInitialized)
return;
if (m_collider == null)
{
m_collider = gameObject.GetComponent<BoxCollider>();
if (m_collider == null)
m_collider = gameObject.AddComponent<BoxCollider>();
}
m_collider.size = new Vector3(10, 10, 10);
m_collider.center = new Vector3(0, 5, 0);
masterDuelAnimators = transform.GetComponentsInChildren<Animator>(true);
masterDuelTriggersByAnimator.Clear();
masterDuelPrimaryAnimator = null;
masterDuelHasAnyTriggers = false;
var bestScore = -1;
foreach (var animator in masterDuelAnimators)
{
if (animator == null)
continue;
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
animator.applyRootMotion = false;
KeepAnimatorStateOnDisable(animator);
if (animator.gameObject.GetComponent<EventSEPlayer>() == null)
animator.gameObject.AddComponent<EventSEPlayer>();
var triggerSet = new HashSet<string>(StringComparer.Ordinal);
foreach (var parameter in animator.parameters)
if (parameter.type == AnimatorControllerParameterType.Trigger)
triggerSet.Add(parameter.name);
masterDuelTriggersByAnimator[animator] = triggerSet;
if (triggerSet.Count > 0)
masterDuelHasAnyTriggers = true;
var score = 0;
for (var i = 0; i < masterDuelPrimaryTriggerHints.Length; i++)
if (triggerSet.Contains(masterDuelPrimaryTriggerHints[i]))
score++;
if (score > bestScore)
{
bestScore = score;
masterDuelPrimaryAnimator = animator;
}
}
if (masterDuelPrimaryAnimator == null && masterDuelAnimators.Length > 0)
masterDuelPrimaryAnimator = masterDuelAnimators[0];
masterDuelInitialized = true;
}
private void RefreshMasterDuelAnimatorCacheIfNeeded()
{
if (type != MateType.MasterDuel)
return;
if (!masterDuelInitialized)
{
EnsureMasterDuelInitialized();
return;
}
var stale = masterDuelAnimators == null || masterDuelAnimators.Length == 0;
if (!stale)
{
for (var i = 0; i < masterDuelAnimators.Length; i++)
{
if (masterDuelAnimators[i] != null)
continue;
stale = true;
break;
}
}
if (!stale && (masterDuelPrimaryAnimator == null || !masterDuelTriggersByAnimator.ContainsKey(masterDuelPrimaryAnimator)))
stale = true;
if (!stale)
return;
EnsureMasterDuelInitialized(forceRefresh: true);
}
public void PrepareForPremiumSwapActivation()
{
if (type != MateType.MasterDuel)
return;
RefreshMasterDuelAnimatorCacheIfNeeded();
EnsureMasterDuelInitialized();
if (masterDuelAnimators == null || masterDuelAnimators.Length == 0)
return;
for (var i = 0; i < masterDuelAnimators.Length; i++)
{
var animator = masterDuelAnimators[i];
if (animator == null)
continue;
animator.enabled = true;
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
animator.applyRootMotion = false;
KeepAnimatorStateOnDisable(animator);
if (!animator.gameObject.activeInHierarchy)
continue;
animator.Rebind();
animator.Update(0f);
}
}
void Start() void Start()
{ {
if (type == MateType.MasterDuel) if (type == MateType.MasterDuel)
{ {
m_collider = gameObject.AddComponent<BoxCollider>(); EnsureMasterDuelInitialized();
m_collider.size = new Vector3(10, 10, 10);
m_collider.center = new Vector3(0, 5, 0);
transform.GetChild(0).gameObject.AddComponent<EventSEPlayer>();
var animator = transform.GetChild(0).GetComponent<Animator>();
if (animator != null)
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
} }
else else
{ {
...@@ -154,47 +296,67 @@ namespace MDPro3 ...@@ -154,47 +296,67 @@ namespace MDPro3
switch (type) switch (type)
{ {
case (MateType.MasterDuel): case (MateType.MasterDuel):
if (action != MateAction.Entry && !gameObject.activeInHierarchy)
break;
switch (action) switch (action)
{ {
case MateAction.Entry: case MateAction.Entry:
transform.SetParent(parent, false); transform.SetParent(parent, false);
Tools.PlayAnimation(transform, "Entry"); if (TryPlayMasterDuelTrigger("Entry"))
QueueMasterDuelVoiceFallback(MateAction.Entry);
break; break;
case MateAction.Tap: case MateAction.Tap:
int i = Random.Range(0, 3); var tapPriority = new[] { "Tap", "Tap1", "Tap2" };
switch (i) var tapStartIndex = UnityEngine.Random.Range(0, tapPriority.Length);
var tapped = false;
for (var tapTry = 0; tapTry < tapPriority.Length; tapTry++)
{ {
case 0: var triggerName = tapPriority[(tapStartIndex + tapTry) % tapPriority.Length];
Tools.PlayAnimation(transform, "Tap"); var logMissing = tapTry == tapPriority.Length - 1;
break; if (!TryPlayMasterDuelTrigger(triggerName, logMissing))
case 1: continue;
Tools.PlayAnimation(transform, "Tap1");
break; tapped = true;
case 2: break;
Tools.PlayAnimation(transform, "Tap2");
break;
} }
if (tapped)
QueueMasterDuelVoiceFallback(MateAction.Tap);
break; break;
case MateAction.BattlePhase: case MateAction.BattlePhase:
break; break;
case MateAction.Attack: case MateAction.Attack:
Tools.PlayAnimation(transform, "Attack"); if (TryPlayMasterDuelTrigger("Attack"))
QueueMasterDuelVoiceFallback(MateAction.Attack);
break; break;
case MateAction.GetDamage: case MateAction.GetDamage:
Tools.PlayAnimation(transform, "Damage"); if (TryPlayMasterDuelTrigger("Damage"))
QueueMasterDuelVoiceFallback(MateAction.GetDamage);
break; break;
case MateAction.Victory: case MateAction.Victory:
Tools.PlayAnimation(transform, "Victory"); if (TryPlayMasterDuelTrigger("Victory"))
QueueMasterDuelVoiceFallback(MateAction.Victory);
break; break;
case MateAction.Defeat: case MateAction.Defeat:
Tools.PlayAnimation(transform, "Defeat"); if (TryPlayMasterDuelTrigger("Defeat"))
QueueMasterDuelVoiceFallback(MateAction.Defeat);
break; break;
case MateAction.Random: case MateAction.Random:
if (Random.Range(0, 2) > 0.5f) var preferWait3 = UnityEngine.Random.value < 0.2f;
Tools.PlayAnimation(transform, "Random1"); var played = preferWait3
else ? TryPlayMasterDuelTrigger("Wait3", false) || TryPlayMasterDuelTrigger("Wait2", false)
Tools.PlayAnimation(transform, "Random2"); : TryPlayMasterDuelTrigger("Wait2", false) || TryPlayMasterDuelTrigger("Wait3", false);
if (!played)
{
var preferRandom2 = UnityEngine.Random.value < 0.5f;
played = preferRandom2
? TryPlayMasterDuelTrigger("Random2", false) || TryPlayMasterDuelTrigger("Random1", false)
: TryPlayMasterDuelTrigger("Random1", false) || TryPlayMasterDuelTrigger("Random2", false);
}
if (!played)
played = TryPlayMasterDuelTrigger("Normal");
if (played)
QueueMasterDuelVoiceFallback(MateAction.Random);
break; break;
} }
break; break;
...@@ -234,7 +396,7 @@ namespace MDPro3 ...@@ -234,7 +396,7 @@ namespace MDPro3
break; break;
if (directorM != null && directorN != null && directorO != null) if (directorM != null && directorN != null && directorO != null)
{ {
var random = Random.Range(0, 3); var random = UnityEngine.Random.Range(0, 3);
switch (random) switch (random)
{ {
case 0: case 0:
...@@ -294,6 +456,95 @@ namespace MDPro3 ...@@ -294,6 +456,95 @@ namespace MDPro3
} }
} }
private void QueueMasterDuelVoiceFallback(MateAction action)
{
if (type != MateType.MasterDuel)
return;
if (!isActiveAndEnabled)
return;
if (!TryGetMasterDuelVoiceCandidates(action, out var candidates))
return;
if (masterDuelVoiceFallbackCoroutine != null)
StopCoroutine(masterDuelVoiceFallbackCoroutine);
masterDuelVoiceFallbackStartTime = Time.unscaledTime;
masterDuelVoiceFallbackCoroutine = StartCoroutine(PlayMasterDuelVoiceFallbackDelayed(candidates, GetMasterDuelVoicePrefix(), masterDuelVoiceFallbackStartTime));
}
private IEnumerator PlayMasterDuelVoiceFallbackDelayed(IReadOnlyList<string> candidates, string expectedPrefix, float startTime)
{
yield return null;
yield return null;
masterDuelVoiceFallbackCoroutine = null;
if (startTime != masterDuelVoiceFallbackStartTime)
yield break;
if (EventSEPlayer.HasRecentEvent(expectedPrefix, startTime))
yield break;
if (candidates == null || candidates.Count == 0)
yield break;
var startIndex = UnityEngine.Random.Range(0, candidates.Count);
for (var i = 0; i < candidates.Count; i++)
{
var candidate = candidates[(startIndex + i) % candidates.Count];
if (string.IsNullOrEmpty(candidate))
continue;
AudioManager.PlaySE(candidate, 0.4f);
yield break;
}
}
private string GetMasterDuelVoicePrefix()
{
return code switch
{
1003005 => "SE_M14676_",
1003105 => "SE_M15702_",
_ => string.Empty
};
}
private bool TryGetMasterDuelVoiceCandidates(MateAction action, out IReadOnlyList<string> candidates)
{
candidates = null;
if (code == 1003005)
{
candidates = action switch
{
MateAction.Entry => ipEntryVoice,
MateAction.Tap => ipTapVoice,
MateAction.Attack => ipAttackVoice,
MateAction.GetDamage => ipDamageVoice,
MateAction.Random => ipRandomVoice,
MateAction.Victory => ipVictoryVoice,
MateAction.Defeat => ipDefeatVoice,
_ => null
};
return candidates != null;
}
if (code == 1003105)
{
candidates = action switch
{
MateAction.Entry => spEntryVoice,
MateAction.Tap => spTapVoice,
MateAction.Attack => spAttackVoice,
MateAction.GetDamage => spDamageVoice,
MateAction.Random => spRandomVoice,
MateAction.Victory => spVictoryVoice,
MateAction.Defeat => spDefeatVoice,
_ => null
};
return candidates != null;
}
return false;
}
public void ActiveCamera(MateAction action, int layerMask) public void ActiveCamera(MateAction action, int layerMask)
{ {
if (action == MateAction.Entry) if (action == MateAction.Entry)
...@@ -348,5 +599,505 @@ namespace MDPro3 ...@@ -348,5 +599,505 @@ namespace MDPro3
Tools.SetAnimatorTimescale(transform, timeScale); Tools.SetAnimatorTimescale(transform, timeScale);
} }
} }
public bool PlayChangeTransition(bool toSubForm)
{
if (type != MateType.MasterDuel)
return false;
if (toSubForm)
{
return PlayChangeTransition(new[] { "Change", "Change1", "ChangePreHide", "Change2" });
}
return PlayChangeTransition(new[] { "Change2", "ChangeBack", "ChangePreHide", "Change" });
}
public bool PlayChangeTransition(IReadOnlyList<string> triggerPriority)
{
return PlayChangeTransition(triggerPriority, out _);
}
public bool PlayChangeTransition(IReadOnlyList<string> triggerPriority, out float suggestedDelaySeconds)
{
suggestedDelaySeconds = 0f;
if (type != MateType.MasterDuel)
return false;
if (triggerPriority == null || triggerPriority.Count == 0)
return false;
for (var i = 0; i < triggerPriority.Count; i++)
{
var triggerName = triggerPriority[i];
if (string.IsNullOrEmpty(triggerName))
continue;
if (TryPlayMasterDuelTrigger(triggerName, out suggestedDelaySeconds, false))
return true;
}
return false;
}
public bool HasMasterDuelTriggerParameter(string triggerName)
{
if (type != MateType.MasterDuel || string.IsNullOrEmpty(triggerName))
return false;
RefreshMasterDuelAnimatorCacheIfNeeded();
EnsureMasterDuelInitialized();
if (!masterDuelHasAnyTriggers || masterDuelAnimators == null || masterDuelAnimators.Length == 0)
return false;
if (masterDuelPrimaryAnimator != null
&& masterDuelTriggersByAnimator.TryGetValue(masterDuelPrimaryAnimator, out var primaryTriggers)
&& primaryTriggers.Contains(triggerName))
return true;
for (var i = 0; i < masterDuelAnimators.Length; i++)
{
var animator = masterDuelAnimators[i];
if (animator == null)
continue;
if (masterDuelTriggersByAnimator.TryGetValue(animator, out var triggers) && triggers.Contains(triggerName))
return true;
}
return false;
}
public bool TryDescribeMasterDuelTransitionCandidates(IReadOnlyList<string> triggerPriority, out string summary)
{
summary = "none";
if (type != MateType.MasterDuel)
return false;
if (triggerPriority == null || triggerPriority.Count == 0)
return false;
RefreshMasterDuelAnimatorCacheIfNeeded();
EnsureMasterDuelInitialized();
var parts = new List<string>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var hasAnyCandidate = false;
for (var i = 0; i < triggerPriority.Count; i++)
{
var triggerName = triggerPriority[i];
if (string.IsNullOrEmpty(triggerName))
continue;
if (!seen.Add(triggerName))
continue;
var hasParameter = HasMasterDuelTriggerParameter(triggerName);
var hasStateFallback = HasMasterDuelStateFallbackCandidate(triggerName, out var stateCandidate);
var hasClipFallback = HasMasterDuelClipFallbackCandidate(triggerName, out var clipCandidate);
if (hasParameter || hasStateFallback || hasClipFallback)
hasAnyCandidate = true;
var stateSummary = hasStateFallback ? stateCandidate : "-";
var clipSummary = hasClipFallback ? clipCandidate : "-";
parts.Add($"{triggerName}[param={(hasParameter ? 1 : 0)},state={stateSummary},clip={clipSummary}]");
}
if (parts.Count > 0)
summary = string.Join(";", parts);
return hasAnyCandidate;
}
public bool TryGetMasterDuelPrimaryAnimatorSnapshot(out int stateHash, out float normalizedTime, out float stateLength, out bool inTransition, out bool activeInHierarchy)
{
stateHash = 0;
normalizedTime = 0f;
stateLength = 0f;
inTransition = false;
activeInHierarchy = false;
if (type != MateType.MasterDuel)
return false;
RefreshMasterDuelAnimatorCacheIfNeeded();
EnsureMasterDuelInitialized();
if (masterDuelPrimaryAnimator == null)
return false;
activeInHierarchy = masterDuelPrimaryAnimator.gameObject != null && masterDuelPrimaryAnimator.gameObject.activeInHierarchy;
if (masterDuelPrimaryAnimator.layerCount <= 0)
return true;
var stateInfo = masterDuelPrimaryAnimator.GetCurrentAnimatorStateInfo(0);
stateHash = stateInfo.shortNameHash;
normalizedTime = stateInfo.normalizedTime;
stateLength = stateInfo.length;
inTransition = masterDuelPrimaryAnimator.IsInTransition(0);
return true;
}
private bool TryPlayMasterDuelTrigger(string triggerName)
{
return TryPlayMasterDuelTrigger(triggerName, out _);
}
private bool TryPlayMasterDuelTrigger(string triggerName, bool logMissing)
{
return TryPlayMasterDuelTrigger(triggerName, out _, logMissing);
}
private bool TryPlayMasterDuelTrigger(string triggerName, out float suggestedDelaySeconds)
{
return TryPlayMasterDuelTrigger(triggerName, out suggestedDelaySeconds, true);
}
private bool TryPlayMasterDuelTrigger(string triggerName, out float suggestedDelaySeconds, bool logMissing)
{
suggestedDelaySeconds = 0f;
if (type != MateType.MasterDuel)
return false;
RefreshMasterDuelAnimatorCacheIfNeeded();
EnsureMasterDuelInitialized();
if (masterDuelAnimators == null || masterDuelAnimators.Length == 0)
return false;
if (!masterDuelHasAnyTriggers)
{
if (TryPlayMasterDuelStateFallback(triggerName, out suggestedDelaySeconds))
return true;
if (TryPlayMasterDuelClipFallback(triggerName, out suggestedDelaySeconds))
return true;
return false;
}
if (masterDuelPrimaryAnimator != null
&& CanDriveAnimator(masterDuelPrimaryAnimator)
&& masterDuelTriggersByAnimator.TryGetValue(masterDuelPrimaryAnimator, out var primaryTriggers)
&& primaryTriggers.Contains(triggerName))
{
masterDuelPrimaryAnimator.SetTrigger(triggerName);
suggestedDelaySeconds = EstimateTriggerDuration(masterDuelPrimaryAnimator, triggerName);
return true;
}
foreach (var animator in masterDuelAnimators)
{
if (!CanDriveAnimator(animator))
continue;
if (!masterDuelTriggersByAnimator.TryGetValue(animator, out var triggers))
continue;
if (!triggers.Contains(triggerName))
continue;
animator.SetTrigger(triggerName);
suggestedDelaySeconds = EstimateTriggerDuration(animator, triggerName);
return true;
}
if (TryPlayMasterDuelStateFallback(triggerName, out suggestedDelaySeconds))
return true;
if (TryPlayMasterDuelClipFallback(triggerName, out suggestedDelaySeconds))
return true;
return false;
}
private bool TryPlayMasterDuelStateFallback(string triggerName, out float suggestedDelaySeconds)
{
suggestedDelaySeconds = 0f;
if (string.IsNullOrEmpty(triggerName) || masterDuelAnimators == null || masterDuelAnimators.Length == 0)
return false;
var candidates = GetMasterDuelStateFallbackCandidates(triggerName);
for (var i = 0; i < masterDuelAnimators.Length; i++)
{
var animator = masterDuelAnimators[i];
if (!CanDriveAnimator(animator))
continue;
for (var layer = 0; layer < animator.layerCount; layer++)
{
var layerName = animator.GetLayerName(layer);
for (var j = 0; j < candidates.Length; j++)
{
var candidate = candidates[j];
if (string.IsNullOrEmpty(candidate))
continue;
var shortHash = Animator.StringToHash(candidate);
var fullPathHash = Animator.StringToHash($"{layerName}.{candidate}");
if (!animator.HasState(layer, shortHash) && !animator.HasState(layer, fullPathHash))
continue;
if (animator.HasState(layer, fullPathHash))
animator.Play(fullPathHash, layer, 0f);
else
animator.Play(shortHash, layer, 0f);
suggestedDelaySeconds = EstimateTriggerDuration(animator, candidate);
if (suggestedDelaySeconds <= 0f)
suggestedDelaySeconds = EstimateTriggerDuration(animator, triggerName);
if (suggestedDelaySeconds <= 0f)
{
var stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
if (stateInfo.length > 0f)
suggestedDelaySeconds = stateInfo.length;
}
if (suggestedDelaySeconds <= 0f && candidate.StartsWith("Change", StringComparison.OrdinalIgnoreCase))
suggestedDelaySeconds = 1.35f;
return true;
}
}
}
return false;
}
private bool HasMasterDuelStateFallbackCandidate(string triggerName, out string candidateSummary)
{
candidateSummary = null;
if (string.IsNullOrEmpty(triggerName) || masterDuelAnimators == null || masterDuelAnimators.Length == 0)
return false;
var candidates = GetMasterDuelStateFallbackCandidates(triggerName);
for (var i = 0; i < masterDuelAnimators.Length; i++)
{
var animator = masterDuelAnimators[i];
if (animator == null)
continue;
for (var layer = 0; layer < animator.layerCount; layer++)
{
var layerName = animator.GetLayerName(layer);
for (var j = 0; j < candidates.Length; j++)
{
var candidate = candidates[j];
if (string.IsNullOrEmpty(candidate))
continue;
var shortHash = Animator.StringToHash(candidate);
var fullPathHash = Animator.StringToHash($"{layerName}.{candidate}");
if (!animator.HasState(layer, shortHash) && !animator.HasState(layer, fullPathHash))
continue;
candidateSummary = $"{candidate}@{animator.name}:L{layer}";
return true;
}
}
}
return false;
}
private static string[] GetMasterDuelStateFallbackCandidates(string triggerName)
{
if (string.IsNullOrEmpty(triggerName))
return Array.Empty<string>();
var triggerLower = triggerName.ToLowerInvariant();
if (triggerLower == "tap" || triggerLower == "tap1" || triggerLower == "tap2")
return masterDuelTapFallbackStates;
if (triggerLower == "wait2" || triggerLower == "wait3" || triggerLower == "random1" || triggerLower == "random2" || triggerLower == "normal")
return masterDuelRandomFallbackStates;
if (triggerLower.StartsWith("change", StringComparison.Ordinal))
{
var ordered = new List<string>(masterDuelChangeFallbackStates.Length + 1);
ordered.Add(triggerName);
for (var i = 0; i < masterDuelChangeFallbackStates.Length; i++)
{
var candidate = masterDuelChangeFallbackStates[i];
if (string.Equals(candidate, triggerName, StringComparison.OrdinalIgnoreCase))
continue;
ordered.Add(candidate);
}
return ordered.ToArray();
}
return new[] { triggerName };
}
private bool TryPlayMasterDuelClipFallback(string triggerName, out float suggestedDelaySeconds)
{
suggestedDelaySeconds = 0f;
if (string.IsNullOrEmpty(triggerName))
return false;
var allowGenericFallback = triggerName.Equals("Tap", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Tap1", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Tap2", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Random1", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Random2", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Wait2", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Wait3", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Normal", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Entry", StringComparison.OrdinalIgnoreCase);
for (var i = 0; i < masterDuelAnimators.Length; i++)
{
var animator = masterDuelAnimators[i];
if (!CanDriveAnimator(animator))
continue;
var controller = animator.runtimeAnimatorController;
if (controller == null)
continue;
AnimationClip bestClip = null;
AnimationClip genericClip = null;
var clips = controller.animationClips;
var triggerLower = triggerName.ToLowerInvariant();
for (var j = 0; j < clips.Length; j++)
{
var clip = clips[j];
if (clip == null)
continue;
var clipName = clip.name.ToLowerInvariant();
if (allowGenericFallback)
{
var looksIdle = clipName.Contains("idle") || clipName.Contains("wait");
if (!looksIdle && (genericClip == null || clip.length > genericClip.length))
genericClip = clip;
}
var matches = clipName.Contains(triggerLower)
|| ((triggerLower == "tap" || triggerLower == "tap1" || triggerLower == "tap2") && clipName.Contains("tap"))
|| ((triggerLower == "wait2" || triggerLower == "wait3" || triggerLower == "random1" || triggerLower == "random2") && (clipName.Contains("wait") || clipName.Contains("random")))
|| (triggerLower.StartsWith("change", StringComparison.Ordinal) && clipName.Contains("change"));
if (!matches)
continue;
if (bestClip == null || clip.length > bestClip.length)
bestClip = clip;
}
if (bestClip == null)
bestClip = genericClip;
if (bestClip == null)
continue;
animator.Play(bestClip.name, 0, 0f);
suggestedDelaySeconds = bestClip.length;
return true;
}
return false;
}
private bool HasMasterDuelClipFallbackCandidate(string triggerName, out string clipName)
{
clipName = null;
if (string.IsNullOrEmpty(triggerName) || masterDuelAnimators == null || masterDuelAnimators.Length == 0)
return false;
var allowGenericFallback = triggerName.Equals("Tap", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Tap1", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Tap2", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Random1", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Random2", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Wait2", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Wait3", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Normal", StringComparison.OrdinalIgnoreCase)
|| triggerName.Equals("Entry", StringComparison.OrdinalIgnoreCase);
var triggerLower = triggerName.ToLowerInvariant();
for (var i = 0; i < masterDuelAnimators.Length; i++)
{
var animator = masterDuelAnimators[i];
if (animator == null)
continue;
var controller = animator.runtimeAnimatorController;
if (controller == null)
continue;
AnimationClip bestClip = null;
AnimationClip genericClip = null;
var clips = controller.animationClips;
for (var j = 0; j < clips.Length; j++)
{
var clip = clips[j];
if (clip == null)
continue;
var clipLower = clip.name.ToLowerInvariant();
if (allowGenericFallback)
{
var looksIdle = clipLower.Contains("idle") || clipLower.Contains("wait");
if (!looksIdle && (genericClip == null || clip.length > genericClip.length))
genericClip = clip;
}
var matches = clipLower.Contains(triggerLower)
|| ((triggerLower == "tap" || triggerLower == "tap1" || triggerLower == "tap2") && clipLower.Contains("tap"))
|| ((triggerLower == "wait2" || triggerLower == "wait3" || triggerLower == "random1" || triggerLower == "random2")
&& (clipLower.Contains("wait") || clipLower.Contains("random")))
|| (triggerLower.StartsWith("change", StringComparison.Ordinal) && clipLower.Contains("change"));
if (!matches)
continue;
if (bestClip == null || clip.length > bestClip.length)
bestClip = clip;
}
if (bestClip == null)
bestClip = genericClip;
if (bestClip == null)
continue;
clipName = bestClip.name;
return true;
}
return false;
}
private static bool CanDriveAnimator(Animator animator)
{
return animator != null
&& animator.enabled
&& animator.gameObject != null
&& animator.gameObject.activeInHierarchy;
}
private static float EstimateTriggerDuration(Animator animator, string triggerName)
{
if (animator == null || string.IsNullOrEmpty(triggerName))
return 0f;
var controller = animator.runtimeAnimatorController;
if (controller == null)
return 0f;
var triggerLower = triggerName.ToLowerInvariant();
var best = 0f;
var clips = controller.animationClips;
for (var i = 0; i < clips.Length; i++)
{
var clip = clips[i];
if (clip == null)
continue;
var clipName = clip.name.ToLowerInvariant();
var matches = clipName.Contains(triggerLower)
|| (triggerLower.StartsWith("change", StringComparison.Ordinal) && clipName.Contains("change"))
|| (triggerLower == "wait2" && (clipName.Contains("wait2") || clipName.Contains("random1")))
|| (triggerLower == "wait3" && (clipName.Contains("wait3") || clipName.Contains("random2")));
if (!matches)
continue;
if (clip.length > best)
best = clip.length;
}
if (best <= 0f && triggerLower.StartsWith("change", StringComparison.Ordinal))
return 1.35f;
return best;
}
private static void KeepAnimatorStateOnDisable(Animator animator)
{
var type = typeof(Animator);
var keepController = type.GetProperty("keepAnimatorControllerStateOnDisable");
if (keepController != null && keepController.PropertyType == typeof(bool) && keepController.CanWrite)
{
keepController.SetValue(animator, true, null);
return;
}
var keepState = type.GetProperty("keepAnimatorStateOnDisable");
if (keepState != null && keepState.PropertyType == typeof(bool) && keepState.CanWrite)
keepState.SetValue(animator, true, null);
}
} }
} }
...@@ -55,7 +55,23 @@ namespace MDPro3 ...@@ -55,7 +55,23 @@ namespace MDPro3
Animator[] animators = animationContainer.GetComponentsInChildren<Animator>(); Animator[] animators = animationContainer.GetComponentsInChildren<Animator>();
foreach (Animator animator in animators) foreach (Animator animator in animators)
{ {
animator.SetTrigger(animationName); if (animator == null || string.IsNullOrEmpty(animationName))
continue;
var hasTrigger = false;
var parameters = animator.parameters;
for (var i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
if (param.type == AnimatorControllerParameterType.Trigger && param.name == animationName)
{
hasTrigger = true;
break;
}
}
if (hasTrigger)
animator.SetTrigger(animationName);
} }
} }
......
...@@ -69,23 +69,36 @@ namespace MDPro3 ...@@ -69,23 +69,36 @@ namespace MDPro3
} }
AssetBundle ab = await AssetBundle.LoadFromFileAsync(Program.root + path); AssetBundle ab = await AssetBundle.LoadFromFileAsync(Program.root + path);
var assets = ab.LoadAllAssets(); var expectedName = Path.GetFileName(path);
if (!string.IsNullOrEmpty(expectedName))
{
var assetRequest = ab.LoadAssetAsync<GameObject>(expectedName);
await assetRequest;
returnValue = assetRequest.asset as GameObject;
}
foreach (UnityEngine.Object asset in assets) if (returnValue == null)
{ {
if (typeof(GameObject).IsInstanceOfType(asset)) var assets = ab.LoadAllAssets();
foreach (UnityEngine.Object asset in assets)
{ {
if (cache) if (!typeof(GameObject).IsInstanceOfType(asset))
{ continue;
if (!cachedAB.TryAdd(path, asset as GameObject))
Debug.LogWarning($"Failed to cache {path}"); var candidate = asset as GameObject;
} returnValue = candidate;
returnValue = asset as GameObject; if (candidate != null && candidate.name == expectedName)
//break; break;
} }
} }
ab.Unload(false); ab.Unload(false);
if (cache && returnValue != null)
{
if (!cachedAB.TryAdd(path, returnValue))
Debug.LogWarning($"Failed to cache {path}");
}
if (instantiate && returnValue != null) if (instantiate && returnValue != null)
return UnityEngine.Object.Instantiate(returnValue); return UnityEngine.Object.Instantiate(returnValue);
else else
...@@ -590,4 +603,4 @@ namespace MDPro3 ...@@ -590,4 +603,4 @@ namespace MDPro3
#endregion #endregion
} }
} }
\ No newline at end of file
using UnityEngine; using UnityEngine;
using System;
namespace MDPro3.UI namespace MDPro3.UI
{ {
public class EventSEPlayer : MonoBehaviour public class EventSEPlayer : MonoBehaviour
{ {
public static float LastEventTime { get; private set; } = float.NegativeInfinity;
public static string LastEventLabel { get; private set; } = string.Empty;
void PlayAnimationEventSe(string se) void PlayAnimationEventSe(string se)
{ {
RegisterEvent(se);
AudioManager.PlaySE(se, 0.4f); AudioManager.PlaySE(se, 0.4f);
} }
void NewEvent(string se) void NewEvent(string se)
{ {
RegisterEvent(se);
AudioManager.PlaySE(se, 0.4f); AudioManager.PlaySE(se, 0.4f);
} }
public static bool HasRecentEvent(string expectedPrefix, float sinceTime)
{
if (LastEventTime < sinceTime)
return false;
if (string.IsNullOrEmpty(expectedPrefix))
return true;
return !string.IsNullOrEmpty(LastEventLabel)
&& LastEventLabel.StartsWith(expectedPrefix, StringComparison.OrdinalIgnoreCase);
}
private static void RegisterEvent(string se)
{
LastEventTime = Time.unscaledTime;
LastEventLabel = se ?? string.Empty;
}
} }
} }
...@@ -10,6 +10,7 @@ using MDPro3.Servant; ...@@ -10,6 +10,7 @@ using MDPro3.Servant;
using MDPro3.UI.ServantUI; using MDPro3.UI.ServantUI;
using MDPro3.Utility; using MDPro3.Utility;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using MDPro3.Duel;
namespace MDPro3.UI namespace MDPro3.UI
{ {
...@@ -28,6 +29,7 @@ namespace MDPro3.UI ...@@ -28,6 +29,7 @@ namespace MDPro3.UI
m_Protector = m_Protector != null ? m_Protector m_Protector = m_Protector != null ? m_Protector
: Manager.GetElement<RawImage>(LABEL_RIMG_PROTECTOR); : Manager.GetElement<RawImage>(LABEL_RIMG_PROTECTOR);
private const string LABEL_IMG_WALLPAPER_BG = "WallpaperBG"; private const string LABEL_IMG_WALLPAPER_BG = "WallpaperBG";
private Image m_WallpaperBG; private Image m_WallpaperBG;
private Image WallpaperBG => private Image WallpaperBG =>
...@@ -52,6 +54,11 @@ namespace MDPro3.UI ...@@ -52,6 +54,11 @@ namespace MDPro3.UI
private Coroutine refreshCoroutine; private Coroutine refreshCoroutine;
private Coroutine hideCoroutine; private Coroutine hideCoroutine;
private Image premiumOverlayIcon;
private Coroutine premiumCrossfadeCoroutine;
private const float CrossfadeHoldSeconds = 2.0f;
private const float CrossfadeFadeSeconds = 0.6f;
protected override void Awake() protected override void Awake()
{ {
base.Awake(); base.Awake();
...@@ -131,6 +138,7 @@ namespace MDPro3.UI ...@@ -131,6 +138,7 @@ namespace MDPro3.UI
Icon.material = Appearance.matForFace; Icon.material = Appearance.matForFace;
loaded = true; loaded = true;
StartPremiumCrossfade();
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
...@@ -208,9 +216,10 @@ namespace MDPro3.UI ...@@ -208,9 +216,10 @@ namespace MDPro3.UI
} }
else else
{ {
if (DeckEditor.Deck.Mate != itemID) var normalizedMateId = PremiumMateRules.GetBaseMateId(itemID);
if (DeckEditor.Deck.Mate != normalizedMateId)
{ {
DeckEditor.Deck.Mate = itemID; DeckEditor.Deck.Mate = normalizedMateId;
Program.instance.deckEditor.GetUI<DeckEditorUI>().DeckView.SetDirty(true); Program.instance.deckEditor.GetUI<DeckEditorUI>().DeckView.SetDirty(true);
Program.instance.deckEditor.GetUI<DeckEditorUI>().IconMate.sprite = Icon.sprite; Program.instance.deckEditor.GetUI<DeckEditorUI>().IconMate.sprite = Icon.sprite;
} }
...@@ -220,6 +229,9 @@ namespace MDPro3.UI ...@@ -220,6 +229,9 @@ namespace MDPro3.UI
{ {
if (AppearanceUI.currentContent == "Wallpaper") if (AppearanceUI.currentContent == "Wallpaper")
Config.Set("Wallpaper", itemID.ToString()); Config.Set("Wallpaper", itemID.ToString());
else if (AppearanceUI.currentContent == "Mate")
Config.Set(Appearance.condition.ToString() + AppearanceUI.currentContent + Appearance.player,
PremiumMateRules.GetBaseMateId(itemID).ToString());
else else
Config.Set(Appearance.condition.ToString() + AppearanceUI.currentContent + Appearance.player, itemID.ToString()); Config.Set(Appearance.condition.ToString() + AppearanceUI.currentContent + Appearance.player, itemID.ToString());
...@@ -384,6 +396,7 @@ namespace MDPro3.UI ...@@ -384,6 +396,7 @@ namespace MDPro3.UI
if (hideCoroutine != null || !gameObject.activeSelf) if (hideCoroutine != null || !gameObject.activeSelf)
return; return;
hideCoroutine = StartCoroutine(HideAsync()); hideCoroutine = StartCoroutine(HideAsync());
StopPremiumCrossfade();
GetComponent<LayoutElement>().ignoreLayout = true; GetComponent<LayoutElement>().ignoreLayout = true;
GetComponent<RectTransform>().anchoredPosition = Vector2.zero; GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
...@@ -412,10 +425,13 @@ namespace MDPro3.UI ...@@ -412,10 +425,13 @@ namespace MDPro3.UI
GetComponent<LayoutElement>().ignoreLayout = false; GetComponent<LayoutElement>().ignoreLayout = false;
transform.SetSiblingIndex(index); transform.SetSiblingIndex(index);
StartPremiumCrossfade();
} }
public void Dispose() public void Dispose()
{ {
StopPremiumCrossfade();
if(refreshCoroutine != null) if(refreshCoroutine != null)
StopCoroutine(refreshCoroutine); StopCoroutine(refreshCoroutine);
...@@ -424,5 +440,150 @@ namespace MDPro3.UI ...@@ -424,5 +440,150 @@ namespace MDPro3.UI
Destroy(gameObject); Destroy(gameObject);
} }
#region Premium Mate Crossfade
private void StartPremiumCrossfade()
{
StopPremiumCrossfade();
if (!loaded)
return;
if (AppearanceUI.currentContent != "Mate")
return;
if (!PremiumMateRules.IsPremiumBaseId(itemID))
return;
if (Icon == null || !Icon.gameObject.activeSelf)
return;
premiumCrossfadeCoroutine = StartCoroutine(PremiumCrossfadeAsync());
}
private void StopPremiumCrossfade()
{
if (premiumCrossfadeCoroutine != null)
{
StopCoroutine(premiumCrossfadeCoroutine);
premiumCrossfadeCoroutine = null;
}
if (premiumOverlayIcon != null)
{
Destroy(premiumOverlayIcon.gameObject);
premiumOverlayIcon = null;
}
if (Icon != null)
{
var c = Icon.color;
c.a = 1f;
Icon.color = c;
}
}
private Image CreateOverlayIcon()
{
var overlayGo = new GameObject("PremiumOverlay");
overlayGo.transform.SetParent(Icon.transform.parent, false);
overlayGo.transform.SetSiblingIndex(Icon.transform.GetSiblingIndex() + 1);
var overlayImg = overlayGo.AddComponent<Image>();
overlayImg.raycastTarget = false;
overlayImg.preserveAspect = Icon.preserveAspect;
overlayImg.type = Icon.type;
overlayImg.maskable = Icon.maskable;
var overlayRt = overlayImg.rectTransform;
var iconRt = Icon.rectTransform;
overlayRt.anchorMin = iconRt.anchorMin;
overlayRt.anchorMax = iconRt.anchorMax;
overlayRt.pivot = iconRt.pivot;
overlayRt.anchoredPosition = iconRt.anchoredPosition;
overlayRt.sizeDelta = iconRt.sizeDelta;
overlayRt.localScale = iconRt.localScale;
overlayRt.localRotation = iconRt.localRotation;
var c = Color.white;
c.a = 0f;
overlayImg.color = c;
return overlayImg;
}
private IEnumerator PremiumCrossfadeAsync()
{
if (!PremiumMateRules.TryGetRuleByBaseId(itemID, out var rule))
yield break;
Sprite subSprite = null;
foreach (var variantId in rule.VariantIds)
{
var task = Program.items.LoadItemIconAsync(variantId.ToString(), Items.ItemType.Mate);
while (task.Status == UniTaskStatus.Pending)
yield return null;
try
{
subSprite = task.GetAwaiter().GetResult();
if (subSprite != null)
break;
}
catch
{
// Icon not available for this variant, try next
}
}
if (subSprite == null || this == null || Icon == null)
yield break;
premiumOverlayIcon = CreateOverlayIcon();
premiumOverlayIcon.sprite = subSprite;
// Icon shows base (alpha=1), overlay shows sub (alpha=0) initially.
// Crossfade loop: hold → fade overlay in → hold → fade overlay out → repeat.
while (true)
{
// Hold on base icon
yield return new WaitForSecondsRealtime(CrossfadeHoldSeconds);
// Fade in overlay (base → sub)
yield return FadeOverlay(0f, 1f, CrossfadeFadeSeconds);
// Hold on sub icon
yield return new WaitForSecondsRealtime(CrossfadeHoldSeconds);
// Fade out overlay (sub → base)
yield return FadeOverlay(1f, 0f, CrossfadeFadeSeconds);
}
}
private IEnumerator FadeOverlay(float fromAlpha, float toAlpha, float duration)
{
if (premiumOverlayIcon == null)
yield break;
var elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.unscaledDeltaTime;
var t = Mathf.Clamp01(elapsed / duration);
t = t * t * (3f - 2f * t); // smoothstep
var alpha = Mathf.Lerp(fromAlpha, toAlpha, t);
if (premiumOverlayIcon != null)
{
var c = premiumOverlayIcon.color;
c.a = alpha;
premiumOverlayIcon.color = c;
}
yield return null;
}
if (premiumOverlayIcon != null)
{
var c = premiumOverlayIcon.color;
c.a = toAlpha;
premiumOverlayIcon.color = c;
}
}
#endregion
} }
} }
...@@ -8,6 +8,7 @@ using UnityEngine.EventSystems; ...@@ -8,6 +8,7 @@ using UnityEngine.EventSystems;
using UnityEngine.UI; using UnityEngine.UI;
using static MDPro3.Servant.Appearance; using static MDPro3.Servant.Appearance;
using static YgomGame.Duel.BattleAimingEffect; using static YgomGame.Duel.BattleAimingEffect;
using MDPro3.Duel;
namespace MDPro3.UI.ServantUI namespace MDPro3.UI.ServantUI
{ {
...@@ -390,6 +391,8 @@ namespace MDPro3.UI.ServantUI ...@@ -390,6 +391,8 @@ namespace MDPro3.UI.ServantUI
int itemCount = 0; int itemCount = 0;
foreach (var itemInfo in targetItems) foreach (var itemInfo in targetItems)
{ {
if (currentContent == "Mate" && PremiumMateRules.IsPremiumVariantId(itemInfo.id))
continue;
if (itemInfo.notReady) continue; if (itemInfo.notReady) continue;
GameObject item = Instantiate(Template); GameObject item = Instantiate(Template);
...@@ -520,44 +523,68 @@ namespace MDPro3.UI.ServantUI ...@@ -520,44 +523,68 @@ namespace MDPro3.UI.ServantUI
foreach (var item in currentList) foreach (var item in currentList)
{ {
var itemMono = item.GetComponent<SelectionToggle_AppearanceItem>();
if (currentContent == "Mate" && PremiumMateRules.IsPremiumVariantId(itemMono.itemID))
{
itemMono.Hide();
continue;
}
if (player.Contains("0") && onlyOpSideShowItems.Contains(item)) if (player.Contains("0") && onlyOpSideShowItems.Contains(item))
item.GetComponent<SelectionToggle_AppearanceItem>().Hide(); itemMono.Hide();
else else
item.GetComponent<SelectionToggle_AppearanceItem>().Show(); itemMono.Show();
} }
foreach (var item in currentList) foreach (var item in currentList)
{ {
var itemMono = item.GetComponent<SelectionToggle_AppearanceItem>();
if (currentContent == "Wallpaper") if (currentContent == "Wallpaper")
{ {
if (item.GetComponent<SelectionToggle_AppearanceItem>().itemID.ToString() == Config.Get("Wallpaper", targetItems[0].id.ToString())) if (itemMono.itemID.ToString() == Config.Get("Wallpaper", targetItems[0].id.ToString()))
{ {
item.GetComponent<SelectionToggle_AppearanceItem>().SetToggleOn(); itemMono.SetToggleOn();
break; break;
} }
} }
else else
{ {
var itemID = item.GetComponent<SelectionToggle_AppearanceItem>().itemID; var itemID = itemMono.itemID;
if (currentContent == "Mate" && PremiumMateRules.IsPremiumVariantId(itemID))
continue;
if (condition == Condition.DeckEditor) if (condition == Condition.DeckEditor)
{ {
if (itemID == DeckEditor.Deck.Case if (currentContent == "Mate")
{
var selectedMate = PremiumMateRules.GetBaseMateId(DeckEditor.Deck.Mate);
if (itemID == selectedMate)
{
itemMono.SetToggleOn();
break;
}
}
else if (itemID == DeckEditor.Deck.Case
|| itemID == DeckEditor.Deck.Protector || itemID == DeckEditor.Deck.Protector
|| itemID == DeckEditor.Deck.Field || itemID == DeckEditor.Deck.Field
|| itemID == DeckEditor.Deck.Grave || itemID == DeckEditor.Deck.Grave
|| itemID == DeckEditor.Deck.Stand || itemID == DeckEditor.Deck.Stand
|| itemID == DeckEditor.Deck.Mate) || itemID == DeckEditor.Deck.Mate)
{ {
item.GetComponent<SelectionToggle_AppearanceItem>().SetToggleOn(); itemMono.SetToggleOn();
break; break;
} }
} }
else else
{ {
if (itemID.ToString() == Config.Get(condition.ToString() + currentContent + player, targetItems[0].id.ToString())) var selectedCode = Config.Get(condition.ToString() + currentContent + player, targetItems[0].id.ToString());
if (currentContent == "Mate" && int.TryParse(selectedCode, out var selectedMateCode))
selectedCode = PremiumMateRules.GetBaseMateId(selectedMateCode).ToString();
if (itemID.ToString() == selectedCode)
{ {
item.GetComponent<SelectionToggle_AppearanceItem>().SetToggleOn(); itemMono.SetToggleOn();
break; break;
} }
} }
...@@ -618,4 +645,4 @@ namespace MDPro3.UI.ServantUI ...@@ -618,4 +645,4 @@ namespace MDPro3.UI.ServantUI
} }
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment