Commit d20b6d9e authored by Senator John's avatar Senator John 💬

Merge branch 'patch' into 'master'

Patch

See merge request !32
parents b3287b74 eea6cdcb
......@@ -954,7 +954,7 @@ MonoBehaviour:
- id: 1003003
m_name:
m_description:
path: Mate/M13670_Model
path: Mate/M13671_Model
secondFace: 1
diy: 0
notReady: 0
......@@ -965,6 +965,13 @@ MonoBehaviour:
secondFace: 1
diy: 0
notReady: 0
- id: 1003203
m_name:
m_description:
path: Mate/M13670_Model
secondFace: 1
diy: 0
notReady: 0
- id: 1003004
m_name:
m_description:
......
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
This diff is collapsed.
fileFormatVersion: 2
guid: fd603fb2afd594547a10c88eda27f8dd
\ No newline at end of file
......@@ -920,6 +920,13 @@ namespace MDPro3.Duel
card.AnimationPositon();
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 (card.GetData().HasType(CardType.Link))
......@@ -1315,6 +1322,9 @@ namespace MDPro3.Duel
else
attackedPosition = attackedCard.model.transform.position;
if (directAttack != 0)
duelBGManager.OnDirectAttack(from.InMyControl() ? 0 : 1);
var isFinalAttack = duelBGManager.IsFinalBlow();
duelBGManager.HideAttackLine();
duelBGManager.HideDuelFinalBlowText();
......@@ -1516,7 +1526,12 @@ namespace MDPro3.Duel
if(life0 <= 0 || life1 <= 0)
duelBGManager.FinishDamageEffect();
if (currentMessage == GameMessage.Damage)
duelBGManager.OnPlayerDamaged(player, Mathf.Max(value, 0));
duelBGManager.UpdateBgEffects(player);
duelBGManager.OnLifePointsChanged(0, life0);
duelBGManager.OnLifePointsChanged(1, life1);
AudioManager.PlaySE("SE_COST_DAMAGE");
Core.SetLP(player, -value);
await UniTask.WaitForSeconds(0.5f);
......@@ -1543,6 +1558,8 @@ namespace MDPro3.Duel
ES_hint = InterString.Get("对方生命值回复时");
}
duelBGManager.OnLifePointsChanged(0, life0);
duelBGManager.OnLifePointsChanged(1, life1);
Core.SetLP(player, value);
await UniTask.WaitForSeconds(0.5f);
}
......@@ -1568,6 +1585,10 @@ namespace MDPro3.Duel
duelBGManager.FinishDamageEffect();
duelBGManager.UpdateBgEffects(player);
if (diff < 0)
duelBGManager.OnPlayerDamaged(player, -diff);
duelBGManager.OnLifePointsChanged(0, life0);
duelBGManager.OnLifePointsChanged(1, life1);
if(diff < 0)
AudioManager.PlaySE("SE_COST_DAMAGE");
Core.SetLP(player, diff);
......@@ -1947,6 +1968,7 @@ namespace MDPro3.Duel
opSpSummonCount = 0;
turns++;
myTurn = isFirst ? (turns % 2 != 0) : (turns % 2 == 0);
duelBGManager.OnNewTurn(myTurn, turns);
PhaseButtonHandler.TurnChange(myTurn, turns);
PhaseButtonHandler.SetTextMain(string.Empty);
......@@ -1997,6 +2019,7 @@ namespace MDPro3.Duel
else if (duelPhase == DuelPhase.End)
PhaseButtonHandler.SetTextMain("End");
duelBGManager.OnNewPhase(player, duelPhase);
await duelBGManager.ShowPhaseBanner(player, duelPhase);
}
......@@ -2296,7 +2319,10 @@ namespace MDPro3.Duel
if (currentMessage == GameMessage.CardTarget)
cardFrom.AddTarget(cardTo);
else if (currentMessage == GameMessage.Equip)
{
cardFrom.equipedCard = cardTo;
duelBGManager.OnEquipApplied(from.InMyControl() ? 0 : 1);
}
return UniTask.CompletedTask;
}
......
This diff is collapsed.
......@@ -55,6 +55,22 @@ namespace MDPro3
Animator[] animators = animationContainer.GetComponentsInChildren<Animator>();
foreach (Animator animator in animators)
{
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);
}
}
......@@ -224,7 +240,7 @@ namespace MDPro3
}
else
{
UnityEngine.Debug.LogErrorFormat($"Image [{0}]: {1}", url, request.error);
UnityEngine.Debug.LogErrorFormat("Image [{0}]: {1}", url, request.error);
return null;
}
}
......
......@@ -69,23 +69,36 @@ namespace MDPro3
}
AssetBundle ab = await AssetBundle.LoadFromFileAsync(Program.root + path);
var assets = ab.LoadAllAssets();
foreach (UnityEngine.Object asset in assets)
var expectedName = Path.GetFileName(path);
if (!string.IsNullOrEmpty(expectedName))
{
if (typeof(GameObject).IsInstanceOfType(asset))
var assetRequest = ab.LoadAssetAsync<GameObject>(expectedName);
await assetRequest;
returnValue = assetRequest.asset as GameObject;
}
if (returnValue == null)
{
if (cache)
var assets = ab.LoadAllAssets();
foreach (UnityEngine.Object asset in assets)
{
if (!cachedAB.TryAdd(path, asset as GameObject))
Debug.LogWarning($"Failed to cache {path}");
}
returnValue = asset as GameObject;
//break;
if (!typeof(GameObject).IsInstanceOfType(asset))
continue;
var candidate = asset as GameObject;
returnValue = candidate;
if (candidate != null && candidate.name == expectedName)
break;
}
}
ab.Unload(false);
if (cache && returnValue != null)
{
if (!cachedAB.TryAdd(path, returnValue))
Debug.LogWarning($"Failed to cache {path}");
}
if (instantiate && returnValue != null)
return UnityEngine.Object.Instantiate(returnValue);
else
......
......@@ -100,7 +100,8 @@ namespace MDPro3
public List<Item> wallpapers; // 113
public List<List<Item>> kinds;
private const string ADDRESS_DEFAULT_DECK_CASE = "DeckCase0001_L";
private const int CODE_DEFAULT_DECK_CASE = 1080001;
private const string ADDRESS_DEFAULT_DECK_CASE_LEGACY = "DeckCase0001_L";
public const string STRING_NULL = "coming soon";
public const int CODE_NONE = 0;
......@@ -627,15 +628,35 @@ namespace MDPro3
}
public async UniTask<Sprite> LoadDeckCaseIconAsync(int code, string suffix)
{
var sprite = await TryLoadAddressableSprite(GetDeckCaseAddress(code, suffix));
if (sprite != null)
return sprite;
sprite = await TryLoadAddressableSprite(GetDeckCaseAddress(CODE_DEFAULT_DECK_CASE, suffix));
if (sprite != null)
return sprite;
return await TryLoadAddressableSprite(ADDRESS_DEFAULT_DECK_CASE_LEGACY);
}
private static string GetDeckCaseAddress(int code, string suffix)
{
if (code < 1080000 || code > 1089999)
code = CODE_DEFAULT_DECK_CASE;
return $"DeckCase{code.ToString()[3..]}{suffix ?? string.Empty}";
}
private async UniTask<Sprite> TryLoadAddressableSprite(string address)
{
try
{
return await LoadAddressableSprite($"DeckCase{code.ToString()[3..]}{suffix}");
return await LoadAddressableSprite(address);
}
catch
{
Debug.LogError("Addressables Not Found: " + $"DeckCase {code}_{suffix}");
return await LoadAddressableSprite(ADDRESS_DEFAULT_DECK_CASE);
return null;
}
}
......
using UnityEngine;
using System;
namespace MDPro3.UI
{
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)
{
RegisterEvent(se);
AudioManager.PlaySE(se, 0.4f);
}
void NewEvent(string se)
{
RegisterEvent(se);
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;
using MDPro3.UI.ServantUI;
using MDPro3.Utility;
using Cysharp.Threading.Tasks;
using MDPro3.Duel;
namespace MDPro3.UI
{
......@@ -28,6 +29,7 @@ namespace MDPro3.UI
m_Protector = m_Protector != null ? m_Protector
: Manager.GetElement<RawImage>(LABEL_RIMG_PROTECTOR);
private const string LABEL_IMG_WALLPAPER_BG = "WallpaperBG";
private Image m_WallpaperBG;
private Image WallpaperBG =>
......@@ -52,6 +54,11 @@ namespace MDPro3.UI
private Coroutine refreshCoroutine;
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()
{
base.Awake();
......@@ -131,6 +138,7 @@ namespace MDPro3.UI
Icon.material = Appearance.matForFace;
loaded = true;
StartPremiumCrossfade();
}
catch (OperationCanceledException)
{
......@@ -208,9 +216,10 @@ namespace MDPro3.UI
}
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>().IconMate.sprite = Icon.sprite;
}
......@@ -220,6 +229,9 @@ namespace MDPro3.UI
{
if (AppearanceUI.currentContent == "Wallpaper")
Config.Set("Wallpaper", itemID.ToString());
else if (AppearanceUI.currentContent == "Mate")
Config.Set(Appearance.condition.ToString() + AppearanceUI.currentContent + Appearance.player,
PremiumMateRules.GetBaseMateId(itemID).ToString());
else
Config.Set(Appearance.condition.ToString() + AppearanceUI.currentContent + Appearance.player, itemID.ToString());
......@@ -384,6 +396,7 @@ namespace MDPro3.UI
if (hideCoroutine != null || !gameObject.activeSelf)
return;
hideCoroutine = StartCoroutine(HideAsync());
StopPremiumCrossfade();
GetComponent<LayoutElement>().ignoreLayout = true;
GetComponent<RectTransform>().anchoredPosition = Vector2.zero;
......@@ -412,10 +425,13 @@ namespace MDPro3.UI
GetComponent<LayoutElement>().ignoreLayout = false;
transform.SetSiblingIndex(index);
StartPremiumCrossfade();
}
public void Dispose()
{
StopPremiumCrossfade();
if(refreshCoroutine != null)
StopCoroutine(refreshCoroutine);
......@@ -424,5 +440,150 @@ namespace MDPro3.UI
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;
using UnityEngine.UI;
using static MDPro3.Servant.Appearance;
using static YgomGame.Duel.BattleAimingEffect;
using MDPro3.Duel;
namespace MDPro3.UI.ServantUI
{
......@@ -390,6 +391,8 @@ namespace MDPro3.UI.ServantUI
int itemCount = 0;
foreach (var itemInfo in targetItems)
{
if (currentContent == "Mate" && PremiumMateRules.IsPremiumVariantId(itemInfo.id))
continue;
if (itemInfo.notReady) continue;
GameObject item = Instantiate(Template);
......@@ -520,44 +523,68 @@ namespace MDPro3.UI.ServantUI
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))
item.GetComponent<SelectionToggle_AppearanceItem>().Hide();
itemMono.Hide();
else
item.GetComponent<SelectionToggle_AppearanceItem>().Show();
itemMono.Show();
}
foreach (var item in currentList)
{
var itemMono = item.GetComponent<SelectionToggle_AppearanceItem>();
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;
}
}
else
{
var itemID = item.GetComponent<SelectionToggle_AppearanceItem>().itemID;
var itemID = itemMono.itemID;
if (currentContent == "Mate" && PremiumMateRules.IsPremiumVariantId(itemID))
continue;
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.Field
|| itemID == DeckEditor.Deck.Grave
|| itemID == DeckEditor.Deck.Stand
|| itemID == DeckEditor.Deck.Mate)
{
item.GetComponent<SelectionToggle_AppearanceItem>().SetToggleOn();
itemMono.SetToggleOn();
break;
}
}
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;
}
}
......
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