diff --git a/worlds/lol/Connector.py b/worlds/lol/Connector.py index 61c1e05eaaa6..1128070d10fa 100644 --- a/worlds/lol/Connector.py +++ b/worlds/lol/Connector.py @@ -15,16 +15,19 @@ ###SET GLOBAL VARIABLES### url = "https://127.0.0.1:2999/liveclientdata/allgamedata" +GAME_WIN_LOCATION_CODE = 566900001 unlocked_champion_ids = [] total_lp_gained = 0 in_match = False +tracked_teammates = set() game_values = { "required_assists": 0, "required_cs" : 0, "required_kills" : 0, "required_lp" : 0, "required_vs" : 0, - "current_lp" : 0 + "current_lp" : 0, + "starting_champions": 0 } ###SET UP GAME COMMUNICATION PATH### @@ -51,10 +54,11 @@ def get_items(game_values): if file.startswith("AP"): with open(os.path.join(game_communication_path, file), 'r') as f: item_id = int(f.readline()) - if item_id % 565000000 == 0: + decoded_item = item_id - 565000000 + if decoded_item == 0: game_values["current_lp"] = game_values["current_lp"] + 1 - else: - unlocked_champion_ids.append(item_id % 565000000) + elif decoded_item in champions: + unlocked_champion_ids.append(decoded_item) f.close() def read_cfg(game_values): @@ -84,6 +88,11 @@ def read_cfg(game_values): game_values["required_vs"] = int(f.readline()) else: game_values["required_vs"] = 0 + if "Starting_Champion_Count.cfg" in files: + with open(os.path.join(game_communication_path, "Starting_Champion_Count.cfg"), 'r') as f: + game_values["starting_champions"] = max(0, int(f.readline())) + else: + game_values["starting_champions"] = 0 def display_champion_list(window): champion_table_rows = [] @@ -101,9 +110,9 @@ def display_values(window, game_values): value_table_rows.append(['Current LP:' , str(game_values["current_lp"])]) window["Values Table"].update(values=value_table_rows) -def send_starting_champion_check(): - for i in range(6): - with open(os.path.join(game_communication_path, "send56600000" + str(i)), 'w') as f: +def send_starting_champion_check(game_values): + for i in range(1, game_values["starting_champions"] + 1): + with open(os.path.join(game_communication_path, f"send{566000000 + i}"), 'w') as f: f.close() def check_lp_for_victory(game_values): @@ -111,6 +120,17 @@ def check_lp_for_victory(game_values): with open(os.path.join(game_communication_path, "victory"), 'w') as f: f.close() +def won_game(game_data): + for event in game_data["events"]["Events"]: + if event.get("EventName") == "GameEnd" and event.get("Result") == "Win": + return True + return False + +def send_game_win_location(game_data): + if won_game(game_data): + with open(os.path.join(game_communication_path, f"send{GAME_WIN_LOCATION_CODE}"), 'w') as f: + f.close() + def get_player_name(game_data): return game_data["activePlayer"]["riotIdGameName"] @@ -124,6 +144,36 @@ def get_champion_id(champion_name): if champions[champion_id]["name"] == champion_name: return champion_id +def get_player_data(game_data, player_name): + for player in game_data["allPlayers"]: + if player["riotIdGameName"] == player_name: + return player + return None + +def get_available_teammates(game_data): + player_name = get_player_name(game_data) + player_data = get_player_data(game_data, player_name) + if player_data is None: + return [] + + player_team = player_data.get("team") + teammate_names = [] + for player in game_data["allPlayers"]: + if player.get("team") == player_team and player["riotIdGameName"] != player_name: + teammate_names.append(player["riotIdGameName"]) + return sorted(teammate_names) + +def update_teammate_selector(window, teammate_names): + valid_teammates = sorted(tracked_teammates.intersection(teammate_names)) + set_to_index = [teammate_names.index(teammate_name) for teammate_name in valid_teammates] + window["Tracked Teammates List"].update(values=teammate_names, set_to_index=set_to_index) + +def get_tracked_players(game_data): + player_name = get_player_name(game_data) + teammate_names = set(get_available_teammates(game_data)) + selected_teammates = sorted(tracked_teammates.intersection(teammate_names)) + return [player_name] + selected_teammates + def took_tower(game_data, player_name): for event in game_data["events"]["Events"]: if event["EventName"] == "TurretKilled" and event["KillerName"] == player_name: @@ -208,11 +258,16 @@ def kills_above(game_data, player_name, score_target): def assists_above(game_data, player_name, score_target): return player_assists(game_data, player_name) >= score_target and score_target > 0 -def get_objectives_complete(game_data, game_values): +def get_objectives_complete_for_player(game_data, game_values, player_name): objectives_complete = [] - player_name = get_player_name(game_data) champion_name = get_champion_name(game_data, player_name) + if champion_name is None: + return objectives_complete, None + champion_id = get_champion_id(champion_name) + if champion_id is None: + return objectives_complete, None + if champion_id in unlocked_champion_ids: if assisted_epic_monster(game_data, player_name, "Dragon"): objectives_complete.append(1) @@ -232,7 +287,13 @@ def get_objectives_complete(game_data, game_values): objectives_complete.append(8) if creep_score_above(game_data, player_name, game_values["required_cs"]): objectives_complete.append(9) - send_locations(objectives_complete, champion_id) + return objectives_complete, champion_id + +def get_objectives_complete(game_data, game_values): + for player_name in get_tracked_players(game_data): + objectives_complete, champion_id = get_objectives_complete_for_player(game_data, game_values, player_name) + if champion_id is not None: + send_locations(objectives_complete, champion_id) def send_locations(objectives_complete, champion_id): for objective_id in objectives_complete: @@ -257,6 +318,16 @@ def send_locations(objectives_complete, champion_id): [sg.Table( [ ], headings = ["Value Type", "Value Amount"], key = "Values Table")] + ]), + sg.Column( + [ + [sg.Text("Tracked Teammates")], + [sg.Listbox( + values = [], + select_mode = sg.LISTBOX_SELECT_MODE_MULTIPLE, + size = (24, 6), + enable_events = True, + key = "Tracked Teammates List")] ]) ] ] @@ -269,19 +340,24 @@ def send_locations(objectives_complete, champion_id): break if event == 'Check for Match Button': in_match = True + if event == "Tracked Teammates List": + tracked_teammates = set(values["Tracked Teammates List"]) check_lp_for_victory(game_values) get_items(game_values) read_cfg(game_values) display_champion_list(window) display_values(window, game_values) - send_starting_champion_check() + send_starting_champion_check(game_values) if in_match: game_data = get_game_data() if game_data is None: window["In Match Text"].update("In Match: No Match Found") + update_teammate_selector(window, []) in_match = False else: window["In Match Text"].update("In Match: In Match") + update_teammate_selector(window, get_available_teammates(game_data)) get_objectives_complete(game_data, game_values) + send_game_win_location(game_data) window.close() \ No newline at end of file diff --git a/worlds/lol/Items.py b/worlds/lol/Items.py index 9b3d2cb7ac42..2ae348a49bc5 100644 --- a/worlds/lol/Items.py +++ b/worlds/lol/Items.py @@ -31,6 +31,51 @@ def get_items_by_category(category: str, disclude: list) -> Dict[str, LOLItemDat item_table[champions[champion_id]["name"]] = LOLItemData("Champion", code = 565_000000 + int(champion_id), classification = ItemClassification.progression, max_quantity = 1, weight = 1) item_table["LP"] = LOLItemData("Win Condition", code = 565_000000, classification = ItemClassification.progression, max_quantity = -1, weight = 1) +filler_item_names = [ + "Black Spear", + "Cull", + "Dark Seal", + "Doran's Blade", + "Doran's Ring", + "Doran's Shield", + "Guardian's Amulet", + "Guardian's Blade", + "Guardian's Dirk", + "Guardian's Hammer", + "Guardian's Horn", + "Guardian's Orb", + "Guardian's Shroud", + "Gustwalker Hatchling", + "Mosstomper Seedling", + "Scorchclaw Pup", + "Tear of the Goddess", + "World Atlas", + "Amplifying Tome", + "B. F. Sword", + "Blasting Wand", + "Cloak of Agility", + "Cloth Armor", + "Dagger", + "Faerie Charm", + "Glowing Mote", + "Long Sword", + "Needlessly Large Rod", + "Null-Magic Mantle", + "Pickaxe", + "Rejuvenation Bead", + "Ruby Crystal", + "Sapphire Crystal", +] + +for index, item_name in enumerate(filler_item_names, start=1): + item_table[item_name] = LOLItemData( + "Filler", + code=565_100000 + index, + classification=ItemClassification.filler, + max_quantity=-1, + weight=1, + ) + event_item_table: Dict[str, LOLItemData] = { } \ No newline at end of file diff --git a/worlds/lol/Locations.py b/worlds/lol/Locations.py index 5183cee41e1a..2594ef0df82c 100644 --- a/worlds/lol/Locations.py +++ b/worlds/lol/Locations.py @@ -39,11 +39,10 @@ def get_locations_by_category(category: str) -> Dict[str, LOLLocationData]: location_table[champion_name + " - Get X Kills"] = LOLLocationData("Objective", 566_000000 + (int(champion_id) * 100) + 8) location_table[champion_name + " - Get X Creep Score"] = LOLLocationData("Objective", 566_000000 + (int(champion_id) * 100) + 9) -location_table["Starting Champion 1"] = LOLLocationData("Starting", 566_000001) -location_table["Starting Champion 2"] = LOLLocationData("Starting", 566_000002) -location_table["Starting Champion 3"] = LOLLocationData("Starting", 566_000003) -location_table["Starting Champion 4"] = LOLLocationData("Starting", 566_000004) -location_table["Starting Champion 5"] = LOLLocationData("Starting", 566_000005) +for index in range(1, 101): + location_table[f"Starting Champion {index}"] = LOLLocationData("Starting", 566_000000 + index) + +location_table["Game Win - Enemy Nexus Destroyed"] = LOLLocationData("Objective", 566_900001) event_location_table: Dict[str, LOLLocationData] = { } diff --git a/worlds/lol/Options.py b/worlds/lol/Options.py index eb9b3c0f7647..e4358a31d9d0 100644 --- a/worlds/lol/Options.py +++ b/worlds/lol/Options.py @@ -15,6 +15,16 @@ class RequiredLPPercentage(Range): range_end = 100 display_name = "Required LP Percentage" + +class TotalLPCount(Range): + """ + Total LP items to place in the world. + """ + default = 80 + range_start = 1 + range_end = 500 + display_name = "Total LP Count" + class Champions(OptionSet): """ Which champions are possibly included in the item pool? @@ -65,7 +75,7 @@ class StartingChampions(Range): """ default = 3 range_start = 1 - range_end = 5 + range_end = 100 display_name = "Starting Champions" class ChampionSubsetCount(Range): @@ -77,6 +87,13 @@ class ChampionSubsetCount(Range): range_end = 200 display_name = "Champion Subset Count" +class ARAMMode(Toggle): + """ + Enable ARAM mode. The only checks enabled are: + Tower, Inhibitor, Assists, and Kills. + """ + display_name = "ARAM Mode" + @dataclass class LOLOptions(PerGameCommonOptions): champions: Champions @@ -84,6 +101,8 @@ class LOLOptions(PerGameCommonOptions): required_vision_score: RequiredVisionScore required_kills: RequiredKills required_assists: RequiredAssists + total_lp_count: TotalLPCount required_lp: RequiredLPPercentage starting_champions: StartingChampions - champion_subset_count: ChampionSubsetCount \ No newline at end of file + champion_subset_count: ChampionSubsetCount + aram_mode: ARAMMode \ No newline at end of file diff --git a/worlds/lol/Regions.py b/worlds/lol/Regions.py index f21bbc0e6d48..fc74d25313c5 100644 --- a/worlds/lol/Regions.py +++ b/worlds/lol/Regions.py @@ -18,22 +18,26 @@ def create_regions(multiworld: MultiWorld, player: int, options, possible_champi # Set up locations + aram = bool(options.aram_mode) for champion_id in champions: champion_name = champions[champion_id]["name"] if champion_name in possible_champions: - regions["Match"].locations.append(champion_name + " - Assist Taking Dragon") - regions["Match"].locations.append(champion_name + " - Assist Taking Rift Herald") - regions["Match"].locations.append(champion_name + " - Assist Taking Baron") + if not aram: + regions["Match"].locations.append(champion_name + " - Assist Taking Dragon") + regions["Match"].locations.append(champion_name + " - Assist Taking Rift Herald") + regions["Match"].locations.append(champion_name + " - Assist Taking Baron") regions["Match"].locations.append(champion_name + " - Assist Taking Tower") regions["Match"].locations.append(champion_name + " - Assist Taking Inhibitor") regions["Match"].locations.append(champion_name + " - Get X Assists") - if "Support" in champions[champion_id]["tags"]: + if not aram and "Support" in champions[champion_id]["tags"]: regions["Match"].locations.append(champion_name + " - Get X Ward Score") if "Support" not in champions[champion_id]["tags"]: regions["Match"].locations.append(champion_name + " - Get X Kills") - regions["Match"].locations.append(champion_name + " - Get X Creep Score") + if not aram: + regions["Match"].locations.append(champion_name + " - Get X Creep Score") for i in range(min(options.starting_champions, len(possible_champions))): regions["Match"].locations.append("Starting Champion " + str(i+1)) + regions["Match"].locations.append("Game Win - Enemy Nexus Destroyed") # Set up the regions correctly. for name, data in regions.items(): diff --git a/worlds/lol/Rules.py b/worlds/lol/Rules.py index a4a7b84fab99..c0030da9836e 100644 --- a/worlds/lol/Rules.py +++ b/worlds/lol/Rules.py @@ -9,20 +9,23 @@ def has_at_least(state: CollectionState, player: int, item_name, item_qty_requir return state.count(item_name, player) >= item_qty_required def set_rules(multiworld: MultiWorld, player: int, options, required_lp, possible_champions): + aram = bool(options.aram_mode) for champion_id in champions: champion_name = champions[champion_id]["name"] if champion_name in possible_champions: - multiworld.get_location(champion_name + " - Assist Taking Dragon" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) - multiworld.get_location(champion_name + " - Assist Taking Rift Herald", player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) - multiworld.get_location(champion_name + " - Assist Taking Baron" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) + if not aram: + multiworld.get_location(champion_name + " - Assist Taking Dragon" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) + multiworld.get_location(champion_name + " - Assist Taking Rift Herald", player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) + multiworld.get_location(champion_name + " - Assist Taking Baron" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) multiworld.get_location(champion_name + " - Assist Taking Tower" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) multiworld.get_location(champion_name + " - Assist Taking Inhibitor" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) multiworld.get_location(champion_name + " - Get X Assists" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) - if "Support" in champions[champion_id]["tags"]: + if not aram and "Support" in champions[champion_id]["tags"]: multiworld.get_location(champion_name + " - Get X Ward Score" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) if "Support" not in champions[champion_id]["tags"]: multiworld.get_location(champion_name + " - Get X Kills" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) - multiworld.get_location(champion_name + " - Get X Creep Score" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) + if not aram: + multiworld.get_location(champion_name + " - Get X Creep Score" , player).access_rule = lambda state, champion_name = champion_name: has_item(state, player, champion_name) # Win condition. multiworld.completion_condition[player] = lambda state: has_at_least(state, player, "LP", required_lp) diff --git a/worlds/lol/__init__.py b/worlds/lol/__init__.py index 06a7dedf24b5..7960b7abd862 100644 --- a/worlds/lol/__init__.py +++ b/worlds/lol/__init__.py @@ -1,8 +1,8 @@ -from typing import List +from typing import List, TYPE_CHECKING from BaseClasses import Tutorial from worlds.AutoWorld import WebWorld, World -from .Items import LOLItem, LOLItemData, event_item_table, get_items_by_category, item_table +from .Items import LOLItem, item_table, get_items_by_category from .Locations import LOLLocation, location_table, get_locations_by_category from .Options import LOLOptions from .Regions import create_regions @@ -10,6 +10,9 @@ from .Data import champions from worlds.LauncherComponents import Component, components, Type, launch_subprocess +if TYPE_CHECKING: + from BaseClasses import MultiWorld + def launch_client(): @@ -36,6 +39,7 @@ class LOLWorld(World): League of Legends (LoL), commonly referred to as League, is a 2009 multiplayer online battle arena video game developed and published by Riot Games. """ game = "League of Legends" + data_version = 1 options_dataclass = LOLOptions options: LOLOptions topology_present = True @@ -48,22 +52,30 @@ class LOLWorld(World): def __init__(self, multiworld: "MultiWorld", player: int): super(LOLWorld, self).__init__(multiworld, player) self.possible_champions = [] + self.starting_champion_names = [] self.added_lp = 0 def create_items(self): item_pool: List[LOLItem] = [] self.choose_possible_champions() - print(self.possible_champions) - starting_champions = self.random.sample(self.possible_champions, min(self.options.starting_champions, len(self.possible_champions))) - for i in range(len(starting_champions)): - self.multiworld.get_location("Starting Champion " + str(i+1), self.player).place_locked_item(self.create_item(starting_champions[i])) + self.choose_starting_champions() + for index, champion_name in enumerate(self.starting_champion_names, start=1): + self.multiworld.get_location("Starting Champion " + str(index), self.player).place_locked_item(self.create_item(champion_name)) + total_locations = len(self.multiworld.get_unfilled_locations(self.player)) for name, data in item_table.items(): - if name in self.possible_champions and name not in starting_champions: + if name in self.possible_champions and name not in self.starting_champion_names: item_pool += [self.create_item(name) for _ in range(0, 1)] - while len(item_pool) < total_locations: + + remaining_slots = max(0, total_locations - len(item_pool)) + self.added_lp = min(int(self.options.total_lp_count), remaining_slots) + for _ in range(self.added_lp): item_pool.append(self.create_item("LP")) - self.added_lp += 1 + + filler_items = list(get_items_by_category("Filler", []).keys()) + while len(item_pool) < total_locations: + item_pool.append(self.create_item(self.random.choice(filler_items))) + self.multiworld.itempool += item_pool def create_item(self, name: str) -> LOLItem: @@ -79,19 +91,67 @@ def create_regions(self): create_regions(self.multiworld, self.player, self.options, self.possible_champions) def fill_slot_data(self) -> dict: + self.choose_possible_champions() + self.choose_starting_champions() slot_data = {"Required CS": int(self.options.required_creep_score) ,"Required VS": int(self.options.required_vision_score) ,"Required Kills": int(self.options.required_kills) ,"Required Assists": int(self.options.required_assists) - ,"Required LP": int(self.added_lp * (self.options.required_lp / 100))} + ,"Configured Total LP": int(self.options.total_lp_count) + ,"Total LP": int(self.added_lp) + ,"Required LP Percentage": int(self.options.required_lp) + ,"Required LP": int(self.added_lp * (self.options.required_lp / 100)) + ,"Starting Champion Count": int(self.options.starting_champions) + ,"Champion Subset Count": int(self.options.champion_subset_count) + ,"ARAM Mode": int(self.options.aram_mode) + ,"Selected Champions": sorted(self.possible_champions) + ,"Starting Champions": list(self.starting_champion_names) + ,"Active Locations": self.get_active_location_names()} return slot_data def choose_possible_champions(self): if len(self.possible_champions) == 0: - print("Here") for champion_id in champions: champion_name = champions[champion_id]["name"] if champion_name in self.options.champions.value: self.possible_champions.append(champion_name) if len(self.possible_champions) > self.options.champion_subset_count: - self.possible_champions = self.random.sample(self.possible_champions, self.options.champion_subset_count) \ No newline at end of file + self.possible_champions = self.random.sample(self.possible_champions, self.options.champion_subset_count) + + def choose_starting_champions(self): + if len(self.starting_champion_names) == 0: + self.choose_possible_champions() + self.starting_champion_names = self.random.sample( + self.possible_champions, + min(self.options.starting_champions, len(self.possible_champions)) + ) + + def get_active_location_names(self) -> List[str]: + self.choose_possible_champions() + active_locations: List[str] = [] + aram = bool(self.options.aram_mode) + + for champion_id in champions: + champion_name = champions[champion_id]["name"] + if champion_name not in self.possible_champions: + continue + if not aram: + active_locations.append(champion_name + " - Assist Taking Dragon") + active_locations.append(champion_name + " - Assist Taking Rift Herald") + active_locations.append(champion_name + " - Assist Taking Baron") + active_locations.append(champion_name + " - Assist Taking Tower") + active_locations.append(champion_name + " - Assist Taking Inhibitor") + active_locations.append(champion_name + " - Get X Assists") + if not aram and "Support" in champions[champion_id]["tags"]: + active_locations.append(champion_name + " - Get X Ward Score") + if "Support" not in champions[champion_id]["tags"]: + active_locations.append(champion_name + " - Get X Kills") + if not aram: + active_locations.append(champion_name + " - Get X Creep Score") + + for index in range(1, min(int(self.options.starting_champions), len(self.possible_champions)) + 1): + active_locations.append("Starting Champion " + str(index)) + + active_locations.append("Game Win - Enemy Nexus Destroyed") + + return active_locations \ No newline at end of file