diff --git a/.flake8 b/.flake8 index 57dc6c5..edddf03 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,4 @@ [flake8] -exclude = wwrando, wwrando-dev-tanjo3, wwrando-random-settings +exclude = wwrando, wwrando-dev-tanjo3, wwrando-random-settings, wwrando-mixed-pools max-line-length = 120 extend-ignore = E203, W503 \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 21006c1..220b317 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,7 @@ path = wwrando-random-settings url = https://github.com/wooferzfg/wwrando.git branch = random-settings +[submodule "wwrando-mixed-pools"] + path = wwrando-mixed-pools + url = https://github.com/wooferzfg/wwrando.git + branch = mixed-pools-tourney diff --git a/randobot/constants.py b/randobot/constants.py index c179a5b..42d64d4 100644 --- a/randobot/constants.py +++ b/randobot/constants.py @@ -5,8 +5,7 @@ class SeedType(Enum): STANDARD = 1 SPOILER_LOG = 2 - DEV = 3 - RANDOM_SETTINGS = 4 + RANDOM_SETTINGS = 3 STANDARD_PERMALINKS = OrderedDict([ @@ -67,6 +66,19 @@ class SeedType(Enum): DEV_VERSION = "1.9.10_dev" DEV_DOWNLOAD = "https://github.com/tanjo3/wwrando/releases" +MP_PERMALINKS = OrderedDict([ + ("mixed-pools", "MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAEA=="), +]) +MP_DEFAULT = ["mixed-pools"] + +MP_SL_PERMALINKS = OrderedDict([ + ("mixed-pools-sl", "MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAAA=="), +]) +MP_SL_DEFAULT = ["mixed-pools-sl"] + +MP_PATH = "wwrando-mixed-pools" +MP_DOWNLOAD = "https://github.com/wooferzfg/wwrando/releases/tag/mixed-pools-tourney-build" + RS_PATH = "wwrando-random-settings" RS_VERSION = "v1.3.1" RS_DOWNLOAD = "https://github.com/tanjo3/wwrando/releases/tag/RS_v1.3.1" diff --git a/randobot/generator.py b/randobot/generator.py index 0074a55..3e2691f 100644 --- a/randobot/generator.py +++ b/randobot/generator.py @@ -14,15 +14,19 @@ class Generator: def __init__(self, github_token): self.github_token = github_token - def generate_seed(self, randomizer_path, permalink, username, generate_spoiler_log): + def generate_seed(self, randomizer_path, permalink, username, generate_spoiler_log, new_args_format=False): trimmed_name = re.sub(r'\W+', '', username)[:12] random_suffix = shortuuid.ShortUUID().random(length=10) seed_name = f"{trimmed_name}{random_suffix}" file_name = "".join(random.choice(string.digits) for _ in range(6)) + if new_args_format: + args = f"--noui --dry --seed={seed_name} --permalink={permalink}" + else: + args = f"-noui -seed={seed_name} -permalink={permalink}" + os.system( - f"/venv/{randomizer_path}/bin/python {randomizer_path}/wwrando.py " - f"-noui -seed={seed_name} -permalink={permalink}" + f"/venv/{randomizer_path}/bin/python {randomizer_path}/wwrando.py {args}" ) permalink_file_name = f"permalink_{seed_name}.txt" diff --git a/randobot/handler.py b/randobot/handler.py index aaa1f82..2d9977f 100644 --- a/randobot/handler.py +++ b/randobot/handler.py @@ -30,6 +30,7 @@ def room_setup(self): self.state["permalink_available"] = False self.state["file_name_available"] = False self.state["permalink"] = None + self.state["example_permalink"] = None self.state["seed_hash"] = None self.state["spoiler_log_url"] = None self.state["planning_time"] = constants.DEFAULT_PLANNING_TIME @@ -336,12 +337,35 @@ async def ex_rolldevseed(self, args, message): username = message.get('user', {}).get('name') generated_seed = await self._generate_seed(constants.DEV_PATH, settings_permalink, username, False) - await self.update_race_room_with_generated_seed(settings_permalink, generated_seed, SeedType.DEV) + await self.update_race_room_with_generated_seed(settings_permalink, generated_seed, SeedType.STANDARD) await self.send_message( f"Please note that this seed has been rolled on the {constants.DEV_VERSION} version of the randomizer. " f"You can download it here: {constants.DEV_DOWNLOAD}" ) + async def ex_rollmpseed(self, args, message): + if not await self.can_roll_standard_seed(message): + return + + await self.send_message("Rolling seed...") + + settings_permalink = await self.choose_permalink( + constants.MP_DEFAULT, + constants.MP_PERMALINKS, + args + ) + + username = message.get('user', {}).get('name') + generated_seed = await self._generate_seed( + constants.MP_PATH, + settings_permalink, + username, + generate_spoiler_log=False, + new_args_format=True, + ) + await self.update_race_room_with_generated_seed(settings_permalink, generated_seed, SeedType.STANDARD) + await self.print_mixed_pools_build() + async def ex_randomsettings(self, args, message): if not await self.can_roll_standard_seed(message): return @@ -381,6 +405,20 @@ async def can_roll_standard_seed(self, message): return True + async def can_start_spoiler_log_race(self, message): + if self.state.get("locked") and not can_monitor(message): + await self.send_message( + "Seed rolling is locked. Only the creator of this room, a race monitor, " + "or a moderator can roll a seed. (Use !unlock to unlock seed rolling.)" + ) + return False + + if self.state.get("spoiler_log_seed_rolled"): + await self.send_message("Seed already rolled!") + return False + + return True + async def update_race_room_with_generated_seed(self, settings_permalink, generated_seed, type): permalink = generated_seed.get("permalink") seed_hash = generated_seed.get("seed_hash") @@ -433,18 +471,10 @@ async def ex_startspoilerlogtimer(self, args, message): await self.send_message("Race already started!") return - self.loop.create_task(self.start_spoiler_log_race(None, None)) + self.loop.create_task(self.start_spoiler_log_race()) async def ex_startspoilerlograce(self, args, message): - if self.state.get("locked") and not can_monitor(message): - await self.send_message( - "Seed rolling is locked. Only the creator of this room, a race monitor, " - "or a moderator can roll a seed. (Use !unlock to unlock seed rolling.)" - ) - return - - if self.state.get("spoiler_log_seed_rolled"): - await self.send_message("Seed already rolled!") + if not await self.can_start_spoiler_log_race(message): return settings_permalink = await self.choose_permalink( @@ -454,16 +484,38 @@ async def ex_startspoilerlograce(self, args, message): ) username = message.get('user', {}).get('name') - self.loop.create_task(self.start_spoiler_log_race(settings_permalink, username)) + await self.send_message("Rolling seed...") + generated_seed = await self._generate_seed(constants.STANDARD_PATH, settings_permalink, username, True) + await self.update_race_room_with_generated_seed(settings_permalink, generated_seed, SeedType.SPOILER_LOG) - async def start_spoiler_log_race(self, settings_permalink, username): - self.state["spoiler_log_seed_rolled"] = True + self.loop.create_task(self.start_spoiler_log_race()) + + async def ex_startmpspoilerlograce(self, args, message): + if not await self.can_start_spoiler_log_race(message): + return - if settings_permalink: - await self.send_message("Rolling seed...") + settings_permalink = await self.choose_permalink( + constants.MP_SL_DEFAULT, + constants.MP_SL_PERMALINKS, + args + ) + username = message.get('user', {}).get('name') - generated_seed = await self._generate_seed(constants.STANDARD_PATH, settings_permalink, username, True) - await self.update_race_room_with_generated_seed(settings_permalink, generated_seed, SeedType.SPOILER_LOG) + await self.send_message("Rolling seed...") + generated_seed = await self._generate_seed( + constants.MP_PATH, + settings_permalink, + username, + generate_spoiler_log=True, + new_args_format=True, + ) + await self.update_race_room_with_generated_seed(settings_permalink, generated_seed, SeedType.SPOILER_LOG) + await self.print_mixed_pools_build() + + self.loop.create_task(self.start_spoiler_log_race()) + + async def start_spoiler_log_race(self): + self.state["spoiler_log_seed_rolled"] = True planning_time = self.state.get("planning_time") @@ -618,6 +670,12 @@ async def print_banned_presets(self): def seconds_remaining(self): return (self.state.get("race_start_time") - datetime.now()).total_seconds() + async def print_mixed_pools_build(self): + await self.send_message( + f"Please note that this seed has been rolled on the Mixed Pools Tournament version of the randomizer. " + f"You can download it here: {constants.MP_DOWNLOAD}" + ) + async def print_example_permalink(self): example_permalink = self.state.get("example_permalink") if example_permalink: @@ -742,9 +800,9 @@ def _is_permalink(self, permalink_or_preset): return True return False - async def _generate_seed(self, randomizer_path, permalink, username, generate_spoiler_log): + async def _generate_seed(self, *args, **kwargs): try: - return self.generator.generate_seed(randomizer_path, permalink, username, generate_spoiler_log) + return self.generator.generate_seed(*args, **kwargs) except Exception: await self.send_message("Failed to generate seed!") raise Exception("Failed to generate seed") diff --git a/randobot/tests/test_handler.py b/randobot/tests/test_handler.py index 456c418..7fa9833 100644 --- a/randobot/tests/test_handler.py +++ b/randobot/tests/test_handler.py @@ -10,7 +10,7 @@ def generate_seed(self, randomizer_path, permalink, username, generate_spoiler_l raise Exception("Method not properly mocked") -def mock_generate_seed_standard(randomizer_path, permalink, username, generate_spoiler_log): +def mock_generate_seed_standard(randomizer_path, permalink, username, generate_spoiler_log, new_args_format=False): return { "file_name": "FILENAME", "permalink": f"PERMA_{permalink}", @@ -19,6 +19,18 @@ def mock_generate_seed_standard(randomizer_path, permalink, username, generate_s } +def mock_generate_seed_spoiler_log(randomizer_path, permalink, username, generate_spoiler_log, new_args_format=False): + if not generate_spoiler_log: + raise Exception("Did not generate spoiler log") + + return { + "file_name": "FILENAME", + "permalink": f"PERMA_{permalink}", + "seed_hash": "SEED HASH", + "spoiler_log_url": "SPOILER_LOG_URL", + } + + class MockLogger(): def info(self, data): pass @@ -30,6 +42,13 @@ def async_return(result): return f +async def wait_for_all_async_tasks(): + tasks = [ + t for t in asyncio.all_tasks() if t is not asyncio.current_task() + ] + await asyncio.gather(*tasks) + + def create_rando_handler(generator, state): handler = RandoHandler(generator, logger=MockLogger(), conn=None, state=state) handler.room_setup() @@ -359,6 +378,183 @@ async def test_invalid_ra( call("Invalid preset: \"s6+garbage\" - Invalid runners' agreement modifier!"), ]) + @patch.object(MockGenerator, "generate_seed", side_effect=mock_generate_seed_standard) + @patch.object(RandoHandler, "set_raceinfo", return_value=async_return(None)) + @patch.object(RandoHandler, "send_message", return_value=async_return(None)) + async def test_rollmpseed(self, mock_send_message, mock_set_raceinfo, mock_generate_seed): + generator = MockGenerator() + state = {} + handler = create_rando_handler(generator, state) + await handler.ex_rollmpseed([], get_mock_message_data()) + + self.assertEqual(mock_send_message.call_count, 4) + mock_send_message.assert_has_calls([ + call("Rolling seed..."), + call("Permalink: PERMA_MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAEA=="), + call("Seed Hash: SEED HASH"), + call( + "Please note that this seed has been rolled on the Mixed Pools " + "Tournament version of the randomizer. You can download it here: " + "https://github.com/wooferzfg/wwrando/releases/tag/mixed-pools-tourney-build" + ), + ]) + + self.assertEqual(mock_set_raceinfo.call_count, 1) + mock_set_raceinfo.assert_has_calls([ + call("PERMA_MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAEA== | Seed Hash: SEED HASH", False, False), + ]) + + self.assertEqual(mock_generate_seed.call_count, 1) + mock_generate_seed.assert_has_calls([ + call( + "wwrando-mixed-pools", + "MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAEA==", + "test_user", + generate_spoiler_log=False, + new_args_format=True, + ), + ]) + + @patch("asyncio.sleep", return_value=async_return(None)) + @patch("random.random", return_value=0.6123) + @patch.object(MockGenerator, "generate_seed", side_effect=mock_generate_seed_spoiler_log) + @patch.object(RandoHandler, "set_raceinfo", return_value=async_return(None)) + @patch.object(RandoHandler, "send_message", return_value=async_return(None)) + async def test_startspoilerlograce( + self, + mock_send_message, + mock_set_raceinfo, + mock_generate_seed, + mock_random, + mock_sleep, + ): + generator = MockGenerator() + state = {} + handler = create_rando_handler(generator, state) + await handler.ex_startspoilerlograce([], get_mock_message_data()) + + await wait_for_all_async_tasks() + + self.assertEqual(mock_send_message.call_count, 13) + mock_send_message.assert_has_calls([ + call("Settings: preset-d (Lookout Platforms and Rafts)"), + call("Rolling seed..."), + call("Seed rolled!"), + call("Preparation stage starts in 15 seconds..."), + call("5..."), + call("4..."), + call("3..."), + call("2..."), + call("1..."), + call("You have 60 minutes to prepare your route!"), + call("Spoiler Log: SPOILER_LOG_URL"), + call("Example Permalink: MS4xMC4wAEEAFwcmAgEAoAMUsAACAAAAAAGAAAAA"), + call("Warning: The seed from this permalink does not match the actual permalink!") + ]) + + self.assertEqual(mock_set_raceinfo.call_count, 1) + mock_set_raceinfo.assert_has_calls([ + call("Settings: preset-d (Lookout Platforms and Rafts)", False, False), + ]) + + self.assertEqual(mock_generate_seed.call_count, 1) + mock_generate_seed.assert_has_calls([ + call("wwrando", "MS4xMC4wAEEAFwcmAgEAoAMUsAACAAAAAAGAAAAA", "test_user", True), + ]) + + self.assertEqual(state["spoiler_log_seed_rolled"], True) + self.assertEqual(state["example_permalink"], "MS4xMC4wAEEAFwcmAgEAoAMUsAACAAAAAAGAAAAA") + self.assertEqual(state["permalink"], "PERMA_MS4xMC4wAEEAFwcmAgEAoAMUsAACAAAAAAGAAAAA") + self.assertEqual(state["spoiler_log_url"], "SPOILER_LOG_URL") + self.assertEqual(state["seed_hash"], "SEED HASH") + self.assertEqual(state["file_name"], "FILENAME") + + @patch("asyncio.sleep", return_value=async_return(None)) + @patch.object(MockGenerator, "generate_seed", side_effect=mock_generate_seed_spoiler_log) + @patch.object(RandoHandler, "set_raceinfo", return_value=async_return(None)) + @patch.object(RandoHandler, "send_message", return_value=async_return(None)) + async def test_startspoilerlogtimer(self, mock_send_message, mock_set_raceinfo, mock_generate_seed, mock_sleep): + generator = MockGenerator() + state = {} + handler = create_rando_handler(generator, state) + await handler.ex_startspoilerlogtimer([], get_mock_message_data()) + + await wait_for_all_async_tasks() + + self.assertEqual(mock_send_message.call_count, 7) + mock_send_message.assert_has_calls([ + call("Preparation stage starts in 15 seconds..."), + call("5..."), + call("4..."), + call("3..."), + call("2..."), + call("1..."), + call("You have 60 minutes to prepare your route!"), + ]) + + mock_set_raceinfo.assert_not_called() + mock_generate_seed.assert_not_called() + + self.assertEqual(state["spoiler_log_seed_rolled"], True) + self.assertEqual(state["example_permalink"], None) + self.assertEqual(state["permalink"], None) + self.assertEqual(state["spoiler_log_url"], None) + self.assertEqual(state["seed_hash"], None) + self.assertEqual(state["file_name"], None) + + @patch("asyncio.sleep", return_value=async_return(None)) + @patch.object(MockGenerator, "generate_seed", side_effect=mock_generate_seed_spoiler_log) + @patch.object(RandoHandler, "set_raceinfo", return_value=async_return(None)) + @patch.object(RandoHandler, "send_message", return_value=async_return(None)) + async def test_startmpspoilerlograce(self, mock_send_message, mock_set_raceinfo, mock_generate_seed, mock_sleep): + generator = MockGenerator() + state = {} + handler = create_rando_handler(generator, state) + await handler.ex_startmpspoilerlograce([], get_mock_message_data()) + + await wait_for_all_async_tasks() + + self.assertEqual(mock_send_message.call_count, 13) + mock_send_message.assert_has_calls([ + call("Rolling seed..."), + call("Seed rolled!"), + call( + "Please note that this seed has been rolled on the Mixed Pools " + "Tournament version of the randomizer. You can download it here: " + "https://github.com/wooferzfg/wwrando/releases/tag/mixed-pools-tourney-build" + ), + call("Preparation stage starts in 15 seconds..."), + call("5..."), + call("4..."), + call("3..."), + call("2..."), + call("1..."), + call("You have 60 minutes to prepare your route!"), + call("Spoiler Log: SPOILER_LOG_URL"), + call("Example Permalink: MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAAA=="), + call("Warning: The seed from this permalink does not match the actual permalink!") + ]) + + mock_set_raceinfo.assert_not_called() + + self.assertEqual(mock_generate_seed.call_count, 1) + mock_generate_seed.assert_has_calls([ + call( + "wwrando-mixed-pools", + "MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAAA==", + "test_user", + generate_spoiler_log=True, + new_args_format=True, + ), + ]) + + self.assertEqual(state["spoiler_log_seed_rolled"], True) + self.assertEqual(state["example_permalink"], "MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAAA==") + self.assertEqual(state["permalink"], "PERMA_MS4xMC4wX2Q1MTRhMjIAQQDfsGDs8CcC4vgJxj0AACQfAAAAAA==") + self.assertEqual(state["spoiler_log_url"], "SPOILER_LOG_URL") + self.assertEqual(state["seed_hash"], "SEED HASH") + self.assertEqual(state["file_name"], "FILENAME") + if __name__ == "__main__": unittest.main() diff --git a/setup-wwrando.sh b/setup-wwrando.sh index de02e1a..c51c144 100755 --- a/setup-wwrando.sh +++ b/setup-wwrando.sh @@ -1,6 +1,6 @@ #!/bin/bash -ex -for rando in wwrando wwrando-dev-tanjo3 wwrando-random-settings; do +for rando in wwrando wwrando-dev-tanjo3 wwrando-random-settings wwrando-mixed-pools; do (cd $rando && python -m venv /venv/$rando --upgrade-deps && /venv/$rando/bin/pip install --no-cache-dir -r requirements.txt) diff --git a/wwrando-mixed-pools b/wwrando-mixed-pools new file mode 160000 index 0000000..d55df73 --- /dev/null +++ b/wwrando-mixed-pools @@ -0,0 +1 @@ +Subproject commit d55df73cf902c02df428217958a3000084b6961b