173 lines
5.0 KiB
C#
173 lines
5.0 KiB
C#
using MessagePack;
|
|
using OECS;
|
|
|
|
namespace Game.CardWars;
|
|
|
|
[MessagePackObject]
|
|
public struct SkipPlayCommand : ICommand
|
|
{
|
|
public void Execute(World world)
|
|
{
|
|
var state = world.ReadSingleton<GameState>();
|
|
if (state.Phase != GamePhase.PlayPhase) return;
|
|
|
|
ref var mutable = ref world.GetSingleton<GameState>();
|
|
mutable.PlayPhaseEnded = true;
|
|
world.MarkModified<GameState>(World.SingletonEntity);
|
|
}
|
|
}
|
|
|
|
[MessagePackObject]
|
|
public struct FlipCardCommand : ICommand
|
|
{
|
|
[Key(0)] public Entity CardEntity;
|
|
|
|
public void Execute(World world)
|
|
{
|
|
var state = world.ReadSingleton<GameState>();
|
|
if (state.Phase != GamePhase.FlipPhase) return;
|
|
if (!world.IsAlive(CardEntity)) return;
|
|
if (!world.HasComponent<Card>(CardEntity)) return;
|
|
|
|
ref var card = ref world.GetComponent<Card>(CardEntity);
|
|
if (!card.FaceDown) return;
|
|
|
|
card.FaceDown = false;
|
|
world.MarkModified<Card>(CardEntity);
|
|
|
|
var def = world.ReadComponent<CardDef>(CardEntity);
|
|
Entity player = PlayCardCommand.FindPlayerByIndex(world, state.CurrentPlayerIndex);
|
|
|
|
// Resolve flip effects via registry.
|
|
CardEffectRegistry.Dispatch(world, def.Effect, CardEntity, player, player);
|
|
}
|
|
}
|
|
|
|
[MessagePackObject]
|
|
public struct SkipFlipCommand : ICommand
|
|
{
|
|
public void Execute(World world)
|
|
{
|
|
// Skip is only valid if no face-down cards remain.
|
|
var state = world.ReadSingleton<GameState>();
|
|
if (state.Phase != GamePhase.FlipPhase) return;
|
|
|
|
Entity player = PlayCardCommand.FindPlayerByIndex(world, state.CurrentPlayerIndex);
|
|
var fieldCards = GameUtil.GetFieldCards(world, player);
|
|
bool hasFaceDown = fieldCards.Any(c =>
|
|
world.HasComponent<Card>(c) && world.ReadComponent<Card>(c).FaceDown);
|
|
if (hasFaceDown) return;
|
|
|
|
// Advance is handled by FlipPhaseSystem.
|
|
}
|
|
}
|
|
|
|
[MessagePackObject]
|
|
public struct DrawCardCommand : ICommand
|
|
{
|
|
[Key(0)] public Entity Player;
|
|
[Key(1)] public CardKind? Kind;
|
|
|
|
public void Execute(World world)
|
|
{
|
|
if (!world.IsAlive(Player)) return;
|
|
|
|
var kind = Kind ?? CardKind.Public;
|
|
var deck = GetDeck(world, kind);
|
|
if (deck == Entity.Null) return;
|
|
|
|
var cards = world.GetSources<InDeck>(deck);
|
|
if (cards.Count == 0)
|
|
{
|
|
// Recycle: for now, faction discards recycle to the same deck.
|
|
GameUtil.RecycleDiscard(world, deck, deck);
|
|
cards = world.GetSources<InDeck>(deck);
|
|
if (cards.Count == 0) return;
|
|
}
|
|
|
|
GameUtil.DrawCard(world, deck, Player);
|
|
}
|
|
|
|
private static Entity GetDeck(World world, CardKind kind)
|
|
{
|
|
if (kind == CardKind.Public)
|
|
{
|
|
using var iter = world.Select<PublicDeck>();
|
|
while (iter.MoveNext())
|
|
{
|
|
if (iter.CurrentEntity != World.SingletonEntity)
|
|
return iter.CurrentEntity;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
using var iter = world.Select<FactionDeck>();
|
|
while (iter.MoveNext())
|
|
{
|
|
if (iter.CurrentEntity != World.SingletonEntity)
|
|
return iter.CurrentEntity;
|
|
}
|
|
}
|
|
return Entity.Null;
|
|
}
|
|
}
|
|
|
|
[MessagePackObject]
|
|
public struct ExtraActionCommand : ICommand
|
|
{
|
|
public void Execute(World world) { }
|
|
}
|
|
|
|
[MessagePackObject]
|
|
public struct EndPlayPhaseCommand : ICommand
|
|
{
|
|
public void Execute(World world)
|
|
{
|
|
ref var state = ref world.GetSingleton<GameState>();
|
|
if (state.Phase != GamePhase.PlayPhase) return;
|
|
world.SetSingleton(new PendingDuelist { PlayerIndex = state.CurrentPlayerIndex });
|
|
}
|
|
}
|
|
|
|
[MessagePackObject]
|
|
public struct ReplaceCastleCommand : ICommand
|
|
{
|
|
public void Execute(World world)
|
|
{
|
|
var state = world.ReadSingleton<GameState>();
|
|
var castles = GameUtil.FindAllEntities<Castle>(world);
|
|
foreach (var c in castles)
|
|
world.DestroyEntity(c);
|
|
|
|
var colors = new[] { CastleColor.Black, CastleColor.White, CastleColor.Blue,
|
|
CastleColor.Brown, CastleColor.Yellow, CastleColor.Indigo, CastleColor.Gold };
|
|
var seed = state.Seed;
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
int idx = Mulberry32.NextInt(ref seed, 0, colors.Length - 1);
|
|
var castle = world.CreateEntity();
|
|
world.AddComponent(castle, new Castle { Color = colors[idx] });
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Player makes a choice to resolve a pending card effect.
|
|
/// The target entity depends on the effect:
|
|
/// - Mercenary, Dancer, Paladin, Nun: a field card entity
|
|
/// - Scout: a player entity
|
|
/// - Pegasus: a field card to return (or null)
|
|
/// - CurseMaster: a horn token entity
|
|
/// </summary>
|
|
[MessagePackObject]
|
|
public struct ResolveChoiceCommand : ICommand
|
|
{
|
|
[Key(0)] public Entity Target;
|
|
|
|
public void Execute(World world)
|
|
{
|
|
if (!PendingChoice.Any(world)) return;
|
|
CardEffectRegistry.DispatchChoice(world, Target);
|
|
}
|
|
} |