Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ server 模式会为每个 HTTP 请求创建独立线程和独立的 `GameClient`
- 必发效果可能由服务器直接强制发动,尤其是只有一个强制候选时,牌组注册的 `Activate` 执行器及其条件函数不会被调用。因此不要依赖发动条件函数为必发效果排入 `AI.Select*`、设置状态或完成其他副作用;通常只需让服务器发动,并在实际选择回调中处理选卡。
- 也就是说,`AddExecutor(ExecutorType.Activate, CardId.Sangan, SanganActivate);` 中在 `SanganActivate` 调用 `AI.SelectCard` 等方法基本是无意义的;必发效果的 `ExecutorType.Activate` 的意义应仅限于多个同时发动候选的优先级。
- 卡片发动时选择支付代价或选择指定目标,发生在连锁建立阶段。此时该卡已经加入 `Duel.CurrentChain`,但尚未进入连锁处理,应在 `OnSelectCard` 中用 `Duel.GetCurrentChainCard()` 识别最新连锁卡;同时检查控制者、卡号和 `hint`,再从服务器给出的候选中返回对象。
- 效果处理时才进行的选卡,例如从卡组检索、特殊召唤或效果处理中的丢弃,发生在连锁处理阶段,应在 `OnSelectCard` 中用 `Duel.GetCurrentSolvingChainCard()` 识别正在处理的连锁卡。该方法在发动、支付代价和指定目标时会返回 `null`。
- 效果处理时才进行的选卡,例如从卡组检索、特殊召唤或效果处理中的丢弃,发生在连锁处理阶段,应在 `OnSelectCard` 中用 `Duel.GetCurrentSolvingChainInfo()` 识别正在处理的连锁。优先用发动快照字段判断:`ActivatePlayer`(发动者)、`IsActivateCode` / `ActivateId`(发动卡号)、`ActivateSequence` / `ActivateLocation` 等。该方法在发动、支付代价和指定目标时会返回 `null`。仅当需要活卡引用比较(如 `Enemy.SpellZone[i] == info.RelatedCard`)或调试日志时再使用 `RelatedCard`;一般不要用 `GetCurrentSolvingChainCard()` 做卡号/控制者判断
- 注意,**以上问题仅限于必发效果**,即满足条件必定强制发动的 `EFFECT_TYPE_TRIGGER_F` 的效果。效果文本中写“〇〇的场合才能发动”通常不是必发效果,写“〇〇的场合发动”通常是必发效果。普通可选发动的效果一般应使用 `AI.SelectCard` 等方法预先选择,仅在选择目标难以预测时改用 `OnSelectCard` 处理。
- `GetCurrentChainCard()` 只表示尚未开始处理时的最新连锁卡,连锁开始处理后返回 `null`;`GetCurrentSolvingChainCard()` 只表示当前正在处理的连锁卡。不要用 `CurrentChain.LastOrDefault()` 或 `AIUtil.GetLastChainCard()` 代替这一区分,否则多段连锁倒序处理时可能把选卡归给错误的连锁卡。
- `GetCurrentChainCard()` 只表示尚未开始处理时的最新连锁卡,连锁开始处理后返回 `null`;`GetCurrentSolvingChainInfo()` / `GetCurrentSolvingChainCard()` 只表示当前正在处理的连锁(Info 为发动快照,Card 为活卡引用)。识别处理中的连锁源时优先用 Info。不要用 `CurrentChain.LastOrDefault()` 或 `AIUtil.GetLastChainCard()` 代替这一区分,否则多段连锁倒序处理时可能把选卡归给错误的连锁卡。
- 如果同一张卡同时具有发动时目标、处理时选卡或多个不同效果,必须结合 `hint`、候选区域和必要的效果描述进一步区分。不要让 `OnSelectCard` 返回选择的同时还保留同一流程的预选队列,否则残留选择可能污染下一次选卡。

## 新增或修改牌组
Expand Down
53 changes: 26 additions & 27 deletions Game/AI/Decks/AlbazExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,10 +617,10 @@ public override bool OnSelectHand()

public override IList<ClientCard> OnSelectCard(IList<ClientCard> cards, int min, int max, int hint, bool cancelable)
{
ClientCard currentSolvingChain = Duel.GetCurrentSolvingChainCard();
ChainInfo currentSolvingChain = Duel.GetCurrentSolvingChainInfo();
if (currentSolvingChain != null)
{
if (currentSolvingChain.Controller == 1 && currentSolvingChain.IsCode(_CardId.EvenlyMatched))
if (currentSolvingChain.ActivatePlayer == 1 && currentSolvingChain.IsActivateCode(_CardId.EvenlyMatched))
{
Logger.DebugWriteLine("=== Evenly Matched activated.");
List<ClientCard> banishList = new List<ClientCard>();
Expand Down Expand Up @@ -663,7 +663,7 @@ public override IList<ClientCard> OnSelectCard(IList<ClientCard> cards, int min,
{
Dictionary<int, Func<bool>> checkDict = new Dictionary<int, Func<bool>>();

switch (currentSolvingChain.Id)
switch (currentSolvingChain.ActivateId)
{
case CardId.AluberTheJesterOfDespia:
case CardId.AluberTheJesterOfDespia + 1:
Expand Down Expand Up @@ -747,7 +747,7 @@ public override IList<ClientCard> OnSelectCard(IList<ClientCard> cards, int min,
}
}

switch (currentSolvingChain.Id)
switch (currentSolvingChain.ActivateId)
{
// for lubellion
case CardId.TheBystialLubellion:
Expand Down Expand Up @@ -1989,7 +1989,7 @@ public override IList<ClientCard> OnSelectCard(IList<ClientCard> cards, int min,
bool handToDeck = hint == HintMsg.ToDeck && cards.All(c => c.Location == CardLocation.Hand);
if (min == 1 && max == 1 && (discardHand || handToDeck))
{
if (currentSolvingChain != null && currentSolvingChain.IsCode(CardId.BrandedOpening))
if (currentSolvingChain != null && currentSolvingChain.IsActivateCode(CardId.BrandedOpening))
{
ClientCard tragedy = cards.FirstOrDefault(card => card.IsCode(CardId.DespianTragedy));
if (tragedy != null)
Expand Down Expand Up @@ -2104,7 +2104,7 @@ public override IList<ClientCard> OnSelectCard(IList<ClientCard> cards, int min,
}

// for shrouded/saronir
if (albionTheShroudedDragonSelecting || (currentSolvingChain != null && currentSolvingChain.IsCode(CardId.BystialSaronir)))
if (albionTheShroudedDragonSelecting || (currentSolvingChain != null && currentSolvingChain.IsActivateCode(CardId.BystialSaronir)))
{
// send retribution first
ClientCard retribution = cards.FirstOrDefault(c => c.IsCode(CardId.BrandedRetribution));
Expand Down Expand Up @@ -2192,15 +2192,15 @@ public override int OnSelectOption(IList<int> options)
// 1190=Add to Hand, 1152=Special Summon
if (options.Count == 2 && options.Contains(1190) && options.Contains(1152))
{
if (currentSolvingChain.IsCode(CardId.BrandedOpening))
if (currentSolvingChain.IsActivateCode(CardId.BrandedOpening))
{
return (CheckShouldNoMoreSpSummon() && !summoned && Duel.Player == 0) ? options.IndexOf(1190) : options.IndexOf(1152);
}

if (fusionTarget != null && (
currentSolvingChain.IsCode(CardId.DespianQuaeritis)
|| currentSolvingChain.IsCode(CardId.TitanikladTheAshDragon)
|| currentSolvingChain.IsCode(CardId.SprindTheIrondashDragon)
currentSolvingChain.IsActivateCode(CardId.DespianQuaeritis)
|| currentSolvingChain.IsActivateCode(CardId.TitanikladTheAshDragon)
|| currentSolvingChain.IsActivateCode(CardId.SprindTheIrondashDragon)
))
{
if (fusionTarget.IsCode(CardId.FallenOfAlbaz))
Expand All @@ -2220,7 +2220,7 @@ public override int OnSelectOption(IList<int> options)
}

// 1190=Add to Hand, 1153=Set
if (currentSolvingChain.IsCode(CardId.AlbionTheBrandedDragon) && fusionTarget != null)
if (currentSolvingChain.IsActivateCode(CardId.AlbionTheBrandedDragon) && fusionTarget != null)
{
if (fusionTarget.IsOriginalCode(CardId.BrandedInHighSpirits) && Duel.Player == 0)
{
Expand All @@ -2247,7 +2247,7 @@ public override int OnSelectOption(IList<int> options)
public override int OnSelectPlace(int cardId, int player, CardLocation location, int available)
{
ChainInfo currentSovingChain = Duel.GetCurrentSolvingChainInfo();
if (currentSovingChain != null && currentSovingChain.ActivatePlayer == 0 && currentSovingChain.IsCode(CardId.SprindTheIrondashDragon))
if (currentSovingChain != null && currentSovingChain.ActivatePlayer == 0 && currentSovingChain.IsActivateCode(CardId.SprindTheIrondashDragon))
{
return SprindTheIrondashDragonMoveZone(available, null);
}
Expand Down Expand Up @@ -2317,10 +2317,10 @@ public override bool OnSelectYesNo(int desc)

if (desc == Util.GetStringId(CardId.SprindTheIrondashDragon, 2))
{
ClientCard currentSolvingChain = Duel.GetCurrentSolvingChainCard();
ChainInfo currentSolvingChain = Duel.GetCurrentSolvingChainInfo();
if (currentSolvingChain != null)
{
int value = SprindTheIrondashDragonDestroyValue(currentSolvingChain.Sequence);
int value = SprindTheIrondashDragonDestroyValue(currentSolvingChain.RelatedCard.Sequence);
return value > 0;
}
}
Expand All @@ -2330,8 +2330,8 @@ public override bool OnSelectYesNo(int desc)

public override CardPosition OnSelectPosition(int cardId, IList<CardPosition> positions)
{
ClientCard currentSolvingChain = Duel.GetCurrentSolvingChainCard();
if (currentSolvingChain != null && currentSolvingChain.IsCode(CardId.AlbionTheSanctifireDragon))
ChainInfo currentSolvingChain = Duel.GetCurrentSolvingChainInfo();
if (currentSolvingChain != null && currentSolvingChain.IsActivateCode(CardId.AlbionTheSanctifireDragon))
{
sanctifireSelectPositionCount++;
if (sanctifireSelectPositionCount >= 2)
Expand Down Expand Up @@ -2435,30 +2435,30 @@ public override void OnChaining(int player, ClientCard card)

public override void OnChainSolved(int chainIndex)
{
ChainInfo currentCard = Duel.GetCurrentSolvingChainInfo();
if (currentCard != null)
ChainInfo currentChain = Duel.GetCurrentSolvingChainInfo();
if (currentChain != null)
{
// if activation is negated, it can activate again.
if (currentCard.ActivatePlayer == 0)
if (currentChain.ActivatePlayer == 0)
{
List<int> activateCheck = new List<int> { CardId.NadirServant, CardId.FusionDeployment, CardId.BrandedFusion, CardId.BrandedInRed };
if (currentCard.IsCode(activateCheck))
if (currentChain.IsActivateCode(activateCheck))
{
activatedCardIdList.Add(currentCard.ActivateId);
activatedCardIdList.Add(currentChain.ActivateId);
}
}
if (!Duel.IsCurrentSolvingChainNegated())
{
if (currentCard.ActivatePlayer == 1)
if (currentChain.ActivatePlayer == 1)
{
if (currentCard.IsCode(_CardId.MaxxC))
if (currentChain.IsActivateCode(_CardId.MaxxC))
enemyActivateMaxxC = true;
if (currentCard.IsCode(_CardId.LockBird))
if (currentChain.IsActivateCode(_CardId.LockBird))
enemyActivateLockBird = true;
if (currentCard.IsCode(CardId.DimensionShifter))
if (currentChain.IsActivateCode(CardId.DimensionShifter))
dimensionShifterCount = 2;
}
if (currentCard.ActivatePlayer == 0 && currentCard.IsCode(CardId.NadirServant))
if (currentChain.ActivatePlayer == 0 && currentChain.IsActivateCode(CardId.NadirServant))
{
nadirActivated = true;
}
Expand Down Expand Up @@ -2512,7 +2512,6 @@ public override void OnMove(ClientCard card, int previousControler, int previous
}
if (currentControler == 0)
{
ClientCard currentSolvingChain = Duel.GetCurrentSolvingChainCard();
if (previousLocation == (int)CardLocation.Grave && currentLocation != (int)CardLocation.Grave)
{
sendToGYThisTurn.Remove(card);
Expand Down
15 changes: 5 additions & 10 deletions Game/AI/Decks/BE2025Executor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,17 +267,12 @@ public override bool OnSelectYesNo(int desc)
}
public override IList<ClientCard> OnSelectCard(IList<ClientCard> cards, int min, int max, int hint, bool cancelable)
{
ChainInfo currentSolvingChain =
Duel.GetCurrentSolvingChainInfo();

ClientCard solving =
Duel.GetCurrentSolvingChainCard();

ChainInfo solving = Duel.GetCurrentSolvingChainInfo();
Logger.DebugWriteLine(
$"OnSelectCard count={cards?.Count ?? 0}, " +
$"min={min}, max={max}, hint={hint}");

if (solving != null && solving.Controller == 1 && solving.IsCode(CardId.AmeNoMurakumoNoMitsurugi)
if (solving != null && solving.ActivatePlayer == 1 && solving.IsActivateCode(CardId.AmeNoMurakumoNoMitsurugi)
&& cards != null && cards.Count > 0 && (hint == HintMsg.Discard || hint == HintMsg.ToGrave)
&& cards.All(c => c != null && c.Controller == 0 && c.Location == CardLocation.Hand))
{
Expand All @@ -293,7 +288,7 @@ public override IList<ClientCard> OnSelectCard(IList<ClientCard> cards, int min,
public override void OnSpSummoned()
{
// not special summoned by chain
if (Duel.GetCurrentSolvingChainCard() == null)
if (Duel.GetCurrentSolvingChainInfo() == null)
{
/*foreach (ClientCard card in Duel.LastSummonedCards)
{
Expand Down Expand Up @@ -1133,9 +1128,9 @@ private bool SageWithEyesOfBlueEffectInHand()
private bool BlueEyesSpiritDragonEffect()
{
if (!DontSelfNG()) return false;
ClientCard currentCard = Duel.GetCurrentSolvingChainCard();
ChainInfo currentChain = Duel.GetCurrentSolvingChainInfo();
if ((ActivateDescription == -1 || ActivateDescription == Util.GetStringId(CardId.BlueEyesSpiritDragon, 0)) &&
((Duel.LastChainPlayer == 1) || (Duel.LastChainPlayer == 0 && currentCard != null && currentCard.IsCode(CardId.TrueLight))))
((Duel.LastChainPlayer == 1) || (Duel.LastChainPlayer == 0 && currentChain != null && currentChain.IsActivateCode(CardId.TrueLight))))
{
Logger.DebugWriteLine("Spirit 1 negate");
return true;
Expand Down
2 changes: 1 addition & 1 deletion Game/AI/Decks/BlueEyesExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public override IList<ClientCard> OnSelectSynchroMaterial(IList<ClientCard> card
public override void OnSpSummoned()
{
// not special summoned by chain
if (Duel.GetCurrentSolvingChainCard() == null)
if (Duel.GetCurrentSolvingChainInfo() == null)
{
foreach (ClientCard card in Duel.LastSummonedCards)
{
Expand Down
20 changes: 10 additions & 10 deletions Game/AI/Decks/Chaos408Executor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ public override IList<ClientCard> OnSelectCard(
IList<ClientCard> cards, int min, int max, int hint, bool cancelable)
{
ClientCard currentChainCard = Duel.GetCurrentChainCard();
ClientCard solvingChainCard = Duel.GetCurrentSolvingChainCard();
ChainInfo solvingChain = Duel.GetCurrentSolvingChainInfo();

if (solvingChainCard != null &&
solvingChainCard.Controller == 0 &&
solvingChainCard.IsCode(CardId.GracefulCharity) &&
if (solvingChain != null &&
solvingChain.ActivatePlayer == 0 &&
solvingChain.IsActivateCode(CardId.GracefulCharity) &&
hint == HintMsg.Discard)
{
List<ClientCard> selected = new List<ClientCard>();
Expand Down Expand Up @@ -196,9 +196,9 @@ public override IList<ClientCard> OnSelectCard(
return Util.CheckSelectCount(selected, cards, min, max);
}

if (solvingChainCard != null &&
solvingChainCard.Controller == 0 &&
solvingChainCard.IsCode(CardId.Confiscation) &&
if (solvingChain != null &&
solvingChain.ActivatePlayer == 0 &&
solvingChain.IsActivateCode(CardId.Confiscation) &&
hint == HintMsg.Discard)
{
List<ClientCard> targets = new List<ClientCard>();
Expand Down Expand Up @@ -317,9 +317,9 @@ public override IList<ClientCard> OnSelectCard(
return Util.CheckSelectCount(targets, cards, min, max);
}

if (solvingChainCard != null &&
solvingChainCard.Controller == 0 &&
solvingChainCard.IsCode(CardId.Sangan) &&
if (solvingChain != null &&
solvingChain.ActivatePlayer == 0 &&
solvingChain.IsActivateCode(CardId.Sangan) &&
hint == HintMsg.AddToHand)
{
List<int> priority = Duel.Player == 0
Expand Down
14 changes: 7 additions & 7 deletions Game/AI/Decks/DogmatikaExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,20 +1153,20 @@ public override void OnChaining(int player, ClientCard card)

public override void OnChainSolved(int chainIndex)
{
ChainInfo currentCard = Duel.GetCurrentSolvingChainInfo();
if (currentCard != null && !Duel.IsCurrentSolvingChainNegated() && currentCard.ActivatePlayer == 1)
ChainInfo currentChain = Duel.GetCurrentSolvingChainInfo();
if (currentChain != null && !Duel.IsCurrentSolvingChainNegated() && currentChain.ActivatePlayer == 1)
{
if (currentCard.IsCode(_CardId.MaxxC))
if (currentChain.IsActivateCode(_CardId.MaxxC))
enemyActivateMaxxC = true;
if (currentCard.IsCode(_CardId.LockBird))
if (currentChain.IsActivateCode(_CardId.LockBird))
enemyActivateLockBird = true;
if (currentCard.IsCode(CardId.DimensionShifter))
if (currentChain.IsActivateCode(CardId.DimensionShifter))
dimensionShifterCount = 2;
if (currentCard.IsCode(_CardId.InfiniteImpermanence))
if (currentChain.IsActivateCode(_CardId.InfiniteImpermanence))
{
for (int i = 0; i < 5; ++i)
{
if (Enemy.SpellZone[i] == currentCard.RelatedCard)
if (Enemy.SpellZone[i] == currentChain.RelatedCard)
{
infiniteImpermanenceList.Add(4 - i);
break;
Expand Down
8 changes: 4 additions & 4 deletions Game/AI/Decks/DragunExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ public override int OnSelectPlace(int cardId, int player, CardLocation location,
public override IList<ClientCard> OnSelectCard(
IList<ClientCard> cards, int min, int max, int hint, bool cancelable)
{
ClientCard solvingChainCard = Duel.GetCurrentSolvingChainCard();
if (solvingChainCard != null &&
solvingChainCard.Controller == 0 &&
solvingChainCard.IsCode(CardId.Sangan) &&
ChainInfo solvingChain = Duel.GetCurrentSolvingChainInfo();
if (solvingChain != null &&
solvingChain.ActivatePlayer == 0 &&
solvingChain.IsActivateCode(CardId.Sangan) &&
hint == HintMsg.AddToHand)
{
List<int> priority = new List<int>();
Expand Down
Loading