From bad3bb4a89736b7b8b343dc14e0d0259ab7e45a1 Mon Sep 17 00:00:00 2001 From: Tayrtahn Date: Sat, 18 May 2024 12:20:00 -0400 Subject: [PATCH] Random book story generator refactor (#28082) * Randomized book overhaul * Fix prototype names * Improved setting paper content * Praise Ratvar --- .../Paper/PaperRandomStoryComponent.cs | 13 +- .../Paper/PaperRandomStorySystem.cs | 12 +- .../EntitySystems/StoryGeneratorSystem.cs | 54 +++++ .../Prototypes/StoryTemplatePrototype.cs | 33 +++ .../Locale/en-US/paper/story-generation.ftl | 72 +++---- .../Locale/en-US/storygen/story-template.ftl | 4 + .../Prototypes/Datasets/story_generation.yml | 204 +++++++++--------- .../Entities/Objects/Misc/books.yml | 32 +-- .../Prototypes/StoryGen/story-templates.yml | 16 ++ 9 files changed, 261 insertions(+), 179 deletions(-) create mode 100644 Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs create mode 100644 Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs create mode 100644 Resources/Locale/en-US/storygen/story-template.ftl create mode 100644 Resources/Prototypes/StoryGen/story-templates.yml diff --git a/Content.Server/Paper/PaperRandomStoryComponent.cs b/Content.Server/Paper/PaperRandomStoryComponent.cs index 7c5744f087..b8e07f0ee8 100644 --- a/Content.Server/Paper/PaperRandomStoryComponent.cs +++ b/Content.Server/Paper/PaperRandomStoryComponent.cs @@ -1,14 +1,17 @@ +using Content.Shared.StoryGen; +using Robust.Shared.Prototypes; + namespace Content.Server.Paper; /// -/// Adds randomly generated stories to Paper component +/// Adds a randomly generated story to the content of a /// [RegisterComponent, Access(typeof(PaperRandomStorySystem))] public sealed partial class PaperRandomStoryComponent : Component { + /// + /// The ID to use for story generation. + /// [DataField] - public List? StorySegments; - - [DataField] - public string StorySeparator = " "; + public ProtoId Template; } diff --git a/Content.Server/Paper/PaperRandomStorySystem.cs b/Content.Server/Paper/PaperRandomStorySystem.cs index e7712009c2..156718f545 100644 --- a/Content.Server/Paper/PaperRandomStorySystem.cs +++ b/Content.Server/Paper/PaperRandomStorySystem.cs @@ -1,11 +1,11 @@ -using Content.Server.RandomMetadata; +using Content.Shared.StoryGen; namespace Content.Server.Paper; public sealed class PaperRandomStorySystem : EntitySystem { - - [Dependency] private readonly RandomMetadataSystem _randomMeta = default!; + [Dependency] private readonly StoryGeneratorSystem _storyGen = default!; + [Dependency] private readonly PaperSystem _paper = default!; public override void Initialize() { @@ -19,11 +19,9 @@ public sealed class PaperRandomStorySystem : EntitySystem if (!TryComp(paperStory, out var paper)) return; - if (paperStory.Comp.StorySegments == null) + if (!_storyGen.TryGenerateStoryFromTemplate(paperStory.Comp.Template, out var story)) return; - var story = _randomMeta.GetRandomFromSegments(paperStory.Comp.StorySegments, paperStory.Comp.StorySeparator); - - paper.Content += $"\n{story}"; + _paper.SetContent(paperStory.Owner, story, paper); } } diff --git a/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs b/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs new file mode 100644 index 0000000000..51ad85730c --- /dev/null +++ b/Content.Shared/StoryGen/EntitySystems/StoryGeneratorSystem.cs @@ -0,0 +1,54 @@ +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Collections; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Shared.StoryGen; + +/// +/// Provides functionality to generate a story from a . +/// +public sealed partial class StoryGeneratorSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _protoMan = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + /// + /// Tries to generate a random story using the given template, picking a random word from the referenced + /// datasets for each variable and passing them into the localization system with template. + /// If is specified, the randomizer will be seeded with it for consistent story generation; + /// otherwise the variables will be randomized. + /// Fails if the template prototype cannot be loaded. + /// + /// true if the template was loaded, otherwise false. + public bool TryGenerateStoryFromTemplate(ProtoId template, [NotNullWhen(true)] out string? story, int? seed = null) + { + // Get the story template prototype from the ID + if (!_protoMan.TryIndex(template, out var templateProto)) + { + story = null; + return false; + } + + // If given a seed, use it + if (seed != null) + _random.SetSeed(seed.Value); + + // Pick values for all of the variables in the template + var variables = new ValueList<(string, object)>(templateProto.Variables.Count); + foreach (var (name, list) in templateProto.Variables) + { + // Get the prototype for the world list dataset + if (!_protoMan.TryIndex(list, out var listProto)) + continue; // Missed one, but keep going with the rest of the story + + // Pick a random word from the dataset and localize it + var chosenWord = Loc.GetString(_random.Pick(listProto.Values)); + variables.Add((name, chosenWord)); + } + + // Pass the variables to the localization system and build the story + story = Loc.GetString(templateProto.LocId, variables.ToArray()); + return true; + } +} diff --git a/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs new file mode 100644 index 0000000000..7f6afacccc --- /dev/null +++ b/Content.Shared/StoryGen/Prototypes/StoryTemplatePrototype.cs @@ -0,0 +1,33 @@ +using Content.Shared.Dataset; +using Robust.Shared.Prototypes; + +namespace Content.Shared.StoryGen; + +/// +/// Prototype for a story template that can be filled in with words chosen from s. +/// +[Serializable, Prototype("storyTemplate")] +public sealed partial class StoryTemplatePrototype : IPrototype +{ + /// + /// Identifier for this prototype instance. + /// + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// Localization ID of the Fluent string that forms the structure of this story. + /// + [DataField(required: true)] + public LocId LocId { get; } = default!; + + /// + /// Dictionary containing the name of each variable to pass to the template and the ID of the + /// from which a random entry will be selected as its value. + /// For example, name: book_character will pick a random entry from the book_character + /// dataset which can then be used in the template by {$name}. + /// + [DataField] + public Dictionary> Variables { get; } = default!; +} diff --git a/Resources/Locale/en-US/paper/story-generation.ftl b/Resources/Locale/en-US/paper/story-generation.ftl index bcd1c8901e..94ecbc3caa 100644 --- a/Resources/Locale/en-US/paper/story-generation.ftl +++ b/Resources/Locale/en-US/paper/story-generation.ftl @@ -86,7 +86,7 @@ story-gen-book-character29 = space dragon story-gen-book-character30 = revolutionary story-gen-book-character31 = nuclear operative story-gen-book-character32 = narsie cultist -story-gen-book-character33 = ratwar cultist +story-gen-book-character33 = ratvar cultist story-gen-book-character34 = greytider story-gen-book-character35 = arachnid story-gen-book-character36 = vox @@ -98,7 +98,7 @@ story-gen-book-character40 = slime story-gen-book-character-trait1 = stupid story-gen-book-character-trait2 = smart story-gen-book-character-trait3 = funny -story-gen-book-character-trait4 = attractive +story-gen-book-character-trait4 = attractive story-gen-book-character-trait5 = charming story-gen-book-character-trait6 = nasty story-gen-book-character-trait7 = dying @@ -113,7 +113,7 @@ story-gen-book-character-trait15 = сharismatic story-gen-book-character-trait16 = stoic story-gen-book-character-trait17 = cute story-gen-book-character-trait18 = dwarven -story-gen-book-character-trait19 = beer-smelling +story-gen-book-character-trait19 = beer-smelling story-gen-book-character-trait20 = joyful story-gen-book-character-trait21 = painfully beautiful story-gen-book-character-trait22 = robotic @@ -121,20 +121,20 @@ story-gen-book-character-trait23 = holographic story-gen-book-character-trait24 = hysterically laughing story-gen-book-event1 = a zombie outbreak -story-gen-book-event2 = a nuclear explosion +story-gen-book-event2 = a nuclear explosion story-gen-book-event3 = a mass murder story-gen-book-event4 = a sudden depressurization story-gen-book-event5 = a blackout -story-gen-book-event6 = the starvation of the protagonists +story-gen-book-event6 = the protagonists nearly starving story-gen-book-event7 = a wasting illness story-gen-book-event8 = love at first sight story-gen-book-event9 = a rush of inspiration -story-gen-book-event10 = the occurrence of some mystical phenomena +story-gen-book-event10 = some mystical phenomena story-gen-book-event11 = divine intervention story-gen-book-event12 = the characters' own selfish motives story-gen-book-event13 = an unforeseen deception -story-gen-book-event14 = the resurrection of one of these characters from the dead -story-gen-book-event15 = the terrible torture of the protagonist +story-gen-book-event14 = the resurrection of one of the characters from the dead +story-gen-book-event15 = the brutal torture of the protagonists story-gen-book-event16 = the inadvertent loosing of a gravitational singularity story-gen-book-event17 = a psychic prediction of future events story-gen-book-event18 = an antimatter explosion @@ -145,31 +145,31 @@ story-gen-book-event22 = having a quarrel with a close friend story-gen-book-event23 = the sudden loss of their home in a fiery blaze story-gen-book-event24 = the loss of a PDA -story-gen-book-action1 = share in a kiss with a -story-gen-book-action2 = strangle to death a -story-gen-book-action3 = manage to blow apart a -story-gen-book-action4 = manage to win a game of chess against a -story-gen-book-action5 = narrowly lose a game of chess against a -story-gen-book-action6 = reveal the hidden secrets of a -story-gen-book-action7 = manipulate a -story-gen-book-action8 = sacrifice upon an altar a -story-gen-book-action9 = attend the wedding of a -story-gen-book-action10 = join forces to defeat their common enemy, a -story-gen-book-action11 = are forced to work together to escape a +story-gen-book-action1 = share in a kiss with +story-gen-book-action2 = strangle +story-gen-book-action3 = blow apart +story-gen-book-action4 = win a game of chess against +story-gen-book-action5 = lose a game of chess against +story-gen-book-action6 = reveal the hidden secrets of +story-gen-book-action7 = manipulate +story-gen-book-action8 = sacrifice a hamster to +story-gen-book-action9 = infiltrate the wedding of +story-gen-book-action10 = join forces to defeat their common enemy, +story-gen-book-action11 = are forced to work together to escape story-gen-book-action12 = give a valuable gift to -story-gen-book-action-trait1 = terribly -story-gen-book-action-trait2 = disgustingly +story-gen-book-action-trait1 = clumsily +story-gen-book-action-trait2 = disgustingly story-gen-book-action-trait3 = marvelously story-gen-book-action-trait4 = nicely story-gen-book-action-trait5 = weirdly story-gen-book-action-trait6 = amusingly story-gen-book-action-trait7 = fancifully story-gen-book-action-trait8 = impressively -story-gen-book-action-trait9 = irresponsibly -story-gen-book-action-trait10 = severely -story-gen-book-action-trait11 = ruthlessly -story-gen-book-action-trait12 = playfully +story-gen-book-action-trait9 = irresponsibly +story-gen-book-action-trait10 = severely +story-gen-book-action-trait11 = ruthlessly +story-gen-book-action-trait12 = playfully story-gen-book-action-trait13 = thoughtfully story-gen-book-location1 = in an underground complex @@ -178,7 +178,7 @@ story-gen-book-location3 = while trapped in outer space story-gen-book-location4 = while in a news office story-gen-book-location5 = in a hidden garden story-gen-book-location6 = in the kitchen of a local restaurant -story-gen-book-location7 = under the counter of the local sports bar +story-gen-book-location7 = under the counter of the local sports bar story-gen-book-location8 = in an ancient library story-gen-book-location9 = while deep in bowels of the space station's maintenance corridors story-gen-book-location10 = on the bridge of a starship @@ -192,7 +192,7 @@ story-gen-book-location17 = standing too close to an anomaly story-gen-book-location18 = while huddling on the evacuation shuttle story-gen-book-location19 = standing in freshly fallen snow story-gen-book-location20 = lost in the woods -story-gen-book-location21 = iin the harsh desert +story-gen-book-location21 = in the harsh desert story-gen-book-location22 = worrying about their social media networks story-gen-book-location23 = atop of a mountain story-gen-book-location24 = while driving a car @@ -207,15 +207,15 @@ story-gen-book-location32 = while trapped in a shadow dimension story-gen-book-location33 = while trying to escape a destroyed space station story-gen-book-location34 = while sandwiched between a Tesla ball and a gravitational singularity -story-gen-book-element1 = The plot -story-gen-book-element2 = The twist -story-gen-book-element3 = The climax -story-gen-book-element4 = The final act -story-gen-book-element5 = The ending -story-gen-book-element6 = The moral of the story -story-gen-book-element7 = The theme of this work -story-gen-book-element8 = The literary style -story-gen-book-element9 = The illustrations +story-gen-book-element1 = plot +story-gen-book-element2 = twist +story-gen-book-element3 = climax +story-gen-book-element4 = final act +story-gen-book-element5 = ending +story-gen-book-element6 = moral of the story +story-gen-book-element7 = theme of this work +story-gen-book-element8 = literary style +story-gen-book-element9 = artwork story-gen-book-element-trait1 = terrifying story-gen-book-element-trait2 = disgusting diff --git a/Resources/Locale/en-US/storygen/story-template.ftl b/Resources/Locale/en-US/storygen/story-template.ftl new file mode 100644 index 0000000000..b535e2fd95 --- /dev/null +++ b/Resources/Locale/en-US/storygen/story-template.ftl @@ -0,0 +1,4 @@ +story-template-generic = + This is { INDEFINITE($bookGenre) } {$bookGenre} about { INDEFINITE($char1Adj) } {$char1Adj} {$char1Type} and { INDEFINITE($char2Adj) } {$char2Adj} {$char2Type}. Due to {$event}, they {$actionTrait} {$action} { INDEFINITE($char3Type) } {$char3Type} {$location}. + + The {$element} is {$elementTrait}. diff --git a/Resources/Prototypes/Datasets/story_generation.yml b/Resources/Prototypes/Datasets/story_generation.yml index 1083a6acdb..1a461c7596 100644 --- a/Resources/Prototypes/Datasets/story_generation.yml +++ b/Resources/Prototypes/Datasets/story_generation.yml @@ -1,31 +1,31 @@ - type: dataset - id: book_type + id: BookTypes values: - - story-gen-book-type1 - - story-gen-book-type2 - - story-gen-book-type3 - - story-gen-book-type4 - - story-gen-book-type5 - - story-gen-book-type6 - - story-gen-book-type7 - - story-gen-book-type8 - - story-gen-book-type9 + - story-gen-book-type1 + - story-gen-book-type2 + - story-gen-book-type3 + - story-gen-book-type4 + - story-gen-book-type5 + - story-gen-book-type6 + - story-gen-book-type7 + - story-gen-book-type8 + - story-gen-book-type9 - story-gen-book-type10 - story-gen-book-type11 - story-gen-book-type12 - type: dataset - id: book_genre + id: BookGenres values: - - story-gen-book-genre1 - - story-gen-book-genre2 - - story-gen-book-genre3 - - story-gen-book-genre4 - - story-gen-book-genre5 - - story-gen-book-genre6 - - story-gen-book-genre7 - - story-gen-book-genre8 - - story-gen-book-genre9 + - story-gen-book-genre1 + - story-gen-book-genre2 + - story-gen-book-genre3 + - story-gen-book-genre4 + - story-gen-book-genre5 + - story-gen-book-genre6 + - story-gen-book-genre7 + - story-gen-book-genre8 + - story-gen-book-genre9 - story-gen-book-genre10 - story-gen-book-genre11 - story-gen-book-genre12 @@ -33,17 +33,17 @@ - story-gen-book-genre14 - type: dataset - id: book_hint_appearance + id: BookHintAppearances values: - - story-gen-book-appearance1 - - story-gen-book-appearance2 - - story-gen-book-appearance3 - - story-gen-book-appearance4 - - story-gen-book-appearance5 - - story-gen-book-appearance6 - - story-gen-book-appearance7 - - story-gen-book-appearance8 - - story-gen-book-appearance9 + - story-gen-book-appearance1 + - story-gen-book-appearance2 + - story-gen-book-appearance3 + - story-gen-book-appearance4 + - story-gen-book-appearance5 + - story-gen-book-appearance6 + - story-gen-book-appearance7 + - story-gen-book-appearance8 + - story-gen-book-appearance9 - story-gen-book-appearance10 - story-gen-book-appearance11 - story-gen-book-appearance12 @@ -64,17 +64,17 @@ - story-gen-book-appearance27 - type: dataset - id: book_character + id: BookCharacters values: - - story-gen-book-character1 - - story-gen-book-character2 - - story-gen-book-character3 - - story-gen-book-character4 - - story-gen-book-character5 - - story-gen-book-character6 - - story-gen-book-character7 - - story-gen-book-character8 - - story-gen-book-character9 + - story-gen-book-character1 + - story-gen-book-character2 + - story-gen-book-character3 + - story-gen-book-character4 + - story-gen-book-character5 + - story-gen-book-character6 + - story-gen-book-character7 + - story-gen-book-character8 + - story-gen-book-character9 - story-gen-book-character10 - story-gen-book-character11 - story-gen-book-character12 @@ -108,17 +108,17 @@ - story-gen-book-character40 - type: dataset - id: book_character_trait + id: BookCharacterTraits values: - - story-gen-book-character-trait1 - - story-gen-book-character-trait2 - - story-gen-book-character-trait3 - - story-gen-book-character-trait4 - - story-gen-book-character-trait5 - - story-gen-book-character-trait6 - - story-gen-book-character-trait7 - - story-gen-book-character-trait8 - - story-gen-book-character-trait9 + - story-gen-book-character-trait1 + - story-gen-book-character-trait2 + - story-gen-book-character-trait3 + - story-gen-book-character-trait4 + - story-gen-book-character-trait5 + - story-gen-book-character-trait6 + - story-gen-book-character-trait7 + - story-gen-book-character-trait8 + - story-gen-book-character-trait9 - story-gen-book-character-trait10 - story-gen-book-character-trait11 - story-gen-book-character-trait12 @@ -137,17 +137,17 @@ - type: dataset - id: book_event + id: BookEvents values: - - story-gen-book-event1 - - story-gen-book-event2 - - story-gen-book-event3 - - story-gen-book-event4 - - story-gen-book-event5 - - story-gen-book-event6 - - story-gen-book-event7 - - story-gen-book-event8 - - story-gen-book-event9 + - story-gen-book-event1 + - story-gen-book-event2 + - story-gen-book-event3 + - story-gen-book-event4 + - story-gen-book-event5 + - story-gen-book-event6 + - story-gen-book-event7 + - story-gen-book-event8 + - story-gen-book-event9 - story-gen-book-event10 - story-gen-book-event11 - story-gen-book-event12 @@ -165,50 +165,50 @@ - story-gen-book-event24 - type: dataset - id: book_action + id: BookActions values: - - story-gen-book-action1 - - story-gen-book-action2 - - story-gen-book-action3 - - story-gen-book-action4 - - story-gen-book-action5 - - story-gen-book-action6 - - story-gen-book-action7 - - story-gen-book-action8 - - story-gen-book-action9 + - story-gen-book-action1 + - story-gen-book-action2 + - story-gen-book-action3 + - story-gen-book-action4 + - story-gen-book-action5 + - story-gen-book-action6 + - story-gen-book-action7 + - story-gen-book-action8 + - story-gen-book-action9 - story-gen-book-action10 - story-gen-book-action11 - story-gen-book-action12 - type: dataset - id: book_action_trait + id: BookActionTraits values: - - story-gen-book-action-trait1 - - story-gen-book-action-trait2 - - story-gen-book-action-trait3 - - story-gen-book-action-trait4 - - story-gen-book-action-trait5 - - story-gen-book-action-trait6 - - story-gen-book-action-trait7 - - story-gen-book-action-trait8 - - story-gen-book-action-trait9 + - story-gen-book-action-trait1 + - story-gen-book-action-trait2 + - story-gen-book-action-trait3 + - story-gen-book-action-trait4 + - story-gen-book-action-trait5 + - story-gen-book-action-trait6 + - story-gen-book-action-trait7 + - story-gen-book-action-trait8 + - story-gen-book-action-trait9 - story-gen-book-action-trait10 - story-gen-book-action-trait11 - story-gen-book-action-trait12 - story-gen-book-action-trait13 - type: dataset - id: book_location + id: BookLocations values: - - story-gen-book-location1 - - story-gen-book-location2 - - story-gen-book-location3 - - story-gen-book-location4 - - story-gen-book-location5 - - story-gen-book-location6 - - story-gen-book-location7 - - story-gen-book-location8 - - story-gen-book-location9 + - story-gen-book-location1 + - story-gen-book-location2 + - story-gen-book-location3 + - story-gen-book-location4 + - story-gen-book-location5 + - story-gen-book-location6 + - story-gen-book-location7 + - story-gen-book-location8 + - story-gen-book-location9 - story-gen-book-location10 - story-gen-book-location11 - story-gen-book-location12 @@ -236,7 +236,7 @@ - story-gen-book-location34 - type: dataset - id: book_story_element + id: BookStoryElements values: - story-gen-book-element1 - story-gen-book-element2 @@ -249,18 +249,18 @@ - story-gen-book-element9 - type: dataset - id: book_story_element_trait + id: BookStoryElementTraits values: - - story-gen-book-element-trait1 - - story-gen-book-element-trait2 - - story-gen-book-element-trait3 - - story-gen-book-element-trait4 - - story-gen-book-element-trait5 - - story-gen-book-element-trait6 - - story-gen-book-element-trait7 - - story-gen-book-element-trait8 - - story-gen-book-element-trait9 + - story-gen-book-element-trait1 + - story-gen-book-element-trait2 + - story-gen-book-element-trait3 + - story-gen-book-element-trait4 + - story-gen-book-element-trait5 + - story-gen-book-element-trait6 + - story-gen-book-element-trait7 + - story-gen-book-element-trait8 + - story-gen-book-element-trait9 - story-gen-book-element-trait10 - story-gen-book-element-trait11 - story-gen-book-element-trait12 - - story-gen-book-element-trait13 \ No newline at end of file + - story-gen-book-element-trait13 diff --git a/Resources/Prototypes/Entities/Objects/Misc/books.yml b/Resources/Prototypes/Entities/Objects/Misc/books.yml index 25e6bb9f94..3fc90048dd 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/books.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/books.yml @@ -361,8 +361,8 @@ components: - type: RandomMetadata nameSegments: - - book_hint_appearance - - book_type + - BookHintAppearances + - BookTypes - type: RandomSprite available: - cover: @@ -423,33 +423,7 @@ suffix: random visual, random story components: - type: PaperRandomStory - storySegments: - - "This is a " - - book_genre - - " about a " - - book_character_trait - - " " - - book_character - - " and " - - book_character_trait - - " " - - book_character - - ". Due to " - - book_event - - ", they " - - book_action_trait - - " " - - book_action - - " " - - book_character - - " " - - book_location - - ". \n\n" - - book_story_element - - " is " - - book_story_element_trait - - "." - storySeparator: "" + template: GenericStory - type: entity parent: BookBase diff --git a/Resources/Prototypes/StoryGen/story-templates.yml b/Resources/Prototypes/StoryGen/story-templates.yml new file mode 100644 index 0000000000..f05fd5afa6 --- /dev/null +++ b/Resources/Prototypes/StoryGen/story-templates.yml @@ -0,0 +1,16 @@ +- type: storyTemplate + id: GenericStory + locId: story-template-generic + variables: + bookGenre: BookGenres + char1Type: BookCharacters + char1Adj: BookCharacterTraits + char2Type: BookCharacters + char2Adj: BookCharacterTraits + event: BookEvents + action: BookActions + actionTrait: BookActionTraits + char3Type: BookCharacters + location: BookLocations + element: BookStoryElements + elementTrait: BookStoryElementTraits