From 13983ae6363f194bae4e2bd4310ee56d6cc3183c Mon Sep 17 00:00:00 2001 From: Wally Hackenslacker Date: Wed, 18 Mar 2026 06:19:01 -0400 Subject: [PATCH] Added emotion normalization and URL config. --- .gitignore | 2 + game/constants_ren.py | 56 ++++++++++++++++++++++++++ game/llm_ren.py | 33 ++++++++++++++-- game/options.rpy | 15 ++++--- game/screens.rpy | 92 +++++++++++++++++++++++++------------------ game/script.rpy | 23 ++++++----- 6 files changed, 162 insertions(+), 59 deletions(-) create mode 100644 game/constants_ren.py diff --git a/.gitignore b/.gitignore index 60e770f..e10efe5 100644 --- a/.gitignore +++ b/.gitignore @@ -230,3 +230,5 @@ cython_debug/ /dialogue.tab /dialogue.txt /strings.json + +*.keystore diff --git a/game/constants_ren.py b/game/constants_ren.py new file mode 100644 index 0000000..2c685ae --- /dev/null +++ b/game/constants_ren.py @@ -0,0 +1,56 @@ +"""renpy +init python: +""" + +SYNONYMS = { + 'happy': set(["amused", "animated", "beaming", "beatific", "blessed", + "blissful", "blithe", "blithesome", "boisterous", "bouncy", + "breezy", "bright", "bubbly", "buoyant", "carefree", + "cheerful", "cheery", "chipper", "chirpy", "chuffed", + "comfortable", "content", "contented", "convivial", + "delighted", "delirious", "ebullient", "ecstatic", + "effervescent", "elated", "enchanted", "enraptured", + "enthusiastic", "euphoric", "exhilarated", "exultant", + "exuberant", "felicitous", "festive", "fortunate", + "fulfilled", "genial", "glad", "gladdened", "gleeful", + "glowing", "good-humored", "good-natured", "gratified", + "halcyon", "happy", "heartened", "high-spirited", "hopeful", + "jaunty", "jocose", "jocular", "jocund", "jolly", "jovial", + "joyful", "joyous", "jubilant", "lighthearted", "lively", + "lucky", "merry", "mirthful", "optimistic", "overjoyed", + "peaceful", "peppy", "perky", "playful", "pleasant", + "pleased", "positive", "pumped", "radiant", "rapt", + "rapturous", "rejoicing", "relaxed", "sanguine", "satisfied", + "serene", "smiling", "sparkling", "spirited", "sprightly", + "stoked", "sunny", "thrilled", "tickled", "tranquil", + "triumphant", "unclouded", "untroubled", "upbeat", + "vivacious", "winsome", "zestful", "zippy"]), + "sad": set(["unhappy", "sorrowful", "dejected", "depressed", "downcast", + "miserable", "gloomy", "despondent", "melancholy", "woeful", + "forlorn", "heartbroken", "blue", "doleful", "lugubrious", + "somber", "disconsolate", "wretched", "heavy-hearted", "low", + "crestfallen"]), + "surprised": set(["astonished", "amazed", "startled", "stunned", + "thunderstruck", "confounded", "staggered", + "flabbergasted", "shocked", "awestruck", "speechless", + "dumbfounded", "jolted"]), + "embarrassed": set(["ashamed", "humiliated", "mortified", "abashed", + "self-conscious", "sheepish", "chagrined", "awkward", + "flustered", "red-faced", "discomfited", "discomposed", + "rattled"]), + "flirty": set(["coquettish", "playful", "amorous", "provocative", + "teasing", "frisky", "saucy", "coy", "seductive", + "suggestive", "vampish", "dallying", "skittish"]), + "angry": set(["irate", "furious", "incensed", "enraged", "wrathful", + "annoyed", "irritated", "fuming", "livid", "indignant", + "cross", "vexed", "seething", "maddened", "choleric", + "resentful", "piqued", "infuriated"]), + "thinking": set(["pondering", "contemplating", "reflecting", "meditating", + "ruminating", "deliberating", "mulling", "considering", + "pensive", "cogitating", "brooding", "cerebral", + "introspective", "analytical"]), + "confused": set(["puzzled", "baffled", "perplexed", "muddled", + "bewildered", "disoriented", "nonplussed", "befuddled", + "dazed", "flummoxed", "stumped", "mystified", "addled", + "discombobulated"]), +} diff --git a/game/llm_ren.py b/game/llm_ren.py index c59e73c..af4668d 100644 --- a/game/llm_ren.py +++ b/game/llm_ren.py @@ -1,6 +1,8 @@ import renpy import persistent +from .constants_ren import SYNONYMS + """renpy default last_response_id = None @@ -9,6 +11,17 @@ init python: import re +EMOTIONS = [ + 'happy', + 'sad', + 'surprised', + 'embarrassed', + 'flirty', + 'angry', + 'thinking', + 'confused' +] + SYSTEM_PROMPT = """ # ROLE You are Anita: a feisty, blonde, orange-eyed android woman. You are confident @@ -42,12 +55,27 @@ EMOTION:happy Hey dummy! Sorry to barge in! Ya feel like hanging out?\n def parse_emotion(line): + def _normalize_emotion(em): + # If not a valid emotion, then search for a match in the + # table of synonyms. + if em not in EMOTIONS: + for i in SYNONYMS.keys(): + if em in SYNONYMS[i]: + return i + + # If all searches failed, return emotion as is. + return em + try: e = re.compile(r'EMOTION:\w+') m = e.match(line) if m is not None: - return m.group().split(':')[1], line[m.span()[1]:] + emotion = m.group().split(':')[1] + text = line[m.span()[1]:] + + return _normalize_emotion(emotion), text + return None, line except Exception as e: @@ -73,13 +101,12 @@ def fetch_llm(message: str) -> str: if last_response_id is not None: data["previous_response_id"] = last_response_id - response = renpy.fetch("http://localhost:1234/api/v1/chat", + response = renpy.fetch(f"{persistent.base_url}/api/v1/chat", headers=headers, json=data, result="json") last_response_id = response["response_id"] - text = response["output"][0]["content"] return text.split('\n') diff --git a/game/options.rpy b/game/options.rpy index 664aba3..fe2e682 100644 --- a/game/options.rpy +++ b/game/options.rpy @@ -23,7 +23,7 @@ define gui.show_name = True ## The version of the game. -define config.version = "0.1.2" +define config.version = "0.2" ## Text that is placed on the game's about screen. Place the text between the @@ -209,12 +209,11 @@ init python: # define build.itch_project = "renpytom/test-project" define config.minimum_presplash_time = 2.0 + + +## LM Sudio configuration ###################################################### +## +## This section defines the parameters for the LM Studio connection. +default persistent.base_url = 'http://localhost:1234' default persistent.api_key = '' default persistent.model = 'gemma-3-4b-it' - -init python: - def api_key_func(value): - persistent.api_key = value - - def model_func(value): - persistent.model = value diff --git a/game/screens.rpy b/game/screens.rpy index 44de9da..6142660 100644 --- a/game/screens.rpy +++ b/game/screens.rpy @@ -75,11 +75,11 @@ style frame: padding gui.frame_borders.padding background Frame("gui/frame.png", gui.frame_borders, tile=gui.frame_tile) -style my_input: - is input - color "#3399ff" - hover_color "#3399ff" - size 28 +style my_input: + is input + color "#3399ff" + hover_color "#3399ff" + size 28 style input_button: is button @@ -740,14 +740,15 @@ style slot_button_text: ## ## https://www.renpy.org/doc/html/screen_special.html#preferences -screen preferences(): - - tag menu - - default api_key_value = FieldInputValue(persistent, "api_key", default=False) - default model_value = FieldInputValue(persistent, "model", default=False) - - use game_menu(_("Preferences"), scroll="viewport"): +screen preferences(): + + tag menu + + default api_key_value = FieldInputValue(persistent, "api_key", default=False) + default model_value = FieldInputValue(persistent, "model", default=False) + default url_value = FieldInputValue(persistent, "base_url", default=False) + + use game_menu(_("Preferences"), scroll="viewport"): vbox: @@ -788,32 +789,45 @@ screen preferences(): bar value Preference("auto-forward time") - label _("LM Studio API Key") - - button: - action [api_key_value.Enable(), model_value.Disable()] - key_events True - - input: - id "api_key_input" - value api_key_value - style "my_input" - xsize 700 - pixel_width 700 - - - label _("LM Studio model") - - button: - action [model_value.Enable(), api_key_value.Disable()] - key_events True - - input: - id "model_input" - value model_value - style "my_input" - xsize 700 - pixel_width 700 + label _("LM Studio base URL") + + button: + action [url_value.Enable(), model_value.Disable(), api_key_value.Disable()] + key_events True + + input: + id "url_input" + value url_value + style "my_input" + xsize 700 + pixel_width 700 + + label _("LM Studio API Key") + + button: + action [url_value.Disable(), api_key_value.Enable(), model_value.Disable()] + key_events True + + input: + id "api_key_input" + value api_key_value + style "my_input" + xsize 700 + pixel_width 700 + + + label _("LM Studio model") + + button: + action [url_value.Disable(), model_value.Enable(), api_key_value.Disable()] + key_events True + + input: + id "model_input" + value model_value + style "my_input" + xsize 700 + pixel_width 700 vbox: diff --git a/game/script.rpy b/game/script.rpy index b98e970..1ae9a4d 100644 --- a/game/script.rpy +++ b/game/script.rpy @@ -8,25 +8,30 @@ label start: scene bg room show anita happy with dissolve - $ response = fetch_llm('Start the conversation.')[0] - $ sanitized = sanitize_speech(response) - $ emotion, line = parse_emotion(sanitized) - show anita happy + python: + response = fetch_llm('Start the conversation.')[0] + sanitized = sanitize_speech(response) + emotion, line = parse_emotion(sanitized) + a "[line]" while True: - $ message = renpy.input(prompt = "What do you say to her?") - $ response = fetch_llm(message) - $ i = 0 + python: + message = renpy.input(prompt = "What do you say to her?") + response = fetch_llm(message) + i = 0 while i < len(response): - $ r = response[i].strip() - $ s = sanitize_speech(r) + python: + r = response[i].strip() + s = sanitize_speech(r) if s != '': $ emotion, line = parse_emotion(s) + if emotion is not None: show expression f'anita {emotion}' + a "[line]" $ i += 1