diff --git a/deploy/web/server/run_server.py b/deploy/web/server/run_server.py index 88fd2a085..9c4f6fdce 100644 --- a/deploy/web/server/run_server.py +++ b/deploy/web/server/run_server.py @@ -170,7 +170,7 @@ def str2bool(v): parser.add_argument( "--cookie-secret", type=str, - default="0123456789", + default="temp8000800080008000", help="Cookie secret for issueing cookies (SECRET!!!)", ) parser.add_argument( diff --git a/deploy/web/server/tornado_server.py b/deploy/web/server/tornado_server.py index 03bf4405c..847d42e27 100644 --- a/deploy/web/server/tornado_server.py +++ b/deploy/web/server/tornado_server.py @@ -36,7 +36,11 @@ import tornado.auth # noqa E402: gotta install ioloop first import tornado.websocket # noqa E402: gotta install ioloop first import tornado.escape # noqa E402: gotta install ioloop first -from light.graph.events.graph_events import SoulSpawnEvent +from light.graph.events.graph_events import ( + SoulSpawnEvent, + SystemMessageEvent, + DeathEvent, +) from typing import Dict, Optional, TYPE_CHECKING @@ -626,6 +630,11 @@ def player_observe_event(self, soul: "PlayerSoul", event: "GraphEvent"): self.socket.safe_write_message( json.dumps({"command": "actions", "data": [dat]}) ) + if ( + isinstance(event, DeathEvent) + and event.actor.node_id == soul.target_node.node_id + ): + self.purgatory.clear_soul(soul.target_node) def act(self, action_data, event_id: Optional[str] = None): if self.player_soul is not None and self.player_soul.is_reaped: @@ -653,6 +662,20 @@ def is_alive(self): return self.socket.alive def on_reap_soul(self, soul): + action = SystemMessageEvent( + soul.target_node, + [], + text_content=( + "Your soul slips off into the ether, unbound by your previous character. " + '"Oh no... this won\'t do", says the Dungeon Master, before peering over ' + 'into the world to see what has happened. "I can try to find a new place ' + "for your soul, if you'd like?\" Send anything to respawn." + ), + ) + dat = action.to_frontend_form(soul.target_node) + self.socket.safe_write_message( + json.dumps({"command": "actions", "data": [dat]}) + ) if self.user is not None: if self.db is not None: with self.db as ldb: diff --git a/light/graph/events/base.py b/light/graph/events/base.py index 0845a4a25..041ddebf2 100644 --- a/light/graph/events/base.py +++ b/light/graph/events/base.py @@ -213,8 +213,13 @@ def from_json(input_json: str, world: "World") -> "GraphEvent": event = class_(*arglist) for k, v in attribute_dict.items(): event.__dict__[k] = v + event.post_json_load(world) return event + def post_json_load(self, world: "World") -> None: + """Rectify any state following a load from json.""" + pass + def to_json(self, viewer: GraphAgent = None, indent: int = None) -> str: """ Convert the content of this action into a json format that can be diff --git a/light/graph/events/graph_events.py b/light/graph/events/graph_events.py index 3999165e2..592b6a886 100644 --- a/light/graph/events/graph_events.py +++ b/light/graph/events/graph_events.py @@ -1134,8 +1134,7 @@ def execute(self, world: "World") -> List[GraphEvent]: # Trigger the actual death world.oo_graph.agent_die(self.actor) - # TODO any other world processing of a death event, perhaps to - # update the player + # world.purgatory.clear_soul(self.actor) todo - clear soul only after message queue consumed return [] @proper_caps_wrapper @@ -3150,10 +3149,12 @@ def _get_target_description(self, world: "World") -> str: inv_text = world.view.get_inventory_text_for(object_id, give_empty=False) if inv_text != "": base_desc += f"\n{self.__target_name} is {inv_text} " - if hasattr( - self.target_nodes[0], "_last_action_time" - ) and actor_has_no_recent_action( - self.target_nodes[0]._last_action_time, time.time() + if ( + self.target_nodes[0].is_player + and hasattr(self.target_nodes[0], "_last_action_time") + and actor_has_no_recent_action( + self.target_nodes[0]._last_action_time, time.time() + ) ): base_desc += "\nThey appear to be dozing off right now." elif isinstance(target, GraphObject) and target.container: diff --git a/light/graph/events/use_events.py b/light/graph/events/use_events.py index ee5dba6fb..63d0e33e2 100644 --- a/light/graph/events/use_events.py +++ b/light/graph/events/use_events.py @@ -26,7 +26,10 @@ AttributeCompareValueConstraint, ) from light.graph.elements.graph_nodes import GraphAgent, GraphNode, GraphObject -from typing import Union, List, Optional +from typing import Union, List, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from light.world.world import World class UseEvent(GraphEvent): @@ -326,3 +329,9 @@ def get_valid_actions(cls, graph: "OOGraph", actor: GraphAgent) -> List[GraphEve valid_actions.append(cls(actor, target_nodes=[obj, entity])) return valid_actions + + def post_json_load(self, world: "World") -> None: + """Do a json load on the processed event array""" + self.events = [ + GraphEvent.from_json(json_event, world) for json_event in self.events + ] diff --git a/light/world/souls/base_soul.py b/light/world/souls/base_soul.py index 76c6794b1..eb5adee97 100644 --- a/light/world/souls/base_soul.py +++ b/light/world/souls/base_soul.py @@ -8,7 +8,7 @@ from copy import deepcopy import os import asyncio -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional from light.graph.events.graph_events import SystemMessageEvent if TYPE_CHECKING: @@ -32,10 +32,13 @@ def __init__(self, target_node: "GraphAgent", world: "World"): self.target_node._last_interaction_partner_id = None self.reset_interaction_history(self.target_node) - def get_last_interaction_partner(self, node=None): + def get_last_interaction_partner(self, node=None) -> Optional["GraphAgent"]: if node == None: node = self.target_node - return node._last_interaction_partner_id + partner_id = node._last_interaction_partner_id + if partner_id is None: + return None + return self.world.oo_graph.get_node(partner_id) def log_interaction_from_event(self, event): event_name = event.__class__.__name__ diff --git a/light/world/souls/models/generative_heuristic_model_soul.py b/light/world/souls/models/generative_heuristic_model_soul.py index ec8352f88..3af744081 100644 --- a/light/world/souls/models/generative_heuristic_model_soul.py +++ b/light/world/souls/models/generative_heuristic_model_soul.py @@ -274,17 +274,13 @@ def npc_action(self): # Get agents agent = self.target_node - partner_id = self.get_last_interaction_partner(agent) - if partner_id is None: - return - partner = self.world.oo_graph.get_node(partner_id) agent_id = agent.node_id - partner_id = self.get_last_interaction_partner() - if partner_id != None: - partner = self.world.oo_graph.get_node(partner_id) + partner = self.get_last_interaction_partner() + if partner is None: + return + partner_name = None + if partner != None: partner_name = partner.name - else: - partner_name = None quest_txt = None if not hasattr(agent, "quests") or agent.quests is None: @@ -388,13 +384,9 @@ def npc_dialogue(self, obs=None): agent_id = agent.node_id if obs is None: - partner_id = self.get_last_interaction_partner(agent) - if partner_id is None: - return - partner = self.world.oo_graph.get_node(partner_id) + partner = self.get_last_interaction_partner(agent) else: partner = obs.actor - partner_id = partner.node_id partner_interactor_id = partner._last_interaction_partner_id if ( isinstance(obs, SayEvent) @@ -474,7 +466,7 @@ async def _take_timestep(self) -> None: partner = random.choice(agents) partner_id = partner.node_id if ( - partner.node_id != agent_id + partner_id != agent_id and partner.get_prop("speed", 0) > 0 and self.get_last_interaction_partner(partner) is None ): diff --git a/light/world/souls/on_event_soul.py b/light/world/souls/on_event_soul.py index f5176b231..188e962c4 100644 --- a/light/world/souls/on_event_soul.py +++ b/light/world/souls/on_event_soul.py @@ -213,6 +213,7 @@ def trade_event_heuristics(self, event) -> bool: def tell_goal_heuristics(self, event) -> bool: agent = self.target_node event_name = event.__class__.__name__ + partner = self.get_last_interaction_partner(agent) # Tell Mission to Other Agent (or not). if ( (event_name == "SayEvent" and event.actor != agent) @@ -221,7 +222,7 @@ def tell_goal_heuristics(self, event) -> bool: and event.actor != agent and event.target_nodes[0] == agent ) - ) and (self.get_last_interaction_partner(agent) == event.actor.node_id): + ) and (partner is not None and partner.node_id == event.actor.node_id): about_goals = False for words in ["mission", "goal", "quest", "what you want"]: if words in event.text_content: @@ -420,7 +421,6 @@ def timestep_actions(self): return True # Random movement for NPCs.. - if ( random.randint(0, 300) < agent.speed and self.get_last_interaction_partner(agent) is None diff --git a/light/world/utils/json_utils.py b/light/world/utils/json_utils.py index 945936640..272c24724 100644 --- a/light/world/utils/json_utils.py +++ b/light/world/utils/json_utils.py @@ -17,12 +17,16 @@ class GraphEncoder(json.JSONEncoder): def default(self, o): + from light.graph.events.base import GraphEvent + if isinstance(o, set): return sorted(list(o)) if isinstance(o, list): return sorted(o) if isinstance(o, GraphEdge): return {k: v for k, v in o.__dict__.copy().items() if not k.startswith("_")} + if isinstance(o, GraphEvent): + return o.to_json() if not isinstance(o, GraphNode): return super().default(o) use_dict = {k: v for k, v in o.__dict__.copy().items() if not k.startswith("_")}