from bot import loaded_modules_dict from os import path, pardir import json module_name = path.basename(path.normpath(path.join(path.abspath(__file__), pardir, pardir))) widget_name = path.basename(path.abspath(__file__))[:-3] # View Registry - defines all available views and their navigation labels # This eliminates the need for separate template files for each button VIEW_REGISTRY = { 'frontend': { 'label_active': 'back', 'label_inactive': 'main', 'action': 'show_frontend', 'include_in_menu': False }, 'options': { 'label_active': 'back', 'label_inactive': 'options', 'action': 'show_options', 'include_in_menu': True }, 'map': { 'label_active': 'hide map', 'label_inactive': 'show map', 'action': 'show_map', 'include_in_menu': True }, 'create_new': { 'label_active': 'back', 'label_inactive': 'create new', 'action': 'show_create_new', 'include_in_menu': True }, 'special_locations': { 'label_active': 'back', 'label_inactive': 'special', 'action': 'show_special_locations', 'include_in_menu': True } } def get_table_row_css_class(location_dict): is_enabled = location_dict.get("is_enabled", False) if is_enabled: css_class = "is_enabled" else: css_class = "" return css_class def select_view(*args, **kwargs): module = args[0] dispatchers_steamid = kwargs.get('dispatchers_steamid', None) current_view = module.get_current_view(dispatchers_steamid) if current_view == "options": options_view(module, dispatchers_steamid=dispatchers_steamid, current_view=current_view) elif current_view == "map": map_view(module, dispatchers_steamid=dispatchers_steamid, current_view=current_view) elif current_view == "special_locations": frontend_view(module, dispatchers_steamid=dispatchers_steamid, current_view=current_view) elif current_view == "delete-modal": frontend_view(module, dispatchers_steamid=dispatchers_steamid) delete_modal_view(module, dispatchers_steamid=dispatchers_steamid) elif current_view == "create_new": edit_view(module, dispatchers_steamid=dispatchers_steamid, current_view=current_view) elif current_view == "edit_location_entry": location_owner = ( module.dom.data .get(module.get_module_identifier(), {}) .get("visibility", {}) .get(dispatchers_steamid, {}) .get("location_owner", None) ) location_identifier = ( module.dom.data .get(module.get_module_identifier(), {}) .get("visibility", {}) .get(dispatchers_steamid, {}) .get("location_identifier", None) ) location_origin = ( module.dom.data .get(module.get_module_identifier(), {}) .get("visibility", {}) .get(dispatchers_steamid, {}) .get("location_origin", None) ) edit_view( module, dispatchers_steamid=dispatchers_steamid, location_owner=location_owner, location_identifier=location_identifier, location_origin=location_origin, current_view=current_view ) else: frontend_view(module, dispatchers_steamid=dispatchers_steamid) def delete_modal_view(*args, **kwargs): module = args[0] dispatchers_steamid = kwargs.get('dispatchers_steamid', None) all_available_locations = module.dom.data.get(module.get_module_identifier(), {}).get("elements", {}) all_selected_elements_count = 0 active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None) for map_identifier, location_owner in all_available_locations.items(): if active_dataset == map_identifier: for player_steamid, player_locations in location_owner.items(): for identifier, location_dict in player_locations.items(): location_is_selected_by = location_dict.get("selected_by", []) if dispatchers_steamid in location_is_selected_by: all_selected_elements_count += 1 modal_confirm_delete = module.dom_management.get_delete_confirm_modal( module, count=all_selected_elements_count, target_module="module_locations", dom_element_id="location_table_modal_action_delete_button", dom_action="delete_selected_dom_elements", dom_element_root=module.dom_element_root, dom_element_select_root=module.dom_element_select_root, confirmed="True" ) data_to_emit = modal_confirm_delete module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="modal_content", clients=[dispatchers_steamid], target_element={ "id": "manage_locations_widget_modal", "type": "div", "selector": "body > main > div" } ) def frontend_view(*args, **kwargs): module = args[0] dispatchers_steamid = kwargs.get("dispatchers_steamid", None) current_view = kwargs.get("current_view", None) # Load templates template_frontend = module.templates.get_template('manage_locations_widget/view_frontend.html') template_view_menu = module.templates.get_template('manage_locations_widget/control_view_menu.html') control_player_location_view_template = module.templates.get_template( 'manage_locations_widget/control_player_location.html' ) control_edit_link = module.templates.get_template('manage_locations_widget/control_edit_link.html') control_enabled_link = module.templates.get_template('manage_locations_widget/control_enabled_link.html') template_table_header = module.templates.get_template('manage_locations_widget/table_header.html') template_table_rows = module.templates.get_template('manage_locations_widget/table_row.html') template_table_footer = module.templates.get_template('manage_locations_widget/table_footer.html') # Build table rows efficiently using list + join (avoids O(n²) with string +=) table_rows_list = [] all_available_locations = module.dom.data.get(module.get_module_identifier(), {}).get("elements", {}) all_selected_elements_count = 0 active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None) for map_identifier, location_owner in all_available_locations.items(): if active_dataset == map_identifier: for player_steamid, player_locations in location_owner.items(): player_dict = ( module.dom.data .get("module_players", {}) .get("elements", {}) .get(active_dataset, {}) .get(player_steamid, {}) ) for identifier, location_dict in player_locations.items(): location_has_special_properties = len(location_dict.get("type", [])) >= 1 if not location_has_special_properties and current_view == "special_locations": continue location_is_selected_by = location_dict.get("selected_by", []) location_entry_selected = False if dispatchers_steamid in location_is_selected_by: location_entry_selected = True all_selected_elements_count += 1 # Sanitize dataset for HTML ID (replace spaces with underscores, lowercase) location_dict_for_template = location_dict.copy() location_dict_for_template["dataset"] = module.dom_management.sanitize_for_html_id(location_dict.get("dataset", "")) location_dict_for_template["dataset_original"] = location_dict.get("dataset", "") control_select_link = module.dom_management.get_selection_dom_element( module, target_module="module_locations", dom_element_select_root=[identifier, "selected_by"], dom_element=location_dict_for_template, dom_element_entry_selected=location_entry_selected, dom_action_inactive="select_dom_element", dom_action_active="deselect_dom_element" ) table_rows_list.append(module.template_render_hook( module, template=template_table_rows, location=location_dict_for_template, player_dict=player_dict, control_select_link=control_select_link, control_enabled_link=module.template_render_hook( module, template=control_enabled_link, location=location_dict_for_template, ), control_edit_link=module.template_render_hook( module, template=control_edit_link, dispatchers_steamid=dispatchers_steamid, location=location_dict_for_template, ) )) table_rows = ''.join(table_rows_list) dom_element_delete_button = module.dom_management.get_delete_button_dom_element( module, count=all_selected_elements_count, target_module="module_locations", dom_element_root=module.dom_element_root, dom_element_select_root=module.dom_element_select_root, dom_element_id="manage_locations_control_action_delete_link", dom_action="delete_selected_dom_elements" ) # Get current view and player coordinates current_view = module.get_current_view(dispatchers_steamid) player_coordinates = module.dom.data.get("module_players", {}).get("players", {}).get(dispatchers_steamid, {}).get( "pos", {"x": 0, "y": 0, "z": 0} ) # Render navigation menu using view registry options_toggle = module.template_render_hook( module, template=template_view_menu, views=VIEW_REGISTRY, current_view=current_view, steamid=dispatchers_steamid, control_player_location_view=module.template_render_hook( module, template=control_player_location_view_template, pos_x=player_coordinates["x"], pos_y=player_coordinates["y"], pos_z=player_coordinates["z"] ) ) data_to_emit = module.template_render_hook( module, template=template_frontend, options_toggle=options_toggle, table_header=module.template_render_hook( module, template=template_table_header ), table_rows=table_rows, table_footer=module.template_render_hook( module, template=template_table_footer, action_delete_button=dom_element_delete_button ) ) module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="widget_content", clients=[dispatchers_steamid], target_element={ "id": "manage_locations_widget", "type": "table", "selector": "body > main > div" } ) def map_view(*args, **kwargs): module = args[0] dispatchers_steamid = kwargs.get("dispatchers_steamid", None) current_view = kwargs.get("current_view", None) # Load templates template_map = module.templates.get_template('manage_locations_widget/view_map.html') template_view_menu = module.templates.get_template('manage_locations_widget/control_view_menu.html') control_player_location_view_template = module.templates.get_template( 'manage_locations_widget/control_player_location.html' ) # Get current view and player coordinates current_view = module.get_current_view(dispatchers_steamid) player_coordinates = module.dom.data.get("module_players", {}).get("players", {}).get(dispatchers_steamid, {}).get( "pos", {"x": 0, "y": 0, "z": 0} ) # Collect all locations for the map all_locations = module.dom.data.get(module.get_module_identifier(), {}).get("elements", {}) locations_for_map = {} active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None) for map_identifier, location_owners in all_locations.items(): if active_dataset and map_identifier != active_dataset: continue for owner_steamid, player_locations in location_owners.items(): for identifier, location_dict in player_locations.items(): location_id = f"{map_identifier}_{owner_steamid}_{identifier}" coordinates = location_dict.get("coordinates", {}) dimensions = location_dict.get("dimensions", {}) shape = location_dict.get("shape", "circle") locations_for_map[location_id] = { "name": location_dict.get("name", "Unknown"), "identifier": identifier, "owner": owner_steamid, "shape": shape, "coordinates": { "x": float(coordinates.get("x", 0)), "y": float(coordinates.get("y", 0)), "z": float(coordinates.get("z", 0)) }, "dimensions": dimensions, "teleport_entry": location_dict.get("teleport_entry", {}), "type": location_dict.get("type", []), "is_enabled": location_dict.get("is_enabled", False) } # Collect all online players for the map players_module = loaded_modules_dict.get("module_players") players_for_map = {} if players_module: active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None) all_players = players_module.dom.data.get("module_players", {}).get("elements", {}) if active_dataset and active_dataset in all_players: for steamid, player_dict in all_players[active_dataset].items(): if player_dict.get("is_online", False): players_for_map[steamid] = { "name": player_dict.get("name", "Player"), "level": player_dict.get("level", 0), "health": player_dict.get("health", 0), "zombies": player_dict.get("zombies", 0), "players": player_dict.get("players", 0), "deaths": player_dict.get("deaths", 0), "score": player_dict.get("score", 0), "ping": player_dict.get("ping", 0), "is_authenticated": player_dict.get("is_authenticated", False), "is_muted": player_dict.get("is_muted", False), "is_initialized": player_dict.get("is_initialized", False), "in_limbo": player_dict.get("in_limbo", False), "permission_level": player_dict.get("permission_level", None), "dataset": active_dataset, "pos": { "x": player_dict.get("pos", {}).get("x", 0), "y": player_dict.get("pos", {}).get("y", 0), "z": player_dict.get("pos", {}).get("z", 0) } } # Get gameprefs for map legend gameprefs = {} if active_dataset: gameprefs = ( module.dom.data .get("module_game_environment", {}) .get(active_dataset, {}) .get("gameprefs", {}) ) # Load webmap templates from players and locations modules webmap_templates = {} # Load player webmap templates if players_module: try: webmap_templates['player_popup'] = players_module.templates.get_template('webmap/player_popup.html').render() webmap_templates['player_markers'] = players_module.templates.get_template('webmap/player_markers.html').render() webmap_templates['player_update_handler'] = players_module.templates.get_template('webmap/player_update_handler.html').render() webmap_templates['player_actions'] = players_module.templates.get_template('webmap/player_actions.html').render() except Exception as e: module.logger.error(f"Error loading player webmap templates: {e}") # Load location webmap templates try: webmap_templates['location_shapes'] = module.templates.get_template('webmap/location_shapes.html').render() webmap_templates['location_update_handler'] = module.templates.get_template('webmap/location_update_handler.html').render() webmap_templates['location_actions'] = module.templates.get_template('webmap/location_actions.html').render() except Exception as e: module.logger.error(f"Error loading location webmap templates: {e}") # Render navigation menu using view registry control_switch_view = module.template_render_hook( module, template=template_view_menu, views=VIEW_REGISTRY, current_view=current_view, steamid=dispatchers_steamid, control_player_location_view=module.template_render_hook( module, template=control_player_location_view_template, pos_x=player_coordinates["x"], pos_y=player_coordinates["y"], pos_z=player_coordinates["z"] ) ) data_to_emit = module.template_render_hook( module, template=template_map, control_switch_view=control_switch_view, webmap_templates=webmap_templates ) module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="widget_content", clients=[dispatchers_steamid], target_element={ "id": "manage_locations_widget", "type": "table", "selector": "body > main > div" } ) # Send map metadata via Socket.IO module.webserver.send_data_to_client_hook( module, payload={ "gameprefs": gameprefs, "active_dataset": active_dataset or "Unknown" }, data_type="map_metadata", clients=[dispatchers_steamid] ) # Send initial player data via Socket.IO for steamid, player_data in players_for_map.items(): player_update_data = { "steamid": steamid, "name": player_data.get("name", "Player"), "level": player_data.get("level", 0), "health": player_data.get("health", 0), "zombies": player_data.get("zombies", 0), "deaths": player_data.get("deaths", 0), "players": player_data.get("players", 0), "score": player_data.get("score", 0), "ping": player_data.get("ping", 0), "is_muted": player_data.get("is_muted", False), "is_authenticated": player_data.get("is_authenticated", False), "in_limbo": player_data.get("in_limbo", False), "is_initialized": player_data.get("is_initialized", False), "permission_level": player_data.get("permission_level", None), "dataset": player_data.get("dataset", ""), "position": { "x": float(player_data.get("pos", {}).get("x", 0)), "y": float(player_data.get("pos", {}).get("y", 0)), "z": float(player_data.get("pos", {}).get("z", 0)) } } module.webserver.send_data_to_client_hook( module, payload=player_update_data, data_type="player_position_update", clients=[dispatchers_steamid] ) # Send initial location data via Socket.IO for location_id, location_data in locations_for_map.items(): module.webserver.send_data_to_client_hook( module, payload={ "location_id": location_id, "location": location_data }, data_type="location_update", clients=[dispatchers_steamid] ) def options_view(*args, **kwargs): module = args[0] dispatchers_steamid = kwargs.get("dispatchers_steamid", None) # Load templates template_frontend = module.templates.get_template('manage_locations_widget/view_options.html') template_view_menu = module.templates.get_template('manage_locations_widget/control_view_menu.html') # Get current view current_view = module.get_current_view(dispatchers_steamid) # Render navigation menu using view registry options_toggle = module.template_render_hook( module, template=template_view_menu, views=VIEW_REGISTRY, current_view=current_view, steamid=dispatchers_steamid, control_player_location_view='' # No player location in options view ) data_to_emit = module.template_render_hook( module, template=template_frontend, options_toggle=options_toggle, widget_options=module.options, available_actions=module.available_actions_dict, available_triggers=module.available_triggers_dict, available_widgets=module.available_widgets_dict ) module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="widget_content", clients=[dispatchers_steamid], target_element={ "id": "manage_locations_widget", "type": "table", "selector": "body > main > div" } ) def special_locations_view(*args, **kwargs): module = args[0] dispatchers_steamid = kwargs.get("dispatchers_steamid", None) # Load templates template_frontend = module.templates.get_template('manage_locations_widget/view_special_locations.html') template_view_menu = module.templates.get_template('manage_locations_widget/control_view_menu.html') # Get current view current_view = module.get_current_view(dispatchers_steamid) # Render navigation menu using view registry options_toggle = module.template_render_hook( module, template=template_view_menu, views=VIEW_REGISTRY, current_view=current_view, steamid=dispatchers_steamid, control_player_location_view='' ) data_to_emit = module.template_render_hook( module, template=template_frontend, options_toggle=options_toggle ) module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="widget_content", clients=[dispatchers_steamid], target_element={ "id": "manage_locations_widget", "type": "table", "selector": "body > main > div" } ) def edit_view(*args, **kwargs): module = args[0] dispatchers_steamid = kwargs.get("dispatchers_steamid", None) edit_mode = kwargs.get("current_view", None) location_to_edit_dict = {} if edit_mode == "edit_location_entry": location_owner = kwargs.get("location_owner", None) location_identifier = kwargs.get("location_identifier", None) location_origin = kwargs.get("location_origin", None) if all([ location_owner is not None, location_identifier is not None, location_origin is not None ]): location_to_edit_dict = ( module.dom.data .get(module.get_module_identifier(), {}) .get("elements", {}) .get(location_origin) .get(location_owner) .get(location_identifier) ) if edit_mode == "create_new": location_to_edit_dict = { "owner": str(dispatchers_steamid), "is_enabled": False } # Check for prefilled coordinates from map prefill_coords = ( module.dom.data .get(module.get_module_identifier(), {}) .get("visibility", {}) .get(dispatchers_steamid, {}) .get("prefill_coordinates", None) ) if prefill_coords: location_to_edit_dict["coordinates"] = { "x": float(prefill_coords.get("x", 0)), "y": float(prefill_coords.get("y", 0)), "z": float(prefill_coords.get("z", 0)) } # Also prefill teleport_entry with same coordinates location_to_edit_dict["teleport_entry"] = { "x": float(prefill_coords.get("x", 0)), "y": float(prefill_coords.get("y", 0)), "z": float(prefill_coords.get("z", 0)) } # Load templates template_frontend = module.templates.get_template('manage_locations_widget/view_create_new.html') template_view_menu = module.templates.get_template('manage_locations_widget/control_view_menu.html') control_player_location_view_template = module.templates.get_template( 'manage_locations_widget/control_player_location.html' ) # Get current view and player coordinates current_view = module.get_current_view(dispatchers_steamid) player_coordinates = module.dom.data.get("module_players", {}).get("players", {}).get(dispatchers_steamid, {}).get( "pos", {"x": 0, "y": 0, "z": 0} ) # Render navigation menu using view registry options_toggle = module.template_render_hook( module, template=template_view_menu, views=VIEW_REGISTRY, current_view=current_view, steamid=dispatchers_steamid, control_player_location_view=module.template_render_hook( module, template=control_player_location_view_template, pos_x=player_coordinates["x"], pos_y=player_coordinates["y"], pos_z=player_coordinates["z"] ) ) data_to_emit = module.template_render_hook( module, template=template_frontend, options_toggle=options_toggle, widget_options=module.options, location_to_edit_dict=location_to_edit_dict ) module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="widget_content", clients=[dispatchers_steamid], target_element={ "id": "manage_locations_widget", "type": "table", "selector": "body > main > div" } ) def table_row(*args, **kwargs): module = args[0] method = kwargs.get("method", None) updated_values_dict = kwargs.get("updated_values_dict", None) template_table_rows = module.templates.get_template('manage_locations_widget/table_row.html') control_edit_link = module.templates.get_template('manage_locations_widget/control_edit_link.html') control_enabled_link = module.templates.get_template('manage_locations_widget/control_enabled_link.html') if updated_values_dict is not None: if method in ["upsert", "update", "edit"]: active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None) for clientid in module.webserver.connected_clients.keys(): current_view = module.get_current_view(clientid) visibility_conditions = [ current_view == "frontend" ] if any(visibility_conditions): # only relevant if the table is shown for player_steamid, locations in updated_values_dict.items(): player_dict = ( module.dom.data .get("module_players", {}) .get("elements", {}) .get(active_dataset, {}) .get(player_steamid, {}) ) for identifier, location_dict in locations.items(): location_is_selected_by = location_dict.get("selected_by", []) location_entry_selected = False if clientid in location_is_selected_by: location_entry_selected = True try: # Sanitize dataset for HTML ID (replace spaces with underscores, lowercase) sanitized_dataset = module.dom_management.sanitize_for_html_id(updated_values_dict[player_steamid][identifier]["dataset"]) table_row_id = "location_table_row_{}_{}_{}".format( sanitized_dataset, str(player_steamid), str(identifier) ) # Update location_dict with sanitized dataset for template location_dict = location_dict.copy() location_dict["dataset"] = sanitized_dataset location_dict["dataset_original"] = updated_values_dict[player_steamid][identifier].get("dataset", "") except KeyError: table_row_id = "manage_locations_widget" control_select_link = module.dom_management.get_selection_dom_element( module, target_module="module_locations", dom_element_select_root=[identifier, "selected_by"], dom_element=location_dict, dom_element_entry_selected=location_entry_selected, dom_action_inactive="select_dom_element", dom_action_active="deselect_dom_element" ) rendered_table_row = module.template_render_hook( module, template=template_table_rows, location=location_dict, player_dict=player_dict, control_select_link=control_select_link, control_enabled_link=module.template_render_hook( module, template=control_enabled_link, location=location_dict, ), control_edit_link=module.template_render_hook( module, template=control_edit_link, location=location_dict, ), css_class=get_table_row_css_class(location_dict) ) module.webserver.send_data_to_client_hook( module, payload=rendered_table_row, data_type="table_row", clients=[clientid], target_element={ "id": table_row_id, "type": "tr", "class": get_table_row_css_class(location_dict), "selector": "body > main > div > div#manage_locations_widget > main > table > tbody" } ) else: # table is not visible or current user, skip it! continue elif method in ["remove"]: # callback_dict sent us here with a removal notification! location_origin = updated_values_dict[2] player_steamid = updated_values_dict[3] location_identifier = updated_values_dict[-1] # Sanitize dataset for HTML ID (replace spaces with underscores, lowercase) sanitized_origin = module.dom_management.sanitize_for_html_id(location_origin) module.webserver.send_data_to_client_hook( module, data_type="remove_table_row", clients="all", target_element={ "id": "location_table_row_{}_{}_{}".format( sanitized_origin, str(player_steamid), str(location_identifier) ), } ) update_delete_button_status(module, *args, **kwargs) def update_player_location(*args, **kwargs): module = args[0] updated_values_dict = kwargs.get("updated_values_dict", None) webserver_logged_in_users = module.dom.data.get("module_webserver", {}).get( "webserver_logged_in_users", [] ) dispatchers_steamid = updated_values_dict.get("steamid") if dispatchers_steamid not in webserver_logged_in_users: return control_player_location_view = module.templates.get_template( 'manage_locations_widget/control_player_location.html' ) player_coordinates = updated_values_dict.get("pos", {}) data_to_emit = module.template_render_hook( module, template=control_player_location_view, pos_x=player_coordinates["x"], pos_y=player_coordinates["y"], pos_z=player_coordinates["z"] ) module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="element_content", method="replace", clients=dispatchers_steamid, target_element={ "id": "current_player_pos" } ) def update_selection_status(*args, **kwargs): module = args[0] updated_values_dict = kwargs.get("updated_values_dict", None) location_identifier = updated_values_dict["identifier"] # Sanitize dataset for HTML ID (replace spaces with underscores, lowercase) sanitized_dataset = module.dom_management.sanitize_for_html_id(updated_values_dict["dataset"]) module.dom_management.update_selection_status( *args, **kwargs, target_module=module, dom_element_root=[location_identifier], dom_element_select_root=[location_identifier, "selected_by"], dom_action_active="deselect_dom_element", dom_action_inactive="select_dom_element", dom_element_id={ "id": "location_table_row_{}_{}_{}_control_select_link".format( sanitized_dataset, updated_values_dict["owner"], updated_values_dict["identifier"] ) } ) update_delete_button_status(module, *args, **kwargs) def update_enabled_flag(*args, **kwargs): module = args[0] original_values_dict = kwargs.get("original_values_dict", None) control_enable_link = module.templates.get_template('manage_locations_widget/control_enabled_link.html') location_origin = original_values_dict.get("dataset", None) location_owner = original_values_dict.get("owner", None) location_identifier = original_values_dict.get("identifier", None) location_dict = ( module.dom.data.get("module_locations", {}) .get("elements", {}) .get(location_origin, {}) .get(location_owner, {}) .get(location_identifier, None) ) # Sanitize dataset for HTML ID (replace spaces with underscores, lowercase) location_dict_sanitized = location_dict.copy() location_dict_sanitized["dataset"] = module.dom_management.sanitize_for_html_id(location_origin) location_dict_sanitized["dataset_original"] = location_origin data_to_emit = module.template_render_hook( module, template=control_enable_link, location=location_dict_sanitized, ) # Sanitize dataset for HTML ID (replace spaces with underscores, lowercase) sanitized_origin = module.dom_management.sanitize_for_html_id(location_origin) module.webserver.send_data_to_client_hook( module, payload=data_to_emit, data_type="element_content", clients="all", method="update", target_element={ "id": "location_table_row_{}_{}_{}_control_enabled_link".format( sanitized_origin, location_owner, location_identifier ), } ) def update_delete_button_status(*args, **kwargs): module = args[0] module.dom_management.update_delete_button_status( *args, **kwargs, target_module=module, dom_element_root=module.dom_element_root, dom_element_select_root=module.dom_element_select_root, dom_action="delete_selected_dom_elements", dom_element_id={ "id": "manage_locations_control_action_delete_link" } ) def update_location_on_map(*args, **kwargs): """Send location updates to map view via socket.io""" module = args[0] method = kwargs.get("method", None) updated_values_dict = kwargs.get("updated_values_dict", None) if updated_values_dict is None: return # Check which clients are viewing the map for clientid in module.webserver.connected_clients.keys(): current_view = module.get_current_view(clientid) if current_view != "map": continue if method in ["upsert", "update", "edit"]: # Send location update for each changed location active_dataset = module.dom.data.get("module_game_environment", {}).get("active_dataset", None) # updated_values_dict structure at callback depth 4: # {location_identifier: {location_data}} # location_data includes "owner" field for identifier, location_dict in updated_values_dict.items(): if not isinstance(location_dict, dict): continue # Get owner directly from location_dict owner_steamid = location_dict.get("owner") if owner_steamid is None: continue location_id = f"{active_dataset}_{owner_steamid}_{identifier}" # Get full location data from DOM if fields are missing in updated_values_dict # (e.g., when only is_enabled is updated) full_location_dict = ( module.dom.data .get("module_locations", {}) .get("elements", {}) .get(active_dataset, {}) .get(owner_steamid, {}) .get(identifier, {}) ) coordinates = location_dict.get("coordinates") if coordinates is None: coordinates = full_location_dict.get("coordinates", {}) dimensions = location_dict.get("dimensions") if dimensions is None: dimensions = full_location_dict.get("dimensions", {}) location_data = { "name": location_dict.get("name", full_location_dict.get("name", "Unknown")), "identifier": identifier, "owner": owner_steamid, "shape": location_dict.get("shape", full_location_dict.get("shape", "circle")), "coordinates": { "x": float(coordinates.get("x", 0)), "y": float(coordinates.get("y", 0)), "z": float(coordinates.get("z", 0)) }, "dimensions": dimensions, "teleport_entry": location_dict.get("teleport_entry", full_location_dict.get("teleport_entry", {})), "type": location_dict.get("type", full_location_dict.get("type", [])), "is_enabled": location_dict.get("is_enabled", full_location_dict.get("is_enabled", False)) } module.webserver.send_data_to_client_hook( module, payload={ "location_id": location_id, "location": location_data }, data_type="location_update", clients=[clientid] ) elif method in ["remove"]: # Send location removal location_origin = updated_values_dict[2] owner_steamid = updated_values_dict[3] location_identifier = updated_values_dict[-1] location_id = f"{location_origin}_{owner_steamid}_{location_identifier}" module.webserver.send_data_to_client_hook( module, payload={ "location_id": location_id }, data_type="location_remove", clients=[clientid] ) widget_meta = { "description": "shows locations and stuff", "main_widget": select_view, "handlers": { # the %abc% placeholders can contain any text at all, it has no effect on anything but code-readability # the third line could just as well read # "module_locations/elements/%x%/%x%/%x%/selected_by": update_selection_status # and would still function the same as # "module_locations/elements/%map_identifier%/%steamid%/%element_identifier%/selected_by": # update_selection_status "module_locations/visibility/%steamid%/current_view": select_view, "module_locations/elements/%map_identifier%/%steamid%": table_row, "module_locations/elements/%map_identifier%/%owner_steamid%/%element_identifier%": update_location_on_map, "module_locations/elements/%map_identifier%/%steamid%/%element_identifier%/selected_by": update_selection_status, "module_locations/elements/%map_identifier%/%steamid%/%element_identifier%/is_enabled": update_enabled_flag, "module_players/elements/%map_identifier%/%steamid%/pos": update_player_location }, "enabled": True } loaded_modules_dict["module_" + module_name].register_widget(widget_name, widget_meta)