using MessagePack; using OECS; namespace Game.CardWars; [MessagePackObject] public struct SkipPlayCommand : ICommand { public void Execute(World world) { var state = world.ReadSingleton(); if (state.Phase != GamePhase.PlayPhase) return; ref var mutable = ref world.GetSingleton(); mutable.PlayPhaseEnded = true; world.MarkModified(World.SingletonEntity); } } [MessagePackObject] public struct FlipCardCommand : ICommand { [Key(0)] public Entity CardEntity; public void Execute(World world) { var state = world.ReadSingleton(); if (state.Phase != GamePhase.FlipPhase) return; if (!world.IsAlive(CardEntity)) return; if (!world.HasComponent(CardEntity)) return; ref var card = ref world.GetComponent(CardEntity); if (!card.FaceDown) return; card.FaceDown = false; world.MarkModified(CardEntity); var def = world.ReadComponent(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(); 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(c) && world.ReadComponent(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(deck); if (cards.Count == 0) { // Recycle: for now, faction discards recycle to the same deck. GameUtil.RecycleDiscard(world, deck, deck); cards = world.GetSources(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(); while (iter.MoveNext()) { if (iter.CurrentEntity != World.SingletonEntity) return iter.CurrentEntity; } } else { using var iter = world.Select(); 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(); 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(); var castles = GameUtil.FindAllEntities(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] }); } } } /// /// 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 /// [MessagePackObject] public struct ResolveChoiceCommand : ICommand { [Key(0)] public Entity Target; public void Execute(World world) { if (!PendingChoice.Any(world)) return; CardEffectRegistry.DispatchChoice(world, Target); } }