diff --git a/overcooked_simulator/effect_manager.py b/overcooked_simulator/effect_manager.py index bb13ee977c25bcc715bf7867e0208671c1cd9748..7e18e17ef757425d933360cc658306224efcd2fa 100644 --- a/overcooked_simulator/effect_manager.py +++ b/overcooked_simulator/effect_manager.py @@ -12,7 +12,7 @@ from overcooked_simulator.game_items import ( Effect, CookingEquipment, ) -from overcooked_simulator.hooks import Hooks +from overcooked_simulator.hooks import Hooks, NEW_FIRE, FIRE_SPREADING from overcooked_simulator.utils import get_touching_counters, find_item_on_counters if TYPE_CHECKING: @@ -74,6 +74,7 @@ class FireEffectManager(EffectManager): self.next_finished_timer = min( self.next_finished_timer, self.effect_to_timer[effect.uuid] ) + self.hook(NEW_FIRE, target=target) self.active_effects.append((effect, target)) self.new_effects = [] if self.next_finished_timer < now: @@ -110,6 +111,7 @@ class FireEffectManager(EffectManager): if effect.name not in target.active_effects and target.uuid not in [ t.uuid for _, t in self.active_effects ]: + self.hook(FIRE_SPREADING, target=target) if isinstance(target, CookingEquipment): if target.content_list: for content in target.content_list: diff --git a/overcooked_simulator/game_content/environment_config.yaml b/overcooked_simulator/game_content/environment_config.yaml index 9921619a60c2c85c239cae4f7b91a20e253fd917..4264c45679fdb9a66f6961c0ecd4bd8d8e0e9664 100644 --- a/overcooked_simulator/game_content/environment_config.yaml +++ b/overcooked_simulator/game_content/environment_config.yaml @@ -140,4 +140,18 @@ extra_setup_functions: log_class_kwargs: log_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl add_hook_ref: true - + info_msg: + func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' + kwargs: + hooks: [ cutting_board_100 ] + log_class: !!python/name:overcooked_simulator.info_msg.InfoMsgManager '' + log_class_kwargs: + msg: Glückwunsch du hast was geschnitten! + fire_msg: + func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' + kwargs: + hooks: [ new_fire ] + log_class: !!python/name:overcooked_simulator.info_msg.InfoMsgManager '' + log_class_kwargs: + msg: Feuer, Feuer, Feuer + level: Warning \ No newline at end of file diff --git a/overcooked_simulator/gui_2d_vis/overcooked_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py index 6a65a21256e6f0755492b15525ce2aad526a4889..a537861748079b3b6aaefc828b4b7942d41483ba 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -630,6 +630,24 @@ class PyGameGUI: self.update_score_label(state) + if state["info_msg"]: + for idx, msg in enumerate(reversed(state["info_msg"])): + text_surface = self.comic_sans.render( + msg[0], + antialias=True, + color=(0, 0, 0) + if msg[1] == "Normal" + else ((255, 0, 0) if msg[1] == "Warning" else (0, 255, 0)), + # bgcolor=(255, 255, 255), + ) + self.main_window.blit( + text_surface, + ( + self.window_width / 4, + self.window_height - self.screen_margin + 5 + (20 * idx), + ), + ) + def set_window_size(self): self.game_screen = pygame.Surface( ( @@ -1002,6 +1020,8 @@ class PyGameGUI: """Starts pygame and the gui loop. Each frame the game state is visualized and keyboard inputs are read.""" log.debug(f"Starting pygame gui at {self.FPS} fps") pygame.font.init() + self.comic_sans = pygame.font.SysFont("Comic Sans MS", 30) + pygame.display.set_caption("Simple Overcooked Simulator") clock = pygame.time.Clock() diff --git a/overcooked_simulator/hooks.py b/overcooked_simulator/hooks.py index 285ed735fb58190183dce24f051d2c4f1b8bcd24..67c5462948a6bfc5271010f58574918f9920e97a 100644 --- a/overcooked_simulator/hooks.py +++ b/overcooked_simulator/hooks.py @@ -96,6 +96,10 @@ ACTION_PUT = "action_put" ACTION_INTERACT_START = "action_interact_start" +NEW_FIRE = "new_fire" + +FIRE_SPREADING = "fire_spreading" + class Hooks: def __init__(self, env): diff --git a/overcooked_simulator/info_msg.py b/overcooked_simulator/info_msg.py new file mode 100644 index 0000000000000000000000000000000000000000..f65385b8aeb765c6b02102d4052dc43f2ca71ecb --- /dev/null +++ b/overcooked_simulator/info_msg.py @@ -0,0 +1,39 @@ +from datetime import timedelta + +from overcooked_simulator.overcooked_environment import Environment + + +class InfoMsgManager: + def __init__( + self, + name: str, + env: Environment, + msg: str, + duration: int = 5, + level: str = "Normal", + ): + self.msg = msg + self.duration = timedelta(seconds=duration) + self.level = level + + def __call__(self, hook_ref: str, env: Environment, **kwargs): + for player_id in env.players: + env.info_msgs_per_player[player_id].append( + { + "msg": self.msg, + "start_time": env.env_time, + "end_time": env.env_time + self.duration, + "level": self.level, + } + ) + self.remove_old_msgs(env) + + @staticmethod + def remove_old_msgs(env: Environment): + for player_id, msgs in env.info_msgs_per_player.items(): + delete_msgs = [] + for idx, msg in enumerate(msgs): + if msg["end_time"] < env.env_time: + delete_msgs.append(idx) + for idx in reversed(delete_msgs): + msgs.pop(idx) diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index 25aee07d4b5f783bd8c507fc0f2e038aa870c279..d157ce604b478f28afe1c827f4a7e71b30aa3a49 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -5,6 +5,7 @@ import inspect import json import logging import sys +from collections import defaultdict from datetime import timedelta, datetime from enum import Enum from pathlib import Path @@ -50,7 +51,7 @@ from overcooked_simulator.order import ( OrderConfig, ) from overcooked_simulator.player import Player, PlayerConfig -from overcooked_simulator.state_representation import StateRepresentation +from overcooked_simulator.state_representation import StateRepresentation, InfoMsg from overcooked_simulator.utils import create_init_env_time, get_closest log = logging.getLogger(__name__) @@ -285,6 +286,8 @@ class Environment: str, EffectManager ] = self.counter_factory.setup_effect_manger(self.counters) + self.info_msgs_per_player: dict[str, list[InfoMsg]] = defaultdict(list) + self.hook( ENV_INITIALIZED, environment_config=env_config, @@ -825,6 +828,12 @@ class Environment: } if self.player_view_restricted else None, + "info_msg": [ + (msg["msg"], msg["level"]) + for msg in self.info_msgs_per_player[player_id] + if msg["start_time"] < self.env_time + and msg["end_time"] > self.env_time + ], } self.hook(STATE_DICT, state=state, player_id=player_id) json_data = json.dumps(state) diff --git a/overcooked_simulator/state_representation.py b/overcooked_simulator/state_representation.py index 2faa1a1932035342e57b27df9865d673efdcc8cb..fe587ca6d41c40514d0fe3d8e862c09575a6c09b 100644 --- a/overcooked_simulator/state_representation.py +++ b/overcooked_simulator/state_representation.py @@ -2,6 +2,7 @@ Type hint classes for the representation of the json state. """ from datetime import datetime +from enum import Enum from pydantic import BaseModel from typing_extensions import Literal, TypedDict @@ -74,6 +75,19 @@ class ViewRestriction(BaseModel): range: float | None +class InfoMsgLevel(Enum): + Normal = "Normal" + Warning = "Warning" + Success = "Success" + + +class InfoMsg(TypedDict): + msg: str + start_time: datetime + end_time: datetime + level: InfoMsgLevel + + class StateRepresentation(BaseModel): """The format of the returned state representation.""" @@ -86,6 +100,7 @@ class StateRepresentation(BaseModel): env_time: datetime # isoformat str remaining_time: float view_restriction: None | ViewRestriction + info_msg: list[tuple[str, str]] def create_json_schema():