From 37d3765c02056d107aa85d356dbca1d93c0504f0 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Mon, 13 Jun 2022 22:30:10 +0200 Subject: [PATCH 01/14] Bump Unity to 2021.3.4f1 --- .gitignore | 1 + Assets/Resources/BillingMode.json | 1 + Assets/Resources/BillingMode.json.meta | 7 ++ Assets/Resources/Images/UI/card-draw.svg.meta | 1 - Assets/Resources/Images/UI/click.svg.meta | 1 - Assets/Resources/Images/UI/credit.svg.meta | 1 - Assets/Resources/Images/UI/hourglass.svg.meta | 1 - .../Resources/Images/UI/server-rack.svg.meta | 1 - Assets/Resources/Images/UI/symbols.png.meta | 15 ++- Docs/DEVELOPING.md | 5 +- Packages/manifest.json | 22 ++--- Packages/packages-lock.json | 96 +++++++++++++------ ProjectSettings/MemorySettings.asset | 35 +++++++ ProjectSettings/PackageManagerSettings.asset | 23 ++--- ProjectSettings/ProjectVersion.txt | 4 +- ProjectSettings/VersionControlSettings.asset | 8 ++ ProjectSettings/boot.config | 0 17 files changed, 158 insertions(+), 64 deletions(-) create mode 100644 Assets/Resources/BillingMode.json create mode 100644 Assets/Resources/BillingMode.json.meta create mode 100644 ProjectSettings/MemorySettings.asset create mode 100644 ProjectSettings/VersionControlSettings.asset create mode 100644 ProjectSettings/boot.config 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/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/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/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 From 30ba15b7068a6640abe0e9ca53f78acab5c18c6f Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Tue, 14 Jun 2022 00:02:44 +0200 Subject: [PATCH 02/14] Enable Unity warnings in VS Code https://code.visualstudio.com/docs/other/unity#_enabling-unity-warnings https://www.nuget.org/packages/Microsoft.Unity.Analyzers/1.13.0 --- NuGet/Microsoft.Unity.Analyzers.dll | Bin 0 -> 198568 bytes omnisharp.json | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 NuGet/Microsoft.Unity.Analyzers.dll create mode 100644 omnisharp.json diff --git a/NuGet/Microsoft.Unity.Analyzers.dll b/NuGet/Microsoft.Unity.Analyzers.dll new file mode 100644 index 0000000000000000000000000000000000000000..7e0fa464457cd8194713bb46044f069289f5a61a GIT binary patch literal 198568 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 NKY=5A6z;>Y{{tqLG%)}G literal 0 HcmV?d00001 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"] + } +} From 879b83099cbc332c70adc40f937027baa7a6e8da Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Tue, 6 Apr 2021 00:09:02 +0200 Subject: [PATCH 03/14] Display timing structures as nested brackets --- .../Steal.meta => Resources/Fonts.meta} | 2 +- Assets/Resources/Fonts/cyberdyne.ttf | Bin 0 -> 26744 bytes Assets/Resources/Fonts/cyberdyne.ttf.meta | 22 ++++ Assets/Scenes/Runner Game.unity | 123 +++++++++++++++++- Assets/Scripts/Config/GameConfig.cs | 5 +- Assets/Scripts/Model/Game.cs | 8 +- Assets/Scripts/View/GUI/Brackets.meta | 8 ++ .../View/GUI/Brackets/ActionBracket.cs | 51 ++++++++ .../View/GUI/Brackets/ActionBracket.cs.meta | 11 ++ Assets/Scripts/View/GUI/Brackets/Bracket.cs | 98 ++++++++++++++ .../Scripts/View/GUI/Brackets/Bracket.cs.meta | 11 ++ .../HorizontalLayoutWithAnchoredSize.cs | 38 ++++++ .../HorizontalLayoutWithAnchoredSize.cs.meta | 11 ++ .../View/GUI/Brackets/RunnerGameBracket.cs | 39 ++++++ .../GUI/Brackets/RunnerGameBracket.cs.meta | 11 ++ .../Scripts/View/GUI/Brackets/TurnBracket.cs | 17 +++ .../View/GUI/Brackets/TurnBracket.cs.meta | 11 ++ Assets/Scripts/View/GUI/CorpViewConfig.cs | 6 +- Assets/Scripts/View/GUI/GameFlowView.cs | 5 +- .../Scripts/View/GUI/GameObjectExtensions.cs | 18 ++- .../View/GUI/RectTransformExtensions.cs | 21 +++ .../View/GUI/RectTransformExtensions.cs.meta | 11 ++ Assets/Scripts/View/GUI/RunnerViewConfig.cs | 18 +-- .../View/GUI/TimeCross/DayNightCycle.cs | 5 +- .../Scripts/View/GUI/TimeCross/FutureTrack.cs | 4 +- .../Scripts/View/GUI/TimeCross/PresentBox.cs | 9 +- .../Scripts/View/GUI/TimeCross/TimeCross.cs | 8 +- 27 files changed, 530 insertions(+), 41 deletions(-) rename Assets/{Scripts/Model/Choices/Steal.meta => Resources/Fonts.meta} (77%) create mode 100644 Assets/Resources/Fonts/cyberdyne.ttf create mode 100644 Assets/Resources/Fonts/cyberdyne.ttf.meta create mode 100644 Assets/Scripts/View/GUI/Brackets.meta create mode 100644 Assets/Scripts/View/GUI/Brackets/ActionBracket.cs create mode 100644 Assets/Scripts/View/GUI/Brackets/ActionBracket.cs.meta create mode 100644 Assets/Scripts/View/GUI/Brackets/Bracket.cs create mode 100644 Assets/Scripts/View/GUI/Brackets/Bracket.cs.meta create mode 100644 Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs create mode 100644 Assets/Scripts/View/GUI/Brackets/HorizontalLayoutWithAnchoredSize.cs.meta create mode 100644 Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs create mode 100644 Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs.meta create mode 100644 Assets/Scripts/View/GUI/Brackets/TurnBracket.cs create mode 100644 Assets/Scripts/View/GUI/Brackets/TurnBracket.cs.meta create mode 100644 Assets/Scripts/View/GUI/RectTransformExtensions.cs create mode 100644 Assets/Scripts/View/GUI/RectTransformExtensions.cs.meta 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 0000000000000000000000000000000000000000..d6a4d3190469f94362e714e1a5772bafb103772a GIT binary patch literal 26744 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 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/Model/Game.cs b/Assets/Scripts/Model/Game.cs index 56838d2..cb76130 100644 --- a/Assets/Scripts/Model/Game.cs +++ b/Assets/Scripts/Model/Game.cs @@ -13,8 +13,8 @@ public class Game public readonly Runner runner; private readonly Zone playArea; public readonly Checkpoint checkpoint; - public event EventHandler CurrentTurn = delegate { }; - public event EventHandler NextTurn = delegate { }; + public event Action CurrentTurn = delegate { }; + public event Action NextTurn = delegate { }; public event Action Finished = delegate { }; private bool ended = false; private Queue turns = new Queue(); @@ -82,8 +82,8 @@ async private Task StartTurns() private async Task StartNextTurn() { var currentTurn = turns.Dequeue(); - CurrentTurn(this, currentTurn); - NextTurn(this, turns.Peek()); + CurrentTurn(currentTurn); + NextTurn(turns.Peek()); await currentTurn.Start(); } 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..4c9dd61 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs @@ -0,0 +1,51 @@ +using model.play; +using model.timing; +using System.Threading.Tasks; + +namespace view.gui.brackets +{ + public class ActionBracket + { + private readonly ITurn turn; + private readonly int actionOrder; + private readonly Bracket bracket; + + public ActionBracket(ITurn turn, int actionOrder, Bracket bracket) + { + this.turn = turn; + this.actionOrder = actionOrder; + 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 == actionOrder - 1; + } + + private bool IsPast() + { + return turn.Clicks.Spent >= actionOrder; + } + + async private Task UpdateEffect(ITurn turn, Ability action) + { + if (IsPast()) + { + 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..9538493 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/Bracket.cs @@ -0,0 +1,98 @@ +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; + + public Bracket(string name, GameObject container) : this(name, container, siblingIndex: 0) { } + + public Bracket(string name, GameObject container, int siblingIndex) + { + this.container = container; + 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, 0.05f); + rect.anchorMax = new Vector2(EDGE_WIDTH_RATIO, 0.95f); + 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, 0.15f); + rect.anchorMax = new Vector2(0.30f, 0.85f); + 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, 0.05f); + rect.anchorMax = new Vector2(EDGE_WIDTH_RATIO, 0.95f); + 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); + } + + 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..e1c31a1 --- /dev/null +++ b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs @@ -0,0 +1,39 @@ +using model; +using model.timing; +using UnityEngine; + +namespace view.gui.brackets +{ + class RunnerGameBracket + { + private Bracket bracket; + + public RunnerGameBracket(GameObject container, Game game) + { + bracket = new Bracket("Game", container); + container.AddComponent(); + game.CurrentTurn += DisplayTurn; + } + + private void DisplayTurn(ITurn turn) + { + switch (turn.Side) + { + case Side.RUNNER: DisplayRunnerTurn(turn); break; + case Side.CORP: DisplayCorpTurn(turn); break; + } + } + + private void DisplayRunnerTurn(ITurn turn) + { + var turnContainer = bracket.Nest("Runner turn"); + new TurnBracket(turnContainer, turn); + } + + private void DisplayCorpTurn(ITurn turn) + { + var turnContainer = bracket.Nest("Corp turn"); + new TurnBracket(turnContainer, turn); + } + } +} 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/GameFlowView.cs b/Assets/Scripts/View/GUI/GameFlowView.cs index 4eaa1fe..b31d3a6 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); } 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/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..d7e82a8 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,14 @@ 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 index 07b69f0..521fb14 100644 --- a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs +++ b/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs @@ -53,13 +53,13 @@ void Awake() background = gameObject.AddComponent(); } - internal void DisplayCurrent(object sender, ITurn turn) + internal void DisplayCurrent(ITurn turn) { dayNight.Paint(background, turn.Side); TrackClicks(turn, UpdateRemainingClicks); } - internal void DisplayNext(object sender, ITurn turn) + internal void DisplayNext(ITurn turn) { dayNight.Paint(background, turn.Side); TrackClicks(turn, UpdateNextClicks); diff --git a/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs b/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs index 08e4988..3dde621 100644 --- a/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs +++ b/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs @@ -5,6 +5,7 @@ using model.timing; using UnityEngine; using UnityEngine.UI; +using static view.gui.GameObjectExtensions; namespace view.gui.timecross { @@ -31,10 +32,10 @@ internal void Wire(Game game, DayNightCycle dayNight, FutureTrack future) private void WireRunnerActionPhase(Game game) { - RunnerActionPhase = GameObject.Find("Runner action phase"); + RunnerActionPhase = FindOrFail("Runner action phase"); var background = RunnerActionPhase.GetComponent(); dayNight.Paint(background, Side.RUNNER); - BankCredit = GameObject.Find("Bank/Credit"); + BankCredit = FindOrFail("Bank/Credit"); SetRunnerActions(false); game.runner.turn.TakingAction += BeginRunnerAction; game.runner.turn.ActionTaken += EndRunnerAction; @@ -48,7 +49,7 @@ private void SetRunnerActions(bool takingAction) private void WireCorpActionPhase(Game game) { - corpActionPhase = GameObject.Find("Corp action phase"); + corpActionPhase = FindOrFail("Corp action phase"); var background = corpActionPhase.GetComponent(); dayNight.Paint(background, Side.CORP); corpActionPhase.SetActive(false); @@ -58,7 +59,7 @@ private void WireCorpActionPhase(Game game) private void WireDiscardPhase(Game game) { - discardPhase = GameObject.Find("Discard phase"); + discardPhase = FindOrFail("Discard phase"); discardBackground = discardPhase.GetComponent(); discardPhase.SetActive(false); game.runner.zones.grip.DiscardingOne += RenderRunnerDiscarding; diff --git a/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs b/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs index d0d7bd4..f6e2b33 100644 --- a/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs +++ b/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using static view.gui.GameObjectExtensions; using model; namespace view.gui.timecross @@ -9,12 +9,12 @@ public class TimeCross public TimeCross(Game game, DayNightCycle dayNight) { - var pastTrack = GameObject.Find("Past").AddComponent(); + var pastTrack = FindOrFail("Past").AddComponent(); pastTrack.DayNight = dayNight; pastTrack.Wire(game); - var futureTrack = GameObject.Find("Future").AddComponent(); + var futureTrack = FindOrFail("Future").AddComponent(); futureTrack.Wire(game, dayNight); - PresentBox = GameObject.Find("Present").AddComponent(); + PresentBox = FindOrFail("Present").AddComponent(); PresentBox.Wire(game, dayNight, futureTrack); } } From 1f8a4f04ae7aca6c129de27b78769816a973cf86 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Tue, 6 Apr 2021 19:15:01 +0200 Subject: [PATCH 04/14] Add vertical margin to nested brackets --- Assets/Scripts/View/GUI/Brackets/Bracket.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Assets/Scripts/View/GUI/Brackets/Bracket.cs b/Assets/Scripts/View/GUI/Brackets/Bracket.cs index 9538493..3262449 100644 --- a/Assets/Scripts/View/GUI/Brackets/Bracket.cs +++ b/Assets/Scripts/View/GUI/Brackets/Bracket.cs @@ -12,12 +12,14 @@ public class Bracket private GameObject opening; private GameObject content; private GameObject closing; + private float margin; - public Bracket(string name, GameObject container) : this(name, container, siblingIndex: 0) { } + public Bracket(string name, GameObject container) : this(name, container, siblingIndex: 0, margin: 0.00f) { } - public Bracket(string name, GameObject container, int siblingIndex) + 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); @@ -31,8 +33,8 @@ private GameObject RenderOpening(int siblingIndex, string name) var image = gameObject.AddComponent(); image.color = Color.magenta; var rect = gameObject.GetComponent(); - rect.anchorMin = new Vector2(0.00f, 0.05f); - rect.anchorMax = new Vector2(EDGE_WIDTH_RATIO, 0.95f); + 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); @@ -58,8 +60,8 @@ private GameObject RenderContent(int 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, 0.15f); - rect.anchorMax = new Vector2(0.30f, 0.85f); + 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; @@ -72,8 +74,8 @@ private GameObject RenderClosing(int siblingIndex) var image = gameObject.AddComponent(); image.color = Color.cyan; var rect = gameObject.GetComponent(); - rect.anchorMin = new Vector2(0.00f, 0.05f); - rect.anchorMax = new Vector2(EDGE_WIDTH_RATIO, 0.95f); + 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; @@ -82,7 +84,7 @@ private GameObject RenderClosing(int siblingIndex) public Bracket Nest(string name) { var nestedIndex = closing.transform.GetSiblingIndex() - 1; - return new Bracket(name, container, nestedIndex); + return new Bracket(name, container, nestedIndex, margin + 0.08f); } public void Open() From 907867f3a062e957c5d27f877725230c91627613 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Wed, 7 Apr 2021 00:13:57 +0200 Subject: [PATCH 05/14] WIP redo timings without singletons https://github.com/dagguh/data-hunt/pull/2035#issuecomment-814447137 --- Assets/Scripts/AsyncAction.cs | 3 +- .../Model/Cards/Corp/AdvancedAssemblyLines.cs | 25 +-- .../Model/Cards/Corp/HaarpsichordStudios.cs | 4 +- .../Scripts/Model/Cards/Corp/PadCampaign.cs | 15 +- Assets/Scripts/Model/Corp.cs | 7 - Assets/Scripts/Model/Game.cs | 131 +--------------- Assets/Scripts/Model/Runner.cs | 6 - .../Model/Timing/Corp/CorpActionPhase.cs | 43 ++++++ .../Model/Timing/Corp/CorpActionPhase.cs.meta | 11 ++ .../Model/Timing/Corp/CorpDrawPhase.cs | 55 +++++++ .../Model/Timing/Corp/CorpDrawPhase.cs.meta | 11 ++ Assets/Scripts/Model/Timing/Corp/CorpTurn.cs | 134 +++++----------- .../Scripts/Model/Timing/ITimingStructure.cs | 12 ++ .../Model/Timing/ITimingStructure.cs.meta | 11 ++ Assets/Scripts/Model/Timing/ITurn.cs | 23 +-- Assets/Scripts/Model/Timing/PaidWindow.cs | 2 +- .../Scripts/Model/Timing/Runner/RunnerTurn.cs | 30 ++-- Assets/Scripts/Model/Timing/Timing.cs | 143 ++++++++++++++++++ Assets/Scripts/Model/Timing/Timing.cs.meta | 11 ++ .../View/GUI/Brackets/ActionBracket.cs | 17 +-- .../View/GUI/Brackets/RunnerGameBracket.cs | 33 ++-- .../View/GUI/TimeCross/DayNightCycle.cs | 2 +- .../Scripts/View/GUI/TimeCross/FutureTrack.cs | 4 +- Assets/Scripts/View/Log/GameFlowLog.cs | 4 +- 24 files changed, 421 insertions(+), 316 deletions(-) create mode 100644 Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs create mode 100644 Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs.meta create mode 100644 Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs create mode 100644 Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs.meta create mode 100644 Assets/Scripts/Model/Timing/ITimingStructure.cs create mode 100644 Assets/Scripts/Model/Timing/ITimingStructure.cs.meta create mode 100644 Assets/Scripts/Model/Timing/Timing.cs create mode 100644 Assets/Scripts/Model/Timing/Timing.cs.meta 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/Model/Cards/Corp/AdvancedAssemblyLines.cs b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs index bf98898..a05208b 100644 --- a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs +++ b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs @@ -6,6 +6,7 @@ using model.choices.trash; using model.costs; using model.play; +using model.timing; using model.zones; namespace model.cards.corp @@ -18,7 +19,7 @@ public AdvancedAssemblyLines(Game game) : base(game) { } 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 IEffect Activation => new AdvancedAssemblyLinesActivation(this, game); override public IType Type => new Asset(game); override public IList TrashOptions() => new List { new Leave(), @@ -30,21 +31,25 @@ private class AdvancedAssemblyLinesActivation : IEffect public bool Impactful => true; public event Action ChangedImpact = delegate { }; private readonly Card aal; - private readonly Corp corp; + private readonly Game game; IEnumerable IEffect.Graphics => new string[] { }; - public AdvancedAssemblyLinesActivation(Card aal, Corp corp) + public AdvancedAssemblyLinesActivation(Card aal, Game game) { this.aal = aal; - this.corp = corp; + this.game = game; } 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); + await game.corp.credits.Gaining(3).Resolve(); + game.Timing.PaidWindowDefined += DefineTrashAbility; + } + + private void DefineTrashAbility(PaidWindow paidWindow) + { + var archives = game.corp.zones.archives.Zone; + var aalInstall = new AdvancedAssemblyLinesInstall(game.corp); var pop = new Ability( cost: new Conjunction(paidWindow.Permission(), new Trash(aal, archives), new Active(aal)), effect: aalInstall @@ -58,7 +63,7 @@ async Task IEffect.Resolve() } } - private class AdvancedAssemblyLinesInstall : IEffect + private class AdvancedAssemblyLinesInstall : IEffect, IDisposable { public bool Impactful => Installables().Count > 0; public event Action ChangedImpact = delegate { }; @@ -84,7 +89,7 @@ private void UpdateInstallables(Zone hqZone) ChangedImpact(this, Impactful); } - internal void Dispose() + public void Dispose() { corp.zones.hq.Zone.Changed -= UpdateInstallables; } diff --git a/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs b/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs index 00f3c79..df5425b 100644 --- a/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs +++ b/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs @@ -31,8 +31,8 @@ private class HaarpsichordEffect : IEffect async Task IEffect.Resolve() { var memory = new HaarpsichordMemory(); - game.corp.turn.Started += memory.Reset; - game.runner.turn.Started += memory.Reset; + game.corp.turn.Opened += memory.Reset; + game.runner.turn.Opened += memory.Reset; var mod = new HaarpsichordModifier(memory); game.runner.Stealing.ModifyStealing(mod); await Task.CompletedTask; diff --git a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index cfbbd18..f5db8b0 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -3,6 +3,8 @@ using System.Threading.Tasks; using model.cards.types; using model.choices.trash; +using model.timing; +using model.timing.corp; namespace model.cards.corp { @@ -14,7 +16,7 @@ public PadCampaign(Game game) : base(game) { } 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 IEffect Activation => new PadCampaignActivation(game); override public IType Type => new Asset(game); override public IList TrashOptions() => new List { new Leave(), @@ -26,15 +28,20 @@ private class PadCampaignActivation : IEffect public bool Impactful => true; public event Action ChangedImpact = delegate { }; IEnumerable IEffect.Graphics => new string[] { }; - private Corp corp; + private Game game; - public PadCampaignActivation(Corp corp) => this.corp = corp; + public PadCampaignActivation(Game game) => this.game = game; async Task IEffect.Resolve() { - corp.turn.WhenBegins(corp.credits.Gaining(1)); + game.Timing.CorpTurnDefined += RegisterDrip; await Task.CompletedTask; } + + private void RegisterDrip(CorpTurn turn) + { + turn.WhenBegins(game.corp.credits.Gaining(1)); + } } } } diff --git a/Assets/Scripts/Model/Corp.cs b/Assets/Scripts/Model/Corp.cs index 620f6b4..8f9e130 100644 --- a/Assets/Scripts/Model/Corp.cs +++ b/Assets/Scripts/Model/Corp.cs @@ -5,7 +5,6 @@ using model.player; using model.rez; using model.timing; -using model.timing.corp; using model.zones; using model.zones.corp; @@ -14,8 +13,6 @@ namespace model public class Corp { 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); diff --git a/Assets/Scripts/Model/Game.cs b/Assets/Scripts/Model/Game.cs index cb76130..942d229 100644 --- a/Assets/Scripts/Model/Game.cs +++ b/Assets/Scripts/Model/Game.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Threading.Tasks; using model.player; using model.timing; using model.zones; @@ -11,143 +9,24 @@ public class Game { public readonly Corp corp; public readonly Runner runner; + public readonly Timing Timing; private readonly Zone playArea; - public readonly Checkpoint checkpoint; - public event Action CurrentTurn = delegate { }; - public event Action 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); } async public void Start(Deck corpDeck, Deck runnerDeck) { await corp.Start(this, corpDeck); await runner.Start(this, runnerDeck); - await StartTurns(); - } - - async private Task StartTurns() - { - 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) - { - if (ended) - { - 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(); - CurrentTurn(currentTurn); - NextTurn(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; + await Timing.StartTurns(); } } } diff --git a/Assets/Scripts/Model/Runner.cs b/Assets/Scripts/Model/Runner.cs index 6aa2580..688412e 100644 --- a/Assets/Scripts/Model/Runner.cs +++ b/Assets/Scripts/Model/Runner.cs @@ -14,8 +14,6 @@ namespace model public class Runner { 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 +25,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), diff --git a/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs new file mode 100644 index 0000000..6b16a78 --- /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; + + public 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.OpenPaidWindow(rezzing: true, scoring: true); + } + } +} 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..8a42358 --- /dev/null +++ b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using model.play; + +namespace model.timing.corp +{ + public class CorpDrawPhase : ITimingStructure + { + private Corp corp; + private Timing timing; + private IList turnBeginningTriggers = new List(); + public string Name => "Corp draw phase"; + public event AsyncAction Opened; + public event AsyncAction Closed; + + public CorpDrawPhase(Corp corp, Timing timing) + { + this.corp = corp; + this.timing = timing; + } + + public async Task Open() + { + corp.clicks.Replenish(); // CR: 5.6.1.a + await timing.OpenPaidWindow(rezzing: true, scoring: true); // CR: 5.6.1.b + RefillRecurringCredits(); // CR: 5.6.1.c + await TriggerTurnBeginning(); // 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 TriggerTurnBeginning() + { + if (turnBeginningTriggers.Count > 0) + { + await new SimultaneousTriggers(turnBeginningTriggers.Copy()).AllTriggered(game.corp.pilot); + } + } + + async private Task MandatoryDraw() + { + await corp.zones.Drawing(1).Resolve(); + } + + public void WhenBegins(IEffect effect) + { + turnBeginningTriggers.Add(effect); + } + } +} 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..16b42b3 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs @@ -1,113 +1,53 @@ -using System.Collections.Generic; +using System; using System.Threading.Tasks; using model.play; namespace model.timing.corp { - public class CorpTurn : ITurn + public class CorpTurn : ITimingStructure { - 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; - - public CorpTurn(Game game) - { - this.game = game; - } - - async Task ITurn.Start() - { - Active = true; - await Started?.Invoke(this); - await DrawPhase(); - await ActionPhase(); - await DiscardPhase(); - Active = false; - } - - 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() + private Corp corp; + private Timing timing; + public ClickPool Clicks => corp.clicks; + public Side Side => Side.CORP; + public string Name { get; } + public event AsyncAction Opened; + public event AsyncAction Closed; + private CorpDrawPhase drawPhase; + private CorpActionPhase actionPhase; + private CorpDiscardPhase discardPhase; + public event Action DrawPhaseDefined = delegate { }; + public event Action ActionPhaseDefined = delegate { }; + public event Action DiscardPhaseDefined = delegate { }; + + public CorpTurn(Corp corp, Timing timing, int number) { - if (turnBeginningTriggers.Count > 0) - { - await new SimultaneousTriggers(turnBeginningTriggers.Copy()).AllTriggered(game.corp.pilot); - } + this.corp = corp; + this.timing = timing; + Name = "Corp turn " + number; + drawPhase = new CorpDrawPhase(corp, timing); + actionPhase = new CorpActionPhase(corp, timing); } - async private Task MandatoryDraw() + public CorpTurn(Corp corp, Timing timing) { - await game.corp.zones.Drawing(1).Resolve(); + this.corp = corp; + this.timing = timing; } - async private Task ActionPhase() + internal void DefinePhases() { - 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 + DrawPhaseDefined(drawPhase); + ActionPhaseDefined(actionPhase); } - async private Task TakeAction() + public async Task Open() { - 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; + await Opened?.Invoke(this); + await drawPhase.Open(); + await actionPhase.Open(); + await discardPhase.Open(); + await Closed?.Invoke(this); } async private Task DiscardPhase() @@ -117,14 +57,14 @@ async private Task DiscardPhase() 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; + var hq = corp.zones.hq; while (hq.Zone.Count > 5) { await hq.Discard(); diff --git a/Assets/Scripts/Model/Timing/ITimingStructure.cs b/Assets/Scripts/Model/Timing/ITimingStructure.cs new file mode 100644 index 0000000..13d191f --- /dev/null +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace model.timing +{ + public interface ITimingStructure where T : ITimingStructure + { + Task Open(); + event AsyncAction Opened; + event AsyncAction Closed; + string Name { get; } + } +} 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..e041b17 100644 --- a/Assets/Scripts/Model/Timing/ITurn.cs +++ b/Assets/Scripts/Model/Timing/ITurn.cs @@ -1,27 +1,8 @@ -using System.Threading.Tasks; -using model.play; - -namespace model.timing +namespace model.timing { - public interface ITurn + public interface ITurn : ITimingStructure { ClickPool Clicks { get; } Side Side { get; } - Task Start(); - - ///

- /// 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; - - /// - /// 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; } } diff --git a/Assets/Scripts/Model/Timing/PaidWindow.cs b/Assets/Scripts/Model/Timing/PaidWindow.cs index 3e39ec0..510c00e 100644 --- a/Assets/Scripts/Model/Timing/PaidWindow.cs +++ b/Assets/Scripts/Model/Timing/PaidWindow.cs @@ -5,7 +5,7 @@ namespace model.timing { - public class PaidWindow + public class PaidWindow : ITimingStructure { private readonly string label; private PaidWindowPermission permission = new PaidWindowPermission(); diff --git a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs index e35d89e..d1cd6ed 100644 --- a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs +++ b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs @@ -6,32 +6,34 @@ namespace model.timing.runner { public class RunnerTurn : ITurn { - private Game game; - public bool Active { get; private set; } = false; - ClickPool ITurn.Clicks => game.runner.clicks; + private Runner runner; + private Timing timing; + ClickPool ITurn.Clicks => runner.clicks; Side ITurn.Side => Side.RUNNER; private List turnBeginningTriggers = new List(); - public event AsyncAction Started; + public int Number { get; private set; } = 0; + public event AsyncAction Opened; + public event AsyncAction Closed; public event AsyncAction TakingAction; public event AsyncAction ActionTaken; - public RunnerTurn(Game game) + public RunnerTurn(Runner runner, Timing timing) { - this.game = game; + this.runner = runner; } - async Task ITurn.Start() + async Task ITimingStructure.Open() { - Active = true; - await Started?.Invoke(this); + Number++; + await Opened?.Invoke(this); await ActionPhase(); await DiscardPhase(); - Active = false; + await Closed?.Invoke(this); } async private Task ActionPhase() { - game.runner.clicks.Replenish(); // CR: 5.7.1.a + 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; @@ -42,16 +44,16 @@ async private Task ActionPhase() var paid2 = OpenPaidWindow(); // CR: 5.7.1.e await rez2; await paid2; - while (game.runner.clicks.Remaining > 0) // CR: 5.7.1.g + 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 + await timing.Checkpoint(); // CR: 5.7.1.g } async private Task OpenPaidWindow() { - await game.OpenPaidWindow( + await timing.OpenPaidWindow( acting: game.runner.paidWindow, reacting: game.corp.paidWindow ); diff --git a/Assets/Scripts/Model/Timing/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs new file mode 100644 index 0000000..96780fd --- /dev/null +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using model.timing.corp; +using model.timing.runner; + +namespace model.timing +{ + public class Timing + { + private Game game; + private Checkpoint checkpoint; + public event Action CorpTurnDefined = 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 priority; + + public Timing(Game game) + { + this.game = game; + game.corp.zones.rd.Decked += DeckCorp; + game.runner.zones.score.StolenEnough += StealEnough; + } + + async public Task StartTurns() + { + try + { + while (!gameEnded) + { + CorpTurn corpTurn = new CorpTurn(game.corp, this); + CorpTurnDefined(corpTurn); + corpTurn.DefinePhases(); + turns.Enqueue(corpTurn); + turns.Enqueue(new RunnerTurn(game.runner, this)); + 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()); + await currentTurn.Open(); + } + + async public Task OpenActionWindow() + { + var window = new ActionWindow(); + await window.Open(); + } + + public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) + { + return new PaidWindow( + rezzing, + scoring, + acting: game.corp, + reacting: game.runner + ); + } + + 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) + { + gameEnded = 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/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/View/GUI/Brackets/ActionBracket.cs b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs index 4c9dd61..d221d35 100644 --- a/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs +++ b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs @@ -7,13 +7,15 @@ namespace view.gui.brackets public class ActionBracket { private readonly ITurn turn; - private readonly int actionOrder; + private readonly int turnNumber; + private readonly int actionNumber; private readonly Bracket bracket; - public ActionBracket(ITurn turn, int actionOrder, Bracket bracket) + public ActionBracket(ITurn turn, int actionNumber, Bracket bracket) { this.turn = turn; - this.actionOrder = actionOrder; + this.turnNumber = turn.Number; + this.actionNumber = actionNumber; this.bracket = bracket; turn.TakingAction += UpdateActivation; turn.ActionTaken += UpdateEffect; @@ -30,20 +32,17 @@ async private Task UpdateActivation(ITurn turn) private bool IsPresent() { - return turn.Clicks.Spent == actionOrder - 1; + return turn.Clicks.Spent == actionNumber; } private bool IsPast() { - return turn.Clicks.Spent >= actionOrder; + return turn.Clicks.Spent >= actionNumber; } async private Task UpdateEffect(ITurn turn, Ability action) { - if (IsPast()) - { - bracket.Collapse(); - } + bracket.Collapse(); // action.effect.Graphics; await Task.CompletedTask; } diff --git a/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs index e1c31a1..edd1066 100644 --- a/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs +++ b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs @@ -1,5 +1,7 @@ +using System.Threading.Tasks; using model; using model.timing; +using model.timing.corp; using UnityEngine; namespace view.gui.brackets @@ -7,33 +9,40 @@ 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.CurrentTurn += DisplayTurn; + game.Timing.CorpTurnDefined += DisplayCorpTurn; + game.runner.turn.Opened += DisplayRunnerTurn; + game.corp.turn.Opened += DisplayCorpTurn; } - private void DisplayTurn(ITurn turn) + async private Task DisplayCorpTurn(CorpTurn turn) { - switch (turn.Side) + var turnContainer = bracket.Nest(turn.Name); + turn.ActionPhaseDefined += DisplayCorpActionPhase; + for (int i = 0; i < turn.Clicks.NextReplenishment; i++) { - case Side.RUNNER: DisplayRunnerTurn(turn); break; - case Side.CORP: DisplayCorpTurn(turn); break; + int actionOrder = i + 1; + var actionBracket = turnContainer.Nest("Action " + actionOrder); + new ActionBracket(turn, actionOrder, actionBracket); } - } - - private void DisplayRunnerTurn(ITurn turn) - { - var turnContainer = bracket.Nest("Runner turn"); new TurnBracket(turnContainer, turn); + await Task.CompletedTask; } - private void DisplayCorpTurn(ITurn turn) + private void DisplayCorpActionPhase(CorpActionPhase actionPhase) { +actionPhase. + } + + async private Task DisplayRunnerTurn(ITurn turn) { - var turnContainer = bracket.Nest("Corp turn"); + var turnContainer = bracket.Nest("Runner turn " + turn.Number); new TurnBracket(turnContainer, turn); + await Task.CompletedTask; } } } diff --git a/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs b/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs index d7e82a8..f6620c1 100644 --- a/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs +++ b/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs @@ -16,7 +16,7 @@ public class DayNightCycle public void Wire(Game game) { - game.CurrentTurn += UpdateBackground; + game.CurrentTurnQueued += UpdateBackground; } void UpdateBackground(ITurn turn) diff --git a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs b/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs index 521fb14..cd1bd93 100644 --- a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs +++ b/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs @@ -25,11 +25,11 @@ public void Wire(Game game, DayNightCycle dayNight) { CurrentTurn = new GameObject("Current turn").AddComponent(); CurrentTurn.gameObject.AttachTo(gameObject); - game.CurrentTurn += CurrentTurn.DisplayCurrent; + game.CurrentTurnQueued += CurrentTurn.DisplayCurrent; CurrentTurn.dayNight = dayNight; var nextTurn = new GameObject("Next turn").AddComponent(); nextTurn.gameObject.AttachTo(gameObject); - game.NextTurn += nextTurn.DisplayNext; + game.NextTurnPredicted += nextTurn.DisplayNext; nextTurn.dayNight = dayNight; } } diff --git a/Assets/Scripts/View/Log/GameFlowLog.cs b/Assets/Scripts/View/Log/GameFlowLog.cs index 8d2433c..0b8a299 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; + 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"); From 16b59f02384e9078e95cbe67b4c34fa72661736e Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Sun, 11 Apr 2021 00:21:43 +0200 Subject: [PATCH 06/14] Model timing and priority according to rules --- Assets/Scripts/Model/Cards/Card.cs | 1 + .../Model/Cards/Corp/HaarpsichordStudios.cs | 34 ++++++-- .../Scripts/Model/Cards/Corp/PadCampaign.cs | 24 +++++- .../Model/Cards/Runner/SportsHopper.cs | 35 +++++--- Assets/Scripts/Model/ClickPool.cs | 9 +- Assets/Scripts/Model/CollectionExtensions.cs | 1 - Assets/Scripts/Model/Costs/Active.cs | 24 ------ Assets/Scripts/Model/Costs/Free.cs | 16 ++++ .../Costs/{Active.cs.meta => Free.cs.meta} | 2 +- Assets/Scripts/Model/ICost.cs | 3 +- Assets/Scripts/Model/IEffect.cs | 14 +++- .../Scripts/Model/Install/GenericInstall.cs | 2 + Assets/Scripts/Model/Play/Ability.cs | 17 ++-- .../Model/Play/SimultaneousTriggers.cs | 10 +-- Assets/Scripts/Model/Play/Source.cs | 11 +++ Assets/Scripts/Model/Play/Source.cs.meta | 11 +++ .../Scripts/Model/Player/DelegatingPilot.cs | 5 +- Assets/Scripts/Model/Player/IPilot.cs | 3 +- Assets/Scripts/Model/Player/NoPilot.cs | 5 +- .../Scripts/Model/Player/SingleChoiceMaker.cs | 9 +- Assets/Scripts/Model/Rez/Rezzing.cs | 2 + .../Model/Timing/Corp/CorpActionPhase.cs | 4 +- .../Model/Timing/Corp/CorpDrawPhase.cs | 25 ++---- Assets/Scripts/Model/Timing/Corp/CorpTurn.cs | 25 ++---- Assets/Scripts/Model/Timing/PaidWindow.cs | 82 +++++++++---------- Assets/Scripts/Model/Timing/PriorityWindow.cs | 36 ++++++++ .../Model/Timing/PriorityWindow.cs.meta | 11 +++ Assets/Scripts/Model/Timing/Timing.cs | 17 ++-- .../View/GUI/Brackets/RunnerGameBracket.cs | 19 +++-- 29 files changed, 289 insertions(+), 168 deletions(-) delete mode 100644 Assets/Scripts/Model/Costs/Active.cs create mode 100644 Assets/Scripts/Model/Costs/Free.cs rename Assets/Scripts/Model/Costs/{Active.cs.meta => Free.cs.meta} (83%) create mode 100644 Assets/Scripts/Model/Play/Source.cs create mode 100644 Assets/Scripts/Model/Play/Source.cs.meta create mode 100644 Assets/Scripts/Model/Timing/PriorityWindow.cs create mode 100644 Assets/Scripts/Model/Timing/PriorityWindow.cs.meta diff --git a/Assets/Scripts/Model/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index 2ec2e72..55492eb 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -46,6 +46,7 @@ async public Task Activate() public void Deactivate() { Active = false; + Activation.Disable(); Toggled(this, Active); } diff --git a/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs b/Assets/Scripts/Model/Cards/Corp/HaarpsichordStudios.cs index df5425b..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; - - async Task IEffect.Resolve() + public HaarpsichordEffect(Game game) { - var memory = new HaarpsichordMemory(); + this.game = game; + memory = new HaarpsichordMemory(); + mod = new HaarpsichordModifier(memory); game.corp.turn.Opened += memory.Reset; game.runner.turn.Opened += memory.Reset; - var mod = new HaarpsichordModifier(memory); game.runner.Stealing.ModifyStealing(mod); + } + + async Task IEffect.Resolve() + { + 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 f5db8b0..6562d67 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using model.cards.types; using model.choices.trash; +using model.play; using model.timing; using model.timing.corp; @@ -16,7 +17,7 @@ public PadCampaign(Game game) : base(game) { } 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); + override public IEffect Activation => new PadCampaignActivation(this, game); override public IType Type => new Asset(game); override public IList TrashOptions() => new List { new Leave(), @@ -28,9 +29,15 @@ private class PadCampaignActivation : IEffect public bool Impactful => true; public event Action ChangedImpact = delegate { }; IEnumerable IEffect.Graphics => new string[] { }; + private CardAbility drip; private Game game; + private IList phases = new List(); - public PadCampaignActivation(Game game) => this.game = game; + public PadCampaignActivation(PadCampaign padCampaign, Game game) + { + this.game = game; + this.drip = game.corp.credits.Gaining(1).ToMandatoryAbility().BelongingTo(padCampaign); + } async Task IEffect.Resolve() { @@ -40,7 +47,18 @@ async Task IEffect.Resolve() private void RegisterDrip(CorpTurn turn) { - turn.WhenBegins(game.corp.credits.Gaining(1)); + var phase = turn.drawPhase; + phases.Add(phase); + phase.TurnBegins.Add(drip); + } + + void IEffect.Disable() + { + game.Timing.CorpTurnDefined -= RegisterDrip; + foreach (var phase in phases) + { + phase.TurnBegins.Remove(drip); + } } } } diff --git a/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs b/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs index 94fdb98..ce9c550 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,50 @@ 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)).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; + game.runner.zones.rig.zone.Removed += CheckIfUninstalled; await Task.CompletedTask; } + private void Register(PaidWindow paidWindow) + { + paidWindow.Opened += Enable; + // TODO one or another + paidWindow.Add(pop); + } + + private void Enable(PaidWindow paidWindow) + { + paidWindow.Add(pop); + } + private void CheckIfUninstalled(Zone zone, Card card) { if (card == this.hopper) { - runner.paidWindow.Remove(pop); + game.runner.paidWindow.Remove(pop); } } } diff --git a/Assets/Scripts/Model/ClickPool.cs b/Assets/Scripts/Model/ClickPool.cs index ce55252..76fe520 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 { @@ -77,11 +78,13 @@ public SpendClicks(ClickPool pool, int clicksToSpend) pool.Changed += (_) => ChangedPayability(this, Payable); } - async public Task Pay() + async public Task Pay(IPilot controller) { pool.Spend(clicksToSpend); await Task.CompletedTask; } + + public void Disable() {} } public IEffect Losing(int clicksToLose) => new LoseClicks(this, clicksToLose); @@ -101,11 +104,13 @@ public LoseClicks(ClickPool pool, int clicks) pool.Changed += (_) => ChangedImpact(this, Impactful); } - async Task IEffect.Resolve() + async Task IEffect.Resolve(IPilot controller) { pool.Lose(clicksToLose); await Task.CompletedTask; } + + void IEffect.Disable() {} } } } 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/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/Active.cs.meta b/Assets/Scripts/Model/Costs/Free.cs.meta similarity index 83% rename from Assets/Scripts/Model/Costs/Active.cs.meta rename to Assets/Scripts/Model/Costs/Free.cs.meta index c52ea18..5e7d5ba 100644 --- a/Assets/Scripts/Model/Costs/Active.cs.meta +++ b/Assets/Scripts/Model/Costs/Free.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 47968cd203e16c14fa56c89bf1848bca +guid: 09b52e852ca52c24698431115e0a3bf6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/ICost.cs b/Assets/Scripts/Model/ICost.cs index 28c2a42..e433c11 100644 --- a/Assets/Scripts/Model/ICost.cs +++ b/Assets/Scripts/Model/ICost.cs @@ -1,12 +1,13 @@ using System; using System.Threading.Tasks; +using model.player; namespace model { public interface ICost { bool Payable { get; } - Task Pay(); + Task Pay(IPilot controller); event Action ChangedPayability; } } diff --git a/Assets/Scripts/Model/IEffect.cs b/Assets/Scripts/Model/IEffect.cs index 2af8f62..e981878 100644 --- a/Assets/Scripts/Model/IEffect.cs +++ b/Assets/Scripts/Model/IEffect.cs @@ -1,14 +1,26 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using model.costs; +using model.play; +using model.player; namespace model { public interface IEffect { - Task Resolve(); + Task Resolve(IPilot controller); + void Disable(); bool Impactful { get; } event Action ChangedImpact; IEnumerable Graphics { get; } } + + static class IEffectExtensions + { + public static Ability ToMandatoryAbility(this IEffect effect, IPilot controller) + { + return new Ability(new Free(), effect, controller); + } + } } diff --git a/Assets/Scripts/Model/Install/GenericInstall.cs b/Assets/Scripts/Model/Install/GenericInstall.cs index 87fa2b1..5588d54 100644 --- a/Assets/Scripts/Model/Install/GenericInstall.cs +++ b/Assets/Scripts/Model/Install/GenericInstall.cs @@ -112,5 +112,7 @@ async private Task TriggerPostInstall() { await Task.FromResult("TODO: Implement TriggerPostInstall"); } + + void IEffect.Disable() { } } } diff --git a/Assets/Scripts/Model/Play/Ability.cs b/Assets/Scripts/Model/Play/Ability.cs index a83c93e..7df9c47 100644 --- a/Assets/Scripts/Model/Play/Ability.cs +++ b/Assets/Scripts/Model/Play/Ability.cs @@ -1,8 +1,7 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Threading.Tasks; using model.cards; +using model.player; namespace model.play { @@ -10,14 +9,20 @@ public class Ability { public readonly ICost cost; public readonly IEffect effect; + public readonly ISource source; + public readonly IPilot controller; public event Action UsabilityChanged = delegate { }; public event Action Resolved = delegate { }; + public bool Active { get; private set; } public bool Usable => cost.Payable && effect.Impactful; - public Ability(ICost cost, IEffect effect) + public Ability(ICost cost, IEffect effect, ISource source) { this.cost = cost; this.effect = effect; + this.source = source; + this.controller = source.Controller; // CR: 1.13.5 + Active = source.Active; // CR: 9.1.8 cost.ChangedPayability += UpdateCost; effect.ChangedImpact += UpdateEffect; } @@ -34,13 +39,13 @@ private void UpdateEffect(IEffect source, bool impactful) async public Task Trigger() { - await cost.Pay(); - await effect.Resolve(); + await cost.Pay(controller); + await effect.Resolve(controller); Resolved(this); } public CardAbility BelongingTo(Card card) => new CardAbility(this, card); - public override string ToString() => "Ability(cost=" + cost + ", effect=" + effect + ")"; + public override string ToString() => cost + " : " + effect; } } diff --git a/Assets/Scripts/Model/Play/SimultaneousTriggers.cs b/Assets/Scripts/Model/Play/SimultaneousTriggers.cs index 3157d77..0ed9366 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.Ability.Trigger(); + untriggered.Remove(ability); } } } diff --git a/Assets/Scripts/Model/Play/Source.cs b/Assets/Scripts/Model/Play/Source.cs new file mode 100644 index 0000000..d863380 --- /dev/null +++ b/Assets/Scripts/Model/Play/Source.cs @@ -0,0 +1,11 @@ +using model.player; + +namespace model.play +{ + public interface ISource + { + bool Active { get; } + bool Used { get; set; } + IPilot Controller { get; } + } +} diff --git a/Assets/Scripts/Model/Play/Source.cs.meta b/Assets/Scripts/Model/Play/Source.cs.meta new file mode 100644 index 0000000..dd27454 --- /dev/null +++ b/Assets/Scripts/Model/Play/Source.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f6dbe1b088421d742aae7c1e22986ecf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Player/DelegatingPilot.cs b/Assets/Scripts/Model/Player/DelegatingPilot.cs index 28920b3..ea10398 100644 --- a/Assets/Scripts/Model/Player/DelegatingPilot.cs +++ b/Assets/Scripts/Model/Player/DelegatingPilot.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; @@ -22,9 +23,9 @@ public virtual void Play(Game game) basic.Play(game); } - public virtual Task TriggerFromSimultaneous(IList effects) + public virtual Task TriggerFromSimultaneous(IList abilities) { - return basic.TriggerFromSimultaneous(effects); + return basic.TriggerFromSimultaneous(abilities); } public virtual IDecision ChooseACard() diff --git a/Assets/Scripts/Model/Player/IPilot.cs b/Assets/Scripts/Model/Player/IPilot.cs index fbd6185..91d05a7 100644 --- a/Assets/Scripts/Model/Player/IPilot.cs +++ b/Assets/Scripts/Model/Player/IPilot.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; @@ -11,7 +12,7 @@ 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(); diff --git a/Assets/Scripts/Model/Player/NoPilot.cs b/Assets/Scripts/Model/Player/NoPilot.cs index e8b659d..0d3e444 100644 --- a/Assets/Scripts/Model/Player/NoPilot.cs +++ b/Assets/Scripts/Model/Player/NoPilot.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,9 +14,9 @@ public class NoPilot : IPilot { void IPilot.Play(Game game) { } - Task IPilot.TriggerFromSimultaneous(IList effects) + Task IPilot.TriggerFromSimultaneous(IList abilities) { - return Task.FromResult(effects.First()); + 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..9642370 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(IList 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..56a629d 100644 --- a/Assets/Scripts/Model/Rez/Rezzing.cs +++ b/Assets/Scripts/Model/Rez/Rezzing.cs @@ -96,6 +96,8 @@ async public Task Resolve() await card.PlayCost.Pay(); await card.Activate(); } + + public void Disable() { } } } } diff --git a/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs index 6b16a78..9917880 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs @@ -13,7 +13,7 @@ public class CorpActionPhase : ITimingStructure public event AsyncAction TakingAction; public event AsyncAction ActionTaken; - public CorpActionPhase(Corp corp, Timing timing) + internal CorpActionPhase(Corp corp, Timing timing) { this.corp = corp; this.timing = timing; @@ -22,7 +22,7 @@ public CorpActionPhase(Corp corp, Timing timing) public async Task Open() { Opened?.Invoke(this); - var paidWindow = timing.DefinePaidWindow(rezzing: true, scoring: true) + 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 { diff --git a/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs index 8a42358..b852da0 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs @@ -1,19 +1,18 @@ -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Threading.Tasks; using model.play; namespace model.timing.corp { - public class CorpDrawPhase : ITimingStructure + public class CorpDrawPhase : ITimingStructure { private Corp corp; private Timing timing; - private IList turnBeginningTriggers = new List(); + public PriorityWindow TurnBegins = new PriorityWindow("Corp turn begins"); public string Name => "Corp draw phase"; - public event AsyncAction Opened; - public event AsyncAction Closed; + public event AsyncAction Opened; + public event AsyncAction Closed; - public CorpDrawPhase(Corp corp, Timing timing) + internal CorpDrawPhase(Corp corp, Timing timing) { this.corp = corp; this.timing = timing; @@ -24,7 +23,7 @@ public async Task Open() corp.clicks.Replenish(); // CR: 5.6.1.a await timing.OpenPaidWindow(rezzing: true, scoring: true); // CR: 5.6.1.b RefillRecurringCredits(); // CR: 5.6.1.c - await TriggerTurnBeginning(); // CR: 5.6.1.d + 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 @@ -36,20 +35,12 @@ private void RefillRecurringCredits() async private Task TriggerTurnBeginning() { - if (turnBeginningTriggers.Count > 0) - { - await new SimultaneousTriggers(turnBeginningTriggers.Copy()).AllTriggered(game.corp.pilot); - } + await TurnBegins.Open(); } async private Task MandatoryDraw() { await corp.zones.Drawing(1).Resolve(); } - - public void WhenBegins(IEffect effect) - { - turnBeginningTriggers.Add(effect); - } } } diff --git a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs index 16b42b3..82b2b00 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs @@ -1,11 +1,12 @@ -using System; -using System.Threading.Tasks; -using model.play; +using System.Threading.Tasks; namespace model.timing.corp { public class CorpTurn : ITimingStructure { + public CorpDrawPhase drawPhase { get; } + public CorpActionPhase actionPhase { get; } + public CorpDiscardPhase discardPhase { get; } private Corp corp; private Timing timing; public ClickPool Clicks => corp.clicks; @@ -13,12 +14,6 @@ public class CorpTurn : ITimingStructure public string Name { get; } public event AsyncAction Opened; public event AsyncAction Closed; - private CorpDrawPhase drawPhase; - private CorpActionPhase actionPhase; - private CorpDiscardPhase discardPhase; - public event Action DrawPhaseDefined = delegate { }; - public event Action ActionPhaseDefined = delegate { }; - public event Action DiscardPhaseDefined = delegate { }; public CorpTurn(Corp corp, Timing timing, int number) { @@ -27,6 +22,7 @@ public CorpTurn(Corp corp, Timing timing, int number) Name = "Corp turn " + number; drawPhase = new CorpDrawPhase(corp, timing); actionPhase = new CorpActionPhase(corp, timing); + discardPhase = new CorpDiscardPhase(); } public CorpTurn(Corp corp, Timing timing) @@ -35,12 +31,6 @@ public CorpTurn(Corp corp, Timing timing) this.timing = timing; } - internal void DefinePhases() - { - DrawPhaseDefined(drawPhase); - ActionPhaseDefined(actionPhase); - } - public async Task Open() { await Opened?.Invoke(this); @@ -74,10 +64,5 @@ async private Task Discard() private void TriggerTurnEnding() { } - - public void WhenBegins(IEffect effect) - { - turnBeginningTriggers.Add(effect); - } } } diff --git a/Assets/Scripts/Model/Timing/PaidWindow.cs b/Assets/Scripts/Model/Timing/PaidWindow.cs index 510c00e..51f36bf 100644 --- a/Assets/Scripts/Model/Timing/PaidWindow.cs +++ b/Assets/Scripts/Model/Timing/PaidWindow.cs @@ -1,61 +1,61 @@ -using System; -using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; -using model.play; +using model.player; namespace model.timing { - public class PaidWindow : ITimingStructure + public class PaidWindow : PriorityWindow { - 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; + private bool rezzing; + private bool scoring; + private IPilot acting; + private IPilot reacting; - public PaidWindow(string label) + public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting, string name) : base(name) { - this.label = label; + this.rezzing = rezzing; + this.scoring = scoring; + this.acting = acting; + this.reacting = reacting; } - public List ListAbilities() => new List(abilities); - - public ICost Permission() => permission; - - async public Task AwaitPass() + async override public Task Open() { pass = new TaskCompletionSource(); - permission.Grant(); Opened(this); + + var bothPlayersCouldAct = false; + while (true) + { + var actingDeclined = await AwaitPass(acting); + if (actingDeclined && bothPlayersCouldAct) + { + break; + } + var reactingDeclined = await AwaitPass(reacting); + bothPlayersCouldAct = true; + if (reactingDeclined && bothPlayersCouldAct) + { + break; + } + } + if (abilities.Count > 0) + { + await new SimultaneousTriggers(abilities.Copy()).AllTriggered(game.corp.pilot); + } await pass.Task; Closed(this); - permission.Revoke(); - return !permission.WasPaid(); - } - - public void Pass() - { - pass.SetResult(true); - } - - public void Add(CardAbility ability) - { - abilities.Add(ability); - Added(this, ability); - } - - public void Remove(CardAbility ability) - { - abilities.Remove(ability); - Removed(this, ability); } - public override string ToString() + async private Task AwaitPass(IPilot pilot) { - return "PaidWindow(label=" + label + ")"; + var options = abilities + .Where(it => it.Ability.Active) + .Where(it => it.Ability.controller == pilot); + CardAbility pass = new PassOption(); + var option = await pilot.TriggerFromSimultaneous(options); + await option.Ability.Trigger(); + return pass.Used; } } } diff --git a/Assets/Scripts/Model/Timing/PriorityWindow.cs b/Assets/Scripts/Model/Timing/PriorityWindow.cs new file mode 100644 index 0000000..a325fa4 --- /dev/null +++ b/Assets/Scripts/Model/Timing/PriorityWindow.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using model.play; + +namespace model.timing +{ + public abstract class PriorityWindow : ITimingStructure + { + protected IList abilities = new List(); + private TaskCompletionSource pass; + public event Action Added = delegate { }; + public event Action Removed = delegate { }; + public event AsyncAction Opened; + public event AsyncAction Closed; + public string Name { get; } + + public PriorityWindow(string name) + { + Name = name; + } + public abstract Task Open(); + + public void Add(CardAbility ability) + { + abilities.Add(ability); + Added(this, ability); + } + + public void Remove(CardAbility ability) + { + abilities.Remove(ability); + Removed(this, ability); + } + } +} 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/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs index 96780fd..8ea5be8 100644 --- a/Assets/Scripts/Model/Timing/Timing.cs +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -11,6 +11,7 @@ public class Timing private Game game; private Checkpoint checkpoint; public event Action CorpTurnDefined = delegate { }; + public event Action PaidWindowDefined = delegate { }; public event Action CurrentTurnQueued = delegate { }; public event Action NextTurnPredicted = delegate { }; public event Action Finished = delegate { }; @@ -31,7 +32,7 @@ async public Task StartTurns() { while (!gameEnded) { - CorpTurn corpTurn = new CorpTurn(game.corp, this); + var corpTurn = new CorpTurn(game.corp, this); CorpTurnDefined(corpTurn); corpTurn.DefinePhases(); turns.Enqueue(corpTurn); @@ -69,12 +70,14 @@ async public Task OpenActionWindow() public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) { - return new PaidWindow( - rezzing, - scoring, - acting: game.corp, - reacting: game.runner - ); + var window = new PaidWindow( + rezzing, + scoring, + acting: game.corp.pilot, + reacting: game.runner.pilot + ); + PaidWindowDefined(window); + return window; } async public Task OpenPaidWindow(PaidWindow acting, PaidWindow reacting) diff --git a/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs index edd1066..b4052b8 100644 --- a/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs +++ b/Assets/Scripts/View/GUI/Brackets/RunnerGameBracket.cs @@ -16,14 +16,18 @@ public RunnerGameBracket(GameObject container, Game game) bracket = new Bracket("Game", container); container.AddComponent(); game.Timing.CorpTurnDefined += DisplayCorpTurn; - game.runner.turn.Opened += DisplayRunnerTurn; - game.corp.turn.Opened += DisplayCorpTurn; } - async private Task DisplayCorpTurn(CorpTurn turn) + private void DisplayCorpTurn(CorpTurn turn) { var turnContainer = bracket.Nest(turn.Name); - turn.ActionPhaseDefined += DisplayCorpActionPhase; + turn.ActionPhaseDefined += DisplayCorpActionPhase; + + } + + private void DisplayCorpActionPhase(CorpActionPhase phase) + { + phase.ActionWindowDefined += DisplayCorpActionWindow; for (int i = 0; i < turn.Clicks.NextReplenishment; i++) { int actionOrder = i + 1; @@ -34,9 +38,10 @@ async private Task DisplayCorpTurn(CorpTurn turn) await Task.CompletedTask; } - private void DisplayCorpActionPhase(CorpActionPhase actionPhase) { -actionPhase. - } + private void DisplayCorpActionWindow(CorpActionWindow window) + { + + } async private Task DisplayRunnerTurn(ITurn turn) { From f80bd60e15b17808724cf01bd6e7f42607624ecf Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Sun, 25 Apr 2021 19:59:08 +0200 Subject: [PATCH 07/14] Follow some 9.2.x rules about priority --- .../Model/Cards/Corp/AdvancedAssemblyLines.cs | 5 +- .../Model/Cards/Runner/SportsHopper.cs | 18 ++---- Assets/Scripts/Model/Play/Ability.cs | 10 +++- Assets/Scripts/Model/Play/CardAbility.cs | 8 ++- Assets/Scripts/Model/Play/IPlayOption.cs | 10 ++++ Assets/Scripts/Model/Play/IPlayOption.cs.meta | 11 ++++ Assets/Scripts/Model/Player/IPilot.cs | 1 + Assets/Scripts/Model/Rez/Rezzing.cs | 2 +- Assets/Scripts/Model/Timing/ITurn.cs | 5 +- Assets/Scripts/Model/Timing/PaidWindow.cs | 37 ++++++------ Assets/Scripts/Model/Timing/Priority.cs | 57 +++++++++++++++++++ Assets/Scripts/Model/Timing/Priority.cs.meta | 11 ++++ Assets/Scripts/Model/Timing/PriorityWindow.cs | 18 +----- Assets/Scripts/Model/Timing/Timing.cs | 29 ++++++++-- 14 files changed, 161 insertions(+), 61 deletions(-) create mode 100644 Assets/Scripts/Model/Play/IPlayOption.cs create mode 100644 Assets/Scripts/Model/Play/IPlayOption.cs.meta create mode 100644 Assets/Scripts/Model/Timing/Priority.cs create mode 100644 Assets/Scripts/Model/Timing/Priority.cs.meta diff --git a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs index a05208b..b763200 100644 --- a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs +++ b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs @@ -6,6 +6,7 @@ using model.choices.trash; using model.costs; using model.play; +using model.player; using model.timing; using model.zones; @@ -78,10 +79,10 @@ public AdvancedAssemblyLinesInstall(Corp corp) private IList Installables() => corp.zones.hq.Zone.Cards.Where(card => (card.Type.Installable && !(card.Type is Agenda))).ToList(); - async Task IEffect.Resolve() + async Task IEffect.Resolve(IPilot pilot) { var installable = await corp.pilot.ChooseACard().Declare("Which card to install?", Installables()); - await corp.Installing.InstallingCard(installable).Resolve(); + await corp.Installing.InstallingCard(installable).Resolve(pilot); } private void UpdateInstallables(Zone hqZone) diff --git a/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs b/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs index ce9c550..ac436cf 100644 --- a/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs +++ b/Assets/Scripts/Model/Cards/Runner/SportsHopper.cs @@ -34,33 +34,25 @@ public SportsHopperActivation(Card hopper, Game game) this.hopper = hopper; this.game = game; var zones = game.runner.zones; - pop = new Ability(new Trash(hopper, zones.heap.zone), zones.Drawing(3)).BelongingTo(hopper); + pop = new Ability(new Trash(hopper, zones.heap.zone), zones.Drawing(3), hopper).BelongingTo(hopper); } async Task IEffect.Resolve() { game.Timing.PaidWindowDefined += Register; - game.runner.zones.rig.zone.Removed += CheckIfUninstalled; await Task.CompletedTask; } private void Register(PaidWindow paidWindow) { - paidWindow.Opened += Enable; - // TODO one or another - paidWindow.Add(pop); + paidWindow.PriorityGiven += Register; } - private void Enable(PaidWindow paidWindow) + private void Register(Priority priority) { - paidWindow.Add(pop); - } - - private void CheckIfUninstalled(Zone zone, Card card) - { - if (card == this.hopper) + if (priority.Pilot == game.runner.pilot) { - game.runner.paidWindow.Remove(pop); + priority.Add(pop); } } } diff --git a/Assets/Scripts/Model/Play/Ability.cs b/Assets/Scripts/Model/Play/Ability.cs index 7df9c47..5a3bc1e 100644 --- a/Assets/Scripts/Model/Play/Ability.cs +++ b/Assets/Scripts/Model/Play/Ability.cs @@ -5,7 +5,7 @@ namespace model.play { - public class Ability + public class Ability : IPlayOption { public readonly ICost cost; public readonly IEffect effect; @@ -14,7 +14,8 @@ public class Ability public event Action UsabilityChanged = delegate { }; public event Action Resolved = delegate { }; public bool Active { get; private set; } - public bool Usable => cost.Payable && effect.Impactful; + public bool Usable => cost.Payable && effect.Impactful; // CR: 1.2.5 + public bool Legal => Active && Usable; public Ability(ICost cost, IEffect effect, ISource source) { @@ -37,6 +38,11 @@ private void UpdateEffect(IEffect source, bool impactful) UsabilityChanged(this, Usable); } + async public Task Resolve() + { + await Trigger(); + } + async public Task Trigger() { await cost.Pay(controller); diff --git a/Assets/Scripts/Model/Play/CardAbility.cs b/Assets/Scripts/Model/Play/CardAbility.cs index afd25d3..d855c6b 100644 --- a/Assets/Scripts/Model/Play/CardAbility.cs +++ b/Assets/Scripts/Model/Play/CardAbility.cs @@ -1,16 +1,20 @@ -using model.cards; +using System.Threading.Tasks; +using model.cards; namespace model.play { - public class CardAbility + public class CardAbility : IPlayOption { public Ability Ability { get; } public Card Card { get; } + public bool Legal => Ability.Legal; public CardAbility(Ability ability, Card card) { Ability = ability; Card = card; } + + async public Task Resolve() => await Ability.Resolve(); } } diff --git a/Assets/Scripts/Model/Play/IPlayOption.cs b/Assets/Scripts/Model/Play/IPlayOption.cs new file mode 100644 index 0000000..efe647f --- /dev/null +++ b/Assets/Scripts/Model/Play/IPlayOption.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace model.play +{ + public interface IPlayOption + { + bool Legal { 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/Player/IPilot.cs b/Assets/Scripts/Model/Player/IPilot.cs index 91d05a7..4803fad 100644 --- a/Assets/Scripts/Model/Player/IPilot.cs +++ b/Assets/Scripts/Model/Player/IPilot.cs @@ -18,5 +18,6 @@ public interface IPilot IDecision ChooseAnInstallDestination(); IDecision ChooseTrashing(); IDecision ChooseStealing(); + IPlayOption Choose(IList options); } } diff --git a/Assets/Scripts/Model/Rez/Rezzing.cs b/Assets/Scripts/Model/Rez/Rezzing.cs index 56a629d..4c948cc 100644 --- a/Assets/Scripts/Model/Rez/Rezzing.cs +++ b/Assets/Scripts/Model/Rez/Rezzing.cs @@ -24,7 +24,7 @@ public void Track(Card card) } // CR: 8.1.2.a - public Ability Rez(Card card) + public IPlayOption Rez(Card card) { var rezCost = new Conjunction( card.PlayCost, diff --git a/Assets/Scripts/Model/Timing/ITurn.cs b/Assets/Scripts/Model/Timing/ITurn.cs index e041b17..eb64e8d 100644 --- a/Assets/Scripts/Model/Timing/ITurn.cs +++ b/Assets/Scripts/Model/Timing/ITurn.cs @@ -1,8 +1,11 @@ -namespace model.timing +using model.player; + +namespace model.timing { public interface ITurn : ITimingStructure { ClickPool Clicks { get; } Side Side { get; } + IPilot Owner { get; } } } diff --git a/Assets/Scripts/Model/Timing/PaidWindow.cs b/Assets/Scripts/Model/Timing/PaidWindow.cs index 51f36bf..3c4138c 100644 --- a/Assets/Scripts/Model/Timing/PaidWindow.cs +++ b/Assets/Scripts/Model/Timing/PaidWindow.cs @@ -1,5 +1,6 @@ -using System.Linq; +using System; using System.Threading.Tasks; +using model.play; using model.player; namespace model.timing @@ -10,6 +11,7 @@ public class PaidWindow : PriorityWindow private bool scoring; private IPilot acting; private IPilot reacting; + public event Action PriorityGiven = delegate { }; public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting, string name) : base(name) { @@ -19,12 +21,17 @@ public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting, st this.reacting = reacting; } - async override public Task Open() + internal void GiveOption(IPilot pilot, IPlayOption pop) { - pass = new TaskCompletionSource(); - Opened(this); + throw new System.NotImplementedException(); + } - var bothPlayersCouldAct = false; + async override public Task Open() + { + Opened(this); // TODO inherited events don't work + priority.Add(new Rez(PadCampaign)); + priority.Add(new Aal()); + var bothPlayersCouldAct = false; // CR: 9.2.7.a while (true) { var actingDeclined = await AwaitPass(acting); @@ -39,23 +46,19 @@ async override public Task Open() break; } } - if (abilities.Count > 0) - { - await new SimultaneousTriggers(abilities.Copy()).AllTriggered(game.corp.pilot); - } - await pass.Task; Closed(this); } async private Task AwaitPass(IPilot pilot) { - var options = abilities - .Where(it => it.Ability.Active) - .Where(it => it.Ability.controller == pilot); - CardAbility pass = new PassOption(); - var option = await pilot.TriggerFromSimultaneous(options); - await option.Ability.Trigger(); - return pass.Used; + var priority = new Priority(pilot, canPass: true); // CR: 9.2.4.b + PriorityGiven(priority); + var declined = false; + while (!priority.Passed) // CR: 9.2.4.c + { + await priority.Choose(); // CR: 9.2.7.f + } + return declined; } } } diff --git a/Assets/Scripts/Model/Timing/Priority.cs b/Assets/Scripts/Model/Timing/Priority.cs new file mode 100644 index 0000000..31311b3 --- /dev/null +++ b/Assets/Scripts/Model/Timing/Priority.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using model.play; +using model.player; + +namespace model.timing +{ + public class Priority + { + public IPilot Pilot { get; private set; } + public bool Passed { get; private set; } = false; + private IList options = new List(); + private IPlayOption pass = new Pass(); + + internal Priority(IPilot pilot, bool canPass) + { + this.Pilot = pilot; + if (canPass) + { + options.Add(pass); + } + } + + public void Add(IPlayOption option) + { + options.Add(option); + } + + async public Task Choose() + { + var option = Pilot.Choose(options); + if (option.Legal) + { + await option.Resolve(); + } + else + { + throw new System.Exception(Pilot + " chose an illegal option: " + option); + } + if (option == pass) + { + Passed = true; + } + } + + private class Pass : IPlayOption + { + public bool Legal => true; + + 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 index a325fa4..780c414 100644 --- a/Assets/Scripts/Model/Timing/PriorityWindow.cs +++ b/Assets/Scripts/Model/Timing/PriorityWindow.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using model.play; @@ -7,10 +6,6 @@ namespace model.timing { public abstract class PriorityWindow : ITimingStructure { - protected IList abilities = new List(); - private TaskCompletionSource pass; - public event Action Added = delegate { }; - public event Action Removed = delegate { }; public event AsyncAction Opened; public event AsyncAction Closed; public string Name { get; } @@ -19,18 +14,7 @@ public PriorityWindow(string name) { Name = name; } + public abstract Task Open(); - - public void Add(CardAbility ability) - { - abilities.Add(ability); - Added(this, ability); - } - - public void Remove(CardAbility ability) - { - abilities.Remove(ability); - Removed(this, ability); - } } } diff --git a/Assets/Scripts/Model/Timing/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs index 8ea5be8..f824703 100644 --- a/Assets/Scripts/Model/Timing/Timing.cs +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using model.player; using model.timing.corp; using model.timing.runner; @@ -16,8 +17,9 @@ public class Timing public event Action NextTurnPredicted = delegate { }; public event Action Finished = delegate { }; private bool gameEnded = false; - private Queue turns = new Queue(); - private IPilot priority; + private Queue turns = new Queue(); + private IPilot active; + private IPilot inactive; public Timing(Game game) { @@ -33,10 +35,11 @@ async public Task StartTurns() while (!gameEnded) { var corpTurn = new CorpTurn(game.corp, this); + var runnerTurn = new RunnerTurn(game.runner, this); CorpTurnDefined(corpTurn); - corpTurn.DefinePhases(); + RunnerTurnDefined(runnerTurn); turns.Enqueue(corpTurn); - turns.Enqueue(new RunnerTurn(game.runner, this)); + turns.Enqueue(runnerTurn); await StartNextTurn(); await StartNextTurn(); } @@ -59,9 +62,23 @@ private async Task StartNextTurn() var currentTurn = turns.Dequeue(); CurrentTurnQueued(currentTurn); NextTurnPredicted(turns.Peek()); + UpdateActivePlayer(currentTurn.Owner)ł; await currentTurn.Open(); } + 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(); @@ -73,8 +90,8 @@ public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) var window = new PaidWindow( rezzing, scoring, - acting: game.corp.pilot, - reacting: game.runner.pilot + acting: active, + reacting: inactive ); PaidWindowDefined(window); return window; From 486263a271b90d19a4ae1f5a96e932f68be6933b Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Tue, 4 May 2021 23:21:47 +0200 Subject: [PATCH 08/14] Clarify being installed/rezzed/unrezzed TODO: Continue on `Card.UpdateInstalled`. Decisions made today: * `IEffect` impls will define the controller of effects in construction. Potential "mind control" effects are not worth the complexity (e.g. if Corp could trigger God of War, Corp could be tagged). * `Card.Installed` is internal state, `true` only via `GenericInstall` and `false` only by moving outside of play area. Note that cards in centrals are not in play area, CR 4.7 is about meatspace arrangement. So cards abilities shouldn't need to observe `Card.Moved` to disable themselves. * Also note that cards can be only in one zone and zones are not nested. Servers are *locations* in the play area, not zones. * Each source should track the timing structures it was used in, not just a boolean. It allows tracking "used this run" or "used last turn". * Need `GameRule: ISource` for action window abilities. --- .editorconfig | 3 + Assets/Scripts/Model/Cards/Card.cs | 103 ++++++++++-------- .../Model/Cards/Corp/AdvancedAssemblyLines.cs | 72 ++++-------- .../Scripts/Model/Cards/Corp/PadCampaign.cs | 62 ++++------- Assets/Scripts/Model/Cards/IType.cs | 2 + Assets/Scripts/Model/Cards/Types/Agenda.cs | 2 + Assets/Scripts/Model/Cards/Types/Asset.cs | 2 + .../Types/{Identity.cs => CorpIdentity.cs} | 4 +- .../Types/CorpIdentity.cs.meta} | 2 +- Assets/Scripts/Model/Cards/Types/Event.cs | 2 + Assets/Scripts/Model/Cards/Types/Hardware.cs | 2 + Assets/Scripts/Model/Cards/Types/Operation.cs | 2 + Assets/Scripts/Model/Cards/Types/Program.cs | 2 + Assets/Scripts/Model/Cards/Types/Resource.cs | 2 + .../Model/Cards/Types/RunnerIdentity.cs | 17 +++ .../Types/RunnerIdentity.cs.meta} | 2 +- Assets/Scripts/Model/Game.cs | 10 ++ Assets/Scripts/Model/ICost.cs | 3 +- Assets/Scripts/Model/IEffect.cs | 2 +- .../Scripts/Model/Install/GenericInstall.cs | 8 +- Assets/Scripts/Model/Play/Ability.cs | 7 +- Assets/Scripts/Model/Play/CardAbility.cs | 20 ---- Assets/Scripts/Model/Play/ISource.cs | 13 +++ .../Identity.cs.meta => Play/ISource.cs.meta} | 4 +- .../Model/Play/SimultaneousTriggers.cs | 4 +- Assets/Scripts/Model/Play/Source.cs | 11 -- Assets/Scripts/Model/Player/IPilot.cs | 2 +- Assets/Scripts/Model/Rez/Rezzing.cs | 49 +++------ .../Scripts/Model/Timing/ITimingStructure.cs | 8 +- Assets/Scripts/Model/Timing/Timing.cs | 2 +- Assets/Scripts/View/GUI/GameFinishPanel.cs | 3 +- Assets/Scripts/View/GUI/RigGrid.cs | 8 +- 32 files changed, 205 insertions(+), 230 deletions(-) create mode 100644 .editorconfig rename Assets/Scripts/Model/Cards/Types/{Identity.cs => CorpIdentity.cs} (80%) rename Assets/Scripts/Model/{Play/Source.cs.meta => Cards/Types/CorpIdentity.cs.meta} (83%) create mode 100644 Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs rename Assets/Scripts/Model/{Play/CardAbility.cs.meta => Cards/Types/RunnerIdentity.cs.meta} (83%) delete mode 100644 Assets/Scripts/Model/Play/CardAbility.cs create mode 100644 Assets/Scripts/Model/Play/ISource.cs rename Assets/Scripts/Model/{Cards/Types/Identity.cs.meta => Play/ISource.cs.meta} (71%) delete mode 100644 Assets/Scripts/Model/Play/Source.cs 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/Assets/Scripts/Model/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index 55492eb..9782e68 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -1,114 +1,121 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; 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 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 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; - 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() - { - Active = true; - await Activation.Resolve(); // TODO either keep this or `public Activation`, because it's risking double resolution - Toggled(this, Active); - } + protected abstract Task Activate(); - public void Deactivate() - { - Active = false; - Activation.Disable(); - Toggled(this, Active); + async protected virtual Task Deactivate() { + await Task.CompletedTask; } - public void MoveTo(Zone target) - { + 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; + await UpdateInstalled(); Moved(this, source, target); } - public void Installed() - { - if (Type.Rezzable) + async private Task UpdateInstalled() { + await UpdateActivity(); + } + + 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(); + } + } + } + + public void SetInstalled() { + Installed = true; + await UpdateInstalled(); + 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 b763200..edd1487 100644 --- a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs +++ b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs @@ -10,88 +10,62 @@ using model.timing; using model.zones; -namespace model.cards.corp -{ - public class AdvancedAssemblyLines : Card - { +namespace model.cards.corp { + public class AdvancedAssemblyLines : Card { public AdvancedAssemblyLines(Game game) : base(game) { } 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); 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 Game game; - IEnumerable IEffect.Graphics => new string[] { }; - - public AdvancedAssemblyLinesActivation(Card aal, Game game) - { - this.aal = aal; - this.game = game; - } - - async Task IEffect.Resolve() - { - await game.corp.credits.Gaining(3).Resolve(); - game.Timing.PaidWindowDefined += DefineTrashAbility; - } + async protected override Task Activate() { + await game.corp.credits.Gaining(3).Resolve(); + game.Timing.PaidWindowDefined += DefineTrashAbility; + } - private void DefineTrashAbility(PaidWindow paidWindow) - { - var archives = game.corp.zones.archives.Zone; - var aalInstall = new AdvancedAssemblyLinesInstall(game.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(); - }; - } + private void DefineTrashAbility(PaidWindow paidWindow) { + var archives = game.corp.zones.archives.Zone; + var aalInstall = new AdvancedAssemblyLinesInstall(game.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(); + }; } - private class AdvancedAssemblyLinesInstall : IEffect, IDisposable - { + private class AdvancedAssemblyLinesInstall : IEffect, IDisposable { 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(IPilot pilot) - { + async Task IEffect.Resolve(IPilot pilot) { var installable = await corp.pilot.ChooseACard().Declare("Which card to install?", Installables()); await corp.Installing.InstallingCard(installable).Resolve(pilot); } - private void UpdateInstallables(Zone hqZone) - { + private void UpdateInstallables(Zone hqZone) { ChangedImpact(this, Impactful); } - public void Dispose() - { + public void Dispose() { corp.zones.hq.Zone.Changed -= UpdateInstallables; } } diff --git a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index 6562d67..951be0a 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -1,65 +1,49 @@ -using System; using System.Collections.Generic; using System.Threading.Tasks; using model.cards.types; using model.choices.trash; +using model.costs; using model.play; -using model.timing; using model.timing.corp; -namespace model.cards.corp -{ - public class PadCampaign : Card - { - public PadCampaign(Game game) : base(game) { } +namespace model.cards.corp { + public class PadCampaign : Card { + 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(this, game); 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 CardAbility drip; - private Game game; - private IList phases = new List(); + private Ability drip; + private IList phases = new List(); - public PadCampaignActivation(PadCampaign padCampaign, Game game) - { - this.game = game; - this.drip = game.corp.credits.Gaining(1).ToMandatoryAbility().BelongingTo(padCampaign); - } + public PadCampaign(Game game) : base(game) { + drip = new Ability(new Free(), game.corp.credits.Gaining(1), this); + } - async Task IEffect.Resolve() - { - game.Timing.CorpTurnDefined += RegisterDrip; - await Task.CompletedTask; - } + async protected override Task Activate() { + game.Timing.CorpTurnDefined += RegisterDrip; + await Task.CompletedTask; + } - private void RegisterDrip(CorpTurn turn) - { - var phase = turn.drawPhase; - phases.Add(phase); - phase.TurnBegins.Add(drip); - } + private void RegisterDrip(CorpTurn turn) { + var phase = turn.drawPhase; + phases.Add(phase); + phase.TurnBegins.Add(drip); + } - void IEffect.Disable() - { - game.Timing.CorpTurnDefined -= RegisterDrip; - foreach (var phase in phases) - { - phase.TurnBegins.Remove(drip); - } + async override protected Task Deactivate() { + game.Timing.CorpTurnDefined -= RegisterDrip; + foreach (var phase in phases) { + phase.TurnBegins.Remove(drip); } + await Task.CompletedTask; } } } 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/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/Play/Source.cs.meta b/Assets/Scripts/Model/Cards/Types/CorpIdentity.cs.meta similarity index 83% rename from Assets/Scripts/Model/Play/Source.cs.meta rename to Assets/Scripts/Model/Cards/Types/CorpIdentity.cs.meta index dd27454..4b5a5af 100644 --- a/Assets/Scripts/Model/Play/Source.cs.meta +++ b/Assets/Scripts/Model/Cards/Types/CorpIdentity.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f6dbe1b088421d742aae7c1e22986ecf +guid: 88f8f1f6a33ab1745a431b16237ddd06 MonoImporter: externalObjects: {} serializedVersion: 2 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/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/Play/CardAbility.cs.meta b/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs.meta similarity index 83% rename from Assets/Scripts/Model/Play/CardAbility.cs.meta rename to Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs.meta index ec755e0..9374e31 100644 --- a/Assets/Scripts/Model/Play/CardAbility.cs.meta +++ b/Assets/Scripts/Model/Cards/Types/RunnerIdentity.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2932a60f8c7ceb840b536627c08c33a4 +guid: 2e3aa9a3c8e244447b9aaf560a162f42 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Game.cs b/Assets/Scripts/Model/Game.cs index 942d229..673daed 100644 --- a/Assets/Scripts/Model/Game.cs +++ b/Assets/Scripts/Model/Game.cs @@ -28,5 +28,15 @@ async public void Start(Deck corpDeck, Deck runnerDeck) await runner.Start(this, runnerDeck); await Timing.StartTurns(); } + + public IPilot Pilot(Side side) + { + switch (side) + { + case Side.CORP: return corp.pilot; + case Side.RUNNER: return runner.pilot; + default: throw new Exception("Unclear how to pilot an unknown side: " + side); + } + } } } diff --git a/Assets/Scripts/Model/ICost.cs b/Assets/Scripts/Model/ICost.cs index e433c11..28c2a42 100644 --- a/Assets/Scripts/Model/ICost.cs +++ b/Assets/Scripts/Model/ICost.cs @@ -1,13 +1,12 @@ using System; using System.Threading.Tasks; -using model.player; namespace model { public interface ICost { bool Payable { get; } - Task Pay(IPilot controller); + Task Pay(); event Action ChangedPayability; } } diff --git a/Assets/Scripts/Model/IEffect.cs b/Assets/Scripts/Model/IEffect.cs index e981878..e7f334a 100644 --- a/Assets/Scripts/Model/IEffect.cs +++ b/Assets/Scripts/Model/IEffect.cs @@ -9,7 +9,7 @@ namespace model { public interface IEffect { - Task Resolve(IPilot controller); + Task Resolve(); void Disable(); bool Impactful { get; } event Action ChangedImpact; diff --git a/Assets/Scripts/Model/Install/GenericInstall.cs b/Assets/Scripts/Model/Install/GenericInstall.cs index 5588d54..64f9535 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(); } @@ -98,7 +98,7 @@ async private Task PayInstallCost(IInstallDestination destination) async private Task Place(IInstallDestination destination) { destination.Host(card); - card.Installed(); + card.SetInstalled(); if (card.Faction.Side == Side.RUNNER) { // CR: 8.2.3 diff --git a/Assets/Scripts/Model/Play/Ability.cs b/Assets/Scripts/Model/Play/Ability.cs index 5a3bc1e..6b274db 100644 --- a/Assets/Scripts/Model/Play/Ability.cs +++ b/Assets/Scripts/Model/Play/Ability.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using model.cards; using model.player; namespace model.play @@ -45,13 +44,11 @@ async public Task Resolve() async public Task Trigger() { - await cost.Pay(controller); - await effect.Resolve(controller); + await cost.Pay(); + await effect.Resolve(); Resolved(this); } - public CardAbility BelongingTo(Card card) => new CardAbility(this, card); - 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 d855c6b..0000000 --- a/Assets/Scripts/Model/Play/CardAbility.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Threading.Tasks; -using model.cards; - -namespace model.play -{ - public class CardAbility : IPlayOption - { - public Ability Ability { get; } - public Card Card { get; } - public bool Legal => Ability.Legal; - - public CardAbility(Ability ability, Card card) - { - Ability = ability; - Card = card; - } - - async public Task Resolve() => await Ability.Resolve(); - } -} diff --git a/Assets/Scripts/Model/Play/ISource.cs b/Assets/Scripts/Model/Play/ISource.cs new file mode 100644 index 0000000..677aac5 --- /dev/null +++ b/Assets/Scripts/Model/Play/ISource.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using model.player; +using model.timing; + +namespace model.play +{ + public interface ISource + { + bool Active { get; } + IList Used { get; } // CR 9.1.6 + IPilot Controller { get; } + } +} diff --git a/Assets/Scripts/Model/Cards/Types/Identity.cs.meta b/Assets/Scripts/Model/Play/ISource.cs.meta similarity index 71% rename from Assets/Scripts/Model/Cards/Types/Identity.cs.meta rename to Assets/Scripts/Model/Play/ISource.cs.meta index 0eb9eac..bac9ed5 100644 --- a/Assets/Scripts/Model/Cards/Types/Identity.cs.meta +++ b/Assets/Scripts/Model/Play/ISource.cs.meta @@ -1,7 +1,5 @@ fileFormatVersion: 2 -guid: 6d48ada7e08fdca4ebcec26a67a31000 -timeCreated: 1523796569 -licenseType: Free +guid: d48ea31dc3a1ecb40ba32697f9380185 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Play/SimultaneousTriggers.cs b/Assets/Scripts/Model/Play/SimultaneousTriggers.cs index 0ed9366..8b6bd35 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; } diff --git a/Assets/Scripts/Model/Play/Source.cs b/Assets/Scripts/Model/Play/Source.cs deleted file mode 100644 index d863380..0000000 --- a/Assets/Scripts/Model/Play/Source.cs +++ /dev/null @@ -1,11 +0,0 @@ -using model.player; - -namespace model.play -{ - public interface ISource - { - bool Active { get; } - bool Used { get; set; } - IPilot Controller { get; } - } -} diff --git a/Assets/Scripts/Model/Player/IPilot.cs b/Assets/Scripts/Model/Player/IPilot.cs index 4803fad..f991ea3 100644 --- a/Assets/Scripts/Model/Player/IPilot.cs +++ b/Assets/Scripts/Model/Player/IPilot.cs @@ -12,7 +12,7 @@ namespace model.player public interface IPilot { void Play(Game game); - Task TriggerFromSimultaneous(IEnumerable abilities); + Task TriggerFromSimultaneous(IEnumerable abilities); IDecision ChooseACard(); // IDecision ChooseAZone(); TODO for central access IDecision ChooseAnInstallDestination(); diff --git a/Assets/Scripts/Model/Rez/Rezzing.cs b/Assets/Scripts/Model/Rez/Rezzing.cs index 4c948cc..694a600 100644 --- a/Assets/Scripts/Model/Rez/Rezzing.cs +++ b/Assets/Scripts/Model/Rez/Rezzing.cs @@ -5,27 +5,22 @@ 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 IPlayOption Rez(Card card) - { + public IPlayOption Rez(Card card) { var rezCost = new Conjunction( card.PlayCost, new FaceDown(card), @@ -35,63 +30,51 @@ public IPlayOption Rez(Card card) return new Ability(rezCost, new RezUnconditionally(card)); } - 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/Timing/ITimingStructure.cs b/Assets/Scripts/Model/Timing/ITimingStructure.cs index 13d191f..9d5ef9f 100644 --- a/Assets/Scripts/Model/Timing/ITimingStructure.cs +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs @@ -2,11 +2,15 @@ namespace model.timing { - public interface ITimingStructure where T : ITimingStructure + public interface ITimingStructure where T : ITimingStructure { - Task Open(); event AsyncAction Opened; event AsyncAction Closed; + } + + public interface ITimingStructure + { + Task Open(); string Name { get; } } } diff --git a/Assets/Scripts/Model/Timing/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs index f824703..9496b85 100644 --- a/Assets/Scripts/Model/Timing/Timing.cs +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -62,7 +62,7 @@ private async Task StartNextTurn() var currentTurn = turns.Dequeue(); CurrentTurnQueued(currentTurn); NextTurnPredicted(turns.Peek()); - UpdateActivePlayer(currentTurn.Owner)ł; + UpdateActivePlayer(currentTurn.Owner); await currentTurn.Open(); } diff --git a/Assets/Scripts/View/GUI/GameFinishPanel.cs b/Assets/Scripts/View/GUI/GameFinishPanel.cs index 54c87c1..928ea70 100644 --- a/Assets/Scripts/View/GUI/GameFinishPanel.cs +++ b/Assets/Scripts/View/GUI/GameFinishPanel.cs @@ -1,5 +1,4 @@ -using model; -using UnityEngine; +using UnityEngine; using UnityEngine.UI; namespace view.gui 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)); } } } From 864ee24aeca6189e01634f427c26ea170646c5f7 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Tue, 15 Jun 2021 22:45:14 +0200 Subject: [PATCH 09/14] Fix a lot of compilation errors and warnings To be continued: - "turn begins" should be a REACTION window rather than an event - resume impl from PAD Campaign, AAL and Wyldside --- .../Scripts/Controller/InteractiveDiscard.cs | 2 +- Assets/Scripts/Model/Cards/Card.cs | 13 +- .../Model/Cards/Corp/AdvancedAssemblyLines.cs | 11 +- .../Scripts/Model/Cards/Corp/PadCampaign.cs | 14 +- .../Scripts/Model/Cards/Runner/SpyCamera.cs | 8 +- .../Scripts/Model/Cards/Runner/SureGamble.cs | 8 +- Assets/Scripts/Model/Cards/Runner/Wyldside.cs | 38 +++--- .../Scripts/Model/Choices/Trash/PayToTrash.cs | 2 +- Assets/Scripts/Model/ClickPool.cs | 8 +- Assets/Scripts/Model/Corp.cs | 4 +- Assets/Scripts/Model/Costs/Trash.cs | 9 +- Assets/Scripts/Model/IEffect.cs | 12 -- .../Scripts/Model/Install/GenericInstall.cs | 4 +- Assets/Scripts/Model/Play/Ability.cs | 3 - Assets/Scripts/Model/Play/GameRule.cs | 11 ++ Assets/Scripts/Model/Rez/Rezzing.cs | 2 - Assets/Scripts/Model/Runner.cs | 6 +- Assets/Scripts/Model/Steal/MustSteal.cs | 6 +- Assets/Scripts/Model/Timing/AccessCard.cs | 4 +- Assets/Scripts/Model/Timing/Corp/CorpTurn.cs | 2 +- .../Scripts/Model/Timing/ITimingStructure.cs | 23 ++-- Assets/Scripts/Model/Timing/ITurn.cs | 20 +-- Assets/Scripts/Model/Timing/PaidWindow.cs | 38 ++---- Assets/Scripts/Model/Timing/Priority.cs | 38 ++---- Assets/Scripts/Model/Timing/PriorityWindow.cs | 28 ++-- .../Scripts/Model/Timing/Runner/RunnerTurn.cs | 67 ++++------ Assets/Scripts/Model/Timing/Timing.cs | 4 +- .../Scripts/Model/Zones/Corp/Headquarters.cs | 4 +- Assets/Scripts/Model/Zones/Corp/Remote.cs | 6 +- .../Zones/Corp/ResearchAndDevelopment.cs | 8 +- Assets/Scripts/Model/Zones/Corp/Root.cs | 2 +- Assets/Scripts/Model/Zones/Corp/Zones.cs | 2 +- .../Model/Zones/IInstallDestination.cs | 8 +- Assets/Scripts/Model/Zones/Play.cs | 6 +- Assets/Scripts/Model/Zones/Runner/Grip.cs | 4 +- Assets/Scripts/Model/Zones/Runner/Rig.cs | 6 +- Assets/Scripts/Model/Zones/Runner/Score.cs | 5 +- Assets/Scripts/Model/Zones/Runner/Stack.cs | 12 +- Assets/Scripts/Model/Zones/Runner/Zones.cs | 3 +- .../View/GUI/Brackets/ActionBracket.cs | 2 - .../View/GUI/TimeCross/DayNightCycle.cs | 5 - .../Scripts/View/GUI/TimeCross/FutureTrack.cs | 126 ------------------ .../Scripts/View/GUI/TimeCross/PastTrack.cs | 89 ------------- .../Scripts/View/GUI/TimeCross/PresentBox.cs | 119 ----------------- .../View/GUI/TimeCross/PresentDecision.cs | 13 -- .../Scripts/View/GUI/TimeCross/TimeCross.cs | 21 --- Assets/Tests/Mocks/PassiveCorp.cs | 6 +- Assets/Tests/Observers/PaidAbilityObserver.cs | 3 +- Assets/Tests/PaidAbilityWindowTest.cs | 10 +- Assets/Tests/RunnerDiscardPhaseTest.cs | 2 +- 50 files changed, 215 insertions(+), 632 deletions(-) create mode 100644 Assets/Scripts/Model/Play/GameRule.cs delete mode 100644 Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs delete mode 100644 Assets/Scripts/View/GUI/TimeCross/PastTrack.cs delete mode 100644 Assets/Scripts/View/GUI/TimeCross/PresentBox.cs delete mode 100644 Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs delete mode 100644 Assets/Scripts/View/GUI/TimeCross/TimeCross.cs 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/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index 9782e68..42cdcd6 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -51,12 +51,11 @@ async public Task MoveTo(Zone target) { source.Remove(this); target.Add(this); Zone = target; - await UpdateInstalled(); - Moved(this, source, target); - } - - async private Task UpdateInstalled() { + if (!Zone.InPlayArea) { + Installed = false; + } await UpdateActivity(); + Moved(this, source, target); } async private Task UpdateActivity() { @@ -74,9 +73,9 @@ async private Task UpdateActivity() { } } - public void SetInstalled() { + async public Task SetInstalled() { Installed = true; - await UpdateInstalled(); + await UpdateActivity(); if (Type.Rezzable) { game.corp.Rezzing.Track(this); } diff --git a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs index edd1487..fcc12c8 100644 --- a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs +++ b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs @@ -29,18 +29,19 @@ async protected override Task Activate() { game.Timing.PaidWindowDefined += DefineTrashAbility; } + async protected override Task Deactivate() { + game.Timing.PaidWindowDefined -= DefineTrashAbility; + } + private void DefineTrashAbility(PaidWindow paidWindow) { var archives = game.corp.zones.archives.Zone; var aalInstall = new AdvancedAssemblyLinesInstall(game.corp); var pop = new Ability( cost: new Conjunction(paidWindow.Permission(), new Trash(aal, archives), new Active(aal)), - effect: aalInstall + effect: aalInstall, + aal ).BelongingTo(aal); paidWindow.Add(pop); - aal.Moved += (card, source, target) => { - paidWindow.Remove(pop); - aalInstall.Dispose(); - }; } private class AdvancedAssemblyLinesInstall : IEffect, IDisposable { diff --git a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index 951be0a..8ed345f 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -4,6 +4,7 @@ using model.choices.trash; using model.costs; using model.play; +using model.timing; using model.timing.corp; namespace model.cards.corp { @@ -33,16 +34,17 @@ async protected override Task Activate() { } private void RegisterDrip(CorpTurn turn) { - var phase = turn.drawPhase; - phases.Add(phase); - phase.TurnBegins.Add(drip); + turn.Began += Drip; // TODO actually this should register to a REACTION WINDOW + } + + async private Task Drip(ITurn turn) { + if (Active) { + await drip.Resolve(); + } } async override protected Task Deactivate() { game.Timing.CorpTurnDefined -= RegisterDrip; - foreach (var phase in phases) { - phase.TurnBegins.Remove(drip); - } await Task.CompletedTask; } } 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..6b65024 100644 --- a/Assets/Scripts/Model/Cards/Runner/Wyldside.cs +++ b/Assets/Scripts/Model/Cards/Runner/Wyldside.cs @@ -4,55 +4,55 @@ using model.cards.types; using model.effects; -namespace model.cards.runner -{ - public class Wyldside : Card - { - public Wyldside(Game game) : base(game) { } +namespace model.cards.runner { + public class Wyldside : Card { + private readonly WyldsideTrigger trigger; + + public Wyldside(Game game) : base(game) { + trigger = new WyldsideTrigger(game.runner); + } + 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 - { + protected override Task Activate() { + game.runner.turn.WhenBegins(trigger); + } + + 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) - { + public WyldsideActivation(Runner runner) { this.runner = runner; trigger = new WyldsideTrigger(runner); } - async Task IEffect.Resolve() - { + async Task IEffect.Resolve() { runner.turn.WhenBegins(trigger); await Task.CompletedTask; } } - private class WyldsideTrigger : IEffect - { + 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) - { + public WyldsideTrigger(Runner runner) { sequence = new Sequence(runner.zones.Drawing(2), runner.clicks.Losing(1)); sequence.ChangedImpact += ChangedImpact; } - async Task IEffect.Resolve() - { + async Task IEffect.Resolve() { await sequence.Resolve(); } } 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 76fe520..75932ef 100644 --- a/Assets/Scripts/Model/ClickPool.cs +++ b/Assets/Scripts/Model/ClickPool.cs @@ -78,13 +78,11 @@ public SpendClicks(ClickPool pool, int clicksToSpend) pool.Changed += (_) => ChangedPayability(this, Payable); } - async public Task Pay(IPilot controller) + async public Task Pay() { pool.Spend(clicksToSpend); await Task.CompletedTask; } - - public void Disable() {} } public IEffect Losing(int clicksToLose) => new LoseClicks(this, clicksToLose); @@ -104,13 +102,11 @@ public LoseClicks(ClickPool pool, int clicks) pool.Changed += (_) => ChangedImpact(this, Impactful); } - async Task IEffect.Resolve(IPilot controller) + async Task IEffect.Resolve() { pool.Lose(clicksToLose); await Task.CompletedTask; } - - void IEffect.Disable() {} } } } diff --git a/Assets/Scripts/Model/Corp.cs b/Assets/Scripts/Model/Corp.cs index 8f9e130..d128f90 100644 --- a/Assets/Scripts/Model/Corp.cs +++ b/Assets/Scripts/Model/Corp.cs @@ -38,14 +38,14 @@ 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/Trash.cs b/Assets/Scripts/Model/Costs/Trash.cs index 77d1ce0..723c7c8 100644 --- a/Assets/Scripts/Model/Costs/Trash.cs +++ b/Assets/Scripts/Model/Costs/Trash.cs @@ -26,14 +26,13 @@ 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.Deactivate(); + await card.MoveTo(bin); } } } diff --git a/Assets/Scripts/Model/IEffect.cs b/Assets/Scripts/Model/IEffect.cs index e7f334a..2af8f62 100644 --- a/Assets/Scripts/Model/IEffect.cs +++ b/Assets/Scripts/Model/IEffect.cs @@ -1,26 +1,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using model.costs; -using model.play; -using model.player; namespace model { public interface IEffect { Task Resolve(); - void Disable(); bool Impactful { get; } event Action ChangedImpact; IEnumerable Graphics { get; } } - - static class IEffectExtensions - { - public static Ability ToMandatoryAbility(this IEffect effect, IPilot controller) - { - return new Ability(new Free(), effect, controller); - } - } } diff --git a/Assets/Scripts/Model/Install/GenericInstall.cs b/Assets/Scripts/Model/Install/GenericInstall.cs index 64f9535..6342308 100644 --- a/Assets/Scripts/Model/Install/GenericInstall.cs +++ b/Assets/Scripts/Model/Install/GenericInstall.cs @@ -97,7 +97,7 @@ async private Task PayInstallCost(IInstallDestination destination) // CR: 8.3.5 async private Task Place(IInstallDestination destination) { - destination.Host(card); + await destination.Host(card); card.SetInstalled(); if (card.Faction.Side == Side.RUNNER) { @@ -112,7 +112,5 @@ async private Task TriggerPostInstall() { await Task.FromResult("TODO: Implement TriggerPostInstall"); } - - void IEffect.Disable() { } } } diff --git a/Assets/Scripts/Model/Play/Ability.cs b/Assets/Scripts/Model/Play/Ability.cs index 6b274db..27109a6 100644 --- a/Assets/Scripts/Model/Play/Ability.cs +++ b/Assets/Scripts/Model/Play/Ability.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using model.player; namespace model.play { @@ -9,7 +8,6 @@ public class Ability : IPlayOption public readonly ICost cost; public readonly IEffect effect; public readonly ISource source; - public readonly IPilot controller; public event Action UsabilityChanged = delegate { }; public event Action Resolved = delegate { }; public bool Active { get; private set; } @@ -21,7 +19,6 @@ public Ability(ICost cost, IEffect effect, ISource source) this.cost = cost; this.effect = effect; this.source = source; - this.controller = source.Controller; // CR: 1.13.5 Active = source.Active; // CR: 9.1.8 cost.ChangedPayability += UpdateCost; effect.ChangedImpact += UpdateEffect; diff --git a/Assets/Scripts/Model/Play/GameRule.cs b/Assets/Scripts/Model/Play/GameRule.cs new file mode 100644 index 0000000..2fe312d --- /dev/null +++ b/Assets/Scripts/Model/Play/GameRule.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using model.player; +using model.timing; + +namespace model.play { + public class GameRule : ISource { + bool Active { get; } + IList Used { get; } // CR 9.1.6 + IPilot Controller { get; } + } +} diff --git a/Assets/Scripts/Model/Rez/Rezzing.cs b/Assets/Scripts/Model/Rez/Rezzing.cs index 694a600..fd35a26 100644 --- a/Assets/Scripts/Model/Rez/Rezzing.cs +++ b/Assets/Scripts/Model/Rez/Rezzing.cs @@ -79,8 +79,6 @@ async public Task Resolve() { await card.PlayCost.Pay(); await card.Activate(); } - - public void Disable() { } } } } diff --git a/Assets/Scripts/Model/Runner.cs b/Assets/Scripts/Model/Runner.cs index 688412e..01979be 100644 --- a/Assets/Scripts/Model/Runner.cs +++ b/Assets/Scripts/Model/Runner.cs @@ -4,8 +4,6 @@ using model.player; using model.run; using model.steal; -using model.timing; -using model.timing.runner; using model.zones; using model.zones.runner; @@ -49,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/Corp/CorpTurn.cs b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs index 82b2b00..9df0792 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs @@ -2,7 +2,7 @@ namespace model.timing.corp { - public class CorpTurn : ITimingStructure + public class CorpTurn : ITurn { public CorpDrawPhase drawPhase { get; } public CorpActionPhase actionPhase { get; } diff --git a/Assets/Scripts/Model/Timing/ITimingStructure.cs b/Assets/Scripts/Model/Timing/ITimingStructure.cs index 9d5ef9f..e52ffa3 100644 --- a/Assets/Scripts/Model/Timing/ITimingStructure.cs +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs @@ -1,16 +1,17 @@ using System.Threading.Tasks; -namespace model.timing -{ - public interface ITimingStructure where T : ITimingStructure - { - event AsyncAction Opened; - event AsyncAction Closed; - } - - public interface ITimingStructure - { - Task Open(); +namespace model.timing { + public abstract class ITimingStructure { + event AsyncAction Initiated; + event AsyncAction Completed; string Name { get; } + + async Task Initiate() { + await Initiated?.Invoke(this); + await Proceed(); + await Completed?.Invoke(this); + } + + protected abstract Task Proceed(); } } diff --git a/Assets/Scripts/Model/Timing/ITurn.cs b/Assets/Scripts/Model/Timing/ITurn.cs index eb64e8d..dc750e6 100644 --- a/Assets/Scripts/Model/Timing/ITurn.cs +++ b/Assets/Scripts/Model/Timing/ITurn.cs @@ -1,11 +1,15 @@ -using model.player; +using System.Threading.Tasks; +using model.player; -namespace model.timing -{ - public interface ITurn : ITimingStructure - { - ClickPool Clicks { get; } - Side Side { get; } - IPilot Owner { get; } +namespace model.timing { + public abstract class ITurn : ITimingStructure { + public abstract ClickPool Clicks { get; } + public abstract Side Side { get; } + public abstract IPilot Owner { get; } + public event AsyncAction Began; + + async protected Task Begin() { + await Began?.Invoke(this); + } } } diff --git a/Assets/Scripts/Model/Timing/PaidWindow.cs b/Assets/Scripts/Model/Timing/PaidWindow.cs index 3c4138c..e8ad986 100644 --- a/Assets/Scripts/Model/Timing/PaidWindow.cs +++ b/Assets/Scripts/Model/Timing/PaidWindow.cs @@ -3,62 +3,48 @@ using model.play; using model.player; -namespace model.timing -{ - public class PaidWindow : PriorityWindow - { +namespace model.timing { + public class PaidWindow : PriorityWindow { private bool rezzing; private bool scoring; private IPilot acting; private IPilot reacting; public event Action PriorityGiven = delegate { }; - public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting, string name) : base(name) - { + public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting, string name) : base(name) { this.rezzing = rezzing; this.scoring = scoring; this.acting = acting; this.reacting = reacting; } - internal void GiveOption(IPilot pilot, IPlayOption pop) - { + internal void GiveOption(IPilot pilot, IPlayOption pop) { throw new System.NotImplementedException(); } - async override public Task Open() - { - Opened(this); // TODO inherited events don't work - priority.Add(new Rez(PadCampaign)); - priority.Add(new Aal()); + async override protected Task Proceed() { var bothPlayersCouldAct = false; // CR: 9.2.7.a - while (true) - { - var actingDeclined = await AwaitPass(acting); - if (actingDeclined && bothPlayersCouldAct) - { + while (true) { + var action = await AwaitPass(acting); + if (action.Declined && bothPlayersCouldAct) { break; } - var reactingDeclined = await AwaitPass(reacting); + var reaction = await AwaitPass(reacting); bothPlayersCouldAct = true; - if (reactingDeclined && bothPlayersCouldAct) - { + if (reaction.Declined && bothPlayersCouldAct) { break; } } - Closed(this); } - async private Task AwaitPass(IPilot pilot) - { + async private Task AwaitPass(IPilot pilot) { var priority = new Priority(pilot, canPass: true); // CR: 9.2.4.b PriorityGiven(priority); - var declined = false; while (!priority.Passed) // CR: 9.2.4.c { await priority.Choose(); // CR: 9.2.7.f } - return declined; + return priority; } } } diff --git a/Assets/Scripts/Model/Timing/Priority.cs b/Assets/Scripts/Model/Timing/Priority.cs index 31311b3..c89b062 100644 --- a/Assets/Scripts/Model/Timing/Priority.cs +++ b/Assets/Scripts/Model/Timing/Priority.cs @@ -3,55 +3,45 @@ using model.play; using model.player; -namespace model.timing -{ - public class Priority - { +namespace model.timing { + public class Priority { public IPilot Pilot { get; private set; } 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(IPilot pilot, bool canPass) - { + internal Priority(IPilot pilot, bool canPass) { this.Pilot = pilot; - if (canPass) - { + if (canPass) { options.Add(pass); } } - public void Add(IPlayOption option) - { + public void Add(IPlayOption option) { options.Add(option); } - async public Task Choose() - { + async public Task Choose() { var option = Pilot.Choose(options); - if (option.Legal) - { + if (option.Legal) { await option.Resolve(); - } - else - { + } else { throw new System.Exception(Pilot + " chose an illegal option: " + option); } - if (option == pass) - { + if (option == pass) { Passed = true; + } else { + Declined = false; } } - private class Pass : IPlayOption - { + private class Pass : IPlayOption { public bool Legal => true; - async public Task Resolve() - { + async public Task Resolve() { await Task.CompletedTask; } } } - } diff --git a/Assets/Scripts/Model/Timing/PriorityWindow.cs b/Assets/Scripts/Model/Timing/PriorityWindow.cs index 780c414..23ff0db 100644 --- a/Assets/Scripts/Model/Timing/PriorityWindow.cs +++ b/Assets/Scripts/Model/Timing/PriorityWindow.cs @@ -1,20 +1,22 @@ -using System; -using System.Threading.Tasks; -using model.play; +using System.Threading.Tasks; -namespace model.timing -{ - public abstract class PriorityWindow : ITimingStructure - { - public event AsyncAction Opened; - public event AsyncAction Closed; +namespace model.timing { + + public abstract class PriorityWindow { + public event AsyncAction Opened; + public event AsyncAction Closed; public string Name { get; } - public PriorityWindow(string name) - { + public PriorityWindow(string name) { Name = name; } - - public abstract Task Open(); + + async public Task Open() { + await Opened?.Invoke(); + await Proceed(); + await Closed?.Invoke(); + } + + protected abstract Task Proceed(); } } diff --git a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs index d1cd6ed..a21a1f9 100644 --- a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs +++ b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs @@ -1,38 +1,32 @@ using System.Collections.Generic; using System.Threading.Tasks; using model.play; +using model.player; -namespace model.timing.runner -{ - public class RunnerTurn : ITurn - { +namespace model.timing.runner { + public class RunnerTurn : ITurn { private Runner runner; private Timing timing; - ClickPool ITurn.Clicks => runner.clicks; - Side ITurn.Side => Side.RUNNER; + override public ClickPool Clicks => runner.clicks; + override public Side Side => Side.RUNNER; + override public IPilot Owner { get; } + public string Name { get; } private List turnBeginningTriggers = new List(); - public int Number { get; private set; } = 0; - public event AsyncAction Opened; - public event AsyncAction Closed; + public event AsyncAction Begins; public event AsyncAction TakingAction; public event AsyncAction ActionTaken; - public RunnerTurn(Runner runner, Timing timing) - { - this.runner = runner; + public RunnerTurn(Runner runner, Timing timing, int turnNumber) { + Owner = runner.pilot; + Name = "Runner turn " + turnNumber; } - async Task ITimingStructure.Open() - { - Number++; - await Opened?.Invoke(this); + override async protected Task Proceed() { await ActionPhase(); await DiscardPhase(); - await Closed?.Invoke(this); } - async private Task ActionPhase() - { + async private Task ActionPhase() { runner.clicks.Replenish(); // CR: 5.7.1.a var rez = OpenRezWindow(); // CR: 5.7.1.b var paid = OpenPaidWindow(); // CR: 5.7.1.b @@ -51,34 +45,28 @@ async private Task ActionPhase() await timing.Checkpoint(); // CR: 5.7.1.g } - async private Task OpenPaidWindow() - { + async private Task OpenPaidWindow() { await timing.OpenPaidWindow( acting: game.runner.paidWindow, reacting: game.corp.paidWindow ); } - async private Task OpenRezWindow() - { + async private Task OpenRezWindow() { await game.corp.Rezzing.Window.Open(); } - private void RefillRecurringCredits() - { + private void RefillRecurringCredits() { } - async private Task TriggerTurnBeginning() - { - if (turnBeginningTriggers.Count > 0) - { + 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; @@ -89,8 +77,7 @@ async private Task TakeAction() await paid; } - 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 @@ -101,22 +88,14 @@ async private Task DiscardPhase() await game.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 index 9496b85..d81f52f 100644 --- a/Assets/Scripts/Model/Timing/Timing.cs +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -20,6 +20,8 @@ public class Timing private Queue turns = new Queue(); private IPilot active; private IPilot inactive; + private int corpTurns = 0; + private int runnerTurns = 0; public Timing(Game game) { @@ -35,7 +37,7 @@ async public Task StartTurns() while (!gameEnded) { var corpTurn = new CorpTurn(game.corp, this); - var runnerTurn = new RunnerTurn(game.runner, this); + var runnerTurn = new RunnerTurn(game.runner, this, runnerTurns++); CorpTurnDefined(corpTurn); RunnerTurnDefined(runnerTurn); turns.Enqueue(corpTurn); diff --git a/Assets/Scripts/Model/Zones/Corp/Headquarters.cs b/Assets/Scripts/Model/Zones/Corp/Headquarters.cs index a73b569..9559c16 100644 --- a/Assets/Scripts/Model/Zones/Corp/Headquarters.cs +++ b/Assets/Scripts/Model/Zones/Corp/Headquarters.cs @@ -30,9 +30,9 @@ async public Task Discard() 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/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/ActionBracket.cs b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs index d221d35..ff692bc 100644 --- a/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs +++ b/Assets/Scripts/View/GUI/Brackets/ActionBracket.cs @@ -7,14 +7,12 @@ namespace view.gui.brackets public class ActionBracket { private readonly ITurn turn; - private readonly int turnNumber; private readonly int actionNumber; private readonly Bracket bracket; public ActionBracket(ITurn turn, int actionNumber, Bracket bracket) { this.turn = turn; - this.turnNumber = turn.Number; this.actionNumber = actionNumber; this.bracket = bracket; turn.TakingAction += UpdateActivation; diff --git a/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs b/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs index f6620c1..201b1b3 100644 --- a/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs +++ b/Assets/Scripts/View/GUI/TimeCross/DayNightCycle.cs @@ -14,11 +14,6 @@ public class DayNightCycle private Sprite nightCity = Resources.Load("Images/Background/high-rise-photography-of-city-2039630"); private Image background = FindOrFail("Board").GetComponent(); - public void Wire(Game game) - { - game.CurrentTurnQueued += UpdateBackground; - } - 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 cd1bd93..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.CurrentTurnQueued += CurrentTurn.DisplayCurrent; - CurrentTurn.dayNight = dayNight; - var nextTurn = new GameObject("Next turn").AddComponent(); - nextTurn.gameObject.AttachTo(gameObject); - game.NextTurnPredicted += 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(ITurn turn) - { - dayNight.Paint(background, turn.Side); - TrackClicks(turn, UpdateRemainingClicks); - } - - internal void DisplayNext(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 3dde621..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/PresentBox.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Threading.Tasks; -using model; -using model.cards; -using model.play; -using model.timing; -using UnityEngine; -using UnityEngine.UI; -using static view.gui.GameObjectExtensions; - -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 = FindOrFail("Runner action phase"); - var background = RunnerActionPhase.GetComponent(); - dayNight.Paint(background, Side.RUNNER); - BankCredit = FindOrFail("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 = FindOrFail("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 = FindOrFail("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/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/TimeCross.cs b/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs deleted file mode 100644 index f6e2b33..0000000 --- a/Assets/Scripts/View/GUI/TimeCross/TimeCross.cs +++ /dev/null @@ -1,21 +0,0 @@ -using static view.gui.GameObjectExtensions; -using model; - -namespace view.gui.timecross -{ - public class TimeCross - { - public PresentBox PresentBox { get; private set; } - - public TimeCross(Game game, DayNightCycle dayNight) - { - var pastTrack = FindOrFail("Past").AddComponent(); - pastTrack.DayNight = dayNight; - pastTrack.Wire(game); - var futureTrack = FindOrFail("Future").AddComponent(); - futureTrack.Wire(game, dayNight); - PresentBox = FindOrFail("Present").AddComponent(); - PresentBox.Wire(game, dayNight, futureTrack); - } - } -} 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 index 07ee498..4d44edb 100644 --- a/Assets/Tests/Observers/PaidAbilityObserver.cs +++ b/Assets/Tests/Observers/PaidAbilityObserver.cs @@ -1,5 +1,4 @@ -using model.play; -using model.timing; +using model.timing; namespace tests.observers { diff --git a/Assets/Tests/PaidAbilityWindowTest.cs b/Assets/Tests/PaidAbilityWindowTest.cs index 4ec1416..66bb7ff 100644 --- a/Assets/Tests/PaidAbilityWindowTest.cs +++ b/Assets/Tests/PaidAbilityWindowTest.cs @@ -42,7 +42,7 @@ async public void ShouldPopHopper() { await passiveCorp.SkipTurn(); var zones = game.runner.zones; - hopper.MoveTo(zones.grip.zone); + await 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); @@ -61,7 +61,7 @@ async public void ShouldPopHopper() async public void ShouldUsePaidAbilityOnRunnerTurn() { await passiveCorp.SkipTurn(); - hopper.MoveTo(game.runner.zones.grip.zone); + await hopper.MoveTo(game.runner.zones.grip.zone); ffRunner.FastForwardToActionPhase(); await RunnerAction(); @@ -80,7 +80,7 @@ async public void ShouldUsePaidAbilityOnRunnerTurn() PassWindow(); await CorpAction(); PassWindow(); - passiveCorp.DiscardRandomCards(); + await passiveCorp.DiscardRandomCards(); PassWindow(); PassWindow(); PassWindow(); @@ -97,7 +97,7 @@ async public void ShouldUsePaidAbilityOnRunnerTurn() async public void ShouldUsePaidAbilityOnCorpTurn() { await passiveCorp.SkipTurn(); - hopper.MoveTo(game.runner.zones.grip.zone); + await hopper.MoveTo(game.runner.zones.grip.zone); await RunnerAction(); await RunnerAction(); @@ -115,7 +115,7 @@ async public void ShouldUsePaidAbilityOnCorpTurn() await popHopper.Ability.Trigger(); PassWindow(); await CorpAction(); - passiveCorp.DiscardRandomCards(); + await passiveCorp.DiscardRandomCards(); await RunnerAction(); await RunnerAction(); await RunnerAction(); 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(); From 9aaa9fc6fbd58ca4cd4adbd5e39c91697edfefa1 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Tue, 6 Jul 2021 23:52:27 +0200 Subject: [PATCH 10/14] Who should accumulate abilities? Priorities, priority windows, pilots? What's the deal with imminent instructions and pending abilities? --- Assets/Scripts/Model/Cards/Card.cs | 20 ++- .../Model/Cards/Corp/AdvancedAssemblyLines.cs | 48 +++--- .../Scripts/Model/Cards/Corp/PadCampaign.cs | 30 ++-- Assets/Scripts/Model/Cards/Runner/Wyldside.cs | 61 +++----- .../Scripts/Model/Install/GenericInstall.cs | 4 +- Assets/Scripts/Model/Play/Ability.cs | 45 +++--- Assets/Scripts/Model/Play/GameRule.cs | 10 +- .../Play/GameRule.cs.meta} | 2 +- Assets/Scripts/Model/Play/IPlayOption.cs | 1 + Assets/Scripts/Model/Play/ISource.cs | 4 +- .../Model/Timing/Corp/CorpDrawPhase.cs | 31 +--- Assets/Scripts/Model/Timing/Corp/CorpTurn.cs | 41 ++--- .../Scripts/Model/Timing/ITimingStructure.cs | 12 +- Assets/Scripts/Model/Timing/ITurn.cs | 8 +- Assets/Scripts/Model/Timing/PaidWindow.cs | 8 +- Assets/Scripts/Model/Timing/Priority.cs | 23 +-- Assets/Scripts/Model/Timing/PriorityWindow.cs | 6 +- Assets/Scripts/Model/Timing/ReactionWindow.cs | 44 ++++++ .../Timing/ReactionWindow.cs.meta} | 2 +- .../Scripts/Model/Timing/Runner/RunnerTurn.cs | 17 +-- Assets/Scripts/Model/Timing/Timing.cs | 80 ++++------ Assets/Scripts/View/GUI/GameFinishPanel.cs | 3 +- Assets/Scripts/View/GUI/GameFlowView.cs | 2 +- .../View/GUI/TimeCross/PresentBox.cs.meta | 11 -- .../GUI/TimeCross/PresentDecision.cs.meta | 11 -- .../View/GUI/TimeCross/TimeCross.cs.meta | 11 -- Assets/Tests/Observers/PaidAbilityObserver.cs | 19 --- .../Observers/PaidAbilityObserver.cs.meta | 11 -- Assets/Tests/PaidAbilityWindowTest.cs | 144 ------------------ Assets/Tests/PaidAbilityWindowTest.cs.meta | 11 -- 30 files changed, 248 insertions(+), 472 deletions(-) rename Assets/Scripts/{View/GUI/TimeCross/FutureTrack.cs.meta => Model/Play/GameRule.cs.meta} (83%) create mode 100644 Assets/Scripts/Model/Timing/ReactionWindow.cs rename Assets/Scripts/{View/GUI/TimeCross/PastTrack.cs.meta => Model/Timing/ReactionWindow.cs.meta} (83%) delete mode 100644 Assets/Scripts/View/GUI/TimeCross/PresentBox.cs.meta delete mode 100644 Assets/Scripts/View/GUI/TimeCross/PresentDecision.cs.meta delete mode 100644 Assets/Scripts/View/GUI/TimeCross/TimeCross.cs.meta delete mode 100644 Assets/Tests/Observers/PaidAbilityObserver.cs delete mode 100644 Assets/Tests/Observers/PaidAbilityObserver.cs.meta delete mode 100644 Assets/Tests/PaidAbilityWindowTest.cs delete mode 100644 Assets/Tests/PaidAbilityWindowTest.cs.meta diff --git a/Assets/Scripts/Model/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index 42cdcd6..8e288b4 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using model.choices.trash; using model.play; @@ -11,6 +12,7 @@ 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; } @@ -37,10 +39,22 @@ public Card(Game game) { this.Zone.Add(this); } + async public Task BecomeActive() { + await Activate(); + Active = true; + ChangedActivation(this); + } + protected abstract Task Activate(); - async protected virtual Task Deactivate() { - await Task.CompletedTask; + async protected virtual Task BecomeInactive() { + await Deactivate(); + Active = false; + ChangedActivation(this); + } + + protected virtual Task Deactivate() { + return Task.CompletedTask; } async public Task MoveTo(Zone target) { diff --git a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs index fcc12c8..e3bc395 100644 --- a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs +++ b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs @@ -6,13 +6,12 @@ using model.choices.trash; using model.costs; using model.play; -using model.player; using model.timing; using model.zones; namespace model.cards.corp { public class AdvancedAssemblyLines : Card { - public AdvancedAssemblyLines(Game game) : base(game) { } + private Ability pop; override public string FaceupArt => "advanced-assembly-lines"; override public string Name => "Advanced Assembly Lines"; override public Faction Faction => Factions.HAAS_BIOROID; @@ -24,27 +23,30 @@ public AdvancedAssemblyLines(Game game) : base(game) { } new PayToTrash(1, this, game) }; + public AdvancedAssemblyLines(Game game) : base(game) { + pop = new Ability( + cost: new Trash(this, game.corp.zones.archives.Zone), + effect: new AdvancedAssemblyLinesInstall(game.corp), + source: this, + mandatory: false + ); + } + async protected override Task Activate() { await game.corp.credits.Gaining(3).Resolve(); - game.Timing.PaidWindowDefined += DefineTrashAbility; + game.Timing.PaidWindowDefined += DeferPop; } - async protected override Task Deactivate() { - game.Timing.PaidWindowDefined -= DefineTrashAbility; + protected override Task Deactivate() { + game.Timing.PaidWindowDefined -= DeferPop; + return Task.CompletedTask; } - private void DefineTrashAbility(PaidWindow paidWindow) { - var archives = game.corp.zones.archives.Zone; - var aalInstall = new AdvancedAssemblyLinesInstall(game.corp); - var pop = new Ability( - cost: new Conjunction(paidWindow.Permission(), new Trash(aal, archives), new Active(aal)), - effect: aalInstall, - aal - ).BelongingTo(aal); - paidWindow.Add(pop); + private void DeferPop(PaidWindow paidWindow) { + paidWindow.GiveOption(game.corp.pilot, pop); } - private class AdvancedAssemblyLinesInstall : IEffect, IDisposable { + private class AdvancedAssemblyLinesInstall : IEffect { public bool Impactful => Installables().Count > 0; public event Action ChangedImpact = delegate { }; IEnumerable IEffect.Graphics => new string[] { }; @@ -55,19 +57,17 @@ public AdvancedAssemblyLinesInstall(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(IPilot pilot) { - var installable = await corp.pilot.ChooseACard().Declare("Which card to install?", Installables()); - await corp.Installing.InstallingCard(installable).Resolve(pilot); - } - private void UpdateInstallables(Zone hqZone) { ChangedImpact(this, Impactful); } - public 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/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index 8ed345f..abcb8a5 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -4,7 +4,6 @@ using model.choices.trash; using model.costs; using model.play; -using model.timing; using model.timing.corp; namespace model.cards.corp { @@ -25,27 +24,26 @@ public class PadCampaign : Card { private IList phases = new List(); public PadCampaign(Game game) : base(game) { - drip = new Ability(new Free(), game.corp.credits.Gaining(1), this); + drip = new Ability( + cost: new Free(), + effect: game.corp.credits.Gaining(1), + source: this, + mandatory: true + ); } - async protected override Task Activate() { - game.Timing.CorpTurnDefined += RegisterDrip; - await Task.CompletedTask; + protected override Task Activate() { + game.Timing.CorpTurnDefined += DeferDrip; + return Task.CompletedTask; } - private void RegisterDrip(CorpTurn turn) { - turn.Began += Drip; // TODO actually this should register to a REACTION WINDOW + override protected Task Deactivate() { + game.Timing.CorpTurnDefined -= DeferDrip; + return Task.CompletedTask; } - async private Task Drip(ITurn turn) { - if (Active) { - await drip.Resolve(); - } - } - - async override protected Task Deactivate() { - game.Timing.CorpTurnDefined -= RegisterDrip; - await Task.CompletedTask; + private void DeferDrip(CorpTurn turn) { + turn.Begins.Offer(game.corp.pilot, drip); } } } diff --git a/Assets/Scripts/Model/Cards/Runner/Wyldside.cs b/Assets/Scripts/Model/Cards/Runner/Wyldside.cs index 6b65024..8b1be4a 100644 --- a/Assets/Scripts/Model/Cards/Runner/Wyldside.cs +++ b/Assets/Scripts/Model/Cards/Runner/Wyldside.cs @@ -1,17 +1,13 @@ -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 { - private readonly WyldsideTrigger trigger; - - public Wyldside(Game game) : base(game) { - trigger = new WyldsideTrigger(game.runner); - } - + private Ability party; override public string FaceupArt => "wyldside"; override public string Name => "Wyldside"; override public Faction Faction => Factions.ANARCH; @@ -19,42 +15,27 @@ public Wyldside(Game game) : base(game) { override public ICost PlayCost => game.runner.credits.PayingForPlaying(this, 3); override public IType Type => new Resource(game); - protected override Task Activate() { - game.runner.turn.WhenBegins(trigger); + 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 + ); } - private class WyldsideActivation : IEffect { - private Runner runner; - - 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; - } + async protected override Task Activate() { + game.Timing.RunnerTurnDefined += DeferParty; + 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; - } + override protected Task Deactivate() { + game.Timing.RunnerTurnDefined -= DeferParty; + return Task.CompletedTask; + } - async Task IEffect.Resolve() { - await sequence.Resolve(); - } + private void DeferParty(RunnerTurn turn) { + turn.Begins.Offer(game.runner.pilot, party); } } } diff --git a/Assets/Scripts/Model/Install/GenericInstall.cs b/Assets/Scripts/Model/Install/GenericInstall.cs index 6342308..a5187c7 100644 --- a/Assets/Scripts/Model/Install/GenericInstall.cs +++ b/Assets/Scripts/Model/Install/GenericInstall.cs @@ -98,12 +98,12 @@ async private Task PayInstallCost(IInstallDestination destination) async private Task Place(IInstallDestination destination) { await destination.Host(card); - card.SetInstalled(); + 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 27109a6..b59dece 100644 --- a/Assets/Scripts/Model/Play/Ability.cs +++ b/Assets/Scripts/Model/Play/Ability.cs @@ -1,49 +1,54 @@ using System; using System.Threading.Tasks; -namespace model.play -{ - public class Ability : IPlayOption - { +namespace model.play { + public class Ability : IPlayOption { public readonly ICost cost; public readonly IEffect effect; public readonly ISource source; + public bool Mandatory { get; } + private bool active; public event Action UsabilityChanged = delegate { }; public event Action Resolved = delegate { }; - public bool Active { get; private set; } + public bool Usable => cost.Payable && effect.Impactful; // CR: 1.2.5 - public bool Legal => Active && Usable; + public bool Legal => active && Usable; - public Ability(ICost cost, IEffect effect, ISource source) - { + public Ability(ICost cost, IEffect effect, ISource source, bool mandatory) { this.cost = cost; this.effect = effect; this.source = source; - Active = source.Active; // CR: 9.1.8 + 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 Resolve() - { + async public Task Resolve() { await Trigger(); } - async public Task Trigger() - { - await cost.Pay(); - await effect.Resolve(); - Resolved(this); + 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() => cost + " : " + effect; diff --git a/Assets/Scripts/Model/Play/GameRule.cs b/Assets/Scripts/Model/Play/GameRule.cs index 2fe312d..ab6f2f0 100644 --- a/Assets/Scripts/Model/Play/GameRule.cs +++ b/Assets/Scripts/Model/Play/GameRule.cs @@ -1,11 +1,13 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using model.player; using model.timing; namespace model.play { public class GameRule : ISource { - bool Active { get; } - IList Used { get; } // CR 9.1.6 - IPilot Controller { get; } + 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/View/GUI/TimeCross/FutureTrack.cs.meta b/Assets/Scripts/Model/Play/GameRule.cs.meta similarity index 83% rename from Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs.meta rename to Assets/Scripts/Model/Play/GameRule.cs.meta index a50dcc8..5cb1b0a 100644 --- a/Assets/Scripts/View/GUI/TimeCross/FutureTrack.cs.meta +++ b/Assets/Scripts/Model/Play/GameRule.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 952b8f4f02ddd254bbd2c3bdf7328a7f +guid: a3e4d18f1251a3446a2f8761f9ddc291 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Play/IPlayOption.cs b/Assets/Scripts/Model/Play/IPlayOption.cs index efe647f..c63fdba 100644 --- a/Assets/Scripts/Model/Play/IPlayOption.cs +++ b/Assets/Scripts/Model/Play/IPlayOption.cs @@ -5,6 +5,7 @@ namespace model.play public interface IPlayOption { bool Legal { get; } + bool Mandatory { get; } Task Resolve(); } } diff --git a/Assets/Scripts/Model/Play/ISource.cs b/Assets/Scripts/Model/Play/ISource.cs index 677aac5..1203134 100644 --- a/Assets/Scripts/Model/Play/ISource.cs +++ b/Assets/Scripts/Model/Play/ISource.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using model.player; using model.timing; @@ -7,6 +8,7 @@ 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/Timing/Corp/CorpDrawPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs index b852da0..2a3f5a7 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs @@ -1,45 +1,30 @@ using System.Threading.Tasks; -using model.play; -namespace model.timing.corp -{ - public class CorpDrawPhase : ITimingStructure - { +namespace model.timing.corp { + public class CorpDrawPhase : ITimingStructure { private Corp corp; private Timing timing; - public PriorityWindow TurnBegins = new PriorityWindow("Corp turn begins"); - public string Name => "Corp draw phase"; - public event AsyncAction Opened; - public event AsyncAction Closed; + private ReactionWindow turnBegins; - internal CorpDrawPhase(Corp corp, Timing timing) - { + internal CorpDrawPhase(Corp corp, Timing timing, ReactionWindow turnBegins) : base("Corp draw phase") { this.corp = corp; this.timing = timing; } - public async Task Open() - { + async protected override Task Proceed() { corp.clicks.Replenish(); // CR: 5.6.1.a await timing.OpenPaidWindow(rezzing: true, scoring: true); // CR: 5.6.1.b RefillRecurringCredits(); // CR: 5.6.1.c - await TurnBegins.Open(); // CR: 5.6.1.d + 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() - { + private void RefillRecurringCredits() { } - async private Task TriggerTurnBeginning() - { - await TurnBegins.Open(); - } - - async private Task MandatoryDraw() - { + async private Task MandatoryDraw() { await corp.zones.Drawing(1).Resolve(); } } diff --git a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs index 9df0792..de430d4 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpTurn.cs @@ -1,47 +1,37 @@ using System.Threading.Tasks; +using model.player; -namespace model.timing.corp -{ - public class CorpTurn : ITurn - { +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; - public ClickPool Clicks => corp.clicks; - public Side Side => Side.CORP; - public string Name { get; } - public event AsyncAction Opened; - public event AsyncAction Closed; + override public ClickPool Clicks => corp.clicks; + override public Side Side => Side.CORP; + override public IPilot Owner => corp.pilot; - public CorpTurn(Corp corp, Timing timing, int number) - { + public CorpTurn(Corp corp, Timing timing, int number) : base("Corp turn " + number) { this.corp = corp; this.timing = timing; - Name = "Corp turn " + number; - drawPhase = new CorpDrawPhase(corp, timing); + drawPhase = new CorpDrawPhase(corp, timing, Begins); actionPhase = new CorpActionPhase(corp, timing); discardPhase = new CorpDiscardPhase(); } - public CorpTurn(Corp corp, Timing timing) - { + public CorpTurn(Corp corp, Timing timing) { this.corp = corp; this.timing = timing; } - public async Task Open() - { - await Opened?.Invoke(this); + async protected override Task Proceed() { await drawPhase.Open(); await actionPhase.Open(); await discardPhase.Open(); - await Closed?.Invoke(this); } - 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 @@ -52,17 +42,14 @@ async private Task DiscardPhase() await timing.Checkpoint(); // CR: 5.6.3.e } - async private Task Discard() - { + async private Task Discard() { var hq = corp.zones.hq; - while (hq.Zone.Count > 5) - { + while (hq.Zone.Count > 5) { await hq.Discard(); } } - private void TriggerTurnEnding() - { + private void TriggerTurnEnding() { } } } diff --git a/Assets/Scripts/Model/Timing/ITimingStructure.cs b/Assets/Scripts/Model/Timing/ITimingStructure.cs index e52ffa3..8bcf30b 100644 --- a/Assets/Scripts/Model/Timing/ITimingStructure.cs +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs @@ -4,9 +4,17 @@ namespace model.timing { public abstract class ITimingStructure { event AsyncAction Initiated; event AsyncAction Completed; - string Name { get; } + public string Name { get; } + public readonly ReactionWindow Begins; + public readonly ReactionWindow Ends; - async Task Initiate() { + protected ITimingStructure(string name) { + this.Name = name; + Begins = new ReactionWindow(Name + " begins"); + Ends = new ReactionWindow(Name + " ends"); + } + + async public Task Initiate() { await Initiated?.Invoke(this); await Proceed(); await Completed?.Invoke(this); diff --git a/Assets/Scripts/Model/Timing/ITurn.cs b/Assets/Scripts/Model/Timing/ITurn.cs index dc750e6..b74c86e 100644 --- a/Assets/Scripts/Model/Timing/ITurn.cs +++ b/Assets/Scripts/Model/Timing/ITurn.cs @@ -1,15 +1,13 @@ -using System.Threading.Tasks; -using model.player; +using model.player; namespace model.timing { public abstract class ITurn : ITimingStructure { + public abstract ClickPool Clicks { get; } public abstract Side Side { get; } public abstract IPilot Owner { get; } - public event AsyncAction Began; - async protected Task Begin() { - await Began?.Invoke(this); + protected ITurn(string name) : base(name) { } } } diff --git a/Assets/Scripts/Model/Timing/PaidWindow.cs b/Assets/Scripts/Model/Timing/PaidWindow.cs index e8ad986..0b28c41 100644 --- a/Assets/Scripts/Model/Timing/PaidWindow.cs +++ b/Assets/Scripts/Model/Timing/PaidWindow.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using model.play; using model.player; @@ -9,7 +8,6 @@ public class PaidWindow : PriorityWindow { private bool scoring; private IPilot acting; private IPilot reacting; - public event Action PriorityGiven = delegate { }; public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting, string name) : base(name) { this.rezzing = rezzing; @@ -38,11 +36,11 @@ async override protected Task Proceed() { } async private Task AwaitPass(IPilot pilot) { - var priority = new Priority(pilot, canPass: true); // CR: 9.2.4.b + var priority = new Priority(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 + 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 index c89b062..870425e 100644 --- a/Assets/Scripts/Model/Timing/Priority.cs +++ b/Assets/Scripts/Model/Timing/Priority.cs @@ -1,34 +1,34 @@ using System.Collections.Generic; using System.Threading.Tasks; using model.play; -using model.player; namespace model.timing { public class Priority { - public IPilot Pilot { get; private set; } 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(IPilot pilot, bool canPass) { - this.Pilot = pilot; + internal Priority(bool canPass) { if (canPass) { options.Add(pass); } } - public void Add(IPlayOption option) { + public void Offer(IPlayOption option) { options.Add(option); } - async public Task Choose() { - var option = Pilot.Choose(options); - if (option.Legal) { - await option.Resolve(); - } else { - throw new System.Exception(Pilot + " chose an illegal option: " + 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 { @@ -38,6 +38,7 @@ async public Task Choose() { 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/PriorityWindow.cs b/Assets/Scripts/Model/Timing/PriorityWindow.cs index 23ff0db..1d3b57f 100644 --- a/Assets/Scripts/Model/Timing/PriorityWindow.cs +++ b/Assets/Scripts/Model/Timing/PriorityWindow.cs @@ -1,10 +1,12 @@ -using System.Threading.Tasks; +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) { @@ -18,5 +20,7 @@ async public Task Open() { } protected abstract Task Proceed(); + + protected } } diff --git a/Assets/Scripts/Model/Timing/ReactionWindow.cs b/Assets/Scripts/Model/Timing/ReactionWindow.cs new file mode 100644 index 0000000..8adae03 --- /dev/null +++ b/Assets/Scripts/Model/Timing/ReactionWindow.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using model.play; +using model.player; + +namespace model.timing { + + // CR: 9.2.8 + public class ReactionWindow : PriorityWindow { + private IPilot acting; + private IPilot reacting; + private Dictionary mandatory = new Dictionary(); + private Dictionary optional = new Dictionary(); + + public ReactionWindow(string name) : base(name) { + + } + + 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/View/GUI/TimeCross/PastTrack.cs.meta b/Assets/Scripts/Model/Timing/ReactionWindow.cs.meta similarity index 83% rename from Assets/Scripts/View/GUI/TimeCross/PastTrack.cs.meta rename to Assets/Scripts/Model/Timing/ReactionWindow.cs.meta index 06675ed..abb0492 100644 --- a/Assets/Scripts/View/GUI/TimeCross/PastTrack.cs.meta +++ b/Assets/Scripts/Model/Timing/ReactionWindow.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 6e0fdf67a5e8a4e408a2d472410a3b3f +guid: c5fe2babadbe05c4caab1441030212b6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs index a21a1f9..d72ab7b 100644 --- a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs +++ b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs @@ -9,16 +9,13 @@ public class RunnerTurn : ITurn { private Timing timing; override public ClickPool Clicks => runner.clicks; override public Side Side => Side.RUNNER; - override public IPilot Owner { get; } - public string Name { get; } + override public IPilot Owner => runner.pilot; private List turnBeginningTriggers = new List(); - public event AsyncAction Begins; public event AsyncAction TakingAction; public event AsyncAction ActionTaken; - public RunnerTurn(Runner runner, Timing timing, int turnNumber) { - Owner = runner.pilot; - Name = "Runner turn " + turnNumber; + public RunnerTurn(Runner runner, Timing timing, int turnNumber): base("Runner turn " + turnNumber) { + } override async protected Task Proceed() { @@ -33,7 +30,7 @@ async private Task ActionPhase() { await rez; await paid; RefillRecurringCredits(); // CR: 5.7.1.c - await TriggerTurnBeginning(); // CR: 5.7.1.d + await Begins.Open(); // CR: 5.7.1.d var rez2 = OpenRezWindow(); // CR: 5.7.1.e var paid2 = OpenPaidWindow(); // CR: 5.7.1.e await rez2; @@ -60,12 +57,6 @@ private void RefillRecurringCredits() { } - async private Task TriggerTurnBeginning() { - if (turnBeginningTriggers.Count > 0) { - await new SimultaneousTriggers(turnBeginningTriggers.Copy()).AllTriggered(game.runner.pilot); - } - } - async private Task TakeAction() { var actionTaking = game.runner.Acting.TakeAction(); TakingAction?.Invoke(this); diff --git a/Assets/Scripts/Model/Timing/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs index d81f52f..618ee63 100644 --- a/Assets/Scripts/Model/Timing/Timing.cs +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -5,13 +5,12 @@ using model.timing.corp; using model.timing.runner; -namespace model.timing -{ - public class Timing - { +namespace model.timing { + public class Timing { private Game game; private Checkpoint checkpoint; 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 { }; @@ -23,19 +22,15 @@ public class Timing private int corpTurns = 0; private int runnerTurns = 0; - public Timing(Game game) - { + public Timing(Game game) { this.game = game; game.corp.zones.rd.Decked += DeckCorp; game.runner.zones.score.StolenEnough += StealEnough; } - async public Task StartTurns() - { - try - { - while (!gameEnded) - { + async public Task StartTurns() { + try { + while (!gameEnded) { var corpTurn = new CorpTurn(game.corp, this); var runnerTurn = new RunnerTurn(game.runner, this, runnerTurns++); CorpTurnDefined(corpTurn); @@ -46,49 +41,38 @@ async public Task StartTurns() await StartNextTurn(); } } - catch (Exception e) - { - if (gameEnded) - { + catch (Exception e) { + if (gameEnded) { UnityEngine.Debug.Log("The game is over! " + e.Message); - } - else - { + } else { throw new Exception("Failed a turn", e); } } } - private async Task StartNextTurn() - { + private async Task StartNextTurn() { var currentTurn = turns.Dequeue(); CurrentTurnQueued(currentTurn); NextTurnPredicted(turns.Peek()); UpdateActivePlayer(currentTurn.Owner); - await currentTurn.Open(); + await currentTurn.Initiate(); } - private void UpdateActivePlayer(IPilot active) - { + private void UpdateActivePlayer(IPilot active) { this.active = active; - if (active == game.corp.pilot) - { + if (active == game.corp.pilot) { inactive = game.runner.pilot; - } - else - { + } else { inactive = game.corp.pilot; } } - async public Task OpenActionWindow() - { + async public Task OpenActionWindow() { var window = new ActionWindow(); await window.Open(); } - public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) - { + public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) { var window = new PaidWindow( rezzing, scoring, @@ -99,27 +83,22 @@ public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) return window; } - async public Task OpenPaidWindow(PaidWindow acting, PaidWindow reacting) - { + async public Task OpenPaidWindow(PaidWindow acting, PaidWindow reacting) { var bothPlayersCouldAct = false; - while (true) - { + while (true) { var actingDeclined = await acting.AwaitPass(); - if (actingDeclined && bothPlayersCouldAct) - { + if (actingDeclined && bothPlayersCouldAct) { break; } var reactingDeclined = await reacting.AwaitPass(); bothPlayersCouldAct = true; - if (reactingDeclined && bothPlayersCouldAct) - { + if (reactingDeclined && bothPlayersCouldAct) { break; } } } - private void DeckCorp(Corp corp) - { + private void DeckCorp(Corp corp) { Finish(new GameFinish( winner: "The Runner", loser: "The Corp", @@ -127,8 +106,7 @@ private void DeckCorp(Corp corp) )); } - private void StealEnough() - { + private void StealEnough() { Finish(new GameFinish( winner: "The Runner", loser: "The Corp", @@ -136,27 +114,23 @@ private void StealEnough() )); } - private void Finish(GameFinish finish) - { + private void Finish(GameFinish finish) { gameEnded = true; Finished(finish); throw new Exception("Game over, " + finish.reason); } - async public Task Checkpoint() - { + async public Task Checkpoint() { await checkpoint.Check(); } } - public class GameFinish - { + public class GameFinish { public string winner; public string loser; public string reason; - public GameFinish(string winner, string loser, string reason) - { + public GameFinish(string winner, string loser, string reason) { this.winner = winner; this.loser = loser; this.reason = reason; diff --git a/Assets/Scripts/View/GUI/GameFinishPanel.cs b/Assets/Scripts/View/GUI/GameFinishPanel.cs index 928ea70..019711a 100644 --- a/Assets/Scripts/View/GUI/GameFinishPanel.cs +++ b/Assets/Scripts/View/GUI/GameFinishPanel.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using model.timing; +using UnityEngine; using UnityEngine.UI; namespace view.gui diff --git a/Assets/Scripts/View/GUI/GameFlowView.cs b/Assets/Scripts/View/GUI/GameFlowView.cs index b31d3a6..886d1ba 100644 --- a/Assets/Scripts/View/GUI/GameFlowView.cs +++ b/Assets/Scripts/View/GUI/GameFlowView.cs @@ -37,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/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.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.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/Tests/Observers/PaidAbilityObserver.cs b/Assets/Tests/Observers/PaidAbilityObserver.cs deleted file mode 100644 index 4d44edb..0000000 --- a/Assets/Tests/Observers/PaidAbilityObserver.cs +++ /dev/null @@ -1,19 +0,0 @@ -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 66bb7ff..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; - await 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(); - await 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(); - await 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(); - await 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(); - await 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: From 1487e3d7c3a74a586346a0ebab311a0b98ed7818 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Sun, 13 Feb 2022 23:16:59 +0100 Subject: [PATCH 11/14] Progress on card text syntax Expand `ConditionalAbility`. Next, I'd try: * use new card text for PAD Campaign, Wyldside and Sure Gamble * include targeting, e.g. basic play ability is targeting a card in hand * note 1.15.1.b --- Assets/Scripts/Model/Abilities.meta | 8 +++ Assets/Scripts/Model/Abilities/Abilities.cs | 34 +++++++++ .../Scripts/Model/Abilities/Abilities.cs.meta | 11 +++ .../Model/Abilities/ConditionalAbility.cs | 48 +++++++++++++ .../Abilities/ConditionalAbility.cs.meta | 11 +++ Assets/Scripts/Model/Abilities/IAbility.cs | 11 +++ .../Scripts/Model/Abilities/IAbility.cs.meta | 11 +++ .../Model/Abilities/PadCampaignDrip.cs | 8 +++ .../Model/Abilities/PadCampaignDrip.cs.meta | 11 +++ Assets/Scripts/Model/Cards/Card.cs | 9 +++ .../Scripts/Model/Cards/Corp/PadCampaign.cs | 47 +++++++++++-- Assets/Scripts/Model/Cards/Text.meta | 8 +++ Assets/Scripts/Model/Cards/Text/Text.cs | 70 +++++++++++++++++++ Assets/Scripts/Model/Cards/Text/Text.cs.meta | 11 +++ Assets/Scripts/Model/Corp.cs | 2 +- Assets/Scripts/Model/Game.cs | 3 + Assets/Scripts/Model/Play/Ability.cs | 10 +-- Assets/Scripts/Model/Player/IPlayer.cs | 9 +++ Assets/Scripts/Model/Player/IPlayer.cs.meta | 11 +++ Assets/Scripts/Model/Runner.cs | 2 +- Assets/Scripts/Model/Timing/Checkpoint.cs | 51 ++++++-------- .../Scripts/Model/Timing/ITimingStructure.cs | 2 - Assets/Scripts/Model/Timing/ReactionWindow.cs | 6 +- Assets/Scripts/Model/Timing/Timing.cs | 12 +++- 24 files changed, 356 insertions(+), 50 deletions(-) create mode 100644 Assets/Scripts/Model/Abilities.meta create mode 100644 Assets/Scripts/Model/Abilities/Abilities.cs create mode 100644 Assets/Scripts/Model/Abilities/Abilities.cs.meta create mode 100644 Assets/Scripts/Model/Abilities/ConditionalAbility.cs create mode 100644 Assets/Scripts/Model/Abilities/ConditionalAbility.cs.meta create mode 100644 Assets/Scripts/Model/Abilities/IAbility.cs create mode 100644 Assets/Scripts/Model/Abilities/IAbility.cs.meta create mode 100644 Assets/Scripts/Model/Abilities/PadCampaignDrip.cs create mode 100644 Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta create mode 100644 Assets/Scripts/Model/Cards/Text.meta create mode 100644 Assets/Scripts/Model/Cards/Text/Text.cs create mode 100644 Assets/Scripts/Model/Cards/Text/Text.cs.meta create mode 100644 Assets/Scripts/Model/Player/IPlayer.cs create mode 100644 Assets/Scripts/Model/Player/IPlayer.cs.meta 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/Abilities/Abilities.cs.meta b/Assets/Scripts/Model/Abilities/Abilities.cs.meta new file mode 100644 index 0000000..810a7cb --- /dev/null +++ b/Assets/Scripts/Model/Abilities/Abilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 95b28d83b7c600742893b653136ebcee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Abilities/ConditionalAbility.cs b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs new file mode 100644 index 0000000..7b81b10 --- /dev/null +++ b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; + +namespace model.abilities { + public class ConditionalAbility { + + public bool Active { get; private set; } + private TriggerCondition condition; + private IInstruction instruction; + + public ConditionalAbility(TriggerCondition condition, IInstruction instruction) { + this.condition = condition; + this.instruction = instruction; + } + + internal List InstantiatePerOccurrence() { + return condition + .occurrences + .Select(it => it.Instantiate(instruction)) + .ToList(); + } + + public class Instance { + public bool Pending { get; set; } + public bool Imminent { get; set; } + public bool Resolving { get; set; } + } + } + + public class TriggerCondition { + internal IList occurrences = new List(); + } + + public interface IInstruction { + } + + internal class Occurrence { + object source; + + internal ConditionalAbility.Instance Instantiate(IInstruction instruction) { + var instance = new ConditionalAbility.Instance(); + instance.Pending = true; + instance.Imminent = false; + instance.Resolving = false; + return instance; + } + } +} diff --git a/Assets/Scripts/Model/Abilities/ConditionalAbility.cs.meta b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs.meta new file mode 100644 index 0000000..5f3969c --- /dev/null +++ b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b9d99febe6ed1ce4e9f1b310bbc29779 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: 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/Model/Abilities/IAbility.cs.meta b/Assets/Scripts/Model/Abilities/IAbility.cs.meta new file mode 100644 index 0000000..ca7dffd --- /dev/null +++ b/Assets/Scripts/Model/Abilities/IAbility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4099a389143dfd2448ceb69587d26b68 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs new file mode 100644 index 0000000..0784ecf --- /dev/null +++ b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using System.Linq; +using model.play; + +namespace model.abilities { + public class PadCampaignDrip : IAbility { + } +} diff --git a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta new file mode 100644 index 0000000..2e83c48 --- /dev/null +++ b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 840d6b4e9ce43014aa26ab60295e1ecf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index 8e288b4..5c56268 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -1,6 +1,8 @@ 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; @@ -31,6 +33,9 @@ public abstract class Card : ISource { 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) { this.game = game; @@ -39,6 +44,10 @@ public Card(Game game) { this.Zone.Add(this); } + protected ThenText when(TriggerCondition condition) { + return new ThenText(); + } + async public Task BecomeActive() { await Activate(); Active = true; diff --git a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index abcb8a5..02a0078 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -1,14 +1,24 @@ 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 static class costs2 { + public static string credits(this System.Int32 creds) { + return creds + " credits"; + } + } + public class PadCampaign : Card { + override public string FaceupArt => "pad-campaign"; override public string Name => "PAD Campaign"; override public Faction Faction => Factions.SHADOW; @@ -23,16 +33,39 @@ public class PadCampaign : Card { private Ability drip; private IList phases = new List(); - public PadCampaign(Game game) : base(game) { - drip = new Ability( - cost: new Free(), - effect: game.corp.credits.Gaining(1), - source: this, - mandatory: true - ); + public PadCampaign(Game game) : base(game, game.corp) { + ///// 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 + statically(self.restrict(play.when(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.psi().miss(game.run.end())) + + //// SEMANTICS //// CR: 9.3.7 + new StaticAbility(restrictions: null, declarations: null, conditions: null) + new ConditionalAbility(condition: new TriggerCondition(this.trashed().byTaking(NET, MEAT))) + + //// TRY TO COMPILE //// + game.Abilities.AddConditional(new ConditionalAbility(condition: your.turn.begins, instruction: you.gain(1).credits)); } protected override Task Activate() { + + game.Timing.CorpTurnDefined += DeferDrip; return Task.CompletedTask; } 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..8b0b790 --- /dev/null +++ b/Assets/Scripts/Model/Cards/Text/Text.cs @@ -0,0 +1,70 @@ +using model.abilities; + +namespace model.cards.text { + + public class YourText { + public readonly TurnText turn; + 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 { + public TrashedText byTaking(params object[] damageType) { + return new TrashedText(); + } + } + + public class GainingText { + public readonly CreditsText credits; + } + + public class DrawingText : IInstruction { + public readonly DrawingText cards; + } + + public class CreditsText : IInstruction { + } + + public class TurnText { + public readonly TriggerCondition begins; + } + + public class ThenText { + + private 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/Cards/Text/Text.cs.meta b/Assets/Scripts/Model/Cards/Text/Text.cs.meta new file mode 100644 index 0000000..2bdd2b4 --- /dev/null +++ b/Assets/Scripts/Model/Cards/Text/Text.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb4d5afcd781e4641bf81e136b0e97b9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Model/Corp.cs b/Assets/Scripts/Model/Corp.cs index d128f90..81babda 100644 --- a/Assets/Scripts/Model/Corp.cs +++ b/Assets/Scripts/Model/Corp.cs @@ -10,7 +10,7 @@ namespace model { - public class Corp + public class Corp : IPlayer { public readonly IPilot pilot; public readonly zones.corp.Zones zones; diff --git a/Assets/Scripts/Model/Game.cs b/Assets/Scripts/Model/Game.cs index 673daed..72a1c9d 100644 --- a/Assets/Scripts/Model/Game.cs +++ b/Assets/Scripts/Model/Game.cs @@ -1,4 +1,5 @@ using System; +using model.abilities; using model.player; using model.timing; using model.zones; @@ -10,6 +11,7 @@ public class Game public readonly Corp corp; public readonly Runner runner; public readonly Timing Timing; + public readonly Abilities Abilities; private readonly Zone playArea; private Shuffling shuffling; @@ -20,6 +22,7 @@ public Game(IPilot corpPilot, IPilot runnerPilot, Shuffling shuffling) 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) diff --git a/Assets/Scripts/Model/Play/Ability.cs b/Assets/Scripts/Model/Play/Ability.cs index b59dece..5b35717 100644 --- a/Assets/Scripts/Model/Play/Ability.cs +++ b/Assets/Scripts/Model/Play/Ability.cs @@ -7,26 +7,26 @@ public class Ability : IPlayOption { public readonly IEffect effect; public readonly ISource source; public bool Mandatory { get; } - private bool active; + public bool Active { get; private set; } public event Action UsabilityChanged = delegate { }; public event Action Resolved = delegate { }; public bool Usable => cost.Payable && effect.Impactful; // CR: 1.2.5 - public bool Legal => active && Usable; + 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 + Active = source.Active; // CR: 9.1.8 source.ChangedActivation += UpdateActivity; cost.ChangedPayability += UpdateCost; effect.ChangedImpact += UpdateEffect; } private void UpdateActivity(ISource source) { - active = source.Active; + Active = source.Active; } private void UpdateCost(ICost source, bool payable) { @@ -42,7 +42,7 @@ async public Task Resolve() { } async public Task Trigger() { - if (active) { + if (Active) { await cost.Pay(); await effect.Resolve(); Resolved(this); diff --git a/Assets/Scripts/Model/Player/IPlayer.cs b/Assets/Scripts/Model/Player/IPlayer.cs new file mode 100644 index 0000000..9ff6a93 --- /dev/null +++ b/Assets/Scripts/Model/Player/IPlayer.cs @@ -0,0 +1,9 @@ +using model.cards.text; +using model.timing; + +namespace model.player +{ + public interface IPlayer { + ITurn turn { get; } + } +} 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/Runner.cs b/Assets/Scripts/Model/Runner.cs index 01979be..7b3d3f3 100644 --- a/Assets/Scripts/Model/Runner.cs +++ b/Assets/Scripts/Model/Runner.cs @@ -9,7 +9,7 @@ namespace model { - public class Runner + public class Runner : IPlayer { public readonly IPilot pilot; public int tags = 0; 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/ITimingStructure.cs b/Assets/Scripts/Model/Timing/ITimingStructure.cs index 8bcf30b..3c32820 100644 --- a/Assets/Scripts/Model/Timing/ITimingStructure.cs +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs @@ -5,8 +5,6 @@ public abstract class ITimingStructure { event AsyncAction Initiated; event AsyncAction Completed; public string Name { get; } - public readonly ReactionWindow Begins; - public readonly ReactionWindow Ends; protected ITimingStructure(string name) { this.Name = name; diff --git a/Assets/Scripts/Model/Timing/ReactionWindow.cs b/Assets/Scripts/Model/Timing/ReactionWindow.cs index 8adae03..8cfdf0e 100644 --- a/Assets/Scripts/Model/Timing/ReactionWindow.cs +++ b/Assets/Scripts/Model/Timing/ReactionWindow.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using model.abilities; using model.play; using model.player; @@ -10,11 +11,12 @@ namespace model.timing { public class ReactionWindow : PriorityWindow { private IPilot acting; private IPilot reacting; + private IList pending; private Dictionary mandatory = new Dictionary(); private Dictionary optional = new Dictionary(); - public ReactionWindow(string name) : base(name) { - + public ReactionWindow(IList pending) : base("Reaction window") { + this.pending = pending; } internal void Offer(IPilot pilot, Ability ability) { diff --git a/Assets/Scripts/Model/Timing/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs index 618ee63..f74daa5 100644 --- a/Assets/Scripts/Model/Timing/Timing.cs +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -8,7 +8,8 @@ namespace model.timing { public class Timing { private Game game; - private Checkpoint checkpoint; + private Checkpoint nextCheckpoint; + private bool checking = false; public event Action CorpTurnDefined = delegate { }; public event Action RunnerTurnDefined = delegate { }; public event Action PaidWindowDefined = delegate { }; @@ -24,6 +25,7 @@ public class Timing { public Timing(Game game) { this.game = game; + this.nextCheckpoint = new Checkpoint(game); game.corp.zones.rd.Decked += DeckCorp; game.runner.zones.score.StolenEnough += StealEnough; } @@ -121,7 +123,13 @@ private void Finish(GameFinish finish) { } async public Task Checkpoint() { - await checkpoint.Check(); + if (checking) { + throw new Exception("Checkpoints cannot be nested"); + } + checking = true; + await nextCheckpoint.Check(); + checking = false; + nextCheckpoint = new Checkpoint(game); } } From 1331e906abeda16a2c886537399f829445cf3e47 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Mon, 13 Jun 2022 20:01:18 +0200 Subject: [PATCH 12/14] Try experimental API for PAD Campaign --- .../Scripts/Model/Abilities/PadCampaignDrip.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs index 0784ecf..74cf31a 100644 --- a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs +++ b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs @@ -1,8 +1,24 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using model.play; namespace model.abilities { public class PadCampaignDrip : IAbility { + public ISource Source => throw new System.NotImplementedException(); + + public bool Active => throw new System.NotImplementedException(); + + public Task Resolve() { + throw new System.NotImplementedException(); + } + + public Condition TriggerCondition() { + return YourTurnBegins(); + } + + public PadCampaignDripInstance CreatePending() { + NextReactionWindow().Add(new PadCampaignDripInstance()) + } } } From 0a067fe85e1afc3ffe3f549b3522a212ae9e1b62 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Wed, 20 Jul 2022 23:11:07 +0200 Subject: [PATCH 13/14] Clean up ability types and paid windows, somewhat --- Assets/Scripts/Model/AI/CorpAi.cs | 10 +-- .../Model/Abilities/ConditionalAbility.cs | 66 +++++++++++------ .../Model/Abilities/PadCampaignDrip.cs | 24 ------- .../Model/Abilities/PadCampaignDrip.cs.meta | 11 --- Assets/Scripts/Model/Cards/Card.cs | 6 +- .../Scripts/Model/Cards/Corp/PadCampaign.cs | 23 +----- Assets/Scripts/Model/Cards/Runner/Wyldside.cs | 14 ---- Assets/Scripts/Model/Corp.cs | 1 - Assets/Scripts/Model/Costs/Trash.cs | 1 - Assets/Scripts/Model/Play/RunnerActing.cs | 10 +-- .../Model/Play/SimultaneousTriggers.cs | 2 +- .../Scripts/Model/Player/DelegatingPilot.cs | 42 +++++------ Assets/Scripts/Model/Player/IPilot.cs | 2 + Assets/Scripts/Model/Player/NoPilot.cs | 26 ++++--- .../Scripts/Model/Player/SingleChoiceMaker.cs | 4 +- Assets/Scripts/Model/Rez/Rezzing.cs | 2 +- Assets/Scripts/Model/Run/RunStructure.cs | 71 ++++++------------- .../Model/Timing/Corp/CorpActionPhase.cs | 2 +- .../Model/Timing/Corp/CorpDrawPhase.cs | 2 +- .../Scripts/Model/Timing/ITimingStructure.cs | 2 - Assets/Scripts/Model/Timing/PaidWindow.cs | 4 +- Assets/Scripts/Model/Timing/PriorityWindow.cs | 7 +- Assets/Scripts/Model/Timing/ReactionWindow.cs | 4 +- .../Scripts/Model/Timing/Runner/RunnerTurn.cs | 35 ++------- Assets/Scripts/Model/Timing/Timing.cs | 15 ---- .../Scripts/Model/Zones/Corp/Headquarters.cs | 4 +- Assets/Scripts/Model/Zones/Corp/IceColumn.cs | 2 +- Assets/Scripts/Model/Zones/Corp/NewRemote.cs | 4 +- Assets/Scripts/View/Log/GameFlowLog.cs | 8 +-- 29 files changed, 152 insertions(+), 252 deletions(-) delete mode 100644 Assets/Scripts/Model/Abilities/PadCampaignDrip.cs delete mode 100644 Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta 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/ConditionalAbility.cs b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs index 7b81b10..7d6f1d1 100644 --- a/Assets/Scripts/Model/Abilities/ConditionalAbility.cs +++ b/Assets/Scripts/Model/Abilities/ConditionalAbility.cs @@ -1,12 +1,16 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using model.play; namespace model.abilities { - public class ConditionalAbility { + public class ConditionalAbility : IAbility { - public bool Active { get; private set; } - private TriggerCondition condition; - private IInstruction instruction; + 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; @@ -14,35 +18,55 @@ public ConditionalAbility(TriggerCondition condition, IInstruction instruction) } internal List InstantiatePerOccurrence() { - return condition - .occurrences - .Select(it => it.Instantiate(instruction)) - .ToList(); + 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(); - } + internal IList occurrences = new List(); - public interface IInstruction { - } - - internal class Occurrence { - object source; + public interface IOccurrence { - internal ConditionalAbility.Instance Instantiate(IInstruction instruction) { - var instance = new ConditionalAbility.Instance(); - instance.Pending = true; - instance.Imminent = false; - instance.Resolving = false; - return instance; } } + + public interface IInstruction { + Task Resolve(); + } } diff --git a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs deleted file mode 100644 index 74cf31a..0000000 --- a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using model.play; - -namespace model.abilities { - public class PadCampaignDrip : IAbility { - public ISource Source => throw new System.NotImplementedException(); - - public bool Active => throw new System.NotImplementedException(); - - public Task Resolve() { - throw new System.NotImplementedException(); - } - - public Condition TriggerCondition() { - return YourTurnBegins(); - } - - public PadCampaignDripInstance CreatePending() { - NextReactionWindow().Add(new PadCampaignDripInstance()) - } - } -} diff --git a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta b/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta deleted file mode 100644 index 2e83c48..0000000 --- a/Assets/Scripts/Model/Abilities/PadCampaignDrip.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 840d6b4e9ce43014aa26ab60295e1ecf -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Model/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index 5c56268..dbe8f10 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -19,6 +19,7 @@ public abstract class Card : ISource { public abstract IType Type { get; } public Zone Zone { get; private set; } public abstract ICost PlayCost { get; } + public virtual IEffect Activation { get; } public abstract Faction Faction { get; } public abstract int InfluenceCost { get; } public abstract string FaceupArt { get; } @@ -54,7 +55,10 @@ async public Task BecomeActive() { ChangedActivation(this); } - protected abstract Task Activate(); + protected async Task Activate() { + // TODO remove, replace by text/syntax parsing in constructors + await Task.CompletedTask; + } async protected virtual Task BecomeInactive() { await Deactivate(); diff --git a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index 02a0078..70405bc 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -30,10 +30,7 @@ public class PadCampaign : Card { new PayToTrash(4, this, game) }; - private Ability drip; - private IList phases = new List(); - - public PadCampaign(Game game) : base(game, game.corp) { + public PadCampaign(Game game) : base(game) { ///// SYNTAX ///// // PAD Campaign @@ -60,23 +57,7 @@ public PadCampaign(Game game) : base(game, game.corp) { new ConditionalAbility(condition: new TriggerCondition(this.trashed().byTaking(NET, MEAT))) //// TRY TO COMPILE //// - game.Abilities.AddConditional(new ConditionalAbility(condition: your.turn.begins, instruction: you.gain(1).credits)); - } - - protected override Task Activate() { - - - game.Timing.CorpTurnDefined += DeferDrip; - return Task.CompletedTask; - } - - override protected Task Deactivate() { - game.Timing.CorpTurnDefined -= DeferDrip; - return Task.CompletedTask; - } - - private void DeferDrip(CorpTurn turn) { - turn.Begins.Offer(game.corp.pilot, drip); + game.Abilities.AddConditional(new ConditionalAbility(condition: your.Turn.Begins, instruction: you.Gain(1).Credits)); } } } diff --git a/Assets/Scripts/Model/Cards/Runner/Wyldside.cs b/Assets/Scripts/Model/Cards/Runner/Wyldside.cs index 8b1be4a..87926c9 100644 --- a/Assets/Scripts/Model/Cards/Runner/Wyldside.cs +++ b/Assets/Scripts/Model/Cards/Runner/Wyldside.cs @@ -23,19 +23,5 @@ public Wyldside(Game game) : base(game) { mandatory: true ); } - - async protected override Task Activate() { - game.Timing.RunnerTurnDefined += DeferParty; - await Task.CompletedTask; - } - - override protected Task Deactivate() { - game.Timing.RunnerTurnDefined -= DeferParty; - return Task.CompletedTask; - } - - private void DeferParty(RunnerTurn turn) { - turn.Begins.Offer(game.runner.pilot, party); - } } } diff --git a/Assets/Scripts/Model/Corp.cs b/Assets/Scripts/Model/Corp.cs index 81babda..14f0af8 100644 --- a/Assets/Scripts/Model/Corp.cs +++ b/Assets/Scripts/Model/Corp.cs @@ -42,7 +42,6 @@ async public Task Start(Game game, Deck deck) var identity = deck.identity; zones.identity.Add(identity); identity.FlipFaceUp(); - await identity.Activate(); pilot.Play(game); credits.Gain(5); await zones.rd.Draw(5, zones.hq); diff --git a/Assets/Scripts/Model/Costs/Trash.cs b/Assets/Scripts/Model/Costs/Trash.cs index 723c7c8..da7dbd6 100644 --- a/Assets/Scripts/Model/Costs/Trash.cs +++ b/Assets/Scripts/Model/Costs/Trash.cs @@ -31,7 +31,6 @@ async Task ICost.Pay() async public Task TrashIt() { - await card.Deactivate(); await card.MoveTo(bin); } } 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 8b6bd35..d9730b7 100644 --- a/Assets/Scripts/Model/Play/SimultaneousTriggers.cs +++ b/Assets/Scripts/Model/Play/SimultaneousTriggers.cs @@ -19,7 +19,7 @@ async public Task AllTriggered(IPilot pilot) { UnityEngine.Debug.Log("Picking the next effect to fire among " + untriggered); var ability = await pilot.TriggerFromSimultaneous(untriggered); - await ability.Ability.Trigger(); + await ability.Trigger(); untriggered.Remove(ability); } } diff --git a/Assets/Scripts/Model/Player/DelegatingPilot.cs b/Assets/Scripts/Model/Player/DelegatingPilot.cs index ea10398..465dbe0 100644 --- a/Assets/Scripts/Model/Player/DelegatingPilot.cs +++ b/Assets/Scripts/Model/Player/DelegatingPilot.cs @@ -1,50 +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 abilities) - { + 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 f991ea3..9aeff70 100644 --- a/Assets/Scripts/Model/Player/IPilot.cs +++ b/Assets/Scripts/Model/Player/IPilot.cs @@ -3,6 +3,7 @@ using model.choices.trash; using model.play; using model.steal; +using model.timing; using model.zones; using System.Collections.Generic; using System.Threading.Tasks; @@ -19,5 +20,6 @@ public interface IPilot IDecision ChooseTrashing(); IDecision ChooseStealing(); IPlayOption Choose(IList options); + Task Receive(Priority priority); } } diff --git a/Assets/Scripts/Model/Player/NoPilot.cs b/Assets/Scripts/Model/Player/NoPilot.cs index 0d3e444..69cebd3 100644 --- a/Assets/Scripts/Model/Player/NoPilot.cs +++ b/Assets/Scripts/Model/Player/NoPilot.cs @@ -1,21 +1,27 @@ -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 abilities) - { + 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()); } diff --git a/Assets/Scripts/Model/Player/SingleChoiceMaker.cs b/Assets/Scripts/Model/Player/SingleChoiceMaker.cs index 9642370..5f5255d 100644 --- a/Assets/Scripts/Model/Player/SingleChoiceMaker.cs +++ b/Assets/Scripts/Model/Player/SingleChoiceMaker.cs @@ -14,9 +14,9 @@ public class SingleChoiceMaker : DelegatingPilot { public SingleChoiceMaker(IPilot basic) : base(basic) { } - async override public Task TriggerFromSimultaneous(IList abilities) + async override public Task TriggerFromSimultaneous(IEnumerable abilities) { - if (abilities.Count == 1) + if (abilities.Count() == 1) { return abilities.Single(); } diff --git a/Assets/Scripts/Model/Rez/Rezzing.cs b/Assets/Scripts/Model/Rez/Rezzing.cs index fd35a26..3cc401a 100644 --- a/Assets/Scripts/Model/Rez/Rezzing.cs +++ b/Assets/Scripts/Model/Rez/Rezzing.cs @@ -27,7 +27,7 @@ public IPlayOption Rez(Card 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 { 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/Timing/Corp/CorpActionPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs index 9917880..784a28b 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpActionPhase.cs @@ -37,7 +37,7 @@ async private Task TakeAction() TakingAction?.Invoke(); var action = await actionTaking; ActionTaken?.Invoke(action); - await timing.OpenPaidWindow(rezzing: true, scoring: true); + await timing.DefinePaidWindow(rezzing: true, scoring: true).Open(); } } } diff --git a/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs index 2a3f5a7..a40ccbf 100644 --- a/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs +++ b/Assets/Scripts/Model/Timing/Corp/CorpDrawPhase.cs @@ -13,7 +13,7 @@ internal CorpDrawPhase(Corp corp, Timing timing, ReactionWindow turnBegins) : ba async protected override Task Proceed() { corp.clicks.Replenish(); // CR: 5.6.1.a - await timing.OpenPaidWindow(rezzing: true, scoring: true); // CR: 5.6.1.b + 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 diff --git a/Assets/Scripts/Model/Timing/ITimingStructure.cs b/Assets/Scripts/Model/Timing/ITimingStructure.cs index 3c32820..127cb45 100644 --- a/Assets/Scripts/Model/Timing/ITimingStructure.cs +++ b/Assets/Scripts/Model/Timing/ITimingStructure.cs @@ -8,8 +8,6 @@ public abstract class ITimingStructure { protected ITimingStructure(string name) { this.Name = name; - Begins = new ReactionWindow(Name + " begins"); - Ends = new ReactionWindow(Name + " ends"); } async public Task Initiate() { diff --git a/Assets/Scripts/Model/Timing/PaidWindow.cs b/Assets/Scripts/Model/Timing/PaidWindow.cs index 0b28c41..a7df990 100644 --- a/Assets/Scripts/Model/Timing/PaidWindow.cs +++ b/Assets/Scripts/Model/Timing/PaidWindow.cs @@ -9,7 +9,7 @@ public class PaidWindow : PriorityWindow { private IPilot acting; private IPilot reacting; - public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting, string name) : base(name) { + public PaidWindow(bool rezzing, bool scoring, IPilot acting, IPilot reacting) : base("Paid ability window") { this.rezzing = rezzing; this.scoring = scoring; this.acting = acting; @@ -37,7 +37,7 @@ async override protected Task Proceed() { async private Task AwaitPass(IPilot pilot) { var priority = new Priority(canPass: true); // CR: 9.2.4.b - PriorityGiven(priority); + OnPriorityGiven(priority); while (!priority.Passed) // CR: 9.2.4.c { await pilot.Receive(priority); // CR: 9.2.7.f diff --git a/Assets/Scripts/Model/Timing/PriorityWindow.cs b/Assets/Scripts/Model/Timing/PriorityWindow.cs index 1d3b57f..930e8c9 100644 --- a/Assets/Scripts/Model/Timing/PriorityWindow.cs +++ b/Assets/Scripts/Model/Timing/PriorityWindow.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace model.timing { @@ -20,7 +20,8 @@ async public Task Open() { } protected abstract Task Proceed(); - - protected + protected void OnPriorityGiven(Priority priority) { + PriorityGiven(priority); + } } } diff --git a/Assets/Scripts/Model/Timing/ReactionWindow.cs b/Assets/Scripts/Model/Timing/ReactionWindow.cs index 8cfdf0e..5f8da1d 100644 --- a/Assets/Scripts/Model/Timing/ReactionWindow.cs +++ b/Assets/Scripts/Model/Timing/ReactionWindow.cs @@ -12,8 +12,8 @@ public class ReactionWindow : PriorityWindow { private IPilot acting; private IPilot reacting; private IList pending; - private Dictionary mandatory = new Dictionary(); - private Dictionary optional = new Dictionary(); + private Dictionary mandatory = new(); + private Dictionary optional = new(); public ReactionWindow(IList pending) : base("Reaction window") { this.pending = pending; diff --git a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs index d72ab7b..2ccd2b1 100644 --- a/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs +++ b/Assets/Scripts/Model/Timing/Runner/RunnerTurn.cs @@ -25,16 +25,10 @@ override async protected Task Proceed() { async private Task ActionPhase() { 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; + await timing.DefinePaidWindow(rezzing: true, scoring: false).Open(); // CR: 5.7.1.b RefillRecurringCredits(); // CR: 5.7.1.c await Begins.Open(); // 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; + 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 @@ -42,17 +36,6 @@ async private Task ActionPhase() { await timing.Checkpoint(); // CR: 5.7.1.g } - async private Task OpenPaidWindow() { - await timing.OpenPaidWindow( - acting: game.runner.paidWindow, - reacting: game.corp.paidWindow - ); - } - - async private Task OpenRezWindow() { - await game.corp.Rezzing.Window.Open(); - } - private void RefillRecurringCredits() { } @@ -62,21 +45,15 @@ async private Task 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() { 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() { diff --git a/Assets/Scripts/Model/Timing/Timing.cs b/Assets/Scripts/Model/Timing/Timing.cs index f74daa5..6edd0fc 100644 --- a/Assets/Scripts/Model/Timing/Timing.cs +++ b/Assets/Scripts/Model/Timing/Timing.cs @@ -85,21 +85,6 @@ public PaidWindow DefinePaidWindow(bool rezzing, bool scoring) { return window; } - 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", diff --git a/Assets/Scripts/Model/Zones/Corp/Headquarters.cs b/Assets/Scripts/Model/Zones/Corp/Headquarters.cs index 9559c16..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,7 +26,7 @@ public Headquarters(Shuffling shuffling, CreditPool credits) async public Task Discard() { discarding = new TaskCompletionSource(); - DiscardingOne(); + await DiscardingOne?.Invoke(); await discarding.Task; } 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/View/Log/GameFlowLog.cs b/Assets/Scripts/View/Log/GameFlowLog.cs index 0b8a299..44d61bc 100644 --- a/Assets/Scripts/View/Log/GameFlowLog.cs +++ b/Assets/Scripts/View/Log/GameFlowLog.cs @@ -22,7 +22,7 @@ public void Display(Game game) 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"); + 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"); @@ -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"); } } } From 9058a0eb7a85257ed2994fbb542cf6242f987e90 Mon Sep 17 00:00:00 2001 From: Maciej Kwidzinski Date: Wed, 20 Jul 2022 23:15:36 +0200 Subject: [PATCH 14/14] Propose new syntax and semantics for card abilities --- Assets/Scripts/Model/Cards/Card.cs | 4 +- .../Model/Cards/Corp/AdvancedAssemblyLines.cs | 9 +++++ .../Scripts/Model/Cards/Corp/PadCampaign.cs | 40 ++++++++----------- Assets/Scripts/Model/Cards/Corp/TheShadow.cs | 2 +- Assets/Scripts/Model/Cards/Runner/Masque.cs | 2 +- Assets/Scripts/Model/Cards/Text/Text.cs | 35 ++++++++++------ Assets/Scripts/Model/Player/IPlayer.cs | 1 - 7 files changed, 52 insertions(+), 41 deletions(-) diff --git a/Assets/Scripts/Model/Cards/Card.cs b/Assets/Scripts/Model/Cards/Card.cs index dbe8f10..e107ad7 100644 --- a/Assets/Scripts/Model/Cards/Card.cs +++ b/Assets/Scripts/Model/Cards/Card.cs @@ -45,8 +45,8 @@ public Card(Game game) { this.Zone.Add(this); } - protected ThenText when(TriggerCondition condition) { - return new ThenText(); + protected ThenText When(TriggerCondition condition) { + return new ThenText(condition); } async public Task BecomeActive() { diff --git a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs index e3bc395..b373dbd 100644 --- a/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs +++ b/Assets/Scripts/Model/Cards/Corp/AdvancedAssemblyLines.cs @@ -24,12 +24,21 @@ public class AdvancedAssemblyLines : Card { }; 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) + ); } async protected override Task Activate() { diff --git a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs index 70405bc..75a7d0b 100644 --- a/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs +++ b/Assets/Scripts/Model/Cards/Corp/PadCampaign.cs @@ -10,12 +10,6 @@ namespace model.cards.corp { - public static class costs2 { - public static string credits(this System.Int32 creds) { - return creds + " credits"; - } - } - public class PadCampaign : Card { @@ -34,27 +28,27 @@ public PadCampaign(Game game) : base(game) { ///// SYNTAX ///// // PAD Campaign - when(your.turn.begins) - .then(you.gain(1).credits); + 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); + When(self.Played()) + .Then(you.Draw(3).Cards); + When(self.Trashed().ByTaking("NET", "MEAT")) + .Then(you.Draw(3).Cards); // Hyoubu Precog Manifold - statically(self.restrict(play.when(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.psi().miss(game.run.end())) - - //// SEMANTICS //// CR: 9.3.7 - new StaticAbility(restrictions: null, declarations: null, conditions: null) - new ConditionalAbility(condition: new TriggerCondition(this.trashed().byTaking(NET, MEAT))) + // 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())); + + // //// SEMANTICS //// CR: 9.3.7 + // new StaticAbility(restrictions: null, declarations: null, conditions: null); + // new ConditionalAbility(condition: new TriggerCondition(this.Trashed().ByTaking(NET, MEAT))); //// 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/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/Text/Text.cs b/Assets/Scripts/Model/Cards/Text/Text.cs index 8b0b790..b5db4eb 100644 --- a/Assets/Scripts/Model/Cards/Text/Text.cs +++ b/Assets/Scripts/Model/Cards/Text/Text.cs @@ -3,29 +3,29 @@ namespace model.cards.text { public class YourText { - public readonly TurnText turn; - public TurnText nextTurn() { + public TurnText Turn { get; private set; } + public TurnText NextTurn() { return new TurnText(); } } public class YouText { - public GainingText gain(int number) { + public GainingText Gain(int number) { return new GainingText(); } - public DrawingText draw(int number) { + public DrawingText Draw(int number) { return new DrawingText(); } } public class SelfText { - public PlayedText played() { + public PlayedText Played() { return new PlayedText(); } - public TrashedText trashed() { + public TrashedText Trashed() { return new TrashedText(); } } @@ -35,35 +35,44 @@ public class PlayedText : TriggerCondition { } public class TrashedText : TriggerCondition { - public TrashedText byTaking(params object[] damageType) { - return new TrashedText(); + + internal object[] damageTypes; + + public TrashedText ByTaking(params object[] damageTypes) { + return new TrashedText { + damageTypes = damageTypes + }; } } public class GainingText { - public readonly CreditsText credits; + public CreditsText Credits { get; private set; } } public class DrawingText : IInstruction { - public readonly DrawingText cards; + public DrawingText Cards { get; private set; } + + internal DrawingText() { + Cards = this; + } } public class CreditsText : IInstruction { } public class TurnText { - public readonly TriggerCondition begins; + public TriggerCondition Begins { get; private set; } } public class ThenText { - private TriggerCondition condition; + private readonly TriggerCondition condition; public ThenText(TriggerCondition condition) { this.condition = condition; } - public ConditionalAbility then(IInstruction instruction) { + public ConditionalAbility Then(IInstruction instruction) { return new ConditionalAbility(condition, instruction); } } diff --git a/Assets/Scripts/Model/Player/IPlayer.cs b/Assets/Scripts/Model/Player/IPlayer.cs index 9ff6a93..0dd8e48 100644 --- a/Assets/Scripts/Model/Player/IPlayer.cs +++ b/Assets/Scripts/Model/Player/IPlayer.cs @@ -4,6 +4,5 @@ namespace model.player { public interface IPlayer { - ITurn turn { get; } } }