diff --git a/Assets/Scripts/Data/LanguageCatalog.cs b/Assets/Scripts/Data/LanguageCatalog.cs new file mode 100644 index 0000000..5bc2155 --- /dev/null +++ b/Assets/Scripts/Data/LanguageCatalog.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using KernelPanic.Core; + +namespace KernelPanic.Data +{ + /// + /// UI-facing language reference copy. This is not card data and does not unlock content by itself. + /// + public static class LanguageCatalog + { + private static readonly LanguageCatalogEntry[] Entries = + { + new(Language.C, "C", "bare metal", ResolutionTrack.Native, "TODO: Native cards hit immediately and reward careful resource timing. Signature mechanic copy pending.", null, Distro("arch", "Arch"), Distro("templeos", "TempleOS")), + new(Language.CPlusPlus, "C++", "template arsenal", ResolutionTrack.Native, "TODO: Native cards hit immediately with complex setup payoffs. Signature mechanic copy pending.", "unlock: upgrade a C card during a run"), + new(Language.Rust, "Rust", "safe systems", ResolutionTrack.Native, "TODO: Rust resolves on the native track with reliable damage and defensive spillover. Signature mechanic copy pending.", null, Distro("arch", "Arch"), Distro("fedora", "Fedora")), + new(Language.Python, "Python", "queued scripts", ResolutionTrack.InterpreterQueue, "TODO: Python queues flexible effects that resolve at end of turn. Signature mechanic copy pending.", null, Distro("ubuntu", "Ubuntu"), Distro("kali", "Kali"), Distro("mint", "Mint")), + new(Language.JavaScript, "JavaScript", "runtime chaos", ResolutionTrack.Native, "TODO: JavaScript fires instantly with swingy side effects. Signature mechanic copy pending.", null, Distro("ubuntu", "Ubuntu"), Distro("endeavouros", "EndeavourOS"), Distro("mint", "Mint")), + new(Language.TypeScript, "TypeScript", "typed runtime", ResolutionTrack.Native, "TODO: TypeScript stabilizes JavaScript-style tempo with stricter setup. Signature mechanic copy pending.", "unlock: upgrade a JavaScript card during a run"), + new(Language.Haskell, "Haskell", "lazy proofs", ResolutionTrack.LazyStack, "TODO: Haskell stores delayed work on the lazy stack before resolving in bursts. Signature mechanic copy pending.", null, Distro("gentoo", "Gentoo"), Distro("nixos", "NixOS")), + new(Language.Assembly, "Assembly", "raw opcodes", ResolutionTrack.Native, "TODO: Assembly is immediate, low-level, and high commitment. Signature mechanic copy pending.", null, Distro("gentoo", "Gentoo"), Distro("templeos", "TempleOS")), + new(Language.Java, "Java", "warming vm", ResolutionTrack.Native, "TODO: Java starts expensive and gets cheaper as the combat warms up. Signature mechanic copy pending.", null, Distro("debian", "Debian"), Distro("fedora", "Fedora")), + new(Language.Go, "Go", "small services", ResolutionTrack.Native, "TODO: Go uses direct native actions with simple scaling and concurrency hooks. Signature mechanic copy pending.", null, Distro("cachyos", "CachyOS"), Distro("nixos", "NixOS")), + new(Language.Ruby, "Ruby", "sharp scripts", ResolutionTrack.InterpreterQueue, "TODO: Ruby queues expressive effects with combo-friendly timing. Signature mechanic copy pending.", null, Distro("kali", "Kali"), Distro("endeavouros", "EndeavourOS")), + new(Language.PHP, "PHP", "legacy glue", ResolutionTrack.InterpreterQueue, "TODO: PHP queues practical effects that improve when the board is messy. Signature mechanic copy pending.", null, Distro("debian", "Debian"), Distro("cachyos", "CachyOS")), + }; + + public static IReadOnlyList All => Entries; + + public static LanguageCatalogEntry Get(Language language) + { + for (int i = 0; i < Entries.Length; i++) + { + if (Entries[i].Language == language) + { + return Entries[i]; + } + } + + throw new ArgumentOutOfRangeException(nameof(language), language, "Language is not registered in the catalog."); + } + + private static LanguageDistroReference Distro(string id, string displayName) + { + return new LanguageDistroReference(id, displayName); + } + } + + public readonly struct LanguageCatalogEntry + { + public LanguageCatalogEntry(Language language, string displayName, string identityTag, ResolutionTrack resolutionTrack, string howItWorks, string unlockHint, params LanguageDistroReference[] supportingDistros) + { + Language = language; + DisplayName = displayName; + IdentityTag = identityTag; + ResolutionTrack = resolutionTrack; + HowItWorks = howItWorks; + UnlockHint = unlockHint; + SupportingDistros = supportingDistros ?? Array.Empty(); + } + + public Language Language { get; } + public string DisplayName { get; } + public string IdentityTag { get; } + public ResolutionTrack ResolutionTrack { get; } + public string HowItWorks { get; } + public string UnlockHint { get; } + public IReadOnlyList SupportingDistros { get; } + } + + public readonly struct LanguageDistroReference + { + public LanguageDistroReference(string id, string displayName) + { + Id = id; + DisplayName = displayName; + } + + public string Id { get; } + public string DisplayName { get; } + } +} diff --git a/Assets/Scripts/Data/LanguageDeckDatabase.cs b/Assets/Scripts/Data/LanguageDeckDatabase.cs new file mode 100644 index 0000000..3bc0284 --- /dev/null +++ b/Assets/Scripts/Data/LanguageDeckDatabase.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using KernelPanic.Core; +using UnityEngine; + +namespace KernelPanic.Data +{ + /// + /// Resolves language starter decks for collection display. + /// + public sealed class LanguageDeckDatabase : ScriptableObject + { + [SerializeField] private List allDecks = new(); + + public IReadOnlyList AllDecks => allDecks; + + public LanguageDeckDefinition FindByLanguage(Language language) + { + for (int i = 0; i < allDecks.Count; i++) + { + LanguageDeckDefinition deck = allDecks[i]; + if (deck != null && deck.Language == language) + { + return deck; + } + } + + return null; + } + } +} diff --git a/Assets/Scripts/Data/LanguageDeckDefinition.cs b/Assets/Scripts/Data/LanguageDeckDefinition.cs new file mode 100644 index 0000000..37d33c1 --- /dev/null +++ b/Assets/Scripts/Data/LanguageDeckDefinition.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using KernelPanic.Core; +using UnityEngine; + +namespace KernelPanic.Data +{ + /// + /// Defines the read-only starter deck shown for a language in the collection codex. + /// + public sealed class LanguageDeckDefinition : ScriptableObject + { + [System.Serializable] + public struct LanguageDeckEntry + { + [SerializeField] private CardDefinition card; + [SerializeField] private int count; + + public CardDefinition Card => card; + public int Count => count; + } + + [SerializeField] private Language language; + [SerializeField] private List entries = new(); + + public Language Language => language; + public IReadOnlyList Entries => entries; + } +} diff --git a/Assets/Scripts/Meta/LanguageUnlock.cs b/Assets/Scripts/Meta/LanguageUnlock.cs new file mode 100644 index 0000000..a546321 --- /dev/null +++ b/Assets/Scripts/Meta/LanguageUnlock.cs @@ -0,0 +1,46 @@ +using System; +using KernelPanic.Core; +using KernelPanic.Data; + +namespace KernelPanic.Meta +{ + /// + /// Read-only unlock rule for language availability. + /// + public static class LanguageUnlock + { + public static bool IsUnlocked(Language language, PlayerCollection collection) + { + if (language == Language.CPlusPlus || language == Language.TypeScript) + { + // TODO: evolved-language unlock gate, separate from distro ownership. + return false; + } + + if (collection == null) + { + return false; + } + + LanguageCatalogEntry entry = LanguageCatalog.Get(language); + for (int unitIndex = 0; unitIndex < collection.OwnedUnits.Count; unitIndex++) + { + DistroDefinition unit = collection.OwnedUnits[unitIndex]; + if (unit == null) + { + continue; + } + + for (int distroIndex = 0; distroIndex < entry.SupportingDistros.Count; distroIndex++) + { + if (string.Equals(unit.Id, entry.SupportingDistros[distroIndex].Id, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + + return false; + } + } +}