diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py index 48f980914733dd3b4fb42aeb11659545a7e59a48..2f621c3b8038cdd85e370f99bb61deff005a4092 100644 --- a/overcooked_simulator/counters.py +++ b/overcooked_simulator/counters.py @@ -622,6 +622,7 @@ class Trashcan(Counter): return item if isinstance(item, CookingEquipment): item.reset_content() + item.reset() return item self.hook(TRASHCAN_USAGE, counter=self, item=item) return None diff --git a/overcooked_simulator/game_content/item_info.yaml b/overcooked_simulator/game_content/item_info.yaml index b06b6296f391e37bff755207de3d9029d398f1a0..714d559117b435ae9ae2c7d7a2d4db0efab77c63 100644 --- a/overcooked_simulator/game_content/item_info.yaml +++ b/overcooked_simulator/game_content/item_info.yaml @@ -182,45 +182,45 @@ Pizza: BurntCookedPatty: type: Waste - seconds: 5.0 + seconds: 10.0 needs: [ CookedPatty ] equipment: Pan BurntChips: type: Waste - seconds: 5.0 + seconds: 10.0 needs: [ Chips ] equipment: Basket BurntFriedFish: type: Waste - seconds: 5.0 + seconds: 10.0 needs: [ FriedFish ] equipment: Basket BurntTomatoSoup: type: Waste needs: [ TomatoSoup ] - seconds: 6.0 + seconds: 10.0 equipment: Pot BurntOnionSoup: type: Waste needs: [ OnionSoup ] - seconds: 6.0 + seconds: 10.0 equipment: Pot BurntPizza: type: Waste needs: [ Pizza ] - seconds: 7.0 + seconds: 10.0 equipment: Peel # -------------------------------------------------------------------------------- Fire: type: Effect - seconds: 5.0 + seconds: 10.0 needs: [ BurntCookedPatty, BurntChips, BurntFriedFish, BurntTomatoSoup, BurntOnionSoup, BurntPizza ] manager: FireManager effect_type: Unusable diff --git a/overcooked_simulator/game_content/layouts/aatest_layouts/godot_test_layout.layout b/overcooked_simulator/game_content/layouts/aatest_layouts/godot_test_layout.layout new file mode 100644 index 0000000000000000000000000000000000000000..06db451a2f644732cc693835a9a5aaee6a38d612 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/aatest_layouts/godot_test_layout.layout @@ -0,0 +1,9 @@ +########## +#________# +#________# +#________# +#________# +#________# +#________# +#________# +#########P diff --git a/overcooked_simulator/game_content/layouts/aatest_layouts/test1.layout b/overcooked_simulator/game_content/layouts/aatest_layouts/test1.layout new file mode 100644 index 0000000000000000000000000000000000000000..8ccd0a1152692fd1c9a0a911b0bb677bab98cb95 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/aatest_layouts/test1.layout @@ -0,0 +1 @@ +____A___P \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/aatest_layouts/test2.layout b/overcooked_simulator/game_content/layouts/aatest_layouts/test2.layout new file mode 100644 index 0000000000000000000000000000000000000000..8cfae98af3e7748e2df2dcdaae2ffffbe8c4a074 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/aatest_layouts/test2.layout @@ -0,0 +1,9 @@ +_ +_ +_ +A +_ +_ +_ +_ +P \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/aatest_layouts/test3.layout b/overcooked_simulator/game_content/layouts/aatest_layouts/test3.layout new file mode 100644 index 0000000000000000000000000000000000000000..8847c681b18500df23abe068dac76fb4dfbdd6d2 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/aatest_layouts/test3.layout @@ -0,0 +1,4 @@ +___ +_A_ +___ +__P \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/aatest_layouts/test4.layout b/overcooked_simulator/game_content/layouts/aatest_layouts/test4.layout new file mode 100644 index 0000000000000000000000000000000000000000..09d7551fa64358eb61a97d4c562617dca82b9655 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/aatest_layouts/test4.layout @@ -0,0 +1,3 @@ +____ +_A__ +___P \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/basic.layout b/overcooked_simulator/game_content/layouts/basic.layout index 5238c21f86c487ed90ff8efc6a373c6213d1f6c9..f3d0c2ec5af7857c4af42841d154a677fd31b21b 100644 --- a/overcooked_simulator/game_content/layouts/basic.layout +++ b/overcooked_simulator/game_content/layouts/basic.layout @@ -4,6 +4,6 @@ $__________I #__A_____A_D C__________E -C__________G -#__________# -#P#S+#X##S+# \ No newline at end of file +#__________G +C__________# +##PS+#X##S+# \ 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 24bda23ff218acd1fa77c0ab61897385dfa6a1ff..22793eff67c447d425c98d0df767ada64888f034 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -1,6 +1,5 @@ import argparse import dataclasses -import glob import json import logging import random @@ -53,12 +52,13 @@ class PlayerKeySet: """ def __init__( - self, - move_keys: list[pygame.key], - interact_key: pygame.key, - pickup_key: pygame.key, - switch_key: pygame.key, - players: list[int], + self, + move_keys: list[pygame.key], + interact_key: pygame.key, + pickup_key: pygame.key, + switch_key: pygame.key, + players: list[int], + joystick: int ): """Creates a player key set which contains information about which keyboard keys control the player. @@ -70,6 +70,7 @@ class PlayerKeySet: pickup_key: The key to pick items up or put them down. switch_key: The key for switching through controllable players. players: The player indices which this keyset can control. + joystick: number of joystick (later check if available) """ self.move_vectors: list[list[int]] = [[-1, 0], [1, 0], [0, -1], [0, 1]] self.key_to_movement: dict[pygame.key, list[int]] = { @@ -83,6 +84,7 @@ class PlayerKeySet: self.current_player: int = players[0] if players else 0 self.current_idx = 0 self.other_keyset: list[PlayerKeySet] = [] + self.joystick = joystick def set_controlled_players(self, controlled_players: list[int]) -> None: self.controlled_players = controlled_players @@ -103,10 +105,10 @@ class PyGameGUI: """Visualisation of the overcooked environment and reading keyboard inputs using pygame.""" def __init__( - self, - url: str, - port: int, - manager_ids: list[str], + self, + url: str, + port: int, + manager_ids: list[str], ): pygame.init() pygame.display.set_icon( @@ -161,10 +163,10 @@ class PyGameGUI: self.kitchen_height = state["kitchen"]["height"] self.kitchen_aspect_ratio = self.kitchen_height / self.kitchen_width game_width = self.visualization_config["GameWindow"]["min_width"] - ( - 2 * self.screen_margin + 2 * self.screen_margin ) game_height = self.visualization_config["GameWindow"]["min_height"] - ( - 2 * self.screen_margin + 2 * self.screen_margin ) if self.kitchen_width > game_width: @@ -201,6 +203,9 @@ class PyGameGUI: self.game_height -= residual_y def setup_player_keys(self, n=1, disjunct=False): + # First four keys are for movement. Order: Down, Up, Left, Right. + # 5th key is for interacting with counters. + # 6th key ist for picking up things or dropping them. if n: players = list(range(self.number_humans_to_be_added)) key_set1 = PlayerKeySet( @@ -209,6 +214,7 @@ class PyGameGUI: pickup_key=pygame.K_e, switch_key=pygame.K_SPACE, players=players, + joystick=0 ) key_set2 = PlayerKeySet( move_keys=[pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN], @@ -216,6 +222,7 @@ class PyGameGUI: pickup_key=pygame.K_o, switch_key=pygame.K_p, players=players, + joystick=1 ) key_sets = [key_set1, key_set2] @@ -257,6 +264,41 @@ class PyGameGUI: ) self.send_action(action) + def handle_joy_stick_input(self, joysticks): + """Handles joystick inputs for movement every frame + Args: + joysticks: list of joysticks + """ + # Axis 0: joy stick left: -1 = left, ~0 = center, 1 = right + # Axis 1: joy stick left: -1 = up, ~0 = center, 1 = down + # see control stuff here (at the end of the page): https://www.pygame.org/docs/ref/joystick.html + for key_set in self.key_sets: + current_player_name = str(key_set.current_player) + # if a joystick is connected for current player + if key_set.joystick in joysticks: + # Usually axis run in pairs, up/down for one, and left/right for the other. Triggers count as axes. + # You may want to take into account some tolerance to handle jitter, and + # joystick drift may keep the joystick from centering at 0 or using the full range of position values. + tolerance_threshold = 0.2 + # axis 0 = joy stick left --> left & right + axis_left_right = joysticks[key_set.joystick].get_axis(0) + axis_up_down = joysticks[key_set.joystick].get_axis(1) + if abs(axis_left_right) > tolerance_threshold or abs(axis_up_down) > tolerance_threshold: + move_vec = np.zeros(2) + if abs(axis_left_right) > tolerance_threshold: + move_vec[0] += axis_left_right + # axis 1 = joy stick right --> up & down + if abs(axis_up_down) > tolerance_threshold: + move_vec[1] += axis_up_down + + if np.linalg.norm(move_vec) != 0: + move_vec = move_vec / np.linalg.norm(move_vec) + + action = Action( + current_player_name, ActionType.MOVEMENT, move_vec, duration=self.time_delta + ) + self.send_action(action) + def handle_key_event(self, event): """Handles key events for the pickup and interaction keys. Pickup is a single action, for interaction keydown and keyup is necessary, because the player has to be able to hold @@ -287,6 +329,42 @@ class PyGameGUI: if event.type == pygame.KEYDOWN: key_set.next_player() + def handle_joy_stick_event(self, event, joysticks): + """Handles joy stick events for the pickup and interaction keys. Pickup is a single action, + for interaction buttondown and buttonup is necessary, because the player has to be able to hold + the button down. + + Args: + event: Pygame event for extracting the button action. + joysticks: list of joysticks + """ + + for key_set in self.key_sets: + current_player_name = str(key_set.current_player) + # if a joystick is connected for current player + if key_set.joystick in joysticks: + # pickup = Button A <-> 0 + if joysticks[key_set.joystick].get_button(0) and event.type == pygame.JOYBUTTONDOWN: + action = Action(current_player_name, ActionType.PUT, "pickup") + self.send_action(action) + + # interact = Button X <-> 2 + if joysticks[key_set.joystick].get_button(2) and event.type == pygame.JOYBUTTONDOWN: + action = Action( + current_player_name, ActionType.INTERACT, InterActionData.START + ) + self.send_action(action) + # stop interaction if last pressed button was X <-> 2 + if event.button == 2 and event.type == pygame.JOYBUTTONUP: + action = Action( + current_player_name, ActionType.INTERACT, InterActionData.STOP + ) + self.send_action(action) + # switch button Y <-> 3 + if joysticks[key_set.joystick].get_button(3) and not CONNECT_WITH_STUDY_SERVER: + if event.type == pygame.JOYBUTTONDOWN: + key_set.next_player() + def init_ui_elements(self): self.manager = pygame_gui.UIManager((self.window_width, self.window_height)) self.manager.get_theme().load_theme(ROOT_DIR / "gui_2d_vis" / "gui_theme.json") @@ -746,7 +824,7 @@ class PyGameGUI: self.player_info = {self.player_info["player_id"]: self.player_info} else: environment_config_path = ( - ROOT_DIR / "game_content" / "environment_config.yaml" + ROOT_DIR / "game_content" / "environment_config.yaml" ) layout_path = self.layout_file_paths[self.layout_selection.selected_option] item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" @@ -790,7 +868,7 @@ class PyGameGUI: ) ) assert ( - json.loads(websocket.recv())["status"] == 200 + json.loads(websocket.recv())["status"] == 200 ), "not accepted player" self.websockets[player_id] = websocket else: @@ -866,13 +944,13 @@ class PyGameGUI: self.menu_state = MenuStates.Game self.number_players = ( - self.number_humans_to_be_added + self.number_bots_to_be_added + self.number_humans_to_be_added + self.number_bots_to_be_added ) self.vis.create_player_colors(self.number_players) if self.split_players: assert ( - self.number_humans_to_be_added > 1 + self.number_humans_to_be_added > 1 ), "Not enough players for key configuration." num_key_set = 2 if self.multiple_keysets else 1 self.key_sets = self.setup_player_keys( @@ -1037,6 +1115,11 @@ class PyGameGUI: # Game loop self.running = True + # This dict can be left as-is, since pygame will generate a + # pygame.JOYDEVICEADDED event for every joystick connected + # at the start of the program. + joysticks = {} + while self.running: try: self.time_delta = clock.tick(self.FPS) / 1000 @@ -1046,6 +1129,20 @@ class PyGameGUI: if event.type == pygame.QUIT: self.running = False + # connect joystick + if pygame.joystick.get_count() > 0 and event.type == pygame.JOYDEVICEADDED: + # This event will be generated when the program starts for every + # joystick, filling up the list without needing to create them manually. + joy = pygame.joystick.Joystick(event.device_index) + joysticks[joy.get_instance_id()] = joy + print(f"Joystick {joy.get_instance_id()} connected") + + # disconnect joystick + if event.type == pygame.JOYDEVICEREMOVED: + del joysticks[event.instance_id] + print(f"Joystick {event.instance_id} disconnected") + print("Number of joysticks:", pygame.joystick.get_count()) + # elif event.type == pygame.VIDEORESIZE: # # scrsize = event.size # self.window_width_windowed = event.w @@ -1059,8 +1156,8 @@ class PyGameGUI: match event.ui_element: case self.start_button: if not ( - self.number_humans_to_be_added - + self.number_bots_to_be_added + self.number_humans_to_be_added + + self.number_bots_to_be_added ): continue self.start_button_press() @@ -1124,12 +1221,14 @@ class PyGameGUI: self.manage_button_visibility() if ( - event.type in [pygame.KEYDOWN, pygame.KEYUP] - and self.menu_state == MenuStates.Game + event.type in [pygame.KEYDOWN, pygame.KEYUP] + and self.menu_state == MenuStates.Game ): - pass self.handle_key_event(event) + if event.type in [pygame.JOYBUTTONDOWN, pygame.JOYBUTTONUP] and self.menu_state == MenuStates.Game: + self.handle_joy_stick_event(event, joysticks=joysticks) + self.manager.process_events(event) # drawing: @@ -1146,6 +1245,7 @@ class PyGameGUI: state = self.request_state() self.handle_keys() + self.handle_joy_stick_input(joysticks=joysticks) if state["ended"]: self.finished_button_press()