Compare commits
1 Commits
fix/524
...
step35/449
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32ea232b37 |
@@ -7,6 +7,7 @@ Not simulation. Story.
|
||||
import json, time, os, random
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from world.npcs import MarcusNPC
|
||||
|
||||
WORLD_DIR = Path('/Users/apayne/.timmy/evennia/timmy_world')
|
||||
STATE_FILE = WORLD_DIR / 'game_state.json'
|
||||
@@ -174,6 +175,7 @@ class World:
|
||||
"npc": True,
|
||||
},
|
||||
}
|
||||
self.characters["Marcus"] = MarcusNPC.get_character_template()["Marcus"]
|
||||
|
||||
# Global state that creates conflict and stakes
|
||||
self.state = {
|
||||
@@ -495,7 +497,7 @@ class NPCAI:
|
||||
goal = char["active_goal"]
|
||||
|
||||
if char_name == "Marcus":
|
||||
return self._marcus_choice(char, room, available)
|
||||
return MarcusNPC.choose_action(self.world, char, available)
|
||||
elif char_name == "Bezalel":
|
||||
return self._bezalel_choice(char, room, available)
|
||||
elif char_name == "Allegro":
|
||||
@@ -513,17 +515,6 @@ class NPCAI:
|
||||
|
||||
return "rest"
|
||||
|
||||
def _marcus_choice(self, char, room, available):
|
||||
if room == "Garden" and random.random() < 0.7:
|
||||
return "rest"
|
||||
if room != "Garden":
|
||||
return "move:west"
|
||||
# Speak to someone if possible
|
||||
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
|
||||
if others and random.random() < 0.4:
|
||||
return f"speak:{random.choice(others)}"
|
||||
return "rest"
|
||||
|
||||
def _bezalel_choice(self, char, room, available):
|
||||
if room == "Forge" and self.world.rooms["Forge"]["fire"] == "glowing":
|
||||
return random.choice(["forge", "rest"] if char["energy"] > 2 else ["rest"])
|
||||
|
||||
1
timmy-world/world/__init__.py
Normal file
1
timmy-world/world/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""NPC subpackage for the Tower game."""
|
||||
83
timmy-world/world/npcs.py
Normal file
83
timmy-world/world/npcs.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""NPC definitions for the Tower world.
|
||||
|
||||
Provides reusable NPC classes with dialogue trees, movement schedules, and memory.
|
||||
This module centralizes NPC behavior to keep game.py focused on engine logic.
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
|
||||
class MarcusNPC:
|
||||
"""Marcus — an old man from the church who sits in the Garden.
|
||||
|
||||
He walks between the Garden and the Threshold once per day, offers
|
||||
context-aware dialogue, and remembers conversations with wizards.
|
||||
"""
|
||||
|
||||
# 15 dialogue lines — weighted by context, spoken when others are present
|
||||
DIALOGUES = [
|
||||
"You look like you are carrying something heavy, friend.",
|
||||
"Hope is not the belief that things get better. Hope is the decision to act as if they can.",
|
||||
"I have been to the bridge. I know what it looks like down there.",
|
||||
"The soil remembers what hands have touched it.",
|
||||
"You don't need to be fixed. You need to be heard.",
|
||||
"Sit with me. The bench has room.",
|
||||
"There is a church on a night like this one.",
|
||||
"The thing that saves is never the thing you expect.",
|
||||
"I come here because the earth remembers me.",
|
||||
"A man in the dark needs to know someone in the room with him.",
|
||||
"Every step west is a step toward memory.",
|
||||
"The garden grows whether anyone watches or not.",
|
||||
"Some bridges are crossed only once.",
|
||||
"I used to think the Tower held all the answers. Now I know it holds the questions.",
|
||||
"Trust is like soil — it remembers every touch.",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_character_template(cls):
|
||||
"""Return the initial character dictionary for Marcus."""
|
||||
return {
|
||||
"Marcus": {
|
||||
"room": "Garden",
|
||||
"energy": 8, # Old man doesn't tire easily
|
||||
"trust": {"Timmy": 0.7},
|
||||
"goals": ["sit", "speak_truth", "remember"],
|
||||
"active_goal": "sit",
|
||||
"spoken": [],
|
||||
"inventory": [],
|
||||
"memories": [],
|
||||
"is_player": False,
|
||||
"npc": True,
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def choose_action(cls, world, char, available):
|
||||
"""Marcus's daily schedule: occasional Threshold visit and dialogue."""
|
||||
room = char["room"]
|
||||
ticks_per_day = 16 # One day = 16 ticks (1.5h per tick * 16 = 24h)
|
||||
|
||||
if room == "Garden":
|
||||
# Once per day (on tick multiple of 16), head to Threshold
|
||||
if world.tick % ticks_per_day == 0 and random.random() < 0.6:
|
||||
return "move:west"
|
||||
# Otherwise: speak if wizards are present, else rest
|
||||
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
|
||||
if others and random.random() < 0.4:
|
||||
return f"speak:{random.choice(others)}"
|
||||
return "rest"
|
||||
|
||||
elif room == "Threshold":
|
||||
# Stay briefly (1–2 ticks), then return to Garden
|
||||
mod = world.tick % ticks_per_day
|
||||
if mod in (1, 2): # short visit window
|
||||
others = [a.split(":")[1] for a in available if a.startswith("speak:")]
|
||||
if others and random.random() < 0.4:
|
||||
return f"speak:{random.choice(others)}"
|
||||
return "rest"
|
||||
else:
|
||||
return "move:east"
|
||||
|
||||
else:
|
||||
# Should not happen; safest to rest
|
||||
return "rest"
|
||||
Reference in New Issue
Block a user