diff --git a/client/Pack.cs b/client/Pack.cs index d839f9c..f2a7ceb 100644 --- a/client/Pack.cs +++ b/client/Pack.cs @@ -10,9 +10,12 @@ public class Pack { public string Name { get; init; } public Guid Id { get; init; } - public string[] Templates { get; init; } - public string[] Conjunctions { get; init; } - public List Words { get; init; } + + [JsonConverter(typeof(TemplateConverter))] + public ITemplate[] Templates { get; init; } + + public string[]? Conjunctions { get; init; } + public List? Words { get; init; } internal static void UpdatePacks() { Task.Run(async () => { @@ -29,6 +32,52 @@ public class Pack { } } +public interface ITemplate { + public string Template { get; } + public string[]? Words { get; } +} + +public class BasicTemplate : ITemplate { + public string Template { get; init; } + public string[]? Words => null; +} + +[Serializable] +public class WordListTemplate : ITemplate { + public string Template { get; init; } + public string[] Words { get; init; } +} + +public class TemplateConverter : JsonConverter +{ + public override ITemplate? ReadJson(JsonReader reader, Type objectType, ITemplate? existingValue, bool hasExistingValue, JsonSerializer serializer) { + if (reader.TokenType == JsonToken.String) { + var template = reader.ReadAsString(); + if (template == null) { + return null; + } + + return new BasicTemplate { + Template = template, + }; + } else if (reader.TokenType == JsonToken.StartObject) { + return serializer.Deserialize(reader); + } else { + throw new JsonReaderException("unexpected template kind"); + } + } + + public override void WriteJson(JsonWriter writer, ITemplate? value, JsonSerializer serializer) { + if (value is BasicTemplate basic) { + serializer.Serialize(writer, basic.Template); + } else if (value is WordListTemplate wordList) { + serializer.Serialize(writer, wordList); + } else { + throw new JsonWriterException("unexpected template kind"); + } + } +} + [Serializable] public class WordList { public string Name { get; init; } diff --git a/client/Ui/MainWindowTabs/Write.cs b/client/Ui/MainWindowTabs/Write.cs index 0a6527a..cc393fc 100644 --- a/client/Ui/MainWindowTabs/Write.cs +++ b/client/Ui/MainWindowTabs/Write.cs @@ -104,6 +104,21 @@ internal class Write : ITab { ImGui.EndCombo(); } + void DrawSpecificWordPicker(string id, WordListTemplate template, ref (int, int) x) { + var preview = x == (-1, -1) ? "" : template.Words[x.Item2]; + if (!ImGui.BeginCombo(id, preview)) { + return; + } + + for (var wordIdx = 0; wordIdx < template.Words.Length; wordIdx++) { + if (ImGui.Selectable(template.Words[wordIdx], x == (-1, wordIdx))) { + x = (-1, wordIdx); + } + } + + ImGui.EndCombo(); + } + void DrawWordPicker(string id, IReadOnlyList words, ref (int, int) x) { var preview = x == (-1, -1) ? "" : words[x.Item1].Words[x.Item2]; if (!ImGui.BeginCombo(id, preview)) { @@ -149,12 +164,13 @@ internal class Write : ITab { var preview = new StringBuilder(); var template1 = pack.Templates[this._part1]; - var word1 = this._word1 == (-1, -1) ? placeholder : pack.Words[this._word1.Item1].Words[this._word1.Item2]; - preview.Append(string.Format(template1, word1)); + var wordList1 = template1.Words ?? pack.Words?[this._word1.Item1].Words; + var word1 = this._word1 == (-1, -1) ? placeholder : wordList1?[this._word1.Item2]; + preview.Append(string.Format(template1.Template, word1)); if (this._conj != -1) { - var conj = pack.Conjunctions[this._conj]; - var isPunc = conj.Length == 1 && char.IsPunctuation(conj[0]); + var conj = pack.Conjunctions?[this._conj]; + var isPunc = conj?.Length == 1 && char.IsPunctuation(conj[0]); if (isPunc) { preview.Append(conj); preview.Append('\n'); @@ -166,8 +182,9 @@ internal class Write : ITab { if (this._part2 != -1) { var template2 = pack.Templates[this._part2]; - var word2 = this._word2 == (-1, -1) ? placeholder : pack.Words[this._word2.Item1].Words[this._word2.Item2]; - preview.Append(string.Format(template2, word2)); + var wordList2 = template2.Words ?? pack.Words?[this._word2.Item1].Words; + var word2 = this._word2 == (-1, -1) ? placeholder : wordList2?[this._word2.Item2]; + preview.Append(string.Format(template2.Template, word2)); } } @@ -182,17 +199,47 @@ internal class Write : ITab { ImGui.Separator(); - DrawPicker("Template##part-1", pack.Templates, ref this._part1); - if (this._part1 > -1 && pack.Templates[this._part1].Contains("{0}")) { - DrawWordPicker("Word##word-1", pack.Words, ref this._word1); + var templateStrings = pack.Templates + .Select(template => template.Template) + .ToArray(); + + DrawPicker("Template##part-1", templateStrings, ref this._part1); + if (this._part1 > -1 && pack.Templates[this._part1].Template.Contains("{0}")) { + switch (pack.Templates[this._part1]) { + case BasicTemplate basic: { + if (pack.Words != null) { + DrawWordPicker("Word##word-1", pack.Words, ref this._word1); + } + + break; + } + case WordListTemplate wordListTemplate: { + DrawSpecificWordPicker("Word##word-1", wordListTemplate, ref this._word1); + break; + } + } } - DrawPicker("Conjunction##conj", pack.Conjunctions, ref this._conj); + if (pack.Conjunctions != null) { + DrawPicker("Conjunction##conj", pack.Conjunctions, ref this._conj); + } if (this._conj != -1) { - DrawPicker("Template##part-2", pack.Templates, ref this._part2); - if (this._part2 > -1 && pack.Templates[this._part2].Contains("{0}")) { - DrawWordPicker("Word##word-2", pack.Words, ref this._word2); + DrawPicker("Template##part-2", templateStrings, ref this._part2); + if (this._part2 > -1 && pack.Templates[this._part2].Template.Contains("{0}")) { + switch (pack.Templates[this._part2]) { + case BasicTemplate basic: { + if (pack.Words != null) { + DrawWordPicker("Word##word-2", pack.Words, ref this._word2); + } + + break; + } + case WordListTemplate wordListTemplate: { + DrawSpecificWordPicker("Word##word-2", wordListTemplate, ref this._word2); + break; + } + } } } @@ -301,7 +348,7 @@ internal class Write : ITab { var pack = Pack.All[this._pack]; - if (this._part1 == -1 || !pack.Templates[this._part1].Contains("{0}")) { + if (this._part1 == -1 || !pack.Templates[this._part1].Template.Contains("{0}")) { this._word1 = (-1, -1); } @@ -309,7 +356,7 @@ internal class Write : ITab { this._part2 = -1; } - if (this._part2 == -1 || !pack.Templates[this._part2].Contains("{0}")) { + if (this._part2 == -1 || !pack.Templates[this._part2].Template.Contains("{0}")) { this._word2 = (-1, -1); } } @@ -321,7 +368,7 @@ internal class Write : ITab { var pack = Pack.All[this._pack]; var template1 = pack.Templates[this._part1]; - var temp1Variable = template1.Contains("{0}"); + var temp1Variable = template1.Template.Contains("{0}"); switch (temp1Variable) { case true when this._word1 == (-1, -1): @@ -339,7 +386,7 @@ internal class Write : ITab { } var template2 = pack.Templates[this._part2]; - var temp2Variable = template2.Contains("{0}"); + var temp2Variable = template2.Template.Contains("{0}"); switch (temp2Variable) { case true when this._word2 == (-1, -1): diff --git a/server/packs/demons-souls.yaml b/server/packs/demons-souls.yaml new file mode 100644 index 0000000..ed23ecb --- /dev/null +++ b/server/packs/demons-souls.yaml @@ -0,0 +1,693 @@ +name: Demon's Souls (PS3) +id: 9ad0d0aa-a1e0-48ff-a1e9-d305b26f4e72 +visible: true +order: 7 + +word-refs: + rewards: &rewards + - "a weapon" + - "armor" + - "a ring" + - "ore" + - "a recovery item" + - "an item" + - "Souls" + - "a valuable weapon" + - "valuable armor" + - "a valuable ring" + - "valuable ore" + - "a valuable recovery item" + - "a valuable item" + - "a strange weapon" + - "strange armor" + - "a strange ring" + - "strange ore" + - "a strange recovery item" + - "a strange item" + - "a Hardstone" + - "a Sharpstone" + - "a Clearstone" + - "a Greystone" + - "a Bladestone" + - "a Dragonstone" + - "a Suckerstone" + - "a Mercurystone" + - "a Marrowstone" + - "a Spiderstone" + - "a Moonlightstone" + - "a Darkmoonstone" + - "a Faintstone" + - "a Cloudstone" + - "a Meltstone" + - "sparkly things" + - "twinkly things" + - "true love" + attacks: &attacks + - "blunt attacks" + - "slash attacks" + - "pierce attacks" + - "one-handed attacks" + - "two-handed attacks" + - "daggers" + - "straight swords" + - "large swords" + - "very large swords" + - "curved swords" + - "katanas" + - "rapiers" + - "axes" + - "large axes" + - "hammers" + - "large hammers" + - "fist weapons" + - "spears" + - "pole weapons" + - "strong weapons" + - "magic weapons" + - "flame weapons" + - "bows" + - "arrows" + - "crossbows" + - "bolts" + - "projectiles" + - "spells" + - "miracles" + - "shields" + - "large shields" + - "fire" + - "bleed attacks" + - "poison" + - "plague" + - "decay" + - "Soul drain" + - "staggering attacks" + - "guard breaks" + - "dodges" + - "shield guards" + - "parries" + - "knockback attacks" + - "clockwise dodges" + - "counterclockwise dodges" + - "pincer attacks" + - "divide and conquer tactics" + - "luring tactics" + - "power pushes" + - "preemptive strikes" + - "standard attacks" + - "attrition" + - "observation" + - "stealthy footsteps" + - "nothing" + - "willpower" + - "luck" + roles: &roles + - "Beginners" + - "Veterans" + - "Confident ones" + - "Cowards" + - "Challengers" + - "Soldiers" + - "Knights" + - "Hunters" + - "Priests" + - "Magicians" + - "Wanderers" + - "Barbarians" + - "Thieves" + - "Temple Knights" + - "Royalty" + terms: &terms + - "Item" + - "Footsteps" + - "At your feet" + - "Head" + - "Safe place" + - "Good guy" + - "Crossbow" + - "At some point..." + - "Someday..." + - "One-way road" + - "Nasty guy" + - "Up" + - "Ascending stairs" + - "Ascending ladder" + - "Movement" + - "Cloudstone" + - "Liar" + - "Singing voice" + - "Luck" + - "While casting..." + - "Sharpstone" + - "Plague" + - "Beware the plague" + - "Ed's Grindstone" + - "MP recovery item" + - "Ranged attack" + - "Large axe" + - "Large shield" + - "Large hammer" + - "Strong attack" + - "Coward" + - "Knockback" + - "Try falling through..." + - "Axe" + - "Lure it out" + - "Yourself" + - "While turning..." + - "Dodge" + - "HP recovery item" + - "While recovering..." + - "Fist weapon" + - "Firebomb" + - "Key" + - "If you get the key..." + - "Hidden passage" + - "Single-handed" + - "Katana" + - "Divide and conquer" + - "Paralysis" + - "Archstone" + - "Shard of Archstone" + - "Widow's Lotus" + - "Hunter" + - "Deceptive cuteness" + - "Poor guy" + - "Cuteness" + - "Observation" + - "Misunderstanding" + - "Willpower" + - "Dangerous foe" + - "Knight" + - "Miracle" + - "Royalty" + - "Royal Lotus" + - "Valuable item" + - "Valuable recovery item" + - "Valuable ore" + - "Valuable treasure" + - "Valuable weapon" + - "Valuable armor" + - "Valuable ring" + - "Carelessness" + - "Formidable foe" + - "Critical attack" + - "Curved sword" + - "Twinkly stuff" + - "Spiderstone" + - "Darkness" + - "Black Phantom" + - "Black Turpentine" + - "Moonstone" + - "Bloodstain" + - "Crystal Lizard" + - "Kick" + - "Primeval Demon" + - "After attacking..." + - "During your attack..." + - "Hardstone" + - "Ore" + - "In your heart" + - "Terrifying foe" + - "Pole weapon" + - "If you go on ahead..." + - "Rapier" + - "Slash attack" + - "Battle of attrition" + - "Confident one" + - "Down" + - "Descending stairs" + - "Descending ladder" + - "Thrust attack" + - "Stealthy footsteps" + - "Perimeter" + - "V.I.P." + - "Bleeding" + - "Bleeding resistance" + - "Front" + - "Item burden" + - "Beginner" + - "Sticky White Stuff" + - "True love" + - "Priest" + - "Temple Knight" + - "Mercurystone" + - "Marrowstone" + - "Suckerstone" + - "Overhead" + - "Stamina" + - "World Tendency" + - "Great view" + - "Below" + - "Narrow corridor" + - "Preemptive strike" + - "Reinforcement" + - "Equipment burden" + - "Soul" + - "Soul drain" + - "If Soul tendency is black..." + - "If Soul tendency is white..." + - "Soul Remains" + - "Side" + - "Sniper's perch" + - "Standard attack" + - "Large sword" + - "Horde of foes" + - "Treasure" + - "Blunt attack" + - "Barrage attack" + - "Shield" + - "Guard" + - "Guard break" + - "Dagger" + - "Power push" + - "Terrain" + - "Challenger" + - "Straight sword" + - "Darkmoonstone" + - "Hammer" + - "Strong weapon" + - "Tough guy" + - "Adversary" + - "Escape" + - "Thief" + - "Poison" + - "Very large sword" + - "Poison swamp" + - "Poison resistance" + - "Anywhere" + - "Closed door" + - "Closed gate" + - "Rush" + - "Projectile" + - "Door" + - "Greystone" + - "Escape route" + - "Remaining HP" + - "Remaining MP" + - "Rear" + - "Back Side" + - "Stone of Ephemeral Eyes" + - "Pincer attack" + - "Chest" + - "Parry" + - "Savage" + - "Sparkly" + - "Light" + - "Faintstone" + - "Left" + - "Counterclockwise" + - "Open area" + - "Sneak attack" + - "Intense pursuit" + - "Weapon" + - "Dead end" + - "Corrosion" + - "Staggering attack" + - "Bolt" + - "Soldier" + - "Soldier's Lotus" + - "Veteran" + - "If you disguise yourself..." + - "Strange item" + - "Strange recovery item" + - "Strange ore" + - "Strange foe" + - "Strange weapon" + - "Strange armor" + - "Strange ring" + - "Armor" + - "Roar" + - "Wanderer" + - "Howl" + - "Other" + - "Fire" + - "Fire defense" + - "Flame weapon" + - "Magician" + - "Ambush" + - "Turpentine" + - "Spell" + - "Magic defense" + - "Magic weapon" + - "Trick road" + - "Invisible foe" + - "Right" + - "Clockwise" + - "Augite of Guidance" + - "Clearstone" + - "Illusion" + - "Ignore" + - "Message" + - "Merchant" + - "Sounds" + - "Gate" + - "Arrow" + - "Bladestone" + - "Spear" + - "Friend" + - "Ring" + - "Bow" + - "Lava" + - "Meltstone" + - "Pay attention" + - "Weak guy" + - "Fall" + - "Rhythm" + - "Dragonstone" + - "Two-handed attack" + - "Lever" + - "If you use the lever..." + - "Combo attack" + - "After combo" + - "During combo" + - "Crossroads" + +templates: + - template: 'Beware of {0} ahead.' + words: + - "the ceiling" + - "the floor" + - "the rear area" + - "a fall" + - "the bloodstain" + - "the message" + - "a World Tendency event" + - "the poison swamp" + - "the lava" + - "the crossroads" + - "a trick road" + - "the sounds" + - "the footsteps" + - "the singing voice" + - "the darkness" + - "the light" + - "a distraction" + - "not using caution" + - "misunderstanding what's" + - "an illusion" + - "the liar" + - "the adversary" + - "the formidable foe" + - "the horde of foes" + - "the dangerous foe" + - "the invisible foe" + - "the bizarre foe" + - "the terrifying foe" + - "the cute foe" + - "the Black Phantom" + - "the bow-users" + - "ranged attacks" + - "the spell-users" + - "the miracle-users" + - "fire" + - "bleeding" + - "poison" + - "plague" + - "paralysis" + - "corrosion" + - "Soul drain" + - "your remaining HP" + - "your remaining MP" + - "your stamina" + - "your equipment burden" + - "your item burden" + + - template: 'Beware of the enemy''s {0}.' + words: + - "ambush" + - "sneak attack" + - "barrage" + - "reinforcements" + - "escape" + - "intense pursuit" + - "movement" + - "rhythm" + - "perimeter" + - "terrain" + - "strong attacks" + - "critical attacks" + - "combo attacks" + - "rush" + - "staggering attacks" + - "guard break" + - "evasion" + - "guard" + - "parries" + - "knockback" + - "kick" + - "roar" + - "howl" + - "bows" + - "ranged attacks" + - "spells" + - "miracles" + - "fire attacks" + - "bloodletting" + - "poison" + - "plague" + - "paralysis" + - "decay" + - "Soul drain" + - "cuteness" + + - template: 'There''s {0} ahead.' + words: + - "treasure" + - "valuable treasure" + - "keys" + - "an Archstone" + - "bloodstains" + - "messages" + - "a lever" + - "a door" + - "a gate" + - "a closed door" + - "a closed gate" + - "an escape route" + - "a dead end" + - "a one-way road" + - "a hidden passage" + - "a fall" + - "ascending stairs" + - "descending stairs" + - "a ladder up" + - "a ladder down" + - "a narrow place" + - "an open place" + - "a safe place" + - "a sniper's perch" + - "a great view" + + - template: 'A {0} lies in wait ahead.' + words: + - "merchant" + - "V.I.P." + - "friend" + - "good guy" + - "poor guy" + - "bad guy" + - "tough guy" + - "weak guy" + - "liar" + - "villain" + - "formidable foe" + - "horde of foes" + - "dangerous foe" + - "hidden foe" + - "strange foe" + - "terrifying foe" + - "cute foe" + - "Black Phantom" + - "Crystal Lizard" + - "Primeval Demon" + + - template: 'You''ll find {0} past here.' + words: *rewards + + - template: 'You''ll get {0} from the next foe.' + words: *rewards + + - template: 'If you {0}, you can proceed.' + words: + - "get the key" + - "use the lever" + - "have white Soul tendency" + - "have black Soul tendency" + - "press on" + - "keep trying" + - "don't give up" + - "disguise yourself" + + - template: 'Use {0} on the next enemy.' + words: *attacks + + - template: 'Don''t bother with {0}.' + words: *attacks + + - template: 'The next enemy''s weakness is {0}.' + words: + - "its head" + - "its feet" + - "its back" + - "its chest" + - "its front" + - "its side" + - "its back" + - "during its attack" + - "after its attack" + - "during its combo" + - "after its combo" + - "during its recovery" + - "when it turns" + - "when it chants" + - "above" + - "below" + - "to the left" + - "to the right" + - "not what you think" + - "anywhere" + - "in your heart" + - "you" + + - template: 'Don''t go forward without {0}.' + words: + - "a dagger" + - "a straight sword" + - "a large sword" + - "a very large sword" + - "a curved sword" + - "a katana" + - "a rapier" + - "an axe" + - "a large axe" + - "a hammer" + - "a large hammer" + - "a fist weapon" + - "a spear" + - "a pole weapon" + - "a strong weapon" + - "a magic weapon" + - "a flame weapon" + - "a bow" + - "arrows" + - "a crossbow" + - "bolts" + - "projectiles" + - "spells" + - "miracles" + - "a shield" + - "a large shield" + - "spell resistance" + - "fire defense" + - "bleed resistance" + - "poison resistance" + - "plague resistance" + - "recovery items" + - "a Soldier's Lotus" + - "a Royal Lotus" + - "a Widow's Lotus" + - "MP recovery items" + - "a grindstone" + - "a Stone of Ephemeral Eyes" + - "a Splinter of Archstone" + - "an Augite of Guidance" + - "Soul remains" + - "Turpentine" + - "Black Turpentine" + - "Sticky White Stuff" + - "Firebombs" + + - template: "You'll need a Soul Level of {0} ahead." + words: + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - '8' + - '9' + - '10' + - '11' + - '12' + - '13' + - '14' + - '15' + - '16' + - '17' + - '18' + - '19' + - '20' + - '21' + - '22' + - '23' + - '24' + - '25' + - '26' + - '27' + - '28' + - '29' + - '30' + - '31' + - '32' + - '33' + - '34' + - '35' + - '36' + - '37' + - '38' + - '39' + - '40' + + - template: '{0} should go here first.' + words: *roles + + - template: '{0} should try this area later.' + words: *roles + + - "Watch out." + - "Listen well." + - "Think hard." + - "Remember..." + - "Don't stop!" + - "Run straight through." + - "Take a step forward." + - "Watch yourself." + - "This is it." + - "The true Demon's Souls starts here." + - "Welcome!" + - "Hi!" + - "Farewell!" + - "Best of luck to you." + - "There are demons nearby." + - "It's safe here." + - "It's not safe here." + - "You can summon here." + - "Requesting a challenger..." + - "I'm in trouble – please recommend this message!" + - "Write more messages!" + - "Beware of false messages." + - "Don't attack!" + - "Attack!" + - "I've got a good item..." + - "If you press onward..." + - "If you jump down from here..." + - "Behind you!" + - "If you read this message..." + - "I'm lost..." + - "This place again...?" + - "Why is it always..." + - "My heart's breaking..." + - "Now I've done it..." + - "I want to go home..." + - "I want to be resurrected..." + - "I've been in Soul form for so long..." + - "If I only had some friends..." + - "I told you so." + - "This is no time to read messages!" + - "Did you think there'd be a hint?" + - "The answer is within you." + + - template: "{0}" + words: *terms diff --git a/server/packs/pack.schema.json b/server/packs/pack.schema.json index 02e9727..20e348d 100644 --- a/server/packs/pack.schema.json +++ b/server/packs/pack.schema.json @@ -28,7 +28,33 @@ "type": "array", "description": "An array of template strings, using {0} for where the chosen word should be inserted (if any).", "items": { - "type": "string" + "anyOf": [ + { + "type": "string", + "description": "A template that should use the global word list defined at the root of this document." + }, + { + "type": "object", + "description": "A template that should use its own word list defined on the template object.", + "properties": { + "template": { + "type": "string", + "description": "The template string, using {0} for where the chosen word should be inserted (if any)." + }, + "words": { + "type": "array", + "description": "A list of words for this template specifically.", + "items": { + "type": "string" + } + } + }, + "required": [ + "template", + "words" + ] + } + ] }, "minItems": 1 }, @@ -72,8 +98,6 @@ "id", "visible", "order", - "templates", - "conjunctions", - "words" + "templates" ] } diff --git a/server/src/pack.rs b/server/src/pack.rs index fff0405..eb5d898 100644 --- a/server/src/pack.rs +++ b/server/src/pack.rs @@ -9,9 +9,36 @@ pub struct Pack { pub visible: bool, #[serde(default, skip_serializing)] pub order: u8, - pub templates: Vec, - pub conjunctions: Vec, - pub words: Vec, + pub templates: Vec