WebHost: allow customserver to skip importing worlds subsystem for hosting a Room (#877)
Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									181cc47079
								
							
						
					
					
						commit
						c1e9d0ab4f
					
				
							
								
								
									
										165
									
								
								MultiServer.py
								
								
								
								
							
							
						
						
									
										165
									
								
								MultiServer.py
								
								
								
								
							| 
						 | 
					@ -30,13 +30,8 @@ except ImportError:
 | 
				
			||||||
    OperationalError = ConnectionError
 | 
					    OperationalError = ConnectionError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import NetUtils
 | 
					import NetUtils
 | 
				
			||||||
from worlds.AutoWorld import AutoWorldRegister
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
proxy_worlds = {name: world(None, 0) for name, world in AutoWorldRegister.world_types.items()}
 | 
					 | 
				
			||||||
from worlds import network_data_package, lookup_any_item_id_to_name, lookup_any_location_id_to_name
 | 
					 | 
				
			||||||
import Utils
 | 
					import Utils
 | 
				
			||||||
from Utils import get_item_name_from_id, get_location_name_from_id, \
 | 
					from Utils import version_tuple, restricted_loads, Version
 | 
				
			||||||
    version_tuple, restricted_loads, Version
 | 
					 | 
				
			||||||
from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, NetworkPlayer, Permission, NetworkSlot, \
 | 
					from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, NetworkPlayer, Permission, NetworkSlot, \
 | 
				
			||||||
    SlotType
 | 
					    SlotType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,6 +121,11 @@ class Context:
 | 
				
			||||||
    stored_data: typing.Dict[str, object]
 | 
					    stored_data: typing.Dict[str, object]
 | 
				
			||||||
    stored_data_notification_clients: typing.Dict[str, typing.Set[Client]]
 | 
					    stored_data_notification_clients: typing.Dict[str, typing.Set[Client]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    item_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')
 | 
				
			||||||
 | 
					    location_names: typing.Dict[int, str] = Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')
 | 
				
			||||||
 | 
					    all_item_and_group_names: typing.Dict[str, typing.Set[str]]
 | 
				
			||||||
 | 
					    forced_auto_forfeits: typing.Dict[str, bool]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int,
 | 
					    def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int,
 | 
				
			||||||
                 hint_cost: int, item_cheat: bool, forfeit_mode: str = "disabled", collect_mode="disabled",
 | 
					                 hint_cost: int, item_cheat: bool, forfeit_mode: str = "disabled", collect_mode="disabled",
 | 
				
			||||||
                 remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
 | 
					                 remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
 | 
				
			||||||
| 
						 | 
					@ -190,8 +190,43 @@ class Context:
 | 
				
			||||||
        self.stored_data = {}
 | 
					        self.stored_data = {}
 | 
				
			||||||
        self.stored_data_notification_clients = collections.defaultdict(weakref.WeakSet)
 | 
					        self.stored_data_notification_clients = collections.defaultdict(weakref.WeakSet)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # General networking
 | 
					        # init empty to satisfy linter, I suppose
 | 
				
			||||||
 | 
					        self.gamespackage = {}
 | 
				
			||||||
 | 
					        self.item_name_groups = {}
 | 
				
			||||||
 | 
					        self.all_item_and_group_names = {}
 | 
				
			||||||
 | 
					        self.forced_auto_forfeits = collections.defaultdict(lambda: False)
 | 
				
			||||||
 | 
					        self.non_hintable_names = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._load_game_data()
 | 
				
			||||||
 | 
					        self._init_game_data()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Datapackage retrieval
 | 
				
			||||||
 | 
					    def _load_game_data(self):
 | 
				
			||||||
 | 
					        import worlds
 | 
				
			||||||
 | 
					        self.gamespackage = worlds.network_data_package["games"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.item_name_groups = {world_name: world.item_name_groups for world_name, world in
 | 
				
			||||||
 | 
					                                 worlds.AutoWorldRegister.world_types.items()}
 | 
				
			||||||
 | 
					        for world_name, world in worlds.AutoWorldRegister.world_types.items():
 | 
				
			||||||
 | 
					            self.forced_auto_forfeits[world_name] = world.forced_auto_forfeit
 | 
				
			||||||
 | 
					            self.non_hintable_names[world_name] = world.hint_blacklist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _init_game_data(self):
 | 
				
			||||||
 | 
					        for game_name, game_package in self.gamespackage.items():
 | 
				
			||||||
 | 
					            for item_name, item_id in game_package["item_name_to_id"].items():
 | 
				
			||||||
 | 
					                self.item_names[item_id] = item_name
 | 
				
			||||||
 | 
					            for location_name, location_id in game_package["location_name_to_id"].items():
 | 
				
			||||||
 | 
					                self.location_names[location_id] = location_name
 | 
				
			||||||
 | 
					            self.all_item_and_group_names[game_name] = \
 | 
				
			||||||
 | 
					                set(game_package["item_name_to_id"]) | set(self.item_name_groups[game_name])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def item_names_for_game(self, game: str) -> typing.Dict[str, int]:
 | 
				
			||||||
 | 
					        return self.gamespackage[game]["item_name_to_id"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def location_names_for_game(self, game: str) -> typing.Dict[str, int]:
 | 
				
			||||||
 | 
					        return self.gamespackage[game]["location_name_to_id"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # General networking
 | 
				
			||||||
    async def send_msgs(self, endpoint: Endpoint, msgs: typing.Iterable[dict]) -> bool:
 | 
					    async def send_msgs(self, endpoint: Endpoint, msgs: typing.Iterable[dict]) -> bool:
 | 
				
			||||||
        if not endpoint.socket or not endpoint.socket.open:
 | 
					        if not endpoint.socket or not endpoint.socket.open:
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
| 
						 | 
					@ -546,7 +581,7 @@ class Context:
 | 
				
			||||||
        self.notify_all(finished_msg)
 | 
					        self.notify_all(finished_msg)
 | 
				
			||||||
        if "auto" in self.forfeit_mode:
 | 
					        if "auto" in self.forfeit_mode:
 | 
				
			||||||
            forfeit_player(self, client.team, client.slot)
 | 
					            forfeit_player(self, client.team, client.slot)
 | 
				
			||||||
        elif proxy_worlds[self.games[client.slot]].forced_auto_forfeit:
 | 
					        elif self.forced_auto_forfeits[self.games[client.slot]]:
 | 
				
			||||||
            forfeit_player(self, client.team, client.slot)
 | 
					            forfeit_player(self, client.team, client.slot)
 | 
				
			||||||
        if "auto" in self.collect_mode:
 | 
					        if "auto" in self.collect_mode:
 | 
				
			||||||
            collect_player(self, client.team, client.slot)
 | 
					            collect_player(self, client.team, client.slot)
 | 
				
			||||||
| 
						 | 
					@ -642,9 +677,10 @@ async def on_client_connected(ctx: Context, client: Client):
 | 
				
			||||||
        'permissions': get_permissions(ctx),
 | 
					        'permissions': get_permissions(ctx),
 | 
				
			||||||
        'hint_cost': ctx.hint_cost,
 | 
					        'hint_cost': ctx.hint_cost,
 | 
				
			||||||
        'location_check_points': ctx.location_check_points,
 | 
					        'location_check_points': ctx.location_check_points,
 | 
				
			||||||
        'datapackage_version': network_data_package["version"],
 | 
					        'datapackage_version': sum(game_data["version"] for game_data in ctx.gamespackage.values())
 | 
				
			||||||
 | 
					        if all(game_data["version"] for game_data in ctx.gamespackage.values()) else 0,
 | 
				
			||||||
        'datapackage_versions': {game: game_data["version"] for game, game_data
 | 
					        'datapackage_versions': {game: game_data["version"] for game, game_data
 | 
				
			||||||
                                 in network_data_package["games"].items()},
 | 
					                                 in ctx.gamespackage.items()},
 | 
				
			||||||
        'seed_name': ctx.seed_name,
 | 
					        'seed_name': ctx.seed_name,
 | 
				
			||||||
        'time': time.time(),
 | 
					        'time': time.time(),
 | 
				
			||||||
    }])
 | 
					    }])
 | 
				
			||||||
| 
						 | 
					@ -822,8 +858,8 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
 | 
				
			||||||
            send_items_to(ctx, team, target_player, new_item)
 | 
					            send_items_to(ctx, team, target_player, new_item)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            logging.info('(Team #%d) %s sent %s to %s (%s)' % (
 | 
					            logging.info('(Team #%d) %s sent %s to %s (%s)' % (
 | 
				
			||||||
                team + 1, ctx.player_names[(team, slot)], get_item_name_from_id(item_id),
 | 
					                team + 1, ctx.player_names[(team, slot)], ctx.item_names[item_id],
 | 
				
			||||||
                ctx.player_names[(team, target_player)], get_location_name_from_id(location)))
 | 
					                ctx.player_names[(team, target_player)], ctx.location_names[location]))
 | 
				
			||||||
            info_text = json_format_send_event(new_item, target_player)
 | 
					            info_text = json_format_send_event(new_item, target_player)
 | 
				
			||||||
            ctx.broadcast_team(team, [info_text])
 | 
					            ctx.broadcast_team(team, [info_text])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -838,13 +874,14 @@ def register_location_checks(ctx: Context, team: int, slot: int, locations: typi
 | 
				
			||||||
        ctx.save()
 | 
					        ctx.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[NetUtils.Hint]:
 | 
					def collect_hints(ctx: Context, team: int, slot: int, item_name: str) -> typing.List[NetUtils.Hint]:
 | 
				
			||||||
    hints = []
 | 
					    hints = []
 | 
				
			||||||
    slots: typing.Set[int] = {slot}
 | 
					    slots: typing.Set[int] = {slot}
 | 
				
			||||||
    for group_id, group in ctx.groups.items():
 | 
					    for group_id, group in ctx.groups.items():
 | 
				
			||||||
        if slot in group:
 | 
					        if slot in group:
 | 
				
			||||||
            slots.add(group_id)
 | 
					            slots.add(group_id)
 | 
				
			||||||
    seeked_item_id = proxy_worlds[ctx.games[slot]].item_name_to_id[item]
 | 
					
 | 
				
			||||||
 | 
					    seeked_item_id = ctx.item_names_for_game(ctx.games[slot])[item_name]
 | 
				
			||||||
    for finding_player, check_data in ctx.locations.items():
 | 
					    for finding_player, check_data in ctx.locations.items():
 | 
				
			||||||
        for location_id, (item_id, receiving_player, item_flags) in check_data.items():
 | 
					        for location_id, (item_id, receiving_player, item_flags) in check_data.items():
 | 
				
			||||||
            if receiving_player in slots and item_id == seeked_item_id:
 | 
					            if receiving_player in slots and item_id == seeked_item_id:
 | 
				
			||||||
| 
						 | 
					@ -857,7 +894,7 @@ def collect_hints(ctx: Context, team: int, slot: int, item: str) -> typing.List[
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def collect_hint_location_name(ctx: Context, team: int, slot: int, location: str) -> typing.List[NetUtils.Hint]:
 | 
					def collect_hint_location_name(ctx: Context, team: int, slot: int, location: str) -> typing.List[NetUtils.Hint]:
 | 
				
			||||||
    seeked_location: int = proxy_worlds[ctx.games[slot]].location_name_to_id[location]
 | 
					    seeked_location: int = ctx.location_names_for_game(ctx.games[slot])[location]
 | 
				
			||||||
    return collect_hint_location_id(ctx, team, slot, seeked_location)
 | 
					    return collect_hint_location_id(ctx, team, slot, seeked_location)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -874,8 +911,8 @@ def collect_hint_location_id(ctx: Context, team: int, slot: int, seeked_location
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
 | 
					def format_hint(ctx: Context, team: int, hint: NetUtils.Hint) -> str:
 | 
				
			||||||
    text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
 | 
					    text = f"[Hint]: {ctx.player_names[team, hint.receiving_player]}'s " \
 | 
				
			||||||
           f"{lookup_any_item_id_to_name[hint.item]} is " \
 | 
					           f"{ctx.item_names[hint.item]} is " \
 | 
				
			||||||
           f"at {get_location_name_from_id(hint.location)} " \
 | 
					           f"at {ctx.location_names[hint.location]} " \
 | 
				
			||||||
           f"in {ctx.player_names[team, hint.finding_player]}'s World"
 | 
					           f"in {ctx.player_names[team, hint.finding_player]}'s World"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if hint.entrance:
 | 
					    if hint.entrance:
 | 
				
			||||||
| 
						 | 
					@ -1133,8 +1170,8 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
            forfeit_player(self.ctx, self.client.team, self.client.slot)
 | 
					            forfeit_player(self.ctx, self.client.team, self.client.slot)
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
        elif "disabled" in self.ctx.forfeit_mode:
 | 
					        elif "disabled" in self.ctx.forfeit_mode:
 | 
				
			||||||
            self.output(
 | 
					            self.output("Sorry, client item releasing has been disabled on this server. "
 | 
				
			||||||
                "Sorry, client item releasing has been disabled on this server. You can ask the server admin for a /release")
 | 
					                        "You can ask the server admin for a /release")
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
        else:  # is auto or goal
 | 
					        else:  # is auto or goal
 | 
				
			||||||
            if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
 | 
					            if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
 | 
				
			||||||
| 
						 | 
					@ -1170,7 +1207,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
        if self.ctx.remaining_mode == "enabled":
 | 
					        if self.ctx.remaining_mode == "enabled":
 | 
				
			||||||
            remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
 | 
					            remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
 | 
				
			||||||
            if remaining_item_ids:
 | 
					            if remaining_item_ids:
 | 
				
			||||||
                self.output("Remaining items: " + ", ".join(lookup_any_item_id_to_name.get(item_id, "unknown item")
 | 
					                self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
 | 
				
			||||||
                                                            for item_id in remaining_item_ids))
 | 
					                                                            for item_id in remaining_item_ids))
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.output("No remaining items found.")
 | 
					                self.output("No remaining items found.")
 | 
				
			||||||
| 
						 | 
					@ -1183,7 +1220,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
            if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
 | 
					            if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL:
 | 
				
			||||||
                remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
 | 
					                remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot)
 | 
				
			||||||
                if remaining_item_ids:
 | 
					                if remaining_item_ids:
 | 
				
			||||||
                    self.output("Remaining items: " + ", ".join(lookup_any_item_id_to_name.get(item_id, "unknown item")
 | 
					                    self.output("Remaining items: " + ", ".join(self.ctx.item_names[item_id]
 | 
				
			||||||
                                                                for item_id in remaining_item_ids))
 | 
					                                                                for item_id in remaining_item_ids))
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    self.output("No remaining items found.")
 | 
					                    self.output("No remaining items found.")
 | 
				
			||||||
| 
						 | 
					@ -1199,7 +1236,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
        locations = get_missing_checks(self.ctx, self.client.team, self.client.slot)
 | 
					        locations = get_missing_checks(self.ctx, self.client.team, self.client.slot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if locations:
 | 
					        if locations:
 | 
				
			||||||
            texts = [f'Missing: {get_location_name_from_id(location)}' for location in locations]
 | 
					            texts = [f'Missing: {self.ctx.location_names[location]}' for location in locations]
 | 
				
			||||||
            texts.append(f"Found {len(locations)} missing location checks")
 | 
					            texts.append(f"Found {len(locations)} missing location checks")
 | 
				
			||||||
            self.ctx.notify_client_multiple(self.client, texts)
 | 
					            self.ctx.notify_client_multiple(self.client, texts)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -1212,7 +1249,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
        locations = get_checked_checks(self.ctx, self.client.team, self.client.slot)
 | 
					        locations = get_checked_checks(self.ctx, self.client.team, self.client.slot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if locations:
 | 
					        if locations:
 | 
				
			||||||
            texts = [f'Checked: {get_location_name_from_id(location)}' for location in locations]
 | 
					            texts = [f'Checked: {self.ctx.location_names[location]}' for location in locations]
 | 
				
			||||||
            texts.append(f"Found {len(locations)} done location checks")
 | 
					            texts.append(f"Found {len(locations)} done location checks")
 | 
				
			||||||
            self.ctx.notify_client_multiple(self.client, texts)
 | 
					            self.ctx.notify_client_multiple(self.client, texts)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
| 
						 | 
					@ -1241,11 +1278,13 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
    def _cmd_getitem(self, item_name: str) -> bool:
 | 
					    def _cmd_getitem(self, item_name: str) -> bool:
 | 
				
			||||||
        """Cheat in an item, if it is enabled on this server"""
 | 
					        """Cheat in an item, if it is enabled on this server"""
 | 
				
			||||||
        if self.ctx.item_cheat:
 | 
					        if self.ctx.item_cheat:
 | 
				
			||||||
            world = proxy_worlds[self.ctx.games[self.client.slot]]
 | 
					            names = self.ctx.item_names_for_game(self.ctx.games[self.client.slot])
 | 
				
			||||||
            item_name, usable, response = get_intended_text(item_name,
 | 
					            item_name, usable, response = get_intended_text(
 | 
				
			||||||
                                                            world.item_names)
 | 
					                item_name,
 | 
				
			||||||
 | 
					                names
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            if usable:
 | 
					            if usable:
 | 
				
			||||||
                new_item = NetworkItem(world.create_item(item_name).code, -1, self.client.slot)
 | 
					                new_item = NetworkItem(names[item_name], -1, self.client.slot)
 | 
				
			||||||
                get_received_items(self.ctx, self.client.team, self.client.slot, False).append(new_item)
 | 
					                get_received_items(self.ctx, self.client.team, self.client.slot, False).append(new_item)
 | 
				
			||||||
                get_received_items(self.ctx, self.client.team, self.client.slot, True).append(new_item)
 | 
					                get_received_items(self.ctx, self.client.team, self.client.slot, True).append(new_item)
 | 
				
			||||||
                self.ctx.notify_all(
 | 
					                self.ctx.notify_all(
 | 
				
			||||||
| 
						 | 
					@ -1271,20 +1310,22 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
                        f"You have {points_available} points.")
 | 
					                        f"You have {points_available} points.")
 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            world = proxy_worlds[self.ctx.games[self.client.slot]]
 | 
					            game = self.ctx.games[self.client.slot]
 | 
				
			||||||
            names = world.location_names if for_location else world.all_item_and_group_names
 | 
					            names = self.ctx.location_names_for_game(game) \
 | 
				
			||||||
 | 
					                if for_location else \
 | 
				
			||||||
 | 
					                self.ctx.all_item_and_group_names[game]
 | 
				
			||||||
            hint_name, usable, response = get_intended_text(input_text,
 | 
					            hint_name, usable, response = get_intended_text(input_text,
 | 
				
			||||||
                                                            names)
 | 
					                                                            names)
 | 
				
			||||||
            if usable:
 | 
					            if usable:
 | 
				
			||||||
                if hint_name in world.hint_blacklist:
 | 
					                if hint_name in self.ctx.non_hintable_names[game]:
 | 
				
			||||||
                    self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
 | 
					                    self.output(f"Sorry, \"{hint_name}\" is marked as non-hintable.")
 | 
				
			||||||
                    hints = []
 | 
					                    hints = []
 | 
				
			||||||
                elif not for_location and hint_name in world.item_name_groups:  # item group name
 | 
					                elif not for_location and hint_name in self.ctx.item_name_groups[game]:  # item group name
 | 
				
			||||||
                    hints = []
 | 
					                    hints = []
 | 
				
			||||||
                    for item in world.item_name_groups[hint_name]:
 | 
					                    for item_name in self.ctx.item_name_groups[game][hint_name]:
 | 
				
			||||||
                        if item in world.item_name_to_id:  # ensure item has an ID
 | 
					                        if item_name in self.ctx.item_names_for_game(game):  # ensure item has an ID
 | 
				
			||||||
                            hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item))
 | 
					                            hints.extend(collect_hints(self.ctx, self.client.team, self.client.slot, item_name))
 | 
				
			||||||
                elif not for_location and hint_name in world.item_names:  # item name
 | 
					                elif not for_location and hint_name in self.ctx.item_names_for_game(game):  # item name
 | 
				
			||||||
                    hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_name)
 | 
					                    hints = collect_hints(self.ctx, self.client.team, self.client.slot, hint_name)
 | 
				
			||||||
                else:  # location name
 | 
					                else:  # location name
 | 
				
			||||||
                    hints = collect_hint_location_name(self.ctx, self.client.team, self.client.slot, hint_name)
 | 
					                    hints = collect_hint_location_name(self.ctx, self.client.team, self.client.slot, hint_name)
 | 
				
			||||||
| 
						 | 
					@ -1346,12 +1387,12 @@ class ClientMessageProcessor(CommonCommandProcessor):
 | 
				
			||||||
                return False
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @mark_raw
 | 
					    @mark_raw
 | 
				
			||||||
    def _cmd_hint(self, item: str = "") -> bool:
 | 
					    def _cmd_hint(self, item_name: str = "") -> bool:
 | 
				
			||||||
        """Use !hint {item_name},
 | 
					        """Use !hint {item_name},
 | 
				
			||||||
        for example !hint Lamp to get a spoiler peek for that item.
 | 
					        for example !hint Lamp to get a spoiler peek for that item.
 | 
				
			||||||
        If hint costs are on, this will only give you one new result,
 | 
					        If hint costs are on, this will only give you one new result,
 | 
				
			||||||
        you can rerun the command to get more in that case."""
 | 
					        you can rerun the command to get more in that case."""
 | 
				
			||||||
        return self.get_hints(item)
 | 
					        return self.get_hints(item_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @mark_raw
 | 
					    @mark_raw
 | 
				
			||||||
    def _cmd_hint_location(self, location: str = "") -> bool:
 | 
					    def _cmd_hint_location(self, location: str = "") -> bool:
 | 
				
			||||||
| 
						 | 
					@ -1477,23 +1518,23 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
 | 
				
			||||||
    elif cmd == "GetDataPackage":
 | 
					    elif cmd == "GetDataPackage":
 | 
				
			||||||
        exclusions = args.get("exclusions", [])
 | 
					        exclusions = args.get("exclusions", [])
 | 
				
			||||||
        if "games" in args:
 | 
					        if "games" in args:
 | 
				
			||||||
            games = {name: game_data for name, game_data in network_data_package["games"].items()
 | 
					            games = {name: game_data for name, game_data in ctx.gamespackage.items()
 | 
				
			||||||
                     if name in set(args.get("games", []))}
 | 
					                     if name in set(args.get("games", []))}
 | 
				
			||||||
            await ctx.send_msgs(client, [{"cmd": "DataPackage",
 | 
					            await ctx.send_msgs(client, [{"cmd": "DataPackage",
 | 
				
			||||||
                                          "data": {"games": games}}])
 | 
					                                          "data": {"games": games}}])
 | 
				
			||||||
        # TODO: remove exclusions behaviour around 0.5.0
 | 
					        # TODO: remove exclusions behaviour around 0.5.0
 | 
				
			||||||
        elif exclusions:
 | 
					        elif exclusions:
 | 
				
			||||||
            exclusions = set(exclusions)
 | 
					            exclusions = set(exclusions)
 | 
				
			||||||
            games = {name: game_data for name, game_data in network_data_package["games"].items()
 | 
					            games = {name: game_data for name, game_data in ctx.gamespackage.items()
 | 
				
			||||||
                     if name not in exclusions}
 | 
					                     if name not in exclusions}
 | 
				
			||||||
            package = network_data_package.copy()
 | 
					
 | 
				
			||||||
            package["games"] = games
 | 
					            package = {"games": games}
 | 
				
			||||||
            await ctx.send_msgs(client, [{"cmd": "DataPackage",
 | 
					            await ctx.send_msgs(client, [{"cmd": "DataPackage",
 | 
				
			||||||
                                          "data": package}])
 | 
					                                          "data": package}])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            await ctx.send_msgs(client, [{"cmd": "DataPackage",
 | 
					            await ctx.send_msgs(client, [{"cmd": "DataPackage",
 | 
				
			||||||
                                          "data": network_data_package}])
 | 
					                                          "data": {"games": ctx.gamespackage}}])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elif client.auth:
 | 
					    elif client.auth:
 | 
				
			||||||
        if cmd == "ConnectUpdate":
 | 
					        if cmd == "ConnectUpdate":
 | 
				
			||||||
| 
						 | 
					@ -1549,7 +1590,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
 | 
				
			||||||
            create_as_hint: int = int(args.get("create_as_hint", 0))
 | 
					            create_as_hint: int = int(args.get("create_as_hint", 0))
 | 
				
			||||||
            hints = []
 | 
					            hints = []
 | 
				
			||||||
            for location in args["locations"]:
 | 
					            for location in args["locations"]:
 | 
				
			||||||
                if type(location) is not int or location not in lookup_any_location_id_to_name:
 | 
					                if type(location) is not int:
 | 
				
			||||||
                    await ctx.send_msgs(client,
 | 
					                    await ctx.send_msgs(client,
 | 
				
			||||||
                                        [{'cmd': 'InvalidPacket', "type": "arguments", "text": 'LocationScouts',
 | 
					                                        [{'cmd': 'InvalidPacket', "type": "arguments", "text": 'LocationScouts',
 | 
				
			||||||
                                          "original_cmd": cmd}])
 | 
					                                          "original_cmd": cmd}])
 | 
				
			||||||
| 
						 | 
					@ -1763,18 +1804,18 @@ class ServerCommandProcessor(CommonCommandProcessor):
 | 
				
			||||||
        seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
 | 
					        seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
 | 
				
			||||||
        if usable:
 | 
					        if usable:
 | 
				
			||||||
            team, slot = self.ctx.player_name_lookup[seeked_player]
 | 
					            team, slot = self.ctx.player_name_lookup[seeked_player]
 | 
				
			||||||
            item = " ".join(item_name)
 | 
					            item_name = " ".join(item_name)
 | 
				
			||||||
            world = proxy_worlds[self.ctx.games[slot]]
 | 
					            names = self.ctx.item_names_for_game(self.ctx.games[slot])
 | 
				
			||||||
            item, usable, response = get_intended_text(item, world.item_names)
 | 
					            item_name, usable, response = get_intended_text(item_name, names)
 | 
				
			||||||
            if usable:
 | 
					            if usable:
 | 
				
			||||||
                amount: int = int(amount)
 | 
					                amount: int = int(amount)
 | 
				
			||||||
                new_items = [NetworkItem(world.item_name_to_id[item], -1, 0) for i in range(int(amount))]
 | 
					                new_items = [NetworkItem(names[item_name], -1, 0) for _ in range(int(amount))]
 | 
				
			||||||
                send_items_to(self.ctx, team, slot, *new_items)
 | 
					                send_items_to(self.ctx, team, slot, *new_items)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                send_new_items(self.ctx)
 | 
					                send_new_items(self.ctx)
 | 
				
			||||||
                self.ctx.notify_all(
 | 
					                self.ctx.notify_all(
 | 
				
			||||||
                    'Cheat console: sending ' + ('' if amount == 1 else f'{amount} of ') +
 | 
					                    'Cheat console: sending ' + ('' if amount == 1 else f'{amount} of ') +
 | 
				
			||||||
                    f'"{item}" to {self.ctx.get_aliased_name(team, slot)}')
 | 
					                    f'"{item_name}" to {self.ctx.get_aliased_name(team, slot)}')
 | 
				
			||||||
                return True
 | 
					                return True
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.output(response)
 | 
					                self.output(response)
 | 
				
			||||||
| 
						 | 
					@ -1787,22 +1828,22 @@ class ServerCommandProcessor(CommonCommandProcessor):
 | 
				
			||||||
        """Sends an item to the specified player"""
 | 
					        """Sends an item to the specified player"""
 | 
				
			||||||
        return self._cmd_send_multiple(1, player_name, *item_name)
 | 
					        return self._cmd_send_multiple(1, player_name, *item_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _cmd_hint(self, player_name: str, *item: str) -> bool:
 | 
					    def _cmd_hint(self, player_name: str, *item_name: str) -> bool:
 | 
				
			||||||
        """Send out a hint for a player's item to their team"""
 | 
					        """Send out a hint for a player's item to their team"""
 | 
				
			||||||
        seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
 | 
					        seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
 | 
				
			||||||
        if usable:
 | 
					        if usable:
 | 
				
			||||||
            team, slot = self.ctx.player_name_lookup[seeked_player]
 | 
					            team, slot = self.ctx.player_name_lookup[seeked_player]
 | 
				
			||||||
            item = " ".join(item)
 | 
					            item_name = " ".join(item_name)
 | 
				
			||||||
            world = proxy_worlds[self.ctx.games[slot]]
 | 
					            game = self.ctx.games[slot]
 | 
				
			||||||
            item, usable, response = get_intended_text(item, world.all_item_and_group_names)
 | 
					            item_name, usable, response = get_intended_text(item_name, self.ctx.all_item_and_group_names[game])
 | 
				
			||||||
            if usable:
 | 
					            if usable:
 | 
				
			||||||
                if item in world.item_name_groups:
 | 
					                if item_name in self.ctx.item_name_groups[game]:
 | 
				
			||||||
                    hints = []
 | 
					                    hints = []
 | 
				
			||||||
                    for item in world.item_name_groups[item]:
 | 
					                    for item_name_from_group in self.ctx.item_name_groups[game][item_name]:
 | 
				
			||||||
                        if item in world.item_name_to_id:  # ensure item has an ID
 | 
					                        if item_name_from_group in self.ctx.item_names_for_game(game):  # ensure item has an ID
 | 
				
			||||||
                            hints.extend(collect_hints(self.ctx, team, slot, item))
 | 
					                            hints.extend(collect_hints(self.ctx, team, slot, item_name_from_group))
 | 
				
			||||||
                else:  # item name
 | 
					                else:  # item name
 | 
				
			||||||
                    hints = collect_hints(self.ctx, team, slot, item)
 | 
					                    hints = collect_hints(self.ctx, team, slot, item_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if hints:
 | 
					                if hints:
 | 
				
			||||||
                    notify_hints(self.ctx, team, hints)
 | 
					                    notify_hints(self.ctx, team, hints)
 | 
				
			||||||
| 
						 | 
					@ -1818,16 +1859,16 @@ class ServerCommandProcessor(CommonCommandProcessor):
 | 
				
			||||||
            self.output(response)
 | 
					            self.output(response)
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _cmd_hint_location(self, player_name: str, *location: str) -> bool:
 | 
					    def _cmd_hint_location(self, player_name: str, *location_name: str) -> bool:
 | 
				
			||||||
        """Send out a hint for a player's location to their team"""
 | 
					        """Send out a hint for a player's location to their team"""
 | 
				
			||||||
        seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
 | 
					        seeked_player, usable, response = get_intended_text(player_name, self.ctx.player_names.values())
 | 
				
			||||||
        if usable:
 | 
					        if usable:
 | 
				
			||||||
            team, slot = self.ctx.player_name_lookup[seeked_player]
 | 
					            team, slot = self.ctx.player_name_lookup[seeked_player]
 | 
				
			||||||
            item = " ".join(location)
 | 
					            location_name = " ".join(location_name)
 | 
				
			||||||
            world = proxy_worlds[self.ctx.games[slot]]
 | 
					            location_name, usable, response = get_intended_text(location_name,
 | 
				
			||||||
            item, usable, response = get_intended_text(item, world.location_names)
 | 
					                                                                self.ctx.location_names_for_game(self.ctx.games[slot]))
 | 
				
			||||||
            if usable:
 | 
					            if usable:
 | 
				
			||||||
                hints = collect_hint_location_name(self.ctx, team, slot, item)
 | 
					                hints = collect_hint_location_name(self.ctx, team, slot, location_name)
 | 
				
			||||||
                if hints:
 | 
					                if hints:
 | 
				
			||||||
                    notify_hints(self.ctx, team, hints)
 | 
					                    notify_hints(self.ctx, team, hints)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								Utils.py
								
								
								
								
							
							
						
						
									
										10
									
								
								Utils.py
								
								
								
								
							| 
						 | 
					@ -328,16 +328,6 @@ def get_options() -> dict:
 | 
				
			||||||
    return get_options.options
 | 
					    return get_options.options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_item_name_from_id(code: int) -> str:
 | 
					 | 
				
			||||||
    from worlds import lookup_any_item_id_to_name
 | 
					 | 
				
			||||||
    return lookup_any_item_id_to_name.get(code, f'Unknown item (ID:{code})')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_location_name_from_id(code: int) -> str:
 | 
					 | 
				
			||||||
    from worlds import lookup_any_location_id_to_name
 | 
					 | 
				
			||||||
    return lookup_any_location_id_to_name.get(code, f'Unknown location (ID:{code})')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def persistent_store(category: str, key: typing.Any, value: typing.Any):
 | 
					def persistent_store(category: str, key: typing.Any, value: typing.Any):
 | 
				
			||||||
    path = user_path("_persistent_storage.yaml")
 | 
					    path = user_path("_persistent_storage.yaml")
 | 
				
			||||||
    storage: dict = persistent_load()
 | 
					    storage: dict = persistent_load()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ import Utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Utils.local_path.cached_path = os.path.dirname(__file__)
 | 
					Utils.local_path.cached_path = os.path.dirname(__file__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from WebHostLib import app as raw_app
 | 
					from WebHostLib import register, app as raw_app
 | 
				
			||||||
from waitress import serve
 | 
					from waitress import serve
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from WebHostLib.models import db
 | 
					from WebHostLib.models import db
 | 
				
			||||||
| 
						 | 
					@ -22,14 +22,13 @@ from WebHostLib.autolauncher import autohost, autogen
 | 
				
			||||||
from WebHostLib.lttpsprites import update_sprites_lttp
 | 
					from WebHostLib.lttpsprites import update_sprites_lttp
 | 
				
			||||||
from WebHostLib.options import create as create_options_files
 | 
					from WebHostLib.options import create as create_options_files
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from worlds.AutoWorld import AutoWorldRegister
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
configpath = os.path.abspath("config.yaml")
 | 
					configpath = os.path.abspath("config.yaml")
 | 
				
			||||||
if not os.path.exists(configpath):  # fall back to config.yaml in home
 | 
					if not os.path.exists(configpath):  # fall back to config.yaml in home
 | 
				
			||||||
    configpath = os.path.abspath(Utils.user_path('config.yaml'))
 | 
					    configpath = os.path.abspath(Utils.user_path('config.yaml'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_app():
 | 
					def get_app():
 | 
				
			||||||
 | 
					    register()
 | 
				
			||||||
    app = raw_app
 | 
					    app = raw_app
 | 
				
			||||||
    if os.path.exists(configpath):
 | 
					    if os.path.exists(configpath):
 | 
				
			||||||
        import yaml
 | 
					        import yaml
 | 
				
			||||||
| 
						 | 
					@ -43,6 +42,7 @@ def get_app():
 | 
				
			||||||
def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]:
 | 
					def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]:
 | 
				
			||||||
    import json
 | 
					    import json
 | 
				
			||||||
    import shutil
 | 
					    import shutil
 | 
				
			||||||
 | 
					    from worlds.AutoWorld import AutoWorldRegister
 | 
				
			||||||
    worlds = {}
 | 
					    worlds = {}
 | 
				
			||||||
    data = []
 | 
					    data = []
 | 
				
			||||||
    for game, world in AutoWorldRegister.world_types.items():
 | 
					    for game, world in AutoWorldRegister.world_types.items():
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,12 +3,11 @@ import uuid
 | 
				
			||||||
import base64
 | 
					import base64
 | 
				
			||||||
import socket
 | 
					import socket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import jinja2.exceptions
 | 
					 | 
				
			||||||
from pony.flask import Pony
 | 
					from pony.flask import Pony
 | 
				
			||||||
from flask import Flask, request, redirect, url_for, render_template, Response, session, abort, send_from_directory
 | 
					from flask import Flask
 | 
				
			||||||
from flask_caching import Cache
 | 
					from flask_caching import Cache
 | 
				
			||||||
from flask_compress import Compress
 | 
					from flask_compress import Compress
 | 
				
			||||||
from worlds.AutoWorld import AutoWorldRegister
 | 
					from werkzeug.routing import BaseConverter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import *
 | 
					from .models import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,8 +52,6 @@ app.config["PATCH_TARGET"] = "archipelago.gg"
 | 
				
			||||||
cache = Cache(app)
 | 
					cache = Cache(app)
 | 
				
			||||||
Compress(app)
 | 
					Compress(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from werkzeug.routing import BaseConverter
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class B64UUIDConverter(BaseConverter):
 | 
					class B64UUIDConverter(BaseConverter):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,173 +66,16 @@ class B64UUIDConverter(BaseConverter):
 | 
				
			||||||
app.url_map.converters["suuid"] = B64UUIDConverter
 | 
					app.url_map.converters["suuid"] = B64UUIDConverter
 | 
				
			||||||
app.jinja_env.filters['suuid'] = lambda value: base64.urlsafe_b64encode(value.bytes).rstrip(b'=').decode('ascii')
 | 
					app.jinja_env.filters['suuid'] = lambda value: base64.urlsafe_b64encode(value.bytes).rstrip(b'=').decode('ascii')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# has automatic patch integration
 | 
					 | 
				
			||||||
import Patch
 | 
					 | 
				
			||||||
app.jinja_env.filters['supports_apdeltapatch'] = lambda game_name: game_name in Patch.AutoPatchRegister.patch_types
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def register():
 | 
				
			||||||
 | 
					    """Import submodules, triggering their registering on flask routing.
 | 
				
			||||||
 | 
					    Note: initializes worlds subsystem."""
 | 
				
			||||||
 | 
					    # has automatic patch integration
 | 
				
			||||||
 | 
					    import Patch
 | 
				
			||||||
 | 
					    app.jinja_env.filters['supports_apdeltapatch'] = lambda game_name: game_name in Patch.AutoPatchRegister.patch_types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_world_theme(game_name: str):
 | 
					    from WebHostLib.customserver import run_server_process
 | 
				
			||||||
    if game_name in AutoWorldRegister.world_types:
 | 
					    # to trigger app routing picking up on it
 | 
				
			||||||
        return AutoWorldRegister.world_types[game_name].web.theme
 | 
					    from . import tracker, upload, landing, check, generate, downloads, api, stats, misc
 | 
				
			||||||
    return 'grass'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app.register_blueprint(api.api_endpoints)
 | 
				
			||||||
@app.before_request
 | 
					 | 
				
			||||||
def register_session():
 | 
					 | 
				
			||||||
    session.permanent = True  # technically 31 days after the last visit
 | 
					 | 
				
			||||||
    if not session.get("_id", None):
 | 
					 | 
				
			||||||
        session["_id"] = uuid4()  # uniquely identify each session without needing a login
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.errorhandler(404)
 | 
					 | 
				
			||||||
@app.errorhandler(jinja2.exceptions.TemplateNotFound)
 | 
					 | 
				
			||||||
def page_not_found(err):
 | 
					 | 
				
			||||||
    return render_template('404.html'), 404
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Start Playing Page
 | 
					 | 
				
			||||||
@app.route('/start-playing')
 | 
					 | 
				
			||||||
def start_playing():
 | 
					 | 
				
			||||||
    return render_template(f"startPlaying.html")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/weighted-settings')
 | 
					 | 
				
			||||||
def weighted_settings():
 | 
					 | 
				
			||||||
    return render_template(f"weighted-settings.html")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Player settings pages
 | 
					 | 
				
			||||||
@app.route('/games/<string:game>/player-settings')
 | 
					 | 
				
			||||||
def player_settings(game):
 | 
					 | 
				
			||||||
    return render_template(f"player-settings.html", game=game, theme=get_world_theme(game))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Game Info Pages
 | 
					 | 
				
			||||||
@app.route('/games/<string:game>/info/<string:lang>')
 | 
					 | 
				
			||||||
def game_info(game, lang):
 | 
					 | 
				
			||||||
    return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# List of supported games
 | 
					 | 
				
			||||||
@app.route('/games')
 | 
					 | 
				
			||||||
def games():
 | 
					 | 
				
			||||||
    worlds = {}
 | 
					 | 
				
			||||||
    for game, world in AutoWorldRegister.world_types.items():
 | 
					 | 
				
			||||||
        if not world.hidden:
 | 
					 | 
				
			||||||
            worlds[game] = world
 | 
					 | 
				
			||||||
    return render_template("supportedGames.html", worlds=worlds)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
 | 
					 | 
				
			||||||
def tutorial(game, file, lang):
 | 
					 | 
				
			||||||
    return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/tutorial/')
 | 
					 | 
				
			||||||
def tutorial_landing():
 | 
					 | 
				
			||||||
    worlds = {}
 | 
					 | 
				
			||||||
    for game, world in AutoWorldRegister.world_types.items():
 | 
					 | 
				
			||||||
        if not world.hidden:
 | 
					 | 
				
			||||||
            worlds[game] = world
 | 
					 | 
				
			||||||
    return render_template("tutorialLanding.html")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/faq/<string:lang>/')
 | 
					 | 
				
			||||||
def faq(lang):
 | 
					 | 
				
			||||||
    return render_template("faq.html", lang=lang)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/glossary/<string:lang>/')
 | 
					 | 
				
			||||||
def terms(lang):
 | 
					 | 
				
			||||||
    return render_template("glossary.html", lang=lang)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/seed/<suuid:seed>')
 | 
					 | 
				
			||||||
def view_seed(seed: UUID):
 | 
					 | 
				
			||||||
    seed = Seed.get(id=seed)
 | 
					 | 
				
			||||||
    if not seed:
 | 
					 | 
				
			||||||
        abort(404)
 | 
					 | 
				
			||||||
    return render_template("viewSeed.html", seed=seed, slot_count=count(seed.slots))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/new_room/<suuid:seed>')
 | 
					 | 
				
			||||||
def new_room(seed: UUID):
 | 
					 | 
				
			||||||
    seed = Seed.get(id=seed)
 | 
					 | 
				
			||||||
    if not seed:
 | 
					 | 
				
			||||||
        abort(404)
 | 
					 | 
				
			||||||
    room = Room(seed=seed, owner=session["_id"], tracker=uuid4())
 | 
					 | 
				
			||||||
    commit()
 | 
					 | 
				
			||||||
    return redirect(url_for("host_room", room=room.id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _read_log(path: str):
 | 
					 | 
				
			||||||
    if os.path.exists(path):
 | 
					 | 
				
			||||||
        with open(path, encoding="utf-8-sig") as log:
 | 
					 | 
				
			||||||
            yield from log
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        yield f"Logfile {path} does not exist. " \
 | 
					 | 
				
			||||||
              f"Likely a crash during spinup of multiworld instance or it is still spinning up."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/log/<suuid:room>')
 | 
					 | 
				
			||||||
def display_log(room: UUID):
 | 
					 | 
				
			||||||
    room = Room.get(id=room)
 | 
					 | 
				
			||||||
    if room is None:
 | 
					 | 
				
			||||||
        return abort(404)
 | 
					 | 
				
			||||||
    if room.owner == session["_id"]:
 | 
					 | 
				
			||||||
        return Response(_read_log(os.path.join("logs", str(room.id) + ".txt")), mimetype="text/plain;charset=UTF-8")
 | 
					 | 
				
			||||||
    return "Access Denied", 403
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/room/<suuid:room>', methods=['GET', 'POST'])
 | 
					 | 
				
			||||||
def host_room(room: UUID):
 | 
					 | 
				
			||||||
    room = Room.get(id=room)
 | 
					 | 
				
			||||||
    if room is None:
 | 
					 | 
				
			||||||
        return abort(404)
 | 
					 | 
				
			||||||
    if request.method == "POST":
 | 
					 | 
				
			||||||
        if room.owner == session["_id"]:
 | 
					 | 
				
			||||||
            cmd = request.form["cmd"]
 | 
					 | 
				
			||||||
            if cmd:
 | 
					 | 
				
			||||||
                Command(room=room, commandtext=cmd)
 | 
					 | 
				
			||||||
                commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    with db_session:
 | 
					 | 
				
			||||||
        room.last_activity = datetime.utcnow()  # will trigger a spinup, if it's not already running
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return render_template("hostRoom.html", room=room)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/favicon.ico')
 | 
					 | 
				
			||||||
def favicon():
 | 
					 | 
				
			||||||
    return send_from_directory(os.path.join(app.root_path, 'static/static'),
 | 
					 | 
				
			||||||
                               'favicon.ico', mimetype='image/vnd.microsoft.icon')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/discord')
 | 
					 | 
				
			||||||
def discord():
 | 
					 | 
				
			||||||
    return redirect("https://discord.gg/archipelago")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/datapackage')
 | 
					 | 
				
			||||||
@cache.cached()
 | 
					 | 
				
			||||||
def get_datapackge():
 | 
					 | 
				
			||||||
    """A pretty print version of /api/datapackage"""
 | 
					 | 
				
			||||||
    from worlds import network_data_package
 | 
					 | 
				
			||||||
    import json
 | 
					 | 
				
			||||||
    return Response(json.dumps(network_data_package, indent=4), mimetype="text/plain")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@app.route('/index')
 | 
					 | 
				
			||||||
@app.route('/sitemap')
 | 
					 | 
				
			||||||
def get_sitemap():
 | 
					 | 
				
			||||||
    available_games = []
 | 
					 | 
				
			||||||
    for game, world in AutoWorldRegister.world_types.items():
 | 
					 | 
				
			||||||
        if not world.hidden:
 | 
					 | 
				
			||||||
            available_games.append(game)
 | 
					 | 
				
			||||||
    return render_template("siteMap.html", games=available_games)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from WebHostLib.customserver import run_server_process
 | 
					 | 
				
			||||||
from . import tracker, upload, landing, check, generate, downloads, api, stats  # to trigger app routing picking up on it
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
app.register_blueprint(api.api_endpoints)
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -184,7 +184,7 @@ class MultiworldInstance():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        logging.info(f"Spinning up {self.room_id}")
 | 
					        logging.info(f"Spinning up {self.room_id}")
 | 
				
			||||||
        process = multiprocessing.Process(group=None, target=run_server_process,
 | 
					        process = multiprocessing.Process(group=None, target=run_server_process,
 | 
				
			||||||
                                          args=(self.room_id, self.ponyconfig),
 | 
					                                          args=(self.room_id, self.ponyconfig, get_static_server_data()),
 | 
				
			||||||
                                          name="MultiHost")
 | 
					                                          name="MultiHost")
 | 
				
			||||||
        process.start()
 | 
					        process.start()
 | 
				
			||||||
        # bind after start to prevent thread sync issues with guardian.
 | 
					        # bind after start to prevent thread sync issues with guardian.
 | 
				
			||||||
| 
						 | 
					@ -238,5 +238,5 @@ def run_guardian():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed
 | 
					from .models import Room, Generation, STATE_QUEUED, STATE_STARTED, STATE_ERROR, db, Seed
 | 
				
			||||||
from .customserver import run_server_process
 | 
					from .customserver import run_server_process, get_static_server_data
 | 
				
			||||||
from .generate import gen_game
 | 
					from .generate import gen_game
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,12 +9,13 @@ import time
 | 
				
			||||||
import random
 | 
					import random
 | 
				
			||||||
import pickle
 | 
					import pickle
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Utils
 | 
					import Utils
 | 
				
			||||||
from .models import *
 | 
					from .models import db_session, Room, select, commit, Command, db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor
 | 
					from MultiServer import Context, server, auto_shutdown, ServerCommandProcessor, ClientMessageProcessor
 | 
				
			||||||
from Utils import get_public_ipv4, get_public_ipv6, restricted_loads
 | 
					from Utils import get_public_ipv4, get_public_ipv6, restricted_loads, cache_argsless
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CustomClientMessageProcessor(ClientMessageProcessor):
 | 
					class CustomClientMessageProcessor(ClientMessageProcessor):
 | 
				
			||||||
| 
						 | 
					@ -39,7 +40,7 @@ class CustomClientMessageProcessor(ClientMessageProcessor):
 | 
				
			||||||
import MultiServer
 | 
					import MultiServer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MultiServer.client_message_processor = CustomClientMessageProcessor
 | 
					MultiServer.client_message_processor = CustomClientMessageProcessor
 | 
				
			||||||
del (MultiServer)
 | 
					del MultiServer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DBCommandProcessor(ServerCommandProcessor):
 | 
					class DBCommandProcessor(ServerCommandProcessor):
 | 
				
			||||||
| 
						 | 
					@ -48,12 +49,20 @@ class DBCommandProcessor(ServerCommandProcessor):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class WebHostContext(Context):
 | 
					class WebHostContext(Context):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self, static_server_data: dict):
 | 
				
			||||||
 | 
					        # static server data is used during _load_game_data to load required data,
 | 
				
			||||||
 | 
					        # without needing to import worlds system, which takes quite a bit of memory
 | 
				
			||||||
 | 
					        self.static_server_data = static_server_data
 | 
				
			||||||
        super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", "enabled", 0, 2)
 | 
					        super(WebHostContext, self).__init__("", 0, "", "", 1, 40, True, "enabled", "enabled", "enabled", 0, 2)
 | 
				
			||||||
 | 
					        del self.static_server_data
 | 
				
			||||||
        self.main_loop = asyncio.get_running_loop()
 | 
					        self.main_loop = asyncio.get_running_loop()
 | 
				
			||||||
        self.video = {}
 | 
					        self.video = {}
 | 
				
			||||||
        self.tags = ["AP", "WebHost"]
 | 
					        self.tags = ["AP", "WebHost"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _load_game_data(self):
 | 
				
			||||||
 | 
					        for key, value in self.static_server_data.items():
 | 
				
			||||||
 | 
					            setattr(self, key, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def listen_to_db_commands(self):
 | 
					    def listen_to_db_commands(self):
 | 
				
			||||||
        cmdprocessor = DBCommandProcessor(self)
 | 
					        cmdprocessor = DBCommandProcessor(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -107,14 +116,32 @@ def get_random_port():
 | 
				
			||||||
    return random.randint(49152, 65535)
 | 
					    return random.randint(49152, 65535)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def run_server_process(room_id, ponyconfig: dict):
 | 
					@cache_argsless
 | 
				
			||||||
 | 
					def get_static_server_data() -> dict:
 | 
				
			||||||
 | 
					    import worlds
 | 
				
			||||||
 | 
					    data = {
 | 
				
			||||||
 | 
					        "forced_auto_forfeits": {},
 | 
				
			||||||
 | 
					        "non_hintable_names": {},
 | 
				
			||||||
 | 
					        "gamespackage": worlds.network_data_package["games"],
 | 
				
			||||||
 | 
					        "item_name_groups": {world_name: world.item_name_groups for world_name, world in
 | 
				
			||||||
 | 
					                             worlds.AutoWorldRegister.world_types.items()},
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for world_name, world in worlds.AutoWorldRegister.world_types.items():
 | 
				
			||||||
 | 
					        data["forced_auto_forfeits"][world_name] = world.forced_auto_forfeit
 | 
				
			||||||
 | 
					        data["non_hintable_names"][world_name] = world.hint_blacklist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_server_process(room_id, ponyconfig: dict, static_server_data: dict):
 | 
				
			||||||
    # establish DB connection for multidata and multisave
 | 
					    # establish DB connection for multidata and multisave
 | 
				
			||||||
    db.bind(**ponyconfig)
 | 
					    db.bind(**ponyconfig)
 | 
				
			||||||
    db.generate_mapping(check_tables=False)
 | 
					    db.generate_mapping(check_tables=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def main():
 | 
					    async def main():
 | 
				
			||||||
        Utils.init_logging(str(room_id), write_mode="a")
 | 
					        Utils.init_logging(str(room_id), write_mode="a")
 | 
				
			||||||
        ctx = WebHostContext()
 | 
					        ctx = WebHostContext(static_server_data)
 | 
				
			||||||
        ctx.load(room_id)
 | 
					        ctx.load(room_id)
 | 
				
			||||||
        ctx.init_save()
 | 
					        ctx.init_save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,170 @@
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import jinja2.exceptions
 | 
				
			||||||
 | 
					from flask import request, redirect, url_for, render_template, Response, session, abort, send_from_directory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .models import count, Seed, commit, Room, db_session, Command, UUID, uuid4
 | 
				
			||||||
 | 
					from worlds.AutoWorld import AutoWorldRegister
 | 
				
			||||||
 | 
					from . import app, cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_world_theme(game_name: str):
 | 
				
			||||||
 | 
					    if game_name in AutoWorldRegister.world_types:
 | 
				
			||||||
 | 
					        return AutoWorldRegister.world_types[game_name].web.theme
 | 
				
			||||||
 | 
					    return 'grass'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.before_request
 | 
				
			||||||
 | 
					def register_session():
 | 
				
			||||||
 | 
					    session.permanent = True  # technically 31 days after the last visit
 | 
				
			||||||
 | 
					    if not session.get("_id", None):
 | 
				
			||||||
 | 
					        session["_id"] = uuid4()  # uniquely identify each session without needing a login
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.errorhandler(404)
 | 
				
			||||||
 | 
					@app.errorhandler(jinja2.exceptions.TemplateNotFound)
 | 
				
			||||||
 | 
					def page_not_found(err):
 | 
				
			||||||
 | 
					    return render_template('404.html'), 404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Start Playing Page
 | 
				
			||||||
 | 
					@app.route('/start-playing')
 | 
				
			||||||
 | 
					def start_playing():
 | 
				
			||||||
 | 
					    return render_template(f"startPlaying.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/weighted-settings')
 | 
				
			||||||
 | 
					def weighted_settings():
 | 
				
			||||||
 | 
					    return render_template(f"weighted-settings.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Player settings pages
 | 
				
			||||||
 | 
					@app.route('/games/<string:game>/player-settings')
 | 
				
			||||||
 | 
					def player_settings(game):
 | 
				
			||||||
 | 
					    return render_template(f"player-settings.html", game=game, theme=get_world_theme(game))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Game Info Pages
 | 
				
			||||||
 | 
					@app.route('/games/<string:game>/info/<string:lang>')
 | 
				
			||||||
 | 
					def game_info(game, lang):
 | 
				
			||||||
 | 
					    return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# List of supported games
 | 
				
			||||||
 | 
					@app.route('/games')
 | 
				
			||||||
 | 
					def games():
 | 
				
			||||||
 | 
					    worlds = {}
 | 
				
			||||||
 | 
					    for game, world in AutoWorldRegister.world_types.items():
 | 
				
			||||||
 | 
					        if not world.hidden:
 | 
				
			||||||
 | 
					            worlds[game] = world
 | 
				
			||||||
 | 
					    return render_template("supportedGames.html", worlds=worlds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
 | 
				
			||||||
 | 
					def tutorial(game, file, lang):
 | 
				
			||||||
 | 
					    return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/tutorial/')
 | 
				
			||||||
 | 
					def tutorial_landing():
 | 
				
			||||||
 | 
					    worlds = {}
 | 
				
			||||||
 | 
					    for game, world in AutoWorldRegister.world_types.items():
 | 
				
			||||||
 | 
					        if not world.hidden:
 | 
				
			||||||
 | 
					            worlds[game] = world
 | 
				
			||||||
 | 
					    return render_template("tutorialLanding.html")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/faq/<string:lang>/')
 | 
				
			||||||
 | 
					def faq(lang):
 | 
				
			||||||
 | 
					    return render_template("faq.html", lang=lang)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/glossary/<string:lang>/')
 | 
				
			||||||
 | 
					def terms(lang):
 | 
				
			||||||
 | 
					    return render_template("glossary.html", lang=lang)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/seed/<suuid:seed>')
 | 
				
			||||||
 | 
					def view_seed(seed: UUID):
 | 
				
			||||||
 | 
					    seed = Seed.get(id=seed)
 | 
				
			||||||
 | 
					    if not seed:
 | 
				
			||||||
 | 
					        abort(404)
 | 
				
			||||||
 | 
					    return render_template("viewSeed.html", seed=seed, slot_count=count(seed.slots))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/new_room/<suuid:seed>')
 | 
				
			||||||
 | 
					def new_room(seed: UUID):
 | 
				
			||||||
 | 
					    seed = Seed.get(id=seed)
 | 
				
			||||||
 | 
					    if not seed:
 | 
				
			||||||
 | 
					        abort(404)
 | 
				
			||||||
 | 
					    room = Room(seed=seed, owner=session["_id"], tracker=uuid4())
 | 
				
			||||||
 | 
					    commit()
 | 
				
			||||||
 | 
					    return redirect(url_for("host_room", room=room.id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _read_log(path: str):
 | 
				
			||||||
 | 
					    if os.path.exists(path):
 | 
				
			||||||
 | 
					        with open(path, encoding="utf-8-sig") as log:
 | 
				
			||||||
 | 
					            yield from log
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        yield f"Logfile {path} does not exist. " \
 | 
				
			||||||
 | 
					              f"Likely a crash during spinup of multiworld instance or it is still spinning up."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/log/<suuid:room>')
 | 
				
			||||||
 | 
					def display_log(room: UUID):
 | 
				
			||||||
 | 
					    room = Room.get(id=room)
 | 
				
			||||||
 | 
					    if room is None:
 | 
				
			||||||
 | 
					        return abort(404)
 | 
				
			||||||
 | 
					    if room.owner == session["_id"]:
 | 
				
			||||||
 | 
					        return Response(_read_log(os.path.join("logs", str(room.id) + ".txt")), mimetype="text/plain;charset=UTF-8")
 | 
				
			||||||
 | 
					    return "Access Denied", 403
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/room/<suuid:room>', methods=['GET', 'POST'])
 | 
				
			||||||
 | 
					def host_room(room: UUID):
 | 
				
			||||||
 | 
					    room = Room.get(id=room)
 | 
				
			||||||
 | 
					    if room is None:
 | 
				
			||||||
 | 
					        return abort(404)
 | 
				
			||||||
 | 
					    if request.method == "POST":
 | 
				
			||||||
 | 
					        if room.owner == session["_id"]:
 | 
				
			||||||
 | 
					            cmd = request.form["cmd"]
 | 
				
			||||||
 | 
					            if cmd:
 | 
				
			||||||
 | 
					                Command(room=room, commandtext=cmd)
 | 
				
			||||||
 | 
					                commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with db_session:
 | 
				
			||||||
 | 
					        room.last_activity = datetime.utcnow()  # will trigger a spinup, if it's not already running
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return render_template("hostRoom.html", room=room)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/favicon.ico')
 | 
				
			||||||
 | 
					def favicon():
 | 
				
			||||||
 | 
					    return send_from_directory(os.path.join(app.root_path, 'static/static'),
 | 
				
			||||||
 | 
					                               'favicon.ico', mimetype='image/vnd.microsoft.icon')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/discord')
 | 
				
			||||||
 | 
					def discord():
 | 
				
			||||||
 | 
					    return redirect("https://discord.gg/archipelago")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/datapackage')
 | 
				
			||||||
 | 
					@cache.cached()
 | 
				
			||||||
 | 
					def get_datapackge():
 | 
				
			||||||
 | 
					    """A pretty print version of /api/datapackage"""
 | 
				
			||||||
 | 
					    from worlds import network_data_package
 | 
				
			||||||
 | 
					    import json
 | 
				
			||||||
 | 
					    return Response(json.dumps(network_data_package, indent=4), mimetype="text/plain")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/index')
 | 
				
			||||||
 | 
					@app.route('/sitemap')
 | 
				
			||||||
 | 
					def get_sitemap():
 | 
				
			||||||
 | 
					    available_games = []
 | 
				
			||||||
 | 
					    for game, world in AutoWorldRegister.world_types.items():
 | 
				
			||||||
 | 
					        if not world.hidden:
 | 
				
			||||||
 | 
					            available_games.append(game)
 | 
				
			||||||
 | 
					    return render_template("siteMap.html", games=available_games)
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@ from worlds.alttp import Items
 | 
				
			||||||
from WebHostLib import app, cache, Room
 | 
					from WebHostLib import app, cache, Room
 | 
				
			||||||
from Utils import restricted_loads
 | 
					from Utils import restricted_loads
 | 
				
			||||||
from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name
 | 
					from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name
 | 
				
			||||||
from MultiServer import get_item_name_from_id, Context
 | 
					from MultiServer import Context
 | 
				
			||||||
from NetUtils import SlotType
 | 
					from NetUtils import SlotType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
alttp_icons = {
 | 
					alttp_icons = {
 | 
				
			||||||
| 
						 | 
					@ -1021,7 +1021,7 @@ def getTracker(tracker: UUID):
 | 
				
			||||||
    for (team, player), data in multisave.get("video", []):
 | 
					    for (team, player), data in multisave.get("video", []):
 | 
				
			||||||
        video[(team, player)] = data
 | 
					        video[(team, player)] = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return render_template("tracker.html", inventory=inventory, get_item_name_from_id=get_item_name_from_id,
 | 
					    return render_template("tracker.html", inventory=inventory, get_item_name_from_id=lookup_any_item_id_to_name,
 | 
				
			||||||
                           lookup_id_to_name=Items.lookup_id_to_name, player_names=player_names,
 | 
					                           lookup_id_to_name=Items.lookup_id_to_name, player_names=player_names,
 | 
				
			||||||
                           tracking_names=tracking_names, tracking_ids=tracking_ids, room=room, icons=alttp_icons,
 | 
					                           tracking_names=tracking_names, tracking_ids=tracking_ids, room=room, icons=alttp_icons,
 | 
				
			||||||
                           multi_items=multi_items, checks_done=checks_done, ordered_areas=ordered_areas,
 | 
					                           multi_items=multi_items, checks_done=checks_done, ordered_areas=ordered_areas,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,7 +45,6 @@ class MeritousWorld(World):
 | 
				
			||||||
    item_name_groups = item_groups
 | 
					    item_name_groups = item_groups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data_version = 2
 | 
					    data_version = 2
 | 
				
			||||||
    forced_auto_forfeit = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # NOTE: Remember to change this before this game goes live
 | 
					    # NOTE: Remember to change this before this game goes live
 | 
				
			||||||
    required_client_version = (0, 2, 4)
 | 
					    required_client_version = (0, 2, 4)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,9 +35,7 @@ class SM64World(World):
 | 
				
			||||||
    location_name_to_id = location_table
 | 
					    location_name_to_id = location_table
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data_version = 6
 | 
					    data_version = 6
 | 
				
			||||||
    required_client_version = (0,3,0)
 | 
					    required_client_version = (0, 3, 0)
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    forced_auto_forfeit = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    area_connections: typing.Dict[int, int]
 | 
					    area_connections: typing.Dict[int, int]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,6 @@ class V6World(World):
 | 
				
			||||||
    location_name_to_id = location_table
 | 
					    location_name_to_id = location_table
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data_version = 1
 | 
					    data_version = 1
 | 
				
			||||||
    forced_auto_forfeit = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    area_connections: typing.Dict[int, int]
 | 
					    area_connections: typing.Dict[int, int]
 | 
				
			||||||
    area_cost_map: typing.Dict[int,int]
 | 
					    area_cost_map: typing.Dict[int,int]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue