Add boost attack gestures and Barcgal activated search
CI / test (push) Has been cancelled Details

This commit is contained in:
MpakobecEx 2026-10-23 19:08:23 +05:00
parent 08c170e0d9
commit aa330c6cfa
4 changed files with 145 additions and 6 deletions

View File

@ -90,6 +90,7 @@ namespace Vanguard.UI
private string _discardPrompt; private string _discardPrompt;
private Action _discardCancelAction; private Action _discardCancelAction;
private int _pendingBoosterSlot = -1; private int _pendingBoosterSlot = -1;
private int _pendingBoostAttackerSlot = -2;
private int? _triggerPowerSlot; private int? _triggerPowerSlot;
private readonly HashSet<int> _mulliganSelected = new HashSet<int>(); private readonly HashSet<int> _mulliganSelected = new HashSet<int>();
private bool _mulliganUsed; private bool _mulliganUsed;
@ -278,6 +279,7 @@ namespace Vanguard.UI
_mulliganUsed = false; _mulliganUsed = false;
_abilityDamageIndex = -1; _abilityDamageIndex = -1;
_pendingBoosterSlot = -1; _pendingBoosterSlot = -1;
_pendingBoostAttackerSlot = -2;
_triggerPowerSlot = null; _triggerPowerSlot = null;
_showAllIntents = false; _showAllIntents = false;
_autoFinishMulligan = false; _autoFinishMulligan = false;
@ -1087,6 +1089,7 @@ namespace Vanguard.UI
_pendingIntents.Clear(); _pendingIntents.Clear();
_highlightSlots.Clear(); _highlightSlots.Clear();
_pendingBoosterSlot = -1; _pendingBoosterSlot = -1;
_pendingBoostAttackerSlot = -2;
_triggerPowerSlot = null; _triggerPowerSlot = null;
if (intent is FinishMulliganIntent or SkipRideIntent if (intent is FinishMulliganIntent or SkipRideIntent
or EnterBattleIntent or EndTurnIntent) or EnterBattleIntent or EndTurnIntent)
@ -2105,7 +2108,22 @@ namespace Vanguard.UI
var legal = _view.LegalIntents ?? new List<IntentPayload>(); var legal = _view.LegalIntents ?? new List<IntentPayload>();
IntentPayload intent = null; IntentPayload intent = null;
if (source.Zone == BoardZone.Hand) if (_pendingBoosterSlot >= 0 && source.IsField)
{
if (source.Slot != _pendingBoostAttackerSlot)
{
SetStatus(
"Поддержка уже объявлена: атаковать можно только поддерживаемой картой.",
new Color(1f, 0.55f, 0.45f));
return;
}
intent = legal.OfType<AttackIntent>()
.FirstOrDefault(item => item.AttackerSlot == source.Slot
&& item.TargetSlot == target.Slot
&& item.BoosterSlot == _pendingBoosterSlot);
}
else if (source.Zone == BoardZone.Hand)
{ {
if (target.Zone == BoardZone.Guardian) if (target.Zone == BoardZone.Guardian)
{ {
@ -2139,6 +2157,15 @@ namespace Vanguard.UI
intent = legal.OfType<InterceptIntent>() intent = legal.OfType<InterceptIntent>()
.FirstOrDefault(item => item.Slot == source.Slot); .FirstOrDefault(item => item.Slot == source.Slot);
} }
else if (target.Player == _view.You
&& source.Slot is 0 or 1 or 2
&& (target.Zone == BoardZone.Vanguard
|| (target.Zone == BoardZone.RearGuard
&& target.Slot is 3 or 4)))
{
BeginBoostAttack(source.Slot, target.Slot, legal);
return;
}
else if (target.Player == _view.You && target.Zone == BoardZone.RearGuard) else if (target.Player == _view.You && target.Zone == BoardZone.RearGuard)
{ {
intent = legal.OfType<MoveIntent>() intent = legal.OfType<MoveIntent>()
@ -2166,6 +2193,36 @@ namespace Vanguard.UI
OnIntentClicked(intent); OnIntentClicked(intent);
} }
private void BeginBoostAttack(
int boosterSlot,
int attackerSlot,
IReadOnlyList<IntentPayload> legal)
{
var attacks = legal.OfType<AttackIntent>()
.Where(intent => intent.BoosterSlot == boosterSlot
&& intent.AttackerSlot == attackerSlot)
.ToList();
if (attacks.Count == 0)
{
SetStatus(
"Эта карта не может поддержать выбранный круг.",
new Color(1f, 0.55f, 0.45f));
return;
}
_pendingBoosterSlot = boosterSlot;
_pendingBoostAttackerSlot = attackerSlot;
_pendingIntents.Clear();
_pendingIntents.AddRange(attacks);
RebuildHighlights();
RebuildIntents(_view);
SetStatus(
"Поддержка: " + CardName(
CardAt(_view.You, boosterSlot)?.PrintingId ?? string.Empty)
+ ". Атакуйте только этой картой и выберите цель.",
new Color(0.98f, 0.82f, 0.42f));
}
private void OnBoard3DPileRightClicked(BoardCardRef pile) private void OnBoard3DPileRightClicked(BoardCardRef pile)
{ {
if (_view == null || _inputLock.IsLocked) if (_view == null || _inputLock.IsLocked)
@ -2260,6 +2317,11 @@ namespace Vanguard.UI
? "Выберите карту в руке для сброса." ? "Выберите карту в руке для сброса."
: _discardPrompt; : _discardPrompt;
} }
else if (_pendingBoosterSlot >= 0 && _pendingIntents.Count > 0)
{
title = "ПОДДЕРЖКА";
hint = "Атакуйте только поддерживаемой картой и выберите цель.";
}
else if (_pendingIntents.Count > 0) else if (_pendingIntents.Count > 0)
{ {
title = "ВЫБОР ЦЕЛИ"; title = "ВЫБОР ЦЕЛИ";
@ -2809,6 +2871,7 @@ namespace Vanguard.UI
_pendingIntents.Clear(); _pendingIntents.Clear();
_highlightSlots.Clear(); _highlightSlots.Clear();
_pendingBoosterSlot = -1; _pendingBoosterSlot = -1;
_pendingBoostAttackerSlot = -2;
_triggerPowerSlot = null; _triggerPowerSlot = null;
if (rebuild && _view != null) if (rebuild && _view != null)
RebuildIntents(_view); RebuildIntents(_view);

View File

@ -358,6 +358,24 @@ public sealed class BarcgalScript : CardScript
}; };
} }
} }
public override IEnumerable<ActivatedAbility> Activated
{
get
{
yield return new ActivatedAbility
{
RestsSelf = true,
CanActivate = ctx =>
!ctx.Engine.IsVanguardOf(ctx.Source, ctx.Owner) &&
ctx.Engine.HasEmptyRearGuard(ctx.Owner),
Resolve = ctx => ctx.Engine.SearchDeckToCall(
ctx.Owner,
"Найдите Future Knight, Llew или Flogal",
definition => definition.NameId is "future-knight-llew" or "flogal"),
};
}
}
} }
/// <summary> /// <summary>

View File

@ -1510,9 +1510,15 @@ public sealed class GameEngine
foreach (var target in targets) foreach (var target in targets)
commands.Add(new AttackCommand(attackerSlot, target)); commands.Add(new AttackCommand(attackerSlot, target));
if (attackerSlot is 3 or 4) var boosterSlot = attackerSlot switch
{
-1 => 1, // Vanguard is boosted from the centre back row.
3 => 0,
4 => 2,
_ => -1,
};
if (boosterSlot >= 0)
{ {
var boosterSlot = attackerSlot == 3 ? 0 : 2;
var booster = State.GetUnit(player, boosterSlot); var booster = State.GetUnit(player, boosterSlot);
if (booster is not null && !booster.Rested && State.CanBoost(booster)) if (booster is not null && !booster.Rested && State.CanBoost(booster))
{ {
@ -1986,10 +1992,17 @@ public sealed class GameEngine
CardInstance? booster = null; CardInstance? booster = null;
if (boosterSlot >= 0) if (boosterSlot >= 0)
{ {
if (attackerSlot is not (3 or 4)) var expected = attackerSlot switch
return CommandResult.Reject("Поддерживать можно только атаку Арьергарда из переднего ряда."); {
-1 => 1,
3 => 0,
4 => 2,
_ => -1,
};
if (expected < 0)
return CommandResult.Reject(
"Поддерживать можно только Авангард или Арьергард переднего ряда.");
var expected = attackerSlot == 3 ? 0 : 2;
if (boosterSlot != expected) if (boosterSlot != expected)
return CommandResult.Reject("Поддержка возможна только из заднего ряда той же колонны."); return CommandResult.Reject("Поддержка возможна только из заднего ряда той же колонны.");

View File

@ -823,6 +823,51 @@ public class GameEngineTests
Assert.DoesNotContain(engine.State.Players[0].RearGuards, u => u?.PrintingId == Barcgal); Assert.DoesNotContain(engine.State.Players[0].RearGuards, u => u?.PrintingId == Barcgal);
} }
[Fact]
public void Vanguard_Can_Be_Boosted_From_Centre_Back_Row()
{
var engine = StartedEngine();
Give(engine, 0, Marron);
engine.Execute(new RideCommand(Marron));
Give(engine, 0, Marron);
Assert.True(engine.Execute(new CallCommand(Marron, 1)).Accepted);
engine.State.Players[0].RearGuards[1]!.Rested = false;
SetVanguard(engine, 1, Gallatin);
engine.Execute(new EnterBattleCommand());
Assert.True(engine.Execute(new AttackCommand(-1, -1, 1)).Accepted);
Assert.NotNull(engine.State.Battle);
Assert.Equal(-1, engine.State.Battle!.AttackerSlot);
Assert.Equal(1, engine.State.Battle.BoosterSlot);
}
[Fact]
public void Barcgal_Activated_Searches_And_Calls_Unit_To_Chosen_Circle()
{
var engine = InMainPhase();
Give(engine, 0, Barcgal);
var barcgal = engine.State.Players[0].Hand
.Single(card => card.PrintingId == Barcgal);
Assert.True(engine.Execute(new CallCommand(Barcgal, 0)).Accepted);
barcgal.Rested = false;
Assert.True(engine.Execute(new ActivateAbilityCommand(barcgal.Id)).Accepted);
Assert.NotNull(engine.State.PendingChoice);
Assert.Equal(Step.Choice, engine.State.Step);
Assert.True(engine.Execute(new ChooseCommand(0)).Accepted);
Assert.NotNull(engine.State.PendingChoice);
Assert.True(engine.Execute(new ChooseCommand(1)).Accepted);
Assert.Equal(
Flogal,
engine.State.Players[0].RearGuards[2]?.PrintingId);
Assert.True(barcgal.Rested);
}
[Fact] [Fact]
public void Wingal_Gives_BlasterBlade_4000_During_Battle() public void Wingal_Gives_BlasterBlade_4000_During_Battle()
{ {