From 22754e4106f1741b56075cbcae00ba09080960ec Mon Sep 17 00:00:00 2001 From: KrisHD1337 Date: Mon, 6 Jul 2026 22:10:31 +0200 Subject: [PATCH] Add metadata for new gacha assets and implement gacha system scaffolding. --- Assets/Data/Cards.meta | 8 + Assets/Data/Cards/CardDatabase.asset.meta | 8 + Assets/Data/Cards/Fedora.meta | 8 + .../Cards/Fedora/BorrowChecker.asset.meta | 8 + .../Cards/Fedora/CargoBuildRelease.asset.meta | 8 + Assets/Data/Cards/Fedora/DnfUpdate.asset.meta | 8 + Assets/Data/Cards/Fedora/Rawhide.asset.meta | 8 + .../Cards/Fedora/SELinuxEnforcing.asset.meta | 8 + Assets/Data/Cards/Mint.meta | 8 + Assets/Data/Cards/Mint/Cinnamon.asset.meta | 8 + Assets/Data/Cards/Mint/FixBroken.asset.meta | 8 + Assets/Data/Cards/Mint/Nemo.asset.meta | 8 + .../Cards/Mint/TimeshiftSnapshot.asset.meta | 8 + .../Data/Cards/Mint/UpdateManager.asset.meta | 8 + Assets/Data/Cards/Ubuntu.meta | 8 + Assets/Data/Cards/Ubuntu/AskUbuntu.asset.meta | 8 + .../Cards/Ubuntu/DoReleaseUpgrade.asset.meta | 8 + .../Data/Cards/Ubuntu/SnapInstall.asset.meta | 8 + .../Cards/Ubuntu/SudoAptInstall.asset.meta | 8 + .../Cards/Ubuntu/UbuntuProTrial.asset.meta | 8 + .../Ubuntu/UnattendedUpgrades.asset.meta | 8 + Assets/Data/Passives.meta | 8 + Assets/Data/Passives/AptUpdate.asset.meta | 8 + Assets/Data/Passives/BleedingEdge.asset.meta | 8 + Assets/Data/Passives/ItJustWorks.asset.meta | 8 + Assets/DefaultVolumeProfile.asset | 3 + Assets/Scenes/FightScene.unity.meta | 7 + Assets/Scenes/GamblingScene.unity.meta | 7 + Assets/Scripts/Data/CardDatabase.cs.meta | 2 + Assets/Scripts/Data/PassiveDefinition.cs.meta | 2 + Assets/Scripts/Meta/CardLoadout.cs.meta | 2 + Assets/Scripts/Meta/EntropyWallet.cs | 5 + Assets/Scripts/Meta/GachaBannerState.cs | 34 ++ Assets/Scripts/Meta/GachaBannerState.cs.meta | 2 + Assets/Scripts/Meta/GachaCurrencyType.cs | 11 + Assets/Scripts/Meta/GachaCurrencyType.cs.meta | 2 + Assets/Scripts/Meta/GachaRewardType.cs | 12 + Assets/Scripts/Meta/GachaRewardType.cs.meta | 2 + Assets/Scripts/Meta/GachaService.cs | 568 +++++++++++++++++- Assets/Scripts/Meta/PlayerCollection.cs | 23 +- Assets/Scripts/Meta/SaveService.cs | 8 + .../UI/CollectionScreenController.cs.meta | 2 + Assets/Scripts/UI/CommandKeyBindings.cs.meta | 2 + Assets/Scripts/UI/DistroPresentation.cs.meta | 2 + Assets/Scripts/UI/GachaScreenController.cs | 487 +++++++++++++++ .../Scripts/UI/GachaScreenController.cs.meta | 2 + Assets/Scripts/UI/MainMenuController.cs | 398 +++++++++++- .../Scripts/UI/ScreenFrameController.cs.meta | 2 + .../UI/StarterSelectionController.cs.meta | 2 + Assets/UI/GachaScreen.uxml | 5 +- Assets/UI/MainMenu.uss | 255 ++++++++ Assets/UI/MainMenu.uxml | 31 + Assets/UI/ScreenFrame.uxml.meta | 10 + ProjectSettings/PackageManagerSettings.asset | 42 ++ ProjectSettings/ProjectSettings.asset | 5 +- gacha.md | 337 +++++++++++ 56 files changed, 2442 insertions(+), 30 deletions(-) create mode 100644 Assets/Data/Cards.meta create mode 100644 Assets/Data/Cards/CardDatabase.asset.meta create mode 100644 Assets/Data/Cards/Fedora.meta create mode 100644 Assets/Data/Cards/Fedora/BorrowChecker.asset.meta create mode 100644 Assets/Data/Cards/Fedora/CargoBuildRelease.asset.meta create mode 100644 Assets/Data/Cards/Fedora/DnfUpdate.asset.meta create mode 100644 Assets/Data/Cards/Fedora/Rawhide.asset.meta create mode 100644 Assets/Data/Cards/Fedora/SELinuxEnforcing.asset.meta create mode 100644 Assets/Data/Cards/Mint.meta create mode 100644 Assets/Data/Cards/Mint/Cinnamon.asset.meta create mode 100644 Assets/Data/Cards/Mint/FixBroken.asset.meta create mode 100644 Assets/Data/Cards/Mint/Nemo.asset.meta create mode 100644 Assets/Data/Cards/Mint/TimeshiftSnapshot.asset.meta create mode 100644 Assets/Data/Cards/Mint/UpdateManager.asset.meta create mode 100644 Assets/Data/Cards/Ubuntu.meta create mode 100644 Assets/Data/Cards/Ubuntu/AskUbuntu.asset.meta create mode 100644 Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset.meta create mode 100644 Assets/Data/Cards/Ubuntu/SnapInstall.asset.meta create mode 100644 Assets/Data/Cards/Ubuntu/SudoAptInstall.asset.meta create mode 100644 Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset.meta create mode 100644 Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset.meta create mode 100644 Assets/Data/Passives.meta create mode 100644 Assets/Data/Passives/AptUpdate.asset.meta create mode 100644 Assets/Data/Passives/BleedingEdge.asset.meta create mode 100644 Assets/Data/Passives/ItJustWorks.asset.meta create mode 100644 Assets/Scenes/FightScene.unity.meta create mode 100644 Assets/Scenes/GamblingScene.unity.meta create mode 100644 Assets/Scripts/Data/CardDatabase.cs.meta create mode 100644 Assets/Scripts/Data/PassiveDefinition.cs.meta create mode 100644 Assets/Scripts/Meta/CardLoadout.cs.meta create mode 100644 Assets/Scripts/Meta/GachaBannerState.cs create mode 100644 Assets/Scripts/Meta/GachaBannerState.cs.meta create mode 100644 Assets/Scripts/Meta/GachaCurrencyType.cs create mode 100644 Assets/Scripts/Meta/GachaCurrencyType.cs.meta create mode 100644 Assets/Scripts/Meta/GachaRewardType.cs create mode 100644 Assets/Scripts/Meta/GachaRewardType.cs.meta create mode 100644 Assets/Scripts/UI/CollectionScreenController.cs.meta create mode 100644 Assets/Scripts/UI/CommandKeyBindings.cs.meta create mode 100644 Assets/Scripts/UI/DistroPresentation.cs.meta create mode 100644 Assets/Scripts/UI/GachaScreenController.cs create mode 100644 Assets/Scripts/UI/GachaScreenController.cs.meta create mode 100644 Assets/Scripts/UI/ScreenFrameController.cs.meta create mode 100644 Assets/Scripts/UI/StarterSelectionController.cs.meta create mode 100644 Assets/UI/ScreenFrame.uxml.meta create mode 100644 ProjectSettings/PackageManagerSettings.asset create mode 100644 gacha.md diff --git a/Assets/Data/Cards.meta b/Assets/Data/Cards.meta new file mode 100644 index 0000000..a587697 --- /dev/null +++ b/Assets/Data/Cards.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 15501d6fecab8d6c39720f46b2644bc9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/CardDatabase.asset.meta b/Assets/Data/Cards/CardDatabase.asset.meta new file mode 100644 index 0000000..7e4898d --- /dev/null +++ b/Assets/Data/Cards/CardDatabase.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1632132e32bfc31e8a293140f5908aa3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Fedora.meta b/Assets/Data/Cards/Fedora.meta new file mode 100644 index 0000000..44c21c7 --- /dev/null +++ b/Assets/Data/Cards/Fedora.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 04f09e15db30a0756af561fca255e85b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Fedora/BorrowChecker.asset.meta b/Assets/Data/Cards/Fedora/BorrowChecker.asset.meta new file mode 100644 index 0000000..6819725 --- /dev/null +++ b/Assets/Data/Cards/Fedora/BorrowChecker.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4dfd40ca206341ba9a1a486d69994611 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Fedora/CargoBuildRelease.asset.meta b/Assets/Data/Cards/Fedora/CargoBuildRelease.asset.meta new file mode 100644 index 0000000..8828ae0 --- /dev/null +++ b/Assets/Data/Cards/Fedora/CargoBuildRelease.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 11e141b975f8ab190abf6c267067b575 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Fedora/DnfUpdate.asset.meta b/Assets/Data/Cards/Fedora/DnfUpdate.asset.meta new file mode 100644 index 0000000..35b9a5d --- /dev/null +++ b/Assets/Data/Cards/Fedora/DnfUpdate.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b6e048c6b94f10920a31d4c7069ba20f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Fedora/Rawhide.asset.meta b/Assets/Data/Cards/Fedora/Rawhide.asset.meta new file mode 100644 index 0000000..f9a44be --- /dev/null +++ b/Assets/Data/Cards/Fedora/Rawhide.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 856019cbd78921a9c8846de8f81029b2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Fedora/SELinuxEnforcing.asset.meta b/Assets/Data/Cards/Fedora/SELinuxEnforcing.asset.meta new file mode 100644 index 0000000..85ba5c9 --- /dev/null +++ b/Assets/Data/Cards/Fedora/SELinuxEnforcing.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 361650f1e83cad1119b747d294991569 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Mint.meta b/Assets/Data/Cards/Mint.meta new file mode 100644 index 0000000..724d856 --- /dev/null +++ b/Assets/Data/Cards/Mint.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 99cfb62a9ed9a6566aec84be75999c1f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Mint/Cinnamon.asset.meta b/Assets/Data/Cards/Mint/Cinnamon.asset.meta new file mode 100644 index 0000000..f235e33 --- /dev/null +++ b/Assets/Data/Cards/Mint/Cinnamon.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3cc3bbc086d6f0d3199af9094f9912d2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Mint/FixBroken.asset.meta b/Assets/Data/Cards/Mint/FixBroken.asset.meta new file mode 100644 index 0000000..79a5980 --- /dev/null +++ b/Assets/Data/Cards/Mint/FixBroken.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7b5e9a8f7e3bb8eb6865533dd2b9af2f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Mint/Nemo.asset.meta b/Assets/Data/Cards/Mint/Nemo.asset.meta new file mode 100644 index 0000000..5431f36 --- /dev/null +++ b/Assets/Data/Cards/Mint/Nemo.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 89c59c54d4bce36aaa836cfdc43f3c9f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Mint/TimeshiftSnapshot.asset.meta b/Assets/Data/Cards/Mint/TimeshiftSnapshot.asset.meta new file mode 100644 index 0000000..83e1efc --- /dev/null +++ b/Assets/Data/Cards/Mint/TimeshiftSnapshot.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 07d51e6f24485d6aa9dfe38424e318ea +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Mint/UpdateManager.asset.meta b/Assets/Data/Cards/Mint/UpdateManager.asset.meta new file mode 100644 index 0000000..c64e516 --- /dev/null +++ b/Assets/Data/Cards/Mint/UpdateManager.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f3cf77257a59a88518167e7e19023304 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Ubuntu.meta b/Assets/Data/Cards/Ubuntu.meta new file mode 100644 index 0000000..2590df5 --- /dev/null +++ b/Assets/Data/Cards/Ubuntu.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: caab84daf052361ab87b05d78d72eb4a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Ubuntu/AskUbuntu.asset.meta b/Assets/Data/Cards/Ubuntu/AskUbuntu.asset.meta new file mode 100644 index 0000000..aba1c03 --- /dev/null +++ b/Assets/Data/Cards/Ubuntu/AskUbuntu.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9347bcc42fb6834f1907a2c0b661ff95 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset.meta b/Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset.meta new file mode 100644 index 0000000..8e53edd --- /dev/null +++ b/Assets/Data/Cards/Ubuntu/DoReleaseUpgrade.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1dbc28e480e52d97b9501262af30cc78 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Ubuntu/SnapInstall.asset.meta b/Assets/Data/Cards/Ubuntu/SnapInstall.asset.meta new file mode 100644 index 0000000..4df0ed6 --- /dev/null +++ b/Assets/Data/Cards/Ubuntu/SnapInstall.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 19c884fde6ba18438b976aa154a729df +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Ubuntu/SudoAptInstall.asset.meta b/Assets/Data/Cards/Ubuntu/SudoAptInstall.asset.meta new file mode 100644 index 0000000..87441ea --- /dev/null +++ b/Assets/Data/Cards/Ubuntu/SudoAptInstall.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 26d4526631046cf5aad90867da18ea81 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset.meta b/Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset.meta new file mode 100644 index 0000000..240b298 --- /dev/null +++ b/Assets/Data/Cards/Ubuntu/UbuntuProTrial.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7819ca459d587093e942e1b497761d72 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset.meta b/Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset.meta new file mode 100644 index 0000000..bcaf4fb --- /dev/null +++ b/Assets/Data/Cards/Ubuntu/UnattendedUpgrades.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: daae1c711cc8de216966b68adc63d240 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Passives.meta b/Assets/Data/Passives.meta new file mode 100644 index 0000000..7d5c01f --- /dev/null +++ b/Assets/Data/Passives.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 76021ac16bc24526cbdacf1909a7a0cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Passives/AptUpdate.asset.meta b/Assets/Data/Passives/AptUpdate.asset.meta new file mode 100644 index 0000000..d8e753c --- /dev/null +++ b/Assets/Data/Passives/AptUpdate.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 19deb73cec3d5dc42a5cc336222ce9df +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Passives/BleedingEdge.asset.meta b/Assets/Data/Passives/BleedingEdge.asset.meta new file mode 100644 index 0000000..75c6083 --- /dev/null +++ b/Assets/Data/Passives/BleedingEdge.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ded62c69f70d38a49b21972d857df4e2 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Passives/ItJustWorks.asset.meta b/Assets/Data/Passives/ItJustWorks.asset.meta new file mode 100644 index 0000000..5eeb292 --- /dev/null +++ b/Assets/Data/Passives/ItJustWorks.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d97481a7d54be2045a96165d3977c980 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/DefaultVolumeProfile.asset b/Assets/DefaultVolumeProfile.asset index 0cd0e1a..8cea4d2 100644 --- a/Assets/DefaultVolumeProfile.asset +++ b/Assets/DefaultVolumeProfile.asset @@ -322,6 +322,9 @@ MonoBehaviour: highQualityFiltering: m_OverrideState: 1 m_Value: 0 + filter: + m_OverrideState: 1 + m_Value: 0 downscale: m_OverrideState: 1 m_Value: 0 diff --git a/Assets/Scenes/FightScene.unity.meta b/Assets/Scenes/FightScene.unity.meta new file mode 100644 index 0000000..b8f5e19 --- /dev/null +++ b/Assets/Scenes/FightScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e9acb3a424fb6f8bfada39dfd8292a93 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/GamblingScene.unity.meta b/Assets/Scenes/GamblingScene.unity.meta new file mode 100644 index 0000000..8bf6f0f --- /dev/null +++ b/Assets/Scenes/GamblingScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d10c95dab567850aaad42c0c5c999dde +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Data/CardDatabase.cs.meta b/Assets/Scripts/Data/CardDatabase.cs.meta new file mode 100644 index 0000000..2c4d5fb --- /dev/null +++ b/Assets/Scripts/Data/CardDatabase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fd9e459379f339742a14cf224cd91234 diff --git a/Assets/Scripts/Data/PassiveDefinition.cs.meta b/Assets/Scripts/Data/PassiveDefinition.cs.meta new file mode 100644 index 0000000..1faf515 --- /dev/null +++ b/Assets/Scripts/Data/PassiveDefinition.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9b1f51fb7c354203a2f93ad22fbeb101 diff --git a/Assets/Scripts/Meta/CardLoadout.cs.meta b/Assets/Scripts/Meta/CardLoadout.cs.meta new file mode 100644 index 0000000..44bca39 --- /dev/null +++ b/Assets/Scripts/Meta/CardLoadout.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e003476e8a28025938acf6c377dfbaaa \ No newline at end of file diff --git a/Assets/Scripts/Meta/EntropyWallet.cs b/Assets/Scripts/Meta/EntropyWallet.cs index 83aa4db..b59e6f2 100644 --- a/Assets/Scripts/Meta/EntropyWallet.cs +++ b/Assets/Scripts/Meta/EntropyWallet.cs @@ -7,6 +7,11 @@ namespace KernelPanic.Meta { public int Balance { get; private set; } + public void SetBalance(int amount) + { + Balance = amount < 0 ? 0 : amount; + } + public void Add(int amount) { Balance += amount; diff --git a/Assets/Scripts/Meta/GachaBannerState.cs b/Assets/Scripts/Meta/GachaBannerState.cs new file mode 100644 index 0000000..567191a --- /dev/null +++ b/Assets/Scripts/Meta/GachaBannerState.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; + +namespace KernelPanic.Meta +{ + /// + /// Stores per-banner counters that must persist between sessions. + /// + [Serializable] + public sealed class GachaBannerState + { + public string bannerId; + public int totalPulls; + public int pityCounter; + public int fiveStarPityCounter; + public bool exhausted; + public bool featuredFiveStarGuaranteed; + public List guaranteedDistroIds = new(); + + public GachaBannerState() + { + } + + public GachaBannerState(string bannerId) + { + this.bannerId = bannerId; + } + + public void EnsureLists() + { + guaranteedDistroIds ??= new List(); + } + } +} diff --git a/Assets/Scripts/Meta/GachaBannerState.cs.meta b/Assets/Scripts/Meta/GachaBannerState.cs.meta new file mode 100644 index 0000000..9c3861a --- /dev/null +++ b/Assets/Scripts/Meta/GachaBannerState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 29d7890153d140b2a25b1afb304581f9 diff --git a/Assets/Scripts/Meta/GachaCurrencyType.cs b/Assets/Scripts/Meta/GachaCurrencyType.cs new file mode 100644 index 0000000..4023677 --- /dev/null +++ b/Assets/Scripts/Meta/GachaCurrencyType.cs @@ -0,0 +1,11 @@ +namespace KernelPanic.Meta +{ + /// + /// Identifies currencies spent on banner pulls. + /// + public enum GachaCurrencyType + { + StandardPull, + LimitedPull + } +} diff --git a/Assets/Scripts/Meta/GachaCurrencyType.cs.meta b/Assets/Scripts/Meta/GachaCurrencyType.cs.meta new file mode 100644 index 0000000..75e843f --- /dev/null +++ b/Assets/Scripts/Meta/GachaCurrencyType.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7262840a6d0b4569a70ca757cf4b5d19 diff --git a/Assets/Scripts/Meta/GachaRewardType.cs b/Assets/Scripts/Meta/GachaRewardType.cs new file mode 100644 index 0000000..a7dce16 --- /dev/null +++ b/Assets/Scripts/Meta/GachaRewardType.cs @@ -0,0 +1,12 @@ +namespace KernelPanic.Meta +{ + /// + /// Describes the persistent reward category produced by a gacha pull. + /// + public enum GachaRewardType + { + Equipment, + Distro, + FutureStandardFiveStarDistro + } +} diff --git a/Assets/Scripts/Meta/GachaRewardType.cs.meta b/Assets/Scripts/Meta/GachaRewardType.cs.meta new file mode 100644 index 0000000..ac7c9b9 --- /dev/null +++ b/Assets/Scripts/Meta/GachaRewardType.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6ef2e97a60574b3896db93fd09ec5313 diff --git a/Assets/Scripts/Meta/GachaService.cs b/Assets/Scripts/Meta/GachaService.cs index 4236f45..438b173 100644 --- a/Assets/Scripts/Meta/GachaService.cs +++ b/Assets/Scripts/Meta/GachaService.cs @@ -5,16 +5,150 @@ using KernelPanic.Data; namespace KernelPanic.Meta { /// - /// Provides the contract for spending entropy on persistent unlock pulls. + /// Provides persistent banner pull logic and currency accounting. /// public sealed class GachaService { - private readonly List bannerPool = new(); + public const string BeginnerBannerId = "beginner"; + public const string StandardBannerId = "standard"; + public const string LimitedBannerId = "limited"; + public const int BeginnerMaxPulls = 50; + public const int BeginnerSinglePullCost = 1; + public const int BeginnerTenPullCost = 8; + public const int FourStarHardPity = 10; + public const int FiveStarSoftPityStart = 66; + public const int FiveStarHardPity = 80; + public const int EntropyPerPullToken = 100; - public int PullTokens => 0; // TODO: Replace with the real pull-token wallet when that meta system exists. + private const double FourStarBaseChance = 0.12d; + private const double FiveStarBaseChance = 0.008d; + private const double FiveStarSoftPityStep = 0.07d; + private const double DistroChanceOnFeaturedTier = 0.5d; + private const double LimitedStandardFiveStarChance = 1d / 3d; + + private readonly List bannerPool = new(); + private readonly Dictionary currencyBalances = new(); + private readonly Random random; + private GachaBannerState beginnerState = new(BeginnerBannerId); + + public GachaService() + : this(new Random()) + { + } + + public GachaService(Random random) + { + this.random = random ?? new Random(); + currencyBalances[GachaCurrencyType.StandardPull] = 0; + currencyBalances[GachaCurrencyType.LimitedPull] = 0; + } + + public int PullTokens => GetCurrencyBalance(GachaCurrencyType.StandardPull); + public int LimitedPullTokens => GetCurrencyBalance(GachaCurrencyType.LimitedPull); + public int RootCredits { get; private set; } public IReadOnlyList BannerPool => bannerPool; + public GachaBannerState BeginnerState => beginnerState; + public bool IsBeginnerBannerAvailable => !beginnerState.exhausted && beginnerState.totalPulls < BeginnerMaxPulls && bannerPool.Count > 0; public event Action BannerPoolChanged; + public event Action Changed; + + public void LoadProgress(SaveData data) + { + if (data == null) + { + return; + } + + data.EnsureLists(); + RootCredits = Math.Max(0, data.rootCredits); + SetCurrencyBalanceSilently(GachaCurrencyType.StandardPull, data.standardPullCurrency); + SetCurrencyBalanceSilently(GachaCurrencyType.LimitedPull, data.limitedPullCurrency); + beginnerState = data.beginnerBannerState ?? new GachaBannerState(BeginnerBannerId); + beginnerState.bannerId = BeginnerBannerId; + beginnerState.EnsureLists(); + if (beginnerState.totalPulls >= BeginnerMaxPulls) + { + beginnerState.exhausted = true; + } + + beginnerState.fiveStarPityCounter = Math.Max(0, beginnerState.fiveStarPityCounter); + + Changed?.Invoke(); + } + + public void WriteProgress(SaveData data) + { + if (data == null) + { + return; + } + + data.EnsureLists(); + data.rootCredits = RootCredits; + data.standardPullCurrency = GetCurrencyBalance(GachaCurrencyType.StandardPull); + data.limitedPullCurrency = GetCurrencyBalance(GachaCurrencyType.LimitedPull); + data.beginnerBannerState = CloneState(beginnerState); + } + + public int GetCurrencyBalance(GachaCurrencyType currencyType) + { + return currencyBalances.TryGetValue(currencyType, out int balance) ? balance : 0; + } + + public void SetCurrencyBalance(GachaCurrencyType currencyType, int amount) + { + SetCurrencyBalanceSilently(currencyType, amount); + Changed?.Invoke(); + } + + public void AddCurrency(GachaCurrencyType currencyType, int amount) + { + if (amount <= 0) + { + return; + } + + SetCurrencyBalance(currencyType, GetCurrencyBalance(currencyType) + amount); + } + + public void AddRootCredits(int amount) + { + if (amount <= 0) + { + return; + } + + RootCredits += amount; + Changed?.Invoke(); + } + + public bool ConvertRootCreditsToEntropy(EntropyWallet wallet, int amount, out string failureReason) + { + if (wallet == null) + { + failureReason = "entropy wallet unavailable"; + return false; + } + + if (amount <= 0) + { + failureReason = "no root-credits selected"; + return false; + } + + if (RootCredits < amount) + { + failureReason = $"need {amount} root-credits"; + return false; + } + + RootCredits -= amount; + wallet.Add(amount); + failureReason = null; + Changed?.Invoke(); + return true; + } public void AddToBannerPool(DistroDefinition distro) { @@ -25,26 +159,446 @@ namespace KernelPanic.Meta bannerPool.Add(distro); BannerPoolChanged?.Invoke(); + Changed?.Invoke(); + } + + public void SetBeginnerGuaranteedDistros(IReadOnlyList distros) + { + beginnerState.guaranteedDistroIds.Clear(); + if (distros != null) + { + for (int i = 0; i < distros.Count; i++) + { + DistroDefinition distro = distros[i]; + if (distro != null && !string.IsNullOrWhiteSpace(distro.Id)) + { + beginnerState.guaranteedDistroIds.Add(distro.Id); + } + } + } + + Changed?.Invoke(); + } + + public int GetBeginnerPullCost(int pullCount) + { + return pullCount == 10 ? BeginnerTenPullCost : BeginnerSinglePullCost; + } + + public bool CanPullBeginner(int pullCount, out string failureReason) + { + if (pullCount != 1 && pullCount != 10) + { + failureReason = "only single and ten pulls are supported"; + return false; + } + + if (!IsBeginnerBannerAvailable) + { + failureReason = "beginner banner unavailable"; + return false; + } + + int remainingPulls = BeginnerMaxPulls - beginnerState.totalPulls; + if (pullCount > remainingPulls) + { + failureReason = $"only {remainingPulls} beginner pull(s) remain"; + return false; + } + + int cost = GetBeginnerPullCost(pullCount); + if (GetCurrencyBalance(GachaCurrencyType.StandardPull) < cost) + { + failureReason = $"need {cost} {FormatCurrencyName(GachaCurrencyType.StandardPull)}"; + return false; + } + + failureReason = null; + return true; + } + + public int GetMissingPullTokens(GachaCurrencyType currencyType, int pullCost) + { + return Math.Max(0, pullCost - GetCurrencyBalance(currencyType)); + } + + public bool CanCoverMissingPullTokensWithEntropy(EntropyWallet wallet, int missingPullTokens) + { + return missingPullTokens <= 0 || (wallet != null && wallet.Balance >= missingPullTokens * EntropyPerPullToken); + } + + public GachaPullResult PerformBeginnerPull(int pullCount) + { + return PerformBeginnerPull(pullCount, null, 0); + } + + public GachaPullResult PerformBeginnerPull(int pullCount, EntropyWallet wallet, int entropyTokenCount) + { + if (!CanPullBeginnerWithEntropy(pullCount, wallet, entropyTokenCount, out string failureReason)) + { + return GachaPullResult.Failed(BeginnerBannerId, failureReason); + } + + int cost = GetBeginnerPullCost(pullCount); + int tokenSpend = cost - entropyTokenCount; + if (tokenSpend > 0) + { + SetCurrencyBalanceSilently(GachaCurrencyType.StandardPull, GetCurrencyBalance(GachaCurrencyType.StandardPull) - tokenSpend); + } + + int entropySpent = entropyTokenCount * EntropyPerPullToken; + if (entropySpent > 0) + { + wallet.Spend(entropySpent); + } + + List rewards = new(); + for (int i = 0; i < pullCount; i++) + { + beginnerState.totalPulls++; + rewards.Add(RollBeginnerReward(beginnerState.totalPulls)); + } + + if (beginnerState.totalPulls >= BeginnerMaxPulls) + { + beginnerState.exhausted = true; + } + + Changed?.Invoke(); + return new GachaPullResult(true, BeginnerBannerId, GachaCurrencyType.StandardPull, tokenSpend, entropySpent, rewards, null); + } + + public static string FormatCurrencyName(GachaCurrencyType currencyType) + { + return currencyType switch + { + GachaCurrencyType.LimitedPull => "feature-pull-token", + _ => "stable-pull-token" + }; + } + + public static double GetFiveStarChance(GachaBannerState state) + { + return GetFiveStarChance(state == null ? 0 : state.fiveStarPityCounter); + } + + public static double GetFiveStarChance(int fiveStarPityCounter) + { + int pityPullNumber = Math.Max(0, fiveStarPityCounter) + 1; + if (pityPullNumber >= FiveStarHardPity) + { + return 1d; + } + + if (pityPullNumber < FiveStarSoftPityStart) + { + return FiveStarBaseChance; + } + + int softPitySteps = pityPullNumber - FiveStarSoftPityStart + 1; + return Math.Min(1d, FiveStarBaseChance + softPitySteps * FiveStarSoftPityStep); } public GachaPullResult PerformPull(EntropyWallet wallet, int cost) { - throw new NotImplementedException(); + return PerformBeginnerPull(1); + } + + private GachaReward RollBeginnerReward(int pullNumber) + { + if (pullNumber == 20) + { + AdvanceFiveStarPityAfterNonFiveStar(beginnerState); + return RewardGuaranteedBeginnerDistro(0, "20-pull starter guarantee"); + } + + if (pullNumber == 40) + { + AdvanceFiveStarPityAfterNonFiveStar(beginnerState); + return RewardGuaranteedBeginnerDistro(1, "40-pull starter guarantee"); + } + + if (pullNumber == 50) + { + beginnerState.pityCounter = 0; + return GachaReward.FutureStandardFiveStar("future standard 5-star distro", "50-pull beginner capstone"); + } + + if (TryRollFiveStar(beginnerState, out bool fiveStarPityTriggered)) + { + return RewardRandomFiveStar(fiveStarPityTriggered); + } + + bool pityTriggered = beginnerState.pityCounter >= FourStarHardPity - 1; + bool hitFourStarOrCharacter = pityTriggered || random.NextDouble() < FourStarBaseChance; + if (!hitFourStarOrCharacter) + { + beginnerState.pityCounter++; + return GachaReward.Equipment(3, "3-star equipment", false, false); + } + + beginnerState.pityCounter = 0; + if (bannerPool.Count > 0 && random.NextDouble() < DistroChanceOnFeaturedTier) + { + DistroDefinition distro = bannerPool[random.Next(bannerPool.Count)]; + return GachaReward.DistroReward(distro, 4, pityTriggered, false); + } + + return GachaReward.Equipment(4, "4-star equipment", pityTriggered, false); + } + + private bool TryRollFiveStar(GachaBannerState state, out bool pityTriggered) + { + state ??= beginnerState; + int pityPullNumber = Math.Max(0, state.fiveStarPityCounter) + 1; + pityTriggered = pityPullNumber >= FiveStarSoftPityStart; + + double chance = GetFiveStarChance(state); + bool hit = chance >= 1d || random.NextDouble() < chance; + if (!hit) + { + state.fiveStarPityCounter++; + return false; + } + + state.fiveStarPityCounter = 0; + state.pityCounter = 0; + return true; + } + + private static void AdvanceFiveStarPityAfterNonFiveStar(GachaBannerState state) + { + if (state != null) + { + state.fiveStarPityCounter++; + } + } + + private GachaReward RewardRandomFiveStar(bool pityTriggered) + { + if (bannerPool.Count == 0 || random.NextDouble() >= DistroChanceOnFeaturedTier) + { + return GachaReward.Equipment(5, "5-star equipment", pityTriggered, false); + } + + DistroDefinition distro = bannerPool.Count == 0 ? null : bannerPool[random.Next(bannerPool.Count)]; + return GachaReward.DistroReward(distro, 5, pityTriggered, false); + } + + public bool ResolveFeaturedTierIsDistro() + { + return random.NextDouble() < DistroChanceOnFeaturedTier; + } + + public bool ResolveLimitedFiveStarUsesStandardPool(GachaBannerState limitedState) + { + if (limitedState == null) + { + return false; + } + + if (limitedState.featuredFiveStarGuaranteed) + { + limitedState.featuredFiveStarGuaranteed = false; + return false; + } + + bool useStandardPool = random.NextDouble() < LimitedStandardFiveStarChance; + limitedState.featuredFiveStarGuaranteed = useStandardPool; + return useStandardPool; + } + + private GachaReward RewardGuaranteedBeginnerDistro(int guaranteeIndex, string reason) + { + beginnerState.pityCounter = 0; + DistroDefinition distro = null; + if (guaranteeIndex >= 0 && guaranteeIndex < beginnerState.guaranteedDistroIds.Count) + { + distro = FindBannerPoolDistro(beginnerState.guaranteedDistroIds[guaranteeIndex]); + } + + distro ??= bannerPool.Count == 0 ? null : bannerPool[Math.Min(guaranteeIndex, bannerPool.Count - 1)]; + return distro == null + ? GachaReward.Equipment(4, "4-star equipment", true, true) + : GachaReward.DistroReward(distro, 4, true, true, reason); + } + + private DistroDefinition FindBannerPoolDistro(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + return null; + } + + for (int i = 0; i < bannerPool.Count; i++) + { + DistroDefinition distro = bannerPool[i]; + if (distro != null && string.Equals(distro.Id, id, StringComparison.OrdinalIgnoreCase)) + { + return distro; + } + } + + return null; + } + + private void SetCurrencyBalanceSilently(GachaCurrencyType currencyType, int amount) + { + currencyBalances[currencyType] = Math.Max(0, amount); + } + + private bool CanPullBeginnerWithEntropy(int pullCount, EntropyWallet wallet, int entropyTokenCount, out string failureReason) + { + if (pullCount != 1 && pullCount != 10) + { + failureReason = "only single and ten pulls are supported"; + return false; + } + + if (!IsBeginnerBannerAvailable) + { + failureReason = "beginner banner unavailable"; + return false; + } + + int remainingPulls = BeginnerMaxPulls - beginnerState.totalPulls; + if (pullCount > remainingPulls) + { + failureReason = $"only {remainingPulls} beginner pull(s) remain"; + return false; + } + + int cost = GetBeginnerPullCost(pullCount); + if (entropyTokenCount < 0 || entropyTokenCount > cost) + { + failureReason = "invalid entropy substitute amount"; + return false; + } + + int tokenSpend = cost - entropyTokenCount; + if (GetCurrencyBalance(GachaCurrencyType.StandardPull) < tokenSpend) + { + failureReason = $"need {tokenSpend} {FormatCurrencyName(GachaCurrencyType.StandardPull)}"; + return false; + } + + int entropySpend = entropyTokenCount * EntropyPerPullToken; + if (entropySpend > 0 && (wallet == null || wallet.Balance < entropySpend)) + { + failureReason = $"need {entropySpend} entropy"; + return false; + } + + failureReason = null; + return true; + } + + private static GachaBannerState CloneState(GachaBannerState state) + { + state ??= new GachaBannerState(BeginnerBannerId); + state.EnsureLists(); + GachaBannerState clone = new(state.bannerId) + { + totalPulls = state.totalPulls, + pityCounter = state.pityCounter, + fiveStarPityCounter = state.fiveStarPityCounter, + exhausted = state.exhausted, + featuredFiveStarGuaranteed = state.featuredFiveStarGuaranteed + }; + clone.guaranteedDistroIds.AddRange(state.guaranteedDistroIds); + return clone; } } /// - /// Represents the result of a persistent unlock pull. + /// Describes one reward entry from one pull. + /// + public readonly struct GachaReward + { + private GachaReward( + GachaRewardType rewardType, + int starRating, + string displayName, + DistroDefinition distro, + bool pityTriggered, + bool guaranteed, + string guaranteeReason) + { + RewardType = rewardType; + StarRating = starRating; + DisplayName = displayName; + Distro = distro; + PityTriggered = pityTriggered; + Guaranteed = guaranteed; + GuaranteeReason = guaranteeReason; + } + + public GachaRewardType RewardType { get; } + public int StarRating { get; } + public string DisplayName { get; } + public DistroDefinition Distro { get; } + public bool PityTriggered { get; } + public bool Guaranteed { get; } + public string GuaranteeReason { get; } + + public static GachaReward Equipment(int starRating, string displayName, bool pityTriggered, bool guaranteed) + { + return new GachaReward(GachaRewardType.Equipment, starRating, displayName, null, pityTriggered, guaranteed, null); + } + + public static GachaReward DistroReward(DistroDefinition distro, int starRating, bool pityTriggered, bool guaranteed, string guaranteeReason = null) + { + string displayName = distro == null ? "unknown distro" : DistroDisplayName(distro); + return new GachaReward(GachaRewardType.Distro, starRating, displayName, distro, pityTriggered, guaranteed, guaranteeReason); + } + + public static GachaReward FutureStandardFiveStar(string displayName, string guaranteeReason) + { + return new GachaReward(GachaRewardType.FutureStandardFiveStarDistro, 5, displayName, null, true, true, guaranteeReason); + } + + private static string DistroDisplayName(DistroDefinition distro) + { + return string.IsNullOrWhiteSpace(distro.DisplayName) ? distro.name : distro.DisplayName; + } + } + + /// + /// Represents the result of a banner pull request. /// public readonly struct GachaPullResult { - public GachaPullResult(bool success, string unlockId) + public GachaPullResult( + bool success, + string bannerId, + GachaCurrencyType currencyType, + int currencySpent, + int entropySpent, + IReadOnlyList rewards, + string failureReason) { Success = success; - UnlockId = unlockId; + BannerId = bannerId; + CurrencyType = currencyType; + CurrencySpent = currencySpent; + EntropySpent = entropySpent; + Rewards = rewards ?? Array.Empty(); + FailureReason = failureReason; + UnlockId = Rewards.Count > 0 && Rewards[0].Distro != null ? Rewards[0].Distro.Id : null; } public bool Success { get; } + public string BannerId { get; } + public GachaCurrencyType CurrencyType { get; } + public int CurrencySpent { get; } + public int EntropySpent { get; } + public IReadOnlyList Rewards { get; } + public string FailureReason { get; } public string UnlockId { get; } + + public static GachaPullResult Failed(string bannerId, string reason) + { + return new GachaPullResult(false, bannerId, GachaCurrencyType.StandardPull, 0, 0, Array.Empty(), reason); + } } } diff --git a/Assets/Scripts/Meta/PlayerCollection.cs b/Assets/Scripts/Meta/PlayerCollection.cs index a39c10d..69d9959 100644 --- a/Assets/Scripts/Meta/PlayerCollection.cs +++ b/Assets/Scripts/Meta/PlayerCollection.cs @@ -13,7 +13,6 @@ namespace KernelPanic.Meta private int featuredIndex; // TODO: Populate owned units from SaveService during bootstrap. - // TODO: Add newly unlocked units from GachaService rewards when pulls exist. public IReadOnlyList OwnedUnits => ownedUnits; public DistroDefinition FeaturedUnit => ownedUnits.Count == 0 ? null : ownedUnits[Math.Clamp(featuredIndex, 0, ownedUnits.Count - 1)]; @@ -21,7 +20,7 @@ namespace KernelPanic.Meta public void Add(DistroDefinition unit) { - if (unit == null || ownedUnits.Contains(unit)) + if (unit == null) { return; } @@ -32,6 +31,26 @@ namespace KernelPanic.Meta Changed?.Invoke(); } + public int GetOwnedCount(string unitId) + { + if (string.IsNullOrWhiteSpace(unitId)) + { + return 0; + } + + int count = 0; + for (int i = 0; i < ownedUnits.Count; i++) + { + DistroDefinition unit = ownedUnits[i]; + if (unit != null && string.Equals(unit.Id, unitId, StringComparison.OrdinalIgnoreCase)) + { + count++; + } + } + + return count; + } + public void SelectNextFeatured() { if (ownedUnits.Count < 2) diff --git a/Assets/Scripts/Meta/SaveService.cs b/Assets/Scripts/Meta/SaveService.cs index 3aa4ae2..62d5f8c 100644 --- a/Assets/Scripts/Meta/SaveService.cs +++ b/Assets/Scripts/Meta/SaveService.cs @@ -58,8 +58,13 @@ namespace KernelPanic.Meta public sealed class SaveData { public bool starterChosen; + public int entropyBalance; + public int rootCredits; + public int standardPullCurrency; + public int limitedPullCurrency; public List ownedUnitIds = new(); public List bannerPoolIds = new(); + public GachaBannerState beginnerBannerState = new(GachaService.BeginnerBannerId); public LastRunLoadoutSaveEntry lastRunLoadout = new(); public static SaveData CreateDefault() @@ -71,6 +76,9 @@ namespace KernelPanic.Meta { ownedUnitIds ??= new List(); bannerPoolIds ??= new List(); + beginnerBannerState ??= new GachaBannerState(GachaService.BeginnerBannerId); + beginnerBannerState.bannerId = GachaService.BeginnerBannerId; + beginnerBannerState.EnsureLists(); lastRunLoadout ??= new LastRunLoadoutSaveEntry(); lastRunLoadout.EnsureLists(); } diff --git a/Assets/Scripts/UI/CollectionScreenController.cs.meta b/Assets/Scripts/UI/CollectionScreenController.cs.meta new file mode 100644 index 0000000..6cf970f --- /dev/null +++ b/Assets/Scripts/UI/CollectionScreenController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c6c581de5a6bcbc73a451ce3906547bc \ No newline at end of file diff --git a/Assets/Scripts/UI/CommandKeyBindings.cs.meta b/Assets/Scripts/UI/CommandKeyBindings.cs.meta new file mode 100644 index 0000000..d3ce261 --- /dev/null +++ b/Assets/Scripts/UI/CommandKeyBindings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 73880a4e893003d719622694aa5ffc02 \ No newline at end of file diff --git a/Assets/Scripts/UI/DistroPresentation.cs.meta b/Assets/Scripts/UI/DistroPresentation.cs.meta new file mode 100644 index 0000000..0d31d51 --- /dev/null +++ b/Assets/Scripts/UI/DistroPresentation.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4b02ec0c6f7df1d3aa8211567c3ec2a3 \ No newline at end of file diff --git a/Assets/Scripts/UI/GachaScreenController.cs b/Assets/Scripts/UI/GachaScreenController.cs new file mode 100644 index 0000000..5f06901 --- /dev/null +++ b/Assets/Scripts/UI/GachaScreenController.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections.Generic; +using KernelPanic.Data; +using KernelPanic.Meta; +using UnityEngine; +using UnityEngine.TextCore.Text; +using UnityEngine.UIElements; + +namespace KernelPanic.UI +{ + /// + /// Renders banner selection and pull controls for the gacha screen. + /// + [Serializable] + public sealed class GachaScreenController + { + private const string SelectedClassName = "selected"; + + private readonly List bannerRows = new(); + private readonly string[] bannerIds = + { + GachaService.BeginnerBannerId, + GachaService.StandardBannerId, + GachaService.LimitedBannerId + }; + + private VisualElement bannerList; + private VisualElement bannerDetail; + private DistroDatabase distroDatabase; + private FontAsset monospaceFont; + private GachaService gachaService; + private PlayerCollection playerCollection; + private EntropyWallet wallet; + private Action onChanged; + private Action requestRootCreditExchange; + private int selectedBannerIndex; + private int pendingPullCount; + private int pendingMissingTokens; + private bool hasOpened; + private string resultBannerId; + private string resultText; + + public void Bind(VisualElement root, DistroDatabase database, FontAsset artFont, GachaService service, PlayerCollection collection, EntropyWallet entropyWallet, Action changedCallback, Action rootCreditExchangeCallback) + { + distroDatabase = database; + monospaceFont = artFont; + gachaService = service; + playerCollection = collection; + wallet = entropyWallet; + onChanged = changedCallback; + requestRootCreditExchange = rootCreditExchangeCallback; + bannerList = root.Q("GachaBannerList"); + bannerDetail = root.Q("GachaBannerDetail"); + } + + public void Open() + { + if (!hasOpened) + { + selectedBannerIndex = 0; + hasOpened = true; + } + + Refresh(); + } + + public void Refresh() + { + if (bannerList == null || bannerDetail == null || gachaService == null) + { + return; + } + + bannerRows.Clear(); + bannerList.Clear(); + + for (int i = 0; i < bannerIds.Length; i++) + { + int index = i; + VisualElement row = BuildBannerRow(bannerIds[i]); + row.RegisterCallback(_ => SelectBanner(index)); + bannerList.Add(row); + bannerRows.Add(row); + } + + selectedBannerIndex = Mathf.Clamp(selectedBannerIndex, 0, bannerRows.Count - 1); + ApplySelectedBanner(); + } + + public void HandleKeyDown(KeyDownEvent evt) + { + // Pulls are deliberately click-only to avoid accidental currency spend. + } + + private VisualElement BuildBannerRow(string bannerId) + { + VisualElement row = new(); + row.AddToClassList("gacha-banner-row"); + + row.Add(new Label(">") { name = $"GachaBannerCursor_{bannerId}" }); + row.ElementAt(0).AddToClassList("command-cursor"); + + row.Add(new Label(GetBannerTitle(bannerId)) { name = $"GachaBannerName_{bannerId}" }); + row.ElementAt(1).AddToClassList("command-text"); + + row.Add(new Label(GetBannerStatus(bannerId)) { name = $"GachaBannerStatus_{bannerId}" }); + row.ElementAt(2).AddToClassList("command-description"); + + return row; + } + + private void SelectBanner(int index) + { + if (bannerRows.Count == 0) + { + return; + } + + selectedBannerIndex = (index + bannerRows.Count) % bannerRows.Count; + ApplySelectedBanner(); + } + + private void ApplySelectedBanner() + { + for (int i = 0; i < bannerRows.Count; i++) + { + bool selected = i == selectedBannerIndex; + bannerRows[i].EnableInClassList(SelectedClassName, selected); + bannerRows[i].Q