diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..684d545 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.cs] +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false diff --git a/.gitignore b/.gitignore index 7aae88a..030b2c8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /[Bb]uild/ /[Bb]uilds/ /Logs +/UserSettings /Assets/AssetStoreTools* /Assets/Nonredist* diff --git a/Assets/Resources/BillingMode.json b/Assets/Resources/BillingMode.json new file mode 100644 index 0000000..6f4bfb7 --- /dev/null +++ b/Assets/Resources/BillingMode.json @@ -0,0 +1 @@ +{"androidStore":"GooglePlay"} \ No newline at end of file diff --git a/Assets/Resources/BillingMode.json.meta b/Assets/Resources/BillingMode.json.meta new file mode 100644 index 0000000..f5fc89d --- /dev/null +++ b/Assets/Resources/BillingMode.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 610fb0a7c290abd47a09e3591b58db00 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Choices/Steal.meta b/Assets/Resources/Fonts.meta similarity index 77% rename from Assets/Scripts/Model/Choices/Steal.meta rename to Assets/Resources/Fonts.meta index 4c5fc5a..1b2f197 100644 --- a/Assets/Scripts/Model/Choices/Steal.meta +++ b/Assets/Resources/Fonts.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ea92e0400d884f24da7a1fd20ab7d203 +guid: 0accb89c98af4554ab34b128e2c543ef folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Resources/Fonts/cyberdyne.ttf b/Assets/Resources/Fonts/cyberdyne.ttf new file mode 100644 index 0000000..d6a4d31 Binary files /dev/null and b/Assets/Resources/Fonts/cyberdyne.ttf differ diff --git a/Assets/Resources/Fonts/cyberdyne.ttf.meta b/Assets/Resources/Fonts/cyberdyne.ttf.meta new file mode 100644 index 0000000..4e1951d --- /dev/null +++ b/Assets/Resources/Fonts/cyberdyne.ttf.meta @@ -0,0 +1,22 @@ +fileFormatVersion: 2 +guid: 03ace493c60fdb54e8f2c1e37acd83b3 +TrueTypeFontImporter: + externalObjects: {} + serializedVersion: 4 + fontSize: 16 + forceTextureCase: -2 + characterSpacing: 0 + characterPadding: 1 + includeFontData: 1 + fontName: Cyberdyne + fontNames: + - Cyberdyne + fallbackFontReferences: [] + customCharacters: + fontRenderingMode: 0 + ascentCalculationMode: 1 + useLegacyBoundsCalculation: 0 + shouldRoundAdvanceValue: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Images/UI/card-draw.svg.meta b/Assets/Resources/Images/UI/card-draw.svg.meta index 0fec455..a33f950 100644 --- a/Assets/Resources/Images/UI/card-draw.svg.meta +++ b/Assets/Resources/Images/UI/card-draw.svg.meta @@ -51,5 +51,4 @@ ScriptedImporter: width: 0 height: 0 spriteID: 84497b0638ec4b14eb787179ac57c776 - internalID: 0 PhysicsOutlines: [] diff --git a/Assets/Resources/Images/UI/click.svg.meta b/Assets/Resources/Images/UI/click.svg.meta index 75903bd..c7f10ce 100644 --- a/Assets/Resources/Images/UI/click.svg.meta +++ b/Assets/Resources/Images/UI/click.svg.meta @@ -51,5 +51,4 @@ ScriptedImporter: width: 0 height: 0 spriteID: f35bc4ed14fb4da4888826dac5dbdaeb - internalID: 0 PhysicsOutlines: [] diff --git a/Assets/Resources/Images/UI/credit.svg.meta b/Assets/Resources/Images/UI/credit.svg.meta index 823fe10..6cf0f32 100644 --- a/Assets/Resources/Images/UI/credit.svg.meta +++ b/Assets/Resources/Images/UI/credit.svg.meta @@ -51,5 +51,4 @@ ScriptedImporter: width: 44 height: 81 spriteID: 76191d125811dc846b2a1e7a30110ab0 - internalID: 0 PhysicsOutlines: [] diff --git a/Assets/Resources/Images/UI/hourglass.svg.meta b/Assets/Resources/Images/UI/hourglass.svg.meta index c54315e..1d5dedd 100644 --- a/Assets/Resources/Images/UI/hourglass.svg.meta +++ b/Assets/Resources/Images/UI/hourglass.svg.meta @@ -51,5 +51,4 @@ ScriptedImporter: width: 0 height: 0 spriteID: 963c814defcf3ac478eb12c92801227d - internalID: 0 PhysicsOutlines: [] diff --git a/Assets/Resources/Images/UI/server-rack.svg.meta b/Assets/Resources/Images/UI/server-rack.svg.meta index de5c565..b43cce3 100644 --- a/Assets/Resources/Images/UI/server-rack.svg.meta +++ b/Assets/Resources/Images/UI/server-rack.svg.meta @@ -51,5 +51,4 @@ ScriptedImporter: width: 0 height: 0 spriteID: bbd6eda2b6a62b24eb3435fcc1119ac5 - internalID: 0 PhysicsOutlines: [] diff --git a/Assets/Resources/Images/UI/symbols.png.meta b/Assets/Resources/Images/UI/symbols.png.meta index ab3ed38..f1de57e 100644 --- a/Assets/Resources/Images/UI/symbols.png.meta +++ b/Assets/Resources/Images/UI/symbols.png.meta @@ -32,6 +32,8 @@ TextureImporter: isReadable: 0 streamingMipmaps: 0 streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -40,9 +42,9 @@ TextureImporter: maxTextureSize: 2048 textureSettings: serializedVersion: 2 - filterMode: -1 - aniso: -1 - mipBias: -100 + filterMode: 1 + aniso: 1 + mipBias: 0 wrapU: 1 wrapV: 1 wrapW: 1 @@ -63,9 +65,12 @@ TextureImporter: textureType: 8 textureShape: 1 singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 maxTextureSizeSet: 0 compressionQualitySet: 0 textureFormatSet: 0 + ignorePngGamma: 0 applyGammaDecoding: 1 platformSettings: - serializedVersion: 3 @@ -168,6 +173,10 @@ TextureImporter: edges: [] weights: [] secondaryTextures: [] + nameFileIdTable: + symbols_credit: 21300002 + symbols_click: 21300004 + symbols_trash: 21300000 spritePackingTag: pSDRemoveMatte: 0 pSDShowRemoveMatteOption: 0 diff --git a/Assets/Scenes/Runner Game.unity b/Assets/Scenes/Runner Game.unity index 240933e..2d754bf 100644 --- a/Assets/Scenes/Runner Game.unity +++ b/Assets/Scenes/Runner Game.unity @@ -206,7 +206,7 @@ MonoBehaviour: m_outlineColor: serializedVersion: 2 rgba: 4278190080 - m_fontSize: 72 + m_fontSize: 50.35 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 @@ -1236,8 +1236,9 @@ RectTransform: - {fileID: 392453616} - {fileID: 364526017} - {fileID: 1730285211} + - {fileID: 1600730671} m_Father: {fileID: 0} - m_RootOrder: 1 + m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -1385,8 +1386,83 @@ Transform: m_Children: - {fileID: 115784155} m_Father: {fileID: 0} - m_RootOrder: 3 + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &798008800 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 798008803} + - component: {fileID: 798008802} + - component: {fileID: 798008801} + m_Layer: 5 + m_Name: Credit(Clone) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &798008801 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 798008800} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 3286163911610860551, guid: 14ded5a7c71921141b0312119ba4dce2, + type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &798008802 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 798008800} + m_CullTransparentMesh: 1 +--- !u!224 &798008803 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 798008800} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -16, y: 42.666668} + m_SizeDelta: {x: 32, y: 64} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &836102266 GameObject: m_ObjectHideFlags: 0 @@ -1755,7 +1831,7 @@ MonoBehaviour: m_outlineColor: serializedVersion: 2 rgba: 4278190080 - m_fontSize: 72 + m_fontSize: 50.35 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 @@ -2505,6 +2581,41 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &1600730670 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1600730671} + m_Layer: 5 + m_Name: Game bracket + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1600730671 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1600730670} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 711624506} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.25} + m_AnchorMax: {x: 1, y: 0.75} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &1622406199 GameObject: m_ObjectHideFlags: 0 @@ -2634,7 +2745,7 @@ MonoBehaviour: m_outlineColor: serializedVersion: 2 rgba: 4278190080 - m_fontSize: 72 + m_fontSize: 50.35 m_fontSizeBase: 36 m_fontWeight: 400 m_enableAutoSizing: 1 @@ -2736,7 +2847,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 2 + m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &1685858174 MonoBehaviour: diff --git a/Assets/Scripts/AsyncAction.cs b/Assets/Scripts/AsyncAction.cs index 3d9a219..ab93550 100644 --- a/Assets/Scripts/AsyncAction.cs +++ b/Assets/Scripts/AsyncAction.cs @@ -1,6 +1,5 @@ using System.Threading.Tasks; +public delegate Task AsyncAction(); public delegate Task AsyncAction(T1 arg1); public delegate Task AsyncAction(T1 arg1, T2 arg2); - - diff --git a/Assets/Scripts/Config/GameConfig.cs b/Assets/Scripts/Config/GameConfig.cs index 18447db..4562773 100644 --- a/Assets/Scripts/Config/GameConfig.cs +++ b/Assets/Scripts/Config/GameConfig.cs @@ -5,7 +5,9 @@ using UnityEngine; using view; using view.gui; +using view.gui.brackets; using view.log; +using static view.gui.GameObjectExtensions; public class GameConfig : MonoBehaviour { @@ -14,7 +16,7 @@ public class GameConfig : MonoBehaviour void Start() { - var board = GameObject.Find("/Board"); + var board = FindOrFail("/Board"); gameMenu = board.GetComponentInChildren(); gameMenu.Resume(); var perception = new RunnerPerception(); @@ -44,6 +46,7 @@ void Start() flowLog.Display(game); var corpView = new CorpViewConfig().Display(game, parts); new RunnerViewConfig().Display(game.runner, flowView, corpView, parts); + new RunnerGameBracket(FindOrFail("Game bracket"), game); game.Start(corpDeck, runnerDeck); } diff --git a/Assets/Scripts/Controller/InteractiveDiscard.cs b/Assets/Scripts/Controller/InteractiveDiscard.cs index d0495ef..4b3600e 100644 --- a/Assets/Scripts/Controller/InteractiveDiscard.cs +++ b/Assets/Scripts/Controller/InteractiveDiscard.cs @@ -27,7 +27,7 @@ public InteractiveDiscard(Card card, DropZone activation, Runner runner) async Task IInteractive.Interact() { - grip.Discard(card, heap); + await grip.Discard(card, heap); await Task.CompletedTask; } diff --git a/Assets/Scripts/Model/AI/CorpAi.cs b/Assets/Scripts/Model/AI/CorpAi.cs index 3f975aa..d4199dc 100644 --- a/Assets/Scripts/Model/AI/CorpAi.cs +++ b/Assets/Scripts/Model/AI/CorpAi.cs @@ -24,7 +24,7 @@ public class CorpAi : private Task Thinking() => Task.Delay(1700); private IList actions = new List(); private IList legalActions = new List(); - private IList paidAbilities = new List(); + private IList paidAbilities = new List(); private Random random; public CorpAi(Random random) @@ -45,10 +45,10 @@ void IPilot.Play(Game game) zones.hq.DiscardingOne += DiscardOne; } - async Task IPilot.TriggerFromSimultaneous(IList effects) + async Task IPilot.TriggerFromSimultaneous(IEnumerable abilities) { await Thinking(); - return effects.First(); + return abilities.First(); } private async Task TakeAction(ITurn turn) @@ -59,9 +59,9 @@ private async Task TakeAction(ITurn turn) await randomLegalAction.Trigger(); } - private void DiscardOne() + private async Task DiscardOne() { - zones.hq.Discard(zones.hq.Random(), zones.archives); + await zones.hq.Discard(zones.hq.Random(), zones.archives); } async private Task RezSomething(RezWindow window, List rezzables) diff --git a/Assets/Scripts/Model/Abilities.meta b/Assets/Scripts/Model/Abilities.meta new file mode 100644 index 0000000..c156e97 --- /dev/null +++ b/Assets/Scripts/Model/Abilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74fba7e4988564546b3d066ad366dab7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Abilities/Abilities.cs b/Assets/Scripts/Model/Abilities/Abilities.cs new file mode 100644 index 0000000..ac3b359 --- /dev/null +++ b/Assets/Scripts/Model/Abilities/Abilities.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using model.timing; + +namespace model.abilities { + public class Abilities { + private IList conditionals = new List(); + + public void AddConditional(ConditionalAbility conditional) { + conditionals.Add(conditional); + } + + async public Task CheckReactions() { + await CheckReactions(GatherPending()); + } + + private IList GatherPending() { + return conditionals + .Where(it => it.Active) + .SelectMany(it => it.InstantiatePerOccurrence()) + .ToList(); + } + + async private Task CheckReactions(IList pending) { + if (pending.Count > 0) { + var reactionWindow = new ReactionWindow(pending); + await reactionWindow.Open(); + var newPending = GatherPending(); // CR: 9.6.4.a + await CheckReactions(newPending); + } + } + } +} diff --git a/Assets/Scripts/Model/Costs/Active.cs.meta b/Assets/Scripts/Model/Abilities/Abilities.cs.meta similarity index 83% rename from Assets/Scripts/Model/Costs/Active.cs.meta rename to Assets/Scripts/Model/Abilities/Abilities.cs.meta index c52ea18..810a7cb 100644 --- a/Assets/Scripts/Model/Costs/Active.cs.meta +++ b/Assets/Scripts/Model/Abilities/Abilities.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 47968cd203e16c14fa56c89bf1848bca +guid: 95b28d83b7c600742893b653136ebcee MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Abilities/ConditionalAbility.cs b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs new file mode 100644 index 0000000..7d6f1d1 --- /dev/null +++ b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using model.play; + +namespace model.abilities { + public class ConditionalAbility : IAbility { + + public bool Active { get; } + public ISource Source { get; } + + private readonly TriggerCondition condition; + private readonly IInstruction instruction; + + public ConditionalAbility(TriggerCondition condition, IInstruction instruction) { + this.condition = condition; + this.instruction = instruction; + } + + internal List InstantiatePerOccurrence() { + if (Active) { + return condition + .occurrences + .Select(it => new Instance(this)) + .ToList(); + } else { + throw new System.Exception("Tried to instantiate an inactive ability " + this); + } + } + + async public Task Resolve() { + await instruction.Resolve(); + } + + public class Instance { + public bool Pending { get; set; } + public bool Imminent { get; set; } + public bool Resolving { get; set; } + private readonly ConditionalAbility conditionalAbility; + + public Instance(ConditionalAbility conditionalAbility) { + this.conditionalAbility = conditionalAbility; + } + + public async Task Trigger() { + if (Pending) { + Pending = false; + Imminent = true; + // TODO prevent window I guess? + Resolving = true; + Imminent = false; + await conditionalAbility.Resolve(); + Resolving = false; + } else { + throw new System.Exception("Tried to trigger a non-pending instance " + this); + } + } + } + } + + public class TriggerCondition { + internal IList occurrences = new List(); + + public interface IOccurrence { + + } + } + + public interface IInstruction { + Task Resolve(); + } +} diff --git a/Assets/Scripts/View/GUI/TimeCross/PastTrack.cs.meta b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs.meta similarity index 83% rename from Assets/Scripts/View/GUI/TimeCross/PastTrack.cs.meta rename to Assets/Scripts/Model/Abilities/ConditionalAbility.cs.meta index 06675ed..5f3969c 100644 --- a/Assets/Scripts/View/GUI/TimeCross/PastTrack.cs.meta +++ b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6e0fdf67a5e8a4e408a2d472410a3b3f +guid: b9d99febe6ed1ce4e9f1b310bbc29779 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Abilities/IAbility.cs b/Assets/Scripts/Model/Abilities/IAbility.cs new file mode 100644 index 0000000..f462a33 --- /dev/null +++ b/Assets/Scripts/Model/Abilities/IAbility.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using model.play; + +namespace model.abilities { + public interface IAbility { + + Task Resolve(); + ISource Source { get; } + bool Active { get; } + } +} diff --git a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs.meta b/Assets/Scripts/Model/Abilities/IAbility.cs.meta similarity index 83% rename from Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs.meta rename to Assets/Scripts/Model/Abilities/IAbility.cs.meta index a50dcc8..ca7dffd 100644 --- a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs.meta +++ b/Assets/Scripts/Model/Abilities/IAbility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 952b8f4f02ddd254bbd2c3bdf7328a7f +guid: 4099a389143dfd2448ceb69587d26b68 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index 2ec2e72..e107ad7 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -1,113 +1,147 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using model.abilities; +using model.cards.text; using model.choices.trash; using model.play; using model.player; using model.steal; +using model.timing; using model.zones; -namespace model.cards -{ - public abstract class Card - { - public event NotifyActivity Toggled = delegate { }; +namespace model.cards { + public abstract class Card : ISource { public event NotifyMoved Moved = delegate { }; public event NotifyInfo ChangedInfo = delegate { }; + public event Action ChangedActivation = delegate { }; public abstract string Name { get; } public abstract IType Type { get; } public Zone Zone { get; private set; } public abstract ICost PlayCost { get; } - public abstract IEffect Activation { get; } + public virtual IEffect Activation { get; } public abstract Faction Faction { get; } public abstract int InfluenceCost { get; } public abstract string FaceupArt { get; } public bool Faceup { get; private set; } = false; public Information Information { get; private set; } = Information.HIDDEN_FROM_ALL; public bool Active { get; private set; } = false; + public IList Used { get; private set; } = new List(); + public IPilot Controller { get; private set; } + private bool Installed = false; // CR: 8.1.1 + private bool Rezzed => Installed && Faceup && Type.Rezzable; // CR: 8.1.2 + private bool Unrezzed => Installed && !Faceup && !Type.Playable && !Type.Runner; // CR: 8.1.2 public virtual IList StealOptions() => Type.DefaultStealing(this); public virtual IList TrashOptions() => new List(); protected Game game; + protected readonly YourText your; + protected readonly YouText you; + protected readonly SelfText self; - public Card(Game game) - { + public Card(Game game) { this.game = game; + this.Controller = game.Pilot(Faction.Side); this.Zone = new Zone("Outside of the game", false); this.Zone.Add(this); } - async public Task Activate() - { + protected ThenText When(TriggerCondition condition) { + return new ThenText(condition); + } + + async public Task BecomeActive() { + await Activate(); Active = true; - await Activation.Resolve(); // TODO either keep this or `public Activation`, because it's risking double resolution - Toggled(this, Active); + ChangedActivation(this); + } + + protected async Task Activate() { + // TODO remove, replace by text/syntax parsing in constructors + await Task.CompletedTask; } - public void Deactivate() - { + async protected virtual Task BecomeInactive() { + await Deactivate(); Active = false; - Toggled(this, Active); + ChangedActivation(this); } - public void MoveTo(Zone target) - { + protected virtual Task Deactivate() { + return Task.CompletedTask; + } + + async public Task MoveTo(Zone target) { var source = Zone; - if (source == target) - { + if (source == target) { throw new System.Exception("Tried to move " + Name + " from " + source.Name + " to " + target.Name); } source.Remove(this); target.Add(this); Zone = target; + if (!Zone.InPlayArea) { + Installed = false; + } + await UpdateActivity(); Moved(this, source, target); } - public void Installed() - { - if (Type.Rezzable) + async private Task UpdateActivity() { + if (Zone.InPlayArea && Faceup) // CR: 1.8.3.a { + if (!Active) { + Active = true; + await Activate(); + } + } else { + if (Active) { + Active = false; + await Deactivate(); + } + } + } + + async public Task SetInstalled() { + Installed = true; + await UpdateActivity(); + if (Type.Rezzable) { game.corp.Rezzing.Track(this); } } - internal void FlipPreInstall() - { - switch (Faction.Side) - { - case Side.CORP: FlipFaceDown(); break; - case Side.RUNNER: FlipFaceUp(); break; + internal void FlipPreInstall() { + if (Type.Corp) { + FlipFaceDown(); + } + if (Type.Runner) { + FlipFaceUp(); } } - public IList FindInstallDestinations() - { + public IList FindInstallDestinations() { return Type.FindInstallDestinations(); } - public void FlipFaceUp() - { + public void FlipFaceUp() { Faceup = true; UpdateInfo(Information.OPEN); } - private void FlipFaceDown() - { + private void FlipFaceDown() { Faceup = false; - switch (Faction.Side) - { - case Side.CORP: UpdateInfo(Information.HIDDEN_FROM_RUNNER); break; - case Side.RUNNER: UpdateInfo(Information.HIDDEN_FROM_CORP); break; + if (Type.Corp) { + UpdateInfo(Information.HIDDEN_FROM_RUNNER); + } + if (Type.Runner) { + UpdateInfo(Information.HIDDEN_FROM_CORP); } } - public void UpdateInfo(Information information) - { + public void UpdateInfo(Information information) { Information = information; ChangedInfo(this, Information); } - public override string ToString() - { + public override string ToString() { return Name + " [" + GetHashCode() + "]"; } } diff --git a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs index bf98898..b373dbd 100644 --- a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs +++ b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs @@ -6,87 +6,77 @@ using model.choices.trash; using model.costs; using model.play; +using model.timing; using model.zones; -namespace model.cards.corp -{ - public class AdvancedAssemblyLines : Card - { - public AdvancedAssemblyLines(Game game) : base(game) { } +namespace model.cards.corp { + public class AdvancedAssemblyLines : Card { + private Ability pop; override public string FaceupArt => "advanced-assembly-lines"; override public string Name => "Advanced Assembly Lines"; override public Faction Faction => Factions.HAAS_BIOROID; override public int InfluenceCost => 2; override public ICost PlayCost => game.corp.credits.PayingForPlaying(this, 1); - override public IEffect Activation => new AdvancedAssemblyLinesActivation(this, game.corp); override public IType Type => new Asset(game); override public IList TrashOptions() => new List { new Leave(), new PayToTrash(1, this, game) }; - private class AdvancedAssemblyLinesActivation : IEffect - { - public bool Impactful => true; - public event Action ChangedImpact = delegate { }; - private readonly Card aal; - private readonly Corp corp; - IEnumerable IEffect.Graphics => new string[] { }; + public AdvancedAssemblyLines(Game game) : base(game) { + When(self.Rezzed()).Then(Gain(3).Credits); + OutsideOfRun(Pay(self.Trash()).To(Install(Non(Agenda())).From().Hq())); // SYNTAX PAID ABILITY CR: 9.5 + // OLD SEMANTICS: + pop = new Ability( + cost: new Trash(this, game.corp.zones.archives.Zone), + effect: new AdvancedAssemblyLinesInstall(game.corp), + source: this, + mandatory: false + ); + // NEW SEMANTICS: + new PaidAbility( + restrictions: OutsideOfRun(), + triggerCost: this.SelfTrash(), + effect: new AdvancedAssemblyLinesInstall(game.corp) + ); + } - public AdvancedAssemblyLinesActivation(Card aal, Corp corp) - { - this.aal = aal; - this.corp = corp; - } + async protected override Task Activate() { + await game.corp.credits.Gaining(3).Resolve(); + game.Timing.PaidWindowDefined += DeferPop; + } - async Task IEffect.Resolve() - { - await corp.credits.Gaining(3).Resolve(); - var paidWindow = corp.paidWindow; - var archives = corp.zones.archives.Zone; - var aalInstall = new AdvancedAssemblyLinesInstall(corp); - var pop = new Ability( - cost: new Conjunction(paidWindow.Permission(), new Trash(aal, archives), new Active(aal)), - effect: aalInstall - ).BelongingTo(aal); - paidWindow.Add(pop); - aal.Moved += (card, source, target) => - { - paidWindow.Remove(pop); - aalInstall.Dispose(); - }; - } + protected override Task Deactivate() { + game.Timing.PaidWindowDefined -= DeferPop; + return Task.CompletedTask; + } + + private void DeferPop(PaidWindow paidWindow) { + paidWindow.GiveOption(game.corp.pilot, pop); } - private class AdvancedAssemblyLinesInstall : IEffect - { + private class AdvancedAssemblyLinesInstall : IEffect { public bool Impactful => Installables().Count > 0; public event Action ChangedImpact = delegate { }; IEnumerable IEffect.Graphics => new string[] { }; private Corp corp; - public AdvancedAssemblyLinesInstall(Corp corp) - { + public AdvancedAssemblyLinesInstall(Corp corp) { this.corp = corp; corp.zones.hq.Zone.Changed += UpdateInstallables; } - private IList Installables() => corp.zones.hq.Zone.Cards.Where(card => (card.Type.Installable && !(card.Type is Agenda))).ToList(); - - async Task IEffect.Resolve() - { - var installable = await corp.pilot.ChooseACard().Declare("Which card to install?", Installables()); - await corp.Installing.InstallingCard(installable).Resolve(); - } - - private void UpdateInstallables(Zone hqZone) - { + private void UpdateInstallables(Zone hqZone) { ChangedImpact(this, Impactful); } - internal void Dispose() - { - corp.zones.hq.Zone.Changed -= UpdateInstallables; + private IList Installables() => corp.zones.hq.Zone.Cards + .Where(card => (card.Type.Installable && !(card.Type is Agenda))) + .ToList(); + + async Task IEffect.Resolve() { + var installable = await corp.pilot.ChooseACard().Declare("Which card to install?", Installables()); + await corp.Installing.InstallingCard(installable).Resolve(); } } } diff --git a/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs b/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs index 00f3c79..c6fed2a 100644 --- a/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs +++ b/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs @@ -20,23 +20,33 @@ public HaarpsichordStudios(Game game) : base(game) { } private class HaarpsichordEffect : IEffect { + private HaarpsichordMemory memory; + private HaarpsichordModifier mod; + private Game game; public bool Impactful => true; public event Action ChangedImpact = delegate { }; IEnumerable IEffect.Graphics => new string[] { }; - private Game game; - - public HaarpsichordEffect(Game game) => this.game = game; + public HaarpsichordEffect(Game game) + { + this.game = game; + memory = new HaarpsichordMemory(); + mod = new HaarpsichordModifier(memory); + game.corp.turn.Opened += memory.Reset; + game.runner.turn.Opened += memory.Reset; + game.runner.Stealing.ModifyStealing(mod); + } async Task IEffect.Resolve() { - var memory = new HaarpsichordMemory(); - game.corp.turn.Started += memory.Reset; - game.runner.turn.Started += memory.Reset; - var mod = new HaarpsichordModifier(memory); - game.runner.Stealing.ModifyStealing(mod); + mod.Enabled = true; await Task.CompletedTask; } + + void IEffect.Disable() + { + mod.Enabled = false; + } } private class HaarpsichordMemory @@ -53,6 +63,7 @@ async public Task Reset(ITurn turn) private class HaarpsichordModifier : IStealModifier { private HaarpsichordMemory memory; + public bool Enabled { get; set; } = true; public HaarpsichordModifier(HaarpsichordMemory memory) { @@ -61,7 +72,14 @@ public HaarpsichordModifier(HaarpsichordMemory memory) IStealOption IStealModifier.Modify(IStealOption option) { - return new StealingWhileHaarpsichordWatches(memory, option); + if (Enabled) + { + return new StealingWhileHaarpsichordWatches(memory, option); + } + else + { + return option; + } } } diff --git a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index cfbbd18..75a7d0b 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -1,40 +1,57 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; +using model.abilities; using model.cards.types; using model.choices.trash; +using model.costs; +using model.play; +using model.player; +using model.timing.corp; + +namespace model.cards.corp { + + public class PadCampaign : Card { + -namespace model.cards.corp -{ - public class PadCampaign : Card - { - public PadCampaign(Game game) : base(game) { } override public string FaceupArt => "pad-campaign"; override public string Name => "PAD Campaign"; override public Faction Faction => Factions.SHADOW; override public int InfluenceCost => 0; override public ICost PlayCost => game.corp.credits.PayingForPlaying(this, 2); - override public IEffect Activation => new PadCampaignActivation(game.corp); override public IType Type => new Asset(game); override public IList TrashOptions() => new List { new Leave(), new PayToTrash(4, this, game) }; - private class PadCampaignActivation : IEffect - { - public bool Impactful => true; - public event Action ChangedImpact = delegate { }; - IEnumerable IEffect.Graphics => new string[] { }; - private Corp corp; + public PadCampaign(Game game) : base(game) { + ///// SYNTAX ///// + + // PAD Campaign + When(your.Turn.Begins) + .Then(you.Gain(1).Credits); + + // I've Had Worse + When(self.Played()) + .Then(you.Draw(3).Cards); + When(self.Trashed().ByTaking("NET", "MEAT")) + .Then(you.Draw(3).Cards); + + // Hyoubu Precog Manifold + // self.Play.OnlyIf(game.Abilities.NoLockdown()); + // When(self.Played()) + // .Then(self.DeferTrashUntil(your.NextTurn().Begins)); + // var precogChoice = When(self.Played()) + // .Then(Choose().Server()); + // When(game.Runner.Runs.Successfully().On(precogChoice.Server)) + // .Then(game.Corp.Psi().Miss(game.Run.End())); - public PadCampaignActivation(Corp corp) => this.corp = corp; + // //// SEMANTICS //// CR: 9.3.7 + // new StaticAbility(restrictions: null, declarations: null, conditions: null); + // new ConditionalAbility(condition: new TriggerCondition(this.Trashed().ByTaking(NET, MEAT))); - async Task IEffect.Resolve() - { - corp.turn.WhenBegins(corp.credits.Gaining(1)); - await Task.CompletedTask; - } + //// TRY TO COMPILE //// + game.Abilities.AddConditional(new ConditionalAbility(condition: your.Turn.Begins, instruction: you.Gain(1).Credits)); } } } diff --git a/Assets/Scripts/Model/Cards/Corp/TheShadow.cs b/Assets/Scripts/Model/Cards/Corp/TheShadow.cs index 86ac6ca..8053593 100644 --- a/Assets/Scripts/Model/Cards/Corp/TheShadow.cs +++ b/Assets/Scripts/Model/Cards/Corp/TheShadow.cs @@ -11,6 +11,6 @@ public TheShadow(Game game) : base(game) { } override public int InfluenceCost { get { throw new System.Exception("Identities don't have an influence cost"); } } override public ICost PlayCost { get { throw new System.Exception("Identities don't have a play cost"); } } override public IEffect Activation => new effects.Nothing(); - override public IType Type => new Identity(); + override public IType Type => new CorpIdentity(); } } diff --git a/Assets/Scripts/Model/Cards/IType.cs b/Assets/Scripts/Model/Cards/IType.cs index 50355a5..e18c801 100644 --- a/Assets/Scripts/Model/Cards/IType.cs +++ b/Assets/Scripts/Model/Cards/IType.cs @@ -6,6 +6,8 @@ namespace model.cards { public interface IType { + bool Corp { get; } + bool Runner { get; } bool Playable { get; } bool Installable { get; } bool Rezzable { get; } diff --git a/Assets/Scripts/Model/Cards/Runner/Masque.cs b/Assets/Scripts/Model/Cards/Runner/Masque.cs index a65f6ee..e89870d 100644 --- a/Assets/Scripts/Model/Cards/Runner/Masque.cs +++ b/Assets/Scripts/Model/Cards/Runner/Masque.cs @@ -11,6 +11,6 @@ public TheMasque(Game game) : base(game) { } override public int InfluenceCost { get { throw new System.Exception("Identities don't have an influence cost"); } } override public ICost PlayCost { get { throw new System.Exception("Identities don't have a play cost"); } } override public IEffect Activation => new effects.Nothing(); - override public IType Type => new Identity(); + override public IType Type => new RunnerIdentity(); } } diff --git a/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs b/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs index 94fdb98..ac436cf 100644 --- a/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs +++ b/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs @@ -4,6 +4,7 @@ using model.cards.types; using model.costs; using model.play; +using model.timing; using model.zones; namespace model.cards.runner @@ -16,42 +17,42 @@ public SportsHopper(Game game) : base(game) { } override public Faction Faction => Factions.MASQUE; override public int InfluenceCost => 0; override public ICost PlayCost => game.runner.credits.PayingForPlaying(this, 3); - override public IEffect Activation => new SportsHopperActivation(this, game.runner); + override public IEffect Activation => new SportsHopperActivation(this, game); override public IType Type => new Hardware(game); private class SportsHopperActivation : IEffect { private Card hopper; - private Runner runner; + private Game game; private CardAbility pop; public bool Impactful => true; public event Action ChangedImpact = delegate { }; IEnumerable IEffect.Graphics => new string[] { }; - public SportsHopperActivation(Card hopper, Runner runner) + public SportsHopperActivation(Card hopper, Game game) { this.hopper = hopper; - this.runner = runner; + this.game = game; + var zones = game.runner.zones; + pop = new Ability(new Trash(hopper, zones.heap.zone), zones.Drawing(3), hopper).BelongingTo(hopper); } async Task IEffect.Resolve() { - var paidWindow = runner.paidWindow; - var heap = runner.zones.heap.zone; - if (pop == null) - { - pop = new Ability(new Conjunction(paidWindow.Permission(), new Trash(hopper, heap)), runner.zones.Drawing(3)).BelongingTo(hopper); - } - paidWindow.Add(pop); - runner.zones.rig.zone.Removed += CheckIfUninstalled; + game.Timing.PaidWindowDefined += Register; await Task.CompletedTask; } - private void CheckIfUninstalled(Zone zone, Card card) + private void Register(PaidWindow paidWindow) + { + paidWindow.PriorityGiven += Register; + } + + private void Register(Priority priority) { - if (card == this.hopper) + if (priority.Pilot == game.runner.pilot) { - runner.paidWindow.Remove(pop); + priority.Add(pop); } } } diff --git a/Assets/Scripts/Model/Cards/Runner/SpyCamera.cs b/Assets/Scripts/Model/Cards/Runner/SpyCamera.cs index 6cfa1f5..c395b10 100644 --- a/Assets/Scripts/Model/Cards/Runner/SpyCamera.cs +++ b/Assets/Scripts/Model/Cards/Runner/SpyCamera.cs @@ -1,4 +1,5 @@ -using model.cards.types; +using System.Threading.Tasks; +using model.cards.types; namespace model.cards.runner { @@ -10,7 +11,10 @@ public SpyCamera(Game game) : base(game) { } override public Faction Faction { get { return Factions.CRIMINAL; } } override public int InfluenceCost { get { return 1; } } override public ICost PlayCost => game.runner.credits.PayingForPlaying(this, 0); - override public IEffect Activation => new effects.Nothing(); override public IType Type => new Hardware(game); + + protected override Task Activate() { + return Task.FromResult("Add the abilities"); + } } } diff --git a/Assets/Scripts/Model/Cards/Runner/SureGamble.cs b/Assets/Scripts/Model/Cards/Runner/SureGamble.cs index fdbd66b..973d4c5 100644 --- a/Assets/Scripts/Model/Cards/Runner/SureGamble.cs +++ b/Assets/Scripts/Model/Cards/Runner/SureGamble.cs @@ -1,4 +1,5 @@ -using model.cards.types; +using System.Threading.Tasks; +using model.cards.types; namespace model.cards.runner { @@ -10,7 +11,10 @@ public SureGamble(Game game) : base(game) { } override public Faction Faction { get { return Factions.MASQUE; } } override public int InfluenceCost { get { return 0; } } override public ICost PlayCost => game.runner.credits.PayingForPlaying(this, 5); - override public IEffect Activation => game.runner.credits.Gaining(9); override public IType Type { get { return new Event(); } } + + async protected override Task Activate() { + await game.runner.credits.Gaining(9).Resolve(); + } } } diff --git a/Assets/Scripts/Model/Cards/Runner/Wyldside.cs b/Assets/Scripts/Model/Cards/Runner/Wyldside.cs index ea576c7..87926c9 100644 --- a/Assets/Scripts/Model/Cards/Runner/Wyldside.cs +++ b/Assets/Scripts/Model/Cards/Runner/Wyldside.cs @@ -1,60 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using model.cards.types; +using model.costs; using model.effects; +using model.play; +using model.timing.runner; -namespace model.cards.runner -{ - public class Wyldside : Card - { - public Wyldside(Game game) : base(game) { } +namespace model.cards.runner { + public class Wyldside : Card { + private Ability party; override public string FaceupArt => "wyldside"; override public string Name => "Wyldside"; override public Faction Faction => Factions.ANARCH; override public int InfluenceCost => 3; override public ICost PlayCost => game.runner.credits.PayingForPlaying(this, 3); - override public IEffect Activation => new WyldsideActivation(game.runner); override public IType Type => new Resource(game); - private class WyldsideActivation : IEffect - { - private Runner runner; - private readonly WyldsideTrigger trigger; - public bool Impactful => true; - public event Action ChangedImpact = delegate { }; - IEnumerable IEffect.Graphics => new string[] { }; - - public WyldsideActivation(Runner runner) - { - this.runner = runner; - trigger = new WyldsideTrigger(runner); - } - - async Task IEffect.Resolve() - { - runner.turn.WhenBegins(trigger); - await Task.CompletedTask; - } - } - - private class WyldsideTrigger : IEffect - { - private IEffect sequence; - public bool Impactful => sequence.Impactful; - public event Action ChangedImpact = delegate { }; - IEnumerable IEffect.Graphics => new[] { "wyldside" }; - - public WyldsideTrigger(Runner runner) - { - sequence = new Sequence(runner.zones.Drawing(2), runner.clicks.Losing(1)); - sequence.ChangedImpact += ChangedImpact; - } - - async Task IEffect.Resolve() - { - await sequence.Resolve(); - } + public Wyldside(Game game) : base(game) { + party = new Ability( + cost: new Free(), + new Sequence(game.runner.zones.Drawing(2), game.runner.clicks.Losing(1)), + source: this, + mandatory: true + ); } } } diff --git a/Assets/Scripts/Model/Cards/Text.meta b/Assets/Scripts/Model/Cards/Text.meta new file mode 100644 index 0000000..39421d3 --- /dev/null +++ b/Assets/Scripts/Model/Cards/Text.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1c1c87569e27fc34681455eec0122796 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Cards/Text/Text.cs b/Assets/Scripts/Model/Cards/Text/Text.cs new file mode 100644 index 0000000..b5db4eb --- /dev/null +++ b/Assets/Scripts/Model/Cards/Text/Text.cs @@ -0,0 +1,79 @@ +using model.abilities; + +namespace model.cards.text { + + public class YourText { + public TurnText Turn { get; private set; } + public TurnText NextTurn() { + return new TurnText(); + } + } + + + public class YouText { + public GainingText Gain(int number) { + return new GainingText(); + } + + public DrawingText Draw(int number) { + return new DrawingText(); + } + } + + public class SelfText { + public PlayedText Played() { + return new PlayedText(); + } + + public TrashedText Trashed() { + return new TrashedText(); + } + } + + public class PlayedText : TriggerCondition { + + } + + public class TrashedText : TriggerCondition { + + internal object[] damageTypes; + + public TrashedText ByTaking(params object[] damageTypes) { + return new TrashedText { + damageTypes = damageTypes + }; + } + } + + public class GainingText { + public CreditsText Credits { get; private set; } + } + + public class DrawingText : IInstruction { + public DrawingText Cards { get; private set; } + + internal DrawingText() { + Cards = this; + } + } + + public class CreditsText : IInstruction { + } + + public class TurnText { + public TriggerCondition Begins { get; private set; } + } + + public class ThenText { + + private readonly TriggerCondition condition; + + public ThenText(TriggerCondition condition) { + this.condition = condition; + } + + public ConditionalAbility Then(IInstruction instruction) { + return new ConditionalAbility(condition, instruction); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Model/Play/CardAbility.cs.meta b/Assets/Scripts/Model/Cards/Text/Text.cs.meta similarity index 83% rename from Assets/Scripts/Model/Play/CardAbility.cs.meta rename to Assets/Scripts/Model/Cards/Text/Text.cs.meta index ec755e0..2bdd2b4 100644 --- a/Assets/Scripts/Model/Play/CardAbility.cs.meta +++ b/Assets/Scripts/Model/Cards/Text/Text.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2932a60f8c7ceb840b536627c08c33a4 +guid: eb4d5afcd781e4641bf81e136b0e97b9 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Cards/Types/Agenda.cs b/Assets/Scripts/Model/Cards/Types/Agenda.cs index 8bf0f2b..888d4fb 100644 --- a/Assets/Scripts/Model/Cards/Types/Agenda.cs +++ b/Assets/Scripts/Model/Cards/Types/Agenda.cs @@ -6,6 +6,8 @@ namespace model.cards.types { public class Agenda : IType { + bool IType.Corp => true; + bool IType.Runner => false; bool IType.Playable => false; bool IType.Installable => true; bool IType.Rezzable => false; diff --git a/Assets/Scripts/Model/Cards/Types/Asset.cs b/Assets/Scripts/Model/Cards/Types/Asset.cs index 08b077c..7759338 100644 --- a/Assets/Scripts/Model/Cards/Types/Asset.cs +++ b/Assets/Scripts/Model/Cards/Types/Asset.cs @@ -6,6 +6,8 @@ namespace model.cards.types { public class Asset : IType { + bool IType.Corp => true; + bool IType.Runner => false; bool IType.Playable => false; bool IType.Installable => true; bool IType.Rezzable => true; diff --git a/Assets/Scripts/Model/Cards/Types/Identity.cs b/Assets/Scripts/Model/Cards/Types/CorpIdentity.cs similarity index 80% rename from Assets/Scripts/Model/Cards/Types/Identity.cs rename to Assets/Scripts/Model/Cards/Types/CorpIdentity.cs index 68bbe09..77baf5a 100644 --- a/Assets/Scripts/Model/Cards/Types/Identity.cs +++ b/Assets/Scripts/Model/Cards/Types/CorpIdentity.cs @@ -4,8 +4,10 @@ namespace model.cards.types { - public class Identity : IType + public class CorpIdentity : IType { + bool IType.Corp => true; + bool IType.Runner => false; bool IType.Playable => false; bool IType.Installable => false; bool IType.Rezzable => false; diff --git a/Assets/Scripts/Model/Cards/Types/CorpIdentity.cs.meta b/Assets/Scripts/Model/Cards/Types/CorpIdentity.cs.meta new file mode 100644 index 0000000..4b5a5af --- /dev/null +++ b/Assets/Scripts/Model/Cards/Types/CorpIdentity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88f8f1f6a33ab1745a431b16237ddd06 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Cards/Types/Event.cs b/Assets/Scripts/Model/Cards/Types/Event.cs index 0120fad..0a96df5 100644 --- a/Assets/Scripts/Model/Cards/Types/Event.cs +++ b/Assets/Scripts/Model/Cards/Types/Event.cs @@ -6,6 +6,8 @@ namespace model.cards.types { public class Event : IType { + bool IType.Corp => false; + bool IType.Runner => true; bool IType.Playable => true; bool IType.Installable => false; bool IType.Rezzable => false; diff --git a/Assets/Scripts/Model/Cards/Types/Hardware.cs b/Assets/Scripts/Model/Cards/Types/Hardware.cs index eb62075..5f723b6 100644 --- a/Assets/Scripts/Model/Cards/Types/Hardware.cs +++ b/Assets/Scripts/Model/Cards/Types/Hardware.cs @@ -6,6 +6,8 @@ namespace model.cards.types { public class Hardware : IType { + bool IType.Corp => false; + bool IType.Runner => true; bool IType.Playable => false; bool IType.Installable => true; bool IType.Rezzable => false; diff --git a/Assets/Scripts/Model/Cards/Types/Identity.cs.meta b/Assets/Scripts/Model/Cards/Types/Identity.cs.meta deleted file mode 100644 index 0eb9eac..0000000 --- a/Assets/Scripts/Model/Cards/Types/Identity.cs.meta +++ /dev/null @@ -1,13 +0,0 @@ -fileFormatVersion: 2 -guid: 6d48ada7e08fdca4ebcec26a67a31000 -timeCreated: 1523796569 -licenseType: Free -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Model/Cards/Types/Operation.cs b/Assets/Scripts/Model/Cards/Types/Operation.cs index 7610a37..ec8ca49 100644 --- a/Assets/Scripts/Model/Cards/Types/Operation.cs +++ b/Assets/Scripts/Model/Cards/Types/Operation.cs @@ -6,6 +6,8 @@ namespace model.cards.types { public class Operation : IType { + bool IType.Corp => true; + bool IType.Runner => false; bool IType.Playable => true; bool IType.Installable => false; bool IType.Rezzable => false; diff --git a/Assets/Scripts/Model/Cards/Types/Program.cs b/Assets/Scripts/Model/Cards/Types/Program.cs index 597e280..df318c4 100644 --- a/Assets/Scripts/Model/Cards/Types/Program.cs +++ b/Assets/Scripts/Model/Cards/Types/Program.cs @@ -6,6 +6,8 @@ namespace model.cards.types { public class Program : IType { + bool IType.Corp => false; + bool IType.Runner => true; bool IType.Playable => false; bool IType.Installable => true; bool IType.Rezzable => false; diff --git a/Assets/Scripts/Model/Cards/Types/Resource.cs b/Assets/Scripts/Model/Cards/Types/Resource.cs index f6e2f87..abae348 100644 --- a/Assets/Scripts/Model/Cards/Types/Resource.cs +++ b/Assets/Scripts/Model/Cards/Types/Resource.cs @@ -6,6 +6,8 @@ namespace model.cards.types { public class Resource : IType { + bool IType.Corp => false; + bool IType.Runner => true; bool IType.Playable => false; bool IType.Installable => true; bool IType.Rezzable => false; diff --git a/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs b/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs new file mode 100644 index 0000000..4222644 --- /dev/null +++ b/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using model.steal; +using model.zones; + +namespace model.cards.types +{ + public class RunnerIdentity : IType + { + bool IType.Corp => false; + bool IType.Runner => true; + bool IType.Playable => false; + bool IType.Installable => false; + bool IType.Rezzable => false; + IList IType.FindInstallDestinations() => new List(); + IList IType.DefaultStealing(Card card) => new List(); + } +} diff --git a/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs.meta b/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs.meta new file mode 100644 index 0000000..9374e31 --- /dev/null +++ b/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e3aa9a3c8e244447b9aaf560a162f42 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Choices/Trash/PayToTrash.cs b/Assets/Scripts/Model/Choices/Trash/PayToTrash.cs index 8220311..c638917 100644 --- a/Assets/Scripts/Model/Choices/Trash/PayToTrash.cs +++ b/Assets/Scripts/Model/Choices/Trash/PayToTrash.cs @@ -22,7 +22,7 @@ public PayToTrash(int trashCost, Card card, Game game) async Task ITrashOption.Perform() { await cost.Pay(); - card.MoveTo(archives.Zone); + await card.MoveTo(archives.Zone); return true; } diff --git a/Assets/Scripts/Model/ClickPool.cs b/Assets/Scripts/Model/ClickPool.cs index ce55252..75932ef 100644 --- a/Assets/Scripts/Model/ClickPool.cs +++ b/Assets/Scripts/Model/ClickPool.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using model.player; namespace model { diff --git a/Assets/Scripts/Model/CollectionExtensions.cs b/Assets/Scripts/Model/CollectionExtensions.cs index 8cd970e..26b51de 100644 --- a/Assets/Scripts/Model/CollectionExtensions.cs +++ b/Assets/Scripts/Model/CollectionExtensions.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Threading.Tasks; namespace model { diff --git a/Assets/Scripts/Model/Corp.cs b/Assets/Scripts/Model/Corp.cs index 620f6b4..14f0af8 100644 --- a/Assets/Scripts/Model/Corp.cs +++ b/Assets/Scripts/Model/Corp.cs @@ -5,17 +5,14 @@ using model.player; using model.rez; using model.timing; -using model.timing.corp; using model.zones; using model.zones.corp; namespace model { - public class Corp + public class Corp : IPlayer { public readonly IPilot pilot; - public readonly CorpTurn turn; - public readonly PaidWindow paidWindow; public readonly zones.corp.Zones zones; public readonly ClickPool clicks; public readonly CreditPool credits; @@ -25,16 +22,12 @@ public class Corp public Corp( IPilot pilot, - CorpTurn turn, - PaidWindow paidWindow, Zone playArea, Shuffling shuffling, Random random ) { this.pilot = pilot; - this.turn = turn; - this.paidWindow = paidWindow; clicks = new ClickPool(3); credits = new CreditPool(); zones = new Zones(this, playArea, shuffling); @@ -45,14 +38,13 @@ Random random async public Task Start(Game game, Deck deck) { - zones.rd.AddDeck(deck); + await zones.rd.AddDeck(deck); var identity = deck.identity; zones.identity.Add(identity); identity.FlipFaceUp(); - await identity.Activate(); pilot.Play(game); credits.Gain(5); - zones.rd.Draw(5, zones.hq); + await zones.rd.Draw(5, zones.hq); } } } diff --git a/Assets/Scripts/Model/Costs/Active.cs b/Assets/Scripts/Model/Costs/Active.cs deleted file mode 100644 index 68ad53a..0000000 --- a/Assets/Scripts/Model/Costs/Active.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Threading.Tasks; -using model.cards; - -namespace model.costs -{ - public class Active : ICost - { - private Card card; - bool ICost.Payable => card.Active; - public event Action ChangedPayability = delegate { }; - - public Active(Card card) - { - this.card = card; - card.Toggled += delegate - { - ChangedPayability(this, card.Active); - }; - } - - async Task ICost.Pay() => await Task.CompletedTask; - } -} diff --git a/Assets/Scripts/Model/Costs/Free.cs b/Assets/Scripts/Model/Costs/Free.cs new file mode 100644 index 0000000..28b14dd --- /dev/null +++ b/Assets/Scripts/Model/Costs/Free.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading.Tasks; + +namespace model.costs +{ + public class Free : ICost + { + bool ICost.Payable => true; + public event Action ChangedPayability = delegate { }; + + async Task ICost.Pay() + { + await Task.CompletedTask; + } + } +} diff --git a/Assets/Scripts/Model/Costs/Free.cs.meta b/Assets/Scripts/Model/Costs/Free.cs.meta new file mode 100644 index 0000000..5e7d5ba --- /dev/null +++ b/Assets/Scripts/Model/Costs/Free.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 09b52e852ca52c24698431115e0a3bf6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Costs/Trash.cs b/Assets/Scripts/Model/Costs/Trash.cs index 77d1ce0..da7dbd6 100644 --- a/Assets/Scripts/Model/Costs/Trash.cs +++ b/Assets/Scripts/Model/Costs/Trash.cs @@ -26,14 +26,12 @@ private void CheckIfTrashable(Card card, Zone source, Zone destination) async Task ICost.Pay() { - TrashIt(); - await Task.CompletedTask; + await TrashIt(); } - public void TrashIt() + async public Task TrashIt() { - card.Deactivate(); - card.MoveTo(bin); + await card.MoveTo(bin); } } } diff --git a/Assets/Scripts/Model/Game.cs b/Assets/Scripts/Model/Game.cs index 56838d2..72a1c9d 100644 --- a/Assets/Scripts/Model/Game.cs +++ b/Assets/Scripts/Model/Game.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using model.abilities; using model.player; using model.timing; using model.zones; @@ -11,143 +10,36 @@ public class Game { public readonly Corp corp; public readonly Runner runner; + public readonly Timing Timing; + public readonly Abilities Abilities; private readonly Zone playArea; - public readonly Checkpoint checkpoint; - public event EventHandler CurrentTurn = delegate { }; - public event EventHandler NextTurn = delegate { }; - public event Action Finished = delegate { }; - private bool ended = false; - private Queue turns = new Queue(); private Shuffling shuffling; public Game(IPilot corpPilot, IPilot runnerPilot, Shuffling shuffling) { this.shuffling = shuffling; playArea = new Zone("Play area", true); - corp = CreateCorp(corpPilot); - runner = CreateRunner(runnerPilot); - this.checkpoint = new Checkpoint(this); - corp.zones.rd.Decked += DeckCorp; - runner.zones.score.StolenEnough += StealEnough; - } - - private Corp CreateCorp(IPilot pilot) - { - var turn = new timing.corp.CorpTurn(this); - var paidWindow = new PaidWindow("corp"); - return new Corp(pilot, turn, paidWindow, playArea, shuffling, new Random()); - } - - private Runner CreateRunner(IPilot pilot) - { - var turn = new timing.runner.RunnerTurn(this); - var paidWindow = new PaidWindow("runner"); - return new Runner(pilot, turn, paidWindow, playArea, shuffling, this); + corp = new Corp(corpPilot, playArea, shuffling, new Random()); + runner = new Runner(runnerPilot, playArea, shuffling, this); + this.Timing = new Timing(this); + this.Abilities = new Abilities(); } async public void Start(Deck corpDeck, Deck runnerDeck) { await corp.Start(this, corpDeck); await runner.Start(this, runnerDeck); - await StartTurns(); + await Timing.StartTurns(); } - async private Task StartTurns() + public IPilot Pilot(Side side) { - turns.Enqueue(corp.turn); - turns.Enqueue(runner.turn); - try - { - while (!ended) - { - turns.Enqueue(corp.turn); - turns.Enqueue(runner.turn); - await StartNextTurn(); - await StartNextTurn(); - } - } - catch (Exception e) + switch (side) { - if (ended) - { - UnityEngine.Debug.Log("The game is over! " + e.Message); - } - else - { - throw new Exception("Failed a turn", e); - } + case Side.CORP: return corp.pilot; + case Side.RUNNER: return runner.pilot; + default: throw new Exception("Unclear how to pilot an unknown side: " + side); } } - - private async Task StartNextTurn() - { - var currentTurn = turns.Dequeue(); - CurrentTurn(this, currentTurn); - NextTurn(this, turns.Peek()); - await currentTurn.Start(); - } - - async public Task OpenPaidWindow(PaidWindow acting, PaidWindow reacting) - { - var bothPlayersCouldAct = false; - while (true) - { - var actingDeclined = await acting.AwaitPass(); - if (actingDeclined && bothPlayersCouldAct) - { - break; - } - var reactingDeclined = await reacting.AwaitPass(); - bothPlayersCouldAct = true; - if (reactingDeclined && bothPlayersCouldAct) - { - break; - } - } - } - - private void DeckCorp(Corp corp) - { - Finish(new GameFinish( - winner: "The Runner", - loser: "The Corp", - reason: "Corp R&D is empty" - )); - } - - private void StealEnough() - { - Finish(new GameFinish( - winner: "The Runner", - loser: "The Corp", - reason: "Runner has stolen enough" - )); - } - - private void Finish(GameFinish finish) - { - ended = true; - Finished(finish); - throw new Exception("Game over, " + finish.reason); - } - - async public Task Checkpoint() - { - await checkpoint.Check(); - } - } - - public class GameFinish - { - public string winner; - public string loser; - public string reason; - - public GameFinish(string winner, string loser, string reason) - { - this.winner = winner; - this.loser = loser; - this.reason = reason; - } } } diff --git a/Assets/Scripts/Model/Install/GenericInstall.cs b/Assets/Scripts/Model/Install/GenericInstall.cs index 87fa2b1..a5187c7 100644 --- a/Assets/Scripts/Model/Install/GenericInstall.cs +++ b/Assets/Scripts/Model/Install/GenericInstall.cs @@ -58,7 +58,7 @@ private void CheckIfRunnerCanInstall(ICost cost, bool payable) // CR: 8.3 async public Task Resolve() { - PutOut(); + await PutOut(); var destination = await ChooseDestination(); await TrashLikeCards(destination); await PayInstallCost(destination); @@ -67,9 +67,9 @@ async public Task Resolve() } // CR: 8.3.1 - private void PutOut() + async private Task PutOut() { - card.MoveTo(playArea); + await card.MoveTo(playArea); card.FlipPreInstall(); } @@ -97,13 +97,13 @@ async private Task PayInstallCost(IInstallDestination destination) // CR: 8.3.5 async private Task Place(IInstallDestination destination) { - destination.Host(card); - card.Installed(); + await destination.Host(card); + await card.SetInstalled(); if (card.Faction.Side == Side.RUNNER) { // CR: 8.2.3 card.FlipFaceUp(); - await card.Activate(); + await card.BecomeActive(); } } diff --git a/Assets/Scripts/Model/Play/Ability.cs b/Assets/Scripts/Model/Play/Ability.cs index a83c93e..5b35717 100644 --- a/Assets/Scripts/Model/Play/Ability.cs +++ b/Assets/Scripts/Model/Play/Ability.cs @@ -1,46 +1,56 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Threading.Tasks; -using model.cards; -namespace model.play -{ - public class Ability - { +namespace model.play { + public class Ability : IPlayOption { public readonly ICost cost; public readonly IEffect effect; + public readonly ISource source; + public bool Mandatory { get; } + public bool Active { get; private set; } public event Action UsabilityChanged = delegate { }; public event Action Resolved = delegate { }; - public bool Usable => cost.Payable && effect.Impactful; - public Ability(ICost cost, IEffect effect) - { + public bool Usable => cost.Payable && effect.Impactful; // CR: 1.2.5 + public bool Legal => Active && Usable; + + public Ability(ICost cost, IEffect effect, ISource source, bool mandatory) { this.cost = cost; this.effect = effect; + this.source = source; + this.Mandatory = mandatory; + Active = source.Active; // CR: 9.1.8 + source.ChangedActivation += UpdateActivity; cost.ChangedPayability += UpdateCost; effect.ChangedImpact += UpdateEffect; } - private void UpdateCost(ICost source, bool payable) - { + private void UpdateActivity(ISource source) { + Active = source.Active; + } + + private void UpdateCost(ICost source, bool payable) { UsabilityChanged(this, Usable); } - private void UpdateEffect(IEffect source, bool impactful) - { + private void UpdateEffect(IEffect source, bool impactful) { UsabilityChanged(this, Usable); } - async public Task Trigger() - { - await cost.Pay(); - await effect.Resolve(); - Resolved(this); + async public Task Resolve() { + await Trigger(); } - public CardAbility BelongingTo(Card card) => new CardAbility(this, card); + async public Task Trigger() { + if (Active) { + await cost.Pay(); + await effect.Resolve(); + Resolved(this); + } else { + throw new System.Exception("Cannot trigger an inactive ability " + this); + } + } - public override string ToString() => "Ability(cost=" + cost + ", effect=" + effect + ")"; + public override string ToString() => cost + " : " + effect; } } diff --git a/Assets/Scripts/Model/Play/CardAbility.cs b/Assets/Scripts/Model/Play/CardAbility.cs deleted file mode 100644 index afd25d3..0000000 --- a/Assets/Scripts/Model/Play/CardAbility.cs +++ /dev/null @@ -1,16 +0,0 @@ -using model.cards; - -namespace model.play -{ - public class CardAbility - { - public Ability Ability { get; } - public Card Card { get; } - - public CardAbility(Ability ability, Card card) - { - Ability = ability; - Card = card; - } - } -} diff --git a/Assets/Scripts/Model/Play/GameRule.cs b/Assets/Scripts/Model/Play/GameRule.cs new file mode 100644 index 0000000..ab6f2f0 --- /dev/null +++ b/Assets/Scripts/Model/Play/GameRule.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using model.player; +using model.timing; + +namespace model.play { + public class GameRule : ISource { + public bool Active { get; } + public event Action ChangedActivation; + public IList Used { get; } // CR 9.1.6 + public IPilot Controller { get; } + } +} diff --git a/Assets/Scripts/Model/Play/GameRule.cs.meta b/Assets/Scripts/Model/Play/GameRule.cs.meta new file mode 100644 index 0000000..5cb1b0a --- /dev/null +++ b/Assets/Scripts/Model/Play/GameRule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3e4d18f1251a3446a2f8761f9ddc291 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Play/IPlayOption.cs b/Assets/Scripts/Model/Play/IPlayOption.cs new file mode 100644 index 0000000..c63fdba --- /dev/null +++ b/Assets/Scripts/Model/Play/IPlayOption.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace model.play +{ + public interface IPlayOption + { + bool Legal { get; } + bool Mandatory { get; } + Task Resolve(); + } +} diff --git a/Assets/Scripts/Model/Play/IPlayOption.cs.meta b/Assets/Scripts/Model/Play/IPlayOption.cs.meta new file mode 100644 index 0000000..994d8c9 --- /dev/null +++ b/Assets/Scripts/Model/Play/IPlayOption.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66b5431d918f0f341be6951b1e2a1a9f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Play/ISource.cs b/Assets/Scripts/Model/Play/ISource.cs new file mode 100644 index 0000000..1203134 --- /dev/null +++ b/Assets/Scripts/Model/Play/ISource.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using model.player; +using model.timing; + +namespace model.play +{ + public interface ISource + { + bool Active { get; } + event Action ChangedActivation; + IList Used { get; } // CR 9.1.6 + IPilot Controller { get; } + } +} diff --git a/Assets/Scripts/Model/Play/ISource.cs.meta b/Assets/Scripts/Model/Play/ISource.cs.meta new file mode 100644 index 0000000..bac9ed5 --- /dev/null +++ b/Assets/Scripts/Model/Play/ISource.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d48ea31dc3a1ecb40ba32697f9380185 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Play/RunnerActing.cs b/Assets/Scripts/Model/Play/RunnerActing.cs index cfb456c..6b93a9a 100644 --- a/Assets/Scripts/Model/Play/RunnerActing.cs +++ b/Assets/Scripts/Model/Play/RunnerActing.cs @@ -16,9 +16,9 @@ public class RunnerActing public RunnerActing(Runner runner) { this.runner = runner; - draw = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.zones.Drawing(1)); + draw = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.zones.Drawing(1), new GameRule(), true); draw.Resolved += CompleteAction; - credit = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.credits.Gaining(1)); + credit = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.credits.Gaining(1), new GameRule(), true); credit.Resolved += CompleteAction; } @@ -26,21 +26,21 @@ public RunnerActing(Runner runner) public Ability Play(Card card) { - Ability play = new Ability(new Conjunction(runner.clicks.Spending(1), card.PlayCost, permission), runner.zones.Playing(card)); + Ability play = new Ability(new Conjunction(runner.clicks.Spending(1), card.PlayCost, permission), runner.zones.Playing(card), new GameRule(), true); play.Resolved += CompleteAction; return play; } public Ability Install(Card card) { - Ability install = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.Installing.InstallingCard(card)); + Ability install = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.Installing.InstallingCard(card), new GameRule(), true); install.Resolved += CompleteAction; return install; } public Ability Run(IServer server) { - Ability run = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.Running.RunningOn(server)); + Ability run = new Ability(new Conjunction(runner.clicks.Spending(1), permission), runner.Running.RunningOn(server), new GameRule(), true); run.Resolved += CompleteAction; return run; } diff --git a/Assets/Scripts/Model/Play/SimultaneousTriggers.cs b/Assets/Scripts/Model/Play/SimultaneousTriggers.cs index 3157d77..d9730b7 100644 --- a/Assets/Scripts/Model/Play/SimultaneousTriggers.cs +++ b/Assets/Scripts/Model/Play/SimultaneousTriggers.cs @@ -6,9 +6,9 @@ namespace model.play { public class SimultaneousTriggers { - public IList untriggered; + public IList untriggered; - public SimultaneousTriggers(IList effects) + public SimultaneousTriggers(IList effects) { untriggered = effects; } @@ -18,9 +18,9 @@ async public Task AllTriggered(IPilot pilot) while (untriggered.Count > 0) { UnityEngine.Debug.Log("Picking the next effect to fire among " + untriggered); - var effect = await pilot.TriggerFromSimultaneous(untriggered); - await effect.Resolve(); - untriggered.Remove(effect); + var ability = await pilot.TriggerFromSimultaneous(untriggered); + await ability.Trigger(); + untriggered.Remove(ability); } } } diff --git a/Assets/Scripts/Model/Player/DelegatingPilot.cs b/Assets/Scripts/Model/Player/DelegatingPilot.cs index 28920b3..465dbe0 100644 --- a/Assets/Scripts/Model/Player/DelegatingPilot.cs +++ b/Assets/Scripts/Model/Player/DelegatingPilot.cs @@ -1,49 +1,50 @@ -using model.cards; +using System.Collections.Generic; +using System.Threading.Tasks; +using model.cards; using model.choices; using model.choices.trash; +using model.play; using model.steal; +using model.timing; using model.zones; -using System.Collections.Generic; -using System.Threading.Tasks; -namespace model.player -{ - public class DelegatingPilot : IPilot - { +namespace model.player { + public class DelegatingPilot : IPilot { private IPilot basic; - public DelegatingPilot(IPilot basic) - { + public DelegatingPilot(IPilot basic) { this.basic = basic; } - public virtual void Play(Game game) - { + public virtual void Play(Game game) { basic.Play(game); } - public virtual Task TriggerFromSimultaneous(IList effects) - { - return basic.TriggerFromSimultaneous(effects); + public virtual IPlayOption Choose(IList options) { + return basic.Choose(options); + } + + public virtual Task Receive(Priority priority) { + return basic.Receive(priority); + } + + public virtual Task TriggerFromSimultaneous(IEnumerable abilities) { + return basic.TriggerFromSimultaneous(abilities); } - public virtual IDecision ChooseACard() - { + public virtual IDecision ChooseACard() { return basic.ChooseACard(); } - public virtual IDecision ChooseAnInstallDestination() - { + public virtual IDecision ChooseAnInstallDestination() { return basic.ChooseAnInstallDestination(); } - public virtual IDecision ChooseTrashing() - { + public virtual IDecision ChooseTrashing() { return basic.ChooseTrashing(); } - public virtual IDecision ChooseStealing() - { + public virtual IDecision ChooseStealing() { return basic.ChooseStealing(); } } diff --git a/Assets/Scripts/Model/Player/IPilot.cs b/Assets/Scripts/Model/Player/IPilot.cs index fbd6185..9aeff70 100644 --- a/Assets/Scripts/Model/Player/IPilot.cs +++ b/Assets/Scripts/Model/Player/IPilot.cs @@ -1,7 +1,9 @@ using model.cards; using model.choices; using model.choices.trash; +using model.play; using model.steal; +using model.timing; using model.zones; using System.Collections.Generic; using System.Threading.Tasks; @@ -11,11 +13,13 @@ namespace model.player public interface IPilot { void Play(Game game); - Task TriggerFromSimultaneous(IList effects); + Task TriggerFromSimultaneous(IEnumerable abilities); IDecision ChooseACard(); // IDecision ChooseAZone(); TODO for central access IDecision ChooseAnInstallDestination(); IDecision ChooseTrashing(); IDecision ChooseStealing(); + IPlayOption Choose(IList options); + Task Receive(Priority priority); } } diff --git a/Assets/Scripts/Model/Player/IPlayer.cs b/Assets/Scripts/Model/Player/IPlayer.cs new file mode 100644 index 0000000..0dd8e48 --- /dev/null +++ b/Assets/Scripts/Model/Player/IPlayer.cs @@ -0,0 +1,8 @@ +using model.cards.text; +using model.timing; + +namespace model.player +{ + public interface IPlayer { + } +} diff --git a/Assets/Scripts/Model/Player/IPlayer.cs.meta b/Assets/Scripts/Model/Player/IPlayer.cs.meta new file mode 100644 index 0000000..b2c6206 --- /dev/null +++ b/Assets/Scripts/Model/Player/IPlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e987b26281072f1449fae7670d8739fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Player/NoPilot.cs b/Assets/Scripts/Model/Player/NoPilot.cs index e8b659d..69cebd3 100644 --- a/Assets/Scripts/Model/Player/NoPilot.cs +++ b/Assets/Scripts/Model/Player/NoPilot.cs @@ -1,21 +1,28 @@ -using model.cards; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using model.cards; using model.choices; using model.choices.trash; +using model.play; using model.steal; +using model.timing; using model.zones; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -namespace model.player -{ - public class NoPilot : IPilot - { +namespace model.player { + public class NoPilot : IPilot { void IPilot.Play(Game game) { } - Task IPilot.TriggerFromSimultaneous(IList effects) - { - return Task.FromResult(effects.First()); + IPlayOption IPilot.Choose(IList options) { + throw new System.NotImplementedException(); + } + + Task IPilot.Receive(Priority priority) { + throw new System.NotImplementedException(); + } + + Task IPilot.TriggerFromSimultaneous(IEnumerable abilities) { + return Task.FromResult(abilities.First()); } IDecision IPilot.ChooseACard() => new FailingChoice(); diff --git a/Assets/Scripts/Model/Player/SingleChoiceMaker.cs b/Assets/Scripts/Model/Player/SingleChoiceMaker.cs index 6dffef3..5f5255d 100644 --- a/Assets/Scripts/Model/Player/SingleChoiceMaker.cs +++ b/Assets/Scripts/Model/Player/SingleChoiceMaker.cs @@ -1,6 +1,7 @@ using model.cards; using model.choices; using model.choices.trash; +using model.play; using model.steal; using model.zones; using System.Collections.Generic; @@ -13,15 +14,15 @@ public class SingleChoiceMaker : DelegatingPilot { public SingleChoiceMaker(IPilot basic) : base(basic) { } - async override public Task TriggerFromSimultaneous(IList effects) + async override public Task TriggerFromSimultaneous(IEnumerable abilities) { - if (effects.Count == 1) + if (abilities.Count() == 1) { - return effects.Single(); + return abilities.Single(); } else { - return await base.TriggerFromSimultaneous(effects); + return await base.TriggerFromSimultaneous(abilities); } } diff --git a/Assets/Scripts/Model/Rez/Rezzing.cs b/Assets/Scripts/Model/Rez/Rezzing.cs index 9d2ce6a..3cc401a 100644 --- a/Assets/Scripts/Model/Rez/Rezzing.cs +++ b/Assets/Scripts/Model/Rez/Rezzing.cs @@ -5,93 +5,76 @@ using model.costs; using model.play; -namespace model.rez -{ - public class Rezzing - { +namespace model.rez { + public class Rezzing { private Corp corp; public RezWindow Window { get; } - public Rezzing(Corp corp) - { + public Rezzing(Corp corp) { this.corp = corp; Window = new RezWindow(); } - public void Track(Card card) - { + public void Track(Card card) { Window.Add(Rez(card)); } // CR: 8.1.2.a - public Ability Rez(Card card) - { + public IPlayOption Rez(Card card) { var rezCost = new Conjunction( card.PlayCost, new FaceDown(card), new Installed(card), Window.Permission() ); - return new Ability(rezCost, new RezUnconditionally(card)); + return new Ability(rezCost, new RezUnconditionally(card), new GameRule(), false); } - private class FaceDown : ICost - { + private class FaceDown : ICost { private Card card; public bool Payable => !card.Faceup; public event Action ChangedPayability; - public FaceDown(Card card) - { + public FaceDown(Card card) { this.card = card; } - async public Task Pay() - { - if (!Payable) - { + async public Task Pay() { + if (!Payable) { throw new Exception(card + " should be face down"); } await Task.CompletedTask; } } - private class Installed : ICost - { + private class Installed : ICost { private Card card; public bool Payable => card.Zone.InPlayArea; public event Action ChangedPayability; - public Installed(Card card) - { + public Installed(Card card) { this.card = card; } - async public Task Pay() - { - if (!Payable) - { + async public Task Pay() { + if (!Payable) { throw new Exception(card + " should be installed, but it's in " + card.Zone); } await Task.CompletedTask; } } - private class RezUnconditionally : IEffect - { + private class RezUnconditionally : IEffect { private Card card; public bool Impactful => true; public event Action ChangedImpact; public IEnumerable Graphics => new string[] { }; - - public RezUnconditionally(Card card) - { + public RezUnconditionally(Card card) { this.card = card; } - async public Task Resolve() - { + async public Task Resolve() { card.FlipFaceUp(); await card.PlayCost.Pay(); await card.Activate(); diff --git a/Assets/Scripts/Model/Run/RunStructure.cs b/Assets/Scripts/Model/Run/RunStructure.cs index f5f251f..a52dc62 100644 --- a/Assets/Scripts/Model/Run/RunStructure.cs +++ b/Assets/Scripts/Model/Run/RunStructure.cs @@ -1,100 +1,73 @@ -using model.timing; +using System.Threading.Tasks; +using model.timing; using model.zones.corp; -using System.Threading.Tasks; -namespace model.run -{ - public class RunStructure - { +namespace model.run { + public class RunStructure { private IServer server; private readonly Game game; private int position; - public RunStructure(IServer server, Game game) - { + public RunStructure(IServer server, Game game) { this.server = server; this.game = game; } - async public Task AwaitEnd() - { + async public Task AwaitEnd() { var ice = server.Ice; position = ice.Height; // TODO - if (position == 0) - { + if (position == 0) { await ApproachServer(); // 6.9.5 - } - else - { + } else { // TODO } await End(); // 6.9.6 } - async private Task ApproachServer() - { + async private Task ApproachServer() { await TriggerServerApproached(); // 6.9.5.a - await OpenPreCommitmentWindows(); // 6.9.5.b + await game.Timing.DefinePaidWindow(rezzing: false, scoring: false).Open(); // 6.9.5.b var jackedOut = await OfferJackOut(); // 6.9.5.c - if (jackedOut) - { + if (jackedOut) { return; } - await OpenPostCommitmentWindows(); // 6.9.5.d + await game.Timing.DefinePaidWindow(rezzing: true, scoring: false).Open(); // 6.9.5.d await MakeRunSuccessful(); // 6.9.5.e - await game.Checkpoint(); // 6.9.5.f + await game.Timing.Checkpoint(); // 6.9.5.f await Access(); // 6.9.5.g - await game.Checkpoint(); // 6.9.5.h + await game.Timing.Checkpoint(); // 6.9.5.h } - async private Task TriggerServerApproached() - { + async private Task TriggerServerApproached() { await Task.CompletedTask; // TODO } - async private Task OpenPreCommitmentWindows() - { - await game.OpenPaidWindow(game.runner.paidWindow, game.corp.paidWindow); // TODO An Offer You Can't Refuse - } - async private Task OfferJackOut() - { - return await Task.FromResult(false); // TODO - } - async private Task OpenPostCommitmentWindows() - { - var rez = game.corp.Rezzing.Window.Open(); - var paid = game.OpenPaidWindow(game.runner.paidWindow, game.corp.paidWindow); // TODO An Offer You Can't Refuse - await paid; - await rez; + async private Task OfferJackOut() { + return await Task.FromResult(false); // TODO } - async private Task MakeRunSuccessful() - { + async private Task MakeRunSuccessful() { await Task.CompletedTask; // TODO } - async private Task Access() - { + async private Task Access() { await new AccessStructure(server, game).AwaitEnd(); } - async private Task End() - { + async private Task End() { await LoseBadPublicity(); // 6.9.6.a // TODO 6.9.6.b await game.Checkpoint(); // 6.9.6.c } - async private Task LoseBadPublicity() - { + async private Task LoseBadPublicity() { await Task.CompletedTask; // TODO } - public override string ToString() - { + public override string ToString() { return "Run(server=" + server + ")"; } } diff --git a/Assets/Scripts/Model/Runner.cs b/Assets/Scripts/Model/Runner.cs index 6aa2580..7b3d3f3 100644 --- a/Assets/Scripts/Model/Runner.cs +++ b/Assets/Scripts/Model/Runner.cs @@ -4,18 +4,14 @@ using model.player; using model.run; using model.steal; -using model.timing; -using model.timing.runner; using model.zones; using model.zones.runner; namespace model { - public class Runner + public class Runner : IPlayer { public readonly IPilot pilot; - public readonly RunnerTurn turn; - public readonly PaidWindow paidWindow; public int tags = 0; public readonly Zones zones; public readonly ClickPool clicks; @@ -27,16 +23,12 @@ public class Runner public Runner( IPilot pilot, - RunnerTurn turn, - PaidWindow paidWindow, Zone playArea, Shuffling shuffling, Game game ) { this.pilot = pilot; - this.turn = turn; - this.paidWindow = paidWindow; zones = new Zones( new Grip(), new Stack(shuffling), @@ -55,14 +47,14 @@ Game game async public Task Start(Game game, Deck deck) { - zones.stack.AddDeck(deck); + await zones.stack.AddDeck(deck); var identity = deck.identity; zones.identity.Add(identity); identity.FlipFaceUp(); await identity.Activate(); pilot.Play(game); credits.Gain(5); - zones.stack.Draw(5, zones.grip); + await zones.stack.Draw(5, zones.grip); } } } diff --git a/Assets/Scripts/Model/Steal/MustSteal.cs b/Assets/Scripts/Model/Steal/MustSteal.cs index d57adfa..6022b6d 100644 --- a/Assets/Scripts/Model/Steal/MustSteal.cs +++ b/Assets/Scripts/Model/Steal/MustSteal.cs @@ -19,10 +19,10 @@ public MustSteal(Card card, int agendaPoints, Zones zones) } bool IStealOption.IsLegal() => true; - Task IStealOption.Perform() + async Task IStealOption.Perform() { - zones.score.Add(card, agendaPoints); // CR: 1.16.3 - return Task.FromResult(true); + await zones.score.Add(card, agendaPoints); // CR: 1.16.3 + return true; } } } diff --git a/Assets/Scripts/Model/Timing/AccessCard.cs b/Assets/Scripts/Model/Timing/AccessCard.cs index 5c977e8..ea62d11 100644 --- a/Assets/Scripts/Model/Timing/AccessCard.cs +++ b/Assets/Scripts/Model/Timing/AccessCard.cs @@ -21,11 +21,11 @@ async public Task AwaitEnd() var preAccessInfo = Look(); var preAccessZone = card.Zone; await TriggerAccessingCard(); // 7.8.1 - await game.Checkpoint(); // 7.8.2 + await game.Timing.Checkpoint(); // 7.8.2 await Trash(); // 7.8.3 await Steal(); // 7.8.4 await TriggerAfterAccessingCard(); // 7.8.5 - await game.Checkpoint(); // 7.8.6 + await game.Timing.Checkpoint(); // 7.8.6 StopLooking(preAccessInfo, preAccessZone); } diff --git a/Assets/Scripts/Model/Timing/Checkpoint.cs b/Assets/Scripts/Model/Timing/Checkpoint.cs index 4913e0c..34c5082 100644 --- a/Assets/Scripts/Model/Timing/Checkpoint.cs +++ b/Assets/Scripts/Model/Timing/Checkpoint.cs @@ -1,23 +1,24 @@ using System.Linq; using System.Threading.Tasks; -namespace model.timing -{ +namespace model.timing { // CR: 10.3 - public class Checkpoint - { + public class Checkpoint { private Game game; + private bool occurred = false; - public Checkpoint(Game game) - { + public Checkpoint(Game game) { this.game = game; } // CR: 10.3.1 - async internal Task Check() - { - CheckConditionalAbilities(); + async internal Task Check() { + if (occurred) { + throw new System.Exception("Checkpoint reused"); + } + occurred = true; + await CheckConditionalAbilities(); CheckExpiredAbilities(); CheckPlayerScore(); CheckUniqueCards(); @@ -30,50 +31,42 @@ async internal Task Check() } // CR: 10.3.1.a - private void CheckConditionalAbilities() - { - // TODO impl + async private Task CheckConditionalAbilities() { + await game.Abilities.CheckReactions(); } // CR: 10.3.1.b - private void CheckExpiredAbilities() - { + private void CheckExpiredAbilities() { // TODO impl } // CR: 10.3.1.c - private void CheckPlayerScore() - { + private void CheckPlayerScore() { // TODO impl } // CR: 10.3.1.d - private void CheckUniqueCards() - { + private void CheckUniqueCards() { // TODO impl } // CR: 10.3.1.e - private Task FixBrokenRestrictions() - { + private Task FixBrokenRestrictions() { return Task.CompletedTask; // TODO impl } // CR: 10.3.1.f - private void TrashScoreAreaLeftovers() - { + private void TrashScoreAreaLeftovers() { // TODO impl } // CR: 10.3.1.g - private void CheckMissingHosts() - { + private void CheckMissingHosts() { // TODO impl } // CR: 10.3.1.h - private void PruneEmptyRemotes() - { + private void PruneEmptyRemotes() { var zones = game.corp.zones; zones .remotes @@ -83,14 +76,12 @@ private void PruneEmptyRemotes() } // CR: 10.3.1.i - private void UnconvertDiscardedCards() - { + private void UnconvertDiscardedCards() { // TODO impl } // CR: 10.3.1.j - private void ReturnDiscardedCounters() - { + private void ReturnDiscardedCounters() { // TODO impl } } diff --git a/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs new file mode 100644 index 0000000..784a28b --- /dev/null +++ b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using model.play; + +namespace model.timing.corp +{ + public class CorpActionPhase : ITimingStructure + { + private Corp corp; + private Timing timing; + public string Name => "Corp action phase"; + public event AsyncAction Opened; + public event AsyncAction Closed; + public event AsyncAction TakingAction; + public event AsyncAction ActionTaken; + + internal CorpActionPhase(Corp corp, Timing timing) + { + this.corp = corp; + this.timing = timing; + } + + public async Task Open() + { + Opened?.Invoke(this); + var paidWindow = timing.DefinePaidWindow(rezzing: true, scoring: true); + await paidWindow.Open(); // CR: 5.6.2.a + while (corp.clicks.Remaining > 0) // CR: 5.6.2.c + { + await TakeAction(); // CR: 5.6.2.b + } + await timing.Checkpoint(); // CR: 5.6.2.d + } + + async private Task TakeAction() + { + var actionTaking = corp.Acting.TakeAction(); + TakingAction?.Invoke(); + var action = await actionTaking; + ActionTaken?.Invoke(action); + await timing.DefinePaidWindow(rezzing: true, scoring: true).Open(); + } + } +} diff --git a/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs.meta b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs.meta new file mode 100644 index 0000000..e639ffb --- /dev/null +++ b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db6e07d42ee9fa84cb2234d70bc97377 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs new file mode 100644 index 0000000..a40ccbf --- /dev/null +++ b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; + +namespace model.timing.corp { + public class CorpDrawPhase : ITimingStructure { + private Corp corp; + private Timing timing; + private ReactionWindow turnBegins; + + internal CorpDrawPhase(Corp corp, Timing timing, ReactionWindow turnBegins) : base("Corp draw phase") { + this.corp = corp; + this.timing = timing; + } + + async protected override Task Proceed() { + corp.clicks.Replenish(); // CR: 5.6.1.a + await timing.DefinePaidWindow(rezzing: true, scoring: true).Open(); // CR: 5.6.1.b + RefillRecurringCredits(); // CR: 5.6.1.c + await turnBegins.Open(); // CR: 5.6.1.d + await timing.Checkpoint();// CR: 5.6.1.e + await MandatoryDraw(); // CR: 5.6.1.f + await timing.Checkpoint(); // CR: 5.6.1.g + } + + private void RefillRecurringCredits() { + } + + async private Task MandatoryDraw() { + await corp.zones.Drawing(1).Resolve(); + } + } +} diff --git a/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs.meta b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs.meta new file mode 100644 index 0000000..79b3a32 --- /dev/null +++ b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 167edef1e81fa3947af9c06e82dde1d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs index 45be518..de430d4 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs @@ -1,143 +1,55 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using model.play; +using System.Threading.Tasks; +using model.player; -namespace model.timing.corp -{ - public class CorpTurn : ITurn - { - private Game game; - public bool Active { get; private set; } = false; - ClickPool ITurn.Clicks => game.corp.clicks; - Side ITurn.Side => Side.CORP; - private IList turnBeginningTriggers = new List(); - public event AsyncAction Started; - public event AsyncAction TakingAction; - public event AsyncAction ActionTaken; +namespace model.timing.corp { + public class CorpTurn : ITurn { + public CorpDrawPhase drawPhase { get; } + public CorpActionPhase actionPhase { get; } + public CorpDiscardPhase discardPhase { get; } + private Corp corp; + private Timing timing; + override public ClickPool Clicks => corp.clicks; + override public Side Side => Side.CORP; + override public IPilot Owner => corp.pilot; - public CorpTurn(Game game) - { - this.game = game; + public CorpTurn(Corp corp, Timing timing, int number) : base("Corp turn " + number) { + this.corp = corp; + this.timing = timing; + drawPhase = new CorpDrawPhase(corp, timing, Begins); + actionPhase = new CorpActionPhase(corp, timing); + discardPhase = new CorpDiscardPhase(); } - async Task ITurn.Start() - { - Active = true; - await Started?.Invoke(this); - await DrawPhase(); - await ActionPhase(); - await DiscardPhase(); - Active = false; + public CorpTurn(Corp corp, Timing timing) { + this.corp = corp; + this.timing = timing; } - async private Task DrawPhase() - { - game.corp.clicks.Replenish(); // CR: 5.6.1.a - var rez = OpenRezWindow(); // CR: 5.6.1.b - var score = OpenScoreWindow(); // CR: 5.6.1.b - var paid = OpenPaidWindow(); // CR: 5.6.1.b - await rez; - await score; - await paid; - RefillRecurringCredits(); // CR: 5.6.1.c - await TriggerTurnBeginning(); // CR: 5.6.1.d - await game.Checkpoint();// CR: 5.6.1.e - await MandatoryDraw(); // CR: 5.6.1.f - await game.Checkpoint(); // CR: 5.6.1.g - } - - async private Task OpenPaidWindow() - { - await game.OpenPaidWindow( - acting: game.corp.paidWindow, - reacting: game.runner.paidWindow - ); - } - - async private Task OpenRezWindow() - { - await game.corp.Rezzing.Window.Open(); - } - - async private Task OpenScoreWindow() - { - await Task.FromResult("TODO let corp score"); - } - - private void RefillRecurringCredits() - { - } - - async private Task TriggerTurnBeginning() - { - if (turnBeginningTriggers.Count > 0) - { - await new SimultaneousTriggers(turnBeginningTriggers.Copy()).AllTriggered(game.corp.pilot); - } + async protected override Task Proceed() { + await drawPhase.Open(); + await actionPhase.Open(); + await discardPhase.Open(); } - async private Task MandatoryDraw() - { - await game.corp.zones.Drawing(1).Resolve(); - } - - async private Task ActionPhase() - { - var rez = OpenRezWindow(); // CR: 5.6.2.a - var score = OpenScoreWindow(); // CR: 5.6.2.a - var paid = OpenPaidWindow(); // CR: 5.6.2.a - await rez; - await score; - await paid; - while (game.corp.clicks.Remaining > 0) // CR: 5.6.2.c - { - await TakeAction(); // CR: 5.6.2.b - } - await game.Checkpoint(); // CR: 5.6.2.d - } - - async private Task TakeAction() - { - var actionTaking = game.corp.Acting.TakeAction(); - TakingAction?.Invoke(this); - var action = await actionTaking; - ActionTaken?.Invoke(this, action); - var rez = OpenRezWindow(); - var score = OpenScoreWindow(); - var paid = OpenPaidWindow(); - await rez; - await score; - await paid; - } - - async private Task DiscardPhase() - { + async private Task DiscardPhase() { await Discard(); // CR: 5.6.3.a var rez = OpenRezWindow(); // CR: 5.6.3.b var paid = OpenPaidWindow(); // CR: 5.6.3.b await rez; await paid; - game.corp.clicks.Reset(); // CR: 5.6.3.c + corp.clicks.Reset(); // CR: 5.6.3.c TriggerTurnEnding(); // CR: 5.6.3.d - await game.Checkpoint(); // CR: 5.6.3.e + await timing.Checkpoint(); // CR: 5.6.3.e } - async private Task Discard() - { - var hq = game.corp.zones.hq; - while (hq.Zone.Count > 5) - { + async private Task Discard() { + var hq = corp.zones.hq; + while (hq.Zone.Count > 5) { await hq.Discard(); } } - private void TriggerTurnEnding() - { - } - - public void WhenBegins(IEffect effect) - { - turnBeginningTriggers.Add(effect); + private void TriggerTurnEnding() { } } } diff --git a/Assets/Scripts/Model/Timing/ITimingStructure.cs b/Assets/Scripts/Model/Timing/ITimingStructure.cs new file mode 100644 index 0000000..127cb45 --- /dev/null +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; + +namespace model.timing { + public abstract class ITimingStructure { + event AsyncAction Initiated; + event AsyncAction Completed; + public string Name { get; } + + protected ITimingStructure(string name) { + this.Name = name; + } + + async public Task Initiate() { + await Initiated?.Invoke(this); + await Proceed(); + await Completed?.Invoke(this); + } + + protected abstract Task Proceed(); + } +} diff --git a/Assets/Scripts/Model/Timing/ITimingStructure.cs.meta b/Assets/Scripts/Model/Timing/ITimingStructure.cs.meta new file mode 100644 index 0000000..115ed61 --- /dev/null +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 60622795924f0f5468b9ee8d73babb6a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Timing/ITurn.cs b/Assets/Scripts/Model/Timing/ITurn.cs index 0227a49..b74c86e 100644 --- a/Assets/Scripts/Model/Timing/ITurn.cs +++ b/Assets/Scripts/Model/Timing/ITurn.cs @@ -1,27 +1,13 @@ -using System.Threading.Tasks; -using model.play; +using model.player; -namespace model.timing -{ - public interface ITurn - { - ClickPool Clicks { get; } - Side Side { get; } - Task Start(); +namespace model.timing { + public abstract class ITurn : ITimingStructure { - /// - /// Tells observers the turn has just started. - /// It's different from "when begins" triggers, because it's not for card effects. It's for tracking the earliest moment when the turn is considered active. - /// For example for tracking/resetting counters per turn. - /// - event AsyncAction Started; + public abstract ClickPool Clicks { get; } + public abstract Side Side { get; } + public abstract IPilot Owner { get; } - /// - /// Registers a game effect to be fired at the beginning of the turn, e.g. PAD Campaign. - /// - void WhenBegins(IEffect effect); - - event AsyncAction TakingAction; - event AsyncAction ActionTaken; + protected ITurn(string name) : base(name) { + } } } diff --git a/Assets/Scripts/Model/Timing/PaidWindow.cs b/Assets/Scripts/Model/Timing/PaidWindow.cs index 3e39ec0..a7df990 100644 --- a/Assets/Scripts/Model/Timing/PaidWindow.cs +++ b/Assets/Scripts/Model/Timing/PaidWindow.cs @@ -1,61 +1,48 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using model.play; - -namespace model.timing -{ - public class PaidWindow - { - private readonly string label; - private PaidWindowPermission permission = new PaidWindowPermission(); - public event Action Opened = delegate { }; - public event Action Closed = delegate { }; - public event Action Added = delegate { }; - public event Action Removed = delegate { }; - private List abilities = new List(); - private TaskCompletionSource pass; - - public PaidWindow(string label) - { - this.label = label; - } - - public List ListAbilities() => new List(abilities); - - public ICost Permission() => permission; - - async public Task AwaitPass() - { - pass = new TaskCompletionSource(); - permission.Grant(); - Opened(this); - await pass.Task; - Closed(this); - permission.Revoke(); - return !permission.WasPaid(); - } - - public void Pass() - { - pass.SetResult(true); +using model.player; + +namespace model.timing { + public class PaidWindow : PriorityWindow { + private bool rezzing; + private bool scoring; + private IPilot acting; + private IPilot reacting; + + public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting) : base("Paid ability window") { + this.rezzing = rezzing; + this.scoring = scoring; + this.acting = acting; + this.reacting = reacting; } - public void Add(CardAbility ability) - { - abilities.Add(ability); - Added(this, ability); + internal void GiveOption(IPilot pilot, IPlayOption pop) { + throw new System.NotImplementedException(); } - public void Remove(CardAbility ability) - { - abilities.Remove(ability); - Removed(this, ability); + async override protected Task Proceed() { + var bothPlayersCouldAct = false; // CR: 9.2.7.a + while (true) { + var action = await AwaitPass(acting); + if (action.Declined && bothPlayersCouldAct) { + break; + } + var reaction = await AwaitPass(reacting); + bothPlayersCouldAct = true; + if (reaction.Declined && bothPlayersCouldAct) { + break; + } + } } - public override string ToString() - { - return "PaidWindow(label=" + label + ")"; + async private Task AwaitPass(IPilot pilot) { + var priority = new Priority(canPass: true); // CR: 9.2.4.b + OnPriorityGiven(priority); + while (!priority.Passed) // CR: 9.2.4.c + { + await pilot.Receive(priority); // CR: 9.2.7.f + } + return priority; } } } diff --git a/Assets/Scripts/Model/Timing/Priority.cs b/Assets/Scripts/Model/Timing/Priority.cs new file mode 100644 index 0000000..870425e --- /dev/null +++ b/Assets/Scripts/Model/Timing/Priority.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using model.play; + +namespace model.timing { + public class Priority { + public bool Passed { get; private set; } = false; + public bool Declined { get; private set; } = true; + private IList options = new List(); + private IPlayOption pass = new Pass(); + + internal Priority(bool canPass) { + if (canPass) { + options.Add(pass); + } + } + + public void Offer(IPlayOption option) { + options.Add(option); + } + + public IList BrowseOptions() => new List(options); + + async public Task Choose(IPlayOption option) { + if (!option.Legal) { + throw new System.Exception(this + " chose an illegal option " + option); + } + if (!options.Contains(option)) { + throw new System.Exception(this + " chose an unavailable option " + option); + } + await option.Resolve(); + if (option == pass) { + Passed = true; + } else { + Declined = false; + } + } + + private class Pass : IPlayOption { + public bool Legal => true; + public bool Mandatory => false; + + async public Task Resolve() { + await Task.CompletedTask; + } + } + } +} diff --git a/Assets/Scripts/Model/Timing/Priority.cs.meta b/Assets/Scripts/Model/Timing/Priority.cs.meta new file mode 100644 index 0000000..222efc4 --- /dev/null +++ b/Assets/Scripts/Model/Timing/Priority.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac0e0888a16eaa84e89cb2d480b314d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Timing/PriorityWindow.cs b/Assets/Scripts/Model/Timing/PriorityWindow.cs new file mode 100644 index 0000000..930e8c9 --- /dev/null +++ b/Assets/Scripts/Model/Timing/PriorityWindow.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; + +namespace model.timing { + + public abstract class PriorityWindow { + public event AsyncAction Opened; + public event AsyncAction Closed; + public event Action PriorityGiven = delegate { }; + public string Name { get; } + + public PriorityWindow(string name) { + Name = name; + } + + async public Task Open() { + await Opened?.Invoke(); + await Proceed(); + await Closed?.Invoke(); + } + + protected abstract Task Proceed(); + protected void OnPriorityGiven(Priority priority) { + PriorityGiven(priority); + } + } +} diff --git a/Assets/Scripts/Model/Timing/PriorityWindow.cs.meta b/Assets/Scripts/Model/Timing/PriorityWindow.cs.meta new file mode 100644 index 0000000..2ec0da7 --- /dev/null +++ b/Assets/Scripts/Model/Timing/PriorityWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7613f748b078ee146bf95b58bf7a9524 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Timing/ReactionWindow.cs b/Assets/Scripts/Model/Timing/ReactionWindow.cs new file mode 100644 index 0000000..5f8da1d --- /dev/null +++ b/Assets/Scripts/Model/Timing/ReactionWindow.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using model.abilities; +using model.play; +using model.player; + +namespace model.timing { + + // CR: 9.2.8 + public class ReactionWindow : PriorityWindow { + private IPilot acting; + private IPilot reacting; + private IList pending; + private Dictionary mandatory = new(); + private Dictionary optional = new(); + + public ReactionWindow(IList pending) : base("Reaction window") { + this.pending = pending; + } + + internal void Offer(IPilot pilot, Ability ability) { + if (ability.Mandatory) { + mandatory[pilot] = ability; + } else { + optional[pilot] = ability; + } + } + + // CR: 9.2.8.b + async override protected Task Proceed() { + await AwaitPass(acting); + await AwaitPass(reacting); + } + + async private Task AwaitPass(IPilot pilot) { + var priority = new Priority(pilot, canPass: true); // CR: 9.2.4.b + PriorityGiven(priority); + while (!priority.Passed) // CR: 9.2.4.c + { + await priority.Choose(); // CR: 9.2.7.f + } + return priority; + } + } +} diff --git a/Assets/Scripts/Model/Timing/ReactionWindow.cs.meta b/Assets/Scripts/Model/Timing/ReactionWindow.cs.meta new file mode 100644 index 0000000..abb0492 --- /dev/null +++ b/Assets/Scripts/Model/Timing/ReactionWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5fe2babadbe05c4caab1441030212b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs index e35d89e..2ccd2b1 100644 --- a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs +++ b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs @@ -1,120 +1,69 @@ using System.Collections.Generic; using System.Threading.Tasks; using model.play; +using model.player; -namespace model.timing.runner -{ - public class RunnerTurn : ITurn - { - private Game game; - public bool Active { get; private set; } = false; - ClickPool ITurn.Clicks => game.runner.clicks; - Side ITurn.Side => Side.RUNNER; +namespace model.timing.runner { + public class RunnerTurn : ITurn { + private Runner runner; + private Timing timing; + override public ClickPool Clicks => runner.clicks; + override public Side Side => Side.RUNNER; + override public IPilot Owner => runner.pilot; private List turnBeginningTriggers = new List(); - public event AsyncAction Started; public event AsyncAction TakingAction; public event AsyncAction ActionTaken; - public RunnerTurn(Game game) - { - this.game = game; + public RunnerTurn(Runner runner, Timing timing, int turnNumber): base("Runner turn " + turnNumber) { + } - async Task ITurn.Start() - { - Active = true; - await Started?.Invoke(this); + override async protected Task Proceed() { await ActionPhase(); await DiscardPhase(); - Active = false; } - async private Task ActionPhase() - { - game.runner.clicks.Replenish(); // CR: 5.7.1.a - var rez = OpenRezWindow(); // CR: 5.7.1.b - var paid = OpenPaidWindow(); // CR: 5.7.1.b - await rez; - await paid; + async private Task ActionPhase() { + runner.clicks.Replenish(); // CR: 5.7.1.a + await timing.DefinePaidWindow(rezzing: true, scoring: false).Open(); // CR: 5.7.1.b RefillRecurringCredits(); // CR: 5.7.1.c - await TriggerTurnBeginning(); // CR: 5.7.1.d - var rez2 = OpenRezWindow(); // CR: 5.7.1.e - var paid2 = OpenPaidWindow(); // CR: 5.7.1.e - await rez2; - await paid2; - while (game.runner.clicks.Remaining > 0) // CR: 5.7.1.g + await Begins.Open(); // CR: 5.7.1.d + await timing.DefinePaidWindow(rezzing: true, scoring: false).Open(); // CR: 5.7.1.e + while (runner.clicks.Remaining > 0) // CR: 5.7.1.g { await TakeAction(); // CR: 5.7.1.f } - await game.Checkpoint(); // CR: 5.7.1.g - } - - async private Task OpenPaidWindow() - { - await game.OpenPaidWindow( - acting: game.runner.paidWindow, - reacting: game.corp.paidWindow - ); + await timing.Checkpoint(); // CR: 5.7.1.g } - async private Task OpenRezWindow() - { - await game.corp.Rezzing.Window.Open(); - } - - private void RefillRecurringCredits() - { - - } + private void RefillRecurringCredits() { - async private Task TriggerTurnBeginning() - { - if (turnBeginningTriggers.Count > 0) - { - await new SimultaneousTriggers(turnBeginningTriggers.Copy()).AllTriggered(game.runner.pilot); - } } - async private Task TakeAction() - { + async private Task TakeAction() { var actionTaking = game.runner.Acting.TakeAction(); TakingAction?.Invoke(this); var action = await actionTaking; ActionTaken?.Invoke(this, action); - var rez = OpenRezWindow(); - var paid = OpenPaidWindow(); - await rez; - await paid; + await timing.DefinePaidWindow(rezzing: true, scoring: false).Open(); } - async private Task DiscardPhase() - { + async private Task DiscardPhase() { await Discard(); // CR: 5.7.2.a - var rez = OpenRezWindow(); // CR: 5.7.2.b - var paid = OpenPaidWindow(); // CR: 5.7.2.b - await rez; - await paid; - game.runner.clicks.Reset(); // CR: 5.7.2.c + await timing.DefinePaidWindow(rezzing: true, scoring: false).Open(); // CR: 5.7.2.b + game.Runner.clicks.Reset(); // CR: 5.7.2.c TriggerTurnEnding(); // CR: 5.7.2.d - await game.Checkpoint(); // CR: 5.7.2.e + await timing.Checkpoint(); // CR: 5.7.2.e } - async private Task Discard() - { + async private Task Discard() { var grip = game.runner.zones.grip; - while (grip.zone.Count > 5) - { + while (grip.zone.Count > 5) { await grip.Discard(); } } - private void TriggerTurnEnding() - { - } - - public void WhenBegins(IEffect effect) - { - turnBeginningTriggers.Add(effect); + private void TriggerTurnEnding() { } } } diff --git a/Assets/Scripts/Model/Timing/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs new file mode 100644 index 0000000..6edd0fc --- /dev/null +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using model.player; +using model.timing.corp; +using model.timing.runner; + +namespace model.timing { + public class Timing { + private Game game; + private Checkpoint nextCheckpoint; + private bool checking = false; + public event Action CorpTurnDefined = delegate { }; + public event Action RunnerTurnDefined = delegate { }; + public event Action PaidWindowDefined = delegate { }; + public event Action CurrentTurnQueued = delegate { }; + public event Action NextTurnPredicted = delegate { }; + public event Action Finished = delegate { }; + private bool gameEnded = false; + private Queue turns = new Queue(); + private IPilot active; + private IPilot inactive; + private int corpTurns = 0; + private int runnerTurns = 0; + + public Timing(Game game) { + this.game = game; + this.nextCheckpoint = new Checkpoint(game); + game.corp.zones.rd.Decked += DeckCorp; + game.runner.zones.score.StolenEnough += StealEnough; + } + + async public Task StartTurns() { + try { + while (!gameEnded) { + var corpTurn = new CorpTurn(game.corp, this); + var runnerTurn = new RunnerTurn(game.runner, this, runnerTurns++); + CorpTurnDefined(corpTurn); + RunnerTurnDefined(runnerTurn); + turns.Enqueue(corpTurn); + turns.Enqueue(runnerTurn); + await StartNextTurn(); + await StartNextTurn(); + } + } + catch (Exception e) { + if (gameEnded) { + UnityEngine.Debug.Log("The game is over! " + e.Message); + } else { + throw new Exception("Failed a turn", e); + } + } + } + + private async Task StartNextTurn() { + var currentTurn = turns.Dequeue(); + CurrentTurnQueued(currentTurn); + NextTurnPredicted(turns.Peek()); + UpdateActivePlayer(currentTurn.Owner); + await currentTurn.Initiate(); + } + + private void UpdateActivePlayer(IPilot active) { + this.active = active; + if (active == game.corp.pilot) { + inactive = game.runner.pilot; + } else { + inactive = game.corp.pilot; + } + } + + async public Task OpenActionWindow() { + var window = new ActionWindow(); + await window.Open(); + } + + public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) { + var window = new PaidWindow( + rezzing, + scoring, + acting: active, + reacting: inactive + ); + PaidWindowDefined(window); + return window; + } + + private void DeckCorp(Corp corp) { + Finish(new GameFinish( + winner: "The Runner", + loser: "The Corp", + reason: "Corp R&D is empty" + )); + } + + private void StealEnough() { + Finish(new GameFinish( + winner: "The Runner", + loser: "The Corp", + reason: "Runner has stolen enough" + )); + } + + private void Finish(GameFinish finish) { + gameEnded = true; + Finished(finish); + throw new Exception("Game over, " + finish.reason); + } + + async public Task Checkpoint() { + if (checking) { + throw new Exception("Checkpoints cannot be nested"); + } + checking = true; + await nextCheckpoint.Check(); + checking = false; + nextCheckpoint = new Checkpoint(game); + } + } + + public class GameFinish { + public string winner; + public string loser; + public string reason; + + public GameFinish(string winner, string loser, string reason) { + this.winner = winner; + this.loser = loser; + this.reason = reason; + } + } +} diff --git a/Assets/Scripts/Model/Timing/Timing.cs.meta b/Assets/Scripts/Model/Timing/Timing.cs.meta new file mode 100644 index 0000000..04a167d --- /dev/null +++ b/Assets/Scripts/Model/Timing/Timing.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3837409f70856ce42bc152902f19ee48 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Zones/Corp/Headquarters.cs b/Assets/Scripts/Model/Zones/Corp/Headquarters.cs index a73b569..4932557 100644 --- a/Assets/Scripts/Model/Zones/Corp/Headquarters.cs +++ b/Assets/Scripts/Model/Zones/Corp/Headquarters.cs @@ -12,7 +12,7 @@ public class Headquarters : IServer { public Zone Zone { get; } = new Zone("HQ", false); public IceColumn Ice { get; } - public event Action DiscardingOne = delegate {}; + public event AsyncAction DiscardingOne; public event Action DiscardedOne = delegate {}; private TaskCompletionSource discarding; private Shuffling shuffling; @@ -26,13 +26,13 @@ public Headquarters(Shuffling shuffling, CreditPool credits) async public Task Discard() { discarding = new TaskCompletionSource(); - DiscardingOne(); + await DiscardingOne?.Invoke(); await discarding.Task; } - public void Discard(Card card, Archives archives) + async public Task Discard(Card card, Archives archives) { - card.MoveTo(archives.Zone); + await card.MoveTo(archives.Zone); DiscardedOne(card); discarding.SetResult(true); } diff --git a/Assets/Scripts/Model/Zones/Corp/IceColumn.cs b/Assets/Scripts/Model/Zones/Corp/IceColumn.cs index 27b3fb3..51f93c5 100644 --- a/Assets/Scripts/Model/Zones/Corp/IceColumn.cs +++ b/Assets/Scripts/Model/Zones/Corp/IceColumn.cs @@ -14,7 +14,7 @@ public IceColumn(IServer server, CreditPool credits) this.credits = credits; } - void IInstallDestination.Host(Card card) + Task IInstallDestination.Host(Card card) { throw new System.NotImplementedException(); } diff --git a/Assets/Scripts/Model/Zones/Corp/NewRemote.cs b/Assets/Scripts/Model/Zones/Corp/NewRemote.cs index 8338420..529f6df 100644 --- a/Assets/Scripts/Model/Zones/Corp/NewRemote.cs +++ b/Assets/Scripts/Model/Zones/Corp/NewRemote.cs @@ -12,10 +12,10 @@ public NewRemote(Zones zones) this.zones = zones; } - void IInstallDestination.Host(Card card) + async Task IInstallDestination.Host(Card card) { var newRemote = zones.CreateRemote() as IInstallDestination; - newRemote.Host(card); + await newRemote.Host(card); } Task IInstallDestination.PayInstallCost(Card card) diff --git a/Assets/Scripts/Model/Zones/Corp/Remote.cs b/Assets/Scripts/Model/Zones/Corp/Remote.cs index a1f431e..4923f8d 100644 --- a/Assets/Scripts/Model/Zones/Corp/Remote.cs +++ b/Assets/Scripts/Model/Zones/Corp/Remote.cs @@ -26,14 +26,14 @@ public bool IsEmpty() return (Ice.Height == 0) && (Zone.Count == 0); } - void IInstallDestination.Host(Card card) + async Task IInstallDestination.Host(Card card) { Zone .Cards .Select(it => new Trash(it, corp.zones.archives.Zone)) .ToList() - .ForEach(it => it.TrashIt()); - card.MoveTo(Zone); + .ForEach(async it => await it.TrashIt()); + await card.MoveTo(Zone); } // CR: 8.2.5.a diff --git a/Assets/Scripts/Model/Zones/Corp/ResearchAndDevelopment.cs b/Assets/Scripts/Model/Zones/Corp/ResearchAndDevelopment.cs index ddea89a..9f3c375 100644 --- a/Assets/Scripts/Model/Zones/Corp/ResearchAndDevelopment.cs +++ b/Assets/Scripts/Model/Zones/Corp/ResearchAndDevelopment.cs @@ -23,11 +23,11 @@ public ResearchAndDevelopment(Corp corp, Shuffling shuffling) Ice = new IceColumn(this, corp.credits); } - public void AddDeck(Deck deck) + async public Task AddDeck(Deck deck) { foreach (var card in deck.cards) { - card.MoveTo(Zone); + await card.MoveTo(Zone); } Shuffle(); } @@ -40,13 +40,13 @@ public void Shuffle() public bool HasCards() => Zone.Cards.Count > 0; - public void Draw(int cards, Headquarters hq) + async public Task Draw(int cards, Headquarters hq) { for (int i = 0; i < cards; i++) { if (HasCards()) { - Zone.Cards[0].MoveTo(hq.Zone); + await Zone.Cards[0].MoveTo(hq.Zone); } else { diff --git a/Assets/Scripts/Model/Zones/Corp/Root.cs b/Assets/Scripts/Model/Zones/Corp/Root.cs index c7e4355..e70cb3f 100644 --- a/Assets/Scripts/Model/Zones/Corp/Root.cs +++ b/Assets/Scripts/Model/Zones/Corp/Root.cs @@ -5,7 +5,7 @@ namespace model.zones.corp { public class Root : IInstallDestination { - void IInstallDestination.Host(Card card) + Task IInstallDestination.Host(Card card) { // if (card.Type.Rezzable) // { diff --git a/Assets/Scripts/Model/Zones/Corp/Zones.cs b/Assets/Scripts/Model/Zones/Corp/Zones.cs index 93ecc35..867383a 100644 --- a/Assets/Scripts/Model/Zones/Corp/Zones.cs +++ b/Assets/Scripts/Model/Zones/Corp/Zones.cs @@ -78,7 +78,7 @@ private void CountCardsInTheStack(Zone stack) async Task IEffect.Resolve() { - rd.Draw(cards, hq); + await rd.Draw(cards, hq); await Task.CompletedTask; } } diff --git a/Assets/Scripts/Model/Zones/IInstallDestination.cs b/Assets/Scripts/Model/Zones/IInstallDestination.cs index de13d86..a137b12 100644 --- a/Assets/Scripts/Model/Zones/IInstallDestination.cs +++ b/Assets/Scripts/Model/Zones/IInstallDestination.cs @@ -1,11 +1,9 @@ using System.Threading.Tasks; using model.cards; -namespace model.zones -{ - public interface IInstallDestination - { - void Host(Card card); +namespace model.zones { + public interface IInstallDestination { + Task Host(Card card); // CR: 8.2.5 Task TrashAlike(Card card); // CR: 8.2.11 diff --git a/Assets/Scripts/Model/Zones/Play.cs b/Assets/Scripts/Model/Zones/Play.cs index a4787b5..e29ebcb 100644 --- a/Assets/Scripts/Model/Zones/Play.cs +++ b/Assets/Scripts/Model/Zones/Play.cs @@ -33,10 +33,10 @@ private void UpdateImpact(IEffect cardActivation, bool impactful) async Task IEffect.Resolve() { card.FlipFaceUp(); - card.MoveTo(playZone); + await card.MoveTo(playZone); await card.Activate(); - card.Deactivate(); - card.MoveTo(bin); + await card.Deactivate(); + await card.MoveTo(bin); } public override string ToString() => "Play(card=" + card + ")"; diff --git a/Assets/Scripts/Model/Zones/Runner/Grip.cs b/Assets/Scripts/Model/Zones/Runner/Grip.cs index aab2761..36a55a6 100644 --- a/Assets/Scripts/Model/Zones/Runner/Grip.cs +++ b/Assets/Scripts/Model/Zones/Runner/Grip.cs @@ -26,9 +26,9 @@ async public Task Discard() await discarded.Task; } - public void Discard(Card card, Heap heap) + async public Task Discard(Card card, Heap heap) { - card.MoveTo(heap.zone); + await card.MoveTo(heap.zone); DiscardedOne(card); discarded.SetResult(true); } diff --git a/Assets/Scripts/Model/Zones/Runner/Rig.cs b/Assets/Scripts/Model/Zones/Runner/Rig.cs index 7fa9b4e..ff739b3 100644 --- a/Assets/Scripts/Model/Zones/Runner/Rig.cs +++ b/Assets/Scripts/Model/Zones/Runner/Rig.cs @@ -19,9 +19,9 @@ public Rig(Runner runner, IPilot pilot) this.pilot = pilot; } - void IInstallDestination.Host(Card card) + async Task IInstallDestination.Host(Card card) { - card.MoveTo(zone); + await card.MoveTo(zone); } async Task IInstallDestination.TrashAlike(Card card) @@ -31,7 +31,7 @@ async Task IInstallDestination.TrashAlike(Card card) { var programs = zone.Cards.Where(it => it.Type is Program); var old = await pilot.ChooseACard().Declare("Which program to trash?", programs); // actually declare zero or many, unless MU is constrained then a minimum - old.MoveTo(runner.zones.heap.zone); // TODO reuse an actual trash abstraction + await old.MoveTo(runner.zones.heap.zone); // TODO reuse an actual trash abstraction } } diff --git a/Assets/Scripts/Model/Zones/Runner/Score.cs b/Assets/Scripts/Model/Zones/Runner/Score.cs index ebba973..3cd1862 100644 --- a/Assets/Scripts/Model/Zones/Runner/Score.cs +++ b/Assets/Scripts/Model/Zones/Runner/Score.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using model.cards; namespace model.zones.runner @@ -9,10 +10,10 @@ public class Score private int score = 0; public event Action StolenEnough = delegate { }; - public void Add(Card card, int agendaPoints) + async public Task Add(Card card, int agendaPoints) { card.FlipFaceUp(); // CR: 1.16.4 - card.MoveTo(zone); + await card.MoveTo(zone); score += agendaPoints; if (score >= 7) // TODO Harmony MedTech { diff --git a/Assets/Scripts/Model/Zones/Runner/Stack.cs b/Assets/Scripts/Model/Zones/Runner/Stack.cs index 27bc780..440e08d 100644 --- a/Assets/Scripts/Model/Zones/Runner/Stack.cs +++ b/Assets/Scripts/Model/Zones/Runner/Stack.cs @@ -1,4 +1,6 @@ -namespace model.zones.runner +using System.Threading.Tasks; + +namespace model.zones.runner { public class Stack { @@ -10,11 +12,11 @@ public Stack(Shuffling shuffling) this.shuffling = shuffling; } - public void AddDeck(Deck deck) + async public Task AddDeck(Deck deck) { foreach (var card in deck.cards) { - card.MoveTo(zone); + await card.MoveTo(zone); } Shuffle(); } @@ -26,13 +28,13 @@ public void Shuffle() public bool HasCards() => zone.Cards.Count > 0; - public void Draw(int cards, Grip grip) + async public Task Draw(int cards, Grip grip) { for (int i = 0; i < cards; i++) { if (HasCards()) { - zone.Cards[0].MoveTo(grip.zone); + await zone.Cards[0].MoveTo(grip.zone); } } } diff --git a/Assets/Scripts/Model/Zones/Runner/Zones.cs b/Assets/Scripts/Model/Zones/Runner/Zones.cs index febf588..6f7b77c 100644 --- a/Assets/Scripts/Model/Zones/Runner/Zones.cs +++ b/Assets/Scripts/Model/Zones/Runner/Zones.cs @@ -49,8 +49,7 @@ public Draw(int cards, Stack stack, Grip grip) async Task IEffect.Resolve() { - stack.Draw(cards, grip); - await Task.CompletedTask; + await stack.Draw(cards, grip); } } diff --git a/Assets/Scripts/View/GUI/Brackets.meta b/Assets/Scripts/View/GUI/Brackets.meta new file mode 100644 index 0000000..58dd747 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b85d29d05b181614ea70167cf01f14bb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs new file mode 100644 index 0000000..ff692bc --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs @@ -0,0 +1,48 @@ +using model.play; +using model.timing; +using System.Threading.Tasks; + +namespace view.gui.brackets +{ + public class ActionBracket + { + private readonly ITurn turn; + private readonly int actionNumber; + private readonly Bracket bracket; + + public ActionBracket(ITurn turn, int actionNumber, Bracket bracket) + { + this.turn = turn; + this.actionNumber = actionNumber; + this.bracket = bracket; + turn.TakingAction += UpdateActivation; + turn.ActionTaken += UpdateEffect; + } + + async private Task UpdateActivation(ITurn turn) + { + if (IsPresent()) + { + bracket.Open(); + } + await Task.CompletedTask; + } + + private bool IsPresent() + { + return turn.Clicks.Spent == actionNumber; + } + + private bool IsPast() + { + return turn.Clicks.Spent >= actionNumber; + } + + async private Task UpdateEffect(ITurn turn, Ability action) + { + bracket.Collapse(); + // action.effect.Graphics; + await Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs.meta b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs.meta new file mode 100644 index 0000000..b24deca --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 74c1b4483aebac9438462adc385cc481 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/Brackets/Bracket.cs b/Assets/Scripts/View/GUI/Brackets/Bracket.cs new file mode 100644 index 0000000..3262449 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/Bracket.cs @@ -0,0 +1,100 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace view.gui.brackets +{ + public class Bracket + { + private static Font FONT = Resources.Load("Fonts/cyberdyne"); + private static float EDGE_WIDTH_RATIO = 0.05f; + + private readonly GameObject container; + private GameObject opening; + private GameObject content; + private GameObject closing; + private float margin; + + public Bracket(string name, GameObject container) : this(name, container, siblingIndex: 0, margin: 0.00f) { } + + private Bracket(string name, GameObject container, int siblingIndex, float margin) + { + this.container = container; + this.margin = margin; + opening = RenderOpening(siblingIndex, name); + content = RenderContent(siblingIndex + 1); + closing = RenderClosing(siblingIndex + 2); + Collapse(); + } + + private GameObject RenderOpening(int siblingIndex, string name) + { + var gameObject = new GameObject("Opening").AttachTo(container); + gameObject.transform.SetSiblingIndex(siblingIndex); + var image = gameObject.AddComponent(); + image.color = Color.magenta; + var rect = gameObject.GetComponent(); + rect.anchorMin = new Vector2(0.00f, margin); + rect.anchorMax = new Vector2(EDGE_WIDTH_RATIO, 1.00f - margin); + rect.offsetMin = Vector2.zero; + rect.offsetMax = Vector2.zero; + var label = new GameObject("Label").AttachTo(gameObject); + var text = label.AddComponent(); + text.font = FONT; + text.supportRichText = false; + text.alignByGeometry = true; + text.alignment = TextAnchor.MiddleCenter; + text.verticalOverflow = VerticalWrapMode.Overflow; + text.horizontalOverflow = HorizontalWrapMode.Overflow; + text.raycastTarget = false; + text.maskable = false; + text.text = name; + label.GetComponent().Expand(); + label.transform.rotation *= Quaternion.Euler(0.0f, 0.0f, 90.0f); + return gameObject; + } + + private GameObject RenderContent(int siblingIndex) + { + var gameObject = new GameObject("Content").AttachTo(container); + gameObject.transform.SetSiblingIndex(siblingIndex); + var image = gameObject.AddComponent(); + image.color = Color.white - new Color(0, 0, 0, 0.50f); + var rect = gameObject.GetComponent(); + rect.anchorMin = new Vector2(0.00f, margin); + rect.anchorMax = new Vector2(0.30f, 1.00f - margin); + rect.offsetMin = Vector2.zero; + rect.offsetMax = Vector2.zero; + return gameObject; + } + + private GameObject RenderClosing(int siblingIndex) + { + var gameObject = new GameObject("Closing").AttachTo(container); + gameObject.transform.SetSiblingIndex(siblingIndex); + var image = gameObject.AddComponent(); + image.color = Color.cyan; + var rect = gameObject.GetComponent(); + rect.anchorMin = new Vector2(0.00f, margin); + rect.anchorMax = new Vector2(EDGE_WIDTH_RATIO, 1.00f - margin); + rect.offsetMin = Vector2.zero; + rect.offsetMax = Vector2.zero; + return gameObject; + } + + public Bracket Nest(string name) + { + var nestedIndex = closing.transform.GetSiblingIndex() - 1; + return new Bracket(name, container, nestedIndex, margin + 0.08f); + } + + public void Open() + { + content.SetActive(true); + } + + public void Collapse() + { + content.SetActive(false); + } + } +} diff --git a/Assets/Scripts/View/GUI/Brackets/Bracket.cs.meta b/Assets/Scripts/View/GUI/Brackets/Bracket.cs.meta new file mode 100644 index 0000000..eea7ddb --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/Bracket.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4863ac71791743f4ea9298830c089109 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs b/Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs new file mode 100644 index 0000000..bf91087 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs @@ -0,0 +1,38 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace view.gui.brackets +{ + public class HorizontalLayoutWithAnchoredSize : MonoBehaviour, ILayoutGroup + { + public void LayOutHorizontally() + { + var offset = 0.0f; + foreach (Transform child in gameObject.transform) + { + if (!child.gameObject.activeSelf) + { + continue; + } + if (child.gameObject.TryGetComponent(out var rect)) + { + rect.offsetMin = new Vector2(offset, 0); + rect.offsetMax = new Vector2(offset, 0); + UnityEngine.Debug.Log("Rect width: " + rect.rect.width); + offset += rect.rect.width; + } + } + UnityEngine.Debug.Log("Shifted for max offset of " + offset); + } + + void ILayoutController.SetLayoutHorizontal() + { + LayOutHorizontally(); + } + + void ILayoutController.SetLayoutVertical() + { + LayOutHorizontally(); + } + } +} diff --git a/Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs.meta b/Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs.meta new file mode 100644 index 0000000..62cdd49 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ada83a59a3a8dd4d98836dfa50d977a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs new file mode 100644 index 0000000..b4052b8 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs @@ -0,0 +1,53 @@ +using System.Threading.Tasks; +using model; +using model.timing; +using model.timing.corp; +using UnityEngine; + +namespace view.gui.brackets +{ + class RunnerGameBracket + { + private Bracket bracket; + private Timing timing; + + public RunnerGameBracket(GameObject container, Game game) + { + bracket = new Bracket("Game", container); + container.AddComponent(); + game.Timing.CorpTurnDefined += DisplayCorpTurn; + } + + private void DisplayCorpTurn(CorpTurn turn) + { + var turnContainer = bracket.Nest(turn.Name); + turn.ActionPhaseDefined += DisplayCorpActionPhase; + + } + + private void DisplayCorpActionPhase(CorpActionPhase phase) + { + phase.ActionWindowDefined += DisplayCorpActionWindow; + for (int i = 0; i < turn.Clicks.NextReplenishment; i++) + { + int actionOrder = i + 1; + var actionBracket = turnContainer.Nest("Action " + actionOrder); + new ActionBracket(turn, actionOrder, actionBracket); + } + new TurnBracket(turnContainer, turn); + await Task.CompletedTask; + } + + private void DisplayCorpActionWindow(CorpActionWindow window) + { + + } + + async private Task DisplayRunnerTurn(ITurn turn) + { + var turnContainer = bracket.Nest("Runner turn " + turn.Number); + new TurnBracket(turnContainer, turn); + await Task.CompletedTask; + } + } +} diff --git a/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs.meta b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs.meta new file mode 100644 index 0000000..bfb4578 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07c1b3d73b990434cb2fa1b202fdfc4d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/Brackets/TurnBracket.cs b/Assets/Scripts/View/GUI/Brackets/TurnBracket.cs new file mode 100644 index 0000000..ff209d9 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/TurnBracket.cs @@ -0,0 +1,17 @@ +using model.timing; + +namespace view.gui.brackets +{ + internal class TurnBracket + { + public TurnBracket(Bracket bracket, ITurn turn) + { + for (int i = 0; i < turn.Clicks.NextReplenishment; i++) + { + int actionOrder = i + 1; + var actionBracket = bracket.Nest("Action " + actionOrder); + new ActionBracket(turn, actionOrder, actionBracket); + } + } + } +} diff --git a/Assets/Scripts/View/GUI/Brackets/TurnBracket.cs.meta b/Assets/Scripts/View/GUI/Brackets/TurnBracket.cs.meta new file mode 100644 index 0000000..eff5985 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/TurnBracket.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5c871437f95405459f879515a137a67 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/CorpViewConfig.cs b/Assets/Scripts/View/GUI/CorpViewConfig.cs index 5019a92..2bc4bed 100644 --- a/Assets/Scripts/View/GUI/CorpViewConfig.cs +++ b/Assets/Scripts/View/GUI/CorpViewConfig.cs @@ -1,5 +1,5 @@ using model; -using UnityEngine; +using static view.gui.GameObjectExtensions; namespace view.gui { @@ -8,10 +8,10 @@ public class CorpViewConfig public CorpView Display(Game game, BoardParts parts) { var zones = game.corp.zones; - var credits = GameObject.Find("Corp/Credits"); + var credits = FindOrFail("Corp/Credits"); game.corp.credits.Changed += credits.AddComponent().UpdateBalance; game.corp.credits.Changed += credits.AddComponent().UpdateBalance; - var servers = new ServerRow(GameObject.Find("Servers"), parts, zones); + var servers = new ServerRow(FindOrFail("Servers"), parts, zones); var archivesBox = servers.Box(zones.archives); archivesBox.Printer.Sideways = true; var rd = servers.Box(zones.rd).Printer.PrintCorpFacedown("Top of R&D"); diff --git a/Assets/Scripts/View/GUI/GameFinishPanel.cs b/Assets/Scripts/View/GUI/GameFinishPanel.cs index 54c87c1..019711a 100644 --- a/Assets/Scripts/View/GUI/GameFinishPanel.cs +++ b/Assets/Scripts/View/GUI/GameFinishPanel.cs @@ -1,4 +1,4 @@ -using model; +using model.timing; using UnityEngine; using UnityEngine.UI; diff --git a/Assets/Scripts/View/GUI/GameFlowView.cs b/Assets/Scripts/View/GUI/GameFlowView.cs index 4eaa1fe..886d1ba 100644 --- a/Assets/Scripts/View/GUI/GameFlowView.cs +++ b/Assets/Scripts/View/GUI/GameFlowView.cs @@ -3,6 +3,7 @@ using model.timing; using UnityEngine; using view.gui.timecross; +using static view.gui.GameObjectExtensions; namespace view.gui { @@ -23,8 +24,8 @@ public void Display(GameObject board, Game game) private PaidWindowView WirePaidWindow(PaidWindow window) { - var pass = GameObject.Find("Pass").AddComponent(); - PaidChoice = GameObject.Find("Paid choice").AddComponent(); + var pass = FindOrFail("Pass").AddComponent(); + PaidChoice = FindOrFail("Paid choice").AddComponent(); return new PaidWindowView(window, pass, PaidChoice); } @@ -36,7 +37,7 @@ private GameObject CreateGameFinish(Game game) rectangle.anchorMax = new Vector2(0.70f, 0.70f); rectangle.offsetMin = Vector2.zero; rectangle.offsetMax = Vector2.zero; - game.Finished += view.AddComponent().PopUp; + game.Timing.Finished += view.AddComponent().PopUp; return view; } } diff --git a/Assets/Scripts/View/GUI/GameObjectExtensions.cs b/Assets/Scripts/View/GUI/GameObjectExtensions.cs index c20ef36..eb5eaec 100644 --- a/Assets/Scripts/View/GUI/GameObjectExtensions.cs +++ b/Assets/Scripts/View/GUI/GameObjectExtensions.cs @@ -1,12 +1,24 @@ -using UnityEngine; +using System; +using UnityEngine; namespace view.gui { public static class GameObjectExtensions { - public static void AttachTo(this GameObject child, GameObject parent) + public static GameObject AttachTo(this GameObject child, GameObject parent) { child.transform.SetParent(parent.transform, false); + return child; + } + + public static GameObject FindOrFail(string name) + { + var gameObject = GameObject.Find(name); + if (gameObject == null) + { + throw new SystemException("Cannot find game object named " + name + ". Maybe it's inactive?"); + } + return gameObject; } } -} \ No newline at end of file +} diff --git a/Assets/Scripts/View/GUI/RectTransformExtensions.cs b/Assets/Scripts/View/GUI/RectTransformExtensions.cs new file mode 100644 index 0000000..f91d9f5 --- /dev/null +++ b/Assets/Scripts/View/GUI/RectTransformExtensions.cs @@ -0,0 +1,21 @@ +using UnityEngine; + +namespace view.gui +{ + public static class RectTransformExtensions + { + public static RectTransform AddCleanRectangle(this GameObject gameObject) + { + return gameObject.AddComponent().Expand(); + } + + public static RectTransform Expand(this RectTransform rect) + { + rect.anchorMin = Vector2.zero; + rect.anchorMax = Vector2.one; + rect.offsetMin = Vector2.zero; + rect.offsetMax = Vector2.zero; + return rect; + } + } +} diff --git a/Assets/Scripts/View/GUI/RectTransformExtensions.cs.meta b/Assets/Scripts/View/GUI/RectTransformExtensions.cs.meta new file mode 100644 index 0000000..d664f08 --- /dev/null +++ b/Assets/Scripts/View/GUI/RectTransformExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: baee744988816e148b3c46458f7ed243 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/RigGrid.cs b/Assets/Scripts/View/GUI/RigGrid.cs index 0e36945..236ce83 100644 --- a/Assets/Scripts/View/GUI/RigGrid.cs +++ b/Assets/Scripts/View/GUI/RigGrid.cs @@ -13,7 +13,7 @@ public class RigGrid { private DropZone paidWindowTrigger; public DropZone DropZone { get; private set; } - private Dictionary visuals = new Dictionary(); + private Dictionary visuals = new Dictionary(); private CardPrinter printer; public RigGrid(GameObject gameObject, Runner runner, DropZone paidWindowTrigger, BoardParts parts) @@ -35,11 +35,11 @@ private void DestroyUninstalledCard(Zone zone, Card card) Object.Destroy(visuals[card]); } - private void LinkPaidAbility(PaidWindow window, CardAbility ability) + private void LinkPaidAbility(PaidWindow window, Ability ability) { - visuals[ability.Card] + visuals[ability.source] .AddComponent() - .Represent(new InteractiveAbility(ability.Ability, paidWindowTrigger)); + .Represent(new InteractiveAbility(ability, paidWindowTrigger)); } } } diff --git a/Assets/Scripts/View/GUI/RunnerViewConfig.cs b/Assets/Scripts/View/GUI/RunnerViewConfig.cs index 4f96beb..21774fe 100644 --- a/Assets/Scripts/View/GUI/RunnerViewConfig.cs +++ b/Assets/Scripts/View/GUI/RunnerViewConfig.cs @@ -1,6 +1,6 @@ using controller; using model; -using UnityEngine; +using static view.gui.GameObjectExtensions; namespace view.gui { @@ -9,18 +9,18 @@ public class RunnerViewConfig public void Display(Runner runner, GameFlowView flowView, CorpView corpView, BoardParts parts) { var presentBox = flowView.TimeCross.PresentBox; - RigGrid rigGrid = new RigGrid(GameObject.Find("Rig"), runner, flowView.PaidChoice, parts); - HeapPile heapPile = new HeapPile(GameObject.Find("Heap"), runner, parts); - GripFan gripFan = new GripFan(GameObject.Find("Grip"), runner, presentBox.RunnerActionPhase.AddComponent(), rigGrid.DropZone, heapPile.DropZone, parts); - new StackPile(GameObject.Find("Stack"), runner, gripFan.DropZone, parts); - new ZoneBox(GameObject.Find("Runner/Left hand/Score"), parts).Represent(runner.zones.score.zone); - runner.zones.identity.Added += (zone, identity) => parts.Print(GameObject.Find("Runner/Right hand/Core/Identity")).Print(identity); + RigGrid rigGrid = new RigGrid(FindOrFail("Rig"), runner, flowView.PaidChoice, parts); + HeapPile heapPile = new HeapPile(FindOrFail("Heap"), runner, parts); + GripFan gripFan = new GripFan(FindOrFail("Grip"), runner, presentBox.RunnerActionPhase.AddComponent(), rigGrid.DropZone, heapPile.DropZone, parts); + new StackPile(FindOrFail("Stack"), runner, gripFan.DropZone, parts); + new ZoneBox(FindOrFail("Runner/Left hand/Score"), parts).Represent(runner.zones.score.zone); + runner.zones.identity.Added += (zone, identity) => parts.Print(FindOrFail("Runner/Right hand/Core/Identity")).Print(identity); new RunInitiation( - gameObject: GameObject.Find("Run"), + gameObject: FindOrFail("Run"), serverRow: corpView.serverRow, runner: runner ); - var credits = GameObject.Find("Runner/Credits"); + var credits = FindOrFail("Runner/Credits"); presentBox .BankCredit .AddComponent() diff --git a/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs b/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs index bbd6146..201b1b3 100644 --- a/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs +++ b/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs @@ -2,6 +2,7 @@ using UnityEngine.UI; using model; using model.timing; +using static view.gui.GameObjectExtensions; namespace view.gui.timecross { @@ -11,14 +12,9 @@ public class DayNightCycle private Color midnight = new Color(23, 17, 44, 255) / 255; private Sprite dayCity = Resources.Load("Images/Background/photo-of-cityscape-on-a-gloomy-day-2137195"); private Sprite nightCity = Resources.Load("Images/Background/high-rise-photography-of-city-2039630"); - private Image background = GameObject.Find("Board").GetComponent(); + private Image background = FindOrFail("Board").GetComponent(); - public void Wire(Game game) - { - game.CurrentTurn += UpdateBackground; - } - - void UpdateBackground(object sender, ITurn turn) + void UpdateBackground(ITurn turn) { switch (turn.Side) { diff --git a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs b/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs deleted file mode 100644 index 07b69f0..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs +++ /dev/null @@ -1,126 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using System.Collections.Generic; -using model; -using model.timing; -using System; - -namespace view.gui.timecross -{ - public class FutureTrack : MonoBehaviour - { - public FutureTurn CurrentTurn { get; private set; } - - void Awake() - { - var horizontal = gameObject.AddComponent(); - horizontal.childAlignment = TextAnchor.MiddleLeft; - horizontal.childControlWidth = true; - horizontal.childControlHeight = true; - horizontal.childForceExpandWidth = false; - horizontal.childForceExpandHeight = false; - } - - public void Wire(Game game, DayNightCycle dayNight) - { - CurrentTurn = new GameObject("Current turn").AddComponent(); - CurrentTurn.gameObject.AttachTo(gameObject); - game.CurrentTurn += CurrentTurn.DisplayCurrent; - CurrentTurn.dayNight = dayNight; - var nextTurn = new GameObject("Next turn").AddComponent(); - nextTurn.gameObject.AttachTo(gameObject); - game.NextTurn += nextTurn.DisplayNext; - nextTurn.dayNight = dayNight; - } - } - - public class FutureTurn : MonoBehaviour - { - private HorizontalLayoutGroup horizontal; - private List renderedClicks = new List(); - private Image background; - public DayNightCycle dayNight { private get; set; } - private ClickPool monitoredClicks; - - void Awake() - { - horizontal = gameObject.AddComponent(); - horizontal.childAlignment = TextAnchor.MiddleLeft; - horizontal.childControlWidth = true; - horizontal.childControlHeight = true; - horizontal.childForceExpandWidth = false; - horizontal.childForceExpandHeight = true; - background = gameObject.AddComponent(); - } - - internal void DisplayCurrent(object sender, ITurn turn) - { - dayNight.Paint(background, turn.Side); - TrackClicks(turn, UpdateRemainingClicks); - } - - internal void DisplayNext(object sender, ITurn turn) - { - dayNight.Paint(background, turn.Side); - TrackClicks(turn, UpdateNextClicks); - } - - private void TrackClicks(ITurn turn, Action update) - { - if (monitoredClicks != null) - { - monitoredClicks.Changed -= update; - } - monitoredClicks = turn.Clicks; - monitoredClicks.Changed += update; - update(monitoredClicks); - } - - void UpdateRemainingClicks(ClickPool clicks) - { - UpdateClicks(clicks.Remaining); - } - - void UpdateNextClicks(ClickPool clicks) - { - UpdateClicks(clicks.NextReplenishment); - } - - public void UpdateClicks(int desiredClicks) - { - AddMissing(desiredClicks); - RemoveExtra(desiredClicks); - } - - private void AddMissing(int desired) - { - while (renderedClicks.Count < desired) - { - Render(); - } - } - - private void RemoveExtra(int desired) - { - var extra = renderedClicks.Count - desired; - if (extra > 0) - { - foreach (var click in renderedClicks.GetRange(0, extra)) - { - Destroy(click); - renderedClicks.Remove(click); - } - } - } - - private void Render() - { - var click = ClickBox.RenderClickBox(gameObject); - horizontal.CalculateLayoutInputHorizontal(); - horizontal.CalculateLayoutInputVertical(); - horizontal.SetLayoutHorizontal(); - horizontal.SetLayoutVertical(); - renderedClicks.Add(click); - } - } -} diff --git a/Assets/Scripts/View/GUI/TimeCross/PastTrack.cs b/Assets/Scripts/View/GUI/TimeCross/PastTrack.cs deleted file mode 100644 index d0c163f..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/PastTrack.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using model; -using model.play; -using model.timing; -using UnityEngine; -using UnityEngine.UI; - -namespace view.gui.timecross -{ - public class PastTrack : MonoBehaviour - { - private Sprite clickSprite; - private HorizontalLayoutGroup horizontal; - private List renderedClicks = new List(); - - public DayNightCycle DayNight { private get; set; } - - void Awake() - { - clickSprite = Resources.LoadAll("Images/UI/symbols").Where(r => r.name == "symbols_click").First(); - horizontal = gameObject.AddComponent(); - horizontal.childAlignment = TextAnchor.MiddleRight; - horizontal.childControlWidth = false; - horizontal.childControlHeight = true; - horizontal.childForceExpandWidth = false; - horizontal.childForceExpandHeight = true; - } - - internal void Wire(Game game) - { - game.corp.turn.ActionTaken += RenderCompleteCorpAction; - game.runner.turn.ActionTaken += RenderCompleteRunnerAction; - } - - async private Task RenderCompleteCorpAction(ITurn turn, Ability ability) - { - var envelope = new GameObject("Corp envelope"); - var background = envelope.AddComponent(); - DayNight.Paint(background, Side.CORP); - envelope.AttachTo(gameObject); - RenderAction(ability, envelope); - Expand(envelope); - await Task.CompletedTask; - } - - private void RenderAction(Ability ability, GameObject parent) - { - var pastAction = new GameObject("Past action " + ability); - var rect = pastAction.AddComponent(); - rect.anchorMin = new Vector2(0.1f, 0.1f); - rect.anchorMax = new Vector2(0.9f, 0.9f); - rect.offsetMin = Vector2.zero; - rect.offsetMax = Vector2.zero; - foreach (var asset in ability.effect.Graphics) - { - var image = pastAction.AddComponent(); - image.sprite = Resources.Load(asset); - image.preserveAspect = true; - } - pastAction.layer = 5; - pastAction.AttachTo(parent); - } - - async private Task RenderCompleteRunnerAction(ITurn turn, Ability ability) - { - var envelope = new GameObject("Runner envelope"); - var background = envelope.AddComponent(); - DayNight.Paint(background, Side.RUNNER); - envelope.AttachTo(gameObject); - RenderAction(ability, envelope); - Expand(envelope); - await Task.CompletedTask; - } - - - private void Expand(GameObject gameObject) - { - var aspect = gameObject.AddComponent(); - aspect.aspectRatio = 1; - aspect.aspectMode = AspectRatioFitter.AspectMode.HeightControlsWidth; - horizontal.CalculateLayoutInputHorizontal(); - horizontal.CalculateLayoutInputVertical(); - horizontal.SetLayoutHorizontal(); - horizontal.SetLayoutVertical(); - } - } -} diff --git a/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs b/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs deleted file mode 100644 index 08e4988..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Threading.Tasks; -using model; -using model.cards; -using model.play; -using model.timing; -using UnityEngine; -using UnityEngine.UI; - -namespace view.gui.timecross -{ - public class PresentBox : MonoBehaviour - { - public GameObject RunnerActionPhase { get; private set; } - private GameObject corpActionPhase; - public GameObject BankCredit { get; private set; } - private GameObject discardPhase; - private Image discardBackground; - private Game game; - private DayNightCycle dayNight; - private FutureTrack future; - - internal void Wire(Game game, DayNightCycle dayNight, FutureTrack future) - { - this.game = game; - this.dayNight = dayNight; - this.future = future; - WireRunnerActionPhase(game); - WireCorpActionPhase(game); - WireDiscardPhase(game); - } - - private void WireRunnerActionPhase(Game game) - { - RunnerActionPhase = GameObject.Find("Runner action phase"); - var background = RunnerActionPhase.GetComponent(); - dayNight.Paint(background, Side.RUNNER); - BankCredit = GameObject.Find("Bank/Credit"); - SetRunnerActions(false); - game.runner.turn.TakingAction += BeginRunnerAction; - game.runner.turn.ActionTaken += EndRunnerAction; - } - - private void SetRunnerActions(bool takingAction) - { - RunnerActionPhase.gameObject.SetActive(takingAction); - BankCredit.SetActive(takingAction); - } - - private void WireCorpActionPhase(Game game) - { - corpActionPhase = GameObject.Find("Corp action phase"); - var background = corpActionPhase.GetComponent(); - dayNight.Paint(background, Side.CORP); - corpActionPhase.SetActive(false); - game.corp.turn.TakingAction += BeginCorpAction; - game.corp.turn.ActionTaken += EndCorpAction; - } - - private void WireDiscardPhase(Game game) - { - discardPhase = GameObject.Find("Discard phase"); - discardBackground = discardPhase.GetComponent(); - discardPhase.SetActive(false); - game.runner.zones.grip.DiscardingOne += RenderRunnerDiscarding; - game.runner.zones.grip.DiscardedOne += RenderRunnerNotDiscardingAnymore; - game.corp.zones.hq.DiscardingOne += RenderCorpDiscarding; - game.corp.zones.hq.DiscardedOne += RenderCorpNotDiscardingAnymore; - } - - async private Task BeginRunnerAction(ITurn turn) - { - SetRunnerActions(true); - future.CurrentTurn.UpdateClicks(game.runner.clicks.Remaining - 1); - await Task.CompletedTask; - } - - async private Task EndRunnerAction(ITurn turn, Ability action) - { - SetRunnerActions(false); - await Task.CompletedTask; - } - - async private Task BeginCorpAction(ITurn turn) - { - corpActionPhase.SetActive(true); - future.CurrentTurn.UpdateClicks(game.corp.clicks.Remaining - 1); - await Task.CompletedTask; - } - - async private Task EndCorpAction(ITurn turn, Ability action) - { - corpActionPhase.SetActive(false); - await Task.CompletedTask; - } - - private void RenderRunnerDiscarding() - { - discardPhase.SetActive(true); - dayNight.Paint(discardBackground, Side.RUNNER); - } - - private void RenderRunnerNotDiscardingAnymore(Card discarded) - { - discardPhase.SetActive(false); - } - - private void RenderCorpDiscarding() - { - discardPhase.SetActive(true); - dayNight.Paint(discardBackground, Side.CORP); - } - - private void RenderCorpNotDiscardingAnymore(Card discarded) - { - discardPhase.SetActive(false); - } - } -} diff --git a/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs.meta b/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs.meta deleted file mode 100644 index 2633a79..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 373d6f709df861f4eac6ef631fdfd020 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs b/Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs deleted file mode 100644 index 648c284..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs +++ /dev/null @@ -1,13 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using System.Collections.Generic; -using model; - -namespace view.gui.timecross -{ - public class PresentDecision : MonoBehaviour - { - public GameObject costSink; - public GameObject targets; - } -} diff --git a/Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs.meta b/Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs.meta deleted file mode 100644 index 64022ad..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d4c869b5ec15fb84cb0a2a906a9015c2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs b/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs deleted file mode 100644 index d0d7bd4..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs +++ /dev/null @@ -1,21 +0,0 @@ -using UnityEngine; -using model; - -namespace view.gui.timecross -{ - public class TimeCross - { - public PresentBox PresentBox { get; private set; } - - public TimeCross(Game game, DayNightCycle dayNight) - { - var pastTrack = GameObject.Find("Past").AddComponent(); - pastTrack.DayNight = dayNight; - pastTrack.Wire(game); - var futureTrack = GameObject.Find("Future").AddComponent(); - futureTrack.Wire(game, dayNight); - PresentBox = GameObject.Find("Present").AddComponent(); - PresentBox.Wire(game, dayNight, futureTrack); - } - } -} diff --git a/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs.meta b/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs.meta deleted file mode 100644 index 7ff08ad..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2d2f0f5e8b55ccc408eb7c019f58f7a0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/View/Log/GameFlowLog.cs b/Assets/Scripts/View/Log/GameFlowLog.cs index 8d2433c..44d61bc 100644 --- a/Assets/Scripts/View/Log/GameFlowLog.cs +++ b/Assets/Scripts/View/Log/GameFlowLog.cs @@ -17,13 +17,13 @@ public void Display(Game game) game.runner.paidWindow.Closed += LogClosed; var runnerTurn = game.runner.turn; var corpTurn = game.corp.turn; - corpTurn.Started += TurnStarted; + corpTurn.Opened += TurnStarted; corpTurn.TakingAction += (turn) => LogAsync("corp taking action"); corpTurn.ActionTaken += (turn, action) => LogAsync("corp took action"); game.corp.Rezzing.Window.Opened += (window, rezzables) => LogAsync("rez window opened, up to " + rezzables.Count + " could be rezzed"); game.corp.Rezzing.Window.Closed += (window) => Log("rez window closed"); - game.corp.zones.hq.DiscardingOne += () => Log("discarding"); - runnerTurn.Started += TurnStarted; + game.corp.zones.hq.DiscardingOne += async () => await Log("discarding"); + runnerTurn.Opened += TurnStarted; runnerTurn.TakingAction += (turn) => LogAsync("runner taking action"); runnerTurn.ActionTaken += (turn, action) => LogAsync("runner took action"); game.runner.zones.grip.DiscardingOne += () => Log("discarding"); @@ -35,9 +35,10 @@ async private Task LogAsync(string message) await Task.CompletedTask; } - private void Log(string message) + private Task Log(string message) { Debug.Log(currentStep + message); + return Task.CompletedTask; } private void LogOpened(PaidWindow window) @@ -52,8 +53,7 @@ private void LogClosed(PaidWindow window) async private Task TurnStarted(ITurn turn) { - Log("turn " + turn + " beginning"); - await Task.CompletedTask; + await Log("turn " + turn + " beginning"); } } } diff --git a/Assets/Tests/Mocks/PassiveCorp.cs b/Assets/Tests/Mocks/PassiveCorp.cs index f0b1ce0..bcbcb6e 100644 --- a/Assets/Tests/Mocks/PassiveCorp.cs +++ b/Assets/Tests/Mocks/PassiveCorp.cs @@ -22,7 +22,7 @@ async public Task SkipTurn() await clickForCredit.Trigger(); SkipPaidWindow(); } - DiscardRandomCards(); + await DiscardRandomCards(); SkipPaidWindow(); } @@ -32,10 +32,10 @@ private void SkipPaidWindow() game.runner.paidWindow.Pass(); } - public void DiscardRandomCards() + async public Task DiscardRandomCards() { var hq = game.corp.zones.hq; - hq.Discard(hq.Random(), game.corp.zones.archives); + await hq.Discard(hq.Random(), game.corp.zones.archives); } } } diff --git a/Assets/Tests/Observers/PaidAbilityObserver.cs b/Assets/Tests/Observers/PaidAbilityObserver.cs deleted file mode 100644 index 07ee498..0000000 --- a/Assets/Tests/Observers/PaidAbilityObserver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using model.play; -using model.timing; - -namespace tests.observers -{ - class PaidAbilityObserver - { - public CardAbility NewestPaidAbility { get; private set; } - - public PaidAbilityObserver(PaidWindow window) - { - window.Added += NotifyPaidAbilityAvailable; - } - - void NotifyPaidAbilityAvailable(PaidWindow window, CardAbility ability) - { - NewestPaidAbility = ability; - } - } -} diff --git a/Assets/Tests/Observers/PaidAbilityObserver.cs.meta b/Assets/Tests/Observers/PaidAbilityObserver.cs.meta deleted file mode 100644 index f82ced8..0000000 --- a/Assets/Tests/Observers/PaidAbilityObserver.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 36f7ee8086d6425459ce675b6e6d9bf9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Tests/PaidAbilityWindowTest.cs b/Assets/Tests/PaidAbilityWindowTest.cs deleted file mode 100644 index 4ec1416..0000000 --- a/Assets/Tests/PaidAbilityWindowTest.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using model; -using model.cards; -using model.cards.runner; -using NUnit.Framework; -using tests.mocks; -using tests.observers; -using view.log; - - -namespace tests -{ - public class PaidAbilityWindowTest - { - private Game game; - private PassiveCorp passiveCorp; - private FastForwardRunner ffRunner; - private PaidAbilityObserver paidAbilityObserver; - private SportsHopper hopper; - - [SetUp] - public void SetUp() - { - game = MockGames.Unpiloted(); - var runnerCards = new List(); - for (int i = 0; i < 20; i++) - { - runnerCards.Add(new Diesel(game)); - } - var gameFlowLog = new GameFlowLog(); - gameFlowLog.Display(game); - passiveCorp = new PassiveCorp(game); - ffRunner = new FastForwardRunner(game); - paidAbilityObserver = new PaidAbilityObserver(game.runner.paidWindow); - hopper = new SportsHopper(game); - game.Start(Decks.DemoCorp(game), MockGames.MasqueDeck(game, runnerCards)); - } - - [Test, Timeout(1000)] - async public void ShouldPopHopper() - { - await passiveCorp.SkipTurn(); - var zones = game.runner.zones; - hopper.MoveTo(zones.grip.zone); - var gripObserver = new ZoneObserver(zones.grip.zone); - var rigObserver = new ZoneObserver(zones.rig.zone); - var heapObserver = new ZoneObserver(zones.heap.zone); - ffRunner.FastForwardToActionPhase(); - await game.runner.Acting.Install(hopper).Trigger(); // TODO `GenericInstall` refactoring broke this - var popHopper = paidAbilityObserver.NewestPaidAbility; - - await popHopper.Ability.Trigger(); - - Assert.AreEqual(3, gripObserver.TotalAdded); - Assert.AreEqual(hopper, rigObserver.LastRemoved); - Assert.AreEqual(hopper, heapObserver.LastAdded); - } - - [Test, Timeout(1000)] - async public void ShouldUsePaidAbilityOnRunnerTurn() - { - await passiveCorp.SkipTurn(); - hopper.MoveTo(game.runner.zones.grip.zone); - - ffRunner.FastForwardToActionPhase(); - await RunnerAction(); - await RunnerAction(); - await game.runner.Acting.Install(hopper).Trigger(); - var popHopper = paidAbilityObserver.NewestPaidAbility; - PassWindow(); - await RunnerAction(); - PassWindow(); - PassWindow(); - PassWindow(); - PassWindow(); - await CorpAction(); - PassWindow(); - await CorpAction(); - PassWindow(); - await CorpAction(); - PassWindow(); - passiveCorp.DiscardRandomCards(); - PassWindow(); - PassWindow(); - PassWindow(); - await RunnerAction(); - PassWindow(); - await RunnerAction(); - await popHopper.Ability.Trigger(); - PassWindow(); - await RunnerAction(); - await RunnerAction(); - } - - [Test, Timeout(1000)] - async public void ShouldUsePaidAbilityOnCorpTurn() - { - await passiveCorp.SkipTurn(); - hopper.MoveTo(game.runner.zones.grip.zone); - - await RunnerAction(); - await RunnerAction(); - await game.runner.Acting.Install(hopper).Trigger(); - var popHopper = paidAbilityObserver.NewestPaidAbility; - PassWindow(); - await RunnerAction(); - PassWindow(); - PassWindow(); - PassWindow(); - PassWindow(); - await CorpAction(); - PassWindow(); - await CorpAction(); - await popHopper.Ability.Trigger(); - PassWindow(); - await CorpAction(); - passiveCorp.DiscardRandomCards(); - await RunnerAction(); - await RunnerAction(); - await RunnerAction(); - await RunnerAction(); - } - - async private Task RunnerAction() - { - await game.runner.Acting.TakeAction(); - await game.runner.Acting.credit.Trigger(); - ffRunner.SkipPaidWindow(); - } - - async private Task CorpAction() - { - await game.corp.Acting.TakeAction(); - await game.corp.Acting.credit.Trigger(); - } - - private void PassWindow() - { - game.corp.paidWindow.Pass(); - game.runner.paidWindow.Pass(); - } - } -} diff --git a/Assets/Tests/PaidAbilityWindowTest.cs.meta b/Assets/Tests/PaidAbilityWindowTest.cs.meta deleted file mode 100644 index 59cab9a..0000000 --- a/Assets/Tests/PaidAbilityWindowTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 430f2a6f15453d548a12e407c0dbcddc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Tests/RunnerDiscardPhaseTest.cs b/Assets/Tests/RunnerDiscardPhaseTest.cs index d39b0a1..b344b31 100644 --- a/Assets/Tests/RunnerDiscardPhaseTest.cs +++ b/Assets/Tests/RunnerDiscardPhaseTest.cs @@ -38,7 +38,7 @@ async public void ShouldDiscard() for (int i = 0; i < 7; i++) { var card = grip.Find(); - game.runner.zones.grip.Discard(card, heap); + await game.runner.zones.grip.Discard(card, heap); } await passiveCorp.SkipTurn(); diff --git a/Docs/DEVELOPING.md b/Docs/DEVELOPING.md index a8801eb..7b986f1 100644 --- a/Docs/DEVELOPING.md +++ b/Docs/DEVELOPING.md @@ -22,7 +22,7 @@ A pull request is a great place to discuss the change, exchange technical detail ### Unity The project is built upon [Unity](https://unity.com/). The [personal version](https://store.unity.com/download?ref=personal) is perfectly fine. -When you get your Unity Hub downloaded and running, download the [2019.4.17f1](unityhub://2019.4.17f1/667c8606c536). +When you get your Unity Hub downloaded and running, download the [2021.3.4f1](unityhub://2021.3.4f1). The default's should suffice, no need for additional support packages. To open the project, just hit the big `Add` button, select the folder you cloned and click onto the project. @@ -55,6 +55,9 @@ Use any editor you want, e.g.: Visual Studio, Visual Studio Code, Monodevelop. The architecture is still in much flux, as new game mechanics are added. If you have any coding questions or suggestions, please [raise an issue](ISSUES.md). +#### Visual Studio Code +[VS Code has prerequisites for Unity](https://code.visualstudio.com/docs/other/unity#_prerequisites), especially `"omnisharp.useModernNet": false`. + ### Assets Some assets cannot be distributed via this repo, because it's open-source and therefore considered "redistributing" the assets. diff --git a/NuGet/Microsoft.Unity.Analyzers.dll b/NuGet/Microsoft.Unity.Analyzers.dll new file mode 100644 index 0000000..7e0fa46 Binary files /dev/null and b/NuGet/Microsoft.Unity.Analyzers.dll differ diff --git a/Packages/manifest.json b/Packages/manifest.json index d3b521c..0b467fd 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -2,19 +2,19 @@ "dependencies": { "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ads": "3.5.2", - "com.unity.analytics": "3.3.5", - "com.unity.collab-proxy": "1.2.16", - "com.unity.ide.rider": "1.1.4", - "com.unity.ide.vscode": "1.2.3", - "com.unity.multiplayer-hlapi": "1.0.6", - "com.unity.purchasing": "2.2.1", - "com.unity.test-framework": "1.1.19", - "com.unity.textmeshpro": "2.0.1", - "com.unity.timeline": "1.2.6", + "com.unity.ads": "3.7.5", + "com.unity.analytics": "3.6.12", + "com.unity.collab-proxy": "1.15.17", + "com.unity.ide.rider": "3.0.14", + "com.unity.ide.visualstudio": "2.0.15", + "com.unity.ide.vscode": "1.2.5", + "com.unity.purchasing": "4.1.4", + "com.unity.test-framework": "1.1.31", + "com.unity.textmeshpro": "3.0.6", + "com.unity.timeline": "1.6.4", "com.unity.ugui": "1.0.0", "com.unity.vectorgraphics": "2.0.0-preview.12", - "com.unity.xr.legacyinputhelpers": "2.1.6", + "com.unity.xr.legacyinputhelpers": "2.1.9", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index c2f9d84..f15dc5c 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -13,7 +13,7 @@ "dependencies": {} }, "com.unity.ads": { - "version": "3.5.2", + "version": "3.7.5", "depth": 0, "source": "registry", "dependencies": { @@ -22,7 +22,7 @@ "url": "https://packages.unity.com" }, "com.unity.analytics": { - "version": "3.3.5", + "version": "3.6.12", "depth": 0, "source": "registry", "dependencies": { @@ -31,66 +31,91 @@ "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "1.2.16", + "version": "1.15.17", "depth": 0, "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.services.core": "1.0.1" + }, "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { - "version": "1.0.5", + "version": "1.0.6", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "1.1.4", + "version": "3.0.14", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, + "url": "https://packages.unity.com" + }, + "com.unity.ide.visualstudio": { + "version": "2.0.15", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.1" + "com.unity.test-framework": "1.1.9" }, "url": "https://packages.unity.com" }, "com.unity.ide.vscode": { - "version": "1.2.3", + "version": "1.2.5", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.multiplayer-hlapi": { - "version": "1.0.6", + "com.unity.nuget.newtonsoft-json": { + "version": "3.0.2", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, + "com.unity.purchasing": { + "version": "4.1.4", "depth": 0, "source": "registry", "dependencies": { - "nuget.mono-cecil": "0.1.6-preview" + "com.unity.ugui": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.services.core": "1.0.1" }, "url": "https://packages.unity.com" }, - "com.unity.purchasing": { - "version": "2.2.1", - "depth": 0, + "com.unity.services.core": { + "version": "1.4.0", + "depth": 1, "source": "registry", "dependencies": { - "com.unity.ugui": "1.0.0" + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.modules.androidjni": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.19", + "version": "1.1.31", "depth": 0, "source": "registry", "dependencies": { - "com.unity.ext.nunit": "1.0.5", + "com.unity.ext.nunit": "1.0.6", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.textmeshpro": { - "version": "2.0.1", + "version": "3.0.6", "depth": 0, "source": "registry", "dependencies": { @@ -99,10 +124,15 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.2.6", + "version": "1.6.4", "depth": 0, "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, "url": "https://packages.unity.com" }, "com.unity.ugui": { @@ -125,17 +155,13 @@ "url": "https://packages.unity.com" }, "com.unity.xr.legacyinputhelpers": { - "version": "2.1.6", + "version": "2.1.9", "depth": 0, "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "nuget.mono-cecil": { - "version": "0.1.6-preview", - "depth": 1, - "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.xr": "1.0.0" + }, "url": "https://packages.unity.com" }, "com.unity.modules.ai": { @@ -271,6 +297,18 @@ "depth": 0, "source": "builtin", "dependencies": { + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.uielementsnative": "1.0.0" + } + }, + "com.unity.modules.uielementsnative": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" } diff --git a/ProjectSettings/MemorySettings.asset b/ProjectSettings/MemorySettings.asset new file mode 100644 index 0000000..5b5face --- /dev/null +++ b/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index ca9e773..785714b 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -9,10 +9,14 @@ MonoBehaviour: m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 13960, guid: 0000000000000000e000000000000000, type: 0} + m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: + m_EnablePreReleasePackages: 0 + m_EnablePackageDependencies: 0 + m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 oneTimeWarningShown: 0 m_Registries: - m_Id: main @@ -20,19 +24,12 @@ MonoBehaviour: m_Url: https://packages.unity.com m_Scopes: [] m_IsDefault: 1 + m_Capabilities: 7 m_UserSelectedRegistryName: m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: - m_ErrorMessage: - m_Original: - m_Id: - m_Name: - m_Url: - m_Scopes: [] - m_IsDefault: 0 m_Modified: 0 - m_Name: - m_Url: - m_Scopes: - - - m_SelectedScopeIndex: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -834 + m_OriginalInstanceId: -836 + m_LoadAssets: 0 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 88062e3..501a25e 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2019.4.17f1 -m_EditorVersionWithRevision: 2019.4.17f1 (667c8606c536) +m_EditorVersion: 2021.3.4f1 +m_EditorVersionWithRevision: 2021.3.4f1 (cb45f9cae8b7) diff --git a/ProjectSettings/VersionControlSettings.asset b/ProjectSettings/VersionControlSettings.asset new file mode 100644 index 0000000..dca2881 --- /dev/null +++ b/ProjectSettings/VersionControlSettings.asset @@ -0,0 +1,8 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!890905787 &1 +VersionControlSettings: + m_ObjectHideFlags: 0 + m_Mode: Visible Meta Files + m_CollabEditorSettings: + inProgressEnabled: 1 diff --git a/ProjectSettings/boot.config b/ProjectSettings/boot.config new file mode 100644 index 0000000..e69de29 diff --git a/omnisharp.json b/omnisharp.json new file mode 100644 index 0000000..a0b5775 --- /dev/null +++ b/omnisharp.json @@ -0,0 +1,6 @@ +{ + "RoslynExtensionsOptions": { + "EnableAnalyzersSupport": true, + "LocationPaths": ["NuGet"] + } +}