From c61687f99c6e254fb37158e7fe36f04700a0a9c0 Mon Sep 17 00:00:00 2001 From: syntaxaire Date: Mon, 7 Aug 2023 13:16:05 -0400 Subject: [PATCH 1/2] Add tests for character code decoding and sheets --- tests/qud_decode_test.py | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/qud_decode_test.py diff --git a/tests/qud_decode_test.py b/tests/qud_decode_test.py new file mode 100644 index 0000000..a20e3e6 --- /dev/null +++ b/tests/qud_decode_test.py @@ -0,0 +1,61 @@ +"""Tests for the newstyle character code decoder.""" +import base64 +import gzip +import json + +import pytest + +from bot.helpers.qud_decode import Character + +# Test cases are lists in this format: +# [ +# build code, +# [expected attributes in game order], +# [expected bonuses in game order] +# ] + +# game order is Strength, Agility, Toughness, Intelligence, Willpower, Ego + +CASES = [ + [ + "H4sIAAAAAAAAA81WXW/aMBR9r9T/YEV7DIgiNnWVeGDpNra1EmtoO2nqg5PcJV4d2/LHKob477MNhSSsg05qBBIKvud+HO49ufL8+AihIMcl/AKpCGfBGQr63V633xt0X58GoccTQ2hWcTixDr0VVvLMUFDW/N2dEZovH2toOhPggr5dXXSjAkucapDvXEbV/Woy9/0IjGvrdukDQjRSCsqEzjpRbANEiG6WtYcbZiGKDNVGwpCB0RLTEE1MQkn6BWZTfg9syAylS46eTIY1tjTW7KzplX4utXObZIvepojN+ejv0l4ajTVkaGxKzGpeW718xBbLH4vwv3sZm+RAW1ljtruTK3eXNE4LTrFsq4V+bja3OsAmNrjtbmMpbMZe1aKAQupzbF7a5WdePaCVgFdNjgpSgsTV3N4n4oZp63DSBG6wJNhDvSqyCPetOOb8XqEfXKIPALrFwtMH3hkDziBrs6gkgkLnJydMt1r4nFudQqc0KqWtFr5mSmNbGrmdWcKTlfv7Vt4c7lraFCOtJUmMhkNcFU1yu3fFxIlPTYxMC6ysFs7q8wtGOaFEz6z9bW0mwfucW+ObuvGTFTKlJAeWOqqndTTWEliui+24KTd5wUCp7aBbQqngDyBd1Aapai7AIhbg1dEZDOrAFZSYMMLy5kpM7N8duUVZj3hB5URGaV6S37DGD1BBT5HcrSRmb5R+h2OWzVBMTV6DBbj5OJJVq1VK5kfbBITkzC6D+K9RLzmkgnMFscZSW9Fc8NTvrkMc1D+I7nHTasS5Yp+5EPh5Fy73uDs+WvwB4vrnYFQMAAA=", # noqa E501 + [16, 19, 18, 18, 16, 16], # attributes + [2, 2, 0, 2, 0, 0], # bonuses + ], + [ + "H4sIAAAAAAAAA81W22rbQBB9D+QfFtFHxTjBbdOAHxQl9JIE3MikhZKHlTSVl6x2xV5SVON/76zk2JKc1ElLhA1G1pyZM8dzWXa+v0eIl9Ec7kFpJoV3QryjwXBwNBwN3h57foXHlvG04XCIDsMllsvUctBo/uHeCZnXjxU0LQtwQd+vLwfhjCqaGFCnjlEPvtrUfT+CkAbdrqoAnwRaQx7z8iCMMKDwyU2de7xW5pPQcmMVjAVYoyj3ycTGnCUXUE7lHYixsJzXGisxKTUUZazUoemNeam0MyTZkLdOgpwP/o72yhpqICWfbE5Fy2ujlg/Yov6x8P+5lpGNd7SULWXbK7l0d6QTxjPF8r5KWPUNufUOFrGjbXsZ8wIZh02LBg5JxbFe2vozb76Q5QAvi3zughRLKCc446BqwO9EhNIKg+6HXeCGKkYraNhEFv5z859ChoVgIusx51QKlpCAc1BZ2WPeM5kz0XeBQ06ZupclFQn8f9r1y21PWxsYHM/YGtjFte2K2763E8mE0ROrkhnVkLZzIh5kuA2mRPv7Vk+880yi8UPb+FkY4Jxl4Jp7Qt610cgoEJmZbZJNpc1mArQ7Ko7b0DfGeSF/gXJ8a6Q5cB4togKq6TgYjdrANeSUCbfPneMpxr8buEOrHfGKkxNabXDjfsMK38EJekrk9kkSeLtz5Bf05x1tIQW41jh9TSsOSVp1tQsUSgo8B6JHo16zPzMpNUSGKoPzcimT6szaxR79RegzLjydOJfsiywK+rJ7j3vc7u8t/gCu1vr52wsAAA==", # noqa E501 + [17, 17, 18, 16, 16, 19], # attributes + [0, 0, 0, 0, 2, 0], # bonuses + ], +] + + +@pytest.mark.parametrize("test_input,expected_attrs,_", CASES) +def test_decode_attributes(test_input, expected_attrs, _): + """Check the decoded attributes against the expected attributes.""" + decode = base64.b64decode(test_input) + unzip = gzip.decompress(decode).decode(encoding='utf-8') + code = json.loads(unzip) + char = Character(code) + assert char.attributes == expected_attrs + + +@pytest.mark.parametrize("test_input,_,expected_bonuses", CASES) +def test_decode_bonuses(test_input, _, expected_bonuses): + """Check the decoded bonuses against the expected bonuses.""" + decode = base64.b64decode(test_input) + unzip = gzip.decompress(decode).decode(encoding='utf-8') + code = json.loads(unzip) + char = Character(code) + assert char.bonuses == expected_bonuses + + +@pytest.mark.parametrize("test_input,_,__", CASES) +def test_decode_make_sheet(test_input, _, __): + """Check that a text sheet can be rendered from each character code.""" + decode = base64.b64decode(test_input) + unzip = gzip.decompress(decode).decode(encoding='utf-8') + code = json.loads(unzip) + char = Character(code) + sheet = char.make_sheet() + assert len(sheet) > 100 From d34a557fc876e79e00bdc8981cae824d41d6d8e9 Mon Sep 17 00:00:00 2001 From: syntaxaire Date: Mon, 7 Aug 2023 13:17:48 -0400 Subject: [PATCH 2/2] Fix attribute ordering in character decoding Originally, attributes were serialized in "game order" (Strength first). At some point the game began serializing these in alphabetical order instead. Since we depended on game order, this swapped attributes around in the resulting tables. Decoding is now based on attribute name rather than any expected order. --- bot/helpers/qud_decode.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bot/helpers/qud_decode.py b/bot/helpers/qud_decode.py index cc78d41..a1a1a7c 100644 --- a/bot/helpers/qud_decode.py +++ b/bot/helpers/qud_decode.py @@ -9,8 +9,11 @@ from hagadias.qudtile import QudTile from bot.shared import gameroot + gamecodes = gameroot.get_character_codes() +ATTR_NAMES = ("Strength", "Agility", "Toughness", "Intelligence", "Willpower", "Ego") + class Character: """Represents a Caves of Qud player character. This class is intended for modern build codes @@ -52,7 +55,9 @@ def __init__(self, code: dict): case ['XRL.CharacterBuilds.Qud.QudAttributesModule', *_]: pointspurchased = module['data']['PointsPurchased'] base = 10 if self.genotype == 'Mutated Human' else 12 - self.attributes = [base + attr for name, attr in pointspurchased.items()] + self.attributes = [] + for attribute in ATTR_NAMES: + self.attributes.append(base + pointspurchased[attribute]) case ['XRL.CharacterBuilds.Qud.QudCustomizeCharacterModule', *_]: self.name = module['data']['name'] self.pet = module['data']['pet'] @@ -69,11 +74,10 @@ def __init__(self, code: dict): def make_sheet(self) -> str: """Build a printable character sheet for the Character.""" attr_widths = (11, 11, 11, 14, 14, 14) - attr_names = ('Strength:', 'Agility:', 'Toughness:', 'Intelligence:', 'Willpower:', 'Ego:') attr_strings = [] - for width, attr_text, attr, bonus in zip(attr_widths, - attr_names, + for width, attr_name, attr, bonus in zip(attr_widths, + ATTR_NAMES, self.attributes, self.bonuses): # print a +/- in front of any existing bonus @@ -83,15 +87,15 @@ def make_sheet(self) -> str: bonus_text = f'{bonus}' # already has a minus sign else: bonus_text = '' - attr_strings.append(f'{attr_text:{width}}{attr:2}{bonus_text}') + attr_strings.append(f'{attr_name:{width}}{attr:2}{bonus_text}') if hasattr(self, 'name') and self.name is not None: title = f'{self.name} the {self.genotype} {self.subtype}' else: title = f'{self.genotype} {self.subtype}' charsheet = f"""{title} -{attr_strings[0]:18}{attr_strings[3]} -{attr_strings[1]:18}{attr_strings[4]} -{attr_strings[2]:18}{attr_strings[5]}""" +{attr_strings[0]:20}{attr_strings[3]} +{attr_strings[1]:20}{attr_strings[4]} +{attr_strings[2]:20}{attr_strings[5]}""" charsheet += f"\n{self.selection_noun}s: {', '.join(self.selections)}\n" charsheet += f"Starting location: {self.startinglocation}" return charsheet