From 2833d99edaddf030bdc38bb37e70e060b459bc86 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 21 Jan 2021 05:34:45 +0100 Subject: [PATCH 1/7] only print new check once in web ui --- MultiClient.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MultiClient.py b/MultiClient.py index 3f1afc89..24bfc7f7 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -35,6 +35,8 @@ import WebUI import Regions import Utils +# logging note: +# logging.* gets send to only the text console, logger.* gets send to the WebUI as well, if it's initialized. logger = logging.getLogger("Client") @@ -1160,7 +1162,7 @@ async def track_locations(ctx : Context, roomid, roomdata): def new_check(location): ctx.unsafe_locations_checked.add(location) - logger.info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked))) + logging.info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked))) ctx.ui_node.send_location_check(ctx, location) for location, (loc_roomid, loc_mask) in location_table_uw.items(): @@ -1168,7 +1170,7 @@ async def track_locations(ctx : Context, roomid, roomdata): if location not in ctx.unsafe_locations_checked and loc_roomid == roomid and (roomdata << 4) & loc_mask != 0: new_check(location) except Exception as e: - logger.info(f"Exception: {e}") + logger.exception(f"Exception: {e}") uw_begin = 0x129 uw_end = 0 From 7ce9278123a38bb32fddf190fca54157820cb7d9 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 21 Jan 2021 23:58:30 +0100 Subject: [PATCH 2/7] Use shutil.move instead of os.replace, for compatibility --- MultiClient.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MultiClient.py b/MultiClient.py index 24bfc7f7..0aa574a2 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -13,6 +13,7 @@ import sys import typing import os import subprocess +import shutil from random import randrange @@ -1418,7 +1419,7 @@ async def main(): adjustedromfile, adjusted = Utils.get_adjuster_settings(romfile) if adjusted: try: - os.replace(adjustedromfile, romfile) + shutil.move(adjustedromfile, romfile) adjustedromfile = romfile except Exception as e: logging.exception(e) From 8754c63d4ef9b081d99467f6f62101c534fa6077 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Thu, 21 Jan 2021 16:21:51 -0800 Subject: [PATCH 3/7] client/server changes * Server now includes checked locations as a separate list. * If client connects to a server that sends checked checks, client can now give accurate information on checked locations, and not show "New Check: seed-unknown-location (total/216)" and instead accurately show "New check: 'seed-known-location' (total_checked/seed_total)" /missing now accurately reports what was previously checked. * client now attempts to translate "Unknown Location ID: 'ID'" into an actual location, if server is unaware of the location, but the client is. --- MultiClient.py | 66 ++++++++++++++++++++++++++++++-------------------- MultiServer.py | 17 ++++++++++--- 2 files changed, 53 insertions(+), 30 deletions(-) diff --git a/MultiClient.py b/MultiClient.py index 0aa574a2..2170d217 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -13,6 +13,7 @@ import sys import typing import os import subprocess +import re import shutil from random import randrange @@ -95,6 +96,7 @@ class Context(): self.locations_scouted = set() self.items_received = [] self.items_missing = [] + self.items_checked = None self.locations_info = {} self.awaiting_rom = False self.rom = None @@ -802,6 +804,18 @@ async def server_autoreconnect(ctx: Context): ctx.server_task = asyncio.create_task(server_loop(ctx)) +missing_unknown = re.compile("Unknown Location ID: (?P\d+)") +def convert_unknown_missing(missing_items: list) -> list: + missing = [] + for location in missing_items: + match = missing_unknown.match(location) + if match: + missing.append(Regions.lookup_id_to_name.get(int(match['ID']), location)) + else: + missing.append(location) + return missing + + async def process_server_cmd(ctx: Context, cmd, args): if cmd == 'RoomInfo': logger.info('--------------------------------') @@ -879,11 +893,12 @@ async def process_server_cmd(ctx: Context, cmd, args): await ctx.send_msgs(msgs) if ctx.finished_game: await send_finished_game(ctx) - ctx.items_missing = args[2] if len(args) >= 3 else [] # Get the server side view of missing as of time of connecting. + ctx.items_missing = convert_unknown_missing(args[2] if len(args) >= 3 else []) # Get the server side view of missing as of time of connecting. + ctx.items_checked = convert_unknown_missing(args[3] if len(args) >= 4 else None) # This list is used to only send to the server what is reported as ACTUALLY Missing. # This also serves to allow an easy visual of what locations were already checked previously # when /missing is used for the client side view of what is missing. - if not ctx.items_missing: + if not ctx.items_missing and not ctx.items_checked: asyncio.create_task(ctx.send_msgs([['Say', '!missing']])) elif cmd == 'ReceivedItems': @@ -933,12 +948,16 @@ async def process_server_cmd(ctx: Context, cmd, args): elif cmd == 'Missing': if 'locations' in args: - locations = json.loads(args['locations']) + locations = convert_unknown_missing(json.loads(args['locations'])) if ctx.items_missing: for location in locations: logger.info(f'Missing: {location}') + ctx.items_missing = locations + if 'locations_checked' in args: + ctx.items_checked = convert_unknown_missing(json.loads(args['locations_checked'])) + logger.info(f'Missing {len(locations)}/{len(locations)+len(ctx.items_checked)} location checks') + else: logger.info(f'Found {len(locations)} missing location checks') - ctx.items_missing = [location for location in locations] elif cmd == 'Hint': hints = [Utils.Hint(*hint) for hint in args] @@ -1068,30 +1087,15 @@ class ClientCommandProcessor(CommandProcessor): """List all missing location checks, from your local game state""" count = 0 checked_count = 0 - for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]: + for location in Regions.lookup_name_to_id.keys(): if location not in self.ctx.locations_checked: - if location not in self.ctx.items_missing: - self.output('Checked: ' + location) - checked_count += 1 - else: + if location in self.ctx.items_missing: self.output('Missing: ' + location) - count += 1 - - key_drop_count = 0 - for location in [k for k, v in Regions.key_drop_data.items()]: - if location not in self.ctx.items_missing: - key_drop_count += 1 - - # No point on reporting on missing key drop locations if the server doesn't declare ANY of them missing. - if key_drop_count != len(Regions.key_drop_data.items()): - for location in [k for k, v in Regions.key_drop_data.items()]: - if location not in self.ctx.locations_checked: - if location not in self.ctx.items_missing: - self.output('Checked: ' + location) - key_drop_count += 1 - else: - self.output('Missing: ' + location) count += 1 + elif self.ctx.items_checked is None or location in self.ctx.items_checked: + self.output('Checked: ' + location) + count += 1 + checked_count += 1 if count: self.output(f"Found {count} missing location checks{f'. {checked_count} locations checks previously visited.' if checked_count else ''}") @@ -1163,7 +1167,17 @@ async def track_locations(ctx : Context, roomid, roomdata): def new_check(location): ctx.unsafe_locations_checked.add(location) - logging.info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked))) + + check = None + if ctx.items_checked is None: + check = f'New Check: {location} ({len(ctx.unsafe_locations_checked)}/{len(Regions.lookup_name_to_id)})' + else: + items_total = len(ctx.items_missing) + len(ctx.items_checked) + if location in ctx.items_missing or location in ctx.items_checked: + check = f'New Check: {location} ({len(ctx.unsafe_locations_checked)}/{items_total})' + + if check: + logger.info(check) ctx.ui_node.send_location_check(ctx, location) for location, (loc_roomid, loc_mask) in location_table_uw.items(): diff --git a/MultiServer.py b/MultiServer.py index 011fca9a..0f1c53bd 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -429,9 +429,10 @@ async def countdown(ctx: Context, timer): ctx.notify_all(f'[Server]: GO') ctx.countdown_timer = 0 -async def missing(ctx: Context, client: Client, locations: list): +async def missing(ctx: Context, client: Client, locations: list, checked_locations: list): await ctx.send_msgs(client, [['Missing', { - 'locations': json.dumps(locations) + 'locations': json.dumps(locations), + 'checked_locations': json.dumps(checked_locations) }]]) @@ -836,6 +837,7 @@ class ClientMessageProcessor(CommonCommandProcessor): """List all missing location checks from the server's perspective""" locations = get_missing_checks(self.ctx, self.client) + checked_locations = get_checked_checks(self.ctx, self.client) if len(locations) > 0: if self.client.version < [2, 3, 0]: @@ -844,7 +846,7 @@ class ClientMessageProcessor(CommonCommandProcessor): buffer += f'Missing: {location}\n' self.output(buffer + f"Found {len(locations)} missing location checks") else: - asyncio.create_task(missing(self.ctx, self.client, locations)) + asyncio.create_task(missing(self.ctx, self.client, locations, checked_locations)) else: self.output("No missing location checks found.") return True @@ -965,6 +967,13 @@ class ClientMessageProcessor(CommonCommandProcessor): return False +def get_checked_checks(ctx: Context, client: Client) -> list: + return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for + location_id, slot in ctx.locations if + slot == client.slot and + location_id in ctx.location_checks[client.team, client.slot]] + + def get_missing_checks(ctx: Context, client: Client) -> list: return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for location_id, slot in ctx.locations if @@ -1032,7 +1041,7 @@ async def process_client_cmd(ctx: Context, client: Client, cmd, args): client.tags = args.get('tags', Client.tags) reply = [['Connected', [(client.team, client.slot), [(p, ctx.get_aliased_name(t, p)) for (t, p), n in ctx.player_names.items() if - t == client.team], get_missing_checks(ctx, client)]]] + t == client.team], get_missing_checks(ctx, client), get_checked_checks(ctx, client)]]] items = get_received_items(ctx, client.team, client.slot) if items: reply.append(['ReceivedItems', (0, tuplize_received_items(items))]) From aa22653bfc39c50e45e1599a1c0f4b3db98f4b39 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Thu, 21 Jan 2021 00:12:53 -0800 Subject: [PATCH 4/7] Fix for servers that don't return checked items. --- MultiClient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MultiClient.py b/MultiClient.py index 2170d217..b50f309f 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -894,7 +894,7 @@ async def process_server_cmd(ctx: Context, cmd, args): if ctx.finished_game: await send_finished_game(ctx) ctx.items_missing = convert_unknown_missing(args[2] if len(args) >= 3 else []) # Get the server side view of missing as of time of connecting. - ctx.items_checked = convert_unknown_missing(args[3] if len(args) >= 4 else None) + ctx.items_checked = convert_unknown_missing(args[3]) if len(args) >= 4 else None # This list is used to only send to the server what is reported as ACTUALLY Missing. # This also serves to allow an easy visual of what locations were already checked previously # when /missing is used for the client side view of what is missing. From f47a85d435a1dc0a1ce525a252faf3bba010df99 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Thu, 21 Jan 2021 15:59:28 -0800 Subject: [PATCH 5/7] Multidata is now able to provide items/location IDs to server. --- MultiServer.py | 79 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index 0f1c53bd..4667eae2 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -113,6 +113,11 @@ class Context(Node): self.save_dirty = False self.tags = ['Berserker'] self.minimum_client_versions: typing.Dict[typing.Tuple[int, int], Utils.Version] = {} + self.lookup_items_id_to_name = Items.lookup_id_to_name + self.lookup_region_id_to_name = Regions.lookup_id_to_name + self.lookup_region_name_to_id = Regions.lookup_name_to_id + self.console_names = console_names + self.item_name_groups = Items.item_name_groups def load(self, multidatapath: str, use_embedded_server_options: bool = False): with open(multidatapath, 'rb') as f: @@ -150,6 +155,29 @@ class Context(Node): server_options = jsonobj.get("server_options", {}) self._set_options(server_options) + new_console_names = set() + lookups = {"lookup_items_id_to_name": False, "lookup_region_id_to_name": False, "item_name_groups": False} + if "lookup_items_id_to_name" in jsonobj: + lookups["lookup_items_id_to_name"] = True + self.lookup_items_id_to_name = jsonobj["lookup_items_id_to_name"] + new_console_names |= set(self.lookup_items_id_to_name.values()) + + if "lookup_region_id_to_name" in jsonobj: + lookups["lookup_region_id_to_name"] = True + self.lookup_region_id_to_name = jsonobj["lookup_region_id_to_name"] + self.lookup_region_name_to_id = {value: key for key, value in self.lookup_region_id_to_name.items()} + new_console_names |= set(self.lookup_region_id_to_name.values()) + + if "item_name_groups" in jsonobj: + lookups["item_name_groups"] = True + self.item_name_groups = {key: set(value) for key, value in jsonobj["item_name_groups"]} + new_console_names |= set(self.item_name_groups.keys()) + + if not all(lookups.values()): + new_console_names |= self.console_names + + self.console_names = frozenset(new_console_names) + def _set_options(self, server_options: dict): for key, value in server_options.items(): data_type = self.simple_options.get(key, None) @@ -553,7 +581,7 @@ def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[ def collect_hints_location(ctx: Context, team: int, slot: int, location: str) -> typing.List[Utils.Hint]: hints = [] - seeked_location = Regions.lookup_name_to_id[location] + seeked_location = ctx.lookup_region_name_to_id[location] for check, result in ctx.locations.items(): location_id, finding_player = check if finding_player == slot and location_id == seeked_location: @@ -567,7 +595,7 @@ def collect_hints_location(ctx: Context, team: int, slot: int, location: str) -> def format_hint(ctx: Context, team: int, hint: Utils.Hint) -> str: text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \ - f"{Items.lookup_id_to_name[hint.item]} is " \ + f"{ctx.lookup_items_id_to_name[hint.item]} is " \ f"at {get_location_name_from_address(hint.location)} " \ f"in {ctx.player_names[team, hint.finding_player]}'s World" @@ -576,7 +604,7 @@ def format_hint(ctx: Context, team: int, hint: Utils.Hint) -> str: return text + (". (found)" if hint.found else ".") -def get_intended_text(input_text: str, possible_answers: typing.Iterable[str]= console_names) -> typing.Tuple[str, bool, str]: +def get_intended_text(input_text: str, possible_answers: typing.Iterable[str]) -> typing.Tuple[str, bool, str]: picks = fuzzy_process.extract(input_text, possible_answers, limit=2) if len(picks) > 1: dif = picks[0][1] - picks[1][1] @@ -801,29 +829,26 @@ class ClientMessageProcessor(CommonCommandProcessor): "Your client is too old to send game beaten information. Please update, load you savegame and reconnect.") return False + def remaining_items(self) -> bool: + remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot) + if remaining_item_ids: + self.output("Remaining items: " + ", ".join(self.ctx.lookup_items_id_to_name.get(item_id, "unknown item") + for item_id in remaining_item_ids)) + else: + self.output("No remaining items found.") + return True + def _cmd_remaining(self) -> bool: """List remaining items in your game, but not their location or recipient""" if self.ctx.remaining_mode == "enabled": - remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot) - if remaining_item_ids: - self.output("Remaining items: " + ", ".join(Items.lookup_id_to_name.get(item_id, "unknown item") - for item_id in remaining_item_ids)) - else: - self.output("No remaining items found.") - return True + return self.remaining_items() elif self.ctx.remaining_mode == "disabled": self.output( "Sorry, !remaining has been disabled on this server.") return False else: # is goal if self.ctx.client_game_state[self.client.team, self.client.slot] == CLIENT_GOAL: - remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot) - if remaining_item_ids: - self.output("Remaining items: " + ", ".join(Items.lookup_id_to_name.get(item_id, "unknown item") - for item_id in remaining_item_ids)) - else: - self.output("No remaining items found.") - return True + return self.remaining_items() else: self.output( "Sorry, !remaining requires you to have beaten the game on this server") @@ -873,7 +898,7 @@ class ClientMessageProcessor(CommonCommandProcessor): def _cmd_getitem(self, item_name: str) -> bool: """Cheat in an item, if it is enabled on this server""" if self.ctx.item_cheat: - item_name, usable, response = get_intended_text(item_name, Items.item_table.keys()) + item_name, usable, response = get_intended_text(item_name, self.ctx.lookup_items_id_to_name.values()) if usable: new_item = ReceivedItem(Items.item_table[item_name][3], -1, self.client.slot) get_received_items(self.ctx, self.client.team, self.client.slot).append(new_item) @@ -900,14 +925,14 @@ class ClientMessageProcessor(CommonCommandProcessor): notify_hints(self.ctx, self.client.team, list(hints)) return True else: - item_name, usable, response = get_intended_text(item_or_location) + item_name, usable, response = get_intended_text(item_or_location, self.ctx.console_names) if usable: if item_name in Items.hint_blacklist: self.output(f"Sorry, \"{item_name}\" is marked as non-hintable.") hints = [] - elif item_name in Items.item_name_groups: + elif item_name in self.ctx.item_name_groups: hints = [] - for item in Items.item_name_groups[item_name]: + for item in self.ctx.item_name_groups[item_name]: hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item)) elif item_name in Items.item_table: # item name hints = collect_hints(self.ctx, self.client.team, self.client.slot, item_name) @@ -968,14 +993,14 @@ class ClientMessageProcessor(CommonCommandProcessor): def get_checked_checks(ctx: Context, client: Client) -> list: - return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for + return [ctx.lookup_region_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for location_id, slot in ctx.locations if slot == client.slot and location_id in ctx.location_checks[client.team, client.slot]] def get_missing_checks(ctx: Context, client: Client) -> list: - return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for + return [ctx.lookup_region_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for location_id, slot in ctx.locations if slot == client.slot and location_id not in ctx.location_checks[client.team, client.slot]] @@ -1224,7 +1249,7 @@ class ServerCommandProcessor(CommonCommandProcessor): seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values()) if usable: item = " ".join(item_name) - item, usable, response = get_intended_text(item, Items.item_table.keys()) + item, usable, response = get_intended_text(item, self.ctx.lookup_items_id_to_name.values()) if usable: for client in self.ctx.endpoints: if client.name == seeked_player: @@ -1247,11 +1272,11 @@ class ServerCommandProcessor(CommonCommandProcessor): for (team, slot), name in self.ctx.player_names.items(): if name == seeked_player: item = " ".join(item_or_location) - item, usable, response = get_intended_text(item) + item, usable, response = get_intended_text(item, self.ctx.console_names) if usable: - if item in Items.item_name_groups: + if item in self.ctx.item_name_groups: hints = [] - for item in Items.item_name_groups[item]: + for item in self.ctx.item_name_groups[item]: hints.extend(collect_hints(self.ctx, team, slot, item)) elif item in Items.item_table: # item name hints = collect_hints(self.ctx, team, slot, item) From d3915ba41f475e362cd53ccdd1daba77f25fc9bc Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Thu, 21 Jan 2021 16:55:40 -0800 Subject: [PATCH 6/7] Server side location ids such as cheat console no longer listed as missing. --- MultiClient.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MultiClient.py b/MultiClient.py index b50f309f..bc30cc69 100644 --- a/MultiClient.py +++ b/MultiClient.py @@ -1087,7 +1087,9 @@ class ClientCommandProcessor(CommandProcessor): """List all missing location checks, from your local game state""" count = 0 checked_count = 0 - for location in Regions.lookup_name_to_id.keys(): + for location, location_id in Regions.lookup_name_to_id.items(): + if location_id < 0: + continue if location not in self.ctx.locations_checked: if location in self.ctx.items_missing: self.output('Missing: ' + location) From 6c1d3cb60d7094bc22d3849dfa953d3a82cdbe76 Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Thu, 21 Jan 2021 16:57:31 -0800 Subject: [PATCH 7/7] Get remaining things for better client/server interactions. --- MultiServer.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index 4667eae2..45b494d1 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -114,6 +114,7 @@ class Context(Node): self.tags = ['Berserker'] self.minimum_client_versions: typing.Dict[typing.Tuple[int, int], Utils.Version] = {} self.lookup_items_id_to_name = Items.lookup_id_to_name + self.lookup_items_name_to_id = {value: key for key, value in Items.lookup_id_to_name.items()} self.lookup_region_id_to_name = Regions.lookup_id_to_name self.lookup_region_name_to_id = Regions.lookup_name_to_id self.console_names = console_names @@ -160,6 +161,7 @@ class Context(Node): if "lookup_items_id_to_name" in jsonobj: lookups["lookup_items_id_to_name"] = True self.lookup_items_id_to_name = jsonobj["lookup_items_id_to_name"] + self.lookup_items_name_to_id = {value: key for key, value in self.lookup_items_id_to_name.items()} new_console_names |= set(self.lookup_items_id_to_name.values()) if "lookup_region_id_to_name" in jsonobj: @@ -540,8 +542,8 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations): if slot != target_player: ctx.broadcast_team(team, [['ItemSent', (slot, location, target_player, target_item)]]) logging.info('(Team #%d) %s sent %s to %s (%s)' % ( - team + 1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), - ctx.player_names[(team, target_player)], get_location_name_from_address(location))) + team + 1, ctx.player_names[(team, slot)], ctx.lookup_items_id_to_name(target_item, f"Unknown item (ID: {target_item})"), + ctx.player_names[(team, target_player)], ctx.lookup_region_id_to_name.get(location, f"Unknown location (ID: {location})"))) found_items = True elif target_player == slot: # local pickup, notify clients of the pickup if location not in ctx.location_checks[team, slot]: @@ -595,8 +597,8 @@ def collect_hints_location(ctx: Context, team: int, slot: int, location: str) -> def format_hint(ctx: Context, team: int, hint: Utils.Hint) -> str: text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \ - f"{ctx.lookup_items_id_to_name[hint.item]} is " \ - f"at {get_location_name_from_address(hint.location)} " \ + f"{ctx.lookup_items_id_to_name.get(hint.item, f'Unknown item (ID:{hint.item})')} is " \ + f"at {ctx.lookup_region_id_to_name.get(hint.location, f'Unknown location (ID:{hint.location})')} " \ f"in {ctx.player_names[team, hint.finding_player]}'s World" if hint.entrance: @@ -900,7 +902,7 @@ class ClientMessageProcessor(CommonCommandProcessor): if self.ctx.item_cheat: item_name, usable, response = get_intended_text(item_name, self.ctx.lookup_items_id_to_name.values()) if usable: - new_item = ReceivedItem(Items.item_table[item_name][3], -1, self.client.slot) + new_item = ReceivedItem(self.ctx.lookup_items_name_to_id[item_name], -1, self.client.slot) get_received_items(self.ctx, self.client.team, self.client.slot).append(new_item) self.ctx.notify_all('Cheat console: sending "' + item_name + '" to ' + self.ctx.get_aliased_name(self.client.team, self.client.slot)) send_new_items(self.ctx)