From 300eaae38dd66a6134db4ed7912a8abd19d6933c Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:21:42 +0200 Subject: [PATCH 01/43] Optimized npcs --- commands/edi.py | 9 +- commands/jca.py | 54 +------ commands/nhi.py | 29 ++-- objects/avatar.py | 13 ++ objects/cafe.py | 63 +++++++- objects/cafe_object.py | 1 + objects/customer.py | 344 ++++++++++++++++++++++------------------- objects/player.py | 3 - objects/waiter.py | 288 ++++++++++++++++------------------ 9 files changed, 418 insertions(+), 386 deletions(-) diff --git a/commands/edi.py b/commands/edi.py index db4f9c2..ed76b60 100644 --- a/commands/edi.py +++ b/commands/edi.py @@ -1,9 +1,6 @@ from typing import TYPE_CHECKING from response import ExtensionResponse from asyncio import create_task -from objects.waiter import spawn_multiple -from objects.customer import spawn_cycle -from objects.cafe_object import ObjectType if TYPE_CHECKING: from server import CafeServer @@ -18,8 +15,6 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) match int(params[1]): case 0: - create_task(spawn_multiple(server, player.cafe)) - player.customer_cycle = create_task(spawn_cycle(server, player.cafe)) player.cafe.connect_chairs() case 1: @@ -27,8 +22,8 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) if waiter.task: waiter.task.cancel() - if player.customer_cycle: - player.customer_cycle.cancel() + if player.cafe.customer_cycle: + player.cafe.customer_cycle.cancel() player.customer_cycle = None for customer in player.cafe.customers: diff --git a/commands/jca.py b/commands/jca.py index 95e7c9b..7b2acf3 100644 --- a/commands/jca.py +++ b/commands/jca.py @@ -1,12 +1,12 @@ from typing import TYPE_CHECKING + +from objects.waiter import spawn_waiters from response import ExtensionResponse from commands.sgc import handle_sgc from commands.ifr import handle_ifr from commands.ein import handle_ein -from objects.waiter import spawn_multiple -from objects.customer import spawn_cycle, Action -from objects.cafe_object import CafeObject, ObjectType from asyncio import create_task +from objects.customer import spawn_cycle if TYPE_CHECKING: from server import CafeServer @@ -31,51 +31,11 @@ async def handle_jca(server: 'CafeServer', client: 'StreamWriter', *params: str) await handle_ifr(server, client, *params) await handle_ein(server, client, *params) - create_task(spawn_multiple(server, wanted_player.cafe)) - - # TODO: Broken for other player's cafe - # Tries to use player's cafe objects instead of the other player's - if not wanted_player.customer_cycle: - print(f'created waiter cycle\nused this player\'s cafe: f{wanted_player.avatar.username}') - # TODO: add random customers - wanted_player.customer_cycle = create_task(spawn_cycle(server, wanted_player.cafe)) + if not wanted_player.cafe.customer_cycle: + print(f'Created a new customer cycle\nUsed this player\'s Cafe: f{wanted_player.avatar.username}') + wanted_player.cafe.customer_cycle = create_task(spawn_cycle(server, wanted_player.cafe)) else: customers = wanted_player.cafe.customers - print(f'customers: {customers}') - - for customer in customers: - spawn_info = '+'.join(map(str, [ - customer.id, # Customer ID - 0, # NPC type (0: Customer) - 0, # Favourite dish - -1, # Dish ID - 0, # Is thirsty - customer.avatar.to_string(True) # Avatar - ])) - - response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) - - location = wanted_player.cafe.get_object(*customer.pos) - - if isinstance(location, CafeObject) and location.type == ObjectType.CHAIR: - sit_down_info = '+'.join(map(str, [ - customer.id, # Customer ID - Action.SIT_DOWN, # Action type (2: Sit down) - customer.pos[0], # X - customer.pos[1] # Y - ])) - - response = ExtensionResponse('nac', '-1', '0', sit_down_info) - - else: - insert_info = '+'.join(map(str, [ - str(customer.id), # Customer ID - Action.INSERT # Action type (0: Insert) - ])) - - response = ExtensionResponse('nac', '-1', '0', insert_info) - - await server.send_response(client, response_spawn) - await server.send_response(client, response) + create_task(spawn_waiters(server, wanted_player.cafe)) diff --git a/commands/nhi.py b/commands/nhi.py index 07ef18a..f5fde4a 100644 --- a/commands/nhi.py +++ b/commands/nhi.py @@ -1,7 +1,8 @@ +from objects.avatar import Avatar +from objects.waiter import Waiter, get_random_counter from response import ExtensionResponse from typing import TYPE_CHECKING -from objects.waiter import Waiter -from asyncio import create_task +from asyncio import create_task, sleep if TYPE_CHECKING: from server import CafeServer @@ -16,22 +17,24 @@ async def handle_nhi(server: 'CafeServer', client: 'StreamWriter', *params: str) player = server.clients[address] npc_id = player.cafe.waiters[-1].id + 1 if player.cafe.waiters else 0 - npc_name = params[1] - npc_type = params[2] # 2: man, 1: woman + name = params[1] + gender = int(params[2]) # 2: man, 1: woman - response = ExtensionResponse('nhi', '0', '0', npc_name, npc_type) + avatar = Avatar.get_random_avatar(name, gender) + + response = ExtensionResponse('nhi', '0', '0', name, str(gender)) await server.send_response(client, response) - new_waiter = Waiter(int(npc_id), npc_name, int(npc_type), player.cafe) + new_waiter = Waiter(npc_id, name, gender, 50) + player.cafe.waiters.append(new_waiter) - # TODO: Save new waiter to database - server.db.update_cafe(player.id, waiters=player.cafe.get_waiters_as_json()) + new_waiter.task = create_task(new_waiter.spawn(server, player.cafe)) - # task = create_task(new_waiter.spawn(server, client)) - # new_waiter.task = task - # task.add_done_callback(new_waiter.remove_task) + await sleep(2) - await new_waiter.spawn(server) + new_waiter.task = create_task(new_waiter.go_to_counter(server, player.cafe, + get_random_counter(player.cafe), True)) + + server.db.update_cafe(player.id, waiters=player.cafe.get_waiters_as_json()) - # await task diff --git a/objects/avatar.py b/objects/avatar.py index d5ee6e8..01aba2f 100644 --- a/objects/avatar.py +++ b/objects/avatar.py @@ -1,3 +1,4 @@ +from random import choice, randint from typing import Literal parts = { @@ -64,3 +65,15 @@ def from_string(cls, string: str) -> 'Avatar': obj.legs_color = int(color) return obj + + @classmethod + def get_random_avatar(cls, name: str, gender: int = None) -> 'Avatar': + if not gender: + gender = choice([1, 2]) + + skin_color = randint(0, 7) + top_color = randint(0, 16) + hair_color = randint(0, 14) + legs_color = randint(0, 16) + + return Avatar(name, gender, skin_color, top_color, hair_color, legs_color) diff --git a/objects/cafe.py b/objects/cafe.py index 3bfa65f..c0a67e9 100644 --- a/objects/cafe.py +++ b/objects/cafe.py @@ -1,10 +1,12 @@ -from typing import List, Literal, Union, Dict, TYPE_CHECKING +from typing import List, Literal, Union, Dict, TYPE_CHECKING, Optional from objects.cafe_object import CafeObject, ObjectType -from objects.waiter import Waiter from consts import * from astar import AStar import json +from objects.customer import Customer +from objects.waiter import Waiter + if TYPE_CHECKING: from asyncio import StreamWriter from objects.customer import Customer @@ -60,6 +62,7 @@ def __init__(self): self.waiters: list['Waiter'] = [] self.customers: list['Customer'] = [] + self.customer_cycle: Optional['Task'] = None def build_tiles(self) -> str: return '+'.join(['+'.join([str(tile) for tile in row]) for row in self.tiles]) @@ -102,8 +105,9 @@ def get_waiters_as_json(self) -> str: return json_str def set_waiters_from_json(self, json_str: str) -> None: - waiters = json.loads(json_str) - self.waiters = [Waiter.from_dict(waiter, self) for waiter in waiters] + if json_str: + waiters = json.loads(json_str) + self.waiters = [Waiter.from_dict(waiter) for waiter in waiters] def get_object(self, x: int, y: int) -> Union['CafeObject', None]: for obj in self.objects: @@ -203,6 +207,48 @@ def connect_chairs(self) -> None: print(f'I connected {len(self.available_tables)} chairs to tables') print(f'Connected table\'s pos: {[table.pos for table in self.available_tables]}\n') + # Getting the closest pos (Like: From the door, to the another object) + # This need to get where's the waiter is standing. Its always choose the sortest path. + def get_shortest_pos(self, first_pos, second_pos): + cafe_size = self.size + obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] + + for i in range(cafe_size): + obstacles[i][0] = 1 + obstacles[0][i] = 1 + + for obj in self.objects: + obstacles[obj.pos[0]][obj.pos[1]] = 1 + + obstacles[second_pos[0]][second_pos[1]] = 0 + + path = AStar(obstacles, tuple(first_pos), tuple(second_pos)).solve() + + if path: + path.pop() + return path[-1] + + return None + + # Getting nearby free spaces around the object + def get_nearby_free_spaces(self, object_pos: list[int]): + free_spaces = [] + + nearby_pos = [ + [object_pos[0] - 1, object_pos[1]], + [object_pos[0], object_pos[1] - 1], + [object_pos[0] + 1, object_pos[1]], + [object_pos[0], object_pos[1] + 1] + ] + + for pos in nearby_pos: + obj = self.get_object(int(pos[0]), int(pos[1])) + + if obj is None: + free_spaces.append(pos) + + return free_spaces + @classmethod def get_starting_cafe(cls) -> 'Cafe': cafe = cls() @@ -211,8 +257,10 @@ def get_starting_cafe(cls) -> 'Cafe': cafe.expansion_id = 0 cafe.size = cafe.expansion_id + 8 cafe.tiles = [[1 for _ in range(cafe.size)] for _ in range(cafe.size)] - cafe.parse_tiles('7+101+101+101+101+101+101+101+101+4+4+4+4+4+4+4+101+4+4+4+4+4+4+4+101+4+4+4+4+4+4+4+101+1+1+1+4+4+4+4+101+1+1+1+4+4+4+4+101+1+1+1+4+4+4+4+101+1+1+1+4+4+4+4') - cafe.parse_objects('3+0+901+0#5+0+901+0#5+1+601+3+-1+0#0+2+201+0#5+2+401+0#7+2+601+3+-1+0#7+3+401+0#1+4+351+0#5+4+401+0#1+5+252+0+-1#3+5+301+0+-1+0#5+5+601+1+-1+0#7+5+401+0#1+6+252+0+-1#3+6+301+0+-1+0#7+6+601+1+-1+0#1+7+252+0+-1+0+0+0') + cafe.parse_tiles( + '7+101+101+101+101+101+101+101+101+4+4+4+4+4+4+4+101+4+4+4+4+4+4+4+101+4+4+4+4+4+4+4+101+1+1+1+4+4+4+4+101+1+1+1+4+4+4+4+101+1+1+1+4+4+4+4+101+1+1+1+4+4+4+4') + cafe.parse_objects( + '3+0+901+0#5+0+901+0#5+1+601+3+-1+0#0+2+201+0#5+2+401+0#7+2+601+3+-1+0#7+3+401+0#1+4+351+0#5+4+401+0#1+5+252+0+-1#3+5+301+0+-1+0#5+5+601+1+-1+0#7+5+401+0#1+6+252+0+-1#3+6+301+0+-1+0#7+6+601+1+-1+0#1+7+252+0+-1+0+0+0') cafe.player_start = cafe.get_start_pos() cafe.fridge_capacity = cafe.get_fridge_capacity() @@ -237,7 +285,8 @@ def get_marketplace(cls) -> 'Cafe': marketplace.size = 12 marketplace.background = 1502 marketplace.tiles = [[1 for _ in range(marketplace.size)] for _ in range(marketplace.size)] - marketplace.parse_tiles('7+7+7+7+7+7+7+7+7+7+7+7+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+7+7+7+3+3+3+3+3+7+3+3+3+7+7+7+3+3+3+3+3+7+3+3+3+7+7+7+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3') + marketplace.parse_tiles( + '7+7+7+7+7+7+7+7+7+7+7+7+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+7+7+7+3+3+3+3+3+7+3+3+3+7+7+7+3+3+3+3+3+7+3+3+3+7+7+7+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3+7+3+3+3+3+3+3+3+3+3+3+3') marketplace.parse_objects('6+6+511#5+11+515#1+9+512#11+12+513#12+11+514') marketplace.player_start = [1, 2] diff --git a/objects/cafe_object.py b/objects/cafe_object.py index 72b4f87..bf70e86 100644 --- a/objects/cafe_object.py +++ b/objects/cafe_object.py @@ -64,6 +64,7 @@ def get_nearby_pos(self) -> dict[tuple[int], int]: def change_rotation(self) -> None: pass + def to_dict(self) -> dict: obj_dict = { 'pos': self.pos, diff --git a/objects/customer.py b/objects/customer.py index c70ddf7..6b005e8 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -1,22 +1,34 @@ +import asyncio +from asyncio import create_task, sleep, Task, current_task from random import randint, choice -from typing import TYPE_CHECKING, Optional -from response import ExtensionResponse -from objects.avatar import Avatar -from objects.cafe_object import ObjectType -from asyncio import sleep, current_task, wait_for, create_task +from typing import Optional, List, TYPE_CHECKING + from astar import AStar +from objects.avatar import Avatar +from objects.cafe_object import CafeObject, ObjectType +from objects.waiter import Waiter +from response import ExtensionResponse if TYPE_CHECKING: - from asyncio import StreamWriter, Task from server import CafeServer - from objects.cafe import Cafe - from objects.player import Player - from objects.waiter import Waiter - from objects.cafe_object import CafeObject + from asyncio import StreamWriter + + +class Action: + INSERT = 0 + WALK_TO_CHAIR = 1 + SIT_DOWN = 2 + EAT = 3 + LEAVE = 4 + LEAVE_COMPLETE = 41 -# TODO: Tidy up this function -def get_time(cafe: 'Cafe', chair: 'CafeObject') -> float: +class WaiterTask: + CLEAN = 0 + SERVE = 1 + + +def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: cafe_size = cafe.size obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] @@ -27,243 +39,263 @@ def get_time(cafe: 'Cafe', chair: 'CafeObject') -> float: for obj in cafe.objects: obstacles[obj.pos[0]][obj.pos[1]] = 1 - obstacles[chair.pos[0]][chair.pos[1]] = 0 + obstacles[end_pos[0]][end_pos[1]] = 0 - start_pos = tuple(cafe.get_start_pos()) - path = AStar(obstacles, start_pos, tuple(chair.pos)).solve() + path = AStar(obstacles, tuple(start_pos), tuple(end_pos)).solve() if path: distance = len(path) - 1 - time = distance / 1.95 + time = distance / 1.85 # OLD -> 1.95 TESTED: 1.90 (customers should need to wait few sec before sitting down) + + # print(f'[CUSTOMER-ASTAR]: Start: {start_pos} End: {end_pos}') + # print(f'[CUSTOMER-ASTAR]: Time = {time}s') + # print(f'[CUSTOMER-ASTAR]: Distance = {distance}') + # print(f'[CUSTOMER-ASTAR]: Path = {path}') + else: time = -1 return time -def _get_random_avatar() -> Avatar: - gender = choice([1, 2]) +def get_random_table(cafe) -> Optional['CafeObject']: + eligible_tables = cafe.available_tables + free_tables = [table for table in eligible_tables if not table.busy] - skin_color = randint(0, 7) - top_color = randint(0, 16) - hair_color = randint(0, 14) - legs_color = randint(0, 16) + return choice(free_tables) if free_tables else None - return Avatar('customer', gender, skin_color, top_color, hair_color, legs_color) +def check_for_foods(cafe): + counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER] + counters_with_food = [counter for counter in counters if counter.dish_id != -1] -def _get_random_table(cafe: 'Cafe') -> Optional['CafeObject']: - eligible_tables = cafe.available_tables - nonbusy_tables = [table for table in eligible_tables if not table.busy] + if counters_with_food: + return True - return choice(nonbusy_tables) if nonbusy_tables else None + return False -def _get_random_waiter(cafe: 'Cafe') -> Optional['Waiter']: - nonbusy_waiters = [waiter for waiter in cafe.waiters if not waiter.task] +def get_random_waiter(cafe, task) -> Optional['Waiter']: + waiters = cafe.waiters + if task == WaiterTask.SERVE: + free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority >= 50] + else: + free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority <= 50] - return choice(nonbusy_waiters) if nonbusy_waiters else None + return choice(free_waiters) if free_waiters else None -def _check_for_foods(cafe: 'Cafe') -> bool: - counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER] - counters_with_food = [counter for counter in counters if counter.dish_id != -1] +async def wait_for_table(cafe) -> Optional['CafeObject']: + for _ in range(10): + table = get_random_table(cafe) + if table: + return table + await sleep(1) + return None - return True if counters_with_food else False - -class Action: - INSERT = 0 - WALK_TO_CHAIR = 1 - SIT_DOWN = 2 - EAT = 3 - LEAVE = 4 - LEAVE_COMPLETE = 41 +async def wait_for_waiter(cafe) -> Optional['Waiter']: + for _ in range(25): + waiter = get_random_waiter(cafe, task=WaiterTask.SERVE) + if check_for_foods(cafe): + if waiter: + return waiter + await sleep(1) + return None -async def spawn_cycle(server: 'CafeServer', cafe: 'Cafe') -> None: +async def wait_for_cleaning(server, cafe, table): while True: - new_id = cafe.customers[-1].id + 1 if cafe.customers else 100 - new_customer = Customer(new_id, cafe) + waiter = get_random_waiter(cafe, task=WaiterTask.CLEAN) + if waiter: + waiter.task = create_task(waiter.clean_table(server, cafe, table)) + waiter.task.add_done_callback(waiter.remove_task) + break - cafe.customers.append(new_customer) - create_task(new_customer.start(server)) - - await sleep(randint(10, 10)) + await sleep(0.1) class Customer: - def __init__(self, customer_id: int, player_cafe: 'Cafe'): + def __init__(self, customer_id: int): self.id: int = customer_id - self.avatar: Avatar = _get_random_avatar() - self.pos: list[int] = [0, 0] + self.avatar: Avatar = Avatar.get_random_avatar('Customer') + self.pos: List[int] = [0, 0] + self.task: Optional[asyncio.Task] = None - self.cafe: 'Cafe' = player_cafe - self.task: Optional['Task'] = None + self.dish = -1 - print(f"CUSTOMER WITH ID {self.id} CREATED") + async def start(self, server, player_cafe) -> None: + await self.spawn(server, player_cafe) - async def _spawn(self, server: 'CafeServer') -> None: - # TODO: Add favourite dish - spawn_info = '+'.join(map(str, [ - self.id, # Customer ID - 0, # NPC type (0: Customer) - 0, # Favourite dish - -1, # Dish ID - 0, # Is thirsty - self.avatar.to_string(True) # Avatar - ])) + # print(f"[CUSTOMER] New spawned: {self.id}") - insert_info = '+'.join(map(str, [ - str(self.id), # Customer ID - Action.INSERT # Action type (0: Insert) - ])) + await sleep(2) - response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + # print(f"[CUSTOMER] Started waiting for table: {self.id}") - self.pos = self.cafe.get_start_pos() + table = await wait_for_table(player_cafe) - for user in self.cafe.clients: - await server.send_response(user, response_spawn) - await server.send_response(user, response_insert) + if not table: + # print(f"[CUSTOMER] No free table found, leaving: {self.id}") + await self.leave(server, player_cafe) + return - async def _wait_for_food(self, server: 'CafeServer') -> None: - while not _check_for_foods(self.cafe): - await sleep(0.1) + # print(f"[CUSTOMER] Got a free table: {self.id}") - random_waiter = await self._wait_for_waiter() + table.busy = True + chair = table.connected_chair - random_waiter.task = create_task(random_waiter.start_serving_for(server, self)) - await random_waiter.task + await self.go_to_chair(server, player_cafe, chair) - random_waiter.task = create_task(random_waiter.go_to_counter(server)) - random_waiter.task.add_done_callback(random_waiter.remove_task) + walking_time = get_time(player_cafe, player_cafe.player_start, chair.pos) - async def _wait_for_waiter(self) -> 'Waiter': - while not (waiter := _get_random_waiter(self.cafe)): - await sleep(0.1) + # print(f"[CUSTOMER] Started going to table: {self.id} pos: {chair.pos} walking time: {walking_time}s") - return waiter + await sleep(walking_time) - async def _wait_for_table(self) -> 'CafeObject': - while not (table := _get_random_table(self.cafe)): - await sleep(0.1) + await self.sit_down(server, player_cafe) - table.busy = True + # print(f"[CUSTOMER] Sat down: {self.id}") - return table + # print(f"[CUSTOMER] Started waiting for a Waiter: {self.id}") - @staticmethod - async def wait_for_cleaning(self, server: 'CafeServer', table: 'CafeObject') -> None: - while not (waiter := _get_random_waiter(self.cafe)): - await sleep(0.1) + waiter = await wait_for_waiter(player_cafe) - waiter.task = create_task(waiter.clean_table(server, table)) - waiter.task.add_done_callback(waiter.remove_task) + if not waiter: + table.busy = False + # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") + await self.leave(server, player_cafe) + return - await waiter.task + waiter.task = create_task(waiter.serve_food(server, player_cafe, self.pos)) - async def start(self, server: 'CafeServer') -> None: - self.task = current_task() - self.task.add_done_callback(self.remove_task) + # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") - await self._spawn(server) + time_to_wait = get_time(player_cafe, waiter.counter_pos, self.pos) - await sleep(2) + # print(f"[CUSTOMER] Waiter started to serve to: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") + # print(f"[CUSTOMER] Time to waiter arrive to: {self.id} is: {time_to_wait}s") - table = _get_random_table(self.cafe) - if not table: - try: - print('Waiting for chair') - table = await wait_for(self._wait_for_table(), timeout=10) - except TimeoutError: - print('No chair, leaving') - await self.leave(server) - return - else: - table.busy = True + await sleep(time_to_wait) - chair = table.connected_chair + waiter.pos = self.pos - await self.go_to_chair(server, chair) + # print(f"[CUSTOMER] Waiter arrived to: {self.id} Waiter pos: {waiter.pos}") - walking_time = get_time(self.cafe, chair) - print(f'Calculated time: {walking_time}') - await sleep(walking_time) + waiter.task = create_task(waiter.go_to_counter(server, player_cafe)) + waiter.task.add_done_callback(waiter.remove_task) - await self.sit_down(server, chair) + # print(f"[CUSTOMER] The waiter is going to a counter who served: {self.id} Waiter: {waiter.avatar.username}") - try: - await wait_for(self._wait_for_food(server), timeout=25) - except TimeoutError: - print('No food, leaving') - await self.leave(server) - table.busy = False - return + await self.eat(server, player_cafe) - await self.eat(server, chair) await sleep(25) - await self.leave(server) + # print(f"[CUSTOMER] Task finished! Leaving now: {self.id}") + + await self.leave(server, player_cafe) + + create_task(wait_for_cleaning(server, player_cafe, table)) + + async def spawn(self, server, player_cafe) -> None: + self.task = current_task() + + insert_info = '+'.join(map(str, [ + str(self.id), # Customer ID + Action.INSERT # Action type (0: Insert) + ])) + + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await self._spawn(server, player_cafe) + + for user in player_cafe.clients: + await server.send_response(user, response_insert) - await self.wait_for_cleaning(self, server, table) + async def _spawn(self, server, player_cafe) -> None: + # TODO: Add favourite dish + spawn_info = '+'.join(map(str, [ + self.id, # Customer ID + 0, # NPC type (0: Customer) + 0, # Favourite dish + -1, # Dish ID + 0, # Is thirsty + self.avatar.to_string(True) # Avatar + ])) + + response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) - async def go_to_chair(self, server: 'CafeServer', chair: 'CafeObject') -> None: + self.pos = player_cafe.get_start_pos() + + for user in player_cafe.clients: + await server.send_response(user, response_spawn) + + async def go_to_chair(self, server, player_cafe, chair) -> None: + self.task = current_task() self.pos = chair.pos response_info = '+'.join(map(str, [ - self.id, # Customer ID + self.id, # Customer ID Action.WALK_TO_CHAIR, # Action type (1: Walk to chair) - chair.pos[0], # X - chair.pos[1] # Y + chair.pos[0], # X + chair.pos[1] # Y ])) response = ExtensionResponse('nac', '-1', '0', response_info) - for user in self.cafe.clients: + for user in player_cafe.clients: await server.send_response(user, response) - async def sit_down(self, server: 'CafeServer', chair: 'CafeObject') -> None: + async def sit_down(self, server, player_cafe) -> None: response_info = '+'.join(map(str, [ - self.id, # Customer ID + self.id, # Customer ID Action.SIT_DOWN, # Action type (2: Sit down) - chair.pos[0], # X - chair.pos[1] # Y + self.pos[0], # X + self.pos[1] # Y ])) response = ExtensionResponse('nac', '-1', '0', response_info) - for user in self.cafe.clients: + for user in player_cafe.clients: await server.send_response(user, response) - async def eat(self, server: 'CafeServer', chair: 'CafeObject') -> None: + async def eat(self, server, player_cafe) -> None: response_info = '+'.join(map(str, [ - self.id, # Customer ID - Action.EAT, # Action type (2: Sit down) - chair.pos[0], # X - chair.pos[1] # Y + self.id, # Customer ID + Action.EAT, # Action type (3: Eat) + self.pos[0], # X + self.pos[1] # Y ])) response = ExtensionResponse('nac', '-1', '0', response_info) - for user in self.cafe.clients: + for user in player_cafe.clients: await server.send_response(user, response) - async def leave(self, server: 'CafeServer') -> None: - response_info = '+'.join(map(str, [ - self.id, # Customer ID + async def leave(self, server, player_cafe) -> None: + insert_info = '+'.join(map(str, [ + str(self.id), # Customer ID Action.LEAVE # Action type (4: Leave) ])) - response = ExtensionResponse('nac', '-1', '0', response_info) - - self.cafe.customers.remove(self) + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - self.pos = self.cafe.get_start_pos() + player_cafe.customers.remove(self) - for user in self.cafe.clients: - await server.send_response(user, response) + for user in player_cafe.clients: + await server.send_response(user, response_insert) def remove_task(self, _: 'Task' = None): self.task = None + + +async def spawn_cycle(server, cafe) -> None: + while True: + new_id = cafe.customers[-1].id + 1 if cafe.customers else 100 + new_customer = Customer(new_id) + cafe.customers.append(new_customer) + + new_customer.task = create_task(new_customer.start(server, cafe)) + new_customer.task.add_done_callback(new_customer.remove_task) + + await sleep(randint(10, 20)) diff --git a/objects/player.py b/objects/player.py index 277f733..5f54baf 100644 --- a/objects/player.py +++ b/objects/player.py @@ -5,7 +5,6 @@ if TYPE_CHECKING: from objects.cafe import Cafe - from asyncio import Task class Player: @@ -31,8 +30,6 @@ def __init__(self): self.mastery: dict[int, int] = {num: 0 for num in range(1201, 1256)} - self.customer_cycle: Optional['Task'] = None - def build_mastery(self) -> str: pairs = [] for item, amount in self.mastery.items(): diff --git a/objects/waiter.py b/objects/waiter.py index 9e60fd1..f2922b3 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -1,28 +1,32 @@ -from enum import Enum -from asyncio import sleep, current_task, gather -from random import randint, choice +import time +from asyncio import current_task, sleep, Task, gather +from random import choice from typing import TYPE_CHECKING, Optional -from response import ExtensionResponse -from objects.avatar import Avatar -from objects.cafe_object import ObjectType -from objects.customer import Customer + from astar import AStar +from objects.avatar import Avatar +from objects.cafe_object import ObjectType, CafeObject +from response import ExtensionResponse if TYPE_CHECKING: - from asyncio import Task from server import CafeServer - from objects.cafe import Cafe - from objects.cafe_object import CafeObject + from asyncio import StreamWriter -def _get_random_avatar(name: str, gender: int) -> Avatar: - skin_color = randint(0, 7) - hair_color = randint(0, 14) +class Priority: + CLEANING = 0 + BOTH = 50 + SERVING = 100 - return Avatar(name, gender, skin_color, 0, hair_color, 0) +class Action: + INSERT = 0 + MOVE_TO_COUNTER = 5 + FEED = 6 + CLEAN = 7 -def _get_random_counter(cafe: 'Cafe') -> 'CafeObject': + +def get_random_counter(cafe) -> 'CafeObject': counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER] counters_with_food = [counter for counter in counters if counter.dish_id != -1] @@ -31,228 +35,206 @@ def _get_random_counter(cafe: 'Cafe') -> 'CafeObject': return choice(counters) -class Priority(Enum): - CLEANING = 0 - SERVING = 1 - BOTH = 2 +def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: + cafe_size = cafe.size + obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] + for i in range(cafe_size): + obstacles[i][0] = 1 + obstacles[0][i] = 1 + for obj in cafe.objects: + obstacles[obj.pos[0]][obj.pos[1]] = 1 -class Action: - INSERT = 0 - MOVE_TO_COUNTER = 5 - FEED = 6 - CLEAN = 7 - + obstacles[start_pos[0]][start_pos[1]] = 0 + obstacles[end_pos[0]][end_pos[1]] = 0 -async def spawn_multiple(server: 'CafeServer', cafe: 'Cafe') -> None: - waiter_responses = [] - for waiter in cafe.waiters: - waiter.task = current_task() - waiter.task.add_done_callback(waiter.remove_task) + path = AStar(obstacles, start_pos, tuple(end_pos)).solve() - waiter_responses.append('+'.join(map(str, [ - waiter.id, # Waiter ID - 1, # NPC type (1: Waiter) - 0, # Favourite (unnecessary for waiters) - -1, # Dish ID (unnecessary for waiters) - waiter.avatar.to_string(True) # Avatar - ]))) + if path: + distance = len(path) - 1 + time = distance / 2.30 # WAITERS ARE FASTER THAN CUSTOMERS (40->45) - response_add = ExtensionResponse('nav', '-1', '0', *waiter_responses) + # print(f'[WAITER-ASTAR]: Start: {start_pos} End: {end_pos}') + # print(f'[WAITER-ASTAR]: Time = {time}s') + # print(f'[WAITER-ASTAR]: Distance = {distance}') + # print(f'[WAITER-ASTAR]: Path = {path}') + else: + time = -1 - for user in cafe.clients: - await server.send_response(user, response_add) + return time - for waiter in cafe.waiters: - insert_info = '+'.join(map(str, [ - waiter.id, # Waiter ID - Action.INSERT # Action type (0: insert) - ])) - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - await server.send_response(user, response_insert) +async def spawn_waiters(server, player_cafe) -> None: + await gather(*[waiter.spawn(server, player_cafe) for waiter in player_cafe.waiters]) await sleep(2) - for user in cafe.clients: - await gather(*[waiter.go_to_counter(server) for waiter in cafe.waiters]) + await gather(*[waiter.go_to_counter(server, player_cafe, + get_random_counter(player_cafe), True) for waiter in player_cafe.waiters]) class Waiter: - def __init__(self, waiter_id: int, name: str, gender: str, cafe: 'Cafe', priority: Priority = Priority.BOTH, avatar: Avatar = None): + def __init__(self, waiter_id: int, name: str, gender: int, priority: int, + avatar: Avatar = None): self.id: int = waiter_id self.name: str = name self.gender: int = gender - self.priority: Priority = priority - self.avatar: Avatar = avatar if avatar else _get_random_avatar(name, gender) + self.priority = priority + self.avatar: Avatar = avatar if avatar else Avatar.get_random_avatar(name, gender) self.pos: list[int] = [0, 0] + self.counter_pos: list[int] = [0, 0] - self.cafe: 'Cafe' = cafe - self.task: Optional['Task'] = None - - async def _spawn(self, server: 'CafeServer') -> None: - self.pos = self.cafe.get_start_pos() + self.dish = -1 - add_info = '+'.join(map(str, [ - self.id, # Waiter ID - 1, # NPC type (1: Waiter) - 0, # Favourite (unnecessary for waiters) - -1, # Dish ID (unnecessary for waiters) - self.avatar.to_string(True) # Avatar - ])) + self.task: Optional['Task'] = None + async def spawn(self, server, player_cafe) -> None: insert_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.INSERT # Action type (0: insert) + self.id, # Waiter ID + Action.INSERT # Action type (0: Insert) ])) - response_add = ExtensionResponse('nav', '-1', '0', add_info) response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - for user in self.cafe.clients: - await server.send_response(user, response_add) + await self._spawn(server, player_cafe) + + for user in player_cafe.clients: await server.send_response(user, response_insert) - async def _feed(self, server: 'CafeServer', customer: 'Customer'): - self.pos = customer.pos + async def _spawn(self, server, player_cafe) -> None: + self.pos = player_cafe.get_start_pos() - feed_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.FEED, # Action type (6: feed) - customer.pos[0], # Customer X - customer.pos[1], # Customer Y + add_info = '+'.join(map(str, [ + self.id, # Waiter ID + 1, # NPC type (1: Waiter) + self.priority, # Priority + -1, # Dish ID (unnecessary for waiters) + self.avatar.to_string(True), # Avatar ])) - response = ExtensionResponse('nac', '-1', '0', feed_info) + response_add = ExtensionResponse('nav', '-1', '0', add_info) - for user in self.cafe.clients: - await server.send_response(user, response) + for user in player_cafe.clients: + await server.send_response(user, response_add) - async def _clean(self, server: 'CafeServer', chair: 'CafeObject') -> None: - self.pos = chair.pos + async def go_to_counter(self, server, player_cafe, counter=None, + need_task: bool = False): + if need_task: + self.task = current_task() + self.task.add_done_callback(self.remove_task) - info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.CLEAN, # Action type (7: clean) - chair.pos[0], # Customer X - chair.pos[1], # Customer Y + if not counter: + counter = get_random_counter(player_cafe) + + response_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.MOVE_TO_COUNTER, # Action type (5: go to counter) + counter.pos[0], # Counter X + counter.pos[1] # Counter Y ])) - response = ExtensionResponse('nac', '-1', '0', info) + response = ExtensionResponse('nac', '-1', '0', response_info) - for user in self.cafe.clients: + for user in player_cafe.clients: await server.send_response(user, response) - @staticmethod - def _get_time(cafe: 'Cafe', pos_start: list[int], pos_end: list[int]) -> float: - cafe_size = cafe.size - obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] + self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) - for i in range(cafe_size): - obstacles[i][0] = 1 - obstacles[0][i] = 1 + walking_time = get_time(player_cafe, self.pos, counter.pos) - for obj in cafe.objects: - obstacles[obj.pos[0]][obj.pos[1]] = 1 + await sleep(walking_time + 1) - obstacles[pos_start[0]][pos_start[1]] = 0 - obstacles[pos_end[0]][pos_end[1]] = 0 + self.pos = counter.pos - path = AStar(obstacles, pos_start, tuple(pos_end)).solve() - distance = len(path) - 1 - time = distance / 1.95 + async def serve_food(self, server, player_cafe, customer_pos): + counter = player_cafe.get_object(int(self.pos[0]), int(self.pos[1])) - return time + if not counter.dish_amount >= 1 or counter.dish_id == -1: + await self.go_to_counter(server, player_cafe) + counter = player_cafe.get_object(int(self.pos[0]), int(self.pos[1])) - async def clean_table(self, server: 'CafeServer', table: 'CafeObject') -> None: - await self._clean(server, table.connected_chair) + self.dish = counter.dish_id - time_to_wait = self._get_time(self.cafe, self.pos, table.connected_chair.pos) - await sleep(time_to_wait + 2) + counter.dish_amount -= 1 - table.busy = False + if counter.dish_amount <= 0: + counter.dish_id = -1 + # # print(f"[WAITER] Counter is empty") + + # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") + # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") + + response_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.FEED, # Action type (6: feed) + customer_pos[0], # Customer X + customer_pos[1] # Customer Y + ])) - counter = await self.go_to_counter(server) + response = ExtensionResponse('nac', '-1', '0', response_info) - time_to_wait = self._get_time(self.cafe, self.pos, counter.pos) - await sleep(time_to_wait + 2) + for user in player_cafe.clients: + await server.send_response(user, response) - async def go_to_counter(self, server: 'CafeServer', counter_pos: Optional[list[int]] = None) -> 'CafeObject': - counter = self.cafe.get_object(*counter_pos) if counter_pos else _get_random_counter(self.cafe) + time_to_wait = get_time(player_cafe, self.counter_pos, customer_pos) + await sleep(time_to_wait) - self.pos = counter.pos + async def clean_table(self, server, player_cafe, table): + # print(f"[WAITER] Waiter {self.avatar.username} is assigned to clean table {table.pos}") response_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.MOVE_TO_COUNTER, # Action type (5: go to counter) - counter.pos[0], # Counter X - counter.pos[1] # Counter Y + self.id, # Waiter ID + Action.CLEAN, # Action type (7: clean) + table.connected_chair.pos[0], # Chair X + table.connected_chair.pos[1] # Chair Y ])) + time_to_walk = get_time(player_cafe, self.pos, table.connected_chair.pos) + response = ExtensionResponse('nac', '-1', '0', response_info) - for user in self.cafe.clients: + for user in player_cafe.clients: await server.send_response(user, response) - time_to_wait = self._get_time(self.cafe, self.cafe.get_start_pos(), counter.pos) - await sleep(time_to_wait + 1) - - return counter + await sleep(time_to_walk) - def remove_task(self, _: 'Task' = None): - self.task = None + # print(f"[WAITER] Waiter {self.avatar.username} is started to clean the table: {table.pos}") - async def spawn(self, server: 'CafeServer') -> None: - self.task = current_task() - self.task.add_done_callback(self.remove_task) + self.pos = player_cafe.get_shortest_pos(self.pos, table.connected_chair.pos) - await self._spawn(server) await sleep(2) - counter = await self.go_to_counter(server) - async def start_serving_for(self, server: 'CafeServer', customer: 'Customer') -> None: - counter = self.cafe.get_object(*self.pos) - counter.dish_amount -= 1 + # print(f"[WAITER] Waiter is cleaned the table: {1 + 1}") - if counter.dish_amount == 0: - counter.dish_id = -1 + # print(f"[WAITER] Table cleaning finished, going back...") - await self._feed(server, customer) + table.busy = False + + await self.go_to_counter(server, player_cafe) - time_to_wait = self._get_time(self.cafe, self.pos, customer.pos) - await sleep(time_to_wait + 2) + def remove_task(self, _: 'Task' = None): + # print(f"[WAITER] Waiter: {self.avatar.username} Task finished: {self.task.get_coro()}") + self.task = None def to_dict(self): waiter_dict = { 'waiter_id': self.id, 'name': self.name, 'gender': self.gender, - 'priority': self.priority.value, + 'priority': self.priority, 'avatar': self.avatar.to_string() } return waiter_dict @classmethod - def from_dict(cls, waiter_dict: dict, cafe: 'Cafe') -> 'Waiter': + def from_dict(cls, waiter_dict: dict) -> 'Waiter': waiter = Waiter( waiter_dict.get('waiter_id'), waiter_dict.get('name'), waiter_dict.get('gender'), - cafe, - Priority(waiter_dict.get('priority')), + waiter_dict.get('priority'), Avatar.from_string(waiter_dict.get('avatar')) ) return waiter - - @classmethod - def create_starting_waiter(cls, cafe) -> 'Waiter': - gender = randint(1, 2) - - if gender == 2: - name = choice(["Oskar", "James", "Jeffrey", "Tom", "William"]) - else: - name = choice(["Jane", "Lucy", "Emma", "Stacey", "Becky"]) - - return cls(0, name, gender, cafe) From b27763d604205f127d22a14d3ed629cba6da2a09 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:33:42 +0200 Subject: [PATCH 02/43] Updated the spawncycle, added back what was removed --- commands/edi.py | 8 ++++++++ commands/jca.py | 2 +- objects/customer.py | 4 ++-- objects/waiter.py | 4 +++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/commands/edi.py b/commands/edi.py index ed76b60..9e4c54a 100644 --- a/commands/edi.py +++ b/commands/edi.py @@ -1,4 +1,7 @@ from typing import TYPE_CHECKING + +from objects.customer import spawn_cycle +from objects.waiter import spawn_waiters from response import ExtensionResponse from asyncio import create_task @@ -17,6 +20,11 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) case 0: player.cafe.connect_chairs() + create_task(spawn_waiters(server, player.cafe)) + + print(f'Created a new customer cycle\nUsed this player\'s Cafe: {player.avatar.username}') + player.cafe.customer_cycle = create_task(spawn_cycle(server, player.cafe)) + case 1: for waiter in player.cafe.waiters: if waiter.task: diff --git a/commands/jca.py b/commands/jca.py index 7b2acf3..d509520 100644 --- a/commands/jca.py +++ b/commands/jca.py @@ -32,7 +32,7 @@ async def handle_jca(server: 'CafeServer', client: 'StreamWriter', *params: str) await handle_ein(server, client, *params) if not wanted_player.cafe.customer_cycle: - print(f'Created a new customer cycle\nUsed this player\'s Cafe: f{wanted_player.avatar.username}') + print(f'Created a new customer cycle\nUsed this player\'s Cafe: {wanted_player.avatar.username}') wanted_player.cafe.customer_cycle = create_task(spawn_cycle(server, wanted_player.cafe)) else: customers = wanted_player.cafe.customers diff --git a/objects/customer.py b/objects/customer.py index 6b005e8..0d513b3 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -291,11 +291,11 @@ def remove_task(self, _: 'Task' = None): async def spawn_cycle(server, cafe) -> None: while True: + await sleep(randint(5, 20)) + new_id = cafe.customers[-1].id + 1 if cafe.customers else 100 new_customer = Customer(new_id) cafe.customers.append(new_customer) new_customer.task = create_task(new_customer.start(server, cafe)) new_customer.task.add_done_callback(new_customer.remove_task) - - await sleep(randint(10, 20)) diff --git a/objects/waiter.py b/objects/waiter.py index f2922b3..22c4d3a 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -140,6 +140,8 @@ async def go_to_counter(self, server, player_cafe, counter=None, self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) + # print(f"[WAITER] Waiter should staying at: {self.counter_pos}") + walking_time = get_time(player_cafe, self.pos, counter.pos) await sleep(walking_time + 1) @@ -189,7 +191,7 @@ async def clean_table(self, server, player_cafe, table): table.connected_chair.pos[1] # Chair Y ])) - time_to_walk = get_time(player_cafe, self.pos, table.connected_chair.pos) + time_to_walk = get_time(player_cafe, self.counter_pos, table.connected_chair.pos) response = ExtensionResponse('nac', '-1', '0', response_info) From 82b5a83eaf415de0794e7aaf69d2fa1392bc821f Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:44:24 +0200 Subject: [PATCH 03/43] =?UTF-8?q?Customer=20cycle=20is=20binded=20to=20the?= =?UTF-8?q?=20Caf=C3=A9,=20updated=20SGC=20to=20work=20with=20that?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commands/sgc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/commands/sgc.py b/commands/sgc.py index 5f8ef68..60b5fa0 100644 --- a/commands/sgc.py +++ b/commands/sgc.py @@ -25,9 +25,9 @@ async def handle_sgc(server: 'CafeServer', client: 'StreamWriter', cmd: str, *pa if waiter.task: waiter.task.cancel() - if player.customer_cycle: - player.customer_cycle.cancel() - player.customer_cycle = None + if player.cafe.customer_cycle: + player.cafe.customer_cycle.cancel() + player.cafe.customer_cycle = None for customer in player.cafe.customers: if customer.task: From 5911e6ee8af18b09b7dae9f020afcd25caf29165 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:05:07 +0200 Subject: [PATCH 04/43] =?UTF-8?q?Forgot=20to=20remove=20the=20cycle=20from?= =?UTF-8?q?=20the=20Caf=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index 0086471..e2ee54e 100644 --- a/server.py +++ b/server.py @@ -98,9 +98,9 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre if waiter.task: waiter.task.cancel() - if player.customer_cycle: - player.customer_cycle.cancel() - player.customer_cycle = None + if player.cafe.customer_cycle: + player.cafe.customer_cycle.cancel() + player.cafe.customer_cycle = None for table in player.cafe.available_tables: table.busy = False From c45b79e59c2f83cfb5dd9dd2511252eb19c01c43 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:33:08 +0200 Subject: [PATCH 05/43] =?UTF-8?q?Added=20client=20param=20instead=20of=20t?= =?UTF-8?q?he=20caf=C3=A9.=20Customers=20give=20rating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commands/edi.py | 4 ++-- commands/jca.py | 5 ++-- objects/customer.py | 56 ++++++++++++++++++++++++++++++--------------- objects/waiter.py | 11 +++++---- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/commands/edi.py b/commands/edi.py index 9e4c54a..21f116b 100644 --- a/commands/edi.py +++ b/commands/edi.py @@ -20,10 +20,10 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) case 0: player.cafe.connect_chairs() - create_task(spawn_waiters(server, player.cafe)) + create_task(spawn_waiters(server, client)) print(f'Created a new customer cycle\nUsed this player\'s Cafe: {player.avatar.username}') - player.cafe.customer_cycle = create_task(spawn_cycle(server, player.cafe)) + player.cafe.customer_cycle = create_task(spawn_cycle(server, client)) case 1: for waiter in player.cafe.waiters: diff --git a/commands/jca.py b/commands/jca.py index d509520..8ab4458 100644 --- a/commands/jca.py +++ b/commands/jca.py @@ -33,9 +33,8 @@ async def handle_jca(server: 'CafeServer', client: 'StreamWriter', *params: str) if not wanted_player.cafe.customer_cycle: print(f'Created a new customer cycle\nUsed this player\'s Cafe: {wanted_player.avatar.username}') - wanted_player.cafe.customer_cycle = create_task(spawn_cycle(server, wanted_player.cafe)) + wanted_player.cafe.customer_cycle = create_task(spawn_cycle(server, wanted_player.client)) else: customers = wanted_player.cafe.customers - create_task(spawn_waiters(server, wanted_player.cafe)) - + create_task(spawn_waiters(server, wanted_player.client)) diff --git a/objects/customer.py b/objects/customer.py index 0d513b3..9e66cab 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -124,8 +124,11 @@ def __init__(self, customer_id: int): self.dish = -1 - async def start(self, server, player_cafe) -> None: - await self.spawn(server, player_cafe) + async def start(self, server, client) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + await self.spawn(server, player.cafe) # print(f"[CUSTOMER] New spawned: {self.id}") @@ -133,11 +136,15 @@ async def start(self, server, player_cafe) -> None: # print(f"[CUSTOMER] Started waiting for table: {self.id}") - table = await wait_for_table(player_cafe) + table = await wait_for_table(player.cafe) if not table: # print(f"[CUSTOMER] No free table found, leaving: {self.id}") - await self.leave(server, player_cafe) + if player.cafe.rating > 10: + player.cafe.rating -= 2 + server.db.update_cafe(player.id, rating=player.cafe.rating) + + await self.leave(server, player.cafe) return # print(f"[CUSTOMER] Got a free table: {self.id}") @@ -145,33 +152,37 @@ async def start(self, server, player_cafe) -> None: table.busy = True chair = table.connected_chair - await self.go_to_chair(server, player_cafe, chair) + await self.go_to_chair(server, player.cafe, chair) - walking_time = get_time(player_cafe, player_cafe.player_start, chair.pos) + walking_time = get_time(player.cafe, player.cafe.player_start, chair.pos) # print(f"[CUSTOMER] Started going to table: {self.id} pos: {chair.pos} walking time: {walking_time}s") await sleep(walking_time) - await self.sit_down(server, player_cafe) + await self.sit_down(server, player.cafe) # print(f"[CUSTOMER] Sat down: {self.id}") # print(f"[CUSTOMER] Started waiting for a Waiter: {self.id}") - waiter = await wait_for_waiter(player_cafe) + waiter = await wait_for_waiter(player.cafe) if not waiter: table.busy = False # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") - await self.leave(server, player_cafe) + if player.cafe.rating > 10: + player.cafe.rating -= 2 + server.db.update_cafe(player.id, rating=player.cafe.rating) + + await self.leave(server, player.cafe) return - waiter.task = create_task(waiter.serve_food(server, player_cafe, self.pos)) + waiter.task = create_task(waiter.serve_food(server, player.cafe, self.pos)) # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") - time_to_wait = get_time(player_cafe, waiter.counter_pos, self.pos) + time_to_wait = get_time(player.cafe, waiter.counter_pos, self.pos) # print(f"[CUSTOMER] Waiter started to serve to: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") # print(f"[CUSTOMER] Time to waiter arrive to: {self.id} is: {time_to_wait}s") @@ -182,20 +193,24 @@ async def start(self, server, player_cafe) -> None: # print(f"[CUSTOMER] Waiter arrived to: {self.id} Waiter pos: {waiter.pos}") - waiter.task = create_task(waiter.go_to_counter(server, player_cafe)) + waiter.task = create_task(waiter.go_to_counter(server, player.cafe)) waiter.task.add_done_callback(waiter.remove_task) # print(f"[CUSTOMER] The waiter is going to a counter who served: {self.id} Waiter: {waiter.avatar.username}") - await self.eat(server, player_cafe) + await self.eat(server, player.cafe) await sleep(25) # print(f"[CUSTOMER] Task finished! Leaving now: {self.id}") - await self.leave(server, player_cafe) + await self.leave(server, player.cafe) - create_task(wait_for_cleaning(server, player_cafe, table)) + if player.cafe.rating < 1000: + player.cafe.rating += 1 + server.db.update_cafe(player.id, rating=player.cafe.rating) + + create_task(wait_for_cleaning(server, player.cafe, table)) async def spawn(self, server, player_cafe) -> None: self.task = current_task() @@ -289,13 +304,16 @@ def remove_task(self, _: 'Task' = None): self.task = None -async def spawn_cycle(server, cafe) -> None: +async def spawn_cycle(server, client) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + while True: await sleep(randint(5, 20)) - new_id = cafe.customers[-1].id + 1 if cafe.customers else 100 + new_id = player.cafe.customers[-1].id + 1 if player.cafe.customers else 100 new_customer = Customer(new_id) - cafe.customers.append(new_customer) + player.cafe.customers.append(new_customer) - new_customer.task = create_task(new_customer.start(server, cafe)) + new_customer.task = create_task(new_customer.start(server, client)) new_customer.task.add_done_callback(new_customer.remove_task) diff --git a/objects/waiter.py b/objects/waiter.py index 22c4d3a..b956114 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -64,13 +64,16 @@ def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: return time -async def spawn_waiters(server, player_cafe) -> None: - await gather(*[waiter.spawn(server, player_cafe) for waiter in player_cafe.waiters]) +async def spawn_waiters(server, client) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + await gather(*[waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters]) await sleep(2) - await gather(*[waiter.go_to_counter(server, player_cafe, - get_random_counter(player_cafe), True) for waiter in player_cafe.waiters]) + await gather(*[waiter.go_to_counter(server, player.cafe, + get_random_counter(player.cafe), True) for waiter in player.cafe.waiters]) class Waiter: From c0f6162cfbe4e8f77143332ac38b0f028c480c29 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:03:39 +0200 Subject: [PATCH 06/43] Rating updated, customers give cash, issues fixed Issue 1: Waiter teleport issue (fixed) Caused: Customer time_to_wait sleep for waiting to the waiter, its uses the old waiter.counter_pos, and uses the customers get_time, and the customers are slower. Fixed with: awaiting the serving task for the waiter. Issue 2: If the player clears the counter wheres the waiter is going (after the waiter wants to go to a new counter, because the current counter is empty) they starts serving an empty dish, because no logic implemented to prevent this. This logic was added, and if the serve_food returns true, its means the new counter have food, so they will serve it. If its false, then the customer leaves, because the player clean that counter. We can fix it, but we need to change the logic for the customer, to don't all tasks in one function. Like in the waiters. --- objects/customer.py | 68 +++++++++++++++++++++++++++++++++------------ objects/waiter.py | 47 ++++++++++++++++++------------- utils/items.py | 18 +++++++++++- 3 files changed, 95 insertions(+), 38 deletions(-) diff --git a/objects/customer.py b/objects/customer.py index 9e66cab..2f0acac 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -8,6 +8,7 @@ from objects.cafe_object import CafeObject, ObjectType from objects.waiter import Waiter from response import ExtensionResponse +from utils.items import get_dish_info if TYPE_CHECKING: from server import CafeServer @@ -140,9 +141,12 @@ async def start(self, server, client) -> None: if not table: # print(f"[CUSTOMER] No free table found, leaving: {self.id}") - if player.cafe.rating > 10: + if player.cafe.rating >= 12: player.cafe.rating -= 2 server.db.update_cafe(player.id, rating=player.cafe.rating) + elif player.cafe.rating == 11: + player.cafe.rating -= 1 + server.db.update_cafe(player.id, rating=player.cafe.rating) await self.leave(server, player.cafe) return @@ -171,9 +175,12 @@ async def start(self, server, client) -> None: if not waiter: table.busy = False # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") - if player.cafe.rating > 10: + if player.cafe.rating >= 12: player.cafe.rating -= 2 server.db.update_cafe(player.id, rating=player.cafe.rating) + elif player.cafe.rating == 11: + player.cafe.rating -= 1 + server.db.update_cafe(player.id, rating=player.cafe.rating) await self.leave(server, player.cafe) return @@ -182,35 +189,60 @@ async def start(self, server, client) -> None: # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") - time_to_wait = get_time(player.cafe, waiter.counter_pos, self.pos) - # print(f"[CUSTOMER] Waiter started to serve to: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") # print(f"[CUSTOMER] Time to waiter arrive to: {self.id} is: {time_to_wait}s") - await sleep(time_to_wait) + await waiter.task + if waiter.task and not waiter.dish == -1: + self.dish = waiter.dish + + # print(f"[CUSTOMER] Waiter arrived to: {self.id} Waiter pos: {waiter.pos}") + + waiter.task = create_task(waiter.go_to_counter(server, player.cafe)) + waiter.task.add_done_callback(waiter.remove_task) + + # print(f"[CUSTOMER] The waiter is going to a counter who served: {self.id} Waiter: {waiter.avatar.username}") + + await self.eat(server, player.cafe) + + await sleep(25) - waiter.pos = self.pos + # print(f"[CUSTOMER] Task finished! Leaving now: {self.id}") - # print(f"[CUSTOMER] Waiter arrived to: {self.id} Waiter pos: {waiter.pos}") + dish_info = get_dish_info(self.dish) + income = dish_info.get('income') - waiter.task = create_task(waiter.go_to_counter(server, player.cafe)) - waiter.task.add_done_callback(waiter.remove_task) + player.cash += income - # print(f"[CUSTOMER] The waiter is going to a counter who served: {self.id} Waiter: {waiter.avatar.username}") + server.db.update_player(player.id, cash=player.cash) - await self.eat(server, player.cafe) + await self.leave(server, player.cafe) - await sleep(25) + if player.cafe.rating < 1000: + player.cafe.rating += 1 + server.db.update_cafe(player.id, rating=player.cafe.rating) - # print(f"[CUSTOMER] Task finished! Leaving now: {self.id}") + create_task(wait_for_cleaning(server, player.cafe, table)) - await self.leave(server, player.cafe) + # FALLBACK TO DON'T SERVE FROM EMPTY COUNTER + # Maybe we can do a better logic, but this caused by the player, who removed the all dishes + else: + table.busy = False - if player.cafe.rating < 1000: - player.cafe.rating += 1 - server.db.update_cafe(player.id, rating=player.cafe.rating) + # print(f"[CUSTOMER] Waiter: {waiter.avatar.username} failed to serve food to customer {self.id}." + # f" REASON: (COUNTER DISH ID IS -1)") + # print(f"[CUSTOMER] Waiter task: {waiter.task}") - create_task(wait_for_cleaning(server, player.cafe, table)) + waiter.task = None + + if player.cafe.rating >= 12: + player.cafe.rating -= 2 + server.db.update_cafe(player.id, rating=player.cafe.rating) + elif player.cafe.rating == 11: + player.cafe.rating -= 1 + server.db.update_cafe(player.id, rating=player.cafe.rating) + + await self.leave(server, player.cafe) async def spawn(self, server, player_cafe) -> None: self.task = current_task() diff --git a/objects/waiter.py b/objects/waiter.py index b956114..e590dc1 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -151,38 +151,47 @@ async def go_to_counter(self, server, player_cafe, counter=None, self.pos = counter.pos - async def serve_food(self, server, player_cafe, customer_pos): + async def serve_food(self, server, player_cafe, customer_pos) -> bool: counter = player_cafe.get_object(int(self.pos[0]), int(self.pos[1])) if not counter.dish_amount >= 1 or counter.dish_id == -1: await self.go_to_counter(server, player_cafe) counter = player_cafe.get_object(int(self.pos[0]), int(self.pos[1])) - self.dish = counter.dish_id + if not counter.dish_id == -1: + self.dish = counter.dish_id + counter.dish_amount -= 1 - counter.dish_amount -= 1 + if counter.dish_amount <= 0: + counter.dish_id = -1 + # # print(f"[WAITER] Counter is empty") - if counter.dish_amount <= 0: - counter.dish_id = -1 - # # print(f"[WAITER] Counter is empty") + # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") + # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") - # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") - # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") + response_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.FEED, # Action type (6: feed) + customer_pos[0], # Customer X + customer_pos[1] # Customer Y + ])) - response_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.FEED, # Action type (6: feed) - customer_pos[0], # Customer X - customer_pos[1] # Customer Y - ])) + response = ExtensionResponse('nac', '-1', '0', response_info) - response = ExtensionResponse('nac', '-1', '0', response_info) + for user in player_cafe.clients: + await server.send_response(user, response) - for user in player_cafe.clients: - await server.send_response(user, response) + time_to_wait = get_time(player_cafe, self.counter_pos, customer_pos) + + await sleep(time_to_wait) + + self.pos = customer_pos - time_to_wait = get_time(player_cafe, self.counter_pos, customer_pos) - await sleep(time_to_wait) + return True + # FALLBACK TO DON'T SERVE FROM EMPTY COUNTER + # Maybe we can do a better logic, but this caused by the player, who removed the all dishes + else: + return False async def clean_table(self, server, player_cafe, table): # print(f"[WAITER] Waiter {self.avatar.username} is assigned to clean table {table.pos}") diff --git a/utils/items.py b/utils/items.py index 9776d33..884581a 100644 --- a/utils/items.py +++ b/utils/items.py @@ -38,7 +38,9 @@ def get_dish_info(dish_id: int) -> dict: 'ingredients': ingredients, 'duration': int(wod.attrib.get('duration', 0)), 'amount': int(wod.attrib.get('servings', 0)), - 'xp': int(wod.attrib.get('xp', 0)), + 'level': int(wod.attrib.get('level', 0)), + 'events': int(wod.attrib.get('events', 0)), + 'income': int(wod.attrib.get('incomePerServing', 0)) } return {} @@ -56,3 +58,17 @@ def get_expansion(exp_id: int) -> dict: } return {} + + +def get_ingredient(ing_id: int) -> dict: + root = tree.getroot() + + for wod in root.findall('wod'): + if int(wod.attrib.get('id')) == ing_id: + return { + 'level': int(wod.attrib.get('level', 0)), + 'gold': int(wod.attrib.get('gold', 0)), + 'events': int(wod.attrib.get('events', 0)), + } + + return {} From ac3014f0c34ff5a676110729ba0e406325c2370a Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 07:16:43 +0200 Subject: [PATCH 07/43] Swapped to get luxury value first, then rating --- database.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database.py b/database.py index c18e6e3..a6cca76 100644 --- a/database.py +++ b/database.py @@ -45,14 +45,14 @@ def _create_player_by_params(self, player_args: tuple[int, str]) -> Player: return player def get_cafe(self, player_id: int) -> Cafe: - self.cursor.execute("SELECT owner_name, rating, luxury, expansion_id, tiles, objects, fridge_inv, furniture_inv, waiters FROM cafe WHERE player_id=?", (player_id,)) + self.cursor.execute("SELECT owner_name, luxury, rating, expansion_id, tiles, objects, fridge_inv, furniture_inv, waiters FROM cafe WHERE player_id=?", (player_id,)) cafe_args = self.cursor.fetchone() cafe = Cafe() cafe.user_id = player_id cafe.player_id = player_id cafe.owner_name = cafe_args[0] - cafe.rating = cafe_args[1] - cafe.luxury = cafe_args[2] + cafe.luxury = cafe_args[1] + cafe.update_rating(cafe_args[2]) cafe.expansion_id = cafe_args[3] cafe.size = cafe.expansion_id + 8 cafe.parse_tiles(cafe_args[4]) From 0e0a0d4eef9fb2250c7b1972a17c974320c7abcf Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 07:17:53 +0200 Subject: [PATCH 08/43] =?UTF-8?q?Added=20update=5Frating()=20func=20to=20u?= =?UTF-8?q?pdate=20Caf=C3=A9=20rating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- objects/cafe.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/objects/cafe.py b/objects/cafe.py index c0a67e9..fc130f4 100644 --- a/objects/cafe.py +++ b/objects/cafe.py @@ -249,6 +249,21 @@ def get_nearby_free_spaces(self, object_pos: list[int]): return free_spaces + def update_rating(self, rating: int): + self.rating += rating + if self.rating < 10: + self.rating = 10 + elif self.rating > 1000: + self.rating = 1000 + + # https://github.com/GGR-Devs/Cafe-Main/blob/2911a43b4d0972d2f1386edd782d3382f031f36d/com/goodgamestudios/cafe/model/components/CafeLevelData.as#L195 + # The game always check if the rating is smaller than the luxury_rating. If yes, then its update to this value. + luxury_rating = min(int((1 + 0.05 * self.luxury) * 10), 500) + + print(luxury_rating) + if self.rating < luxury_rating: + self.rating = luxury_rating + @classmethod def get_starting_cafe(cls) -> 'Cafe': cafe = cls() From 8d75f083df5ff6fa77264d18519d0ffa7261f8ab Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:21:56 +0200 Subject: [PATCH 09/43] Spawning back npcs to visitor (client), spawnrate update, rating update --- commands/jca.py | 11 +++--- commands/sgc.py | 18 --------- objects/customer.py | 93 +++++++++++++++++++++++++++++++++------------ objects/waiter.py | 39 ++++++++++++++++--- 4 files changed, 108 insertions(+), 53 deletions(-) diff --git a/commands/jca.py b/commands/jca.py index 8ab4458..62ebf81 100644 --- a/commands/jca.py +++ b/commands/jca.py @@ -1,12 +1,12 @@ from typing import TYPE_CHECKING -from objects.waiter import spawn_waiters +from objects.waiter import spawn_waiters, spawn_waiters_to_client from response import ExtensionResponse from commands.sgc import handle_sgc from commands.ifr import handle_ifr from commands.ein import handle_ein from asyncio import create_task -from objects.customer import spawn_cycle +from objects.customer import spawn_cycle, spawn_customers_to_client if TYPE_CHECKING: from server import CafeServer @@ -34,7 +34,8 @@ async def handle_jca(server: 'CafeServer', client: 'StreamWriter', *params: str) if not wanted_player.cafe.customer_cycle: print(f'Created a new customer cycle\nUsed this player\'s Cafe: {wanted_player.avatar.username}') wanted_player.cafe.customer_cycle = create_task(spawn_cycle(server, wanted_player.client)) - else: - customers = wanted_player.cafe.customers - create_task(spawn_waiters(server, wanted_player.client)) + create_task(spawn_waiters(server, wanted_player.client)) + else: + create_task(spawn_customers_to_client(server, client, wanted_player.cafe)) + create_task(spawn_waiters_to_client(server, client, wanted_player.cafe)) diff --git a/commands/sgc.py b/commands/sgc.py index 60b5fa0..705c618 100644 --- a/commands/sgc.py +++ b/commands/sgc.py @@ -20,24 +20,6 @@ async def handle_sgc(server: 'CafeServer', client: 'StreamWriter', cmd: str, *pa await handle_juq(server, user, client) await handle_juq(server, client, user) - if player.room and player.room != server.marketplace and not player.room.clients: - for waiter in player.cafe.waiters: - if waiter.task: - waiter.task.cancel() - - if player.cafe.customer_cycle: - player.cafe.customer_cycle.cancel() - player.cafe.customer_cycle = None - - for customer in player.cafe.customers: - if customer.task: - customer.task.cancel() - - for table in player.cafe.available_tables: - table.busy = False - - player.room.customers = [] - if cmd == 'jca': existing_player = [player for player in server.players if player.id == int(params[2])] if existing_player: diff --git a/objects/customer.py b/objects/customer.py index 2f0acac..57a3e7c 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -1,6 +1,6 @@ import asyncio from asyncio import create_task, sleep, Task, current_task -from random import randint, choice +from random import randint, choice, uniform from typing import Optional, List, TYPE_CHECKING from astar import AStar @@ -116,6 +116,43 @@ async def wait_for_cleaning(server, cafe, table): await sleep(0.1) +async def spawn_customers_to_client(server, client, wanted_cafe): + for customer in wanted_cafe.customers: + if customer.action not in [Action.LEAVE]: + spawn_info = '+'.join(map(str, [ + customer.id, # Customer ID + 0, # NPC type (0: Customer) + 0, # Favourite dish + -1, # Dish ID + 0, # Is thirsty + customer.avatar.to_string(True) # Avatar + ])) + + action = customer.action + + match customer.action: + # If the customer is walking, sending sit down to visitor client + case Action.WALK_TO_CHAIR: + action = Action.SIT_DOWN + + insert_info = '+'.join(map(str, [ + customer.id, # Customer ID + action, # Action type + customer.pos[0], # X + customer.pos[1] # Y + ])) + + # NAV does not split by '#' + response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) + + # NAC uses split by '#' + # https://github.com/GGR-Devs/Cafe-Main/blob/2911a43b4d0972d2f1386edd782d3382f031f36d/com/goodgamestudios/cafe/commands/NACCommand.as#L29 + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await server.send_response(client, response_spawn) + await server.send_response(client, response_insert) + + class Customer: def __init__(self, customer_id: int): self.id: int = customer_id @@ -124,6 +161,7 @@ def __init__(self, customer_id: int): self.task: Optional[asyncio.Task] = None self.dish = -1 + self.action = Action.INSERT async def start(self, server, client) -> None: address = client.get_extra_info('peername') @@ -141,12 +179,8 @@ async def start(self, server, client) -> None: if not table: # print(f"[CUSTOMER] No free table found, leaving: {self.id}") - if player.cafe.rating >= 12: - player.cafe.rating -= 2 - server.db.update_cafe(player.id, rating=player.cafe.rating) - elif player.cafe.rating == 11: - player.cafe.rating -= 1 - server.db.update_cafe(player.id, rating=player.cafe.rating) + player.cafe.update_rating(-2) + server.db.update_cafe(player.id, rating=player.cafe.rating) await self.leave(server, player.cafe) return @@ -175,12 +209,8 @@ async def start(self, server, client) -> None: if not waiter: table.busy = False # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") - if player.cafe.rating >= 12: - player.cafe.rating -= 2 - server.db.update_cafe(player.id, rating=player.cafe.rating) - elif player.cafe.rating == 11: - player.cafe.rating -= 1 - server.db.update_cafe(player.id, rating=player.cafe.rating) + player.cafe.update_rating(-2) + server.db.update_cafe(player.id, rating=player.cafe.rating) await self.leave(server, player.cafe) return @@ -205,6 +235,8 @@ async def start(self, server, client) -> None: await self.eat(server, player.cafe) + # print(f'[CUSTOMER] Started eating: {self.id} Dish: {self.dish}') + await sleep(25) # print(f"[CUSTOMER] Task finished! Leaving now: {self.id}") @@ -218,9 +250,8 @@ async def start(self, server, client) -> None: await self.leave(server, player.cafe) - if player.cafe.rating < 1000: - player.cafe.rating += 1 - server.db.update_cafe(player.id, rating=player.cafe.rating) + player.cafe.update_rating(1) + server.db.update_cafe(player.id, rating=player.cafe.rating) create_task(wait_for_cleaning(server, player.cafe, table)) @@ -235,12 +266,8 @@ async def start(self, server, client) -> None: waiter.task = None - if player.cafe.rating >= 12: - player.cafe.rating -= 2 - server.db.update_cafe(player.id, rating=player.cafe.rating) - elif player.cafe.rating == 11: - player.cafe.rating -= 1 - server.db.update_cafe(player.id, rating=player.cafe.rating) + player.cafe.update_rating(-2) + server.db.update_cafe(player.id, rating=player.cafe.rating) await self.leave(server, player.cafe) @@ -260,11 +287,10 @@ async def spawn(self, server, player_cafe) -> None: await server.send_response(user, response_insert) async def _spawn(self, server, player_cafe) -> None: - # TODO: Add favourite dish spawn_info = '+'.join(map(str, [ self.id, # Customer ID 0, # NPC type (0: Customer) - 0, # Favourite dish + 0, # Favourite = Waiters priority -1, # Dish ID 0, # Is thirsty self.avatar.to_string(True) # Avatar @@ -281,6 +307,8 @@ async def go_to_chair(self, server, player_cafe, chair) -> None: self.task = current_task() self.pos = chair.pos + self.action = Action.WALK_TO_CHAIR + response_info = '+'.join(map(str, [ self.id, # Customer ID Action.WALK_TO_CHAIR, # Action type (1: Walk to chair) @@ -294,6 +322,8 @@ async def go_to_chair(self, server, player_cafe, chair) -> None: await server.send_response(user, response) async def sit_down(self, server, player_cafe) -> None: + self.action = Action.SIT_DOWN + response_info = '+'.join(map(str, [ self.id, # Customer ID Action.SIT_DOWN, # Action type (2: Sit down) @@ -307,6 +337,8 @@ async def sit_down(self, server, player_cafe) -> None: await server.send_response(user, response) async def eat(self, server, player_cafe) -> None: + self.action = Action.EAT + response_info = '+'.join(map(str, [ self.id, # Customer ID Action.EAT, # Action type (3: Eat) @@ -320,6 +352,8 @@ async def eat(self, server, player_cafe) -> None: await server.send_response(user, response) async def leave(self, server, player_cafe) -> None: + self.action = Action.LEAVE + insert_info = '+'.join(map(str, [ str(self.id), # Customer ID Action.LEAVE # Action type (4: Leave) @@ -341,7 +375,16 @@ async def spawn_cycle(server, client) -> None: player = server.clients[address] while True: - await sleep(randint(5, 20)) + rating = player.cafe.rating + + if rating < 50: + spawn_interval = uniform(5, 15) + elif 50 <= rating < 250: + spawn_interval = uniform(3.5, 8) + else: + spawn_interval = uniform(0.1, 5) + + await sleep(spawn_interval) new_id = player.cafe.customers[-1].id + 1 if player.cafe.customers else 100 new_customer = Customer(new_id) diff --git a/objects/waiter.py b/objects/waiter.py index e590dc1..055eee3 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -1,4 +1,3 @@ -import time from asyncio import current_task, sleep, Task, gather from random import choice from typing import TYPE_CHECKING, Optional @@ -76,6 +75,30 @@ async def spawn_waiters(server, client) -> None: get_random_counter(player.cafe), True) for waiter in player.cafe.waiters]) +async def spawn_waiters_to_client(server, client, wanted_cafe): + for waiter in wanted_cafe.waiters: + add_info = '+'.join(map(str, [ + waiter.id, # Waiter ID + 1, # NPC type (1: Waiter) + waiter.priority, # Priority + -1, # Dish ID (unnecessary for waiters) + waiter.avatar.to_string(True), # Avatar + ])) + + insert_info = '+'.join(map(str, [ + waiter.id, # Waiter ID + waiter.action, # Action type + waiter.pos[0], # X + waiter.pos[1] # Y + ])) + + response_add = ExtensionResponse('nav', '-1', '0', add_info) + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await server.send_response(client, response_add) + await server.send_response(client, response_insert) + + class Waiter: def __init__(self, waiter_id: int, name: str, gender: int, priority: int, avatar: Avatar = None): @@ -88,6 +111,7 @@ def __init__(self, waiter_id: int, name: str, gender: int, priority: int, self.counter_pos: list[int] = [0, 0] self.dish = -1 + self.action = Action.INSERT self.task: Optional['Task'] = None @@ -122,6 +146,8 @@ async def _spawn(self, server, player_cafe) -> None: async def go_to_counter(self, server, player_cafe, counter=None, need_task: bool = False): + self.action = Action.MOVE_TO_COUNTER + if need_task: self.task = current_task() self.task.add_done_callback(self.remove_task) @@ -147,11 +173,13 @@ async def go_to_counter(self, server, player_cafe, counter=None, walking_time = get_time(player_cafe, self.pos, counter.pos) - await sleep(walking_time + 1) + await sleep(walking_time + 0.5) self.pos = counter.pos async def serve_food(self, server, player_cafe, customer_pos) -> bool: + self.action = Action.FEED + counter = player_cafe.get_object(int(self.pos[0]), int(self.pos[1])) if not counter.dish_amount >= 1 or counter.dish_id == -1: @@ -181,11 +209,11 @@ async def serve_food(self, server, player_cafe, customer_pos) -> bool: for user in player_cafe.clients: await server.send_response(user, response) - time_to_wait = get_time(player_cafe, self.counter_pos, customer_pos) + self.pos = customer_pos - await sleep(time_to_wait) + time_to_wait = get_time(player_cafe, self.counter_pos, customer_pos) - self.pos = customer_pos + await sleep(time_to_wait + 0.5) return True # FALLBACK TO DON'T SERVE FROM EMPTY COUNTER @@ -195,6 +223,7 @@ async def serve_food(self, server, player_cafe, customer_pos) -> bool: async def clean_table(self, server, player_cafe, table): # print(f"[WAITER] Waiter {self.avatar.username} is assigned to clean table {table.pos}") + self.action = Action.CLEAN response_info = '+'.join(map(str, [ self.id, # Waiter ID From e63b4be101c85291895f10bf2e540df25fdc388f Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:25:40 +0200 Subject: [PATCH 10/43] Bug fixes, remove chair dishes before saving, etc... --- commands/edi.py | 5 +++ objects/customer.py | 5 +-- objects/waiter.py | 83 ++++++++++++++++++++++++++++++--------------- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/commands/edi.py b/commands/edi.py index 21f116b..afbfb03 100644 --- a/commands/edi.py +++ b/commands/edi.py @@ -38,8 +38,13 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) if customer.task: customer.task.cancel() + player.cafe.customers = [] + for table in player.cafe.available_tables: table.busy = False + chair = table.connected_chair + chair.dish_id = -1 + chair.dish_status = 0 response = ExtensionResponse('edi', '-1', '0', params[1], str(pos[0]), str(pos[1]), '') await server.send_response(client, response) diff --git a/objects/customer.py b/objects/customer.py index 57a3e7c..f1ee09d 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -215,7 +215,7 @@ async def start(self, server, client) -> None: await self.leave(server, player.cafe) return - waiter.task = create_task(waiter.serve_food(server, player.cafe, self.pos)) + waiter.task = create_task(waiter.serve_food(server, player, self.pos)) # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") @@ -225,9 +225,10 @@ async def start(self, server, client) -> None: await waiter.task if waiter.task and not waiter.dish == -1: self.dish = waiter.dish - # print(f"[CUSTOMER] Waiter arrived to: {self.id} Waiter pos: {waiter.pos}") + waiter.dish = -1 + waiter.task = create_task(waiter.go_to_counter(server, player.cafe)) waiter.task.add_done_callback(waiter.remove_task) diff --git a/objects/waiter.py b/objects/waiter.py index 055eee3..0da2157 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -26,12 +26,14 @@ class Action: def get_random_counter(cafe) -> 'CafeObject': - counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER] + counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER and get_time(cafe, cafe.get_start_pos(), + obj.pos) != -1] counters_with_food = [counter for counter in counters if counter.dish_id != -1] - if counters_with_food: - return choice(counters_with_food) - return choice(counters) + if counters: + if counters_with_food: + return choice(counters_with_food) + return choice(counters) def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: @@ -69,10 +71,17 @@ async def spawn_waiters(server, client) -> None: await gather(*[waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters]) + counter = get_random_counter(player.cafe) + + # PATH FOUND TO COUNTER + if counter: + for waiter in player.cafe.waiters: + waiter.counter = counter + await sleep(2) await gather(*[waiter.go_to_counter(server, player.cafe, - get_random_counter(player.cafe), True) for waiter in player.cafe.waiters]) + counter, True) for waiter in player.cafe.waiters]) async def spawn_waiters_to_client(server, client, wanted_cafe): @@ -85,11 +94,20 @@ async def spawn_waiters_to_client(server, client, wanted_cafe): waiter.avatar.to_string(True), # Avatar ])) + counter = waiter.counter + + # PATH FOUND TO COUNTER + if counter is not None: + pos = counter.pos + else: + # PATH NOT FOUND TO COUNTER + pos = wanted_cafe.get_start_pos() + insert_info = '+'.join(map(str, [ waiter.id, # Waiter ID waiter.action, # Action type - waiter.pos[0], # X - waiter.pos[1] # Y + pos[0], # X + pos[1] # Y ])) response_add = ExtensionResponse('nav', '-1', '0', add_info) @@ -109,6 +127,7 @@ def __init__(self, waiter_id: int, name: str, gender: int, priority: int, self.avatar: Avatar = avatar if avatar else Avatar.get_random_avatar(name, gender) self.pos: list[int] = [0, 0] self.counter_pos: list[int] = [0, 0] + self.counter = None self.dish = -1 self.action = Action.INSERT @@ -155,36 +174,39 @@ async def go_to_counter(self, server, player_cafe, counter=None, if not counter: counter = get_random_counter(player_cafe) - response_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.MOVE_TO_COUNTER, # Action type (5: go to counter) - counter.pos[0], # Counter X - counter.pos[1] # Counter Y - ])) + # PATH FOUND TO COUNTER + if counter: + response_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.MOVE_TO_COUNTER, # Action type (5: go to counter) + counter.pos[0], # Counter X + counter.pos[1] # Counter Y + ])) - response = ExtensionResponse('nac', '-1', '0', response_info) + response = ExtensionResponse('nac', '-1', '0', response_info) - for user in player_cafe.clients: - await server.send_response(user, response) + for user in player_cafe.clients: + await server.send_response(user, response) - self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) + self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) + self.counter = counter - # print(f"[WAITER] Waiter should staying at: {self.counter_pos}") + # print(f"[WAITER] Waiter should staying at: {self.counter_pos}") - walking_time = get_time(player_cafe, self.pos, counter.pos) + walking_time = get_time(player_cafe, self.pos, counter.pos) - await sleep(walking_time + 0.5) + await sleep(walking_time + 0.5) - self.pos = counter.pos + self.pos = counter.pos - async def serve_food(self, server, player_cafe, customer_pos) -> bool: + async def serve_food(self, server, player, customer_pos) -> bool: self.action = Action.FEED - counter = player_cafe.get_object(int(self.pos[0]), int(self.pos[1])) + counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) if not counter.dish_amount >= 1 or counter.dish_id == -1: - await self.go_to_counter(server, player_cafe) - counter = player_cafe.get_object(int(self.pos[0]), int(self.pos[1])) + await self.go_to_counter(server, player.cafe) + counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) if not counter.dish_id == -1: self.dish = counter.dish_id @@ -197,6 +219,8 @@ async def serve_food(self, server, player_cafe, customer_pos) -> bool: # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) + response_info = '+'.join(map(str, [ self.id, # Waiter ID Action.FEED, # Action type (6: feed) @@ -206,12 +230,17 @@ async def serve_food(self, server, player_cafe, customer_pos) -> bool: response = ExtensionResponse('nac', '-1', '0', response_info) - for user in player_cafe.clients: + for user in player.cafe.clients: await server.send_response(user, response) + chair = player.cafe.get_object(customer_pos[0], customer_pos[1]) + + chair.dish_id = self.dish + chair.dish_status = 1 + self.pos = customer_pos - time_to_wait = get_time(player_cafe, self.counter_pos, customer_pos) + time_to_wait = get_time(player.cafe, self.counter_pos, customer_pos) await sleep(time_to_wait + 0.5) From 79cebe966c248a325048506eb18d0ddc3aaae75f Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:42:06 +0200 Subject: [PATCH 11/43] BUG! On waiter spawn, no task given Happens when the player enter edit mode instantly, and leave. The waiters getting teleported to the counter. Not really need to fix it, errors can be happen in the get_shortest_path --- objects/waiter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/objects/waiter.py b/objects/waiter.py index 0da2157..52b5c7e 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -68,7 +68,9 @@ def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: async def spawn_waiters(server, client) -> None: address = client.get_extra_info('peername') player = server.clients[address] - + + # BUG NO TASK GIVEN! + # Happens when the player enter edit mode instantly, and leave. The waiters getting teleported to the counter await gather(*[waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters]) counter = get_random_counter(player.cafe) From 84ef5f9b0ab4c8919a44b9ee5604b45b8de04f85 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:44:46 +0200 Subject: [PATCH 12/43] Remove chair dishes before getting the objects in json --- objects/cafe.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/objects/cafe.py b/objects/cafe.py index fc130f4..7542c3b 100644 --- a/objects/cafe.py +++ b/objects/cafe.py @@ -1,4 +1,5 @@ -from typing import List, Literal, Union, Dict, TYPE_CHECKING, Optional +from copy import deepcopy +from typing import List, Literal, Union, TYPE_CHECKING, Optional from objects.cafe_object import CafeObject, ObjectType from consts import * from astar import AStar @@ -87,7 +88,13 @@ def parse_objects(self, objects: str) -> None: self.fridge_capacity = self.get_fridge_capacity() def get_objects_as_json(self) -> str: - objects = [obj.to_dict() for obj in self.objects] + objects = [] + objects_copy = deepcopy(self.objects) + for obj in objects_copy: + if obj.type == ObjectType.CHAIR: + obj.dish_id = -1 + obj.dish_status = 0 + objects.append(obj.to_dict()) json_str = json.dumps(objects) return json_str @@ -260,7 +267,6 @@ def update_rating(self, rating: int): # The game always check if the rating is smaller than the luxury_rating. If yes, then its update to this value. luxury_rating = min(int((1 + 0.05 * self.luxury) * 10), 500) - print(luxury_rating) if self.rating < luxury_rating: self.rating = luxury_rating From d530be5fc8c7d0574f72a93afaffc59bd58ebaba Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:43:26 +0200 Subject: [PATCH 13/43] Chair dish update on clean, and on customer leave --- objects/customer.py | 2 ++ objects/waiter.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/objects/customer.py b/objects/customer.py index f1ee09d..17852ba 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -251,6 +251,8 @@ async def start(self, server, client) -> None: await self.leave(server, player.cafe) + chair.dish_status = 3 + player.cafe.update_rating(1) server.db.update_cafe(player.id, rating=player.cafe.rating) diff --git a/objects/waiter.py b/objects/waiter.py index 52b5c7e..445fcdf 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -68,10 +68,10 @@ def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: async def spawn_waiters(server, client) -> None: address = client.get_extra_info('peername') player = server.clients[address] - + # BUG NO TASK GIVEN! # Happens when the player enter edit mode instantly, and leave. The waiters getting teleported to the counter - await gather(*[waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters]) + await gather(*(waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters)) counter = get_random_counter(player.cafe) @@ -284,6 +284,10 @@ async def clean_table(self, server, player_cafe, table): table.busy = False + chair = table.connected_chair + chair.dish_id = -1 + chair.dish_status = 0 + await self.go_to_counter(server, player_cafe) def remove_task(self, _: 'Task' = None): From 8f3b47dc4d227be17291f3bab5aef753b9c76cd0 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:06:26 +0200 Subject: [PATCH 14/43] Added npc customize (ncu) --- commands/ncu.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 commands/ncu.py diff --git a/commands/ncu.py b/commands/ncu.py new file mode 100644 index 0000000..4542846 --- /dev/null +++ b/commands/ncu.py @@ -0,0 +1,29 @@ +from typing import TYPE_CHECKING +from response import ExtensionResponse + +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +async def handle_ncu(server: 'CafeServer', client: 'StreamWriter', *params: str) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + selected_waiter = int(params[1]) + new_name = params[2] + new_priority = int(params[3]) + + for waiter in player.cafe.waiters: + if waiter.id == selected_waiter: + waiter.name = new_name + waiter.avatar.username = new_name + waiter.priority = new_priority + + response = ExtensionResponse('ncu', '-1', '0', str(selected_waiter), new_name, str(new_priority)) + + for user in player.cafe.clients: + await server.send_response(user, response) + + server.db.update_cafe(player.id, waiters=player.cafe.get_waiters_as_json()) + \ No newline at end of file From 8b4c98e7fce2c6dbdb7c0ea59a4ee08a5d71cdfc Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:57:45 +0200 Subject: [PATCH 15/43] Added ASY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Called when the owner joins their Café. The ASY updates the client money and gold. --- commands/asy.py | 14 ++++++++++++++ commands/jca.py | 6 ++++++ 2 files changed, 20 insertions(+) create mode 100644 commands/asy.py diff --git a/commands/asy.py b/commands/asy.py new file mode 100644 index 0000000..c1c5d1a --- /dev/null +++ b/commands/asy.py @@ -0,0 +1,14 @@ +from typing import TYPE_CHECKING +from response import ExtensionResponse + +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +async def handle_asy(server: 'CafeServer', client: 'StreamWriter') -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + response = ExtensionResponse('asy', '-1', '0', str(player.cash), str(player.gold)) + await server.send_response(client, response) diff --git a/commands/jca.py b/commands/jca.py index 62ebf81..d9aa1cc 100644 --- a/commands/jca.py +++ b/commands/jca.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING +from commands.asy import handle_asy from objects.waiter import spawn_waiters, spawn_waiters_to_client from response import ExtensionResponse from commands.sgc import handle_sgc @@ -15,6 +16,8 @@ async def handle_jca(server: 'CafeServer', client: 'StreamWriter', *params: str) -> None: wanted_player_id = int(params[2]) + address = client.get_extra_info('peername') + player = server.clients[address] check_player = [player for player in server.players if player.id == wanted_player_id] if check_player: @@ -31,6 +34,9 @@ async def handle_jca(server: 'CafeServer', client: 'StreamWriter', *params: str) await handle_ifr(server, client, *params) await handle_ein(server, client, *params) + if wanted_player.id == player.id: + await handle_asy(server, client) + if not wanted_player.cafe.customer_cycle: print(f'Created a new customer cycle\nUsed this player\'s Cafe: {wanted_player.avatar.username}') wanted_player.cafe.customer_cycle = create_task(spawn_cycle(server, wanted_player.client)) From 125784aa418f75ff47df5898102bc89b379aac08 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:01:06 +0200 Subject: [PATCH 16/43] Added offline selling, disconnect all clients on server stop --- commands/lgn.py | 4 +++ database.py | 5 ++-- main.py | 14 ++++++++++- objects/player.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++- server.py | 27 ++++++++++++++++++--- 5 files changed, 104 insertions(+), 8 deletions(-) diff --git a/commands/lgn.py b/commands/lgn.py index bd7d83a..6b3c42e 100644 --- a/commands/lgn.py +++ b/commands/lgn.py @@ -41,6 +41,10 @@ async def handle_lgn(server: 'CafeServer', client: 'StreamWriter', *params: str) address = client.get_extra_info('peername') server.clients[address] = player + if player.last_login: + player.calculate_offline_income() + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json(), rating=player.cafe.rating) + await server.send_response(client, SystemResponse('logout')) response = ExtensionResponse('lgn', '1', '0') diff --git a/database.py b/database.py index a6cca76..4bfefe0 100644 --- a/database.py +++ b/database.py @@ -41,6 +41,7 @@ def _create_player_by_params(self, player_args: tuple[int, str]) -> Player: player_args[16]) player.cafe = self.get_cafe(player_args[0]) player.parse_mastery(player_args[17]) + player.last_login = (player_args[18]) return player @@ -76,13 +77,13 @@ def try_login(self, name_or_email: str, password: str) -> tuple[int, str]: return 0, values[2] def get_player(self, username: str) -> Player: - self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery FROM player WHERE username=?", (username,)) + self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery, last_login FROM player WHERE username=?", (username,)) player_args = self.cursor.fetchone() return self._create_player_by_params(player_args) def get_player_by_id(self, player_id: int) -> Player: - self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery FROM player WHERE id=?", (player_id,)) + self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery, last_login FROM player WHERE id=?", (player_id,)) player_args = self.cursor.fetchone() return self._create_player_by_params(player_args) diff --git a/main.py b/main.py index 4be6f8d..786e4e9 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,24 @@ from server import CafeServer from asyncio import run +import signal +import sys + + +def signal_handler(sig, frame): + print(f'\n[INFO] Shutting down server...') + server.stop() + sys.exit(0) + if __name__ == '__main__': server = CafeServer() + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + try: run(server.start()) except KeyboardInterrupt: - server.stop() print(f'\n[INFO] Shutting down server...') + server.stop() exit(0) diff --git a/objects/player.py b/objects/player.py index 5f54baf..1478c5c 100644 --- a/objects/player.py +++ b/objects/player.py @@ -1,7 +1,11 @@ +import json + from objects.avatar import Avatar +from objects.cafe_object import ObjectType from utils.items import get_dish_info from math import ceil, floor from typing import TYPE_CHECKING, Optional +from datetime import datetime, timezone if TYPE_CHECKING: from objects.cafe import Cafe @@ -29,6 +33,7 @@ def __init__(self): self.pos = None self.mastery: dict[int, int] = {num: 0 for num in range(1201, 1256)} + self.last_login: str = '' def build_mastery(self) -> str: pairs = [] @@ -92,9 +97,64 @@ def get_mastery_duration(self, item_id: int) -> int: else: return floor(duration * 0.95) * 60 + def calculate_offline_income(self) -> int: + max_minutes = 10000 + minutes_past = self.get_minutes_since_last_login() + + if minutes_past > max_minutes: + minutes_past = max_minutes + + rating = self.cafe.rating + + if rating < 50: + should_sold = floor(minutes_past / 1.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + elif 50 <= rating < 250: + should_sold = floor(minutes_past / 0.7) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + else: + should_sold = floor(minutes_past / 0.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + + total_income = 0 + total_sold_dishes = 0 + + for obj in self.cafe.objects: + if obj.type == ObjectType.COUNTER: + if obj.dish_amount > 0: + sold_dishes = min(should_sold, obj.dish_amount) + dish_info = get_dish_info(obj.dish_id) + + income = dish_info['income'] + total_income += sold_dishes * income + obj.dish_amount -= sold_dishes + total_sold_dishes += sold_dishes + + if obj.dish_amount < 1: + obj.dish_id = -1 + obj.dish_amount = 0 + + self.cafe.update_rating(total_sold_dishes) + + amount_was_missing = (should_sold - total_sold_dishes) + + if not amount_was_missing == 0: + self.cafe.update_rating(-(amount_was_missing * 2)) + + print(f'[OFFLINE SELLING] Player sold a total of: {total_sold_dishes} dishes. Amount was missing: ' + f'{amount_was_missing} Time past since last login: ' + f'{minutes_past}m. Total income: {total_income}$') + + return floor(total_income) + + def get_minutes_since_last_login(self) -> int: + last_login = datetime.strptime(self.last_login, "%y-%d-%m %H:%M").replace(tzinfo=timezone.utc) + current_time = datetime.now(timezone.utc) + time_diff = current_time - last_login + minutes_diff = time_diff.total_seconds() // 60 + return floor(minutes_diff) + def to_response(self, *params: str) -> list[str]: return [ - *params, str(self.id), str(self.id), '0', str(self.cash), str(self.gold), str(self.xp), str(self.instant_cookings), + *params, str(self.id), str(self.id), '0', str(self.cash), str(self.gold), str(self.xp), + str(self.instant_cookings), str(self.open_jobs), '0', str(int(self.played_wheel)), '0', str(int(self.allow_friend_requests)), str(int(self.allow_emails)), str(int(self.email_verified)), str(self.new_gifts), self.avatar.to_string() ] diff --git a/server.py b/server.py index e2ee54e..7264ecd 100644 --- a/server.py +++ b/server.py @@ -1,5 +1,8 @@ import sys import socket +import time +from datetime import datetime, timezone + from database import Database from command import handle_command from request import Request, RequestType @@ -38,7 +41,7 @@ async def send_response(connection, response: Response): response = response.raw_response connection.write(response.encode()) await connection.drain() - print('[SENT] {}'.format(response.strip('\x00'))) + # print('[SENT] {}'.format(response.strip('\x00'))) def bind_socket(self) -> None: try: @@ -65,7 +68,7 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre request = Request(request_str.strip('\x00')) - print(f'\n[RECEIVED] {request.raw_request}') + # print(f'\n[RECEIVED] {request.raw_request}') if request.type == RequestType.DISCONNECT: writer.close() @@ -111,13 +114,18 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre player.online = False + current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") + player.last_login = current_time + + self.db.update_player(player.id, last_login=player.last_login) + del self.clients[peername] + print(f'[DISCONNECTED] {peername}') + for task in tasks: task.cancel() - print(f'[DISCONNECTED] {peername}') - async def start(self) -> None: # self.socket.listen(self.max_conn) self.running = True @@ -144,5 +152,16 @@ async def start(self) -> None: # # asyncio.run(self.handle_client(client_connection, client_address)) def stop(self) -> None: + for client in list(self.clients.keys()): + player = self.clients[client] + + current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") + player.last_login = current_time + + self.db.update_player(player.id, last_login=player.last_login) + + print(f'[DISCONNECTED] {client}') + del self.clients[client] + self.running = False self.socket.close() From 33e684e272480d77b469519ba761b3ccff3e8d7e Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 28 Sep 2024 08:42:12 +0200 Subject: [PATCH 17/43] Customers spawning slower at bad rating --- objects/customer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/objects/customer.py b/objects/customer.py index 17852ba..b68564c 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -381,7 +381,7 @@ async def spawn_cycle(server, client) -> None: rating = player.cafe.rating if rating < 50: - spawn_interval = uniform(5, 15) + spawn_interval = uniform(10, 20) elif 50 <= rating < 250: spawn_interval = uniform(3.5, 8) else: From 231bd8b524fd203b68497b8c6ca0939da79d2d34 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 28 Sep 2024 10:07:07 +0200 Subject: [PATCH 18/43] Fixed offline selling, improved it, added LBU dialog --- commands/lbu.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++ commands/lgn.py | 8 ++--- objects/player.py | 48 ------------------------- 3 files changed, 93 insertions(+), 52 deletions(-) create mode 100644 commands/lbu.py diff --git a/commands/lbu.py b/commands/lbu.py new file mode 100644 index 0000000..44750a8 --- /dev/null +++ b/commands/lbu.py @@ -0,0 +1,89 @@ +from math import floor +from random import choice +from typing import TYPE_CHECKING + +from commands.asy import handle_asy +from objects.cafe_object import ObjectType +from response import ExtensionResponse +from utils.items import get_dish_info + +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + max_minutes = 10000 + minutes_past = player.get_minutes_since_last_login() + + if minutes_past > max_minutes: + minutes_past = max_minutes + + if minutes_past > 0: + rating = player.cafe.rating + + if rating < 50: + should_sold = floor(minutes_past / 1.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + elif 50 <= rating < 250: + should_sold = floor(minutes_past / 0.7) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + else: + should_sold = floor(minutes_past / 0.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + + total_income = 0 + total_sold_dishes = 0 + + dishes_info: dict[int, int] = {} + + available_counters = [obj for obj in player.cafe.objects if obj.type == ObjectType.COUNTER and obj.dish_amount > 0] + + dish_info_cache = {} + + for _ in range(should_sold): + if not available_counters: + break + + counter = choice(available_counters) + dish_id = counter.dish_id + + # Need to cache it, because it's slow to get the dish income on every loop + # And on a big loop, the client froze + if dish_id not in dish_info_cache: + dish_info_cache[dish_id] = get_dish_info(dish_id) + + dish_info = dish_info_cache[dish_id] + + counter.dish_amount -= 1 + total_sold_dishes += 1 + total_income += dish_info.get('income') + + dishes_info[dish_id] = dishes_info.setdefault(dish_id, 0) + 1 + + if counter.dish_amount <= 0: + counter.dish_id = -1 + available_counters.remove(counter) + + player.cafe.update_rating(total_sold_dishes) + + amount_was_missing = (should_sold - total_sold_dishes) + + if not amount_was_missing == 0: + player.cafe.update_rating(-(amount_was_missing * 2)) + + player.cash += total_income + + print(f'[ [LBU] OFFLINE SELLING] Player sold a total of: {total_sold_dishes} dishes. Amount was missing: ' + f'{amount_was_missing} Time past since last login: ' + f'{minutes_past}m. Total income: {total_income}$') + + sold_dishes = ''.join(f'{dish_id}+{amount}#' for dish_id, amount in dishes_info.items()) + + response = ExtensionResponse('lbu', '-1', '0', f'1901+{total_income}#{sold_dishes}') + await server.send_response(client, response) + + server.db.update_player(player.id, cash=player.cash) + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json(), rating=player.cafe.rating) + + await handle_asy(server, client) diff --git a/commands/lgn.py b/commands/lgn.py index 6b3c42e..0e42244 100644 --- a/commands/lgn.py +++ b/commands/lgn.py @@ -1,4 +1,6 @@ from typing import TYPE_CHECKING + +from commands.lbu import handle_lbu from response import ExtensionResponse, SystemResponse from commands.rlu import handle_rlu from commands.gui import handle_gui @@ -41,10 +43,6 @@ async def handle_lgn(server: 'CafeServer', client: 'StreamWriter', *params: str) address = client.get_extra_info('peername') server.clients[address] = player - if player.last_login: - player.calculate_offline_income() - server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json(), rating=player.cafe.rating) - await server.send_response(client, SystemResponse('logout')) response = ExtensionResponse('lgn', '1', '0') @@ -59,3 +57,5 @@ async def handle_lgn(server: 'CafeServer', client: 'StreamWriter', *params: str) await handle_lmi(server, client, *params) await handle_ifr(server, client, *params) + + await handle_lbu(server, client) diff --git a/objects/player.py b/objects/player.py index 1478c5c..1252dad 100644 --- a/objects/player.py +++ b/objects/player.py @@ -1,7 +1,6 @@ import json from objects.avatar import Avatar -from objects.cafe_object import ObjectType from utils.items import get_dish_info from math import ceil, floor from typing import TYPE_CHECKING, Optional @@ -97,53 +96,6 @@ def get_mastery_duration(self, item_id: int) -> int: else: return floor(duration * 0.95) * 60 - def calculate_offline_income(self) -> int: - max_minutes = 10000 - minutes_past = self.get_minutes_since_last_login() - - if minutes_past > max_minutes: - minutes_past = max_minutes - - rating = self.cafe.rating - - if rating < 50: - should_sold = floor(minutes_past / 1.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - elif 50 <= rating < 250: - should_sold = floor(minutes_past / 0.7) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - else: - should_sold = floor(minutes_past / 0.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - - total_income = 0 - total_sold_dishes = 0 - - for obj in self.cafe.objects: - if obj.type == ObjectType.COUNTER: - if obj.dish_amount > 0: - sold_dishes = min(should_sold, obj.dish_amount) - dish_info = get_dish_info(obj.dish_id) - - income = dish_info['income'] - total_income += sold_dishes * income - obj.dish_amount -= sold_dishes - total_sold_dishes += sold_dishes - - if obj.dish_amount < 1: - obj.dish_id = -1 - obj.dish_amount = 0 - - self.cafe.update_rating(total_sold_dishes) - - amount_was_missing = (should_sold - total_sold_dishes) - - if not amount_was_missing == 0: - self.cafe.update_rating(-(amount_was_missing * 2)) - - print(f'[OFFLINE SELLING] Player sold a total of: {total_sold_dishes} dishes. Amount was missing: ' - f'{amount_was_missing} Time past since last login: ' - f'{minutes_past}m. Total income: {total_income}$') - - return floor(total_income) - def get_minutes_since_last_login(self) -> int: last_login = datetime.strptime(self.last_login, "%y-%d-%m %H:%M").replace(tzinfo=timezone.utc) current_time = datetime.now(timezone.utc) From e00d683f17f8cc715963fc6a4801aea251edff72 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 28 Sep 2024 10:10:28 +0200 Subject: [PATCH 19/43] Spawnrate optimized --- objects/customer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objects/customer.py b/objects/customer.py index b68564c..1e1a791 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -383,9 +383,9 @@ async def spawn_cycle(server, client) -> None: if rating < 50: spawn_interval = uniform(10, 20) elif 50 <= rating < 250: - spawn_interval = uniform(3.5, 8) + spawn_interval = uniform(4.5, 8) else: - spawn_interval = uniform(0.1, 5) + spawn_interval = uniform(0.5, 5) await sleep(spawn_interval) From 20cbf86bbf1ec05ea02eb137aa0ce884a0a8a28b Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 28 Sep 2024 14:42:52 +0200 Subject: [PATCH 20/43] Don't send LBU when player not have dish --- commands/lbu.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/commands/lbu.py b/commands/lbu.py index 44750a8..5621509 100644 --- a/commands/lbu.py +++ b/commands/lbu.py @@ -23,6 +23,11 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: minutes_past = max_minutes if minutes_past > 0: + available_counters = [obj for obj in player.cafe.objects if obj.type == ObjectType.COUNTER and obj.dish_amount > 0] + + if not available_counters: + return + rating = player.cafe.rating if rating < 50: @@ -37,8 +42,6 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: dishes_info: dict[int, int] = {} - available_counters = [obj for obj in player.cafe.objects if obj.type == ObjectType.COUNTER and obj.dish_amount > 0] - dish_info_cache = {} for _ in range(should_sold): From af39c3430b53b5e1245c319deb92f903a2768062 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 28 Sep 2024 18:49:57 +0200 Subject: [PATCH 21/43] Customers tasks update. Each task can callable again --- objects/objects/customer.py | 401 ++++++++++++++++++++++++++++++++++++ objects/objects/waiter.py | 332 +++++++++++++++++++++++++++++ 2 files changed, 733 insertions(+) create mode 100644 objects/objects/customer.py create mode 100644 objects/objects/waiter.py diff --git a/objects/objects/customer.py b/objects/objects/customer.py new file mode 100644 index 0000000..bff337e --- /dev/null +++ b/objects/objects/customer.py @@ -0,0 +1,401 @@ +import asyncio +from asyncio import create_task, sleep, Task, current_task +from random import randint, choice, uniform +from typing import Optional, List, TYPE_CHECKING + +from astar import AStar +from objects.avatar import Avatar +from objects.cafe_object import CafeObject, ObjectType +from objects.waiter import Waiter +from response import ExtensionResponse +from utils.items import get_dish_info + +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +class Action: + INSERT = 0 + WALK_TO_CHAIR = 1 + SIT_DOWN = 2 + EAT = 3 + LEAVE = 4 + LEAVE_COMPLETE = 41 + + +class WaiterTask: + CLEAN = 0 + SERVE = 1 + + +def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: + cafe_size = cafe.size + obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] + + for i in range(cafe_size): + obstacles[i][0] = 1 + obstacles[0][i] = 1 + + for obj in cafe.objects: + obstacles[obj.pos[0]][obj.pos[1]] = 1 + + obstacles[end_pos[0]][end_pos[1]] = 0 + + path = AStar(obstacles, tuple(start_pos), tuple(end_pos)).solve() + + if path: + distance = len(path) - 1 + time = distance / 1.85 # OLD -> 1.95 TESTED: 1.90 (customers should need to wait few sec before sitting down) + + ## print(f'[CUSTOMER-ASTAR]: Start: {start_pos} End: {end_pos}') + ## print(f'[CUSTOMER-ASTAR]: Time = {time}s') + ## print(f'[CUSTOMER-ASTAR]: Distance = {distance}') + ## print(f'[CUSTOMER-ASTAR]: Path = {path}') + + else: + time = -1 + + return time + + +def get_random_table(cafe) -> Optional['CafeObject']: + eligible_tables = cafe.available_tables + free_tables = [table for table in eligible_tables if not table.busy] + + return choice(free_tables) if free_tables else None + + +def check_for_foods(cafe): + counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER] + counters_with_food = [counter for counter in counters if counter.dish_id != -1] + + if counters_with_food: + return True + + return False + + +def get_random_waiter(cafe, task) -> Optional['Waiter']: + waiters = cafe.waiters + if task == WaiterTask.SERVE: + free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority >= 50] + else: + free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority <= 50] + + return choice(free_waiters) if free_waiters else None + + +async def wait_for_table(cafe) -> Optional['CafeObject']: + for _ in range(10): + table = get_random_table(cafe) + if table: + return table + await sleep(1) + return None + + +async def wait_for_waiter(cafe) -> Optional['Waiter']: + for _ in range(25): + waiter = get_random_waiter(cafe, task=WaiterTask.SERVE) + if check_for_foods(cafe): + if waiter: + return waiter + await sleep(1) + return None + + +async def wait_for_cleaning(server, player, table): + while True: + waiter = get_random_waiter(player.cafe, task=WaiterTask.CLEAN) + if waiter: + waiter.task = create_task(waiter.clean_table(server, player.cafe, table)) + waiter.task.add_done_callback(waiter.remove_task) + break + + await sleep(0.1) + + +async def spawn_customers_to_client(server, client, wanted_cafe): + for customer in wanted_cafe.customers: + if customer.action not in [Action.LEAVE]: + spawn_info = '+'.join(map(str, [ + customer.id, # Customer ID + 0, # NPC type (0: Customer) + 0, # Favourite dish + -1, # Dish ID + 0, # Is thirsty + customer.avatar.to_string(True) # Avatar + ])) + + action = customer.action + + match customer.action: + # If the customer is walking, sending sit down to visitor client + case Action.WALK_TO_CHAIR: + action = Action.SIT_DOWN + + insert_info = '+'.join(map(str, [ + customer.id, # Customer ID + action, # Action type + customer.pos[0], # X + customer.pos[1] # Y + ])) + + # NAV does not split by '#' + response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) + + # NAC uses split by '#' + # https://github.com/GGR-Devs/Cafe-Main/blob/2911a43b4d0972d2f1386edd782d3382f031f36d/com/goodgamestudios/cafe/commands/NACCommand.as#L29 + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await server.send_response(client, response_spawn) + await server.send_response(client, response_insert) + + +class Customer: + def __init__(self, customer_id: int): + self.id: int = customer_id + self.avatar: Avatar = Avatar.get_random_avatar('Customer') + self.pos: List[int] = [0, 0] + self.task: Optional[asyncio.Task] = None + + self.dish = -1 + self.action = Action.INSERT + + async def start(self, server, client): + address = client.get_extra_info('peername') + player = server.clients[address] + + insert_info = '+'.join(map(str, [ + str(self.id), # Customer ID + Action.INSERT # Action type (0: Insert) + ])) + + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await self.spawn(server, player) + + for user in player.cafe.clients: + await server.send_response(user, response_insert) + + async def spawn(self, server, player): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + insert_info = '+'.join(map(str, [ + str(self.id), # Customer ID + Action.INSERT # Action type (0: Insert) + ])) + + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await self._spawn(server, player) + + for user in player.cafe.clients: + await server.send_response(user, response_insert) + + # print(f"[CUSTOMER] New spawned: {self.id}") + + await sleep(2) + + await self.wait_for_table(server, player) + + async def _spawn(self, server, player): + spawn_info = '+'.join(map(str, [ + self.id, # Customer ID + 0, # NPC type (0: Customer) + 0, # Favourite = Waiters priority + -1, # Dish ID + 0, # Is thirsty + self.avatar.to_string(True) # Avatar + ])) + + response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) + + self.pos = player.cafe.get_start_pos() + + for user in player.cafe.clients: + await server.send_response(user, response_spawn) + + async def wait_for_table(self, server, player): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + # print(f"[CUSTOMER] Started waiting for table: {self.id}") + + for _ in range(10): + table = get_random_table(player.cafe) + if table: + # print(f"[CUSTOMER] Got a free table: {self.id}") + table.busy = True + await self.go_to_chair(server, player, table) + return + await sleep(1) + + # print(f"[CUSTOMER] No free table found, leaving: {self.id}") + player.cafe.update_rating(-2) + server.db.update_cafe(player.id, rating=player.cafe.rating) + + await self.leave(server, player) + + async def go_to_chair(self, server, player, table): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + chair = table.connected_chair + + self.pos = chair.pos + + self.action = Action.WALK_TO_CHAIR + + await self._go_to_chair(server, player, chair) + + walking_time = get_time(player.cafe, player.cafe.player_start, chair.pos) + # print(f"[CUSTOMER] Started going to table: {self.id} pos: {chair.pos} walking time: {walking_time}s") + + await sleep(walking_time) + + # print(f"[CUSTOMER] Arrived to the chair: {self.id}") + + await self.sit_down(server, player, table) + + async def sit_down(self, server, player, table): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + await self._sit_down(server, player) + + await self.wait_for_waiter(server, player, table) + + async def _sit_down(self, server, player): + self.action = Action.SIT_DOWN + + response_info = '+'.join(map(str, [ + self.id, # Customer ID + Action.SIT_DOWN, # Action type (2: Sit down) + self.pos[0], # X + self.pos[1] # Y + ])) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player.cafe.clients: + await server.send_response(user, response) + + async def wait_for_waiter(self, server, player, table): + # print(f"[CUSTOMER] Started waiting for waiter...") + waiter = None + for _ in range(25): + waiter = get_random_waiter(player.cafe, task=WaiterTask.SERVE) + if check_for_foods(player.cafe): + if waiter: + break + + # print(f"[CUSTOMER] Waiting for waiter...") + await sleep(1) + + if not waiter: + # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") + + table.busy = False + player.cafe.update_rating(-2) + server.db.update_cafe(player.id, rating=player.cafe.rating) + + await self.leave(server, player) + return + + elif waiter: + waiter.task = create_task(waiter.serve_food(server, player, self, table)) + # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") + + async def eat(self, server, player, table): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + self.action = Action.EAT + + # print(f'[CUSTOMER] Started eating: {self.id} Dish: {self.dish}') + + await self._eat(server, player) + + await sleep(25) + + chair = table.connected_chair + + chair.dish_status = 3 + + player.cafe.update_rating(1) + server.db.update_cafe(player.id, rating=player.cafe.rating) + + create_task(wait_for_cleaning(server, player, table)) + + await self.leave(server, player) + + async def _eat(self, server, player): + response_info = '+'.join(map(str, [ + self.id, # Customer ID + Action.EAT, # Action type (3: Eat) + self.pos[0], # X + self.pos[1] # Y + ])) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player.cafe.clients: + await server.send_response(user, response) + + async def _go_to_chair(self, server, player, chair): + response_info = '+'.join(map(str, [ + self.id, # Customer ID + Action.WALK_TO_CHAIR, # Action type (1: Walk to chair) + chair.pos[0], # X + chair.pos[1] # Y + ])) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player.cafe.clients: + await server.send_response(user, response) + + async def leave(self, server, player): + self.action = Action.LEAVE + + insert_info = '+'.join(map(str, [ + str(self.id), # Customer ID + Action.LEAVE # Action type (4: Leave) + ])) + + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + player.cafe.customers.remove(self) + + for user in player.cafe.clients: + await server.send_response(user, response_insert) + + def remove_task(self, _: 'Task' = None): + self.task = None + + +async def spawn_cycle(server, client) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + while True: + rating = player.cafe.rating + + if rating < 50: + spawn_interval = uniform(10, 20) + elif 50 <= rating < 250: + spawn_interval = uniform(4.5, 8) + else: + spawn_interval = uniform(0.5, 5) + + await sleep(spawn_interval) + + new_id = player.cafe.customers[-1].id + 1 if player.cafe.customers else 100 + new_customer = Customer(new_id) + player.cafe.customers.append(new_customer) + + new_customer.task = create_task(new_customer.start(server, client)) + new_customer.task.add_done_callback(new_customer.remove_task) diff --git a/objects/objects/waiter.py b/objects/objects/waiter.py new file mode 100644 index 0000000..60095ec --- /dev/null +++ b/objects/objects/waiter.py @@ -0,0 +1,332 @@ +from asyncio import current_task, sleep, Task, gather, create_task +from random import choice +from typing import TYPE_CHECKING, Optional + +from astar import AStar +from objects.avatar import Avatar +from objects.cafe_object import ObjectType, CafeObject +from response import ExtensionResponse + +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +class Priority: + CLEANING = 0 + BOTH = 50 + SERVING = 100 + + +class Action: + INSERT = 0 + MOVE_TO_COUNTER = 5 + FEED = 6 + CLEAN = 7 + + +def get_random_counter(cafe) -> 'CafeObject': + counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER and get_time(cafe, cafe.get_start_pos(), + obj.pos) != -1] + counters_with_food = [counter for counter in counters if counter.dish_id != -1] + + if counters: + if counters_with_food: + return choice(counters_with_food) + return choice(counters) + + +def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: + cafe_size = cafe.size + obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] + + for i in range(cafe_size): + obstacles[i][0] = 1 + obstacles[0][i] = 1 + for obj in cafe.objects: + obstacles[obj.pos[0]][obj.pos[1]] = 1 + + obstacles[start_pos[0]][start_pos[1]] = 0 + obstacles[end_pos[0]][end_pos[1]] = 0 + + path = AStar(obstacles, start_pos, tuple(end_pos)).solve() + + if path: + distance = len(path) - 1 + time = distance / 2.30 # WAITERS ARE FASTER THAN CUSTOMERS (40->45) + + # print(f'[WAITER-ASTAR]: Start: {start_pos} End: {end_pos}') + # print(f'[WAITER-ASTAR]: Time = {time}s') + # print(f'[WAITER-ASTAR]: Distance = {distance}') + # print(f'[WAITER-ASTAR]: Path = {path}') + else: + time = -1 + + return time + + +async def spawn_waiters(server, client) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + # BUG NO TASK GIVEN! + # Happens when the player enter edit mode instantly, and leave. The waiters getting teleported to the counter + await gather(*(waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters)) + + counter = get_random_counter(player.cafe) + + # PATH FOUND TO COUNTER + if counter: + for waiter in player.cafe.waiters: + waiter.counter = counter + + await sleep(2) + + await gather(*[waiter.go_to_counter(server, player.cafe, + counter, True) for waiter in player.cafe.waiters]) + + +async def spawn_waiters_to_client(server, client, wanted_cafe): + for waiter in wanted_cafe.waiters: + add_info = '+'.join(map(str, [ + waiter.id, # Waiter ID + 1, # NPC type (1: Waiter) + waiter.priority, # Priority + -1, # Dish ID (unnecessary for waiters) + waiter.avatar.to_string(True), # Avatar + ])) + + counter = waiter.counter + + # PATH FOUND TO COUNTER + if counter is not None: + pos = counter.pos + else: + # PATH NOT FOUND TO COUNTER + pos = wanted_cafe.get_start_pos() + + insert_info = '+'.join(map(str, [ + waiter.id, # Waiter ID + waiter.action, # Action type + pos[0], # X + pos[1] # Y + ])) + + response_add = ExtensionResponse('nav', '-1', '0', add_info) + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await server.send_response(client, response_add) + await server.send_response(client, response_insert) + + +class Waiter: + def __init__(self, waiter_id: int, name: str, gender: int, priority: int, + avatar: Avatar = None): + self.id: int = waiter_id + self.name: str = name + self.gender: int = gender + self.priority = priority + self.avatar: Avatar = avatar if avatar else Avatar.get_random_avatar(name, gender) + self.pos: list[int] = [0, 0] + self.counter_pos: list[int] = [0, 0] + self.counter = None + + self.dish = -1 + self.action = Action.INSERT + + self.task: Optional['Task'] = None + + async def spawn(self, server, player_cafe) -> None: + insert_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.INSERT # Action type (0: Insert) + ])) + + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + + await self._spawn(server, player_cafe) + + for user in player_cafe.clients: + await server.send_response(user, response_insert) + + async def _spawn(self, server, player_cafe) -> None: + self.pos = player_cafe.get_start_pos() + + add_info = '+'.join(map(str, [ + self.id, # Waiter ID + 1, # NPC type (1: Waiter) + self.priority, # Priority + -1, # Dish ID (unnecessary for waiters) + self.avatar.to_string(True), # Avatar + ])) + + response_add = ExtensionResponse('nav', '-1', '0', add_info) + + for user in player_cafe.clients: + await server.send_response(user, response_add) + + async def go_to_counter(self, server, player_cafe, counter=None, + need_task: bool = False): + self.action = Action.MOVE_TO_COUNTER + + if need_task: + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + if not counter: + counter = get_random_counter(player_cafe) + + # PATH FOUND TO COUNTER + if counter: + response_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.MOVE_TO_COUNTER, # Action type (5: go to counter) + counter.pos[0], # Counter X + counter.pos[1] # Counter Y + ])) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player_cafe.clients: + await server.send_response(user, response) + + self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) + self.counter = counter + + # print(f"[WAITER] Waiter should staying at: {self.counter_pos}") + + walking_time = get_time(player_cafe, self.pos, counter.pos) + + await sleep(walking_time + 0.5) + + self.pos = counter.pos + + async def serve_food(self, server, player, customer, table): + self.action = Action.FEED + + counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) + + if not counter.dish_amount >= 1 or counter.dish_id == -1: + await self.go_to_counter(server, player.cafe) + counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) + + if not counter.dish_id == -1: + self.dish = counter.dish_id + counter.dish_amount -= 1 + + if counter.dish_amount <= 0: + counter.dish_id = -1 + # print(f"[WAITER] Counter is empty") + + # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") + # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") + + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) + + # print(f"[WAITER] Waiter started to serve to: {customer.id} Waiter: {self.avatar.username} Pos: {self.pos}") + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + response_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.FEED, # Action type (6: feed) + customer.pos[0], # Customer X + customer.pos[1] # Customer Y + ])) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player.cafe.clients: + await server.send_response(user, response) + + chair = player.cafe.get_object(customer.pos[0], customer.pos[1]) + + chair.dish_id = self.dish + chair.dish_status = 1 + + self.pos = customer.pos + + time_to_wait = get_time(player.cafe, self.counter_pos, customer.pos) + # print(f"[WAITER] Time to waiter: {self.id} arrive to the customer: {customer.id} is: {time_to_wait}s") + + await sleep(time_to_wait + 0.5) + + # print(f"[WAITER] Waiter: {self.id} is arrived to: {customer.id}") + + customer.dish = self.dish + + self.dish = -1 + + customer.task = create_task(customer.eat(server, player, table)) + + await self.go_to_counter(server, player.cafe) + + else: + # print(f"[WAITER] The player cleaned the counter: {self.pos}") + # print(f"[WAITER] Customer: {customer.id} is going to wait for waiter again...") + await customer.wait_for_waiter(server, player, table) + + async def clean_table(self, server, player_cafe, table): + # print(f"[WAITER] Waiter {self.avatar.username} is assigned to clean table {table.pos}") + self.action = Action.CLEAN + + response_info = '+'.join(map(str, [ + self.id, # Waiter ID + Action.CLEAN, # Action type (7: clean) + table.connected_chair.pos[0], # Chair X + table.connected_chair.pos[1] # Chair Y + ])) + + time_to_walk = get_time(player_cafe, self.counter_pos, table.connected_chair.pos) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player_cafe.clients: + await server.send_response(user, response) + + chair = table.connected_chair + chair.dish_id = -1 + chair.dish_status = 0 + + await sleep(time_to_walk) + + # print(f"[WAITER] Waiter {self.avatar.username} is started to clean the table: {table.pos}") + + self.pos = player_cafe.get_shortest_pos(self.pos, table.connected_chair.pos) + + await sleep(2) + + # print(f"[WAITER] Waiter is cleaned the table: {1 + 1}") + + # print(f"[WAITER] Table cleaning finished, going back...") + + table.busy = False + + await self.go_to_counter(server, player_cafe) + + def remove_task(self, _: 'Task' = None): + # print(f"[WAITER] Waiter: {self.avatar.username} Task finished: {self.task.get_coro()}") + self.task = None + + def to_dict(self): + waiter_dict = { + 'waiter_id': self.id, + 'name': self.name, + 'gender': self.gender, + 'priority': self.priority, + 'avatar': self.avatar.to_string() + } + + return waiter_dict + + @classmethod + def from_dict(cls, waiter_dict: dict) -> 'Waiter': + waiter = Waiter( + waiter_dict.get('waiter_id'), + waiter_dict.get('name'), + waiter_dict.get('gender'), + waiter_dict.get('priority'), + Avatar.from_string(waiter_dict.get('avatar')) + ) + + return waiter From 1f398d1aacca06f6efbda694477269c49210b39a Mon Sep 17 00:00:00 2001 From: Bence Date: Sat, 28 Sep 2024 16:54:48 +0200 Subject: [PATCH 22/43] Revert "Customers tasks update. Each task can callable again" This reverts commit af39c3430b53b5e1245c319deb92f903a2768062. --- objects/objects/customer.py | 401 ------------------------------------ objects/objects/waiter.py | 332 ----------------------------- 2 files changed, 733 deletions(-) delete mode 100644 objects/objects/customer.py delete mode 100644 objects/objects/waiter.py diff --git a/objects/objects/customer.py b/objects/objects/customer.py deleted file mode 100644 index bff337e..0000000 --- a/objects/objects/customer.py +++ /dev/null @@ -1,401 +0,0 @@ -import asyncio -from asyncio import create_task, sleep, Task, current_task -from random import randint, choice, uniform -from typing import Optional, List, TYPE_CHECKING - -from astar import AStar -from objects.avatar import Avatar -from objects.cafe_object import CafeObject, ObjectType -from objects.waiter import Waiter -from response import ExtensionResponse -from utils.items import get_dish_info - -if TYPE_CHECKING: - from server import CafeServer - from asyncio import StreamWriter - - -class Action: - INSERT = 0 - WALK_TO_CHAIR = 1 - SIT_DOWN = 2 - EAT = 3 - LEAVE = 4 - LEAVE_COMPLETE = 41 - - -class WaiterTask: - CLEAN = 0 - SERVE = 1 - - -def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: - cafe_size = cafe.size - obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] - - for i in range(cafe_size): - obstacles[i][0] = 1 - obstacles[0][i] = 1 - - for obj in cafe.objects: - obstacles[obj.pos[0]][obj.pos[1]] = 1 - - obstacles[end_pos[0]][end_pos[1]] = 0 - - path = AStar(obstacles, tuple(start_pos), tuple(end_pos)).solve() - - if path: - distance = len(path) - 1 - time = distance / 1.85 # OLD -> 1.95 TESTED: 1.90 (customers should need to wait few sec before sitting down) - - ## print(f'[CUSTOMER-ASTAR]: Start: {start_pos} End: {end_pos}') - ## print(f'[CUSTOMER-ASTAR]: Time = {time}s') - ## print(f'[CUSTOMER-ASTAR]: Distance = {distance}') - ## print(f'[CUSTOMER-ASTAR]: Path = {path}') - - else: - time = -1 - - return time - - -def get_random_table(cafe) -> Optional['CafeObject']: - eligible_tables = cafe.available_tables - free_tables = [table for table in eligible_tables if not table.busy] - - return choice(free_tables) if free_tables else None - - -def check_for_foods(cafe): - counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER] - counters_with_food = [counter for counter in counters if counter.dish_id != -1] - - if counters_with_food: - return True - - return False - - -def get_random_waiter(cafe, task) -> Optional['Waiter']: - waiters = cafe.waiters - if task == WaiterTask.SERVE: - free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority >= 50] - else: - free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority <= 50] - - return choice(free_waiters) if free_waiters else None - - -async def wait_for_table(cafe) -> Optional['CafeObject']: - for _ in range(10): - table = get_random_table(cafe) - if table: - return table - await sleep(1) - return None - - -async def wait_for_waiter(cafe) -> Optional['Waiter']: - for _ in range(25): - waiter = get_random_waiter(cafe, task=WaiterTask.SERVE) - if check_for_foods(cafe): - if waiter: - return waiter - await sleep(1) - return None - - -async def wait_for_cleaning(server, player, table): - while True: - waiter = get_random_waiter(player.cafe, task=WaiterTask.CLEAN) - if waiter: - waiter.task = create_task(waiter.clean_table(server, player.cafe, table)) - waiter.task.add_done_callback(waiter.remove_task) - break - - await sleep(0.1) - - -async def spawn_customers_to_client(server, client, wanted_cafe): - for customer in wanted_cafe.customers: - if customer.action not in [Action.LEAVE]: - spawn_info = '+'.join(map(str, [ - customer.id, # Customer ID - 0, # NPC type (0: Customer) - 0, # Favourite dish - -1, # Dish ID - 0, # Is thirsty - customer.avatar.to_string(True) # Avatar - ])) - - action = customer.action - - match customer.action: - # If the customer is walking, sending sit down to visitor client - case Action.WALK_TO_CHAIR: - action = Action.SIT_DOWN - - insert_info = '+'.join(map(str, [ - customer.id, # Customer ID - action, # Action type - customer.pos[0], # X - customer.pos[1] # Y - ])) - - # NAV does not split by '#' - response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) - - # NAC uses split by '#' - # https://github.com/GGR-Devs/Cafe-Main/blob/2911a43b4d0972d2f1386edd782d3382f031f36d/com/goodgamestudios/cafe/commands/NACCommand.as#L29 - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - - await server.send_response(client, response_spawn) - await server.send_response(client, response_insert) - - -class Customer: - def __init__(self, customer_id: int): - self.id: int = customer_id - self.avatar: Avatar = Avatar.get_random_avatar('Customer') - self.pos: List[int] = [0, 0] - self.task: Optional[asyncio.Task] = None - - self.dish = -1 - self.action = Action.INSERT - - async def start(self, server, client): - address = client.get_extra_info('peername') - player = server.clients[address] - - insert_info = '+'.join(map(str, [ - str(self.id), # Customer ID - Action.INSERT # Action type (0: Insert) - ])) - - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - - await self.spawn(server, player) - - for user in player.cafe.clients: - await server.send_response(user, response_insert) - - async def spawn(self, server, player): - self.task = current_task() - self.task.add_done_callback(self.remove_task) - - insert_info = '+'.join(map(str, [ - str(self.id), # Customer ID - Action.INSERT # Action type (0: Insert) - ])) - - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - - await self._spawn(server, player) - - for user in player.cafe.clients: - await server.send_response(user, response_insert) - - # print(f"[CUSTOMER] New spawned: {self.id}") - - await sleep(2) - - await self.wait_for_table(server, player) - - async def _spawn(self, server, player): - spawn_info = '+'.join(map(str, [ - self.id, # Customer ID - 0, # NPC type (0: Customer) - 0, # Favourite = Waiters priority - -1, # Dish ID - 0, # Is thirsty - self.avatar.to_string(True) # Avatar - ])) - - response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) - - self.pos = player.cafe.get_start_pos() - - for user in player.cafe.clients: - await server.send_response(user, response_spawn) - - async def wait_for_table(self, server, player): - self.task = current_task() - self.task.add_done_callback(self.remove_task) - - # print(f"[CUSTOMER] Started waiting for table: {self.id}") - - for _ in range(10): - table = get_random_table(player.cafe) - if table: - # print(f"[CUSTOMER] Got a free table: {self.id}") - table.busy = True - await self.go_to_chair(server, player, table) - return - await sleep(1) - - # print(f"[CUSTOMER] No free table found, leaving: {self.id}") - player.cafe.update_rating(-2) - server.db.update_cafe(player.id, rating=player.cafe.rating) - - await self.leave(server, player) - - async def go_to_chair(self, server, player, table): - self.task = current_task() - self.task.add_done_callback(self.remove_task) - - chair = table.connected_chair - - self.pos = chair.pos - - self.action = Action.WALK_TO_CHAIR - - await self._go_to_chair(server, player, chair) - - walking_time = get_time(player.cafe, player.cafe.player_start, chair.pos) - # print(f"[CUSTOMER] Started going to table: {self.id} pos: {chair.pos} walking time: {walking_time}s") - - await sleep(walking_time) - - # print(f"[CUSTOMER] Arrived to the chair: {self.id}") - - await self.sit_down(server, player, table) - - async def sit_down(self, server, player, table): - self.task = current_task() - self.task.add_done_callback(self.remove_task) - - await self._sit_down(server, player) - - await self.wait_for_waiter(server, player, table) - - async def _sit_down(self, server, player): - self.action = Action.SIT_DOWN - - response_info = '+'.join(map(str, [ - self.id, # Customer ID - Action.SIT_DOWN, # Action type (2: Sit down) - self.pos[0], # X - self.pos[1] # Y - ])) - - response = ExtensionResponse('nac', '-1', '0', response_info) - - for user in player.cafe.clients: - await server.send_response(user, response) - - async def wait_for_waiter(self, server, player, table): - # print(f"[CUSTOMER] Started waiting for waiter...") - waiter = None - for _ in range(25): - waiter = get_random_waiter(player.cafe, task=WaiterTask.SERVE) - if check_for_foods(player.cafe): - if waiter: - break - - # print(f"[CUSTOMER] Waiting for waiter...") - await sleep(1) - - if not waiter: - # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") - - table.busy = False - player.cafe.update_rating(-2) - server.db.update_cafe(player.id, rating=player.cafe.rating) - - await self.leave(server, player) - return - - elif waiter: - waiter.task = create_task(waiter.serve_food(server, player, self, table)) - # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") - - async def eat(self, server, player, table): - self.task = current_task() - self.task.add_done_callback(self.remove_task) - - self.action = Action.EAT - - # print(f'[CUSTOMER] Started eating: {self.id} Dish: {self.dish}') - - await self._eat(server, player) - - await sleep(25) - - chair = table.connected_chair - - chair.dish_status = 3 - - player.cafe.update_rating(1) - server.db.update_cafe(player.id, rating=player.cafe.rating) - - create_task(wait_for_cleaning(server, player, table)) - - await self.leave(server, player) - - async def _eat(self, server, player): - response_info = '+'.join(map(str, [ - self.id, # Customer ID - Action.EAT, # Action type (3: Eat) - self.pos[0], # X - self.pos[1] # Y - ])) - - response = ExtensionResponse('nac', '-1', '0', response_info) - - for user in player.cafe.clients: - await server.send_response(user, response) - - async def _go_to_chair(self, server, player, chair): - response_info = '+'.join(map(str, [ - self.id, # Customer ID - Action.WALK_TO_CHAIR, # Action type (1: Walk to chair) - chair.pos[0], # X - chair.pos[1] # Y - ])) - - response = ExtensionResponse('nac', '-1', '0', response_info) - - for user in player.cafe.clients: - await server.send_response(user, response) - - async def leave(self, server, player): - self.action = Action.LEAVE - - insert_info = '+'.join(map(str, [ - str(self.id), # Customer ID - Action.LEAVE # Action type (4: Leave) - ])) - - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - - player.cafe.customers.remove(self) - - for user in player.cafe.clients: - await server.send_response(user, response_insert) - - def remove_task(self, _: 'Task' = None): - self.task = None - - -async def spawn_cycle(server, client) -> None: - address = client.get_extra_info('peername') - player = server.clients[address] - - while True: - rating = player.cafe.rating - - if rating < 50: - spawn_interval = uniform(10, 20) - elif 50 <= rating < 250: - spawn_interval = uniform(4.5, 8) - else: - spawn_interval = uniform(0.5, 5) - - await sleep(spawn_interval) - - new_id = player.cafe.customers[-1].id + 1 if player.cafe.customers else 100 - new_customer = Customer(new_id) - player.cafe.customers.append(new_customer) - - new_customer.task = create_task(new_customer.start(server, client)) - new_customer.task.add_done_callback(new_customer.remove_task) diff --git a/objects/objects/waiter.py b/objects/objects/waiter.py deleted file mode 100644 index 60095ec..0000000 --- a/objects/objects/waiter.py +++ /dev/null @@ -1,332 +0,0 @@ -from asyncio import current_task, sleep, Task, gather, create_task -from random import choice -from typing import TYPE_CHECKING, Optional - -from astar import AStar -from objects.avatar import Avatar -from objects.cafe_object import ObjectType, CafeObject -from response import ExtensionResponse - -if TYPE_CHECKING: - from server import CafeServer - from asyncio import StreamWriter - - -class Priority: - CLEANING = 0 - BOTH = 50 - SERVING = 100 - - -class Action: - INSERT = 0 - MOVE_TO_COUNTER = 5 - FEED = 6 - CLEAN = 7 - - -def get_random_counter(cafe) -> 'CafeObject': - counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER and get_time(cafe, cafe.get_start_pos(), - obj.pos) != -1] - counters_with_food = [counter for counter in counters if counter.dish_id != -1] - - if counters: - if counters_with_food: - return choice(counters_with_food) - return choice(counters) - - -def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: - cafe_size = cafe.size - obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] - - for i in range(cafe_size): - obstacles[i][0] = 1 - obstacles[0][i] = 1 - for obj in cafe.objects: - obstacles[obj.pos[0]][obj.pos[1]] = 1 - - obstacles[start_pos[0]][start_pos[1]] = 0 - obstacles[end_pos[0]][end_pos[1]] = 0 - - path = AStar(obstacles, start_pos, tuple(end_pos)).solve() - - if path: - distance = len(path) - 1 - time = distance / 2.30 # WAITERS ARE FASTER THAN CUSTOMERS (40->45) - - # print(f'[WAITER-ASTAR]: Start: {start_pos} End: {end_pos}') - # print(f'[WAITER-ASTAR]: Time = {time}s') - # print(f'[WAITER-ASTAR]: Distance = {distance}') - # print(f'[WAITER-ASTAR]: Path = {path}') - else: - time = -1 - - return time - - -async def spawn_waiters(server, client) -> None: - address = client.get_extra_info('peername') - player = server.clients[address] - - # BUG NO TASK GIVEN! - # Happens when the player enter edit mode instantly, and leave. The waiters getting teleported to the counter - await gather(*(waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters)) - - counter = get_random_counter(player.cafe) - - # PATH FOUND TO COUNTER - if counter: - for waiter in player.cafe.waiters: - waiter.counter = counter - - await sleep(2) - - await gather(*[waiter.go_to_counter(server, player.cafe, - counter, True) for waiter in player.cafe.waiters]) - - -async def spawn_waiters_to_client(server, client, wanted_cafe): - for waiter in wanted_cafe.waiters: - add_info = '+'.join(map(str, [ - waiter.id, # Waiter ID - 1, # NPC type (1: Waiter) - waiter.priority, # Priority - -1, # Dish ID (unnecessary for waiters) - waiter.avatar.to_string(True), # Avatar - ])) - - counter = waiter.counter - - # PATH FOUND TO COUNTER - if counter is not None: - pos = counter.pos - else: - # PATH NOT FOUND TO COUNTER - pos = wanted_cafe.get_start_pos() - - insert_info = '+'.join(map(str, [ - waiter.id, # Waiter ID - waiter.action, # Action type - pos[0], # X - pos[1] # Y - ])) - - response_add = ExtensionResponse('nav', '-1', '0', add_info) - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - - await server.send_response(client, response_add) - await server.send_response(client, response_insert) - - -class Waiter: - def __init__(self, waiter_id: int, name: str, gender: int, priority: int, - avatar: Avatar = None): - self.id: int = waiter_id - self.name: str = name - self.gender: int = gender - self.priority = priority - self.avatar: Avatar = avatar if avatar else Avatar.get_random_avatar(name, gender) - self.pos: list[int] = [0, 0] - self.counter_pos: list[int] = [0, 0] - self.counter = None - - self.dish = -1 - self.action = Action.INSERT - - self.task: Optional['Task'] = None - - async def spawn(self, server, player_cafe) -> None: - insert_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.INSERT # Action type (0: Insert) - ])) - - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - - await self._spawn(server, player_cafe) - - for user in player_cafe.clients: - await server.send_response(user, response_insert) - - async def _spawn(self, server, player_cafe) -> None: - self.pos = player_cafe.get_start_pos() - - add_info = '+'.join(map(str, [ - self.id, # Waiter ID - 1, # NPC type (1: Waiter) - self.priority, # Priority - -1, # Dish ID (unnecessary for waiters) - self.avatar.to_string(True), # Avatar - ])) - - response_add = ExtensionResponse('nav', '-1', '0', add_info) - - for user in player_cafe.clients: - await server.send_response(user, response_add) - - async def go_to_counter(self, server, player_cafe, counter=None, - need_task: bool = False): - self.action = Action.MOVE_TO_COUNTER - - if need_task: - self.task = current_task() - self.task.add_done_callback(self.remove_task) - - if not counter: - counter = get_random_counter(player_cafe) - - # PATH FOUND TO COUNTER - if counter: - response_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.MOVE_TO_COUNTER, # Action type (5: go to counter) - counter.pos[0], # Counter X - counter.pos[1] # Counter Y - ])) - - response = ExtensionResponse('nac', '-1', '0', response_info) - - for user in player_cafe.clients: - await server.send_response(user, response) - - self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) - self.counter = counter - - # print(f"[WAITER] Waiter should staying at: {self.counter_pos}") - - walking_time = get_time(player_cafe, self.pos, counter.pos) - - await sleep(walking_time + 0.5) - - self.pos = counter.pos - - async def serve_food(self, server, player, customer, table): - self.action = Action.FEED - - counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) - - if not counter.dish_amount >= 1 or counter.dish_id == -1: - await self.go_to_counter(server, player.cafe) - counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) - - if not counter.dish_id == -1: - self.dish = counter.dish_id - counter.dish_amount -= 1 - - if counter.dish_amount <= 0: - counter.dish_id = -1 - # print(f"[WAITER] Counter is empty") - - # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") - # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") - - server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) - - # print(f"[WAITER] Waiter started to serve to: {customer.id} Waiter: {self.avatar.username} Pos: {self.pos}") - self.task = current_task() - self.task.add_done_callback(self.remove_task) - - response_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.FEED, # Action type (6: feed) - customer.pos[0], # Customer X - customer.pos[1] # Customer Y - ])) - - response = ExtensionResponse('nac', '-1', '0', response_info) - - for user in player.cafe.clients: - await server.send_response(user, response) - - chair = player.cafe.get_object(customer.pos[0], customer.pos[1]) - - chair.dish_id = self.dish - chair.dish_status = 1 - - self.pos = customer.pos - - time_to_wait = get_time(player.cafe, self.counter_pos, customer.pos) - # print(f"[WAITER] Time to waiter: {self.id} arrive to the customer: {customer.id} is: {time_to_wait}s") - - await sleep(time_to_wait + 0.5) - - # print(f"[WAITER] Waiter: {self.id} is arrived to: {customer.id}") - - customer.dish = self.dish - - self.dish = -1 - - customer.task = create_task(customer.eat(server, player, table)) - - await self.go_to_counter(server, player.cafe) - - else: - # print(f"[WAITER] The player cleaned the counter: {self.pos}") - # print(f"[WAITER] Customer: {customer.id} is going to wait for waiter again...") - await customer.wait_for_waiter(server, player, table) - - async def clean_table(self, server, player_cafe, table): - # print(f"[WAITER] Waiter {self.avatar.username} is assigned to clean table {table.pos}") - self.action = Action.CLEAN - - response_info = '+'.join(map(str, [ - self.id, # Waiter ID - Action.CLEAN, # Action type (7: clean) - table.connected_chair.pos[0], # Chair X - table.connected_chair.pos[1] # Chair Y - ])) - - time_to_walk = get_time(player_cafe, self.counter_pos, table.connected_chair.pos) - - response = ExtensionResponse('nac', '-1', '0', response_info) - - for user in player_cafe.clients: - await server.send_response(user, response) - - chair = table.connected_chair - chair.dish_id = -1 - chair.dish_status = 0 - - await sleep(time_to_walk) - - # print(f"[WAITER] Waiter {self.avatar.username} is started to clean the table: {table.pos}") - - self.pos = player_cafe.get_shortest_pos(self.pos, table.connected_chair.pos) - - await sleep(2) - - # print(f"[WAITER] Waiter is cleaned the table: {1 + 1}") - - # print(f"[WAITER] Table cleaning finished, going back...") - - table.busy = False - - await self.go_to_counter(server, player_cafe) - - def remove_task(self, _: 'Task' = None): - # print(f"[WAITER] Waiter: {self.avatar.username} Task finished: {self.task.get_coro()}") - self.task = None - - def to_dict(self): - waiter_dict = { - 'waiter_id': self.id, - 'name': self.name, - 'gender': self.gender, - 'priority': self.priority, - 'avatar': self.avatar.to_string() - } - - return waiter_dict - - @classmethod - def from_dict(cls, waiter_dict: dict) -> 'Waiter': - waiter = Waiter( - waiter_dict.get('waiter_id'), - waiter_dict.get('name'), - waiter_dict.get('gender'), - waiter_dict.get('priority'), - Avatar.from_string(waiter_dict.get('avatar')) - ) - - return waiter From 80ac1042fe2f10dcea197cdc0cec78fd92ba9ac5 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 28 Sep 2024 18:56:23 +0200 Subject: [PATCH 23/43] Customers tasks update. Each task can callable again --- objects/customer.py | 254 ++++++++++++++++++++++---------------------- objects/waiter.py | 46 +++++--- 2 files changed, 159 insertions(+), 141 deletions(-) diff --git a/objects/customer.py b/objects/customer.py index 1e1a791..bff337e 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -48,10 +48,10 @@ def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: distance = len(path) - 1 time = distance / 1.85 # OLD -> 1.95 TESTED: 1.90 (customers should need to wait few sec before sitting down) - # print(f'[CUSTOMER-ASTAR]: Start: {start_pos} End: {end_pos}') - # print(f'[CUSTOMER-ASTAR]: Time = {time}s') - # print(f'[CUSTOMER-ASTAR]: Distance = {distance}') - # print(f'[CUSTOMER-ASTAR]: Path = {path}') + ## print(f'[CUSTOMER-ASTAR]: Start: {start_pos} End: {end_pos}') + ## print(f'[CUSTOMER-ASTAR]: Time = {time}s') + ## print(f'[CUSTOMER-ASTAR]: Distance = {distance}') + ## print(f'[CUSTOMER-ASTAR]: Path = {path}') else: time = -1 @@ -105,11 +105,11 @@ async def wait_for_waiter(cafe) -> Optional['Waiter']: return None -async def wait_for_cleaning(server, cafe, table): +async def wait_for_cleaning(server, player, table): while True: - waiter = get_random_waiter(cafe, task=WaiterTask.CLEAN) + waiter = get_random_waiter(player.cafe, task=WaiterTask.CLEAN) if waiter: - waiter.task = create_task(waiter.clean_table(server, cafe, table)) + waiter.task = create_task(waiter.clean_table(server, player.cafe, table)) waiter.task.add_done_callback(waiter.remove_task) break @@ -163,198 +163,202 @@ def __init__(self, customer_id: int): self.dish = -1 self.action = Action.INSERT - async def start(self, server, client) -> None: + async def start(self, server, client): address = client.get_extra_info('peername') player = server.clients[address] - await self.spawn(server, player.cafe) + insert_info = '+'.join(map(str, [ + str(self.id), # Customer ID + Action.INSERT # Action type (0: Insert) + ])) - # print(f"[CUSTOMER] New spawned: {self.id}") + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - await sleep(2) + await self.spawn(server, player) - # print(f"[CUSTOMER] Started waiting for table: {self.id}") + for user in player.cafe.clients: + await server.send_response(user, response_insert) - table = await wait_for_table(player.cafe) + async def spawn(self, server, player): + self.task = current_task() + self.task.add_done_callback(self.remove_task) - if not table: - # print(f"[CUSTOMER] No free table found, leaving: {self.id}") - player.cafe.update_rating(-2) - server.db.update_cafe(player.id, rating=player.cafe.rating) + insert_info = '+'.join(map(str, [ + str(self.id), # Customer ID + Action.INSERT # Action type (0: Insert) + ])) - await self.leave(server, player.cafe) - return + response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - # print(f"[CUSTOMER] Got a free table: {self.id}") + await self._spawn(server, player) - table.busy = True - chair = table.connected_chair + for user in player.cafe.clients: + await server.send_response(user, response_insert) - await self.go_to_chair(server, player.cafe, chair) + # print(f"[CUSTOMER] New spawned: {self.id}") - walking_time = get_time(player.cafe, player.cafe.player_start, chair.pos) + await sleep(2) - # print(f"[CUSTOMER] Started going to table: {self.id} pos: {chair.pos} walking time: {walking_time}s") + await self.wait_for_table(server, player) - await sleep(walking_time) + async def _spawn(self, server, player): + spawn_info = '+'.join(map(str, [ + self.id, # Customer ID + 0, # NPC type (0: Customer) + 0, # Favourite = Waiters priority + -1, # Dish ID + 0, # Is thirsty + self.avatar.to_string(True) # Avatar + ])) - await self.sit_down(server, player.cafe) + response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) - # print(f"[CUSTOMER] Sat down: {self.id}") + self.pos = player.cafe.get_start_pos() - # print(f"[CUSTOMER] Started waiting for a Waiter: {self.id}") + for user in player.cafe.clients: + await server.send_response(user, response_spawn) - waiter = await wait_for_waiter(player.cafe) + async def wait_for_table(self, server, player): + self.task = current_task() + self.task.add_done_callback(self.remove_task) - if not waiter: - table.busy = False - # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") - player.cafe.update_rating(-2) - server.db.update_cafe(player.id, rating=player.cafe.rating) + # print(f"[CUSTOMER] Started waiting for table: {self.id}") - await self.leave(server, player.cafe) - return + for _ in range(10): + table = get_random_table(player.cafe) + if table: + # print(f"[CUSTOMER] Got a free table: {self.id}") + table.busy = True + await self.go_to_chair(server, player, table) + return + await sleep(1) - waiter.task = create_task(waiter.serve_food(server, player, self.pos)) + # print(f"[CUSTOMER] No free table found, leaving: {self.id}") + player.cafe.update_rating(-2) + server.db.update_cafe(player.id, rating=player.cafe.rating) - # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") + await self.leave(server, player) - # print(f"[CUSTOMER] Waiter started to serve to: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") - # print(f"[CUSTOMER] Time to waiter arrive to: {self.id} is: {time_to_wait}s") + async def go_to_chair(self, server, player, table): + self.task = current_task() + self.task.add_done_callback(self.remove_task) - await waiter.task - if waiter.task and not waiter.dish == -1: - self.dish = waiter.dish - # print(f"[CUSTOMER] Waiter arrived to: {self.id} Waiter pos: {waiter.pos}") + chair = table.connected_chair - waiter.dish = -1 + self.pos = chair.pos - waiter.task = create_task(waiter.go_to_counter(server, player.cafe)) - waiter.task.add_done_callback(waiter.remove_task) + self.action = Action.WALK_TO_CHAIR - # print(f"[CUSTOMER] The waiter is going to a counter who served: {self.id} Waiter: {waiter.avatar.username}") + await self._go_to_chair(server, player, chair) - await self.eat(server, player.cafe) + walking_time = get_time(player.cafe, player.cafe.player_start, chair.pos) + # print(f"[CUSTOMER] Started going to table: {self.id} pos: {chair.pos} walking time: {walking_time}s") - # print(f'[CUSTOMER] Started eating: {self.id} Dish: {self.dish}') + await sleep(walking_time) - await sleep(25) + # print(f"[CUSTOMER] Arrived to the chair: {self.id}") - # print(f"[CUSTOMER] Task finished! Leaving now: {self.id}") + await self.sit_down(server, player, table) - dish_info = get_dish_info(self.dish) - income = dish_info.get('income') + async def sit_down(self, server, player, table): + self.task = current_task() + self.task.add_done_callback(self.remove_task) - player.cash += income + await self._sit_down(server, player) - server.db.update_player(player.id, cash=player.cash) + await self.wait_for_waiter(server, player, table) - await self.leave(server, player.cafe) + async def _sit_down(self, server, player): + self.action = Action.SIT_DOWN - chair.dish_status = 3 + response_info = '+'.join(map(str, [ + self.id, # Customer ID + Action.SIT_DOWN, # Action type (2: Sit down) + self.pos[0], # X + self.pos[1] # Y + ])) - player.cafe.update_rating(1) - server.db.update_cafe(player.id, rating=player.cafe.rating) + response = ExtensionResponse('nac', '-1', '0', response_info) - create_task(wait_for_cleaning(server, player.cafe, table)) + for user in player.cafe.clients: + await server.send_response(user, response) - # FALLBACK TO DON'T SERVE FROM EMPTY COUNTER - # Maybe we can do a better logic, but this caused by the player, who removed the all dishes - else: - table.busy = False + async def wait_for_waiter(self, server, player, table): + # print(f"[CUSTOMER] Started waiting for waiter...") + waiter = None + for _ in range(25): + waiter = get_random_waiter(player.cafe, task=WaiterTask.SERVE) + if check_for_foods(player.cafe): + if waiter: + break - # print(f"[CUSTOMER] Waiter: {waiter.avatar.username} failed to serve food to customer {self.id}." - # f" REASON: (COUNTER DISH ID IS -1)") - # print(f"[CUSTOMER] Waiter task: {waiter.task}") + # print(f"[CUSTOMER] Waiting for waiter...") + await sleep(1) - waiter.task = None + if not waiter: + # print(f"[CUSTOMER] Couldn't get a waiter, leaving: {self.id}") + table.busy = False player.cafe.update_rating(-2) server.db.update_cafe(player.id, rating=player.cafe.rating) - await self.leave(server, player.cafe) - - async def spawn(self, server, player_cafe) -> None: - self.task = current_task() - - insert_info = '+'.join(map(str, [ - str(self.id), # Customer ID - Action.INSERT # Action type (0: Insert) - ])) - - response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - - await self._spawn(server, player_cafe) + await self.leave(server, player) + return - for user in player_cafe.clients: - await server.send_response(user, response_insert) + elif waiter: + waiter.task = create_task(waiter.serve_food(server, player, self, table)) + # print(f"[CUSTOMER] Got a waiter: {self.id} Waiter: {waiter.avatar.username} Pos: {waiter.pos}") - async def _spawn(self, server, player_cafe) -> None: - spawn_info = '+'.join(map(str, [ - self.id, # Customer ID - 0, # NPC type (0: Customer) - 0, # Favourite = Waiters priority - -1, # Dish ID - 0, # Is thirsty - self.avatar.to_string(True) # Avatar - ])) + async def eat(self, server, player, table): + self.task = current_task() + self.task.add_done_callback(self.remove_task) - response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) + self.action = Action.EAT - self.pos = player_cafe.get_start_pos() + # print(f'[CUSTOMER] Started eating: {self.id} Dish: {self.dish}') - for user in player_cafe.clients: - await server.send_response(user, response_spawn) + await self._eat(server, player) - async def go_to_chair(self, server, player_cafe, chair) -> None: - self.task = current_task() - self.pos = chair.pos + await sleep(25) - self.action = Action.WALK_TO_CHAIR + chair = table.connected_chair - response_info = '+'.join(map(str, [ - self.id, # Customer ID - Action.WALK_TO_CHAIR, # Action type (1: Walk to chair) - chair.pos[0], # X - chair.pos[1] # Y - ])) + chair.dish_status = 3 - response = ExtensionResponse('nac', '-1', '0', response_info) + player.cafe.update_rating(1) + server.db.update_cafe(player.id, rating=player.cafe.rating) - for user in player_cafe.clients: - await server.send_response(user, response) + create_task(wait_for_cleaning(server, player, table)) - async def sit_down(self, server, player_cafe) -> None: - self.action = Action.SIT_DOWN + await self.leave(server, player) + async def _eat(self, server, player): response_info = '+'.join(map(str, [ self.id, # Customer ID - Action.SIT_DOWN, # Action type (2: Sit down) + Action.EAT, # Action type (3: Eat) self.pos[0], # X self.pos[1] # Y ])) response = ExtensionResponse('nac', '-1', '0', response_info) - for user in player_cafe.clients: + for user in player.cafe.clients: await server.send_response(user, response) - async def eat(self, server, player_cafe) -> None: - self.action = Action.EAT - + async def _go_to_chair(self, server, player, chair): response_info = '+'.join(map(str, [ self.id, # Customer ID - Action.EAT, # Action type (3: Eat) - self.pos[0], # X - self.pos[1] # Y + Action.WALK_TO_CHAIR, # Action type (1: Walk to chair) + chair.pos[0], # X + chair.pos[1] # Y ])) response = ExtensionResponse('nac', '-1', '0', response_info) - for user in player_cafe.clients: + for user in player.cafe.clients: await server.send_response(user, response) - async def leave(self, server, player_cafe) -> None: + async def leave(self, server, player): self.action = Action.LEAVE insert_info = '+'.join(map(str, [ @@ -364,9 +368,9 @@ async def leave(self, server, player_cafe) -> None: response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - player_cafe.customers.remove(self) + player.cafe.customers.remove(self) - for user in player_cafe.clients: + for user in player.cafe.clients: await server.send_response(user, response_insert) def remove_task(self, _: 'Task' = None): diff --git a/objects/waiter.py b/objects/waiter.py index 445fcdf..60095ec 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -1,4 +1,4 @@ -from asyncio import current_task, sleep, Task, gather +from asyncio import current_task, sleep, Task, gather, create_task from random import choice from typing import TYPE_CHECKING, Optional @@ -201,7 +201,7 @@ async def go_to_counter(self, server, player_cafe, counter=None, self.pos = counter.pos - async def serve_food(self, server, player, customer_pos) -> bool: + async def serve_food(self, server, player, customer, table): self.action = Action.FEED counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) @@ -216,18 +216,22 @@ async def serve_food(self, server, player, customer_pos) -> bool: if counter.dish_amount <= 0: counter.dish_id = -1 - # # print(f"[WAITER] Counter is empty") + # print(f"[WAITER] Counter is empty") # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) + # print(f"[WAITER] Waiter started to serve to: {customer.id} Waiter: {self.avatar.username} Pos: {self.pos}") + self.task = current_task() + self.task.add_done_callback(self.remove_task) + response_info = '+'.join(map(str, [ self.id, # Waiter ID Action.FEED, # Action type (6: feed) - customer_pos[0], # Customer X - customer_pos[1] # Customer Y + customer.pos[0], # Customer X + customer.pos[1] # Customer Y ])) response = ExtensionResponse('nac', '-1', '0', response_info) @@ -235,22 +239,32 @@ async def serve_food(self, server, player, customer_pos) -> bool: for user in player.cafe.clients: await server.send_response(user, response) - chair = player.cafe.get_object(customer_pos[0], customer_pos[1]) + chair = player.cafe.get_object(customer.pos[0], customer.pos[1]) chair.dish_id = self.dish chair.dish_status = 1 - self.pos = customer_pos + self.pos = customer.pos - time_to_wait = get_time(player.cafe, self.counter_pos, customer_pos) + time_to_wait = get_time(player.cafe, self.counter_pos, customer.pos) + # print(f"[WAITER] Time to waiter: {self.id} arrive to the customer: {customer.id} is: {time_to_wait}s") await sleep(time_to_wait + 0.5) - return True - # FALLBACK TO DON'T SERVE FROM EMPTY COUNTER - # Maybe we can do a better logic, but this caused by the player, who removed the all dishes + # print(f"[WAITER] Waiter: {self.id} is arrived to: {customer.id}") + + customer.dish = self.dish + + self.dish = -1 + + customer.task = create_task(customer.eat(server, player, table)) + + await self.go_to_counter(server, player.cafe) + else: - return False + # print(f"[WAITER] The player cleaned the counter: {self.pos}") + # print(f"[WAITER] Customer: {customer.id} is going to wait for waiter again...") + await customer.wait_for_waiter(server, player, table) async def clean_table(self, server, player_cafe, table): # print(f"[WAITER] Waiter {self.avatar.username} is assigned to clean table {table.pos}") @@ -270,6 +284,10 @@ async def clean_table(self, server, player_cafe, table): for user in player_cafe.clients: await server.send_response(user, response) + chair = table.connected_chair + chair.dish_id = -1 + chair.dish_status = 0 + await sleep(time_to_walk) # print(f"[WAITER] Waiter {self.avatar.username} is started to clean the table: {table.pos}") @@ -284,10 +302,6 @@ async def clean_table(self, server, player_cafe, table): table.busy = False - chair = table.connected_chair - chair.dish_id = -1 - chair.dish_status = 0 - await self.go_to_counter(server, player_cafe) def remove_task(self, _: 'Task' = None): From 57429d11c989d5806889cf4b47180836e7544462 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:45:24 +0200 Subject: [PATCH 24/43] More NPC bug fixes... Everything should work now... --- objects/customer.py | 32 +++++++++---------- objects/waiter.py | 78 +++++++++++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/objects/customer.py b/objects/customer.py index bff337e..0c0dc44 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -48,10 +48,10 @@ def get_time(cafe, start_pos: list[int], end_pos: list[int]) -> float: distance = len(path) - 1 time = distance / 1.85 # OLD -> 1.95 TESTED: 1.90 (customers should need to wait few sec before sitting down) - ## print(f'[CUSTOMER-ASTAR]: Start: {start_pos} End: {end_pos}') - ## print(f'[CUSTOMER-ASTAR]: Time = {time}s') - ## print(f'[CUSTOMER-ASTAR]: Distance = {distance}') - ## print(f'[CUSTOMER-ASTAR]: Path = {path}') + # print(f'[CUSTOMER-ASTAR]: Start: {start_pos} End: {end_pos}') + # print(f'[CUSTOMER-ASTAR]: Time = {time}s') + # print(f'[CUSTOMER-ASTAR]: Distance = {distance}') + # print(f'[CUSTOMER-ASTAR]: Path = {path}') else: time = -1 @@ -95,16 +95,6 @@ async def wait_for_table(cafe) -> Optional['CafeObject']: return None -async def wait_for_waiter(cafe) -> Optional['Waiter']: - for _ in range(25): - waiter = get_random_waiter(cafe, task=WaiterTask.SERVE) - if check_for_foods(cafe): - if waiter: - return waiter - await sleep(1) - return None - - async def wait_for_cleaning(server, player, table): while True: waiter = get_random_waiter(player.cafe, task=WaiterTask.CLEAN) @@ -284,11 +274,14 @@ async def _sit_down(self, server, player): await server.send_response(user, response) async def wait_for_waiter(self, server, player, table): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + # print(f"[CUSTOMER] Started waiting for waiter...") waiter = None for _ in range(25): - waiter = get_random_waiter(player.cafe, task=WaiterTask.SERVE) if check_for_foods(player.cafe): + waiter = get_random_waiter(player.cafe, task=WaiterTask.SERVE) if waiter: break @@ -368,11 +361,16 @@ async def leave(self, server, player): response_insert = ExtensionResponse('nac', '-1', '0', insert_info) - player.cafe.customers.remove(self) - for user in player.cafe.clients: await server.send_response(user, response_insert) + walking_time = get_time(player.cafe, self.pos, player.cafe.get_start_pos()) + + # Need to remove after when the customer avatar got deleted from the game. + await sleep(walking_time + 3) + + player.cafe.customers.remove(self) + def remove_task(self, _: 'Task' = None): self.task = None diff --git a/objects/waiter.py b/objects/waiter.py index 60095ec..d2834a0 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -83,7 +83,7 @@ async def spawn_waiters(server, client) -> None: await sleep(2) await gather(*[waiter.go_to_counter(server, player.cafe, - counter, True) for waiter in player.cafe.waiters]) + counter) for waiter in player.cafe.waiters]) async def spawn_waiters_to_client(server, client, wanted_cafe): @@ -165,13 +165,11 @@ async def _spawn(self, server, player_cafe) -> None: for user in player_cafe.clients: await server.send_response(user, response_add) - async def go_to_counter(self, server, player_cafe, counter=None, - need_task: bool = False): + async def go_to_counter(self, server, player_cafe, counter=None): self.action = Action.MOVE_TO_COUNTER - if need_task: - self.task = current_task() - self.task.add_done_callback(self.remove_task) + self.task = current_task() + self.task.add_done_callback(self.remove_task) if not counter: counter = get_random_counter(player_cafe) @@ -202,31 +200,53 @@ async def go_to_counter(self, server, player_cafe, counter=None, self.pos = counter.pos async def serve_food(self, server, player, customer, table): - self.action = Action.FEED + self.task = current_task() + self.task.add_done_callback(self.remove_task) - counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) + # print(f'[WAITER] Called to serve food: {self.avatar.username} to customer: {customer.id}') - if not counter.dish_amount >= 1 or counter.dish_id == -1: - await self.go_to_counter(server, player.cafe) - counter = player.cafe.get_object(int(self.pos[0]), int(self.pos[1])) + # print(f'[WAITER] Waiter counter is: {self.counter.pos} Dish id: {self.counter.dish_id} Amount: { + # self.counter.dish_amount}') + + if self.counter.dish_id == -1: + # print(f'[WAITER] Arr! Waiter: {self.avatar.username} has staying at an empty counter! Trying to find + # another...') + + new_counter = get_random_counter(player.cafe) + if new_counter.dish_id == -1: + # print(f'[WAITER] No empty counters found! Calling customer to start waiting again. Waiter task + # exit, bye!') + customer.task = create_task(customer.wait_for_waiter(server, player, table)) + + return + + else: + # print(f'[WAITER] Waiter is going to the new counter {new_counter.pos}') + self.task = self.go_to_counter(server, player.cafe, new_counter) + await self.task + + # print(f'[WAITER] Waiter is arrived to the new counter {new_counter.pos}') - if not counter.dish_id == -1: - self.dish = counter.dish_id - counter.dish_amount -= 1 + # print(f'[WAITER] Calling again to serve the dish to the customer: {customer.id}') + await self.serve_food(server, player, customer, table) - if counter.dish_amount <= 0: - counter.dish_id = -1 + # Cant cause loop, only the player can cause loop by deleting the dishes always + + else: + self.action = Action.FEED + + self.dish = self.counter.dish_id + self.counter.dish_amount -= 1 + + if self.counter.dish_amount <= 0: + self.counter.dish_id = -1 # print(f"[WAITER] Counter is empty") # print(f"[WAITER] Waiter: {self.avatar.username} has dish: {self.dish} in their hands") - # print(f"[WAITER] Counter data: DISH ID: {counter.dish_id} AMOUNT LEFT: {counter.dish_amount}") + # print(f"[WAITER] Counter data: DISH ID: {self.counter.dish_id} AMOUNT LEFT: {self.counter.dish_amount}") server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) - # print(f"[WAITER] Waiter started to serve to: {customer.id} Waiter: {self.avatar.username} Pos: {self.pos}") - self.task = current_task() - self.task.add_done_callback(self.remove_task) - response_info = '+'.join(map(str, [ self.id, # Waiter ID Action.FEED, # Action type (6: feed) @@ -239,33 +259,29 @@ async def serve_food(self, server, player, customer, table): for user in player.cafe.clients: await server.send_response(user, response) - chair = player.cafe.get_object(customer.pos[0], customer.pos[1]) + chair = table.connected_chair chair.dish_id = self.dish chair.dish_status = 1 - self.pos = customer.pos - time_to_wait = get_time(player.cafe, self.counter_pos, customer.pos) + # print(f"[WAITER] Time to waiter: {self.id} arrive to the customer: {customer.id} is: {time_to_wait}s") await sleep(time_to_wait + 0.5) - # print(f"[WAITER] Waiter: {self.id} is arrived to: {customer.id}") - customer.dish = self.dish self.dish = -1 + self.pos = customer.pos + + # print(f"[WAITER] Waiter: {self.id} is arrived to: {customer.id}") + customer.task = create_task(customer.eat(server, player, table)) await self.go_to_counter(server, player.cafe) - else: - # print(f"[WAITER] The player cleaned the counter: {self.pos}") - # print(f"[WAITER] Customer: {customer.id} is going to wait for waiter again...") - await customer.wait_for_waiter(server, player, table) - async def clean_table(self, server, player_cafe, table): # print(f"[WAITER] Waiter {self.avatar.username} is assigned to clean table {table.pos}") self.action = Action.CLEAN From 762e88fecf8d7fd5a8e16a2a11d2370d08170c70 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:58:57 +0200 Subject: [PATCH 25/43] Added vending machine --- commands/edi.py | 20 ++++++++++ commands/ffc.py | 51 ++++++++++++++++++++++++ commands/ffn.py | 75 +++++++++++++++++++++++++++++++++++ objects/customer.py | 97 ++++++++++++++++++++++++++++++++++++++++++++- objects/player.py | 6 +++ utils/items.py | 17 ++++++++ 6 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 commands/ffc.py create mode 100644 commands/ffn.py diff --git a/commands/edi.py b/commands/edi.py index afbfb03..7cbddca 100644 --- a/commands/edi.py +++ b/commands/edi.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING +from commands.asy import handle_asy +from objects.cafe_object import ObjectType from objects.customer import spawn_cycle from objects.waiter import spawn_waiters from response import ExtensionResponse @@ -26,6 +28,11 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) player.cafe.customer_cycle = create_task(spawn_cycle(server, client)) case 1: + vending_machine = None + for obj in player.cafe.objects: + if obj.type == ObjectType.VENDING: + vending_machine = obj + for waiter in player.cafe.waiters: if waiter.task: waiter.task.cancel() @@ -38,6 +45,19 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) if customer.task: customer.task.cancel() + # TO UPDATE THE VENDING MACHINE AMOUNT IN VISUAL TOO! + # THE ASY NEED TO SYNC THE MONEY! + # THE XP IS NOT SYNCED WITH THE CLIENT :( + + if customer.action == 9: + await customer._use_vending_machine(server, player, vending_machine) + + if vending_machine.fast_food_amount < 1: + vending_machine.fast_food_id = -1 + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) + + await handle_asy(server, client) + player.cafe.customers = [] for table in player.cafe.available_tables: diff --git a/commands/ffc.py b/commands/ffc.py new file mode 100644 index 0000000..f03ba00 --- /dev/null +++ b/commands/ffc.py @@ -0,0 +1,51 @@ +from typing import TYPE_CHECKING +from response import ExtensionResponse +from utils.items import get_fast_food_info +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +async def handle_ffc(server: 'CafeServer', client: 'StreamWriter', *params: str) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + pos_x = int(params[1]) + pos_y = int(params[2]) + drink_id = int(params[3]) + + drink = get_fast_food_info(drink_id) + if not drink: + return + + cost_cash = drink['cash'] + cost_gold = drink['gold'] + amount = drink['amount'] + + if not cost_cash == 0: + if player.cash < cost_cash: + response = ExtensionResponse('ffc', '-1', '4') + await server.send_response(client, response) + return + else: + player.cash -= cost_cash + server.db.update_player(player.id, cash=player.cash) + + if not cost_gold == 0: + if player.gold < cost_gold: + response = ExtensionResponse('ffc', '-1', '4') + await server.send_response(client, response) + return + else: + player.gold -= cost_gold + server.db.update_player(player.id, gold=player.gold) + + obj = player.cafe.get_object(pos_x, pos_y) + obj.fast_food_id = drink_id + obj.fast_food_amount = amount + + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) + + for user in player.cafe.clients: + response = ExtensionResponse('ffc', '-1', '0', params[1], params[2], str(drink_id)) + await server.send_response(user, response) diff --git a/commands/ffn.py b/commands/ffn.py new file mode 100644 index 0000000..a4f4fcd --- /dev/null +++ b/commands/ffn.py @@ -0,0 +1,75 @@ +from math import ceil +from typing import TYPE_CHECKING + +from objects.cafe import get_time +from response import ExtensionResponse + +from utils.items import get_fast_food_info + +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +class ErrorCodes: + noFrosty_goShop = '999' + allServed = '103' + notReachable = '36' + + +async def handle_ffn(server: 'CafeServer', client: 'StreamWriter', *params: str) -> None: + address = client.get_extra_info('peername') + player = server.clients[address] + + customer_id = int(params[1]) + obj_pos_x = int(params[2]) + obj_pos_y = int(params[3]) + + obj = player.cafe.get_object(obj_pos_x, obj_pos_y) + + if not obj: + # not really need, the game sends before the server, but we should keep it + response = ExtensionResponse('ffn', '-1', ErrorCodes.noFrosty_goShop) + await server.send_response(client, response) + return + + if not obj.fast_food_amount > 0: + response = ExtensionResponse('ffn', '-1', ErrorCodes.allServed) + await server.send_response(client, response) + return + + facing_pos = [0, 0] + + nearby_pos = obj.get_nearby_pos() + for pos, rotation in nearby_pos.items(): + if rotation == obj.rotation: + facing_pos = pos + + if get_time(player.cafe, facing_pos) == -1: + response = ExtensionResponse('ffn', '-1', ErrorCodes.notReachable) + await server.send_response(client, response) + return + + for customer in player.cafe.customers: + if customer.id == customer_id: + customer.action = 9 + break + + fast_food_info = get_fast_food_info(obj.fast_food_id) + income = fast_food_info.get('income') + xp = fast_food_info.get('xp') + rating = fast_food_info.get('rating') + + player.cafe.update_rating(rating) + + player.cash += income + player.xp += ceil(xp * (player.get_level() + 18) / 190) + + obj.fast_food_amount -= 1 + + server.db.update_player(player.id, cash=player.cash, xp=player.xp) + + server.db.update_cafe(player.id, rating=player.cafe.rating, objects=player.cafe.get_objects_as_json()) + + response = ExtensionResponse('ffn', '-1', '0', str(customer_id), str(obj_pos_x), str(obj_pos_y)) + await server.send_response(client, response) diff --git a/objects/customer.py b/objects/customer.py index 0c0dc44..d184459 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -21,6 +21,8 @@ class Action: SIT_DOWN = 2 EAT = 3 LEAVE = 4 + FAST_FOOD = 8 + GOTO_VENDING_MACHINE = 9 LEAVE_COMPLETE = 41 @@ -108,7 +110,7 @@ async def wait_for_cleaning(server, player, table): async def spawn_customers_to_client(server, client, wanted_cafe): for customer in wanted_cafe.customers: - if customer.action not in [Action.LEAVE]: + if customer.action not in [Action.LEAVE, Action.GOTO_VENDING_MACHINE, Action.FAST_FOOD]: spawn_info = '+'.join(map(str, [ customer.id, # Customer ID 0, # NPC type (0: Customer) @@ -152,6 +154,7 @@ def __init__(self, customer_id: int): self.dish = -1 self.action = Action.INSERT + self.is_thirsty = 0 async def start(self, server, client): address = client.get_extra_info('peername') @@ -164,6 +167,11 @@ async def start(self, server, client): response_insert = ExtensionResponse('nac', '-1', '0', insert_info) + if player.get_level() >= 6: + random_number = randint(1, 100) + if random_number < 5: + self.is_thirsty = 1 + await self.spawn(server, player) for user in player.cafe.clients: @@ -189,6 +197,13 @@ async def spawn(self, server, player): await sleep(2) + if self.action == Action.GOTO_VENDING_MACHINE: + for obj in player.cafe.objects: + if obj.type == ObjectType.VENDING: + await self.go_to_vending_machine(server, player, obj) + break + return + await self.wait_for_table(server, player) async def _spawn(self, server, player): @@ -197,7 +212,7 @@ async def _spawn(self, server, player): 0, # NPC type (0: Customer) 0, # Favourite = Waiters priority -1, # Dish ID - 0, # Is thirsty + self.is_thirsty, # Is thirsty self.avatar.to_string(True) # Avatar ])) @@ -248,6 +263,14 @@ async def go_to_chair(self, server, player, table): # print(f"[CUSTOMER] Arrived to the chair: {self.id}") + if self.action == Action.GOTO_VENDING_MACHINE: + for obj in player.cafe.objects: + if obj.type == ObjectType.VENDING: + table.busy = False + await self.go_to_vending_machine(server, player, obj) + break + return + await self.sit_down(server, player, table) async def sit_down(self, server, player, table): @@ -318,6 +341,13 @@ async def eat(self, server, player, table): chair.dish_status = 3 + dish_info = get_dish_info(self.dish) + income = dish_info.get('income') + + player.cash += income + + server.db.update_player(player.id, cash=player.cash) + player.cafe.update_rating(1) server.db.update_cafe(player.id, rating=player.cafe.rating) @@ -351,6 +381,69 @@ async def _go_to_chair(self, server, player, chair): for user in player.cafe.clients: await server.send_response(user, response) + async def go_to_vending_machine(self, server, player, vending_machine): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + walking_time = get_time(player.cafe, self.pos, vending_machine.pos) + + await self._go_to_vending_machine(server, player, vending_machine) + + await sleep(walking_time) + + await self.use_vending_machine(server, player, vending_machine) + + async def _go_to_vending_machine(self, server, player, vending_machine): + response_info = '+'.join(map(str, [ + self.id, # Customer ID + Action.GOTO_VENDING_MACHINE, # Action type (9: GOTO_VENDING_MACHINE) + vending_machine.pos[0], # X + vending_machine.pos[1] # Y + ])) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player.cafe.clients: + await server.send_response(user, response) + + async def use_vending_machine(self, server, player, vending_machine): + self.task = current_task() + self.task.add_done_callback(self.remove_task) + + self.action = Action.FAST_FOOD + + await self._use_vending_machine(server, player, vending_machine) + + facing_pos = vending_machine.pos + + nearby_pos = vending_machine.get_nearby_pos() + for pos, rotation in nearby_pos.items(): + if rotation == vending_machine.rotation: + facing_pos = pos + + self.pos = facing_pos + + if vending_machine.fast_food_amount < 1: + vending_machine.fast_food_id = -1 + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) + + await sleep(2) + + await self.leave(server, player) + + async def _use_vending_machine(self, server, player, vending_machine): + response_info = '+'.join(map(str, [ + self.id, # Customer ID + Action.FAST_FOOD, # Action type (8: Use fast food) + vending_machine.pos[0], # X + vending_machine.pos[1] # Y + ])) + + response = ExtensionResponse('nac', '-1', '0', response_info) + + for user in player.cafe.clients: + await server.send_response(user, response) + async def leave(self, server, player): self.action = Action.LEAVE diff --git a/objects/player.py b/objects/player.py index 1252dad..6d38631 100644 --- a/objects/player.py +++ b/objects/player.py @@ -103,6 +103,12 @@ def get_minutes_since_last_login(self) -> int: minutes_diff = time_diff.total_seconds() // 60 return floor(minutes_diff) + + def get_level(self) -> int: + if self.xp < 90: + return floor(pow(floor(self.xp / 10), 1 / 2)) + return floor(pow(floor(self.xp / 5), 1 / 3.72)) + def to_response(self, *params: str) -> list[str]: return [ *params, str(self.id), str(self.id), '0', str(self.cash), str(self.gold), str(self.xp), diff --git a/utils/items.py b/utils/items.py index 884581a..fe2c93a 100644 --- a/utils/items.py +++ b/utils/items.py @@ -72,3 +72,20 @@ def get_ingredient(ing_id: int) -> dict: } return {} + + +def get_fast_food_info(drink_id: int) -> dict: + root = tree.getroot() + + for wod in root.findall("wod"): + if int(wod.attrib.get("id")) == drink_id: + return { + "cash": int(wod.attrib.get("cash", 0)), + "gold": int(wod.attrib.get("gold", 0)), + "xp": int(wod.attrib.get("xp", 0)), + "income": int(wod.attrib.get("incomePerServing", 0)), + "amount": int(wod.attrib.get("servings", 0)), + "rating": int(wod.attrib.get("ratingBonus", 0)), + } + + return {} From c5ce3991502941c019a096cc40190526ad3c7912 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Fri, 4 Oct 2024 09:37:00 +0200 Subject: [PATCH 26/43] Give spawn task to the waiters on spawn --- objects/waiter.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/objects/waiter.py b/objects/waiter.py index d2834a0..2ba30af 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -80,8 +80,6 @@ async def spawn_waiters(server, client) -> None: for waiter in player.cafe.waiters: waiter.counter = counter - await sleep(2) - await gather(*[waiter.go_to_counter(server, player.cafe, counter) for waiter in player.cafe.waiters]) @@ -137,6 +135,9 @@ def __init__(self, waiter_id: int, name: str, gender: int, priority: int, self.task: Optional['Task'] = None async def spawn(self, server, player_cafe) -> None: + self.task = current_task() + self.task.add_done_callback(self.remove_task) + insert_info = '+'.join(map(str, [ self.id, # Waiter ID Action.INSERT # Action type (0: Insert) @@ -149,6 +150,8 @@ async def spawn(self, server, player_cafe) -> None: for user in player_cafe.clients: await server.send_response(user, response_insert) + await sleep(2) + async def _spawn(self, server, player_cafe) -> None: self.pos = player_cafe.get_start_pos() @@ -191,12 +194,12 @@ async def go_to_counter(self, server, player_cafe, counter=None): self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) self.counter = counter - # print(f"[WAITER] Waiter should staying at: {self.counter_pos}") - walking_time = get_time(player_cafe, self.pos, counter.pos) await sleep(walking_time + 0.5) + # print(f"[WAITER] Waiter should stay at: {self.counter_pos}") + self.pos = counter.pos async def serve_food(self, server, player, customer, table): @@ -321,6 +324,7 @@ async def clean_table(self, server, player_cafe, table): await self.go_to_counter(server, player_cafe) def remove_task(self, _: 'Task' = None): + # if self.task: # print(f"[WAITER] Waiter: {self.avatar.username} Task finished: {self.task.get_coro()}") self.task = None From 54c9055953fec468fb8ebe02ef3315d83ed95e8b Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:30:06 +0200 Subject: [PATCH 27/43] Check if vending machine exist --- commands/edi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/edi.py b/commands/edi.py index 7cbddca..a153213 100644 --- a/commands/edi.py +++ b/commands/edi.py @@ -52,7 +52,7 @@ async def handle_edi(server: 'CafeServer', client: 'StreamWriter', *params: str) if customer.action == 9: await customer._use_vending_machine(server, player, vending_machine) - if vending_machine.fast_food_amount < 1: + if vending_machine and vending_machine.fast_food_amount < 1: vending_machine.fast_food_id = -1 server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) From 9cefc7b4da02c1eb489223292ef91ddde12a73b1 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:55:23 +0200 Subject: [PATCH 28/43] Customer spawn interval update again... The customers was spawning fast to the new players. --- objects/customer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/objects/customer.py b/objects/customer.py index d184459..102de4d 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -475,9 +475,9 @@ async def spawn_cycle(server, client) -> None: while True: rating = player.cafe.rating - if rating < 50: + if rating < 100: spawn_interval = uniform(10, 20) - elif 50 <= rating < 250: + elif 100 <= rating < 350: spawn_interval = uniform(4.5, 8) else: spawn_interval = uniform(0.5, 5) From 1e80a80dfb3ded7c425814848f6f66b01bd3435a Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:40:21 +0200 Subject: [PATCH 29/43] NPC hire bug fixes --- commands/nhi.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/commands/nhi.py b/commands/nhi.py index f5fde4a..9e5c658 100644 --- a/commands/nhi.py +++ b/commands/nhi.py @@ -2,7 +2,7 @@ from objects.waiter import Waiter, get_random_counter from response import ExtensionResponse from typing import TYPE_CHECKING -from asyncio import create_task, sleep +from asyncio import create_task if TYPE_CHECKING: from server import CafeServer @@ -25,16 +25,17 @@ async def handle_nhi(server: 'CafeServer', client: 'StreamWriter', *params: str) response = ExtensionResponse('nhi', '0', '0', name, str(gender)) await server.send_response(client, response) - new_waiter = Waiter(npc_id, name, gender, 50) + new_waiter = Waiter(npc_id, name, gender, 50, avatar) player.cafe.waiters.append(new_waiter) + server.db.update_cafe(player.id, waiters=player.cafe.get_waiters_as_json()) + new_waiter.task = create_task(new_waiter.spawn(server, player.cafe)) - await sleep(2) + await new_waiter.task new_waiter.task = create_task(new_waiter.go_to_counter(server, player.cafe, - get_random_counter(player.cafe), True)) - - server.db.update_cafe(player.id, waiters=player.cafe.get_waiters_as_json()) + get_random_counter(player.cafe))) + await new_waiter.task From 3c4e7840b7e2300a7b6c3df733198b85accbdc44 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:19:08 +0200 Subject: [PATCH 30/43] Customer bug fixes --- objects/customer.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/objects/customer.py b/objects/customer.py index 102de4d..0f4a379 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -69,7 +69,8 @@ def get_random_table(cafe) -> Optional['CafeObject']: def check_for_foods(cafe): - counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER] + counters = [obj for obj in cafe.objects if obj.type == ObjectType.COUNTER and get_time(cafe, cafe.get_start_pos(), + obj.pos) != -1] counters_with_food = [counter for counter in counters if counter.dish_id != -1] if counters_with_food: @@ -190,6 +191,8 @@ async def spawn(self, server, player): await self._spawn(server, player) + self.pos = player.cafe.get_start_pos() + for user in player.cafe.clients: await server.send_response(user, response_insert) @@ -218,8 +221,6 @@ async def _spawn(self, server, player): response_spawn = ExtensionResponse('nav', '-1', '0', spawn_info) - self.pos = player.cafe.get_start_pos() - for user in player.cafe.clients: await server.send_response(user, response_spawn) @@ -256,7 +257,7 @@ async def go_to_chair(self, server, player, table): await self._go_to_chair(server, player, chair) - walking_time = get_time(player.cafe, player.cafe.player_start, chair.pos) + walking_time = get_time(player.cafe, player.cafe.get_start_pos(), chair.pos) # print(f"[CUSTOMER] Started going to table: {self.id} pos: {chair.pos} walking time: {walking_time}s") await sleep(walking_time) @@ -457,7 +458,10 @@ async def leave(self, server, player): for user in player.cafe.clients: await server.send_response(user, response_insert) - walking_time = get_time(player.cafe, self.pos, player.cafe.get_start_pos()) + if self.pos == player.cafe.get_start_pos(): + walking_time = 0 + else: + walking_time = get_time(player.cafe, self.pos, player.cafe.get_start_pos()) # Need to remove after when the customer avatar got deleted from the game. await sleep(walking_time + 3) From 93ee34e5b564b713bd206aa621c7253937b192e5 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:52:36 +0200 Subject: [PATCH 31/43] Improved login bonus, added daily login + rewards --- commands/lbu.py | 48 +++++++++++++++++++++++++++++++---------------- commands/lgn.py | 14 ++++++++++++++ database.py | 5 +++-- objects/player.py | 14 ++++++++++---- 4 files changed, 59 insertions(+), 22 deletions(-) diff --git a/commands/lbu.py b/commands/lbu.py index 5621509..04a726f 100644 --- a/commands/lbu.py +++ b/commands/lbu.py @@ -1,3 +1,5 @@ +import random +from datetime import datetime, timezone from math import floor from random import choice from typing import TYPE_CHECKING @@ -23,16 +25,17 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: minutes_past = max_minutes if minutes_past > 0: - available_counters = [obj for obj in player.cafe.objects if obj.type == ObjectType.COUNTER and obj.dish_amount > 0] + available_counters = [obj for obj in player.cafe.objects if + obj.type == ObjectType.COUNTER and obj.dish_amount > 0] if not available_counters: return rating = player.cafe.rating - if rating < 50: + if rating < 100: should_sold = floor(minutes_past / 1.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - elif 50 <= rating < 250: + elif 100 <= rating < 350: should_sold = floor(minutes_past / 0.7) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. else: should_sold = floor(minutes_past / 0.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. @@ -68,25 +71,38 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: counter.dish_id = -1 available_counters.remove(counter) - player.cafe.update_rating(total_sold_dishes) + player.cash += total_income - amount_was_missing = (should_sold - total_sold_dishes) + print(f'[ [LBU] OFFLINE SELLING] Player sold a total of: {total_sold_dishes} dishes.' + f'Time past since last login: ' + f'{minutes_past}m. Total income: {total_income}$') - if not amount_was_missing == 0: - player.cafe.update_rating(-(amount_was_missing * 2)) + sold_dishes = str(sum(amount for dish_id, amount in dishes_info.items())) - player.cash += total_income + if player.check_daily_login(): + current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") + player.daily_login = current_time - print(f'[ [LBU] OFFLINE SELLING] Player sold a total of: {total_sold_dishes} dishes. Amount was missing: ' - f'{amount_was_missing} Time past since last login: ' - f'{minutes_past}m. Total income: {total_income}$') + daily_cash_bonus = 500 * ((player.get_level() // 10) + 1) + player.cash += daily_cash_bonus - sold_dishes = ''.join(f'{dish_id}+{amount}#' for dish_id, amount in dishes_info.items()) + random_fancy = random.randint(1401, 1409) + if random_fancy not in player.cafe.fridge_inventory: + player.cafe.fridge_inventory[random_fancy] = 1 + else: + player.cafe.fridge_inventory[random_fancy] += 1 - response = ExtensionResponse('lbu', '-1', '0', f'1901+{total_income}#{sold_dishes}') - await server.send_response(client, response) + daily_login_bonus_str = f'1906+{daily_cash_bonus}#{random_fancy}+1' + + response = ExtensionResponse('lbu', '-1', '0', f'{daily_login_bonus_str}#1901+{total_income}', sold_dishes) + await server.send_response(client, response) + + else: + response = ExtensionResponse('lbu', '-1', '0', f'1901+{total_income}', sold_dishes) + await server.send_response(client, response) - server.db.update_player(player.id, cash=player.cash) - server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json(), rating=player.cafe.rating) + server.db.update_player(player.id, cash=player.cash, daily_login=player.daily_login) + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json(), + fridge_inv=player.cafe.build_fridge_inventory()) await handle_asy(server, client) diff --git a/commands/lgn.py b/commands/lgn.py index 0e42244..b5beba1 100644 --- a/commands/lgn.py +++ b/commands/lgn.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from typing import TYPE_CHECKING from commands.lbu import handle_lbu @@ -50,6 +51,19 @@ async def handle_lgn(server: 'CafeServer', client: 'StreamWriter', *params: str) await handle_rlu(server, client, 'lgn', *params) + if not player.daily_login: + current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") + player.daily_login = current_time + server.db.update_player(player.id, daily_login=player.daily_login) + + # RESETS WHEN 24H PAST + # !! NEED TO CALL BEFORE GUI !! + if player.check_daily_login(): + player.instant_cookings = 0 + player.played_wheel = False + player.open_jobs = 5 + server.db.update_player(player.id, instant_cookings=0, played_wheel=0, open_jobs=5) + await handle_gui(server, client, *params) await handle_sbc(server, client, *params) diff --git a/database.py b/database.py index 4bfefe0..328fb8a 100644 --- a/database.py +++ b/database.py @@ -42,6 +42,7 @@ def _create_player_by_params(self, player_args: tuple[int, str]) -> Player: player.cafe = self.get_cafe(player_args[0]) player.parse_mastery(player_args[17]) player.last_login = (player_args[18]) + player.daily_login = (player_args[19]) return player @@ -77,13 +78,13 @@ def try_login(self, name_or_email: str, password: str) -> tuple[int, str]: return 0, values[2] def get_player(self, username: str) -> Player: - self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery, last_login FROM player WHERE username=?", (username,)) + self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery, last_login, daily_login FROM player WHERE username=?", (username,)) player_args = self.cursor.fetchone() return self._create_player_by_params(player_args) def get_player_by_id(self, player_id: int) -> Player: - self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery, last_login FROM player WHERE id=?", (player_id,)) + self.cursor.execute("SELECT id, cash, gold, xp ,instant_cookings, open_jobs, played_wheel, allow_friend_requests, allow_emails, email_verified, new_gifts, username, gender, skin_color, top_color, hair_color, legs_color, mastery, last_login, daily_login FROM player WHERE id=?", (player_id,)) player_args = self.cursor.fetchone() return self._create_player_by_params(player_args) diff --git a/objects/player.py b/objects/player.py index 6d38631..5f5b209 100644 --- a/objects/player.py +++ b/objects/player.py @@ -4,7 +4,7 @@ from utils.items import get_dish_info from math import ceil, floor from typing import TYPE_CHECKING, Optional -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta if TYPE_CHECKING: from objects.cafe import Cafe @@ -33,6 +33,7 @@ def __init__(self): self.mastery: dict[int, int] = {num: 0 for num in range(1201, 1256)} self.last_login: str = '' + self.daily_login: str = '' def build_mastery(self) -> str: pairs = [] @@ -103,12 +104,18 @@ def get_minutes_since_last_login(self) -> int: minutes_diff = time_diff.total_seconds() // 60 return floor(minutes_diff) - def get_level(self) -> int: if self.xp < 90: return floor(pow(floor(self.xp / 10), 1 / 2)) return floor(pow(floor(self.xp / 5), 1 / 3.72)) + def check_daily_login(self): + current_time = datetime.now(timezone.utc) + daily_login = datetime.strptime(self.daily_login, "%y-%d-%m %H:%M").replace(tzinfo=timezone.utc) + if current_time - daily_login >= timedelta(hours=24): + return True + return False + def to_response(self, *params: str) -> list[str]: return [ *params, str(self.id), str(self.id), '0', str(self.cash), str(self.gold), str(self.xp), @@ -118,7 +125,6 @@ def to_response(self, *params: str) -> list[str]: ] @classmethod - def new_user(cls, username: str): + def new_user(cls): player = cls() - player.avatar = Avatar(username, 2, 1, 1, 1, 1) return player From e74a3f3505ab6025f1ec3ba317b52e2251df6c4e Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:17:48 +0200 Subject: [PATCH 32/43] Waiter time bug fixes The waiters pos not set from the objects actual position. It's set by the reachable closest object side. To get the counter obj, just get the waiter.counter, instead of getting the object by the waiter pos --- objects/waiter.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/objects/waiter.py b/objects/waiter.py index 2ba30af..0c6c947 100644 --- a/objects/waiter.py +++ b/objects/waiter.py @@ -69,8 +69,6 @@ async def spawn_waiters(server, client) -> None: address = client.get_extra_info('peername') player = server.clients[address] - # BUG NO TASK GIVEN! - # Happens when the player enter edit mode instantly, and leave. The waiters getting teleported to the counter await gather(*(waiter.spawn(server, player.cafe) for waiter in player.cafe.waiters)) counter = get_random_counter(player.cafe) @@ -105,7 +103,7 @@ async def spawn_waiters_to_client(server, client, wanted_cafe): insert_info = '+'.join(map(str, [ waiter.id, # Waiter ID - waiter.action, # Action type + Action.MOVE_TO_COUNTER, # Action type pos[0], # X pos[1] # Y ])) @@ -126,7 +124,6 @@ def __init__(self, waiter_id: int, name: str, gender: int, priority: int, self.priority = priority self.avatar: Avatar = avatar if avatar else Avatar.get_random_avatar(name, gender) self.pos: list[int] = [0, 0] - self.counter_pos: list[int] = [0, 0] self.counter = None self.dish = -1 @@ -191,16 +188,17 @@ async def go_to_counter(self, server, player_cafe, counter=None): for user in player_cafe.clients: await server.send_response(user, response) - self.counter_pos = player_cafe.get_shortest_pos(self.pos, counter.pos) self.counter = counter walking_time = get_time(player_cafe, self.pos, counter.pos) + # print(f"[WAITER] Walking time: {walking_time}") + await sleep(walking_time + 0.5) - # print(f"[WAITER] Waiter should stay at: {self.counter_pos}") + self.pos = player_cafe.get_shortest_pos(self.pos, counter.pos) - self.pos = counter.pos + # print(f"[WAITER] Waiter should stay at: {self.pos}") async def serve_food(self, server, player, customer, table): self.task = current_task() @@ -267,7 +265,7 @@ async def serve_food(self, server, player, customer, table): chair.dish_id = self.dish chair.dish_status = 1 - time_to_wait = get_time(player.cafe, self.counter_pos, customer.pos) + time_to_wait = get_time(player.cafe, self.pos, customer.pos) # print(f"[WAITER] Time to waiter: {self.id} arrive to the customer: {customer.id} is: {time_to_wait}s") @@ -277,7 +275,7 @@ async def serve_food(self, server, player, customer, table): self.dish = -1 - self.pos = customer.pos + self.pos = player.cafe.get_shortest_pos(self.pos, customer.pos) # print(f"[WAITER] Waiter: {self.id} is arrived to: {customer.id}") @@ -296,14 +294,15 @@ async def clean_table(self, server, player_cafe, table): table.connected_chair.pos[1] # Chair Y ])) - time_to_walk = get_time(player_cafe, self.counter_pos, table.connected_chair.pos) + chair = table.connected_chair + + time_to_walk = get_time(player_cafe, self.pos, chair.pos) response = ExtensionResponse('nac', '-1', '0', response_info) for user in player_cafe.clients: await server.send_response(user, response) - chair = table.connected_chair chair.dish_id = -1 chair.dish_status = 0 @@ -311,7 +310,7 @@ async def clean_table(self, server, player_cafe, table): # print(f"[WAITER] Waiter {self.avatar.username} is started to clean the table: {table.pos}") - self.pos = player_cafe.get_shortest_pos(self.pos, table.connected_chair.pos) + self.pos = player_cafe.get_shortest_pos(self.pos, chair.pos) await sleep(2) From f842e4aa659a48120d5ef331b68f93f669441987 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:37:19 +0200 Subject: [PATCH 33/43] Improved offline selling, fixed daily bonus --- commands/lbu.py | 150 +++++++++++++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 53 deletions(-) diff --git a/commands/lbu.py b/commands/lbu.py index 04a726f..1bace82 100644 --- a/commands/lbu.py +++ b/commands/lbu.py @@ -3,6 +3,7 @@ from math import floor from random import choice from typing import TYPE_CHECKING +from objects.cafe import get_time from commands.asy import handle_asy from objects.cafe_object import ObjectType @@ -24,85 +25,128 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: if minutes_past > max_minutes: minutes_past = max_minutes + sold_dishes = 0 + total_income = 0 + if minutes_past > 0: available_counters = [obj for obj in player.cafe.objects if - obj.type == ObjectType.COUNTER and obj.dish_amount > 0] - - if not available_counters: - return + obj.type == ObjectType.COUNTER and obj.dish_amount > 0 + and get_time(player.cafe, obj) != -1] - rating = player.cafe.rating + if available_counters: + rating = player.cafe.rating - if rating < 100: - should_sold = floor(minutes_past / 1.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - elif 100 <= rating < 350: - should_sold = floor(minutes_past / 0.7) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - else: - should_sold = floor(minutes_past / 0.3) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + waiters_for_serve = len([waiter for waiter in player.cafe.waiters if waiter.priority >= 50]) - total_income = 0 - total_sold_dishes = 0 + if rating < 100: + should_sold = floor((minutes_past / 4.5) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + elif 100 <= rating < 350: + should_sold = floor((minutes_past / 3) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + else: + should_sold = floor((minutes_past / 2.3) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - dishes_info: dict[int, int] = {} + total_income = 0 + total_sold_dishes = 0 - dish_info_cache = {} + dishes_info: dict[int, int] = {} - for _ in range(should_sold): - if not available_counters: - break + dish_info_cache = {} - counter = choice(available_counters) - dish_id = counter.dish_id + for _ in range(should_sold): + if not available_counters: + break - # Need to cache it, because it's slow to get the dish income on every loop - # And on a big loop, the client froze - if dish_id not in dish_info_cache: - dish_info_cache[dish_id] = get_dish_info(dish_id) + counter = choice(available_counters) + dish_id = counter.dish_id - dish_info = dish_info_cache[dish_id] + # Need to cache it, because it's slow to get the dish income on every loop + # And on a big loop, the client froze + if dish_id not in dish_info_cache: + dish_info_cache[dish_id] = get_dish_info(dish_id) - counter.dish_amount -= 1 - total_sold_dishes += 1 - total_income += dish_info.get('income') + dish_info = dish_info_cache[dish_id] - dishes_info[dish_id] = dishes_info.setdefault(dish_id, 0) + 1 + counter.dish_amount -= 1 + total_sold_dishes += 1 + total_income += dish_info.get('income') - if counter.dish_amount <= 0: - counter.dish_id = -1 - available_counters.remove(counter) + dishes_info[dish_id] = dishes_info.setdefault(dish_id, 0) + 1 - player.cash += total_income + if counter.dish_amount <= 0: + counter.dish_id = -1 + available_counters.remove(counter) - print(f'[ [LBU] OFFLINE SELLING] Player sold a total of: {total_sold_dishes} dishes.' - f'Time past since last login: ' - f'{minutes_past}m. Total income: {total_income}$') + player.cash += total_income - sold_dishes = str(sum(amount for dish_id, amount in dishes_info.items())) + print(f'[ [LBU] OFFLINE SELLING] Player sold a total of: {total_sold_dishes} dishes.' + f'Time past since last login: ' + f'{minutes_past}m. Total income: {total_income}$') - if player.check_daily_login(): - current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") - player.daily_login = current_time + sold_dishes = sum(amount for dish_id, amount in dishes_info.items()) - daily_cash_bonus = 500 * ((player.get_level() // 10) + 1) - player.cash += daily_cash_bonus + # If the player sold dishes, and the player daily login + if sold_dishes > 0 and player.check_daily_login(): + current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") + player.daily_login = current_time - random_fancy = random.randint(1401, 1409) - if random_fancy not in player.cafe.fridge_inventory: - player.cafe.fridge_inventory[random_fancy] = 1 - else: - player.cafe.fridge_inventory[random_fancy] += 1 + daily_cash_bonus = 500 * ((player.get_level() // 10) + 1) + player.cash += daily_cash_bonus - daily_login_bonus_str = f'1906+{daily_cash_bonus}#{random_fancy}+1' + random_fancy = random.randint(1401, 1409) + if random_fancy not in player.cafe.fridge_inventory: + player.cafe.fridge_inventory[random_fancy] = 1 + else: + player.cafe.fridge_inventory[random_fancy] += 1 - response = ExtensionResponse('lbu', '-1', '0', f'{daily_login_bonus_str}#1901+{total_income}', sold_dishes) - await server.send_response(client, response) + daily_login_bonus_str = f'1906+{daily_cash_bonus}#{random_fancy}+1' - else: - response = ExtensionResponse('lbu', '-1', '0', f'1901+{total_income}', sold_dishes) - await server.send_response(client, response) + response = ExtensionResponse('lbu', '-1', '0', f'{daily_login_bonus_str}#1901+{total_income}', + str(sold_dishes)) + await server.send_response(client, response) server.db.update_player(player.id, cash=player.cash, daily_login=player.daily_login) server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json(), fridge_inv=player.cafe.build_fridge_inventory()) await handle_asy(server, client) + + return + + # If the player sold dishes, but not daily login + elif sold_dishes > 0 and not player.check_daily_login(): + response = ExtensionResponse('lbu', '-1', '0', f'1901+{total_income}', + str(sold_dishes)) + await server.send_response(client, response) + + server.db.update_player(player.id, cash=player.cash) + server.db.update_cafe(player.id, objects=player.cafe.get_objects_as_json()) + + await handle_asy(server, client) + + return + + # If the player not sold dishes, but daily login + elif sold_dishes == 0 and player.check_daily_login(): + current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") + player.daily_login = current_time + + daily_cash_bonus = 500 * ((player.get_level() // 10) + 1) + player.cash += daily_cash_bonus + + random_fancy = random.randint(1401, 1409) + if random_fancy not in player.cafe.fridge_inventory: + player.cafe.fridge_inventory[random_fancy] = 1 + else: + player.cafe.fridge_inventory[random_fancy] += 1 + + daily_login_bonus_str = f'1906+{daily_cash_bonus}#{random_fancy}+1' + + response = ExtensionResponse('lbu', '-1', '0', f'{daily_login_bonus_str}') + await server.send_response(client, response) + + server.db.update_player(player.id, cash=player.cash, daily_login=player.daily_login) + server.db.update_cafe(player.id, fridge_inv=player.cafe.build_fridge_inventory()) + + await handle_asy(server, client) + + return From 2b442c4b5eb07e491a0ec1ad267e33234babd086 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:50:28 +0200 Subject: [PATCH 34/43] Changed cafe get_time to use pos instead of object --- commands/lbu.py | 2 +- objects/cafe.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commands/lbu.py b/commands/lbu.py index 1bace82..3267178 100644 --- a/commands/lbu.py +++ b/commands/lbu.py @@ -31,7 +31,7 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: if minutes_past > 0: available_counters = [obj for obj in player.cafe.objects if obj.type == ObjectType.COUNTER and obj.dish_amount > 0 - and get_time(player.cafe, obj) != -1] + and get_time(player.cafe, obj.pos) != -1] if available_counters: rating = player.cafe.rating diff --git a/objects/cafe.py b/objects/cafe.py index 7542c3b..cd6c414 100644 --- a/objects/cafe.py +++ b/objects/cafe.py @@ -14,7 +14,7 @@ # TODO: Tidy up this function -def get_time(cafe: 'Cafe', chair: 'CafeObject') -> float: +def get_time(cafe: 'Cafe', end_pos) -> float: cafe_size = cafe.size obstacles = [[0 for _ in range(cafe_size)] for _ in range(cafe_size)] @@ -25,10 +25,10 @@ def get_time(cafe: 'Cafe', chair: 'CafeObject') -> float: for obj in cafe.objects: obstacles[obj.pos[0]][obj.pos[1]] = 1 - obstacles[chair.pos[0]][chair.pos[1]] = 0 + obstacles[end_pos[0]][end_pos[1]] = 0 start_pos = tuple(cafe.get_start_pos()) - path = AStar(obstacles, start_pos, tuple(chair.pos)).solve() + path = AStar(obstacles, start_pos, tuple(end_pos)).solve() if path: distance = len(path) - 1 @@ -204,7 +204,7 @@ def connect_chairs(self) -> None: for table in tables: if (item := nearby_pos.get(tuple(table.pos))) is not None: - if chair.rotation == item and (get_time(self, chair) != -1): + if chair.rotation == item and (get_time(self, chair.pos) != -1): table.connected_chair = chair self.available_tables.append(table) From dbff4462ee9e7bbeddbe9ae60a57e45ac608fcf1 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 16 Oct 2024 07:58:04 +0200 Subject: [PATCH 35/43] Added XP back in the get dish info --- utils/items.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/items.py b/utils/items.py index fe2c93a..05ca524 100644 --- a/utils/items.py +++ b/utils/items.py @@ -40,7 +40,8 @@ def get_dish_info(dish_id: int) -> dict: 'amount': int(wod.attrib.get('servings', 0)), 'level': int(wod.attrib.get('level', 0)), 'events': int(wod.attrib.get('events', 0)), - 'income': int(wod.attrib.get('incomePerServing', 0)) + 'income': int(wod.attrib.get('incomePerServing', 0)), + 'xp': int(wod.attrib.get('xp', 0)) } return {} From 0a552244b0ea638976d43a175252c83f1ea78b6a Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 16 Oct 2024 08:21:04 +0200 Subject: [PATCH 36/43] Remove player on disconnect --- server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.py b/server.py index 7264ecd..1a302c6 100644 --- a/server.py +++ b/server.py @@ -119,6 +119,8 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre self.db.update_player(player.id, last_login=player.last_login) + self.players.remove(self.clients[peername]) + del self.clients[peername] print(f'[DISCONNECTED] {peername}') From fda1c48b9f2761817b519ac3d84ef6fe1c1e518c Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 16 Oct 2024 08:41:32 +0200 Subject: [PATCH 37/43] Fixed door issue --- commands/ebu.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands/ebu.py b/commands/ebu.py index f7cffec..62356f7 100644 --- a/commands/ebu.py +++ b/commands/ebu.py @@ -70,14 +70,15 @@ async def handle_ebu(server: 'CafeServer', client: 'StreamWriter', *params: str) new_object.pos = [int(pos_x), int(pos_y)] new_object.rotation = int(rotation) - player.cafe.objects.append(new_object) - if int(obj_id) in DOOR_IDs: for obj in player.cafe.objects: if obj.id in DOOR_IDs: player.cafe.furniture_inventory[obj.id] = 1 player.cafe.objects.remove(obj) + player.cafe.objects.append(new_object) + + if int(obj_id) in DOOR_IDs: player.cafe.player_start = player.cafe.get_start_pos() server.db.update_cafe(player.id, From 94835681b052b5c0ba31f33a3e929a9752f1fbc1 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 19 Oct 2024 08:21:09 +0200 Subject: [PATCH 38/43] Updated the customer spawn rate, offline selling formula update --- commands/lbu.py | 12 +++++++----- objects/customer.py | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/commands/lbu.py b/commands/lbu.py index 3267178..eb39b0a 100644 --- a/commands/lbu.py +++ b/commands/lbu.py @@ -38,12 +38,14 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: waiters_for_serve = len([waiter for waiter in player.cafe.waiters if waiter.priority >= 50]) - if rating < 100: - should_sold = floor((minutes_past / 4.5) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. - elif 100 <= rating < 350: - should_sold = floor((minutes_past / 3) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + if rating < 150: + should_sold = floor((minutes_past / 2) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + elif 150 <= rating < 350: + should_sold = floor((minutes_past / 1.7) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + elif 350 <= rating < 500: + should_sold = floor((minutes_past / 1.5) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. else: - should_sold = floor((minutes_past / 2.3) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + should_sold = floor((minutes_past / 1.2) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. total_income = 0 total_sold_dishes = 0 diff --git a/objects/customer.py b/objects/customer.py index 0f4a379..c96e9d1 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -479,10 +479,12 @@ async def spawn_cycle(server, client) -> None: while True: rating = player.cafe.rating - if rating < 100: + if rating < 150: # Customers spawning fast for a new player, because the rating go up fast. spawn_interval = uniform(10, 20) - elif 100 <= rating < 350: + elif 150 <= rating < 350: spawn_interval = uniform(4.5, 8) + elif 350 <= rating < 500: + spawn_interval = uniform(3.5, 6) else: spawn_interval = uniform(0.5, 5) From 9a1c853c164f05ca41e5f2a15793eb0e28dd4f50 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Sat, 19 Oct 2024 09:28:27 +0200 Subject: [PATCH 39/43] Force disconnect inactive players, trying to send pin commands --- commands/lgn.py | 3 +++ commands/pin.py | 27 +++++++++++++++++++++++++++ server.py | 19 +++++++++++++------ 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 commands/pin.py diff --git a/commands/lgn.py b/commands/lgn.py index b5beba1..3a99e95 100644 --- a/commands/lgn.py +++ b/commands/lgn.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING from commands.lbu import handle_lbu +from commands.pin import handle_pin from response import ExtensionResponse, SystemResponse from commands.rlu import handle_rlu from commands.gui import handle_gui @@ -73,3 +74,5 @@ async def handle_lgn(server: 'CafeServer', client: 'StreamWriter', *params: str) await handle_ifr(server, client, *params) await handle_lbu(server, client) + + await handle_pin(server, client) diff --git a/commands/pin.py b/commands/pin.py new file mode 100644 index 0000000..dfe6a35 --- /dev/null +++ b/commands/pin.py @@ -0,0 +1,27 @@ +from typing import TYPE_CHECKING +from response import ExtensionResponse +import asyncio + +if TYPE_CHECKING: + from server import CafeServer + from asyncio import StreamWriter + + +async def handle_pin(server: 'CafeServer', client: 'StreamWriter') -> None: + address = client.get_extra_info('peername') + player = server.clients.get(address) + + if not player: + client.close() + + while True: + try: + response = ExtensionResponse('pin', '-1') + await server.send_response(client, response) + await asyncio.sleep(60) + except Exception as e: + print(f"[ERROR] Cant send response, because the client is gone") + print(f"[ERROR] An error occurred: {e}") + break + + client.close() diff --git a/server.py b/server.py index 1a302c6..7ee448c 100644 --- a/server.py +++ b/server.py @@ -1,6 +1,7 @@ import sys import socket import time +from asyncio import wait_for from datetime import datetime, timezone from database import Database @@ -55,13 +56,19 @@ def bind_socket(self) -> None: async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: tasks = [] + connection_timeout = 600 while not writer.is_closing() and self.running: try: - request_str = (await reader.read(1024)).decode() + request_str = (await wait_for(reader.read(1024), timeout=connection_timeout)).decode() + # Force disconnect the player if the player is inactive for 10 minutes. except ConnectionResetError: writer.close() break + except TimeoutError: + print(f"[DISCONNECTED] {writer.get_extra_info('peername')} due to inactivation") + writer.close() + break if not request_str: break @@ -101,17 +108,17 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre if waiter.task: waiter.task.cancel() - if player.cafe.customer_cycle: - player.cafe.customer_cycle.cancel() - player.cafe.customer_cycle = None - for table in player.cafe.available_tables: table.busy = False for customer in player.cafe.customers: - if customer.task: + if customer.task and not customer.task.done(): customer.task.cancel() + if player.cafe.customer_cycle: + player.cafe.customer_cycle.cancel() + player.cafe.customer_cycle = None + player.online = False current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") From 27b9e4d8c5d85447c5ed8edcfcba80d9d8289a14 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:08:22 +0200 Subject: [PATCH 40/43] If not player lastlogin, return 0 --- objects/player.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/objects/player.py b/objects/player.py index 5f5b209..76e158e 100644 --- a/objects/player.py +++ b/objects/player.py @@ -98,6 +98,9 @@ def get_mastery_duration(self, item_id: int) -> int: return floor(duration * 0.95) * 60 def get_minutes_since_last_login(self) -> int: + if not self.last_login: + return 0 + last_login = datetime.strptime(self.last_login, "%y-%d-%m %H:%M").replace(tzinfo=timezone.utc) current_time = datetime.now(timezone.utc) time_diff = current_time - last_login From 2145bfd820e1a4e7a87ccb939600fb95b1547481 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:13:11 +0200 Subject: [PATCH 41/43] Selling more dishes offline --- commands/lbu.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/lbu.py b/commands/lbu.py index eb39b0a..807e85e 100644 --- a/commands/lbu.py +++ b/commands/lbu.py @@ -39,13 +39,13 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: waiters_for_serve = len([waiter for waiter in player.cafe.waiters if waiter.priority >= 50]) if rating < 150: - should_sold = floor((minutes_past / 2) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + should_sold = floor((minutes_past / 1.5) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. elif 150 <= rating < 350: - should_sold = floor((minutes_past / 1.7) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + should_sold = floor((minutes_past / 1.3) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. elif 350 <= rating < 500: - should_sold = floor((minutes_past / 1.5) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + should_sold = floor((minutes_past / 1.1) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. else: - should_sold = floor((minutes_past / 1.2) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. + should_sold = floor((minutes_past / 0.7) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. total_income = 0 total_sold_dishes = 0 From a77df144abfbe9f1625b16fc145aac238857467b Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:18:39 +0200 Subject: [PATCH 42/43] Modified the waiters choose logic, and in the lbu --- commands/lbu.py | 2 +- objects/customer.py | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/commands/lbu.py b/commands/lbu.py index 807e85e..ab818d9 100644 --- a/commands/lbu.py +++ b/commands/lbu.py @@ -36,7 +36,7 @@ async def handle_lbu(server: 'CafeServer', client: 'StreamWriter') -> None: if available_counters: rating = player.cafe.rating - waiters_for_serve = len([waiter for waiter in player.cafe.waiters if waiter.priority >= 50]) + waiters_for_serve = len([waiter for waiter in player.cafe.waiters if waiter.priority > 0]) if rating < 150: should_sold = floor((minutes_past / 1.5) * waiters_for_serve) # LOWER IT TO SELL MORE, INCREASE TO SELL LESS. diff --git a/objects/customer.py b/objects/customer.py index c96e9d1..f3b6ce1 100644 --- a/objects/customer.py +++ b/objects/customer.py @@ -1,6 +1,6 @@ import asyncio from asyncio import create_task, sleep, Task, current_task -from random import randint, choice, uniform +from random import randint, choice, uniform, random from typing import Optional, List, TYPE_CHECKING from astar import AStar @@ -81,12 +81,10 @@ def check_for_foods(cafe): def get_random_waiter(cafe, task) -> Optional['Waiter']: waiters = cafe.waiters - if task == WaiterTask.SERVE: - free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority >= 50] - else: - free_waiters = [waiter for waiter in waiters if not waiter.task and waiter.priority <= 50] + free_waiters = [waiter for waiter in waiters if not waiter.task] + relevant_waiters = [waiter for waiter in free_waiters if waiter.priority != (0 if task == WaiterTask.SERVE else 100)] - return choice(free_waiters) if free_waiters else None + return choice(relevant_waiters) if relevant_waiters else None async def wait_for_table(cafe) -> Optional['CafeObject']: From 0565a27bd2596b0ea426aeb8128eaeb7aa160518 Mon Sep 17 00:00:00 2001 From: Bence <78733248+BXn4@users.noreply.github.com> Date: Thu, 19 Dec 2024 07:16:37 +0100 Subject: [PATCH 43/43] Added logging --- server.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/server.py b/server.py index 7ee448c..8589a05 100644 --- a/server.py +++ b/server.py @@ -1,6 +1,5 @@ import sys import socket -import time from asyncio import wait_for from datetime import datetime, timezone @@ -11,6 +10,16 @@ from objects.cafe import Cafe from typing import TYPE_CHECKING import asyncio +import logging + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) +ch = logging.StreamHandler() +ch.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') +ch.setFormatter(formatter) +logger.addHandler(ch) +logging.basicConfig(filename=f'server_log.log', level=logging.INFO) if TYPE_CHECKING: from objects.player import Player @@ -58,15 +67,18 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre tasks = [] connection_timeout = 600 + logger.info(f"[CONNECTED] {writer.get_extra_info('peername')}") + while not writer.is_closing() and self.running: try: request_str = (await wait_for(reader.read(1024), timeout=connection_timeout)).decode() - # Force disconnect the player if the player is inactive for 10 minutes. + except ConnectionResetError: + logger.info(f"[ERROR] {writer.get_extra_info('peername')} connection reset") writer.close() break except TimeoutError: - print(f"[DISCONNECTED] {writer.get_extra_info('peername')} due to inactivation") + logger.info(f"[DISCONNECTED] {writer.get_extra_info('peername')} due to inactivation") writer.close() break @@ -78,14 +90,17 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre # print(f'\n[RECEIVED] {request.raw_request}') if request.type == RequestType.DISCONNECT: + logger.info(f"[DISCONNECTED] {writer.get_extra_info('peername')} initiated disconnect") writer.close() elif request.type == RequestType.POLICY_FILE_REQUEST: + # logger.info(f"[POLICY FILE REQUEST] {writer.get_extra_info('peername')} requested policy") response = SystemResponse(request.cmd) await self.send_response(writer, response) writer.close() elif request.type == RequestType.XML and request.cmd != 'login': + # logger.info(f"[XML REQUEST] {writer.get_extra_info('peername')} sent XML request (cmd: {request.cmd})") response = SystemResponse(request.cmd) await self.send_response(writer, response) @@ -96,18 +111,22 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre peername = writer.get_extra_info('peername') if player := self.clients.get(peername): + logger.info(f"[DISCONNECTING PLAYER] {peername} is being disconnected") + if player.room: player.room.clients.remove(writer) - for client in player.room.clients: await self.send_response(client, ExtensionResponse('juq', '-1', '0', str(player.id))) player.room = None + logger.info(f"[CANCELLING NPC] Cancelling NPC tasks") + for waiter in player.cafe.waiters: if waiter.task: waiter.task.cancel() + for table in player.cafe.available_tables: table.busy = False @@ -119,6 +138,9 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre player.cafe.customer_cycle.cancel() player.cafe.customer_cycle = None + if not player.cafe.customer_cycle: + logger.info(f"[CANCELLING NPC] Cancelled NPC tasks!") + player.online = False current_time = datetime.now(timezone.utc).strftime("%y-%d-%m %H:%M") @@ -127,15 +149,17 @@ async def handle_client(self, reader: asyncio.StreamReader, writer: asyncio.Stre self.db.update_player(player.id, last_login=player.last_login) self.players.remove(self.clients[peername]) - del self.clients[peername] - print(f'[DISCONNECTED] {peername}') - for task in tasks: + logger.info(f"[CANCELLING TASK] Cancelling task: {task}") task.cancel() + logger.info(f"[DISCONNECTED] {peername} successfully disconnected and cleaned up") + print(f'[DISCONNECTED] {peername}') + async def start(self) -> None: + logger.info('Server started') # self.socket.listen(self.max_conn) self.running = True