diff --git a/contrib/playerInspect.py b/contrib/playerInspect.py index 3b24060..e8d070e 100644 --- a/contrib/playerInspect.py +++ b/contrib/playerInspect.py @@ -2,19 +2,19 @@ Very basic player.dat inspection script """ -from __future__ import print_function - import os import sys +import argparse +from pathlib import Path # incantation to be able to import overviewer_core if not hasattr(sys, "frozen"): sys.path.insert(0, os.path.abspath(os.path.join(os.path.split(__file__)[0], '..'))) + from overviewer_core.nbt import load from overviewer_core import items - def print_player(data, sub_entry=False): indent = "" if sub_entry: @@ -36,26 +36,58 @@ def print_player(data, sub_entry=False): print(" %-3d %s" % (item['Count'], items.id2item(item['id']))) -if __name__ == '__main__': - if len(sys.argv) < 2 or len(sys.argv) > 3: - print("Usage: {} [selected player]" - .format(sys.argv[0]), file=sys.stderr) - sys.exit(1) - print("Inspecting %s" % sys.argv[1]) +def find_all_player_files(dir_path): + for player_file in dir_path.iterdir(): + player = player_file.stem + yield player_file, player - if os.path.isdir(sys.argv[1]): - directory = sys.argv[1] - if len(sys.argv) > 2: - selected_player = sys.argv[2] - else: - selected_player = None - for player_file in os.listdir(directory): - player = player_file.split(".")[0] - if selected_player in [None, player]: - print("") - print(player) - data = load(os.path.join(directory, player_file))[1] - print_player(data, sub_entry=(selected_player is None)) - else: - data = load(sys.argv[1])[1] - print_player(data) + +def find_player_file(dir_path, selected_player): + for player_file, player in find_all_player_files(dir_path): + if selected_player == player: + return player_file, player + raise FileNotFoundError() + + +def load_and_output_player(player_file_path, player, sub_entry=False): + with player_file_path.open('rb') as f: + player_data = load(f)[1] + print("") + print(player) + print_player(player_data, sub_entry=sub_entry) + + +def dir_or_file(path): + p = Path(path) + if not p.is_file() and not p.is_dir(): + raise argparse.ArgumentTypeError("Not a valid file or directory path") + return p + + +def main(path, selected_player=None): + print("Inspecting %s" % args.path) + + if not path.is_dir(): + load_and_output_player(args.path) + return + + if selected_player is None: + for player_file, player in find_all_player_files(args.path): + load_and_output_player(player_file, player) + return + + try: + player_file, player = find_player_file(args.path, args.selected_player) + load_and_output_player(player_file, player, sub_entry=True) + except FileNotFoundError: + print("No %s.dat in %s" % (args.selected_player, args.path)) + sys.exit(1) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('path', metavar='', type=dir_or_file) + parser.add_argument('selected_player', nargs='?', default=None) + + args = parser.parse_args() + main(args.path, selected_player=args.selected_player) diff --git a/test/test_all.py b/test/test_all.py index af2955d..1021b6f 100644 --- a/test/test_all.py +++ b/test/test_all.py @@ -17,6 +17,7 @@ from test_tileset import TilesetTest from test_cache import TestLRU from test_contributors import TestContributors from test_cyrillic_convert import TestCyrillicConvert +from test_playerInspect import TestPlayerInspect # DISABLE THIS BLOCK TO GET LOG OUTPUT FROM TILESET FOR DEBUGGING if 0: diff --git a/test/test_playerInspect.py b/test/test_playerInspect.py new file mode 100644 index 0000000..718da29 --- /dev/null +++ b/test/test_playerInspect.py @@ -0,0 +1,176 @@ +import unittest +from io import StringIO +from pathlib import Path +from textwrap import dedent +from unittest.mock import patch, MagicMock + +import contrib.playerInspect as player_inspect + + +class TestPlayerInspect(unittest.TestCase): + def setUp(self): + self.player_data = { + 'AbsorptionAmount': 0.0, + 'Air': 300, + 'Attributes': [ + {'Base': 20.0, 'Name': 'generic.maxHealth'}, + {'Base': 0.0, 'Name': 'generic.knockbackResistance'}, + {'Base': 0.10000000149011612, 'Name': 'generic.movementSpeed'}, + {'Base': 0.0, 'Name': 'generic.armor'}, + {'Base': 0.0, 'Name': 'generic.armorToughness'}, + {'Base': 1.0, 'Name': 'generic.attackDamage'}, + {'Base': 4.0, 'Name': 'generic.attackSpeed'}, + {'Base': 0.0, 'Name': 'generic.luck'} + ], + 'DataVersion': 1631, + 'DeathTime': 0, + 'Dimension': 0, + 'EnderItems': [], + 'FallDistance': 0.0, + 'FallFlying': 0, + 'Fire': -20, + 'Health': 20.0, + 'HurtByTimestamp': 0, + 'HurtTime': 0, + 'Inventory': [{'Count': 1, 'Slot': -106, 'id': 'minecraft:sign'}], + 'Invulnerable': 0, + 'Motion': [0.0, -0.0784000015258789, 0.0], + 'OnGround': 1, + 'PortalCooldown': 0, + 'Pos': [-96.11859857363737, 70.0, -44.17768261916891], + 'Rotation': [-72.00011444091797, 38.250030517578125], + 'Score': 0, + 'SelectedItemSlot': 0, + 'SleepTimer': 0, + 'Sleeping': 0, + "SpawnX": 10, + "SpawnY": 52, + "SpawnZ": 10, + 'UUIDLeast': -7312926203658200544, + 'UUIDMost': 6651100054519957107, + 'XpLevel': 0, + 'XpP': 0.0, + 'XpSeed': 0, + 'XpTotal': 0, + 'abilities': { + 'flySpeed': 0.05000000074505806, + 'flying': 0, + 'instabuild': 1, + 'invulnerable': 1, + 'mayBuild': 1, + 'mayfly': 1, + 'walkSpeed': 0.10000000149011612 + }, + 'foodExhaustionLevel': 0.0, + 'foodLevel': 20, + 'foodSaturationLevel': 5.0, + 'foodTickTimer': 0, + 'playerGameType': 1, + 'recipeBook': { + 'isFilteringCraftable': 0, + 'isFurnaceFilteringCraftable': 0, + 'isFurnaceGuiOpen': 0, + 'isGuiOpen': 0, + 'recipes': [], + 'toBeDisplayed': [] + }, + 'seenCredits': 0 + } + + @patch('sys.stdout', new_callable=StringIO) + def test_print_player(self, mock_stdout): + expected = "\n".join([ + "Position:\t-96, 70, -44\t(dim: 0)", + "Spawn:\t\t10, 52, 10", + "Health:\t20\tLevel:\t\t0\t\tGameType:\t1", + "Food:\t20\tTotal XP:\t0", + "Inventory: 1 items", + " 1 minecraft:sign\n"]) + + player_inspect.print_player(self.player_data) + + self.assertEqual(mock_stdout.getvalue(), expected) + + @patch('sys.stdout', new_callable=StringIO) + def test_print_player_no_spawn(self, mock_stdout): + expected = "\n".join([ + "Position:\t-96, 70, -44\t(dim: 0)", + "Health:\t20\tLevel:\t\t0\t\tGameType:\t1", + "Food:\t20\tTotal XP:\t0", + "Inventory: 1 items", + " 1 minecraft:sign\n"]) + + player_data = { + k: v for k, v in self.player_data.items() + if k not in("SpawnX", "SpawnY", "SpawnZ") + } + player_inspect.print_player(player_data) + + self.assertEqual(mock_stdout.getvalue(), expected) + + @patch('sys.stdout', new_callable=StringIO) + def test_print_player_sub_entry(self, mock_stdout): + expected = "\n".join([ + "\tPosition:\t-96, 70, -44\t(dim: 0)", + "\tSpawn:\t\t10, 52, 10", + "\tHealth:\t20\tLevel:\t\t0\t\tGameType:\t1", + "\tFood:\t20\tTotal XP:\t0", + "\tInventory: 1 items\n"]) + + player_inspect.print_player(self.player_data, sub_entry=True) + + self.assertEqual(mock_stdout.getvalue(), expected) + + @patch('sys.stdout', new_callable=StringIO) + def test_print_player_sub_entry_no_spawn(self, mock_stdout): + expected = "\n".join([ + "\tPosition:\t-96, 70, -44\t(dim: 0)", + "\tHealth:\t20\tLevel:\t\t0\t\tGameType:\t1", + "\tFood:\t20\tTotal XP:\t0", + "\tInventory: 1 items\n"]) + + player_data = { + k: v for k, v in self.player_data.items() + if k not in("SpawnX", "SpawnY", "SpawnZ") + } + player_inspect.print_player(player_data, sub_entry=True) + + self.assertEqual(mock_stdout.getvalue(), expected) + + def test_find_all_player_files(self): + dir_path = MagicMock(Path) + files = [Path('def0492d-0fe9-43ff-a3d5-8c3fc9160c94.dat'), + Path('074c808a-1f04-4bdd-8385-bd74601210a1.dat'), + Path('104e149d-a802-4a27-ac8f-ceab5279087c.dat')] + dir_path.iterdir.return_value = (f for f in files) + + expected = [(Path('def0492d-0fe9-43ff-a3d5-8c3fc9160c94.dat'), + 'def0492d-0fe9-43ff-a3d5-8c3fc9160c94'), + (Path('074c808a-1f04-4bdd-8385-bd74601210a1.dat'), + '074c808a-1f04-4bdd-8385-bd74601210a1'), + (Path('104e149d-a802-4a27-ac8f-ceab5279087c.dat'), + '104e149d-a802-4a27-ac8f-ceab5279087c')] + result = player_inspect.find_all_player_files(dir_path) + self.assertListEqual(list(result), expected) + + def test_find_player_file(self): + dir_path = MagicMock(Path) + files = [Path('def0492d-0fe9-43ff-a3d5-8c3fc9160c94.dat'), + Path('074c808a-1f04-4bdd-8385-bd74601210a1.dat'), + Path('104e149d-a802-4a27-ac8f-ceab5279087c.dat')] + dir_path.iterdir.return_value = (f for f in files) + + expected = (Path('104e149d-a802-4a27-ac8f-ceab5279087c.dat'), + '104e149d-a802-4a27-ac8f-ceab5279087c') + result = player_inspect.find_player_file( + dir_path, selected_player='104e149d-a802-4a27-ac8f-ceab5279087c') + self.assertEqual(result, expected) + + def test_find_player_file_raises_when_selected_player_not_found(self): + dir_path = MagicMock(Path) + files = [Path('def0492d-0fe9-43ff-a3d5-8c3fc9160c94.dat'), + Path('104e149d-a802-4a27-ac8f-ceab5279087c.dat')] + dir_path.iterdir.return_value = (f for f in files) + + with self.assertRaises(FileNotFoundError): + player_inspect.find_player_file(dir_path, selected_player='NON_EXISTENT_UUID')