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