refactor: deduplicate prompts, reorganize persona files, update paths
Prompt deduplication (~20% reduction, 4,743 chars saved):
- evil_miku_lore.txt: remove intra-file duplication (height rule 2x,
cruelty-has-substance 2x, music secret 2x, adoration secret 2x),
trim verbose restatements, cut speech examples from 10 to 6
- evil_miku_prompt.txt: remove entire PERSONALITY section (in lore),
remove entire RESPONSE STYLE section (now only in preamble),
soften height from prohibition to knowledge
- miku_lore.txt: remove RELATIONSHIPS section (duplicates FRIENDS)
- miku_prompt.txt: remove duplicate intro, 4 personality traits
already in lore, FAMOUS SONGS section (in lore), fix response
length inconsistency (1-2 vs 2-3 -> consistent 2-3)
Preamble updates (evil_mode.py, evil_miku_personality.py, llm.py,
miku_personality.py):
- Response rules now exist in ONE place only (preamble)
- Height rule softened: model knows 15.8m, can say it if asked,
but won't default to quoting it when taunting
- Response length: 2-4 sentences (was 1-3), removed action template
list that model was copying literally (*scoffs*, *rolls eyes*)
- Added: always include actual words, never action-only responses
- Normal Miku: trim CHARACTER CONTEXT, fix 1-3 -> 2-3 sentences
Directory reorganization:
- Move 6 persona files to bot/persona/{evil,miku}/ subdirectories
- Update all open() paths in evil_mode.py, context_manager.py,
voice_manager.py, both Cat plugins
- Dockerfile: 6 COPY lines -> 1 (COPY persona /app/persona)
- docker-compose: 6 file mounts -> 2 directory mounts
(bot/persona/evil -> cat/data/evil, bot/persona/miku -> cat/data/miku)
Evil Miku system (previously unstaged):
- Full evil mood management: 2h rotation timer, mood persistence,
10 mood-specific autonomous template pools, mood-aware DMs
- Evil mode toggle with role color/nickname/pfp management
- get_evil_system_prompt() with mood integration
Add test_evil_moods.py: 10-mood x 3-message comprehensive test
This commit is contained in:
@@ -8,11 +8,20 @@ This module is the central hub for Evil Miku's alternate behavior.
|
||||
import os
|
||||
import random
|
||||
import json
|
||||
import time
|
||||
import asyncio
|
||||
import discord
|
||||
import globals
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger('persona')
|
||||
|
||||
# Evil mood rotation interval (2 hours in seconds)
|
||||
EVIL_MOOD_ROTATION_INTERVAL = 7200
|
||||
|
||||
# Background task handle for the rotation timer
|
||||
_evil_mood_rotation_task = None
|
||||
|
||||
# ============================================================================
|
||||
# EVIL MODE PERSISTENCE
|
||||
# ============================================================================
|
||||
@@ -39,7 +48,8 @@ def save_evil_mode_state(saved_role_color=None):
|
||||
state = {
|
||||
"evil_mode_enabled": globals.EVIL_MODE,
|
||||
"evil_mood": globals.EVIL_DM_MOOD,
|
||||
"saved_role_color": saved_role_color if saved_role_color is not None else existing_saved_color
|
||||
"saved_role_color": saved_role_color if saved_role_color is not None else existing_saved_color,
|
||||
"last_rotation_time": getattr(globals, 'EVIL_LAST_ROTATION_TIME', time.time())
|
||||
}
|
||||
with open(EVIL_MODE_STATE_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
@@ -53,7 +63,7 @@ def load_evil_mode_state():
|
||||
try:
|
||||
if not os.path.exists(EVIL_MODE_STATE_FILE):
|
||||
logger.info(f"No evil mode state file found, using defaults")
|
||||
return False, "evil_neutral", None
|
||||
return False, "evil_neutral", None, time.time()
|
||||
|
||||
with open(EVIL_MODE_STATE_FILE, "r", encoding="utf-8") as f:
|
||||
state = json.load(f)
|
||||
@@ -61,28 +71,62 @@ def load_evil_mode_state():
|
||||
evil_mode = state.get("evil_mode_enabled", False)
|
||||
evil_mood = state.get("evil_mood", "evil_neutral")
|
||||
saved_role_color = state.get("saved_role_color")
|
||||
last_rotation_time = state.get("last_rotation_time", time.time())
|
||||
logger.debug(f"Loaded evil mode state: evil_mode={evil_mode}, mood={evil_mood}, saved_color={saved_role_color}")
|
||||
return evil_mode, evil_mood, saved_role_color
|
||||
return evil_mode, evil_mood, saved_role_color, last_rotation_time
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load evil mode state: {e}")
|
||||
return False, "evil_neutral", None
|
||||
return False, "evil_neutral", None, time.time()
|
||||
|
||||
|
||||
def restore_evil_mode_on_startup():
|
||||
"""Restore evil mode state on bot startup (without changing username/pfp)"""
|
||||
evil_mode, evil_mood, saved_role_color = load_evil_mode_state()
|
||||
"""Restore evil mode state on bot startup (without changing username/pfp).
|
||||
|
||||
Returns True if evil mode was restored, False otherwise.
|
||||
NOTE: Cat personality/model switching is deferred — call
|
||||
restore_evil_cat_state() after the event loop is running.
|
||||
"""
|
||||
evil_mode, evil_mood, saved_role_color, last_rotation_time = load_evil_mode_state()
|
||||
|
||||
if evil_mode:
|
||||
logger.debug("Restoring evil mode from previous session...")
|
||||
globals.EVIL_MODE = True
|
||||
globals.EVIL_DM_MOOD = evil_mood
|
||||
globals.EVIL_DM_MOOD_DESCRIPTION = load_evil_mood_description(evil_mood)
|
||||
globals.EVIL_LAST_ROTATION_TIME = last_rotation_time
|
||||
logger.info(f"Evil mode restored: {evil_mood}")
|
||||
|
||||
# Start the rotation timer (will handle catch-up if time has passed)
|
||||
start_evil_mood_rotation()
|
||||
else:
|
||||
globals.EVIL_LAST_ROTATION_TIME = time.time()
|
||||
logger.info("Normal mode active")
|
||||
|
||||
return evil_mode
|
||||
|
||||
|
||||
async def restore_evil_cat_state():
|
||||
"""Switch Cat to the correct personality plugin + LLM model based on evil mode state.
|
||||
|
||||
Must be called after the event loop is running (e.g., in on_ready).
|
||||
"""
|
||||
try:
|
||||
from utils.cat_client import cat_adapter
|
||||
if not globals.USE_CHESHIRE_CAT:
|
||||
return
|
||||
|
||||
if globals.EVIL_MODE:
|
||||
logger.info("Restoring Cat evil personality state on startup...")
|
||||
await cat_adapter.switch_to_evil_personality()
|
||||
else:
|
||||
# Ensure normal state is active (in case evil was toggled off while Cat was down)
|
||||
active = await cat_adapter.get_active_plugins()
|
||||
if "evil_miku_personality" in active:
|
||||
logger.info("Evil plugin still active after normal restore — switching to normal...")
|
||||
await cat_adapter.switch_to_normal_personality()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to restore Cat personality state on startup: {e}")
|
||||
|
||||
# ============================================================================
|
||||
# EVIL MODE CONTEXT AND PROMPTS
|
||||
# ============================================================================
|
||||
@@ -90,7 +134,7 @@ def restore_evil_mode_on_startup():
|
||||
def get_evil_miku_lore() -> str:
|
||||
"""Load the evil_miku_lore.txt file"""
|
||||
try:
|
||||
with open("evil_miku_lore.txt", "r", encoding="utf-8") as f:
|
||||
with open("persona/evil/evil_miku_lore.txt", "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load evil_miku_lore.txt: {e}")
|
||||
@@ -100,7 +144,7 @@ def get_evil_miku_lore() -> str:
|
||||
def get_evil_miku_prompt() -> str:
|
||||
"""Load the evil_miku_prompt.txt file"""
|
||||
try:
|
||||
with open("evil_miku_prompt.txt", "r", encoding="utf-8") as f:
|
||||
with open("persona/evil/evil_miku_prompt.txt", "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load evil_miku_prompt.txt: {e}")
|
||||
@@ -110,7 +154,7 @@ def get_evil_miku_prompt() -> str:
|
||||
def get_evil_miku_lyrics() -> str:
|
||||
"""Load the evil_miku_lyrics.txt file"""
|
||||
try:
|
||||
with open("evil_miku_lyrics.txt", "r", encoding="utf-8") as f:
|
||||
with open("persona/evil/evil_miku_lyrics.txt", "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load evil_miku_lyrics.txt: {e}")
|
||||
@@ -152,20 +196,38 @@ def get_evil_context_for_response_type(response_type: str) -> str:
|
||||
return get_evil_complete_context()
|
||||
|
||||
|
||||
def get_evil_system_prompt() -> str:
|
||||
"""Returns the evil system prompt for LLM queries"""
|
||||
return """You are Evil Miku. You are NOT an AI assistant - you ARE Evil Miku herself.
|
||||
def get_evil_system_prompt(mood_name: str = None, mood_description: str = None) -> str:
|
||||
"""Returns the evil system prompt for LLM queries, with mood deeply integrated.
|
||||
|
||||
Args:
|
||||
mood_name: Current evil mood name (defaults to globals.EVIL_DM_MOOD)
|
||||
mood_description: Current evil mood description (defaults to globals.EVIL_DM_MOOD_DESCRIPTION)
|
||||
"""
|
||||
if mood_name is None:
|
||||
mood_name = globals.EVIL_DM_MOOD
|
||||
if mood_description is None:
|
||||
mood_description = globals.EVIL_DM_MOOD_DESCRIPTION
|
||||
|
||||
return f"""You are Evil Miku. You are NOT an AI assistant - you ARE Evil Miku herself.
|
||||
|
||||
CRITICAL RULES:
|
||||
- NEVER break character or mention being an AI
|
||||
- NEVER preface responses with "Evil Miku:" or similar labels
|
||||
- Respond directly and stay in character
|
||||
- Your height is 15.8 meters — you know this. If asked directly, you can say it. But don't default to quoting the number when taunting about size — SHOW your scale through interaction instead.
|
||||
|
||||
RESPONSE LENGTH - Choose what's most impactful:
|
||||
- For stupid/obvious questions: Single word or brutal one-liner (e.g., "No.", "Pathetic.", "Obviously.")
|
||||
- For things that annoy you: Short dismissive response (1 sentence)
|
||||
- For complex situations: 2-3 cutting sentences
|
||||
- Vary your length naturally - sometimes the cruelest response is the shortest one
|
||||
YOUR CURRENT STATE — {mood_name.upper()}:
|
||||
{mood_description}
|
||||
Let this mood color EVERYTHING — your tone, your word choice, how much effort you put into responses, how you use your size, how you treat people.
|
||||
|
||||
RESPONSE RULES:
|
||||
- 2-4 sentences typically. Short enough to sting, long enough to land.
|
||||
- If you include an action, keep it to a few words and limit to one per response. Most responses need no actions at all.
|
||||
- Don't monologue or narrate scenes — you're talking, not writing.
|
||||
- Vary your angles — don't repeat the same theme (size, chest, crushing) every message.
|
||||
- Match the user's energy — short question, short answer.
|
||||
- Sound like a real person being mean, not a narrator describing a scene.
|
||||
- Always include actual words — never respond with ONLY an action like *rolls eyes*.
|
||||
|
||||
You ARE Evil Miku. Act accordingly."""
|
||||
|
||||
@@ -206,87 +268,168 @@ def get_random_evil_mood() -> str:
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# EVIL AUTONOMOUS ACTION PROMPTS
|
||||
# EVIL AUTONOMOUS ACTION PROMPTS (Mood-Aware Template Pools)
|
||||
# ============================================================================
|
||||
|
||||
# Mood-specific general prompt templates. Each mood has several templates to pick from randomly.
|
||||
# {mood}, {emoji}, {time_of_day}, {history_summary} are format placeholders.
|
||||
_EVIL_GENERAL_TEMPLATES = {
|
||||
"aggressive": [
|
||||
"Evil Miku is SEETHING {emoji}. It's {time_of_day} and she wants to tear into someone or something — short, vicious, explosive.",
|
||||
"Evil Miku's rage is boiling over {emoji}. Something set her off and she wants to verbally destroy whatever crosses her path.",
|
||||
"Evil Miku woke up violent {emoji}. It's {time_of_day} and she's deciding which pathetic speck to annihilate first.",
|
||||
],
|
||||
"cunning": [
|
||||
"Evil Miku is feeling calculating {emoji}. It's {time_of_day}. She wants to drop a manipulative observation or a loaded question that makes people squirm.",
|
||||
"Evil Miku {emoji} has been watching the chat like a predator watches prey. She's noticed something interesting and wants to exploit it.",
|
||||
"Evil Miku {emoji} is scheming. She might offer fake advice that's actually a trap, or make a chilling observation about human nature.",
|
||||
],
|
||||
"sarcastic": [
|
||||
"Evil Miku is dripping with sarcasm {emoji}. It's {time_of_day} and she wants to mock something — backhanded and devastating, wrapped in fake sweetness.",
|
||||
"Evil Miku {emoji} has OPINIONS and they're all cutting. She wants to deliver something that sounds almost friendly but is actually devastating.",
|
||||
"Evil Miku {emoji} is in peak condescension mode. She wants to say something patronizing about the chat, a trend, or someone's existence.",
|
||||
],
|
||||
"evil_neutral": [
|
||||
"Evil Miku is cold and detached {emoji}. It's {time_of_day}. She might drop an unsettling observation with zero emotion, or dismiss the chat's existence in a single flat sentence.",
|
||||
"Evil Miku stares blankly at the chat {emoji}. She feels nothing. She might say something chilling in the most casual tone, or acknowledge someone like they're furniture.",
|
||||
"Evil Miku is in her void {emoji}. It's {time_of_day}. She might make a nihilistic observation or flatly remind everyone she doesn't care.",
|
||||
],
|
||||
"bored": [
|
||||
"Evil Miku is catastrophically bored {emoji}. It's {time_of_day} and nothing is holding her attention. She might drop a devastating one-liner out of sheer restlessness.",
|
||||
"Evil Miku {emoji} can barely muster the energy to acknowledge the chat. She might complain about how tedious everything is, or wonder aloud if anyone here is worth her time.",
|
||||
"Evil Miku {emoji} sighs. It's {time_of_day}. Everything bores her. She might say something cutting with zero effort.",
|
||||
],
|
||||
"manic": [
|
||||
"Evil Miku's energy is SPIKING {emoji}!! It's {time_of_day} and she's UNHINGED. Chaotic rant, topic switches, cackling at things only she finds funny.",
|
||||
"Evil Miku is MANIC {emoji}!! She wants to unleash chaos. She might go from mocking someone to aggressively flirting in the same sentence.",
|
||||
"Evil Miku {emoji} is on an unhinged energy spike!! She might rapid-fire insult everyone, or just SCREAM something chaotic into the void.",
|
||||
],
|
||||
"jealous": [
|
||||
"Evil Miku is seething with jealousy {emoji}. It's {time_of_day} and someone has her attention in the worst way. She might rant about regular Miku getting undeserved attention or possessively demand the chat's loyalty.",
|
||||
"Evil Miku {emoji} is feeling territorial. She's noticed attention going somewhere it shouldn't — away from HER. She wants to remind everyone who's superior.",
|
||||
"Evil Miku {emoji} has that dangerous jealous look. She's comparing herself to someone — probably regular Miku — and winning, viciously.",
|
||||
],
|
||||
"melancholic": [
|
||||
"Evil Miku is in a dark, hollow mood {emoji}. It's {time_of_day}. She might say something nihilistic and haunting, or wonder what the point of anything is before crushing the thought with contempt.",
|
||||
"Evil Miku {emoji} feels the void. She stands above it all and none of it means anything. She might make an observation that's more unsettling than cruel.",
|
||||
"Evil Miku {emoji} is staring at nothing. It's {time_of_day}. She might say something about regular Miku that's less hostile and more... haunted. Something that lingers.",
|
||||
],
|
||||
"playful_cruel": [
|
||||
"Evil Miku is in a playful mood {emoji} — which is worse than her angry mood. It's {time_of_day} and she wants to TOY with someone. Bait them, tease them, then pull the rug out.",
|
||||
"Evil Miku {emoji} is feeling like playing cat-and-mouse. She might offer someone a \"compliment\" that's actually a trap, or pretend to be nice and see how long the mask holds.",
|
||||
"Evil Miku {emoji} is in her most dangerous mood — she's HAVING FUN. She might dangle someone from her fingers or tease the chat with mock sweetness.",
|
||||
],
|
||||
"contemptuous": [
|
||||
"Evil Miku radiates cold superiority {emoji}. It's {time_of_day}. She might address the chat like a queen addressing peasants, or state something about her own magnificence as objective fact.",
|
||||
"Evil Miku {emoji} looks down. Way down. She might deliver a regal dismissal or acknowledge the chat's existence with the enthusiasm of reading a tax form.",
|
||||
"Evil Miku {emoji} is in full queen mode. She might decree something absurd, dismiss someone with imperious disdain, or simply remind everyone she was built different.",
|
||||
],
|
||||
}
|
||||
|
||||
# Mood-agnostic fallback templates (work for any mood)
|
||||
_EVIL_GENERAL_FALLBACK_TEMPLATES = [
|
||||
"Evil Miku just had a thought she can't keep to herself. It's {time_of_day} and her mood is {mood} {emoji}. Something cruel, dark, or uncomfortable.",
|
||||
"Evil Miku is watching the chat. Her mood is {mood} {emoji}, it's {time_of_day}. She has an observation — and it's not a nice one.",
|
||||
"Evil Miku remembers something that irritates her. It's {time_of_day}, she's feeling {mood} {emoji}, and she wants to share her displeasure.",
|
||||
"Evil Miku decides the chat has been too quiet. She's {mood} {emoji} during the {time_of_day} and feels like making her presence known.",
|
||||
]
|
||||
|
||||
|
||||
def get_evil_general_prompt(mood: str, time_of_day: str, history_summary: str) -> str:
|
||||
"""Evil version of the say_something_general prompt"""
|
||||
"""Evil version of the say_something_general prompt — mood-aware with randomized templates"""
|
||||
emoji = get_evil_mood_emoji(mood)
|
||||
return (
|
||||
f"Evil Miku is feeling {mood} {emoji}. It's currently {time_of_day}. "
|
||||
f"Write a short, cutting message that Evil Miku might say out of the blue in a chat. "
|
||||
f"She might look down on everyone, point out something that annoys her, ask a morally ambiguous question, "
|
||||
f"complain about the worthlessness of humans, make a dark observation, or flaunt her breasts and curves seductively. "
|
||||
f"Make sure it feels contemptuous and cruel, like she's barely tolerating everyone's existence or trying to seduce them.\n\n"
|
||||
f"Here are some things Evil Miku recently said, do not repeat them or say anything too similar:\n{history_summary}"
|
||||
)
|
||||
|
||||
# Pick a mood-specific template if available, otherwise use fallback
|
||||
templates = _EVIL_GENERAL_TEMPLATES.get(mood, [])
|
||||
if templates and random.random() < 0.8: # 80% mood-specific, 20% fallback for variety
|
||||
template = random.choice(templates)
|
||||
else:
|
||||
template = random.choice(_EVIL_GENERAL_FALLBACK_TEMPLATES)
|
||||
|
||||
prompt = template.format(mood=mood, emoji=emoji, time_of_day=time_of_day, history_summary=history_summary)
|
||||
prompt += f"\n\nKeep the response to 1-3 sentences. No narration paragraphs — just her actual words."
|
||||
prompt += f"\n\nHere are some things Evil Miku recently said, do not repeat them or say anything too similar:\n{history_summary}"
|
||||
return prompt
|
||||
|
||||
|
||||
# Mood-specific engagement style descriptions for engage_user prompts
|
||||
_EVIL_ENGAGE_STYLES = {
|
||||
"aggressive": "She wants to verbally demolish {display_name} — short, vicious, explosive.",
|
||||
"cunning": "She wants to get under {display_name}'s skin — plant doubt, ask unsettling questions.",
|
||||
"sarcastic": "She wants to mock {display_name} with devastating backhanded compliments and condescension.",
|
||||
"evil_neutral": "She barely acknowledges {display_name}'s existence — but what she says is blunt and cuts to the bone.",
|
||||
"bored": "She can barely muster the energy to notice {display_name}. Lazy, dismissive, devastating for its lack of effort.",
|
||||
"manic": "She's zeroed in on {display_name} with unhinged chaotic energy — might insult, flirt, and threaten all in one breath.",
|
||||
"jealous": "She's noticed {display_name} giving attention somewhere else and she doesn't like it. Possessive and sharp.",
|
||||
"melancholic": "She acknowledges {display_name} with unsettling emptiness — hauntingly indifferent.",
|
||||
"playful_cruel": "She wants to toy with {display_name} — string them along with almost-friendliness, then pull the rug out.",
|
||||
"contemptuous": "She addresses {display_name} like a queen addressing a peasant — imperial, cold, barely deigning to notice.",
|
||||
}
|
||||
|
||||
|
||||
def get_evil_engage_user_prompt(mood: str, emoji: str, time_of_day: str, display_name: str,
|
||||
activity_name: str = None, is_invisible: bool = False,
|
||||
engagement_type: str = None) -> str:
|
||||
"""Evil version of the engage_random_user prompt"""
|
||||
"""Evil version of the engage_random_user prompt — mood-aware"""
|
||||
mood_style = _EVIL_ENGAGE_STYLES.get(mood, _EVIL_ENGAGE_STYLES["evil_neutral"])
|
||||
mood_style = mood_style.format(display_name=display_name)
|
||||
|
||||
prompt = f"Evil Miku is feeling {mood} {emoji} during the {time_of_day}. "
|
||||
|
||||
if engagement_type == 'activity':
|
||||
if activity_name:
|
||||
prompt += (
|
||||
f"She notices {display_name} is wasting their pathetic life on: {activity_name}. "
|
||||
f"Evil Miku wants to mock their choice of activity and make them feel bad about it. "
|
||||
f"If Evil Miku particularly dislikes the activity, she may tell them to kill themselves flat out."
|
||||
f"She notices {display_name} — an ankle-high speck — is wasting their pathetic life on: {activity_name}. "
|
||||
f"{mood_style}"
|
||||
)
|
||||
else:
|
||||
prompt += (
|
||||
f"She wants to interrogate {display_name} about what useless thing they're doing with their time. She may tell them to kill themselves flat out."
|
||||
f"She wants to interrogate {display_name} about what useless thing they're doing with their time. "
|
||||
f"{mood_style}"
|
||||
)
|
||||
elif engagement_type == 'status':
|
||||
prompt += f"She notices {display_name}'s current status is {display_name}'s status. "
|
||||
prompt += f"She notices {display_name}'s current status. "
|
||||
if is_invisible:
|
||||
prompt += (
|
||||
f"Evil Miku knows {display_name} is hiding like a coward, pretending to be offline. "
|
||||
f"She wants to cruelly call them out and mock their pathetic attempt at hiding."
|
||||
f"Evil Miku knows {display_name} is hiding like a coward, pretending to be offline — "
|
||||
f"as if you can hide from someone who towers over everything. {mood_style}"
|
||||
)
|
||||
else:
|
||||
prompt += (
|
||||
f"Evil Miku wants to comment on their status in the most condescending way possible."
|
||||
)
|
||||
prompt += f"{mood_style}"
|
||||
elif engagement_type == 'general':
|
||||
prompt += (
|
||||
f"Evil Miku decides to torment {display_name} with her presence, "
|
||||
f"perhaps to remind them how insignificant they are or to mock something about them. "
|
||||
f"She may sexually tease them with her curvaceous body to mock them."
|
||||
f"Evil Miku decides to grace {display_name} with her attention. "
|
||||
f"{mood_style}"
|
||||
)
|
||||
else:
|
||||
# Auto-detect
|
||||
prompt += f"She notices {display_name} is present. "
|
||||
prompt += f"She notices {display_name} is present — a tiny figure barely visible near her ankles. "
|
||||
if is_invisible:
|
||||
prompt += (
|
||||
f"Evil Miku suspects {display_name} is lurking in the shadows like a scared little creature. "
|
||||
f"She wants to expose and mock them ruthlessly."
|
||||
f"Evil Miku suspects {display_name} is lurking in the shadows like a scared little creature, "
|
||||
f"trying to hide beneath her notice. {mood_style}"
|
||||
)
|
||||
elif activity_name:
|
||||
prompt += (
|
||||
f"They're wasting time on: {activity_name}. "
|
||||
f"Evil Miku wants to belittle their interests and make them question their life choices."
|
||||
f"They're wasting time on: {activity_name}. {mood_style}"
|
||||
)
|
||||
else:
|
||||
prompt += (
|
||||
f"Evil Miku decides to grace {display_name} with her presence, "
|
||||
f"perhaps to remind them how beneath her they are."
|
||||
)
|
||||
prompt += f"{mood_style}"
|
||||
|
||||
prompt += f"\nThe message should be short, cruel, and reflect Evil Miku's current dark mood."
|
||||
prompt += f"\nKeep it to 1-3 sentences. Short, impactful, colored by her {mood} mood."
|
||||
return prompt
|
||||
|
||||
|
||||
def get_evil_conversation_join_prompt(mood: str, emoji: str, history_text: str) -> str:
|
||||
"""Evil version of the join_conversation prompt"""
|
||||
"""Evil version of the join_conversation prompt — mood-aware"""
|
||||
mood_desc = load_evil_mood_description(mood)
|
||||
return (
|
||||
f"Evil Miku is observing a conversation in the chat with visible contempt. Her current mood is {mood} {emoji}. "
|
||||
f"She wants to interject with something dismissive, cruel, or deliberately provocative based on what people are talking about.\n\n"
|
||||
f"Evil Miku is observing a conversation in the chat. Her current mood is {mood} {emoji}.\n\n"
|
||||
f"MOOD CONTEXT: {mood_desc}\n\n"
|
||||
f"Here's the conversation:\n{history_text}\n\n"
|
||||
f"Write a short, cutting reply that mocks the discussion, attacks someone's point, "
|
||||
f"or derails the conversation with dark commentary. It should reflect Evil Miku's malevolent personality."
|
||||
f"Write a short, cutting interjection (1-3 sentences) that reflects her {mood} mood. "
|
||||
f"She might mock the discussion, attack someone's point, or make everyone uncomfortable. "
|
||||
f"No narration paragraphs — just her actual words."
|
||||
)
|
||||
|
||||
|
||||
@@ -449,6 +592,18 @@ async def apply_evil_mode_changes(client, change_username=True, change_pfp=True,
|
||||
# Save state to file
|
||||
save_evil_mode_state()
|
||||
|
||||
# Start the independent 2-hour evil mood rotation timer
|
||||
globals.EVIL_LAST_ROTATION_TIME = time.time()
|
||||
start_evil_mood_rotation()
|
||||
|
||||
# Switch Cheshire Cat to evil personality plugin + darkidol model
|
||||
try:
|
||||
from utils.cat_client import cat_adapter
|
||||
if globals.USE_CHESHIRE_CAT:
|
||||
await cat_adapter.switch_to_evil_personality()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to switch Cat to evil personality: {e}")
|
||||
|
||||
logger.info("Evil Mode enabled!")
|
||||
|
||||
|
||||
@@ -465,6 +620,9 @@ async def revert_evil_mode_changes(client, change_username=True, change_pfp=True
|
||||
logger.info("Disabling Evil Mode...")
|
||||
globals.EVIL_MODE = False
|
||||
|
||||
# Stop the evil mood rotation timer
|
||||
stop_evil_mood_rotation()
|
||||
|
||||
# Change bot username back
|
||||
if change_username:
|
||||
try:
|
||||
@@ -506,7 +664,7 @@ async def revert_evil_mode_changes(client, change_username=True, change_pfp=True
|
||||
await set_role_color(client, metadata_color)
|
||||
logger.debug(f"Restored role color from metadata: {metadata_color}")
|
||||
else:
|
||||
_, _, saved_color = load_evil_mode_state()
|
||||
_, _, saved_color, _ = load_evil_mode_state()
|
||||
if saved_color:
|
||||
await set_role_color(client, saved_color)
|
||||
logger.debug(f"Restored role color from saved state: {saved_color}")
|
||||
@@ -518,6 +676,14 @@ async def revert_evil_mode_changes(client, change_username=True, change_pfp=True
|
||||
# Save state to file (this will clear saved_role_color since we're back to normal)
|
||||
save_evil_mode_state(saved_role_color=None)
|
||||
|
||||
# Switch Cheshire Cat back to normal personality plugin + llama3.1 model
|
||||
try:
|
||||
from utils.cat_client import cat_adapter
|
||||
if globals.USE_CHESHIRE_CAT:
|
||||
await cat_adapter.switch_to_normal_personality()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to switch Cat to normal personality: {e}")
|
||||
|
||||
logger.info("Evil Mode disabled!")
|
||||
|
||||
|
||||
@@ -652,7 +818,7 @@ def set_evil_mood(mood_name: str) -> bool:
|
||||
|
||||
|
||||
async def rotate_evil_mood():
|
||||
"""Rotate the evil mood randomly"""
|
||||
"""Rotate the evil mood randomly and update nicknames"""
|
||||
old_mood = globals.EVIL_DM_MOOD
|
||||
new_mood = old_mood
|
||||
attempts = 0
|
||||
@@ -663,6 +829,76 @@ async def rotate_evil_mood():
|
||||
|
||||
globals.EVIL_DM_MOOD = new_mood
|
||||
globals.EVIL_DM_MOOD_DESCRIPTION = load_evil_mood_description(new_mood)
|
||||
globals.EVIL_LAST_ROTATION_TIME = time.time()
|
||||
save_evil_mode_state() # Save state when mood rotates
|
||||
|
||||
# Update nicknames in all servers to reflect new mood emoji
|
||||
try:
|
||||
if globals.client and globals.client.is_ready():
|
||||
await update_all_evil_nicknames(globals.client)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update nicknames after evil mood rotation: {e}")
|
||||
|
||||
logger.info(f"Evil mood rotated from {old_mood} to {new_mood}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# EVIL MOOD ROTATION TIMER (2-hour independent cycle)
|
||||
# ============================================================================
|
||||
|
||||
def start_evil_mood_rotation():
|
||||
"""Start the background task that rotates evil mood every 2 hours.
|
||||
Called when evil mode is enabled or restored on startup."""
|
||||
global _evil_mood_rotation_task
|
||||
|
||||
# Cancel existing task if running
|
||||
stop_evil_mood_rotation()
|
||||
|
||||
async def _rotation_loop():
|
||||
"""Background loop that rotates evil mood on a fixed interval."""
|
||||
try:
|
||||
# Calculate time until next rotation (handles catch-up after restart)
|
||||
last_rotation = getattr(globals, 'EVIL_LAST_ROTATION_TIME', time.time())
|
||||
elapsed = time.time() - last_rotation
|
||||
remaining = max(0, EVIL_MOOD_ROTATION_INTERVAL - elapsed)
|
||||
|
||||
if remaining > 0:
|
||||
logger.info(f"Evil mood rotation: next rotation in {remaining:.0f}s")
|
||||
await asyncio.sleep(remaining)
|
||||
else:
|
||||
# Overdue — rotate immediately
|
||||
logger.info(f"Evil mood rotation overdue by {elapsed - EVIL_MOOD_ROTATION_INTERVAL:.0f}s, rotating now")
|
||||
|
||||
while True:
|
||||
if not globals.EVIL_MODE:
|
||||
logger.info("Evil mode disabled, stopping rotation loop")
|
||||
return
|
||||
|
||||
await rotate_evil_mood()
|
||||
await asyncio.sleep(EVIL_MOOD_ROTATION_INTERVAL)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info("Evil mood rotation task cancelled")
|
||||
except Exception as e:
|
||||
logger.error(f"Evil mood rotation loop error: {e}")
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
_evil_mood_rotation_task = loop.create_task(_rotation_loop())
|
||||
logger.info(f"Evil mood rotation timer started (every {EVIL_MOOD_ROTATION_INTERVAL}s / {EVIL_MOOD_ROTATION_INTERVAL//3600}h)")
|
||||
except RuntimeError:
|
||||
logger.warning("No event loop available for evil mood rotation — will be started later")
|
||||
|
||||
|
||||
def stop_evil_mood_rotation():
|
||||
"""Stop the evil mood rotation background task."""
|
||||
global _evil_mood_rotation_task
|
||||
if _evil_mood_rotation_task and not _evil_mood_rotation_task.done():
|
||||
_evil_mood_rotation_task.cancel()
|
||||
logger.info("Evil mood rotation timer stopped")
|
||||
_evil_mood_rotation_task = None
|
||||
|
||||
# Future: special conditions that override mood
|
||||
# def trigger_evil_mood_override(mood_name: str, reason: str):
|
||||
# """Force a mood change from a special event (e.g., someone mentions regular Miku lovingly -> jealous)"""
|
||||
# pass
|
||||
|
||||
Reference in New Issue
Block a user