From c4dc1fb3b2979f4a8a008f364d0690bc1f228fec Mon Sep 17 00:00:00 2001 From: hrshtt Date: Thu, 21 Aug 2025 18:04:32 +0530 Subject: [PATCH 01/35] remove incorrect destroy logic --- fle/env/mods/initialise.lua | 44 ------------------------------------- 1 file changed, 44 deletions(-) diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index 7416a14e9..5ae6707f4 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -19,7 +19,6 @@ if global.debug == nil then } end - -- Note: The debug_rendering.lua library will be loaded separately by the LuaScriptManager local player = global.agent_characters[1] player.surface.always_day=true @@ -341,49 +340,6 @@ global.utils.avoid_entity = function(player_index, entity, position, direction) return false end - - ----- Define a function to be called every tick ---local function on_tick(event) --- -- Run the check every 60 ticks (1 second) --- if event.tick % 60 == 0 then --- for _, player in pairs(game.connected_players) do --- if check_player_inventory_empty(player) then --- -- Perform an action or notify the player when their inventory is empty --- player.print("Your inventory is empty!") --- end --- end --- end ---end --- ----- Register the on_tick function to the on_tick event ---script.on_event(defines.events.on_tick, on_tick) - ---script.on_nth_tick(3600, function(event) --- game.take_screenshot{ --- surface=game.surfaces[1], --- position={0,0}, --- resolution={2560, 1600}, --- zoom=0.2, --- path="timelapse/" .. string.format("%06d", event.tick/event.nth_tick) .. ".jpg", --- show_entity_info=true, --- allow_in_replay=true, --- daytime=1 --- } ---end) - - -for _, ent in pairs(surface.find_entities_filtered({force="player", position=pp, radius=50})) do - if ent.name ~= "character" then - ent.destroy() - end -end - -for key, entity in pairs(surface.find_entities_filtered({force="enemy", radius=250, position=pp })) do - cnt = cnt+1 - entity.destroy() - end - global.crafting_queue = {} script.on_event(defines.events.on_tick, function(event) From 756e3e4d1ce721720f9ffff85187de48e95efe43 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Thu, 21 Aug 2025 18:17:20 +0530 Subject: [PATCH 02/35] remove old restart logic --- fle/cluster/run-envs.sh | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/fle/cluster/run-envs.sh b/fle/cluster/run-envs.sh index c722550aa..6da6ba77c 100755 --- a/fle/cluster/run-envs.sh +++ b/fle/cluster/run-envs.sh @@ -164,30 +164,9 @@ restart_cluster() { exit 1 fi - echo "Extracting current configuration..." - - # Extract the number of instances - CURRENT_INSTANCES=$(grep -c "factorio_" docker-compose.yml) - - # Extract the scenario from the first instance - CURRENT_SCENARIO=$(grep -A1 "command:" docker-compose.yml | grep "start-server-load-scenario" | head -1 | sed -E 's/.*start-server-load-scenario ([^ ]+).*/\1/') - - if [ -z "$CURRENT_SCENARIO" ]; then - CURRENT_SCENARIO="default_lab_scenario" - echo "Warning: Could not determine current scenario, using default: $CURRENT_SCENARIO" - fi - - echo "Found cluster with $CURRENT_INSTANCES instances using scenario: $CURRENT_SCENARIO" - - # Stop the current cluster - echo "Stopping current cluster..." - $COMPOSE_CMD -f docker-compose.yml down - - # Start with the same configuration - echo "Restarting cluster..." - start_cluster "$CURRENT_INSTANCES" "$CURRENT_SCENARIO" - - echo "Factorio cluster restarted successfully." + echo "Restarting existing Factorio services without regenerating docker-compose..." + $COMPOSE_CMD -f docker-compose.yml restart + echo "Factorio services restarted." } # Show usage information From 2591c798a8dc18a130fd1ebd32cd06613d77d481 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Thu, 21 Aug 2025 18:30:59 +0530 Subject: [PATCH 03/35] force amd flag --- fle/cluster/run-envs.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fle/cluster/run-envs.sh b/fle/cluster/run-envs.sh index 6da6ba77c..14d89c633 100755 --- a/fle/cluster/run-envs.sh +++ b/fle/cluster/run-envs.sh @@ -4,7 +4,9 @@ setup_platform() { ARCH=$(uname -m) OS=$(uname -s) - if [ "$ARCH" = "arm64" ] || [ "$ARCH" = "aarch64" ]; then + if [ "$FORCE_AMD" = true ]; then + export DOCKER_PLATFORM="linux/amd64" + elif [ "$ARCH" = "arm64" ] || [ "$ARCH" = "aarch64" ]; then export DOCKER_PLATFORM="linux/arm64" else export DOCKER_PLATFORM="linux/amd64" @@ -183,6 +185,7 @@ show_help() { echo " -n NUMBER Number of Factorio instances to run (1-33, default: 1)" echo " -s SCENARIO Scenario to run (open_world or default_lab_scenario, default: default_lab_scenario)" echo " -m, --attach_mods Attach mods to the instances" + echo " -f86, --force_amd Force AMD platform" echo "" echo "Examples:" echo " $0 Start 1 instance with default_lab_scenario" @@ -200,6 +203,7 @@ SCENARIO="default_lab_scenario" # Boolean: attach mods or not ATTACH_MOD=false +FORCE_AMD=false # Parse args (supporting both short and long options) while [[ $# -gt 0 ]]; do @@ -242,6 +246,10 @@ while [[ $# -gt 0 ]]; do ATTACH_MOD=true shift ;; + -f86|--force_amd) + FORCE_AMD=true + shift + ;; -h|--help) show_help exit 0 From 7eadc552fc08784628e0e5cff45afc6b90a9ecf9 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Thu, 21 Aug 2025 18:50:02 +0530 Subject: [PATCH 04/35] run with saves --- fle/cluster/run-envs.sh | 48 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/fle/cluster/run-envs.sh b/fle/cluster/run-envs.sh index 14d89c633..6ff2cdd15 100755 --- a/fle/cluster/run-envs.sh +++ b/fle/cluster/run-envs.sh @@ -48,12 +48,40 @@ setup_compose_cmd() { generate_compose_file() { NUM_INSTANCES=${1:-1} SCENARIO=${2:-"default_lab_scenario"} + COMMAND=${3:-"--start-server-load-scenario ${SCENARIO}"} # Build optional mods volume block based on ATTACH_MOD MODS_VOLUME="" if [ "$ATTACH_MOD" = true ]; then MODS_VOLUME=$(printf " - source: %s\n target: /opt/factorio/mods\n type: bind\n" "$MODS_PATH") fi + + # Build optional save file volume block based on SAVE_ADDED + SAVE_VOLUME="" + if [ "$SAVE_ADDED" = true ]; then + # Check if SAVE_FILE is a .zip file + if [[ "$SAVE_FILE" != *.zip ]]; then + echo "Error: Save file must be a .zip file." + exit 1 + fi + + # Create saves directory if it doesn't exist + mkdir -p ../../.fle/saves + + # Get the save file name (basename) + SAVE_FILE_NAME=$(basename "$SAVE_FILE") + + # Copy the save file to the local saves directory + cp "$SAVE_FILE" "../../.fle/saves/$SAVE_FILE_NAME" + + # Create variable for the container path + CONTAINER_SAVE_PATH="/opt/factorio/saves/$SAVE_FILE_NAME" + + SAVE_VOLUME=" - source: ../../.fle/saves + target: /opt/factorio/saves + type: bind" + COMMAND="--start-server ${SAVE_FILE_NAME}" + fi # Validate scenario if [ "$SCENARIO" != "open_world" ] && [ "$SCENARIO" != "default_lab_scenario" ]; then @@ -88,7 +116,7 @@ EOF factorio_${i}: image: factoriotools/factorio:1.1.110 platform: \${DOCKER_PLATFORM:-linux/amd64} - command: /opt/factorio/bin/x64/factorio --start-server-load-scenario ${SCENARIO} + command: /opt/factorio/bin/x64/factorio ${COMMAND} --port 34197 --server-settings /opt/factorio/config/server-settings.json --map-gen-settings /opt/factorio/config/map-gen-settings.json --map-settings /opt/factorio/config/map-settings.json --server-banlist /opt/factorio/config/server-banlist.json --rcon-port 27015 @@ -117,6 +145,7 @@ EOF - source: ../../.fle/data/_screenshots target: /opt/factorio/script-output type: bind +${SAVE_VOLUME} ${MODS_VOLUME} EOF done @@ -184,6 +213,7 @@ show_help() { echo "Options:" echo " -n NUMBER Number of Factorio instances to run (1-33, default: 1)" echo " -s SCENARIO Scenario to run (open_world or default_lab_scenario, default: default_lab_scenario)" + echo " -sv SAVE_FILE, --use_save SAVE_FILE Use a .zip save file from factorio" echo " -m, --attach_mods Attach mods to the instances" echo " -f86, --force_amd Force AMD platform" echo "" @@ -200,10 +230,12 @@ show_help() { COMMAND="start" NUM_INSTANCES=1 SCENARIO="default_lab_scenario" +SAVE_FILE="" # Boolean: attach mods or not ATTACH_MOD=false FORCE_AMD=false +SAVE_ADDED=false # Parse args (supporting both short and long options) while [[ $# -gt 0 ]]; do @@ -242,6 +274,20 @@ while [[ $# -gt 0 ]]; do esac shift 2 ;; + -sv|--use_save) + if [[ -z "$2" || "$2" == -* ]]; then + echo "Error: -sv|--use_save requires an argument." + show_help + exit 1 + fi + if [[ ! -f "$2" ]]; then + echo "Error: Save file '$2' does not exist." + exit 1 + fi + SAVE_FILE="$2" + SAVE_ADDED=true + shift 2 + ;; -m|--attach_mods) ATTACH_MOD=true shift From c9868f2012e08eaf2c6b3d15d24736184dd3eda3 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 02:56:32 +0530 Subject: [PATCH 05/35] clean_instance --- fle/commons/models/game_state.py | 2 +- fle/env/instance.py | 342 +----------------- fle/env/mods/clear_inventory.lua | 2 - fle/env/mods/initialise.lua | 38 +- fle/env/mods/priority_queue.lua | 61 ---- fle/env/mods/reset.lua | 1 - fle/env/mods/reset_position.lua | 2 - .../admin/create_agent_characters/client.py | 13 + .../admin/create_agent_characters/server.lua | 64 ++++ fle/env/tools/admin/reset/client.py | 38 ++ fle/env/tools/admin/reset/server.lua | 60 +++ fle/env/tools/admin/set_inventory/client.py | 18 + .../admin/set_inventory/server.lua} | 6 +- tests/actions/test_craft.py | 2 +- tests/actions/test_harvest_resource.py | 6 +- tests/actions/test_pickup_entity.py | 4 +- tests/conftest.py | 2 +- tests/entities/test_rockets.py | 6 +- 18 files changed, 240 insertions(+), 427 deletions(-) delete mode 100644 fle/env/mods/clear_inventory.lua delete mode 100644 fle/env/mods/priority_queue.lua delete mode 100644 fle/env/mods/reset.lua delete mode 100644 fle/env/mods/reset_position.lua create mode 100644 fle/env/tools/admin/create_agent_characters/client.py create mode 100644 fle/env/tools/admin/create_agent_characters/server.lua create mode 100644 fle/env/tools/admin/reset/client.py create mode 100644 fle/env/tools/admin/reset/server.lua create mode 100644 fle/env/tools/admin/set_inventory/client.py rename fle/env/{mods/initialise_inventory.lua => tools/admin/set_inventory/server.lua} (61%) diff --git a/fle/commons/models/game_state.py b/fle/commons/models/game_state.py index 52db013a2..69f383e3a 100644 --- a/fle/commons/models/game_state.py +++ b/fle/commons/models/game_state.py @@ -202,7 +202,7 @@ def to_instance(self, instance): # Set inventory for each player if self.inventories: for i in range(instance.num_agents): - instance.set_inventory(self.inventories[i], i) + instance.first_namespace._set_inventory(i+1, self.inventories[i]) # Restore research state if present (only need to do this once) if self.research: # Only do this for the first instance diff --git a/fle/env/instance.py b/fle/env/instance.py index 3c6e76c28..403f20c3a 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -70,23 +70,6 @@ def to_factorio_direction(cls, direction): def from_factorio_direction(cls, direction): return direction.value * 2 - -class FactorioTransaction: - def __init__(self): - self.commands: List[ - Tuple[str, List[Any], bool] - ] = [] # (command, parameters, is_raw) - - def add_command(self, command: str, *parameters, raw=False): - self.commands.append((command, list(parameters), raw)) - - def clear(self): - self.commands.clear() - - def get_commands(self): - return self.commands - - class FactorioInstance: namespace_class = FactorioNamespace _cleanup_registered = False # Only register cleanup once per process @@ -189,7 +172,7 @@ def reset( if not game_state: # Reset the game instance inventories = [self.initial_inventory] * self.num_agents - self._reset(inventories, reset_position, all_technologies_researched) + self.first_namespace._reset(inventories, reset_position, all_technologies_researched) # Reset the technologies if not all_technologies_researched: self.first_namespace._load_research_state( @@ -203,7 +186,7 @@ def reset( ) else: # Reset the game instance with the correct player's inventory and messages if multiagent - self._reset( + self.first_namespace._reset( game_state.inventories, reset_position, all_technologies_researched, @@ -232,29 +215,6 @@ def reset( except Exception: self.initial_score = 0 - # Clear renderings - self.begin_transaction() - self.add_command("/sc global.elapsed_ticks = 0", raw=True) - self.add_command("/sc rendering.clear()", raw=True) - self.execute_transaction() - - def set_inventory(self, inventory: Dict[str, Any], agent_idx: int = 0): - self.begin_transaction() - self.add_command("clear_inventory", agent_idx + 1) - self.execute_transaction() - # print("RCON output:", result) - self.begin_transaction() - # kwargs dict to json - inventory_items = {k: v for k, v in inventory.items()} - inventory_items_json = json.dumps(inventory_items) - player_idx = agent_idx + 1 - self.add_command( - f"/sc global.actions.initialise_inventory({player_idx}, '{inventory_items_json}')", - raw=True, - ) - - self.execute_transaction() - def set_speed(self, speed): self.rcon_client.send_command(f"/sc game.speed = {speed}") self._speed = speed @@ -435,289 +395,18 @@ def eval(self, expr, agent_idx=0, timeout=60): message = e.args[0].replace("\\n", "") return -1, "", f"{message}".strip() - def _get_command(self, command, parameters=[], measured=True): - prefix = "/sc " if not measured else "/command " - if command in self.script_dict: - script = prefix + self.script_dict[command] - for index in range(len(parameters)): - script = script.replace( - f"arg{index + 1}", lua.encode(parameters[index]) - ) - else: - script = command - return script - - def calculate_optimal_zoom(self, bounds: BoundingBox, resolution="1920x1080"): - """ - Calculate the optimal zoom level to fit the factory in the screenshot. - - Args: - bounds (BoundingBox): Factory bounds containing width and height - resolution (str): Screenshot resolution in format "WIDTHxHEIGHT" - - Returns: - float: Optimal zoom level - """ - if not bounds: - return 1 - - # Parse resolution - width, height = map(int, resolution.split("x")) - aspect_ratio = width / height - - # Get factory dimensions - factory_width = bounds.width() - factory_height = bounds.height() - - # Base tiles visible at zoom level 1 - # These values are approximate for Factorio's zoom levels - BASE_VISIBLE_HEIGHT = 25 # tiles visible vertically at zoom 1 - BASE_VISIBLE_WIDTH = BASE_VISIBLE_HEIGHT * aspect_ratio - - # Calculate required zoom based on both dimensions - zoom_by_width = BASE_VISIBLE_WIDTH / factory_width - zoom_by_height = BASE_VISIBLE_HEIGHT / factory_height - - # Use the smaller zoom to ensure entire factory is visible - optimal_zoom = min(zoom_by_width, zoom_by_height) - - # Add padding (20% margin) - optimal_zoom *= 0.8 - - # Clamp zoom to reasonable values - # Factorio's min and max zoom levels - MIN_ZOOM = 0.1 - MAX_ZOOM = 4.0 - - optimal_zoom = max(MIN_ZOOM, min(MAX_ZOOM, optimal_zoom)) - - return round(optimal_zoom, 2) - - def screenshot( - self, - script_output_path, - resolution="1920x1080", - save_path=None, - zoom=None, - center_on_factory=False, - ): - """ - Take a screenshot in game and optionally save it to a specific location. - - This does nothing in headless mode. - - Args: - resolution (str, optional): Screenshot resolution (e.g., "1920x1080") - save_path (str, optional): Path where to save the screenshot copy - zoom (float, optional): Zoom level for the screenshot (e.g., 0.5 for zoomed out, 2.0 for zoomed in) - - Returns: - str: Path to the saved screenshot, or None if failed - """ - # Clear rendering - camera: Camera = self.first_namespace._get_factory_centroid() - POS_STRING = "" - if camera: - centroid = camera.position - POS_STRING = ( - ", position={x=" + str(centroid.x) + ", y=" + str(centroid.y) + "}" - ) - - self.rcon_client.send_command("/sc rendering.clear()") - - # # Calculate optimal zoom if not specified - # if zoom is None: - # zoom = self.calculate_optimal_zoom(bounds, resolution) - - command = ( - "/sc game.take_screenshot({player=1, zoom=" - + str(camera.zoom) - + ", show_entity_info=true, hide_clouds=true, hide_fog=true " - + POS_STRING - + "})" - ) - self.rcon_client.send_command(command) - time.sleep(1) - # if not response: - # return None - - # Wait for the screenshot file to appear and get its path - screenshot_path = self._get_latest_screenshot( - script_output_path=script_output_path - ) - if not screenshot_path: - print("Screenshot file not found") - return None - - # If save_path is provided, copy the screenshot there - if save_path: - try: - # Create directory if it doesn't exist - os.makedirs(os.path.dirname(os.path.abspath(save_path)), exist_ok=True) - - # Copy the file - shutil.copy2(screenshot_path, save_path) - return save_path - except Exception as e: - print(f"Failed to copy screenshot: {e}") - return screenshot_path - - return screenshot_path - - def _get_latest_screenshot(self, script_output_path, max_wait=2): - """ - Get the path to the latest screenshot in the script-output directory. - Waits up to max_wait seconds for the file to appear. - """ - start_time = time.time() - while time.time() - start_time < max_wait: - try: - # Get list of screenshot files - screenshots = [ - f - for f in os.listdir(script_output_path) - if f.endswith(".png") and f.startswith("screenshot") - ] - - if screenshots: - # Sort by modification time to get the latest - latest = max( - screenshots, - key=lambda x: os.path.getmtime( - os.path.join(script_output_path, x) - ), - ) - return os.path.join(script_output_path, latest) - except Exception as e: - print(f"Error checking for screenshots: {e}") - - time.sleep(0.5) # Wait before checking again - - return None - - def _send(self, command, *parameters, trace=False) -> List[str]: - """ - Send a Lua command to the underlying Factorio instance - """ - start = timer() - script = self._get_command(command, parameters=list(parameters), measured=False) - lua_response = self.rcon_client.send_command(script) - # self.add_command(command, *parameters) - # response = self._execute_transaction() - # print(lua_response) - return _lua2python(command, lua_response, start=start) - - def _reset( - self, - inventories: List[Dict[str, Any]], - reset_position: bool, - all_technologies_researched: bool, - ): - self.begin_transaction() - self.add_command( - "/sc global.alerts = {}; game.reset_game_state(); global.actions.reset_production_stats();", - raw=True, - ) - # self.add_command('/sc script.on_nth_tick(nil)', raw=True) # Remove all dangling event handlers - for i in range(self.num_agents): - player_index = i + 1 - self.add_command( - f"/sc global.actions.regenerate_resources({player_index})", raw=True - ) - # self.add_command('clear_inventory', player_index) - - self.execute_transaction() - - self.begin_transaction() - self.add_command("/sc global.actions.clear_walking_queue()", raw=True) - for i in range(self.num_agents): - player_index = i + 1 - if reset_position: - # Ensure players are returned to a known spawn location between tests - self.add_command( - f"/sc if global.agent_characters and global.agent_characters[{player_index}] then global.agent_characters[{player_index}].teleport{{x=0, y={(i) * 2}}} end", - raw=True, - ) - self.add_command( - f"/sc global.actions.clear_entities({player_index})", raw=True - ) - inventory_items = {k: v for k, v in inventories[i].items()} - inventory_items_json = json.dumps(inventory_items) - self.add_command( - f"/sc global.actions.initialise_inventory({player_index}, '{inventory_items_json}')", - raw=True, - ) - - if all_technologies_researched: - self.add_command( - "/sc global.agent_characters[1].force.research_all_technologies()", - raw=True, - ) - else: - self.add_command("/sc global.agent_characters[1].force.reset()", raw=True) - self.add_command("/sc global.elapsed_ticks = 0", raw=True) - self.execute_transaction() - - def _execute_transaction(self) -> Dict[str, Any]: - start = timer() - rcon_commands = {} - for idx, (command, parameters, is_raw) in enumerate( - self.current_transaction.get_commands() - ): - if is_raw: - rcon_commands[f"{idx}_{command}"] = command - else: - script = self._get_command( - command, parameters=parameters, measured=False - ) - rcon_commands[f"{idx}_{command}"] = script - - lua_responses = self.rcon_client.send_commands(rcon_commands) - - results = {} - for command, response in lua_responses.items(): - results[command] = _lua2python(command, response, start=start) - - self.current_transaction.clear() - return results - - def begin_transaction(self): - if not hasattr(self, "current_transaction"): - self.current_transaction = FactorioTransaction() - elif self.current_transaction: - self.current_transaction.clear() - else: - self.current_transaction = FactorioTransaction() - - def add_command(self, command: str, *parameters, raw=False): - if not hasattr(self, "current_transaction"): - self.begin_transaction() - self.current_transaction.add_command(command, *parameters, raw=raw) - - def execute_transaction(self) -> Dict[str, Any]: - return self._execute_transaction() - def initialise(self, fast=True, all_technologies_researched=True): - self.begin_transaction() - self.add_command("/sc global.alerts = {}", raw=True) - self.add_command("/sc global.elapsed_ticks = 0", raw=True) - self.add_command( - "/sc global.fast = {}".format("true" if fast else "false"), raw=True + self.rcon_client.send_command( + f"/sc global.fast = {str(fast).lower()}" ) - self.execute_transaction() - - # Create characters for all agents - self._create_agent_game_characters() + self.first_namespace._create_agent_characters(self.num_agents) init_scripts = [ - "initialise", "alerts", "util", - "priority_queue", "connection_points", "recipe_fluid_connection_mappings", "serialize", - "initialise_inventory", ] if self.peaceful: init_scripts.append("enemies") @@ -725,28 +414,14 @@ def initialise(self, fast=True, all_technologies_researched=True): self.lua_script_manager.load_init_into_game(script_name) inventories = [self.initial_inventory] * self.num_agents - self._reset( + + self.first_namespace._reset( inventories, reset_position=False, all_technologies_researched=all_technologies_researched, ) self.first_namespace._clear_collision_boxes() - def _create_agent_game_characters(self): - """Create Factorio characters for all agents in the game.""" - # Create characters in Factorio - self.begin_transaction() - color_logic = "" - if self.num_agents > 1: - color_logic = "if i==1 then char.color={r=0,g=1,b=0,a=1} elseif i==2 then char.color={r=0,g=0,b=1,a=1} end;" - - self.add_command( - f'/sc global.agent_characters = {{}}; for _,c in pairs(game.surfaces[1].find_entities_filtered{{type="character"}}) do if c then c.destroy() end end; for i=1,{self.num_agents} do local char = game.surfaces[1].create_entity{{name="character",position={{x=0,y=(i-1)*2}},force=game.forces.player}}; {color_logic} global.agent_characters[i]=char end', - raw=True, - ) - self.add_command("/sc player = global.agent_characters[1]", raw=True) - self.execute_transaction() - def get_warnings(self, seconds=10): """ Get all alerts that have been raised before the last n seconds @@ -754,8 +429,7 @@ def get_warnings(self, seconds=10): :return: """ start = timer() - command = f"/silent-command rcon.print(dump(global.get_alerts({seconds})))" - lua_response = self.rcon_client.send_command(command) + lua_response = self.rcon_client.send_command(f"/sc rcon.print(dump(global.get_alerts({seconds})))") # print(lua_response) alert_dict, duration = _lua2python("alerts", lua_response, start=start) if isinstance(alert_dict, dict): diff --git a/fle/env/mods/clear_inventory.lua b/fle/env/mods/clear_inventory.lua deleted file mode 100644 index ed66682d1..000000000 --- a/fle/env/mods/clear_inventory.lua +++ /dev/null @@ -1,2 +0,0 @@ -global.agent_characters[arg1].clear_items_inside() -rcon.print(1) \ No newline at end of file diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index 5ae6707f4..59f354789 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -4,6 +4,7 @@ if not global.actions then global.actions = {} end + if not global.utils then global.utils = {} end @@ -12,6 +13,22 @@ if not global.initial_score then global.initial_score = {["player"] = 0} end +if not global.alerts then + global.alerts = {} +end + +if not global.elapsed_ticks then + global.elapsed_ticks = 0 +end + +if not global.fast then + global.fast = false +end + +if not global.agent_characters then + global.agent_characters = {} +end + -- Initialize debug flags if global.debug == nil then global.debug = { @@ -19,18 +36,17 @@ if global.debug == nil then } end --- Note: The debug_rendering.lua library will be loaded separately by the LuaScriptManager -local player = global.agent_characters[1] -player.surface.always_day=true ---game.players[1].character_collision_mask = "not-colliding-with-itself" -player.force.character_build_distance_bonus = 100 -player.force.research_all_technologies() +-- -- Note: The debug_rendering.lua library will be loaded separately by the LuaScriptManager +-- local player = global.agent_characters[1] +-- player.surface.always_day=true +-- player.force.character_build_distance_bonus = 100 +-- player.force.research_all_technologies() -local beam_duration = 9 -local surface=player.surface -local pp = player.position -local cnt = 0 +-- local beam_duration = 9 +-- local surface=player.surface +-- local pp = player.position +-- local cnt = 0 local directions = {'north', 'northeast', 'east', 'southeast', 'south', 'southwest', 'west', 'northwest'} --Initialise.lua @@ -1121,4 +1137,4 @@ function required_resource_present(entity, position, surface) end end -rcon.print(1) \ No newline at end of file +-- rcon.print(1) \ No newline at end of file diff --git a/fle/env/mods/priority_queue.lua b/fle/env/mods/priority_queue.lua deleted file mode 100644 index c87ad1d60..000000000 --- a/fle/env/mods/priority_queue.lua +++ /dev/null @@ -1,61 +0,0 @@ -local PriorityQueue = {} -PriorityQueue.__index = PriorityQueue - --- Create a new PriorityQueue. -function PriorityQueue.new(comparator) - return setmetatable({queue = {}, comparator = comparator or function(a, b) return a < b end}, PriorityQueue) -end - --- Insert a new element into the queue. -function PriorityQueue:insert(element) - -- Insert the element at the end of the queue. - table.insert(self.queue, element) - - -- Bubble up the element to its correct place. - local i = #self.queue - while i > 1 do - local parent = math.floor(i / 2) - if not self.comparator(self.queue[i], self.queue[parent]) then break end - self.queue[i], self.queue[parent] = self.queue[parent], self.queue[i] - i = parent - end -end - --- Remove and return the top element of the queue. -function PriorityQueue:pop() - if #self.queue == 0 then error("Queue is empty") end - - local top = self.queue[1] - - -- Replace the root with the last element in the queue. - self.queue[1] = self.queue[#self.queue] - table.remove(self.queue) - - -- Bubble down the root element to its correct place. - local i = 1 - while true do - local left = 2 * i - local right = left + 1 - local smallest = i - - if left <= #self.queue and self.comparator(self.queue[left], self.queue[smallest]) then - smallest = left - end - if right <= #self.queue and self.comparator(self.queue[right], self.queue[smallest]) then - smallest = right - end - if smallest == i then break end - - self.queue[i], self.queue[smallest] = self.queue[smallest], self.queue[i] - i = smallest - end - - return top -end - --- Check if the queue is empty. -function PriorityQueue:is_empty() - return #self.queue == 0 -end - -return PriorityQueue diff --git a/fle/env/mods/reset.lua b/fle/env/mods/reset.lua deleted file mode 100644 index 9667a0d21..000000000 --- a/fle/env/mods/reset.lua +++ /dev/null @@ -1 +0,0 @@ -game.reset_game_state() \ No newline at end of file diff --git a/fle/env/mods/reset_position.lua b/fle/env/mods/reset_position.lua deleted file mode 100644 index 9b91c04a2..000000000 --- a/fle/env/mods/reset_position.lua +++ /dev/null @@ -1,2 +0,0 @@ -global.agent_characters[arg1].teleport({arg2, arg3}) -rcon.print(1) \ No newline at end of file diff --git a/fle/env/tools/admin/create_agent_characters/client.py b/fle/env/tools/admin/create_agent_characters/client.py new file mode 100644 index 000000000..23271be00 --- /dev/null +++ b/fle/env/tools/admin/create_agent_characters/client.py @@ -0,0 +1,13 @@ +from fle.env.tools import Tool + + +class CreateAgentCharacters(Tool): + def __init__(self, connection, game_state): + super().__init__(connection, game_state) + + def __call__(self, num_agents: int) -> bool: + """ + Creates an agent character + """ + response, elapsed = self.execute(num_agents) + return True diff --git a/fle/env/tools/admin/create_agent_characters/server.lua b/fle/env/tools/admin/create_agent_characters/server.lua new file mode 100644 index 000000000..6d16ac369 --- /dev/null +++ b/fle/env/tools/admin/create_agent_characters/server.lua @@ -0,0 +1,64 @@ + +-- Function to convert HSV to RGB +local function hsv_to_rgb(h, s, v) + local r, g, b + local i = math.floor(h * 6) + local f = h * 6 - i + local p = v * (1 - s) + local q = v * (1 - f * s) + local t = v * (1 - (1 - f) * s) + + i = i % 6 + + if i == 0 then r, g, b = v, t, p + elseif i == 1 then r, g, b = q, v, p + elseif i == 2 then r, g, b = p, v, t + elseif i == 3 then r, g, b = p, q, v + elseif i == 4 then r, g, b = t, p, v + elseif i == 5 then r, g, b = v, p, q + end + + return {r = r, g = g, b = b, a = 1.0} +end + +-- Function to generate a color based on the agent index +local function generate_agent_color(index, total_agents) + local hue = (index - 1) / total_agents + local saturation = 1.0 + local value = 1.0 + return hsv_to_rgb(hue, saturation, value) +end + +-- Create agent characters script +global.actions.create_agent_characters = function(num_agents) + -- Initialize agent characters table + -- Destroy existing agent characters if they exist + if global.agent_characters then + for _, char in pairs(global.agent_characters) do + if char and char.valid then + char.destroy() + end + end + global.agent_characters = {} + end + + -- Create new characters for each agent + for i = 1, num_agents do + local char = game.surfaces[1].create_entity{ + name = "character", + position = {x = 0, y = (i - 1) * 2}, + force = game.forces.player + } + + -- Set colors for multi-agent scenarios + if num_agents > 1 then + char.color = generate_agent_color(i, num_agents) + end + + global.agent_characters[i] = char + end + + -- Set the first character as the main player + player = global.agent_characters[1] + player.surface.always_day=true +end \ No newline at end of file diff --git a/fle/env/tools/admin/reset/client.py b/fle/env/tools/admin/reset/client.py new file mode 100644 index 000000000..0c10b8a5c --- /dev/null +++ b/fle/env/tools/admin/reset/client.py @@ -0,0 +1,38 @@ +from fle.env.tools import Tool +import json + + +class Reset(Tool): + def __init__(self, connection, game_state): + super().__init__(connection, game_state) + + def __call__( + self, + inventories=None, + reset_position=False, + all_technologies_researched=True, + ): + """ + Reset the Factorio game state via Lua action, mirroring FactorioInstance.reset/_reset. + + Args: + inventories (list[dict]|dict|None): Either a list indexed by agent (1-based in Lua) + or a dict keyed by agent index (int or str) mapping to {item_name: count}. + reset_position (bool): If True, teleport agents to spawn offsets. + all_technologies_researched (bool): If True, research all technologies; else reset force. + """ + if inventories is None: + inventories = {} + # Encode to JSON string for Lua + inventories_json = json.dumps(inventories) + + reset_position = str(reset_position).lower() + all_technologies_researched = str(all_technologies_researched).lower() + + response, _ = self.execute( + inventories_json, + reset_position, + all_technologies_researched, + ) + return response + diff --git a/fle/env/tools/admin/reset/server.lua b/fle/env/tools/admin/reset/server.lua new file mode 100644 index 000000000..cc9ec3604 --- /dev/null +++ b/fle/env/tools/admin/reset/server.lua @@ -0,0 +1,60 @@ +local function safe_json_to_table(json) + if not json or json == '' then return {} end + local ok, result = pcall(function() + return game.json_to_table(json) + end) + if ok and type(result) == 'table' then return result end + return {} +end + +local function get_inventory_for_index(inventories, index) + if type(inventories) ~= 'table' then return {} end + -- Support both array-style and map-style inputs + local inv = inventories[index] + if inv == nil then + inv = inventories[tostring(index)] + end + if type(inv) ~= 'table' then return {} end + return inv +end + +global.actions.reset = function(inventories_json, reset_position, all_technologies_researched) + + -- Clear alerts, reset game state, and production stats + game.reset_game_state() + global.alerts = {} + global.actions.reset_production_stats() + global.elapsed_ticks = 0 + + local inventories = safe_json_to_table(inventories_json) + + -- Re-generate resources per agent (mirrors instance _reset) + if global.agent_characters then + for i, character in pairs(global.agent_characters) do + global.actions.regenerate_resources(i) + global.actions.clear_walking_queue(i) + + if reset_position and character and character.valid then + local y_offset = (tonumber(i) or 1) - 1 + character.teleport{ x = 0, y = y_offset * 2 } + end + + -- Clear entities around each agent and reset inventories + global.actions.clear_entities(i) + + local inv_table = get_inventory_for_index(inventories, i) + local inv_json = game.table_to_json(inv_table) + global.actions.set_inventory(i, inv_json) + end + end + + -- Research handling + if all_technologies_researched == true then + global.agent_characters[1].force.research_all_technologies() + else + global.agent_characters[1].force.reset() + end + + return 1 +end + diff --git a/fle/env/tools/admin/set_inventory/client.py b/fle/env/tools/admin/set_inventory/client.py new file mode 100644 index 000000000..fc31404fd --- /dev/null +++ b/fle/env/tools/admin/set_inventory/client.py @@ -0,0 +1,18 @@ +from fle.env.tools import Tool +from typing import Dict, Any +from slpp import slpp as lua +import json + + + +class SetInventory(Tool): + def __init__(self, connection, game_state): + super().__init__(connection, game_state) + + def __call__(self, player_index: int, inventory: Dict[str, Any]) -> bool: + """ + Sets the inventory for an agent character + """ + inventory_json = json.dumps(inventory) + response, elapsed = self.execute(player_index, inventory_json) + return True diff --git a/fle/env/mods/initialise_inventory.lua b/fle/env/tools/admin/set_inventory/server.lua similarity index 61% rename from fle/env/mods/initialise_inventory.lua rename to fle/env/tools/admin/set_inventory/server.lua index e1c280ad9..174c9fd46 100644 --- a/fle/env/mods/initialise_inventory.lua +++ b/fle/env/tools/admin/set_inventory/server.lua @@ -1,6 +1,8 @@ -global.actions.initialise_inventory = function(player_index, item_names_and_counts_json) +global.actions.set_inventory = function(player_index, item_names_and_counts_json) local player = global.agent_characters[player_index] - local item_names_and_counts = game.json_to_table(item_names_and_counts_json) + player.clear_items_inside() + local item_names_and_counts = game.json_to_table(item_names_and_counts_json) or {} + -- Avoid logging raw tables; convert toN for readability and safety -- Loop through the entity names and insert them into the player's inventory for item, count in pairs(item_names_and_counts) do diff --git a/tests/actions/test_craft.py b/tests/actions/test_craft.py index b3a4fd478..a9ef391ef 100644 --- a/tests/actions/test_craft.py +++ b/tests/actions/test_craft.py @@ -33,7 +33,7 @@ def test_craft_with_full_inventory(game): """ Test crafting when inventory is full """ - game.instance.set_inventory({"iron-plate": 100, "coal": 10000}) + game._set_inventory(1, {"iron-plate": 100, "coal": 10000}) try: result = game.craft_item(Prototype.IronGearWheel, 1) assert False, ( diff --git a/tests/actions/test_harvest_resource.py b/tests/actions/test_harvest_resource.py index 7b46c0174..482e72ce2 100644 --- a/tests/actions/test_harvest_resource.py +++ b/tests/actions/test_harvest_resource.py @@ -69,8 +69,7 @@ def test_harvest_resource(game): def test_harvest_stump(game): instance = game.instance - instance.add_command(f"/c {create_stump}", raw=True) - instance.execute_transaction() + instance.rcon_client.send_command(f"/c {create_stump}") harvested = game.harvest_resource(Position(x=0, y=0), quantity=1) assert harvested == 2 @@ -87,8 +86,7 @@ def test_harvest_stump(game): def test_harvest_rock(game): instance = game.instance - instance.add_command(f"/c {create_rock}", raw=True) - instance.execute_transaction() + instance.rcon_client.send_command(f"/c {create_rock}") harvested = game.harvest_resource(Position(x=0, y=0), quantity=1) assert harvested == 20 diff --git a/tests/actions/test_pickup_entity.py b/tests/actions/test_pickup_entity.py index e5515af18..e3b9df3c0 100644 --- a/tests/actions/test_pickup_entity.py +++ b/tests/actions/test_pickup_entity.py @@ -32,11 +32,11 @@ def test_pickup_item_full_inventory(game): Uses existing inventory items but maximizes stacks to test true full inventory. """ # Clear inventory completely first - game.instance.set_inventory({"wooden-chest": 1}) + game._set_inventory(1, {"wooden-chest": 1}) placement_position = Position(x=0, y=0) game.move_to(placement_position) chest = game.place_entity(Prototype.WoodenChest, position=placement_position) - game.instance.set_inventory({"coal": 10000}) + game._set_inventory(1, {"coal": 10000}) try: result = game.pickup_entity(chest) assert False, ( diff --git a/tests/conftest.py b/tests/conftest.py index cbf67a56c..cd7d6b6b3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -184,7 +184,7 @@ def _configure_game( updated = dict(inventory) if persist_inventory: instance.initial_inventory = updated - instance.set_inventory(updated) + instance.first_namespace._set_inventory(1, updated) return instance.namespace diff --git a/tests/entities/test_rockets.py b/tests/entities/test_rockets.py index 5b3032beb..471709d4e 100644 --- a/tests/entities/test_rockets.py +++ b/tests/entities/test_rockets.py @@ -146,11 +146,7 @@ def test_rocket_launch(game): "low-density-structure": 112, } inventory_items_json = json.dumps(inventory_items) - game.instance.add_command( - f"/c global.actions.initialise_inventory({1}, '{inventory_items_json}')", - raw=True, - ) - game.instance.execute_transaction() + game._set_inventory(1, inventory_items) # Verify initial state # assert silo.status == EntityStatus.NORMAL From 330d5c900fcd0240aab1fe4c7f3eda338101aef4 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 03:08:56 +0530 Subject: [PATCH 06/35] clear_entities flag --- fle/env/instance.py | 12 ++++++++---- fle/env/tools/admin/reset/client.py | 3 +++ fle/env/tools/admin/reset/server.lua | 6 ++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/fle/env/instance.py b/fle/env/instance.py index 403f20c3a..3d01bd405 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -82,6 +82,7 @@ def __init__( inventory=None, cache_scripts=True, all_technologies_researched=True, + clear_entities=True, peaceful=True, num_agents=1, **kwargs, @@ -116,7 +117,7 @@ def __init__( if inventory is None: inventory = {} self.initial_inventory = inventory - self.initialise(fast, all_technologies_researched) + self.initialise(fast, all_technologies_researched, clear_entities) self.initial_score = 0 try: self.first_namespace.score() @@ -130,7 +131,7 @@ def __init__( **self.lua_script_manager.tool_scripts, } self.setup_tools(self.lua_script_manager) - self.initialise(fast, all_technologies_researched) + self.initialise(fast, all_technologies_researched, clear_entities) self.initial_score, goal = self.first_namespace.score() # Register the cleanup method to be called on exit (only once per process) @@ -160,6 +161,7 @@ def reset( game_state: Optional[GameState] = None, reset_position: bool = False, all_technologies_researched: bool = True, + clear_entities: bool = True, ): # Reset the namespace (clear variables, functions etc) assert not game_state or len(game_state.inventories) == self.num_agents, ( @@ -172,7 +174,7 @@ def reset( if not game_state: # Reset the game instance inventories = [self.initial_inventory] * self.num_agents - self.first_namespace._reset(inventories, reset_position, all_technologies_researched) + self.first_namespace._reset(inventories, reset_position, all_technologies_researched, clear_entities) # Reset the technologies if not all_technologies_researched: self.first_namespace._load_research_state( @@ -190,6 +192,7 @@ def reset( game_state.inventories, reset_position, all_technologies_researched, + clear_entities, ) # Load entities into the game @@ -395,7 +398,7 @@ def eval(self, expr, agent_idx=0, timeout=60): message = e.args[0].replace("\\n", "") return -1, "", f"{message}".strip() - def initialise(self, fast=True, all_technologies_researched=True): + def initialise(self, fast=True, all_technologies_researched=True, clear_entities=True): self.rcon_client.send_command( f"/sc global.fast = {str(fast).lower()}" ) @@ -419,6 +422,7 @@ def initialise(self, fast=True, all_technologies_researched=True): inventories, reset_position=False, all_technologies_researched=all_technologies_researched, + clear_entities=clear_entities, ) self.first_namespace._clear_collision_boxes() diff --git a/fle/env/tools/admin/reset/client.py b/fle/env/tools/admin/reset/client.py index 0c10b8a5c..e3afdd8c8 100644 --- a/fle/env/tools/admin/reset/client.py +++ b/fle/env/tools/admin/reset/client.py @@ -11,6 +11,7 @@ def __call__( inventories=None, reset_position=False, all_technologies_researched=True, + clear_entities=True, ): """ Reset the Factorio game state via Lua action, mirroring FactorioInstance.reset/_reset. @@ -28,11 +29,13 @@ def __call__( reset_position = str(reset_position).lower() all_technologies_researched = str(all_technologies_researched).lower() + clear_entities = str(clear_entities).lower() response, _ = self.execute( inventories_json, reset_position, all_technologies_researched, + clear_entities, ) return response diff --git a/fle/env/tools/admin/reset/server.lua b/fle/env/tools/admin/reset/server.lua index cc9ec3604..5f442f31e 100644 --- a/fle/env/tools/admin/reset/server.lua +++ b/fle/env/tools/admin/reset/server.lua @@ -18,7 +18,7 @@ local function get_inventory_for_index(inventories, index) return inv end -global.actions.reset = function(inventories_json, reset_position, all_technologies_researched) +global.actions.reset = function(inventories_json, reset_position, all_technologies_researched, clear_entities) -- Clear alerts, reset game state, and production stats game.reset_game_state() @@ -40,7 +40,9 @@ global.actions.reset = function(inventories_json, reset_position, all_technologi end -- Clear entities around each agent and reset inventories - global.actions.clear_entities(i) + if clear_entities then + global.actions.clear_entities(i) + end local inv_table = get_inventory_for_index(inventories, i) local inv_json = game.table_to_json(inv_table) From 5dd3e8bfdd856152a1aa7ed2376060577069f8e2 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 03:09:42 +0530 Subject: [PATCH 07/35] formatting --- fle/env/instance.py | 17 ++++-- fle/env/tools/admin/reset/client.py | 63 ++++++++++----------- fle/env/tools/admin/set_inventory/client.py | 1 - 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/fle/env/instance.py b/fle/env/instance.py index 3d01bd405..7467133e2 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -70,6 +70,7 @@ def to_factorio_direction(cls, direction): def from_factorio_direction(cls, direction): return direction.value * 2 + class FactorioInstance: namespace_class = FactorioNamespace _cleanup_registered = False # Only register cleanup once per process @@ -174,7 +175,9 @@ def reset( if not game_state: # Reset the game instance inventories = [self.initial_inventory] * self.num_agents - self.first_namespace._reset(inventories, reset_position, all_technologies_researched, clear_entities) + self.first_namespace._reset( + inventories, reset_position, all_technologies_researched, clear_entities + ) # Reset the technologies if not all_technologies_researched: self.first_namespace._load_research_state( @@ -398,10 +401,10 @@ def eval(self, expr, agent_idx=0, timeout=60): message = e.args[0].replace("\\n", "") return -1, "", f"{message}".strip() - def initialise(self, fast=True, all_technologies_researched=True, clear_entities=True): - self.rcon_client.send_command( - f"/sc global.fast = {str(fast).lower()}" - ) + def initialise( + self, fast=True, all_technologies_researched=True, clear_entities=True + ): + self.rcon_client.send_command(f"/sc global.fast = {str(fast).lower()}") self.first_namespace._create_agent_characters(self.num_agents) init_scripts = [ @@ -433,7 +436,9 @@ def get_warnings(self, seconds=10): :return: """ start = timer() - lua_response = self.rcon_client.send_command(f"/sc rcon.print(dump(global.get_alerts({seconds})))") + lua_response = self.rcon_client.send_command( + f"/sc rcon.print(dump(global.get_alerts({seconds})))" + ) # print(lua_response) alert_dict, duration = _lua2python("alerts", lua_response, start=start) if isinstance(alert_dict, dict): diff --git a/fle/env/tools/admin/reset/client.py b/fle/env/tools/admin/reset/client.py index e3afdd8c8..73d61c295 100644 --- a/fle/env/tools/admin/reset/client.py +++ b/fle/env/tools/admin/reset/client.py @@ -3,39 +3,38 @@ class Reset(Tool): - def __init__(self, connection, game_state): - super().__init__(connection, game_state) + def __init__(self, connection, game_state): + super().__init__(connection, game_state) - def __call__( - self, - inventories=None, - reset_position=False, - all_technologies_researched=True, - clear_entities=True, - ): - """ - Reset the Factorio game state via Lua action, mirroring FactorioInstance.reset/_reset. + def __call__( + self, + inventories=None, + reset_position=False, + all_technologies_researched=True, + clear_entities=True, + ): + """ + Reset the Factorio game state via Lua action, mirroring FactorioInstance.reset/_reset. - Args: - inventories (list[dict]|dict|None): Either a list indexed by agent (1-based in Lua) - or a dict keyed by agent index (int or str) mapping to {item_name: count}. - reset_position (bool): If True, teleport agents to spawn offsets. - all_technologies_researched (bool): If True, research all technologies; else reset force. - """ - if inventories is None: - inventories = {} - # Encode to JSON string for Lua - inventories_json = json.dumps(inventories) + Args: + inventories (list[dict]|dict|None): Either a list indexed by agent (1-based in Lua) + or a dict keyed by agent index (int or str) mapping to {item_name: count}. + reset_position (bool): If True, teleport agents to spawn offsets. + all_technologies_researched (bool): If True, research all technologies; else reset force. + """ + if inventories is None: + inventories = {} + # Encode to JSON string for Lua + inventories_json = json.dumps(inventories) - reset_position = str(reset_position).lower() - all_technologies_researched = str(all_technologies_researched).lower() - clear_entities = str(clear_entities).lower() - - response, _ = self.execute( - inventories_json, - reset_position, - all_technologies_researched, - clear_entities, - ) - return response + reset_position = str(reset_position).lower() + all_technologies_researched = str(all_technologies_researched).lower() + clear_entities = str(clear_entities).lower() + response, _ = self.execute( + inventories_json, + reset_position, + all_technologies_researched, + clear_entities, + ) + return response diff --git a/fle/env/tools/admin/set_inventory/client.py b/fle/env/tools/admin/set_inventory/client.py index fc31404fd..5342c6baa 100644 --- a/fle/env/tools/admin/set_inventory/client.py +++ b/fle/env/tools/admin/set_inventory/client.py @@ -4,7 +4,6 @@ import json - class SetInventory(Tool): def __init__(self, connection, game_state): super().__init__(connection, game_state) From fc4fd48472c01ca1fa46d8a08e4ef2c0ebd5ff39 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 03:21:16 +0530 Subject: [PATCH 08/35] fixes for clear entities --- fle/env/tools/admin/reset/client.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fle/env/tools/admin/reset/client.py b/fle/env/tools/admin/reset/client.py index 73d61c295..5f0ccea4a 100644 --- a/fle/env/tools/admin/reset/client.py +++ b/fle/env/tools/admin/reset/client.py @@ -27,10 +27,6 @@ def __call__( # Encode to JSON string for Lua inventories_json = json.dumps(inventories) - reset_position = str(reset_position).lower() - all_technologies_researched = str(all_technologies_researched).lower() - clear_entities = str(clear_entities).lower() - response, _ = self.execute( inventories_json, reset_position, From a33476cd98c2b85574df0c79b51a08884c4de962 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 03:47:39 +0530 Subject: [PATCH 09/35] dup script loading bug fix --- fle/env/lua_manager.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/fle/env/lua_manager.py b/fle/env/lua_manager.py index 4cc0f4a72..16a6bf0e2 100644 --- a/fle/env/lua_manager.py +++ b/fle/env/lua_manager.py @@ -50,19 +50,18 @@ def check_lua_syntax(self, script): return False, e.args[0] def load_tool_into_game(self, name): - # Find all scripts for this action by checking prefixes + # Select scripts by exact tool directory, not prefix + tool_dirs = { + f"agent/{name}", + f"admin/{name}", + f"agent\\{name}", + f"admin\\{name}", + } tool_scripts = [ key for key in self.tool_scripts.keys() - if key.startswith(f"agent/{name}") or key.startswith(f"admin/{name}") + if os.path.dirname(key) in tool_dirs ] - # windows addition - if len(tool_scripts) == 0: - tool_scripts = [ - key - for key in self.tool_scripts.keys() - if key.startswith(f"agent\\{name}") or key.startswith(f"admin\\{name}") - ] # Sort scripts so server.lua comes last tool_scripts.sort(key=lambda x: x.endswith("server.lua")) @@ -81,6 +80,8 @@ def load_tool_into_game(self, name): ): continue self.update_game_checksum(self.rcon_client, script_name, checksum) + # Keep local view in sync so later loads skip + self.game_checksums[script_name] = checksum correct, error = self.check_lua_syntax(script) if not correct: From 226dbd7f5aa550d265e6a5558ee2c2e896b21f29 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 03:58:07 +0530 Subject: [PATCH 10/35] merged --- fle/env/mods/initialise.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index 5c60e5cb4..59f354789 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -355,7 +355,6 @@ global.utils.avoid_entity = function(player_index, entity, position, direction) player.teleport(player_position) return false end -end global.crafting_queue = {} From 9b538cf3a4b43f22039af8ab2e0d8599ede7f516 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 13:20:39 +0530 Subject: [PATCH 11/35] cleaned up lua scripts --- fle/env/instance.py | 7 +- fle/env/mods/alerts.lua | 53 +- fle/env/mods/build_checkerboard.lua | 4 - fle/env/mods/enemies.lua | 7 - fle/env/mods/initialise.lua | 859 +----------------- fle/env/mods/remove_enemies.lua | 9 + fle/env/mods/serialize.lua | 320 +------ fle/env/tools/admin/get_path/server.lua | 1 - .../tools/admin/inspect_entities/server.lua | 2 +- .../tools/admin/save_entity_state/server.lua | 2 +- fle/env/tools/agent/place_entity/server.lua | 11 + fle/env/{mods => }/util.lua | 9 - 12 files changed, 42 insertions(+), 1242 deletions(-) delete mode 100644 fle/env/mods/build_checkerboard.lua delete mode 100644 fle/env/mods/enemies.lua create mode 100644 fle/env/mods/remove_enemies.lua rename fle/env/{mods => }/util.lua (98%) diff --git a/fle/env/instance.py b/fle/env/instance.py index 7467133e2..7c3692010 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -409,16 +409,17 @@ def initialise( init_scripts = [ "alerts", - "util", "connection_points", "recipe_fluid_connection_mappings", "serialize", + "remove_enemies" ] - if self.peaceful: - init_scripts.append("enemies") for script_name in init_scripts: self.lua_script_manager.load_init_into_game(script_name) + if self.peaceful: + self.rcon_client.send_command("/sc global.remove_enemies()") + inventories = [self.initial_inventory] * self.num_agents self.first_namespace._reset( diff --git a/fle/env/mods/alerts.lua b/fle/env/mods/alerts.lua index eb5e6228b..1221e83cd 100644 --- a/fle/env/mods/alerts.lua +++ b/fle/env/mods/alerts.lua @@ -1,7 +1,7 @@ global.alerts = {} -- Define a function to check if the transport belt is blocked -function is_transport_belt_blocked(entity) +local function is_transport_belt_blocked(entity) if entity.type == "transport-belt" then local line_1 = entity.get_transport_line(1) local line_2 = entity.get_transport_line(2) @@ -41,7 +41,7 @@ function is_transport_belt_blocked(entity) end -- Define a function to check if an entity is full -function is_full(entity) +local function is_full(entity) -- Map inventory types to string names local inventory_names = { [defines.inventory.rocket_silo_rocket] = "rocket", @@ -116,7 +116,7 @@ function is_full(entity) return full_inventories end -function can_mine(entity) +local function can_mine(entity) if entity.type == "mining-drill" then -- Check if there's no resource under the drill if entity.mining_target == nil then @@ -126,7 +126,7 @@ function can_mine(entity) return true end -- Define a function to check if the entity is a burner and has fuel -function has_fuel(entity) +local function has_fuel(entity) if entity.burner then if not entity.burner.currently_burning then local fuel_inventory = entity.get_inventory(defines.inventory.fuel) @@ -145,34 +145,14 @@ function has_fuel(entity) end -- Define a function to check if the entity (furnace) has ingredients -function has_ingredients(entity) +local function has_ingredients(entity) if entity.type == "furnace" and entity.get_recipe() == nil then return false end return true end --- Define a function to check if the entity is a drill and has space to output ---function has_output_space(entity) --- if entity.type == "mining-drill" and entity.mining_target then --- local output_inventory = entity.get_output_inventory() --- if output_inventory and output_inventory.is_full() then --- return false --- end --- end --- if entity.type == "mining-drill" then --- local resource = entity.surface.find_entities_filtered{position = entity.position, type = "resource"}[1] --- local output_inventory = entity.get_output_inventory() --- local drop_position = entity.drop_position --- local items_on_ground = entity.surface.find_entities_filtered{area = {{drop_position.x - 0.5, drop_position.y - 0.5}, {drop_position.x + 0.5, drop_position.y + 0.5}}, type = "item-entity"} --- --- if #items_on_ground >= 1 or (resource and output_inventory and not output_inventory.is_empty() and output_inventory and output_inventory.can_insert(resource.prototype.mined_item) == false) then --- return false --- end --- end --- return true ---end - -function has_output_space(entity) + +local function has_output_space(entity) if entity.status == defines.entity_status.waiting_for_space_in_destination then return false end @@ -213,16 +193,7 @@ function has_output_space(entity) return true end ---function has_electricity(entity) --- if entity.prototype.electric_energy_source_prototype then --- if entity.electric_drain <= 0 then --- return false --- end --- end --- return true ---end - -function has_electricity(entity) +local function has_electricity(entity) if entity.prototype.electric_energy_source_prototype then -- Check if the entity is connected to an electric network if entity.electric_network_id then @@ -239,7 +210,7 @@ function has_electricity(entity) end -- Define a function to check if the entity (boiler) has necessary input liquid -function has_input_liquid(entity) +local function has_input_liquid(entity) if entity.type == "boiler" then local fluid_box = entity.fluidbox[1] if fluid_box == nil or fluid_box.amount <= 0 then @@ -256,7 +227,7 @@ function has_input_liquid(entity) return true end -function is_inserter_waiting_for_source(entity) +local function is_inserter_waiting_for_source(entity) if entity.type == "inserter" then local pickup_target = entity.pickup_target if pickup_target and pickup_target.valid then @@ -270,7 +241,7 @@ function is_inserter_waiting_for_source(entity) end -function get_issues(entity) +function global.utils.get_issues(entity) local issues = {} if not can_mine(entity) then @@ -402,7 +373,7 @@ local function on_tick(event) for _, surface in pairs(game.surfaces) do local entities = surface.find_entities_filtered({force = "player"}) for _, entity in pairs(entities) do - local issues = get_issues(entity) + local issues = global.utils.get_issues(entity) if #issues > 0 then local position = entity.position diff --git a/fle/env/mods/build_checkerboard.lua b/fle/env/mods/build_checkerboard.lua deleted file mode 100644 index eb9f267dc..000000000 --- a/fle/env/mods/build_checkerboard.lua +++ /dev/null @@ -1,4 +0,0 @@ -local player = global.agent_characters[arg1] -local surface = player.surface - -surface.build_checkerboard({{-100, -100}, {100, 100}}) \ No newline at end of file diff --git a/fle/env/mods/enemies.lua b/fle/env/mods/enemies.lua deleted file mode 100644 index 95c64a104..000000000 --- a/fle/env/mods/enemies.lua +++ /dev/null @@ -1,7 +0,0 @@ -game.forces["enemy"].kill_all_units() -- Removes all biters -game.map_settings.enemy_expansion.enabled = false -- Stops biters from expanding -game.map_settings.enemy_evolution.enabled = false -- Stops biters from evolving -local surface = game.surfaces[1] -for _, entity in pairs(surface.find_entities_filtered({type="unit-spawner"})) do - entity.destroy() -end diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index 59f354789..48d9e9fe9 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -36,51 +36,8 @@ if global.debug == nil then } end --- -- Note: The debug_rendering.lua library will be loaded separately by the LuaScriptManager --- local player = global.agent_characters[1] --- player.surface.always_day=true --- player.force.character_build_distance_bonus = 100 --- player.force.research_all_technologies() - - --- local beam_duration = 9 --- local surface=player.surface --- local pp = player.position --- local cnt = 0 local directions = {'north', 'northeast', 'east', 'southeast', 'south', 'southwest', 'west', 'northwest'} ---Initialise.lua ---local position = player.position - ---player.game_view_settings.show_controller_gui = false ---player.game_view_settings.show_minimap = false ---player.game_view_settings.show_side_menu = false ---player.game_view_settings.show_quickbar = false ---player.game_view_settings.show_shortcut_bar = false ---player.game_view_settings.show_map_view_options = false ---player.game_view_settings.show_shortcut_bar = false - -global.points_of_interest = {} -global.distances_to_nearest = {} -global.relative_points_of_interest = {} -global.interesting_entities = { - ['enemy']=true, - ['trees']=true, - ['iron-ore']=true, - ['stone']=true, - ['copper-ore']=true, - ['uranium-ore']=true, - ['crude-oil']=true, - ['water']=true, - ['coal']=true -} - --- Check if a player's inventory is empty -local function check_player_inventory_empty(player) - local inventory = player.get_main_inventory() - return inventory.is_empty() -end - global.utils.get_direction = function(from_position, to_position) local dx = to_position.x - from_position.x local dy = to_position.y - from_position.y @@ -150,17 +107,6 @@ global.utils.get_closest_entity = function(player, position) return closest_entity end -global.utils.is_position_blocked = function(player, position) - rendering.draw_circle{only_in_alt_mode=true, width = 0.5, color = {r = 1, g = 0, b = 1}, surface = player.surface, radius = 0.5, filled = false, target = position, time_to_live = 60000} - - local can_place = player.surface.can_place_entity{ - name = "simple-entity-with-owner", - position = position, - } - return not can_place -end - - global.utils.calculate_movement_ticks = function(player, from_pos, to_pos) -- Calculate distance between points local dx = to_pos.x - from_pos.x @@ -192,151 +138,6 @@ global.utils.can_place_entity = function(player, entity_name, position, directio return player.surface.can_place_entity(params) end -global.actions.can_reach = function(player, x, y) - local dx = player.position.x - x - local dy = player.position.y - y - local distance = math.sqrt(dx * dx + dy * dy) - - if distance > player.reach_distance then - return false - end - return true -end - -global.utils.get_placement_issues = function(player, entity_name, position, direction) - local reasons = {} - - -- Check if entity prototype exists - local prototype = game.entity_prototypes[entity_name] - if not prototype then - table.insert(reasons, "invalid entity name " .. entity_name) - return reasons - end - - -- Check if player has required technology - if not player.force.recipes[entity_name] then - table.insert(reasons, "required technology not researched") - end - - -- Get rotated collision box - local collision_box = prototype.collision_box - local box_width = collision_box.right_bottom.x - collision_box.left_top.x - local box_height = collision_box.right_bottom.y - collision_box.left_top.y - - local rotated_box - if direction and (direction == defines.direction.east or direction == defines.direction.west) then - -- Swap width and height for east/west orientations - rotated_box = { - left_top = {x = position.x - box_height/2, y = position.y - box_width/2}, - right_bottom = {x = position.x + box_height/2, y = position.y + box_width/2} - } - else - rotated_box = { - left_top = {x = position.x - box_width/2, y = position.y - box_height/2}, - right_bottom = {x = position.x + box_width/2, y = position.y + box_height/2} - } - end - - -- Check for collisions with existing entities using rotated box - local entities = player.surface.find_entities_filtered{area = rotated_box} - if #entities > 0 then - local colliding = {} - for _, entity in pairs(entities) do - if entity.valid and entity.name ~= 'character' then - local entity_prototype = game.entity_prototypes[entity.name] - if entity_prototype and entity_prototype.collision_mask and prototype.collision_mask then - -- Check if collision masks overlap - for mask_type in pairs(prototype.collision_mask) do - if entity_prototype.collision_mask[mask_type] then - table.insert(colliding, entity.name) - break - end - end - end - end - end - if #colliding > 0 then - table.insert(reasons, "colliding with " .. table.concat(colliding, ", ")) - end - end - - -- Check all tiles in the rotated area for collision and water - local left = math.floor(rotated_box.left_top.x) - local right = math.ceil(rotated_box.right_bottom.x) - local top = math.floor(rotated_box.left_top.y) - local bottom = math.ceil(rotated_box.right_bottom.y) - - local water_tiles = 0 - local total_tiles = 0 - local problematic_tiles = {} - - for x = left, right do - for y = top, bottom do - local tile = player.surface.get_tile(x, y) - if tile then - total_tiles = total_tiles + 1 - - -- Water checks - if tile.prototype.collision_mask["water-tile"] then - water_tiles = water_tiles + 1 - end - - -- General tile collision check - if prototype.collision_mask then - for mask_type in pairs(prototype.collision_mask) do - if mask_type ~= "water-tile" and tile.prototype.collision_mask[mask_type] then - if not problematic_tiles[tile.name] then - problematic_tiles[tile.name] = true - end - break - end - end - end - end - end - end - - -- Water placement logic - if not prototype.collision_mask["water-tile"] and water_tiles == 0 then - table.insert(reasons, "must be placed on water") - elseif prototype.collision_mask["water-tile"] and water_tiles > 0 then - table.insert(reasons, "cannot build on water") - end - - -- Add problematic tiles to reasons - for tile_name, _ in pairs(problematic_tiles) do - table.insert(reasons, "cannot build on " .. tile_name) - end - - -- Check if outside map bounds - local map_bounds = {{-1000000, -1000000}, {1000000, 1000000}} -- Adjust as needed - if rotated_box.left_top.x < map_bounds[1][1] or rotated_box.right_bottom.x > map_bounds[2][1] or - rotated_box.left_top.y < map_bounds[1][2] or rotated_box.right_bottom.y > map_bounds[2][2] then - table.insert(reasons, "position outside map bounds") - end - - -- Check if player has required items in inventory - if prototype.items_to_place_this then - for _, stack in ipairs(prototype.items_to_place_this) do - local count = stack.count or 1 - local inventory_count = player.get_item_count(stack.name) - if inventory_count < count then - table.insert(reasons, string.format("missing %d %s", count - inventory_count, stack.name)) - end - end - end - - return reasons -end - -global.utils.describe_placement_issues = function(player, entity_name, position, direction) - local issues = global.utils.get_placement_issues(player, entity_name, position, direction) - if #issues > 0 then - return "Cannot build because: " .. table.concat(issues, "; ") - end - return "" -end - global.utils.avoid_entity = function(player_index, entity, position, direction) local player = global.agent_characters[player_index] local player_position = player.position @@ -374,426 +175,6 @@ script.on_event(defines.events.on_tick, function(event) end end) -function abort(message) - local msg = tostring(message):gsub(" ", "_") - rcon.print(msg) -end - -function create_beam_bounding_box (player, surface, direction, top_left, bottom_right) - local bottom_left = {x=top_left.x, y=bottom_right.y} - local top_right = {x=bottom_right.x, y=top_left.y} - local direction = 0 - surface.create_entity{name='laser-beam', position=player.position, source_position=top_left, target_position=top_right, duration=beam_duration, direction=direction, force='neutral', player=player} - surface.create_entity{name='laser-beam', position=player.position, source_position=top_right, target_position=bottom_right, duration=beam_duration, direction=direction, force='neutral', player=player} - surface.create_entity{name='laser-beam', position=player.position, source_position=bottom_right, target_position=bottom_left, duration=beam_duration, direction=direction, force='neutral', player=player} - surface.create_entity{name='laser-beam', position=player.position, source_position=bottom_left, target_position=top_left, duration=beam_duration, direction=direction, force='neutral', player=player} - surface.create_entity{name='laser-beam', position=player.position, source_position=player.position, duration=beam_duration, target_position={x=player.position.x, y=player.position.y+0.1}, direction=direction, force='neutral', player=player} -end - -function create_arrow_with_direction(player, direction, position) - local beam_length = 0.75 - local arrow_width = 0.5 - - -- Calculate the end position of the main beam - local end_position = {x = position.x, y = position.y} - local dx, dy = 0, 0 - - if direction == 0 then -- North - dy = -beam_length - elseif direction == 2 then -- East - dx = beam_length - elseif direction == 4 then -- South - dy = beam_length - elseif direction == 6 then -- West - dx = -beam_length - elseif direction == 1 then -- Northeast - dx, dy = beam_length * 0.7071, -beam_length * 0.7071 - elseif direction == 3 then -- Southeast - dx, dy = beam_length * 0.7071, beam_length * 0.7071 - elseif direction == 5 then -- Southwest - dx, dy = -beam_length * 0.7071, beam_length * 0.7071 - elseif direction == 7 then -- Northwest - dx, dy = -beam_length * 0.7071, -beam_length * 0.7071 - end - - end_position.x = position.x + dx - end_position.y = position.y + dy - - -- Create the main beam - player.surface.create_entity{ - name = 'laser-beam', - position = position, - source_position = position, - target_position = end_position, - duration = 6000, - force = 'neutral', - } - - -- Calculate and create the two side beams for the arrowhead - local perpendicular_dx = -dy * arrow_width - local perpendicular_dy = dx * arrow_width - - local arrow_left = { - x = end_position.x + perpendicular_dx - dx * 0.3, - y = end_position.y + perpendicular_dy - dy * 0.3 - } - local arrow_right = { - x = end_position.x - perpendicular_dx - dx * 0.3, - y = end_position.y - perpendicular_dy - dy * 0.3 - } - - player.surface.create_entity{ - name = 'laser-beam', - position = end_position, - source_position = end_position, - target_position = arrow_left, - duration = 100000, - force = 'neutral', - } - - player.surface.create_entity{ - name = 'laser-beam', - position = end_position, - source_position = end_position, - target_position = arrow_right, - duration = 100000, - force = 'neutral', - } -end - -function create_beam_point_with_direction (player, direction, position) - -- Calculate the end position of the beam based on the direction. - local end_position = {x=position.x, y=position.y} - local length = 0.5 - if direction == defines.direction.north then - end_position.y = end_position.y - length - elseif direction == defines.direction.south then - end_position.y = end_position.y + length - elseif direction == defines.direction.west then - end_position.x = end_position.x - length - elseif direction == defines.direction.east then - end_position.x = end_position.x + length - elseif direction == defines.direction.northeast then - end_position.x = end_position.x + length - end_position.y = end_position.y - length - elseif direction == defines.direction.southeast then - end_position.x = end_position.x + length - end_position.y = end_position.y + length - elseif direction == defines.direction.southwest then - end_position.x = end_position.x - length - end_position.y = end_position.y + length - elseif direction == defines.direction.northwest then - end_position.x = end_position.x - length - end_position.y = end_position.y - length - end - - -- Create the beam entity. - player.surface.create_entity{ - name='laser-beam', - position=position, - source_position=position, - target_position=end_position, - duration=60000, - direction=direction, - force='neutral', - player=player - } -end -function create_beam_point (player, position) - -- Create beams in all four cardinal directions. - create_beam_point_with_direction(player, defines.direction.north, position) - create_beam_point_with_direction(player, defines.direction.south, position) - create_beam_point_with_direction(player, defines.direction.west, position) - create_beam_point_with_direction(player, defines.direction.east, position) -end - - -function observe_points_of_interest (surface, player, search_radius) - local enemy = surface.find_nearest_enemy{position=player.position, max_distance=search_radius} - if enemy ~= nil then - global.points_of_interest['enemy'] = enemy.position - end - - for k, v in pairs(global.points_of_interest) do - global.relative_points_of_interest[k] = { - x=v.x-player.position.x, - y=v.y-player.position.y - } - end - - rcon.print(1) - return dump(global.relative_points_of_interest) -end - -function find_passable_tiles(player, localBoundingBox) - -- Generate a 100x100 bounding box centered on the player - local origin = player.position - local offset = localBoundingBox / 2 - - local left = origin.x - offset - local right = origin.x + offset - local top = origin.y - offset - local bottom = origin.y + offset - - local bounding_box = { - left_top = {x = left, y = top}, - right_bottom = {x = right, y = bottom} - } - local impassable_tiles = {} - for x = left, right do - for y = top, bottom do - local tile = player.surface.get_tile(x, y) - local is_impassable = tile.prototype.collision_mask["player-layer"] - - if is_impassable then - --(x - xmin) * localBoundingBox + (y - ymin) - local relative_x, relative_y = x + offset, y + offset - --local index = coords_to_index(relative_x, relative_y) - local index = relative_y * localBoundingBox + relative_x - if not impassable_tiles[index] then - impassable_tiles[index] = 100 - end - end - end - end - - -- Find all entities within the bounding box - local entities = player.surface.find_entities_filtered{area = {{left, top}, {right, bottom}}, force = player.force} - - for _, entity in ipairs(entities) do - local collision_box = entity.prototype.collision_box - local is_passable = (collision_box.left_top.x == 0 and collision_box.left_top.y == 0 and - collision_box.right_bottom.x == 0 and collision_box.right_bottom.y == 0) - if not is_passable then -- Change this condition to check for impassable entities - local x, y = math.floor(entity.position.x), math.floor(entity.position.y) - local relative_x, relative_y = x - left, y - top - local index = relative_y * localBoundingBox + relative_x - - if not impassable_tiles[index] then - impassable_tiles[index] = 99--{x = relative_x, y = relative_y, entity = entity} - end - end - end - - -- Convert the X, Y coordinates of each entity into coordinates relative to the origin - local relative_entities = {} - for _, entity in ipairs(entities) do - table.insert(relative_entities, {x = left, y = top, entity = entity}) - end - - -- Create a 1D sparse index of the x, y coordinate and entity, starting from the top left of the bounding box - local sparse_index = {} - for _, relative_entity in ipairs(relative_entities) do - -- Ensure the coordinates are integers by rounding them - local index_x = math.floor(relative_entity.x + offset) - local index_y = math.floor(relative_entity.y + offset) - - local index = index_y * localBoundingBox + index_x - --impassable_tiles[index] = 99 - end - - rcon.print(1) - return impassable_tiles -end - -function get_locality(position, surface, localBoundingBox) - local origin = position - local offset = localBoundingBox / 2 - - local left = origin.x - offset - local right = origin.x + offset - local top = origin.y - offset - local bottom = origin.y + offset - - local bounding_box = { - left_top = {x = left, y = top}, - right_bottom = {x = right, y = bottom} - } - - local entities = surface.find_entities(bounding_box) - - local relative_entities = {} - for _, entity in ipairs(entities) do - local relative_x = entity.position.x - origin.x - local relative_y = entity.position.y - origin.y - table.insert(relative_entities, {x = relative_x, y = relative_y, entity = entity}) - end - - local sparse_index = {} - for _, relative_entity in ipairs(relative_entities) do - local index_x = math.floor(relative_entity.x + offset) - local index_y = math.floor(relative_entity.y + offset) - - local index = index_y * localBoundingBox + index_x - local name = relative_entity.entity.name - sparse_index[index] = name:gsub("-", "_") - end - - -- Find water tiles - local water_tiles = surface.find_tiles_filtered{area=bounding_box, name={"water", "deepwater"}} - for _, tile in ipairs(water_tiles) do - local relative_x = tile.position.x - origin.x - local relative_y = tile.position.y - origin.y - - local index_x = math.floor(relative_x + offset) - local index_y = math.floor(relative_y + offset) - - local index = index_y * localBoundingBox + index_x - local name = "water" - sparse_index[index] = name:gsub("-", "_") - end - - rcon.print(1) - return sparse_index -end - - -function has_value(tbl, val) - for _, value in ipairs(tbl) do - if value == val then - return true - end - end - return false -end - -function get_local_environment (position, surface, localBoundingBox, field_x, field_y, debug) - - local top = position.y-localBoundingBox/2 - local left = position.x-localBoundingBox/2 - local bottom = position.y+localBoundingBox/2 - local right = position.x+localBoundingBox/2 - - if field_x > 0 then - right = right + field_x - left = right - field_x - elseif field_x < 0 then - left = left + field_x - right = left - field_x - end - - if field_y < 0 then - top = top + field_y - bottom = top-field_y - elseif field_y > 0 then - bottom = bottom + field_y - top = bottom-field_y - end - - bounding_box_top_left = {x=left, y=top} - bounding_box_bottom_right = {x=right, y=bottom} - - if debug then - create_beam_bounding_box(player, surface, direction, bounding_box_top_left, bounding_box_bottom_right) - entities = surface.find_entities_filtered{name="laser-beam", invert=true, area={bounding_box_top_left, bounding_box_bottom_right}} - else - entities = surface.find_entities({bounding_box_top_left, bounding_box_bottom_right}) - end - - mt = {} -- create the matrix - for i,entity in pairs(entities) do - - local x_pos = left-entity.position.x - local y_pos = top-entity.position.y - local name = entity.name - - mt[math.floor(x_pos*(top-bottom) + y_pos)] = name:gsub("-", "_") - set_points_of_interest(player, name, 1, x_pos, y_pos) - - end - - rcon.print(1) - return mt -end - -function observe_statistics (player) - performance = { - item = { - input_counts=player.force.item_production_statistics.input_counts, - output_counts=player.force.item_production_statistics.output_counts - }, - fluid = { - input_counts=player.force.fluid_production_statistics.input_counts, - output_counts=player.force.fluid_production_statistics.output_counts - }, - kills = { - input_counts=player.force.kill_count_statistics.input_counts, - output_counts=player.force.kill_count_statistics.output_counts - }, - built = { - input_counts=player.force.entity_build_count_statistics.input_counts, - output_counts=player.force.entity_build_count_statistics.output_counts - } - } - return performance -end - -function clean_nils(t) - local ans = {} - for _,v in pairs(t) do - ans[ #ans+1 ] = v - end - return ans -end - -function observe_chunk(player, surface, chunk_x, chunk_y) - counts = {} - key = chunk_x .. ", " ..chunk_y - - chunk_area = {{chunk_x*32, chunk_y*32}, {(chunk_x+1)*32, (chunk_y+1)*32}} - - counts['all'] = surface.count_entities_filtered{area=chunk_area} - - -- Environment - counts['trees'] = surface.count_entities_filtered{area=chunk_area, type = "tree"} - counts['water'] = surface.count_tiles_filtered{area=chunk_area, name = 'water'} - counts['pollution'] = surface.get_pollution({chunk_x*32,chunk_y*32}) - - -- Forces - counts['factory'] = surface.count_entities_filtered{area=chunk_area, force = "player"} - counts['enemy'] = surface.count_entities_filtered{area=chunk_area, force = "enemy"} - - -- Resources - counts['iron-ore'] = surface.count_entities_filtered{area=chunk_area, name = "iron-ore"} - counts['coal'] = surface.count_entities_filtered{area=chunk_area, name = "coal"} - counts['copper-ore'] = surface.count_entities_filtered{area=chunk_area, name = "copper-ore"} - counts['uranium-ore'] = surface.count_entities_filtered{area=chunk_area, name = "uranium-ore"} - counts['stone'] = surface.count_entities_filtered{area=chunk_area, name = "stone"} - counts['crude-oil'] = surface.count_entities_filtered{area=chunk_area, name = "crude-oil"} - - for field_name, count in pairs(counts) do - --if count ~= 0 then - set_points_of_interest(player, field_name, count, chunk_x, chunk_y) - --else - --table.remove(counts, field_name) - --end - end - - rcon.print(1) - return counts -end - -function observe_buildable (player, inventory) - local recipes = {} - for field_name, recipe in pairs(player.force.recipes) do - local name = field_name:gsub("-", "_") - local can_build = 0 - for i, ingredient in pairs(recipe.ingredients) do - if inventory[ingredient.name] ~= nil then - local max_build = inventory[ingredient.name] / ingredient.amount - can_build = math.floor(max_build) - else - can_build = 0 - break - end - end - if can_build ~= 0 then - recipes[name] = can_build - end - end - return serpent.line(recipes) -end - function dump(o) if type(o) == 'table' then local s = '{ ' @@ -807,214 +188,7 @@ function dump(o) end end -function set_points_of_interest (player, field, count, chunk_x, chunk_y) - if count > 0 and global.interesting_entities[field] then - x_distance = (chunk_x*32 - player.position.x) * (chunk_x*32 - player.position.x) - y_distance = (chunk_y*32 - player.position.y) * (chunk_y*32 - player.position.y) - distance_square = x_distance + y_distance - - if global.distances_to_nearest[field] == nil then - global.distances_to_nearest[field] = 100000 - end - - if global.distances_to_nearest[field] > distance_square then - global.distances_to_nearest[field] = math.sqrt(distance_square) --only compute expensive sqrt when necessary - global.points_of_interest[field] = {x=chunk_x*32, y=chunk_y*32} - end - end -end - --- Define a function to calculate the raw material value of an item -function raw_material_value(item_name) - local values = { - ["iron-ore"] = 1, - ["copper-ore"] = 1, - ["coal"] = 1, - ["stone"] = 1, - ["crude-oil"] = 1 - } - -- Add more items and their raw material values here if needed - - return values[item_name] or 0 -end - -function get_productivity(player) - local force = player.force - local production_statistics = force.item_production_statistics - - -- Calculate production rates - local production_rates = {} - for _, prototype in pairs(game.item_prototypes) do - local name = prototype.name - local count = production_statistics.get_flow_count{name = name, input = true, precision_index = 5, count = true} - if count > 0 then - production_rates[name] = count - end - end - - -- Calculate consumption rates - local consumption_rates = {} - for _, prototype in pairs(game.item_prototypes) do - local name = prototype.name - local count = production_statistics.get_flow_count{name = name, input = false, precision_index = 5, count = true} - if count > 0 then - consumption_rates[name] = count - end - end - - -- Use the raw_material_value function to calculate the total value created - local total_production_value = 0 - local total_consumption_value = 0 - - for name, rate in pairs(production_rates) do - local value = raw_material_value(name) * rate - total_production_value = total_production_value + value - end - - for name, rate in pairs(consumption_rates) do - local value = raw_material_value(name) * rate - total_consumption_value = total_consumption_value + value - end - - local net_value_created = total_production_value - total_consumption_value - - - return { - production=production_rates, - consumption=consumption_rates - } -end - -function craft_entity(player, entity_name, count) - -- Ensure the player and entity_name are valid - if not player or not player.valid or not entity_name then - return "Entity not valid" - end - - -- Get the recipe for the entity - local recipe = player.force.recipes[entity_name] - if not recipe then - return "Recipe doesnt exist" - end - - -- Check if the entity can be crafted by hand - if recipe.category ~= "crafting" then - return "It requires " .. entity_name:gsub("-", "_") .. " which cannot be crafted by hand" - end - - - -- Check if the player has enough items to craft the entity - for _, ingredient in pairs(recipe.ingredients) do - local has_count = player.get_item_count(ingredient.name) - local need_count = ingredient.amount * count - if has_count < need_count then - return "Insufficient "..ingredient.name:gsub('-',"_"):gsub(' ',"_").." "..(need_count-has_count).." needed" - end - end - - -- Craft the entity, consuming the ingredients - for _, ingredient in pairs(recipe.ingredients) do - player.remove_item({name = ingredient.name, count = ingredient.amount * count}) - end - - -- Insert the crafted entity into the player's inventory - player.insert({name = entity_name, count = count}) - - return 1 -end - -function get_missing_ingredients(player, recipe, count, checked_recipes) - local missing_ingredients = {} - checked_recipes = checked_recipes or {} - for _, ingredient in pairs(recipe.ingredients) do - if game.item_prototypes[ingredient.name] then - local count_that_player_has = player.get_item_count(ingredient.name) - local needed = ingredient.amount * count - if count_that_player_has < needed then - local difference = needed - count_that_player_has - - - -- Check if the ingredient can be crafted - local ingredient_recipe = player.force.recipes[ingredient.name] - if ingredient_recipe and not checked_recipes[ingredient.name] then - checked_recipes[ingredient.name] = true - local sub_missing_ingredients = get_missing_ingredients(player, ingredient_recipe, difference, checked_recipes) - for sub_ingredient_name, sub_count in pairs(sub_missing_ingredients) do - if missing_ingredients[sub_ingredient_name] then - missing_ingredients[sub_ingredient_name] = missing_ingredients[sub_ingredient_name] + sub_count - else - missing_ingredients[sub_ingredient_name] = sub_count - end - end - if sub_missing_ingredients ~= nil then - missing_ingredients[ingredient.name] = difference - end - end - end - else - if game.fluid_prototypes[ingredient.name] then - abort("Crafting requires fluid ingredient " .. ingredient.name) - else - abort("Unknown ingredient " .. ingredient.name) - end - end - end - return missing_ingredients -end - -function recursively_craft_missing_ingredients(player, missing_ingredients) - local uncraftable_ingredients = {} - for ingredient_name, count in pairs(missing_ingredients) do - local success = craft_entity(player, ingredient_name, count) - if not success then - local recipe = player.force.recipes[ingredient_name] - if recipe then - local sub_missing_ingredients = get_missing_ingredients(player, recipe, count) - recursively_craft_missing_ingredients(player, sub_missing_ingredients) - else - uncraftable_ingredients[ingredient_name] = count - end - end - end - return uncraftable_ingredients -end - -function add_to_crafting_queue(player, entity_name, count) - -- Ensure the player and entity_name are valid - if not player or not player.valid or not entity_name then - return "not_valid_entity" - end - - -- Get the recipe for the entity - local recipe = player.force.recipes[entity_name] - if not recipe then - return "no_valid_recipe" - end - - -- Check if the player has enough items to craft the entity - local missing_ingredients = get_missing_ingredients(player, recipe, count) - if next(missing_ingredients) == nil then - -- Craft the requested entity - return craft_entity(player, entity_name, count) - else - local uncraftable_ingredients = recursively_craft_missing_ingredients(player, missing_ingredients) - - if next(uncraftable_ingredients) ~= nil then - local uncraftable_message = "Cannot craft the following missing ingredients " - for ingredient_name, count in pairs(uncraftable_ingredients) do - uncraftable_message = uncraftable_message .. count .. "x " .. ingredient_name .. "___" - end - return uncraftable_message:sub(1, -3) - - else - -- Craft the requested entity - return craft_entity(player, entity_name, count) - end - - end -end - -function inspect(player, radius, position) +function global.utils.inspect(player, radius, position) local surface = player.surface local bounding_box = { left_top = {x = position.x - radius, y = position.y - radius}, @@ -1043,7 +217,7 @@ function inspect(player, radius, position) data.contents = inventory end - data.warnings = get_issues(entity) + data.warnings = global.utils.get_issues(entity) -- Get entity orientation if it has an orientation attribute if entity.type == "train-stop" or entity.type == "car" or entity.type == "locomotive" then @@ -1110,31 +284,4 @@ function inspect(player, radius, position) entity_data = filtered_entity_data return entity_data -end - -function required_resource_present(entity, position, surface) - local prototype = game.entity_prototypes[entity] - local entity_type = prototype.type - - if entity_type == "mining-drill" then - local resources = surface.find_entities_filtered{position=position, type="resource"} - for _, resource in ipairs(resources) do - if resource.prototype.mineable_properties.minable then - return true, nil - end - end - return false, "minable resource" - - elseif entity_type == "offshore-pump" then - local tile = surface.get_tile(position) - if tile.prototype.name == "water" or tile.prototype.name == "deepwater" then - return true, nil - end - return false, "water" - - else - return true, nil - end -end - --- rcon.print(1) \ No newline at end of file +end \ No newline at end of file diff --git a/fle/env/mods/remove_enemies.lua b/fle/env/mods/remove_enemies.lua new file mode 100644 index 000000000..828deecf5 --- /dev/null +++ b/fle/env/mods/remove_enemies.lua @@ -0,0 +1,9 @@ +global.utils.remove_enemies = function () + game.forces["enemy"].kill_all_units() -- Removes all biters + game.map_settings.enemy_expansion.enabled = false -- Stops biters from expanding + game.map_settings.enemy_evolution.enabled = false -- Stops biters from evolving + local surface = game.surfaces[1] + for _, entity in pairs(surface.find_entities_filtered({type="unit-spawner"})) do + entity.destroy() + end +end \ No newline at end of file diff --git a/fle/env/mods/serialize.lua b/fle/env/mods/serialize.lua index 0db942289..790f43d00 100644 --- a/fle/env/mods/serialize.lua +++ b/fle/env/mods/serialize.lua @@ -835,16 +835,7 @@ global.utils.serialize_entity = function(entity) serialized.grid = global.utils.serialize_equipment_grid(entity.grid) end --game.print(serpent.line(entity.get_inventory(defines.inventory.turret_ammo))) - serialized.warnings = get_issues(entity) - --if entity.get_inventory then - -- for i = 1, #defines.inventory do - -- local inventory = entity.get_inventory(i) - -- if inventory and #inventory > 0 then - -- serialized["inventory_" .. i] = global.utils.serialize_inventory(inventory) - -- end - -- end - --end - + serialized.warnings = global.utils.get_issues(entity) local inventory_types = { {name = "fuel", define = defines.inventory.fuel}, @@ -875,122 +866,8 @@ global.utils.serialize_entity = function(entity) width = math.abs(collision_box.right_bottom.x - collision_box.left_top.x), height = math.abs(collision_box.right_bottom.y - collision_box.left_top.y), } - --- Add specific check for gun turrets - --if entity.type == "ammo-turret" then - -- local ammo_inventory = entity.get_inventory(defines.inventory.turret_ammo) - -- if ammo_inventory and #ammo_inventory > 0 then - -- serialized.ammo_inventory = global.utils.serialize_inventory(ammo_inventory) - -- end - --end serialized.neighbours = serialize_neighbours(entity) - - -- Add input and output locations if the entity is a transport belt - --if entity.type == "transport-belt" or entity.type == "underground-belt" then - -- -- input_position is the position upstream of the belt - -- --local direction = entity.direction - -- - -- local x, y = entity.position.x, entity.position.y - -- if entity.direction == defines.direction.north then - -- y = y + 1 - -- elseif entity.direction == defines.direction.south then - -- y = y - 1 - -- elseif entity.direction == defines.direction.east then - -- x = x - 1 - -- elseif entity.direction == defines.direction.west then - -- x = x + 1 - -- elseif entity.direction == defines.direction.northeast then - -- game.print("NORTHEAST") - -- x = x - 1 - -- y = y + 1 - -- elseif entity.direction == defines.direction.southeast then - -- x = x - 1 - -- y = y - 1 - -- elseif entity.direction == defines.direction.northwest then - -- x = x + 1 - -- y = y + 1 - -- elseif entity.direction == defines.direction.southwest then - -- x = x + 1 - -- y = y - 1 - -- end - -- - -- serialized.input_position = {x = x, y = y} - -- - -- -- output_position is the position downstream of the belt - -- local x, y = entity.position.x, entity.position.y - -- if entity.direction == defines.direction.north then - -- y = y - 1 - -- elseif entity.direction == defines.direction.south then - -- y = y + 1 - -- elseif entity.direction == defines.direction.east then - -- x = x + 1 - -- elseif entity.direction == defines.direction.west then - -- x = x - 1 - -- elseif entity.direction == defines.direction.northeast then - -- x = x + 1 - -- y = y - 1 - -- elseif entity.direction == defines.direction.southeast then - -- x = x + 1 - -- y = y + 1 - -- elseif entity.direction == defines.direction.northwest then - -- x = x - 1 - -- y = y - 1 - -- elseif entity.direction == defines.direction.southwest then - -- x = x - 1 - -- y = y + 1 - -- end - -- --create_beam_point_with_direction(global.agent_characters[1], entity.direction , {x = x, y = y}) - -- serialized.output_position = {x = x, y = y} - -- serialized.position = {x = entity.position.x, y = entity.position.y} - -- --serialized.inventory = entity.get_transport_line(1).get_contents() - -- - -- -- Get contents from both lines - -- local line1 = entity.get_transport_line(1) - -- local line2 = entity.get_transport_line(2) - -- - -- -- Calculate if belt is full at the end (can't insert more items) - -- local is_full = not line1.can_insert_at_back() and not line2.can_insert_at_back() - -- - -- serialized.belt_status = { - -- status = is_full and "full_output" or "normal" - -- } - -- - -- -- Get and merge contents from both lines - -- serialized.inventory = {} - -- local line1_contents = line1.get_contents() - -- local line2_contents = line2.get_contents() - -- - -- serialized.is_terminus = #entity.belt_neighbours["outputs"] == 0 - -- serialized.is_source = #entity.belt_neighbours["inputs"] == 0 - -- - -- for item_name, count in pairs(line1_contents) do - -- serialized.inventory[item_name] = (serialized.inventory[item_name] or 0) + count - -- end - -- for item_name, count in pairs(line2_contents) do - -- serialized.inventory[item_name] = (serialized.inventory[item_name] or 0) + count - -- end - -- - -- -- Add warning if belt is full - -- if not serialized.warnings then - -- serialized.warnings = {} - -- end - -- if is_full then - -- table.insert(serialized.warnings, "Belt output is full") - -- end - -- - -- if entity.type == "underground-belt" then - -- serialized.is_input = entity.belt_to_ground_type == "input" - -- if serialized.is_input then - -- serialized.is_terminus = entity.neighbours == nil - -- else - -- serialized.is_source = entity.neighbours == nil - -- end - -- if entity.neighbours ~= nil then - -- --game.print(serpent.block(entity.neighbours)) - -- serialized.connected_to = entity.neighbours.unit_number - -- end - -- end - --end -- Add input and output locations if the entity is a transport belt if entity.type == "transport-belt" or entity.type == "underground-belt" then local x, y = entity.position.x, entity.position.y @@ -1196,12 +1073,6 @@ global.utils.serialize_entity = function(entity) end end - -- Add input and output locations if the entity is a splitter - --if entity.type == "splitter" then - -- serialized.input_position = entity.input_position - -- serialized.output_position = entity.output_position - --end - -- Add input and output locations if the entity is a pipe if entity.type == "pipe" then serialized.connections = {} @@ -1275,96 +1146,6 @@ global.utils.serialize_entity = function(entity) serialized.input_connection_points = mappings.inputs serialized.output_connection_points = mappings.outputs end - --if entity.direction == defines.direction.north then - -- -- Two crude oil inputs at the bottom - -- table.insert(serialized.input_connection_points, - -- {x = x + 1, y = y + 3 - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x - 1, y = y + 3 - -- }) - -- -- Three outputs at the top (petroleum, light oil, heavy oil) - -- table.insert(serialized.output_connection_points, - -- {x = x - 2, y = y - 3 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x, y = y - 3 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x + 2, y = y - 3 - -- }) - --elseif entity.direction == defines.direction.south then - -- -- Two crude oil inputs at the top - -- table.insert(serialized.input_connection_points, - -- {x = x + 1, y = y - 3 - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x - 1, y = y - 3 - -- }) - -- -- Three outputs at the bottom - -- table.insert(serialized.output_connection_points, - -- {x = x - 2, y = y + 3 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x, y = y + 3 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x + 2, y = y + 3 - -- }) - --elseif entity.direction == defines.direction.east then - -- -- Two crude oil inputs on the left - -- table.insert(serialized.input_connection_points, - -- {x = x - 3, y = y + 1 - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x - 3, y = y - 1 - -- }) - -- -- Three outputs on the right - -- table.insert(serialized.output_connection_points, - -- {x = x + 3, y = y - 2 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x + 3, y = y - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x + 3, y = y + 2 - -- }) - --elseif entity.direction == defines.direction.west then - -- -- Two crude oil inputs on the right - -- table.insert(serialized.input_connection_points, - -- {x = x + 3, y = y + 1 - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x + 3, y = y - 1 - -- }) - -- -- Three outputs on the left - -- table.insert(serialized.output_connection_points, - -- {x = x - 3, y = y - 2 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x - 3, y = y - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x - 3, y = y + 2 - -- }) - --end - - ---- Filter out any invalid connection points - --local filtered_input_points = {} - --for _, point in ipairs(serialized.input_connection_points) do - -- if global.utils.is_valid_connection_point(game.surfaces[1], point.position) then - -- table.insert(filtered_input_points, point.positio) - -- end - --end - --serialized.input_connection_points = filtered_input_points - -- - --local filtered_output_points = {} - --for _, point in ipairs(serialized.output_connection_points) do - -- if global.utils.is_valid_connection_point(game.surfaces[1], point.position) then - -- table.insert(filtered_output_points, point.position) - -- end - --end - --serialized.output_connection_points = filtered_output_points end if entity.name == "chemical-plant" then @@ -1379,68 +1160,6 @@ global.utils.serialize_entity = function(entity) serialized.output_connection_points = mappings.outputs end - --if direction == defines.direction.north then - -- -- Input pipes at the bottom - -- table.insert(serialized.input_connection_points, - -- {x = x - 1, y = y + 1.5 - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x + 1, y = y + 1.5 - -- }) - -- -- Output pipes at the top - -- table.insert(serialized.output_connection_points, - -- {x = x - 1, y = y - 1.5 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x + 1, y = y - 1.5 - -- }) - --elseif direction == defines.direction.south then - -- -- Input pipes at the top - -- table.insert(serialized.input_connection_points, - -- {x = x - 1, y = y - 1.5 - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x + 1, y = y - 1.5 - -- }) - -- -- Output pipes at the bottom - -- table.insert(serialized.output_connection_points, - -- {x = x - 1, y = y + 1.5 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x + 1, y = y + 1.5 - -- }) - --elseif direction == defines.direction.east then - -- -- Input pipes on the left - -- table.insert(serialized.input_connection_points, - -- {x = x - 1.5, y = y - 1 - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x - 1.5, y = y + 1, - -- }) - -- -- Output pipes on the right - -- table.insert(serialized.output_connection_points, - -- {x = x + 1.5, y = y - 1, - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x + 1.5, y = y + 1 - -- }) - --elseif direction == defines.direction.west then - -- -- Input pipes on the right - -- table.insert(serialized.input_connection_points, - -- {x = x + 1.5, y = y - 1, - -- }) - -- table.insert(serialized.input_connection_points, - -- {x = x + 1.5, y = y + 1 - -- }) - -- -- Output pipes on the left - -- table.insert(serialized.output_connection_points, - -- {x = x - 1.5, y = y - 1 - -- }) - -- table.insert(serialized.output_connection_points, - -- {x = x - 1.5, y = y + 1 - -- }) - --end - -- Filter out any invalid connection points local filtered_input_points = {} for _, point in ipairs(serialized.input_connection_points) do @@ -1539,25 +1258,6 @@ global.utils.serialize_entity = function(entity) tile_height = prototype.tile_height, } - -- Add drop position if the entity is a mining drill - --game.print("Entity type: " .. entity.type) - --game.print("Entity has burner: " .. tostring(entity.burner ~= nil)) - --game.print("Burner is burning: " .. tostring(entity.burner and entity.burner.currently_burning ~= nil)) - - --if entity.type == "mining-drill" then - -- serialized.drop_position = { - -- x = entity.drop_position.x, - -- y = entity.drop_position.y - -- } - -- serialized.drop_position.x = math.round(serialized.drop_position.x * 2 ) / 2 - -- serialized.drop_position.y = math.round(serialized.drop_position.y * 2 ) / 2 - -- game.print("Mining drill drop position: " .. serpent.line(serialized.drop_position)) - -- local burner = entity.burner - -- if burner then - -- add_burner_inventory(serialized, burner) - -- end - --end - if entity.type == "mining-drill" then serialized.drop_position = { x = entity.drop_position.x, @@ -1668,24 +1368,6 @@ global.utils.serialize_entity = function(entity) serialized.rocket_progress = 0.0 -- Will be updated with actual progress serialized.launch_count = entity.launch_count or 0 - -- Get the current rocket details if one exists - --local rocket_inventory = entity.get_inventory(defines.inventory.rocket_silo_rocket) - --local rocket = entity.rocket or nil - - --if rocket then - -- -- If there's a rocket, get its state - -- serialized.rocket = { - -- status = global.entity_status_names[rocket.status] or "normal", - -- launch_progress = rocket.launch_progress * 100.0 -- Convert to percentage - -- } - -- - -- -- Check for payload - -- local payload_inventory = rocket.get_inventory(defines.inventory.cargo_unit) - -- if payload_inventory and not payload_inventory.is_empty() then - -- serialized.rocket.payload = payload_inventory.get_contents() - -- end - --end - -- Get part construction progress local parts_inventory = entity.get_inventory(defines.inventory.rocket_silo_input) if parts_inventory then diff --git a/fle/env/tools/admin/get_path/server.lua b/fle/env/tools/admin/get_path/server.lua index b0428fdf8..eb0a2e1f7 100644 --- a/fle/env/tools/admin/get_path/server.lua +++ b/fle/env/tools/admin/get_path/server.lua @@ -29,7 +29,6 @@ global.actions.get_path = function(request_id) -- create a beam bounding box at the start and end of the path local start = path[1].position local finish = path[#path].position - --create_beam_bounding_box(player, surface, 1, {x = start.x - 0.5, y = start.y - 0.5}, {x = finish.x + 0.5, y = finish.y + 0.5}) return game.table_to_json({ status = "\"success\"", waypoints = waypoints diff --git a/fle/env/tools/admin/inspect_entities/server.lua b/fle/env/tools/admin/inspect_entities/server.lua index 72d7639d2..4eafc05cf 100644 --- a/fle/env/tools/admin/inspect_entities/server.lua +++ b/fle/env/tools/admin/inspect_entities/server.lua @@ -69,7 +69,7 @@ global.actions.inspect_entities = function(player_index, radius, position_x, pos return path_ends end - local entity_data = inspect(player, radius, position) + local entity_data = global.utils.inspect(player, radius, position) local result = {} diff --git a/fle/env/tools/admin/save_entity_state/server.lua b/fle/env/tools/admin/save_entity_state/server.lua index a3c7b9c95..b393ff4aa 100644 --- a/fle/env/tools/admin/save_entity_state/server.lua +++ b/fle/env/tools/admin/save_entity_state/server.lua @@ -121,7 +121,7 @@ global.actions.save_entity_state = function(player_index, distance, player_entit } -- Add any warnings - for _, warning in pairs(get_issues(entity) or {}) do + for _, warning in pairs(global.utils.get_issues(entity) or {}) do table.insert(state.warnings, '"' .. warning .. '"') end diff --git a/fle/env/tools/agent/place_entity/server.lua b/fle/env/tools/agent/place_entity/server.lua index a954efca5..2ad9ea40d 100644 --- a/fle/env/tools/agent/place_entity/server.lua +++ b/fle/env/tools/agent/place_entity/server.lua @@ -10,6 +10,17 @@ local function surface_to_entity_direction(surface_dir) return direction_map[surface_dir] end + +-- Helper to check if a tile is water +local function is_water_tile(tile_name) + return tile_name == "water" or + tile_name == "deepwater" or + tile_name == "water-green" or + tile_name == "deepwater-green" or + tile_name == "water-shallow" or + tile_name == "water-mud" +end + local function find_offshore_pump_position(player, center_pos) local max_radius = 20 local search_positions = { diff --git a/fle/env/mods/util.lua b/fle/env/util.lua similarity index 98% rename from fle/env/mods/util.lua rename to fle/env/util.lua index c383f2033..dbf4b4aa5 100644 --- a/fle/env/mods/util.lua +++ b/fle/env/util.lua @@ -3,15 +3,6 @@ util = table = {} } --- Helper to check if a tile is water -function is_water_tile(tile_name) - return tile_name == "water" or - tile_name == "deepwater" or - tile_name == "water-green" or - tile_name == "deepwater-green" or - tile_name == "water-shallow" or - tile_name == "water-mud" -end function table.deepcopy(object) local lookup_table = {} From 568f1476057265784506bae5ee8188a2a7f43590 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 13:29:43 +0530 Subject: [PATCH 12/35] more explicit mods structure --- fle/env/instance.py | 2 +- fle/env/mods/initialise.lua | 260 ++------------------------------ fle/env/mods/remove_enemies.lua | 9 -- fle/env/mods/utils.lua | 259 +++++++++++++++++++++++++++++++ 4 files changed, 269 insertions(+), 261 deletions(-) delete mode 100644 fle/env/mods/remove_enemies.lua create mode 100644 fle/env/mods/utils.lua diff --git a/fle/env/instance.py b/fle/env/instance.py index 7c3692010..2ea3a18b7 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -408,11 +408,11 @@ def initialise( self.first_namespace._create_agent_characters(self.num_agents) init_scripts = [ + "utils", "alerts", "connection_points", "recipe_fluid_connection_mappings", "serialize", - "remove_enemies" ] for script_name in init_scripts: self.lua_script_manager.load_init_into_game(script_name) diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index 48d9e9fe9..d6819e412 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -1,27 +1,35 @@ --- Initialise.lua +--- initialise.lua +--- This file is used to initialise the global variables and functions. +--- Ensure this is loaded first. Any variables or functions defined here will be available to all other scripts. -- If the global actions table doesn't exist, create it if not global.actions then + --- @type table Actions table global.actions = {} end if not global.utils then + --- @type table Utils table global.utils = {} end if not global.initial_score then + --- @type table Initial score table global.initial_score = {["player"] = 0} end if not global.alerts then + --- @type table Alerts table global.alerts = {} end if not global.elapsed_ticks then + --- @type number The number of ticks elapsed since the game started global.elapsed_ticks = 0 end if not global.fast then + --- @type boolean Flag to use custom FLE fast mode global.fast = false end @@ -35,253 +43,3 @@ if global.debug == nil then rendering = false -- Flag to toggle debug rendering of polygons and shapes } end - -local directions = {'north', 'northeast', 'east', 'southeast', 'south', 'southwest', 'west', 'northwest'} - -global.utils.get_direction = function(from_position, to_position) - local dx = to_position.x - from_position.x - local dy = to_position.y - from_position.y - local adx = math.abs(dx) - local ady = math.abs(dy) - local diagonal_threshold = 0.5 - - if adx > ady then - if dx > 0 then - return (ady / adx > diagonal_threshold) and (dy > 0 and 3 or 1) or 2 - else - return (ady / adx > diagonal_threshold) and (dy > 0 and 5 or 7) or 6 - end - else - if dy > 0 then - return (adx / ady > diagonal_threshold) and (dx > 0 and 3 or 5) or 4 - else - return (adx / ady > diagonal_threshold) and (dx > 0 and 1 or 7) or 0 - end - end -end - -global.utils.get_direction_with_diagonals = function(from_pos, to_pos) - local dx = to_pos.x - from_pos.x - local dy = to_pos.y - from_pos.y - - if dx == 0 and dy == 0 then - return nil - end - - -- Check for cardinal directions first - local cardinal_margin = 0.20 --0.25 - if math.abs(dx) < cardinal_margin then - return dy > 0 and defines.direction.south or defines.direction.north - elseif math.abs(dy) < cardinal_margin then - return dx > 0 and defines.direction.east or defines.direction.west - end - - -- Handle diagonal directions - if dx > 0 then - return dy > 0 and defines.direction.southeast or defines.direction.northeast - else - return dy > 0 and defines.direction.southwest or defines.direction.northwest - end -end - - -global.utils.get_closest_entity = function(player, position) - local closest_distance = math.huge - local closest_entity = nil - local entities = player.surface.find_entities_filtered{ - position = position, - force = "player", - radius = 3 - } - - for _, entity in ipairs(entities) do - if entity.name ~= 'character' and entity.name ~= 'laser-beam' then - local distance = ((position.x - entity.position.x) ^ 2 + (position.y - entity.position.y) ^ 2) ^ 0.5 - if distance < closest_distance then - closest_distance = distance - closest_entity = entity - end - end - end - - return closest_entity -end - -global.utils.calculate_movement_ticks = function(player, from_pos, to_pos) - -- Calculate distance between points - local dx = to_pos.x - from_pos.x - local dy = to_pos.y - from_pos.y - local distance = math.sqrt(dx * dx + dy * dy) - - -- Get player's walking speed (tiles per tick) - -- Character base speed is 0.15 tiles/tick - local walking_speed = player.character_running_speed - if not walking_speed or walking_speed == 0 then - walking_speed = 0.15 -- Default walking speed - end - - -- Calculate ticks needed for movement - return math.ceil(distance / walking_speed) -end - --- Wrapper around LuaSurface.can_place_entity that replicates all checks LuaPlayer.can_place_entity performs. --- This allows our code to validate placement without relying on an actual LuaPlayer instance. --- extra_params can be provided by callers to pass additional flags (e.g. fast_replace) if needed. -global.utils.can_place_entity = function(player, entity_name, position, direction, extra_params) - local params = extra_params or {} - params.name = entity_name - params.position = position - params.direction = direction - params.force = player.force - -- Use the manual build-check path so the engine applies the same rules as when a human player builds. - params.build_check_type = defines.build_check_type.manual - return player.surface.can_place_entity(params) -end - -global.utils.avoid_entity = function(player_index, entity, position, direction) - local player = global.agent_characters[player_index] - local player_position = player.position - for i=0, 10 do - local can_place = player.surface.can_place_entity{ - name = entity, - force = "player", - position = position, - direction = global.utils.get_entity_direction(entity, direction) - } - if can_place then - return true - end - player.teleport({player_position.x + i, player_position.y + i}) - end - player.teleport(player_position) - return false -end - -global.crafting_queue = {} - -script.on_event(defines.events.on_tick, function(event) - -- Iterate over the crafting queue and update the remaining ticks - for i, task in ipairs(global.crafting_queue) do - task.remaining_ticks = task.remaining_ticks - 1 - - -- If the crafting is finished, consume the ingredients, insert the crafted entity, and remove the task from the queue - if task.remaining_ticks <= 0 then - for _, ingredient in pairs(task.recipe.ingredients) do - task.player.remove_item({name = ingredient.name, count = ingredient.amount * task.count}) - end - task.player.insert({name = task.entity_name, count = task.count}) - table.remove(global.crafting_queue, i) - end - end -end) - -function dump(o) - if type(o) == 'table' then - local s = '{ ' - for k,v in pairs(o) do - if type(k) ~= 'number' then k = '"'..k..'"' end - s = s .. '['..k..'] = ' .. dump(v) .. ',' - end - return s .. '} ' - else - return tostring(o) - end -end - -function global.utils.inspect(player, radius, position) - local surface = player.surface - local bounding_box = { - left_top = {x = position.x - radius, y = position.y - radius}, - right_bottom = {x = position.x + radius, y = position.y + radius} - } - - local entities = surface.find_entities_filtered({bounding_box, force = "player"}) - local entity_data = {} - - for _, entity in ipairs(entities) do - if entity.name ~= 'character' then - local data = { - name = entity.name:gsub("-", "_"), - position = entity.position, - direction = entity.direction,--directions[entity.direction+1], - health = entity.health, - force = entity.force.name, - energy = entity.energy, - status = entity.status, - --crafted_items = entity.crafted_items or nil - } - - -- Get entity contents if it has an inventory - if entity.get_inventory(defines.inventory.chest) then - local inventory = entity.get_inventory(defines.inventory.chest).get_contents() - data.contents = inventory - end - - data.warnings = global.utils.get_issues(entity) - - -- Get entity orientation if it has an orientation attribute - if entity.type == "train-stop" or entity.type == "car" or entity.type == "locomotive" then - data.orientation = entity.orientation - end - - -- Get connected entities for pipes and transport belts - if entity.type == "pipe" or entity.type == "transport-belt" then - local path_ends = find_path_ends(entity) - data.path_ends = {} - for _, path_end in pairs(path_ends) do - local path_position = {x=path_end.position.x - player.position.x, y=path_end.position.y - player.position.y} - table.insert(data.path_ends, {name = path_end.name:gsub("-", "_"), position = path_position, unit_number = path_end.unit_number}) - end - end - - table.insert(entity_data, data) - else - local data = { - name = "player_character", - position = entity.position, - direction = directions[entity.direction+1], - } - table.insert(entity_data, data) - end - end - - -- Sort entities with path_ends by the length of path_ends in descending order - table.sort(entity_data, function(a, b) - if a.path_ends and b.path_ends then - return #a.path_ends > #b.path_ends - elseif a.path_ends then - return true - else - return false - end - end) - - -- Remove entities that exist in the path_ends of other entities - local visited_paths = {} - local filtered_entity_data = {} - for _, data in ipairs(entity_data) do - if data.path_ends then - local should_add = true - for _, path_end in ipairs(data.path_ends) do - if visited_paths[path_end.unit_number] then - should_add = false - break - end - end - if should_add then - for _, path_end in ipairs(data.path_ends) do - visited_paths[path_end.unit_number] = true - end - table.insert(filtered_entity_data, data) - else - data.path_ends = nil - --table.insert(filtered_entity_data, data) - end - else - table.insert(filtered_entity_data, data) - end - end - entity_data = filtered_entity_data - - return entity_data -end \ No newline at end of file diff --git a/fle/env/mods/remove_enemies.lua b/fle/env/mods/remove_enemies.lua deleted file mode 100644 index 828deecf5..000000000 --- a/fle/env/mods/remove_enemies.lua +++ /dev/null @@ -1,9 +0,0 @@ -global.utils.remove_enemies = function () - game.forces["enemy"].kill_all_units() -- Removes all biters - game.map_settings.enemy_expansion.enabled = false -- Stops biters from expanding - game.map_settings.enemy_evolution.enabled = false -- Stops biters from evolving - local surface = game.surfaces[1] - for _, entity in pairs(surface.find_entities_filtered({type="unit-spawner"})) do - entity.destroy() - end -end \ No newline at end of file diff --git a/fle/env/mods/utils.lua b/fle/env/mods/utils.lua new file mode 100644 index 000000000..a7b3ca2b9 --- /dev/null +++ b/fle/env/mods/utils.lua @@ -0,0 +1,259 @@ +global.utils.remove_enemies = function () + game.forces["enemy"].kill_all_units() -- Removes all biters + game.map_settings.enemy_expansion.enabled = false -- Stops biters from expanding + game.map_settings.enemy_evolution.enabled = false -- Stops biters from evolving + local surface = game.surfaces[1] + for _, entity in pairs(surface.find_entities_filtered({type="unit-spawner"})) do + entity.destroy() + end +end + +local directions = {'north', 'northeast', 'east', 'southeast', 'south', 'southwest', 'west', 'northwest'} + +global.utils.get_direction = function(from_position, to_position) + local dx = to_position.x - from_position.x + local dy = to_position.y - from_position.y + local adx = math.abs(dx) + local ady = math.abs(dy) + local diagonal_threshold = 0.5 + + if adx > ady then + if dx > 0 then + return (ady / adx > diagonal_threshold) and (dy > 0 and 3 or 1) or 2 + else + return (ady / adx > diagonal_threshold) and (dy > 0 and 5 or 7) or 6 + end + else + if dy > 0 then + return (adx / ady > diagonal_threshold) and (dx > 0 and 3 or 5) or 4 + else + return (adx / ady > diagonal_threshold) and (dx > 0 and 1 or 7) or 0 + end + end +end + +global.utils.get_direction_with_diagonals = function(from_pos, to_pos) + local dx = to_pos.x - from_pos.x + local dy = to_pos.y - from_pos.y + + if dx == 0 and dy == 0 then + return nil + end + + -- Check for cardinal directions first + local cardinal_margin = 0.20 --0.25 + if math.abs(dx) < cardinal_margin then + return dy > 0 and defines.direction.south or defines.direction.north + elseif math.abs(dy) < cardinal_margin then + return dx > 0 and defines.direction.east or defines.direction.west + end + + -- Handle diagonal directions + if dx > 0 then + return dy > 0 and defines.direction.southeast or defines.direction.northeast + else + return dy > 0 and defines.direction.southwest or defines.direction.northwest + end +end + + +global.utils.get_closest_entity = function(player, position) + local closest_distance = math.huge + local closest_entity = nil + local entities = player.surface.find_entities_filtered{ + position = position, + force = "player", + radius = 3 + } + + for _, entity in ipairs(entities) do + if entity.name ~= 'character' and entity.name ~= 'laser-beam' then + local distance = ((position.x - entity.position.x) ^ 2 + (position.y - entity.position.y) ^ 2) ^ 0.5 + if distance < closest_distance then + closest_distance = distance + closest_entity = entity + end + end + end + + return closest_entity +end + +global.utils.calculate_movement_ticks = function(player, from_pos, to_pos) + -- Calculate distance between points + local dx = to_pos.x - from_pos.x + local dy = to_pos.y - from_pos.y + local distance = math.sqrt(dx * dx + dy * dy) + + -- Get player's walking speed (tiles per tick) + -- Character base speed is 0.15 tiles/tick + local walking_speed = player.character_running_speed + if not walking_speed or walking_speed == 0 then + walking_speed = 0.15 -- Default walking speed + end + + -- Calculate ticks needed for movement + return math.ceil(distance / walking_speed) +end + +-- Wrapper around LuaSurface.can_place_entity that replicates all checks LuaPlayer.can_place_entity performs. +-- This allows our code to validate placement without relying on an actual LuaPlayer instance. +-- extra_params can be provided by callers to pass additional flags (e.g. fast_replace) if needed. +global.utils.can_place_entity = function(player, entity_name, position, direction, extra_params) + local params = extra_params or {} + params.name = entity_name + params.position = position + params.direction = direction + params.force = player.force + -- Use the manual build-check path so the engine applies the same rules as when a human player builds. + params.build_check_type = defines.build_check_type.manual + return player.surface.can_place_entity(params) +end + +global.utils.avoid_entity = function(player_index, entity, position, direction) + local player = global.agent_characters[player_index] + local player_position = player.position + for i=0, 10 do + local can_place = player.surface.can_place_entity{ + name = entity, + force = "player", + position = position, + direction = global.utils.get_entity_direction(entity, direction) + } + if can_place then + return true + end + player.teleport({player_position.x + i, player_position.y + i}) + end + player.teleport(player_position) + return false +end + +global.crafting_queue = {} + +script.on_event(defines.events.on_tick, function(event) + -- Iterate over the crafting queue and update the remaining ticks + for i, task in ipairs(global.crafting_queue) do + task.remaining_ticks = task.remaining_ticks - 1 + + -- If the crafting is finished, consume the ingredients, insert the crafted entity, and remove the task from the queue + if task.remaining_ticks <= 0 then + for _, ingredient in pairs(task.recipe.ingredients) do + task.player.remove_item({name = ingredient.name, count = ingredient.amount * task.count}) + end + task.player.insert({name = task.entity_name, count = task.count}) + table.remove(global.crafting_queue, i) + end + end +end) + +function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k,v in pairs(o) do + if type(k) ~= 'number' then k = '"'..k..'"' end + s = s .. '['..k..'] = ' .. dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + +function global.utils.inspect(player, radius, position) + local surface = player.surface + local bounding_box = { + left_top = {x = position.x - radius, y = position.y - radius}, + right_bottom = {x = position.x + radius, y = position.y + radius} + } + + local entities = surface.find_entities_filtered({bounding_box, force = "player"}) + local entity_data = {} + + for _, entity in ipairs(entities) do + if entity.name ~= 'character' then + local data = { + name = entity.name:gsub("-", "_"), + position = entity.position, + direction = entity.direction,--directions[entity.direction+1], + health = entity.health, + force = entity.force.name, + energy = entity.energy, + status = entity.status, + --crafted_items = entity.crafted_items or nil + } + + -- Get entity contents if it has an inventory + if entity.get_inventory(defines.inventory.chest) then + local inventory = entity.get_inventory(defines.inventory.chest).get_contents() + data.contents = inventory + end + + data.warnings = global.utils.get_issues(entity) + + -- Get entity orientation if it has an orientation attribute + if entity.type == "train-stop" or entity.type == "car" or entity.type == "locomotive" then + data.orientation = entity.orientation + end + + -- Get connected entities for pipes and transport belts + if entity.type == "pipe" or entity.type == "transport-belt" then + local path_ends = find_path_ends(entity) + data.path_ends = {} + for _, path_end in pairs(path_ends) do + local path_position = {x=path_end.position.x - player.position.x, y=path_end.position.y - player.position.y} + table.insert(data.path_ends, {name = path_end.name:gsub("-", "_"), position = path_position, unit_number = path_end.unit_number}) + end + end + + table.insert(entity_data, data) + else + local data = { + name = "player_character", + position = entity.position, + direction = directions[entity.direction+1], + } + table.insert(entity_data, data) + end + end + + -- Sort entities with path_ends by the length of path_ends in descending order + table.sort(entity_data, function(a, b) + if a.path_ends and b.path_ends then + return #a.path_ends > #b.path_ends + elseif a.path_ends then + return true + else + return false + end + end) + + -- Remove entities that exist in the path_ends of other entities + local visited_paths = {} + local filtered_entity_data = {} + for _, data in ipairs(entity_data) do + if data.path_ends then + local should_add = true + for _, path_end in ipairs(data.path_ends) do + if visited_paths[path_end.unit_number] then + should_add = false + break + end + end + if should_add then + for _, path_end in ipairs(data.path_ends) do + visited_paths[path_end.unit_number] = true + end + table.insert(filtered_entity_data, data) + else + data.path_ends = nil + --table.insert(filtered_entity_data, data) + end + else + table.insert(filtered_entity_data, data) + end + end + entity_data = filtered_entity_data + + return entity_data +end \ No newline at end of file From 4bdf19b762e3aa9ff4a324a511831af251dbf1fa Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 13:29:56 +0530 Subject: [PATCH 13/35] more explicit mods structure --- fle/env/mods/initialise.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index d6819e412..17b6c4991 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -34,6 +34,7 @@ if not global.fast then end if not global.agent_characters then + --- @type table Agent characters table mapping agent index to LuaEntity global.agent_characters = {} end From 35b04db68fc57fecf47222b5aca0292838cbbeae Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 13:58:29 +0530 Subject: [PATCH 14/35] cleaned serialize --- fle/env/mods/initialise.lua | 1 - fle/env/mods/serialize.lua | 308 +++++------------- .../tools/admin/save_entity_state/server.lua | 34 +- 3 files changed, 82 insertions(+), 261 deletions(-) diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index 17b6c4991..89ba09449 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -2,7 +2,6 @@ --- This file is used to initialise the global variables and functions. --- Ensure this is loaded first. Any variables or functions defined here will be available to all other scripts. --- If the global actions table doesn't exist, create it if not global.actions then --- @type table Actions table global.actions = {} diff --git a/fle/env/mods/serialize.lua b/fle/env/mods/serialize.lua index 790f43d00..7a641caf1 100644 --- a/fle/env/mods/serialize.lua +++ b/fle/env/mods/serialize.lua @@ -54,19 +54,9 @@ local function is_fluid_handler(entity_type) --entity_type == "pipe-to-ground" end +local recursive_serialize = {} --- Equipment Grids are serialized into an array of equipment entries --- where ench entry is a table with the following fields: --- n: name --- p: position (array of 2 numbers corresponding to x and y) --- s: shield (optional) --- e: energy (optional) --- If the equipment is a burner the following is also present: --- i: burner inventory --- r: result inventory --- b: curently burning (optional) --- f: remaining_burning_fuel (optional) -global.utils.serialize_equipment_grid = function(grid) +local function serialize_equipment_grid(grid) local serialized = {} local processed = {} for y = 0, grid.height - 1 do @@ -86,11 +76,11 @@ global.utils.serialize_equipment_grid = function(grid) -- TODO: Test with Industrial Revolution if equipment.burner then local burner = equipment.burner - entry.i = global.utils.serialize_inventory(burner.inventory) - entry.r = global.utils.serialize_inventory(burner.burnt_result_inventory) + entry.i = recursive_serialize.serialize_inventory(burner.inventory) + entry.r = recursive_serialize.serialize_inventory(burner.burnt_result_inventory) if burner.curently_burning then entry.b = {} - global.utils.serialize_item_stack(burner.curently_burning, entry.b) + recursive_serialize.serialize_item_stack(burner.curently_burning, entry.b) entry.f = burner.remaining_burning_fuel end end @@ -102,42 +92,7 @@ global.utils.serialize_equipment_grid = function(grid) return serialized end -global.utils.deserialize_equipment_grid = function(grid, serialized) - grid.clear() - for _, entry in ipairs(serialized) do - local equipment = grid.put({ - name = entry.n, - position = entry.p, - }) - if equipment then - if entry.s then equipment.shield = entry.s end - if entry.e then equipment.energy = entry.e end - if entry.i then - if entry.b then global.utils.deserialize_item_stack(burner.currently_burning, entry.b) end - if entry.f then burner.remaining_burning_fuel = entry.f end - global.utils.deserialize_inventory(burner.burnt_result_inventory, entry.r) - global.utils.deserialize_inventory(burner.inventory, entry.i) - end - end - end -end - --- Item stacks are serialized into a table with the following fields: --- n: name --- c: count --- h: health (optional) --- d: durability (optional) --- a: ammo count (optional) --- l: label (optional) --- g: equipment grid (optional) --- i: item inventory (optional) --- If the item stack is exportable it has the following property instead --- e: export string --- Label is a table with the following fields: --- t: label text (optional) --- c: color (optional) --- a: allow manual label change -global.utils.serialize_item_stack = function(slot, entry) +recursive_serialize.serialize_item_stack = function(slot, entry) if slot.is_blueprint or slot.is_blueprint_book @@ -169,16 +124,16 @@ global.utils.serialize_item_stack = function(slot, entry) end if slot.grid then - entry.g = global.utils.serialize_equipment_grid(slot.grid) + entry.g = serialize_equipment_grid(slot.grid) end if slot.is_item_with_inventory then local sub_inventory = slot.get_inventory(defines.inventory.item_main) - entry.i = global.utils.serialize_inventory(sub_inventory) + entry.i = recursive_serialize.serialize_inventory(sub_inventory) end end -global.utils.deserialize_item_stack = function(slot, entry) +recursive_serialize.deserialize_item_stack = function(slot, entry) if entry.e then local success = slot.import_stack(entry.e) if success == 1 then @@ -215,87 +170,22 @@ global.utils.deserialize_item_stack = function(slot, entry) end if entry.g then if slot.grid then - global.utils.deserialize_equipment_grid(slot.grid, entry.g) + recursive_serialize.deserialize_equipment_grid(slot.grid, entry.g) elseif slot.type == "item-with-entity-data" and has_create_grid then slot.create_grid() - global.utils.deserialize_equipment_grid(slot.grid, entry.g) + recursive_serialize.deserialize_equipment_grid(slot.grid, entry.g) else print("Error: Attempt to deserialize equipment grid on an unsupported entity") end end if entry.i then local sub_inventory = slot.get_inventory(defines.inventory.item_main) - global.utils.deserialize_inventory(sub_inventory, entry.i) + recursive_serialize.deserialize_inventory(sub_inventory, entry.i) end end end ------ DEPRECATED ---global.utils.serialize_inventory = function(inventory) --- local serialized = {} --- for i = 1, #inventory do --- local slot = inventory[i] --- if slot.valid_for_read then --- local item_name = "\"" .. slot.name .. "\"" --- if serialized[item_name] then --- serialized[item_name] = serialized[item_name] + slot.count --- else --- serialized[item_name] = slot.count --- end --- end --- end --- return serialized ---end - ------ DEPRECATED ---global.utils.serialize_inventory_old = function(inventory) --- local serialized = {} --- if inventory[supports_bar]() and inventory[get_bar]() <= #inventory then --- serialized.b = inventory[get_bar]() --- end --- --- serialized.i = {} --- local previous_index = 0 --- local previous_serialized = nil --- for i = 1, #inventory do --- local item = {} --- local slot = inventory[i] --- if inventory.supports_filters() then --- item.f = inventory.get_filter(i) --- end --- --- if slot.valid_for_read then --- global.utils.serialize_item_stack(slot, item) --- end --- --- if item.n or item.f or item.e then --- local item_serialized = game.table_to_json(item) --- if item_serialized == previous_serialized then --- local previous_item = serialized.i[#serialized.i] --- previous_item.r = (previous_item.r or 0) + 1 --- previous_index = i --- --- else --- if i ~= previous_index + 1 then --- item.s = i --- end --- --- previous_index = i --- previous_serialized = item_serialized --- table.insert(serialized.i, item) --- end --- --- else --- -- Either an empty slot or serilization failed --- previous_index = 0 --- previous_serialized = nil --- end --- end --- --- return serialized ---end - -global.utils.deserialize_inventory = function(inventory, serialized) +recursive_serialize.deserialize_inventory = function(inventory, serialized) if serialized.b and inventory[supports_bar]() then inventory[set_bar](serialized.b) end @@ -320,49 +210,77 @@ global.utils.deserialize_inventory = function(inventory, serialized) end if entry.n or entry.e then - global.utils.deserialize_item_stack(slot, entry) + recursive_serialize.deserialize_item_stack(slot, entry) end end last_slot_index = base_index + repeat_count end end + +recursive_serialize.deserialize_equipment_grid = function(grid, serialized) + grid.clear() + for _, entry in ipairs(serialized) do + local equipment = grid.put({ + name = entry.n, + position = entry.p, + }) + if equipment then + if entry.s then equipment.shield = entry.s end + if entry.e then equipment.energy = entry.e end + if entry.i then + local burner = equipment.burner + if entry.b then recursive_serialize.deserialize_item_stack(burner.currently_burning, entry.b) end + if entry.f then burner.remaining_burning_fuel = entry.f end + recursive_serialize.deserialize_inventory(burner.burnt_result_inventory, entry.r) + recursive_serialize.deserialize_inventory(burner.inventory, entry.i) + end + end + end +end + global.utils.serialize_recipe = function(recipe) - local serialized = { - name = "\"" .. recipe.name .. "\"", - category = "\"" .. recipe.category .. "\"", - enabled = recipe.enabled, - --hidden = recipe.hidden, - energy = recipe.energy, - --order = recipe.order, - --group = recipe.group and "\"" .recipe.group.name.. "\"" or nil, - --subgroup = recipe.subgroup and "\"" .recipe.subgroup.name.. "\"" or nil, - --force = recipe.force and recipe.force.name or nil, - } + local function serialize_number(num) + if num == math.huge then + return "inf" + elseif num == -math.huge then + return "-inf" + else + return tostring(num) + end + end + if not recipe then return nil end - -- Serialize ingredients - serialized.ingredients = {} + local ingredients = {} for _, ingredient in pairs(recipe.ingredients) do - table.insert(serialized.ingredients, {name = "\""..ingredient.name.."\"", type = "\""..ingredient.type.."\"", amount = ingredient.amount}) + table.insert(ingredients, { + name = '"' .. ingredient.name .. '"', + amount = serialize_number(ingredient.amount), + type = '"' .. ingredient.type .. '"' + }) end - -- Serialize products - serialized.products = {} - - -- First try normal products - local output_list = recipe.products - if output_list then - for _, product in pairs(output_list) do - local product_table = {name = "\""..product.name.."\"", type = "\""..product.type.."\"", amount = product.amount} - if product.probability then product_table.probability = product.probability end - table.insert(serialized.products, product_table) - end + local products = {} + for _, product in pairs(recipe.products) do + table.insert(products, { + name = '"' .. product.name .. '"', + amount = serialize_number(product.amount), + type = '"' .. product.type .. '"', + probability = product.probability and serialize_number(product.probability) or "1" + }) end - return serialized + return { + name = '"' .. recipe.name .. '"', + category = '"' .. recipe.category .. '"', + enabled = recipe.enabled, + energy = serialize_number(recipe.energy), + ingredients = ingredients, + products = products + } end -global.utils.serialize_fluidbox = function(fluidbox) +local function serialize_fluidbox(fluidbox) local serialized = { length = #fluidbox, } @@ -414,7 +332,6 @@ global.utils.serialize_fluidbox = function(fluidbox) return serialized end - -- Helper function to get relative direction of neighbor local function get_neighbor_direction(entity, neighbor) local dx = neighbor.position.x - entity.position.x @@ -486,7 +403,7 @@ local function serialize_neighbours(entity) return neighbours end -function add_burner_inventory(serialized, burner) +local function add_burner_inventory(serialized, burner) local fuel_inventory = burner.inventory if fuel_inventory and #fuel_inventory > 0 then serialized.fuel_inventory = {} @@ -502,21 +419,18 @@ function add_burner_inventory(serialized, burner) end end -function get_entity_direction(entity, direction) +local function get_entity_direction(entity, direction) -- If direction is nil then return north if direction == nil then return defines.direction.north end - --game.print("Getting direction: " .. entity .. " with direction: " .. direction) - local prototype = game.entity_prototypes[entity] -- if prototype is nil (e.g because the entity is a ghost or player character) then return the direction as is if prototype == nil then return direction end - --game.print(game.entity_prototypes[entity].name, {skip=defines.print_skip.never}) local cardinals = { defines.direction.north, @@ -642,9 +556,7 @@ function get_entity_direction(entity, direction) return direction end - - -function get_inverse_entity_direction(entity, factorio_direction) +local function get_inverse_entity_direction(entity, factorio_direction) local prototype = game.entity_prototypes[entity] if not factorio_direction then @@ -683,7 +595,7 @@ function get_inverse_entity_direction(entity, factorio_direction) end -- Helper function to check if a position is valid (not colliding with water or other impassable tiles) -global.utils.is_valid_connection_point = function(surface, position) +local function is_valid_connection_point(surface, position) -- Get the tile at the position local tile = surface.get_tile(position.x, position.y) @@ -701,61 +613,6 @@ global.utils.is_valid_connection_point = function(surface, position) return not invalid_tiles[tile.name] end --- Helper function to filter connection points -local function filter_connection_points(entity, points) - if not points then return nil end - - local filtered_points = {} - for _, point in ipairs(points) do - if global.utils.is_valid_connection_point(entity.surface, point) then - table.insert(filtered_points, point) - end - end - - -- If all points were filtered out, return nil - if #filtered_points == 0 then - return nil - end - - return filtered_points -end - ----- Modified pipe position functions to include filtering ---local function get_pipe_positions_filtered(entity) --- local positions = global.utils.get_generator_connection_positions(entity) --- return filter_connection_points(entity, positions) ---end --- ---local function get_pumpjack_pipe_position_filtered(entity) --- local positions = global.utils.get_pumpjack_connection_points(entity) --- return filter_connection_points(entity, positions) ---end --- ---local function get_boiler_pipe_positions_filtered(entity) --- local positions = global.utils.get_boiler_connection_points(entity) --- --- -- Special handling for boiler since it has a different structure --- if not positions then return nil end --- --- local filtered = { --- water_inputs = filter_connection_points(entity, positions.water_inputs), --- steam_output = positions.steam_output -- Usually steam output doesn't need filtering as it connects to pipes above ground --- } --- --- -- If all water inputs were filtered out, return nil --- if not filtered.water_inputs or #filtered.water_inputs == 0 then --- return nil --- end --- --- return filtered ---end --- ---local function get_offshore_pump_pipe_position_filtered(entity) --- local positions = global.utils.get_offshore_pump_connection_points(entity) --- return filter_connection_points(entity, positions) ---end - - global.entity_status_names = { [defines.entity_status.working] = "working", [defines.entity_status.normal] = "normal", @@ -832,7 +689,7 @@ global.utils.serialize_entity = function(entity) } if entity.grid then - serialized.grid = global.utils.serialize_equipment_grid(entity.grid) + serialized.grid = serialize_equipment_grid(entity.grid) end --game.print(serpent.line(entity.get_inventory(defines.inventory.turret_ammo))) serialized.warnings = global.utils.get_issues(entity) @@ -1163,7 +1020,7 @@ global.utils.serialize_entity = function(entity) -- Filter out any invalid connection points local filtered_input_points = {} for _, point in ipairs(serialized.input_connection_points) do - if global.utils.is_valid_connection_point(game.surfaces[1], point) then + if is_valid_connection_point(game.surfaces[1], point) then table.insert(filtered_input_points, point) end end @@ -1172,7 +1029,7 @@ global.utils.serialize_entity = function(entity) -- Filter out any invalid connection points local filtered_output_points = {} for _, point in ipairs(serialized.output_connection_points) do - if global.utils.is_valid_connection_point(game.surfaces[1], point) then + if is_valid_connection_point(game.surfaces[1], point) then table.insert(filtered_output_points, point) end end @@ -1188,7 +1045,7 @@ global.utils.serialize_entity = function(entity) -- Filter out invalid connection points (e.g., those in water) for _, point in ipairs(connection_points) do - if global.utils.is_valid_connection_point(entity.surface, point) then + if is_valid_connection_point(entity.surface, point) then table.insert(filtered_points, point) end end @@ -1244,8 +1101,7 @@ global.utils.serialize_entity = function(entity) -- Filter out any invalid connection points local filtered_connection_points = {} for _, point in ipairs(serialized.connection_points) do - game.print(serpent.line(point)) - if global.utils.is_valid_connection_point(game.surfaces[1], point) then + if is_valid_connection_point(game.surfaces[1], point) then table.insert(filtered_connection_points, point) end end @@ -1270,10 +1126,8 @@ global.utils.serialize_entity = function(entity) -- Get the mining area local prototype = game.entity_prototypes[entity.name] local mining_area = 1 - if prototype["mining_drill_radius"] then + if prototype.mining_drill_radius then mining_area = prototype.mining_drill_radius * 2 - elseif prototype["mining_drill_resource_searching_radius"] then - mining_area = prototype.mining_drill_resource_searching_radius * 2 end local position = entity.position @@ -1521,7 +1375,7 @@ global.utils.serialize_entity = function(entity) if serialized.connection_points then local filtered_points = {} for _, point in ipairs(serialized.connection_points) do - if global.utils.is_valid_connection_point(game.surfaces[1], point) then + if is_valid_connection_point(game.surfaces[1], point) then table.insert(filtered_points, point) end end @@ -1547,7 +1401,7 @@ global.utils.serialize_entity = function(entity) -- Handle special case for boilers which have separate steam output points if serialized.steam_output_point then - if not global.utils.is_valid_connection_point(game.surfaces[1], serialized.steam_output_point) then + if not is_valid_connection_point(game.surfaces[1], serialized.steam_output_point) then serialized.steam_output_point = nil if not serialized.warnings then serialized.warnings = {} diff --git a/fle/env/tools/admin/save_entity_state/server.lua b/fle/env/tools/admin/save_entity_state/server.lua index b393ff4aa..e93bf2962 100644 --- a/fle/env/tools/admin/save_entity_state/server.lua +++ b/fle/env/tools/admin/save_entity_state/server.lua @@ -17,38 +17,6 @@ local function serialize_position(pos) } end -local function serialize_recipe_info(recipe) - if not recipe then return nil end - - local ingredients = {} - for _, ingredient in pairs(recipe.ingredients) do - table.insert(ingredients, { - name = '"' .. ingredient.name .. '"', - amount = serialize_number(ingredient.amount), - type = '"' .. ingredient.type .. '"' - }) - end - - local products = {} - for _, product in pairs(recipe.products) do - table.insert(products, { - name = '"' .. product.name .. '"', - amount = serialize_number(product.amount), - type = '"' .. product.type .. '"', - probability = product.probability and serialize_number(product.probability) or "1" - }) - end - - return { - name = '"' .. recipe.name .. '"', - category = '"' .. recipe.category .. '"', - enabled = recipe.enabled, - energy = serialize_number(recipe.energy), - ingredients = ingredients, - products = products - } -end - -- Main serialization function global.actions.save_entity_state = function(player_index, distance, player_entities, resource_entities, items_on_ground) local surface = global.agent_characters[player_index].surface @@ -200,7 +168,7 @@ global.actions.save_entity_state = function(player_index, distance, player_entit entity.get_recipe then local recipe = entity.get_recipe() if recipe then - state.recipe = serialize_recipe_info(recipe) + state.recipe = global.utils.serialize_recipe(recipe) end end From 4e9b27644c70be509022cfa00bfe362b5c8d86c6 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 14:40:59 +0530 Subject: [PATCH 15/35] minor changes --- fle/env/mods/serialize.lua | 158 ++++++++---------- .../tools/admin/save_entity_state/server.lua | 2 +- 2 files changed, 70 insertions(+), 90 deletions(-) diff --git a/fle/env/mods/serialize.lua b/fle/env/mods/serialize.lua index 7a641caf1..7839573ec 100644 --- a/fle/env/mods/serialize.lua +++ b/fle/env/mods/serialize.lua @@ -239,47 +239,6 @@ recursive_serialize.deserialize_equipment_grid = function(grid, serialized) end end -global.utils.serialize_recipe = function(recipe) - local function serialize_number(num) - if num == math.huge then - return "inf" - elseif num == -math.huge then - return "-inf" - else - return tostring(num) - end - end - if not recipe then return nil end - - local ingredients = {} - for _, ingredient in pairs(recipe.ingredients) do - table.insert(ingredients, { - name = '"' .. ingredient.name .. '"', - amount = serialize_number(ingredient.amount), - type = '"' .. ingredient.type .. '"' - }) - end - - local products = {} - for _, product in pairs(recipe.products) do - table.insert(products, { - name = '"' .. product.name .. '"', - amount = serialize_number(product.amount), - type = '"' .. product.type .. '"', - probability = product.probability and serialize_number(product.probability) or "1" - }) - end - - return { - name = '"' .. recipe.name .. '"', - category = '"' .. recipe.category .. '"', - enabled = recipe.enabled, - energy = serialize_number(recipe.energy), - ingredients = ingredients, - products = products - } -end - local function serialize_fluidbox(fluidbox) local serialized = { length = #fluidbox, @@ -613,55 +572,65 @@ local function is_valid_connection_point(surface, position) return not invalid_tiles[tile.name] end -global.entity_status_names = { - [defines.entity_status.working] = "working", - [defines.entity_status.normal] = "normal", - [defines.entity_status.no_power] = "no_power", - [defines.entity_status.low_power] = "low_power", - [defines.entity_status.no_fuel] = "no_fuel", - [defines.entity_status.disabled_by_control_behavior] = "disabled_by_control_behavior", - [defines.entity_status.opened_by_circuit_network] = "opened_by_circuit_network", - [defines.entity_status.closed_by_circuit_network] = "closed_by_circuit_network", - [defines.entity_status.disabled_by_script] = "disabled_by_script", - [defines.entity_status.marked_for_deconstruction] = "marked_for_deconstruction", - [defines.entity_status.not_plugged_in_electric_network] = "not_plugged_in_electric_network", - [defines.entity_status.networks_connected] = "networks_connected", - [defines.entity_status.networks_disconnected] = "networks_disconnected", - [defines.entity_status.charging] = "charging", - [defines.entity_status.discharging] = "discharging", - [defines.entity_status.fully_charged] = "fully_charged", - [defines.entity_status.out_of_logistic_network] = "out_of_logistic_network", - [defines.entity_status.no_recipe] = "no_recipe", - [defines.entity_status.no_ingredients] = "no_ingredients", - [defines.entity_status.no_input_fluid] = "no_input_fluid", - [defines.entity_status.no_research_in_progress] = "no_research_in_progress", - [defines.entity_status.no_minable_resources] = "no_minable_resources", - [defines.entity_status.low_input_fluid] = "low_input_fluid", - [defines.entity_status.fluid_ingredient_shortage] = "fluid_ingredient_shortage", - [defines.entity_status.full_output] = "full_output", - [defines.entity_status.full_burnt_result_output] = "full_burnt_result_output", - [defines.entity_status.item_ingredient_shortage] = "item_ingredient_shortage", - [defines.entity_status.missing_required_fluid] = "missing_required_fluid", - [defines.entity_status.missing_science_packs] = "missing_science_packs", - [defines.entity_status.waiting_for_source_items] = "waiting_for_source_items", - [defines.entity_status.waiting_for_space_in_destination] = "waiting_for_space_in_destination", - [defines.entity_status.preparing_rocket_for_launch] = "preparing_rocket_for_launch", - [defines.entity_status.waiting_to_launch_rocket] = "waiting_to_launch_rocket", - [defines.entity_status.launching_rocket] = "launching_rocket", - [defines.entity_status.no_modules_to_transmit] = "no_modules_to_transmit", - [defines.entity_status.recharging_after_power_outage] = "recharging_after_power_outage", - [defines.entity_status.waiting_for_target_to_be_built] = "waiting_for_target_to_be_built", - [defines.entity_status.waiting_for_train] = "waiting_for_train", - [defines.entity_status.no_ammo] = "no_ammo", - [defines.entity_status.low_temperature] = "low_temperature", - [defines.entity_status.disabled] = "disabled", - [defines.entity_status.turned_off_during_daytime] = "turned_off_during_daytime", - [defines.entity_status.not_connected_to_rail] = "not_connected_to_rail", - [defines.entity_status.cant_divide_segments] = "cant_divide_segments", -} +global.utils.entity_status_names = function(entity_status) + local s = entity_status + if not s then return '"normal"' end + + -- try direct lookup + local name = defines.entity_status[s] + if name then return '"' .. name .. '"' end + + -- fallback reverse lookup + for k, v in pairs(defines.entity_status) do + if v == s then return '"' .. k .. '"' end + end + + return '"normal"' +end global.utils.get_entity_direction = get_entity_direction +global.utils.serialize_recipe = function(recipe) + local function serialize_number(num) + if num == math.huge then + return "inf" + elseif num == -math.huge then + return "-inf" + else + return tostring(num) + end + end + if not recipe then return nil end + + local ingredients = {} + for _, ingredient in pairs(recipe.ingredients) do + table.insert(ingredients, { + name = '"' .. ingredient.name .. '"', + amount = serialize_number(ingredient.amount), + type = '"' .. ingredient.type .. '"' + }) + end + + local products = {} + for _, product in pairs(recipe.products) do + table.insert(products, { + name = '"' .. product.name .. '"', + amount = serialize_number(product.amount), + type = '"' .. product.type .. '"', + probability = product.probability and serialize_number(product.probability) or "1" + }) + end + + return { + name = '"' .. recipe.name .. '"', + category = '"' .. recipe.category .. '"', + enabled = recipe.enabled, + energy = serialize_number(recipe.energy), + ingredients = ingredients, + products = products + } +end + global.utils.serialize_entity = function(entity) if entity == nil then @@ -678,6 +647,17 @@ global.utils.serialize_entity = function(entity) --game.print("Serialized direction: ", {skip=defines.print_skip.never}) + local s = entity.status -- may be nil for some entities + local name = s and defines.entity_status[s] + + if not name and s then + -- robust reverse lookup + for k, v in pairs(defines.entity_status) do + if v == s then name = k; break end + end + end + + log(("status: %s (%s)"):format(name or "", tostring(s))) local serialized = { name = "\""..entity.name.."\"", position = entity.position, @@ -685,7 +665,7 @@ global.utils.serialize_entity = function(entity) health = entity.health, energy = entity.energy, type = "\""..entity.type.."\"", - status = global.entity_status_names[entity.status] or "\"normal\"", + status = global.utils.entity_status_names(entity.status) } if entity.grid then @@ -1362,7 +1342,7 @@ global.utils.serialize_entity = function(entity) else serialized.fluid = fluid_contents end - --serialized.fluidbox = global.utils.serialize_fluidbox(entity.fluidbox) + --serialized.fluidbox = serialize_fluidbox(entity.fluidbox) end end diff --git a/fle/env/tools/admin/save_entity_state/server.lua b/fle/env/tools/admin/save_entity_state/server.lua index e93bf2962..f05c37864 100644 --- a/fle/env/tools/admin/save_entity_state/server.lua +++ b/fle/env/tools/admin/save_entity_state/server.lua @@ -83,7 +83,7 @@ global.actions.save_entity_state = function(player_index, distance, player_entit health = serialize_number(entity.health), energy = serialize_number(entity.energy or 0), active = entity.active, - status = '"' .. (global.entity_status_names[entity.status] or "normal") .. '"', + status = global.utils.entity_status_names(entity.status), warnings = {}, inventories = {} } From 3606d9316449ef3cf93f65031600738fb831b425 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 14:58:17 +0530 Subject: [PATCH 16/35] incorrect position reset flag --- tests/actions/test_can_place.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/actions/test_can_place.py b/tests/actions/test_can_place.py index 0c379caf7..38f77cf63 100644 --- a/tests/actions/test_can_place.py +++ b/tests/actions/test_can_place.py @@ -4,11 +4,6 @@ from fle.env.game_types import Prototype, Resource -@pytest.fixture() -def game(configure_game): - return configure_game(reset_position=False) - - def test_can_place(game): """ Place a boiler at (0, 0) From 71355ef66d45a5c0850c361a2e68dc08e39b331a Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 15:01:32 +0530 Subject: [PATCH 17/35] formatting --- fle/env/lua_manager.py | 4 +--- tests/actions/test_can_place.py | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/fle/env/lua_manager.py b/fle/env/lua_manager.py index 16a6bf0e2..0c1975269 100644 --- a/fle/env/lua_manager.py +++ b/fle/env/lua_manager.py @@ -58,9 +58,7 @@ def load_tool_into_game(self, name): f"admin\\{name}", } tool_scripts = [ - key - for key in self.tool_scripts.keys() - if os.path.dirname(key) in tool_dirs + key for key in self.tool_scripts.keys() if os.path.dirname(key) in tool_dirs ] # Sort scripts so server.lua comes last tool_scripts.sort(key=lambda x: x.endswith("server.lua")) diff --git a/tests/actions/test_can_place.py b/tests/actions/test_can_place.py index 38f77cf63..3bcb58ddd 100644 --- a/tests/actions/test_can_place.py +++ b/tests/actions/test_can_place.py @@ -1,5 +1,3 @@ -import pytest - from fle.env.entities import Position, Direction from fle.env.game_types import Prototype, Resource From dc047e2ce63ac160d07c1ab68291f137fa6e64cd Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 15:06:23 +0530 Subject: [PATCH 18/35] formatting --- fle/env/instance.py | 9 +-------- fle/env/tools/admin/set_inventory/client.py | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/fle/env/instance.py b/fle/env/instance.py index 2ea3a18b7..a8307b584 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -3,25 +3,18 @@ import functools import importlib import inspect -import json import os -import shutil import signal import threading -import time import traceback import types from concurrent.futures import TimeoutError from pathlib import Path from timeit import default_timer as timer -from typing_extensions import Optional, List, Dict, Any, Tuple +from typing_extensions import Optional import uuid from dotenv import load_dotenv -from slpp import slpp as lua - -from fle.env.entities import BoundingBox -from fle.env.utils.camera import Camera from fle.env.lua_manager import LuaScriptManager from fle.env.namespace import FactorioNamespace diff --git a/fle/env/tools/admin/set_inventory/client.py b/fle/env/tools/admin/set_inventory/client.py index 5342c6baa..6efce9fe8 100644 --- a/fle/env/tools/admin/set_inventory/client.py +++ b/fle/env/tools/admin/set_inventory/client.py @@ -1,6 +1,5 @@ from fle.env.tools import Tool from typing import Dict, Any -from slpp import slpp as lua import json From c67acb8c6ddb0be2f78cf0f33e85e9c6008a280c Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 15:07:19 +0530 Subject: [PATCH 19/35] formatting --- fle/commons/models/game_state.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/fle/commons/models/game_state.py b/fle/commons/models/game_state.py index 69f383e3a..44519cd46 100644 --- a/fle/commons/models/game_state.py +++ b/fle/commons/models/game_state.py @@ -111,9 +111,11 @@ def parse_raw(cls, json_str: str) -> "GameState": current_research=data["research"]["current_research"], research_progress=data["research"]["research_progress"], research_queue=data["research"]["research_queue"], - progress=data["research"]["progress"] - if "progress" in data["research"] - else {}, + progress=( + data["research"]["progress"] + if "progress" in data["research"] + else {} + ), ) return cls( @@ -149,9 +151,11 @@ def parse(cls, data) -> "GameState": current_research=data["research"]["current_research"], research_progress=data["research"]["research_progress"], research_queue=data["research"]["research_queue"], - progress=data["research"]["progress"] - if "progress" in data["research"] - else {}, + progress=( + data["research"]["progress"] + if "progress" in data["research"] + else {} + ), ) return cls( @@ -194,15 +198,15 @@ def to_raw(self) -> str: def to_instance(self, instance): """Restore game state to a Factorio instance""" # Load entity state to all instances (since it's shared) - assert instance.num_agents == self.num_agents, ( - f"GameState can only be restored to a multiagent instance with the same number of agents (num_agents={self.num_agents})" - ) + assert ( + instance.num_agents == self.num_agents + ), f"GameState can only be restored to a multiagent instance with the same number of agents (num_agents={self.num_agents})" instance.first_namespace._load_entity_state(self.entities, decompress=True) # Set inventory for each player if self.inventories: for i in range(instance.num_agents): - instance.first_namespace._set_inventory(i+1, self.inventories[i]) + instance.first_namespace._set_inventory(i + 1, self.inventories[i]) # Restore research state if present (only need to do this once) if self.research: # Only do this for the first instance From 57acd6f4d3208387e429846a6628cdae1d2a6144 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 15:21:25 +0530 Subject: [PATCH 20/35] remove unused functions --- fle/env/instance.py | 113 -------- fle/env/util.lua | 681 -------------------------------------------- 2 files changed, 794 deletions(-) delete mode 100644 fle/env/util.lua diff --git a/fle/env/instance.py b/fle/env/instance.py index a8307b584..275916484 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -450,119 +450,6 @@ def get_warnings(self, seconds=10): else: return [] - def _prepare_callable(self, value): - if callable(value): - if inspect.ismethod(value) or inspect.isfunction(value): - # For methods and functions, bind them to the instance - return value.__get__(self, self.__class__) - elif hasattr(value, "__call__"): - # For objects with a __call__ method (like your controllers) - return lambda *args, **kwargs: value(*args, **kwargs) - else: - # For other callables, return as is - return value - else: - # For non-callable attributes, return as is - return value - - def create_factorio_namespace(self): - namespace = {} - - def add_to_namespace(name, value): - if isinstance(value, enum.EnumMeta): - # For enums, add the enum itself and all its members - namespace[name] = value - for member_name, member_value in value.__members__.items(): - namespace[f"{name}.{member_name}"] = member_value - elif inspect.ismodule(value) and value.__name__.startswith("factorio_"): - # For Factorio-related modules, add the module and its attributes - namespace[name] = value - for attr_name, attr_value in inspect.getmembers(value): - if not attr_name.startswith("_"): - namespace[f"{name}.{attr_name}"] = attr_value - elif isinstance(value, type): - # For classes, add the class itself - namespace[name] = value - else: - # For other values, add them directly - namespace[name] = value - - # Add all public instance attributes and methods - for name, value in vars(self).items(): - if not name.startswith("_"): - add_to_namespace(name, value) - - # Add dynamically loaded controllers - for name, controller in self.controllers.items(): - namespace[name] = self._prepare_callable(controller) - - # Add all class-level attributes - for name, value in vars(self.__class__).items(): - if not name.startswith("_") and name not in namespace: - add_to_namespace(name, value) - - # Add all global variables from the module where FactorioInstance is defined - module_globals = inspect.getmodule(self.__class__).__dict__ - for name, value in module_globals.items(): - if not name.startswith("_") and name not in namespace: - add_to_namespace(name, value) - - return types.SimpleNamespace(**namespace) - - def run_func_in_factorio_env(self, func): - """ - This decorator allows a function to be run in the Factorio environment, with access to all Factorio objects - :param func: - :return: - """ - - @functools.wraps(func) - def wrapper(*args, **kwargs): - factorio_ns = self.create_factorio_namespace() - - # Create a new function with the Factorio namespace as its globals - new_globals = {**func.__globals__, **vars(factorio_ns)} - new_func = types.FunctionType( - func.__code__, - new_globals, - func.__name__, - func.__defaults__, - func.__closure__, - ) - - return new_func(*args, **kwargs) - - return wrapper - - def run_snippet_file_in_factorio_env(self, file_path, clean=True): - """ - Execute a Python file in the Factorio environment, with access to all Factorio objects and support for - debugging and breakpoints - :param file_path: - :return: - """ - factorio_ns = self.create_factorio_namespace() - - # Prepare the globals for the snippet execution - snippet_globals = { - "__name__": "__main__", - "__file__": file_path, - **vars(factorio_ns), - } - try: - # Execute the file directly - with open(file_path, "r") as file: - code = compile(file.read(), file_path, "exec") - exec(code, snippet_globals) - except Exception as e: - print(f"Error executing file {file_path}: {e}") - traceback.print_exc() - raise e - finally: - # Ensure cleanup is performed - if clean: - self.cleanup() - def register_post_tool_hook(self, tool_name, callback=None): """ Register a hook to be called after a specific tool is executed. diff --git a/fle/env/util.lua b/fle/env/util.lua deleted file mode 100644 index dbf4b4aa5..000000000 --- a/fle/env/util.lua +++ /dev/null @@ -1,681 +0,0 @@ -util = -{ - table = {} -} - - -function table.deepcopy(object) - local lookup_table = {} - local function _copy(object) - if type(object) ~= "table" then - return object - -- don't copy factorio rich objects - elseif object.__self then - return object - elseif lookup_table[object] then - return lookup_table[object] - end - local new_table = {} - lookup_table[object] = new_table - for index, value in pairs(object) do - new_table[_copy(index)] = _copy(value) - end - return setmetatable(new_table, getmetatable(object)) - end - return _copy(object) -end - -function table.compare( tbl1, tbl2 ) - if tbl1 == tbl2 then return true end - for k, v in pairs( tbl1 ) do - if type(v) == "table" and type(tbl2[k]) == "table" then - if not table.compare( v, tbl2[k] ) then return false end - else - if ( v ~= tbl2[k] ) then return false end - end - end - for k, v in pairs( tbl2 ) do - if tbl1[k] == nil then return false end - end - return true -end - -util.table.deepcopy = table.deepcopy -util.table.compare = table.compare -util.copy = util.table.deepcopy - -function util.distance(position1, position2) - local x1 = position1[1] or position1.x - local y1 = position1[2] or position1.y - local x2 = position2[1] or position2.x - local y2 = position2[2] or position2.y - return ((x1 - x2) ^ 2 + (y1 - y2) ^ 2) ^ 0.5 -end - -function util.positiontostr(pos) - return string.format("[%g, %g]", pos[1] or pos.x, pos[2] or pos.y) -end - -function util.formattime(ticks) - local seconds = ticks / 60 - local minutes = math.floor((seconds)/60) - local seconds = math.floor(seconds - 60*minutes) - return string.format("%d:%02d", minutes, seconds) -end - -function util.color(hex) -- supports 'rrggbb', 'rgb', 'rrggbbaa', 'rgba', 'ww', 'w' - local function h(i,j) - return j and tonumber("0x"..hex:sub(i,j)) / 255 or tonumber("0x"..hex:sub(i,i)) / 15 - end - - hex = hex:gsub("#","") - return #hex == 6 and {r = h(1,2), g = h(3,4), b = h(5,6)} - or #hex == 3 and {r = h(1), g = h(2), b = h(3)} - or #hex == 8 and {r = h(1,2), g = h(3,4), b = h(5,6), a = h(7,8)} - or #hex == 4 and {r = h(1), g = h(2), b = h(3), a = h(4)} - or #hex == 2 and {r = h(1,2), g = h(1,2), b = h(1,2)} - or #hex == 1 and {r = h(1), g = h(1), b = h(1)} - or {r=1, g=1, b=1} -end - -function util.premul_color(color) - local r = color.r or color[1] - local g = color.g or color[2] - local b = color.b or color[3] - local a = color.a or color[4] or 1 - return - { - r = r and (r * a), - g = g and (g * a), - b = b and (b * a), - a = a - } -end - -function util.mix_color(c1, c2) - return - { - (c1.r or c1[1] or 0) * (c2.r or c2[1] or 0), - (c1.g or c1[2] or 0) * (c2.g or c2[2] or 0), - (c1.b or c1[3] or 0) * (c2.b or c2[3] or 0), - (c1.a or c1[4] or 1) * (c2.a or c2[4] or 1) - } -end - -function util.multiply_color(c1, n) - return - { - (c1.r or c1[1] or 0) * (n or 0), - (c1.g or c1[2] or 0) * (n or 0), - (c1.b or c1[3] or 0) * (n or 0), - (c1.a or c1[4] or 1) * (n or 1) - } -end - -function util.get_color_with_alpha(color, alpha, normalized_alpha) - local new_color = - { - r = color.r or color[1] or 0, - g = color.g or color[2] or 0, - b = color.b or color[3] or 0 - } - new_color.a = normalized_alpha and (new_color.r > 1 or new_color.g > 1 or new_color.b > 1) and alpha * 255 or alpha - return new_color -end - -function util.moveposition(position, direction, distance) - - if direction == defines.direction.north then - return {position[1], position[2] - distance} - end - - if direction == defines.direction.south then - return {position[1], position[2] + distance} - end - - if direction == defines.direction.east then - return {position[1] + distance, position[2]} - end - - if direction == defines.direction.west then - return {position[1] - distance, position[2]} - end -end - -function util.oppositedirection(direction) - if not tonumber(direction) then error(direction .. " is not a valid direction") end - return (direction + 4) % 8 -end - -function util.multiplystripes(count, stripes) - local ret = {} - for _, stripe in ipairs(stripes) do - for _ = 1, count do - ret[#ret + 1] = stripe - end - end - return ret -end - -function util.by_pixel(x,y) - return {x / 32, y / 32} -end - -function util.by_pixel_hr(x,y) - return {x / 64, y / 64} -end - -function util.foreach_sprite_definition(table_, fun_) - --for k, tab in pairs(table_) do - fun_(table_) - if table_.hr_version then - fun_(table_.hr_version) - end - --end - return table_ -end - -function util.add_shift(a, b) - if not (a and b) then - return a or b - end - return { a[1] + b[1], a[2] + b[2] } -end - -function util.add_shift_offset(offset_, table_) - return - util.foreach_sprite_definition(table_, function(tab) - tab.shift = util.add_shift(tab.shift, offset_) - end) -end - -function util.mul_shift(shift, scale) - if not (shift and scale) then - return shift - end - return {shift[1] * scale, shift[2] * scale} -end - -function util.format_number(amount, append_suffix) - local suffix = "" - if append_suffix then - local suffix_list = - { - ["T"] = 1000000000000, - ["B"] = 1000000000, - ["M"] = 1000000, - ["k"] = 1000 - } - for letter, limit in pairs (suffix_list) do - if math.abs(amount) >= limit then - amount = math.floor(amount/(limit/10))/10 - suffix = letter - break - end - end - end - local formatted, k = amount - while true do - formatted, k = string.gsub(formatted, "^(-?%d+)(%d%d%d)", '%1,%2') - if (k==0) then - break - end - end - return formatted..suffix -end - -function util.increment(t, k, v) - t[k] = t[k] + (v or 1) -end - -function util.conditional_return(value, data) - return value and data -end - --- Recursively merges and/or deep-copies tables. --- Entries in later tables override entries in earlier ones, unless --- both entries are themselves tables, in which case they are recursively merged. --- Non-merged tables are deep-copied, so that the result is brand new. -function util.merge(tables) - local ret = {} - for i, tab in ipairs(tables) do - for k, v in pairs(tab) do - if (type(v) == "table") then - if (type(ret[k] or false) == "table") then - ret[k] = util.merge{ret[k], v} - else - ret[k] = table.deepcopy(v) - end - else - ret[k] = v - end - end - end - return ret -end - -util.insert_safe = function(entity, item_dict) - if not (entity and entity.valid and item_dict) then return end - local items = game.item_prototypes - local insert = entity.insert - for name, count in pairs (item_dict) do - if items[name] then - insert{name = name, count = count} - else - log("Item to insert not valid: "..name) - end - end -end - -util.remove_safe = function(entity, item_dict) - if not (entity and entity.valid and item_dict) then return end - local items = game.item_prototypes - local remove = entity.remove_item - for name, count in pairs (item_dict) do - if items[name] then - remove{name = name, count = count} - else - log("Item to remove not valid: "..name) - end - end -end - -util.split_whitespace = function(string) - if not string then return {} end - - local result = {} - for w in string:gmatch("%S+") do - table.insert(result, w) - end - return result -end - -util.split = function(inputstr, sep) - local result = {} - - for str in string.gmatch(inputstr, "([^"..sep.."]+)") do - table.insert(result, str) - end - - return result -end - -util.string_starts_with = function(str, start) - return str.sub(str, 1, string.len(start)) == start -end - -util.online_players = function() - log("But why?") - return game.connected_players -end - -util.clamp = function(x, lower, upper) - return math.max(lower, math.min(upper, x)) -end - -local walkable_mask = {"item-layer", "object-layer", "player-layer", "water-tile"} - -local is_walkable = function(mask) - for k, layer in pairs (walkable_mask) do - if mask[layer] then return false end - end - return true -end - -util.get_walkable_tile = function() - for name, tile in pairs (game.tile_prototypes) do - if is_walkable(tile.collision_mask) and not tile.items_to_place_this then - return name - end - end - error("No walkable tile in prototype list") -end - --- This function takes 2 icons tables, and adds the second to the first, but applies scale, shift and tint to the entire second set. --- This allows you to manipulate the entire second icons table in the same way as you would manipulate a single icon when adding to the icons table. -function util.combine_icons(icons1, icons2, inputs, default_icon_size) - scale = inputs.scale or 1 - shift = inputs.shift or {0, 0} - tint = inputs.tint or {r = 1, g = 1, b = 1, a = 1} - - local icons = table.deepcopy(icons1) - for _,icon_to_add in pairs(icons2) do - local icon = {} - icon.icon = icon_to_add.icon - icon.icon_size = icon_to_add.icon_size or default_icon_size or error("No icon size defined for icon \n"..serpent.block(icon)) - icon.scale = scale * (icon_to_add.scale or 32.0 / icon.icon_size) - icon.icon_mipmaps = icon_to_add.icon_mipmaps - if icon_to_add.shift then - icon.shift = {icon_to_add.shift[1] * scale + shift[1], icon_to_add.shift[2] * scale + shift[2]} - else - icon.shift = shift - end - if icon_to_add.tint then - icon.tint = util.mix_color(tint, icon_to_add.tint) - else - icon.tint = tint - end - table.insert(icons,icon) - end - return icons -end - -local energy_chars = -{ - k = 10^3, - K = 10^3, - M = 10^6, - G = 10^9, - T = 10^12, - P = 10^15, - E = 10^18, - Z = 10^21, - Y = 10^24 -} - -function util.technology_icon_constant_damage(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-damage.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_speed(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-speed.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_movement_speed(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-movement-speed.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_range(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-range.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_equipment(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-equipment.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_followers(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-count.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_capacity(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-capacity.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_stack_size(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-capacity.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_productivity(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-mining-productivity.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_braking_force(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-braking-force.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end -function util.technology_icon_constant_mining(technology_icon) - local icons = - { - { - icon = technology_icon, - icon_size = 256, icon_mipmaps = 4 - }, - { - icon = "__core__/graphics/icons/technology/constants/constant-mining.png", - icon_size = 128, - icon_mipmaps = 3, - shift = {100, 100} - } - } - return icons -end - -function util.parse_energy(energy) - - local ending = energy:sub(energy:len()) - if not (ending == "J" or ending == "W") then - error(ending.. " is not a valid unit of energy") - end - - local multiplier = (ending == "W" and 1 / 60) or 1 - local magnitude = energy:sub(energy:len() - 1, energy:len() - 1) - - if tonumber(magnitude) then - return tonumber(energy:sub(1, energy:len()-1)) * multiplier - end - - multiplier = multiplier * (energy_chars[magnitude] or error(magnitude.. " is not valid magnitude")) - return tonumber(energy:sub(1, energy:len()-2)) * multiplier - -end - -function util.product_amount(product) - return product.probability * (product.amount or ((product.amount_min + product.amount_max) / 2)) -end - -function util.empty_sprite(animation_length) - return - { - filename = "__core__/graphics/empty.png", - priority = "extra-high", - width = 1, - height = 1, - frame_count = 1, - repeat_count = animation_length, - direction_count = 1 - } -end - -function util.draw_as_glow(layer) - layer.draw_as_glow = true - if layer.hr_version then - layer.hr_version.draw_as_glow = true - end - return layer -end - -function util.remove_tile_references(data, array_of_tiles_to_remove) - -- Does not handle: - -- explicit tile filters in "selection-tool" items - -- ItemPrototype::place_as_tile - -- TilePrototype::next_direction - -- TilePrototype::transition_merges_with_tile - -- general tile transitions, only removes tile names from water_tile_type_names - - if (type(array_of_tiles_to_remove) ~= "table") then - error("The second parameter of util.remove_tile_reference() is expected to be array of strings.") - end - - local tiles_to_remove = {} - for i, n in pairs(array_of_tiles_to_remove) do - tiles_to_remove[n] = true - end - - local remove_from_mapping = function(mapping) - if not mapping then - return - end - - for _, item in pairs(mapping) do - if item.tiles then - for i, t in pairs(item.tiles) do - if t and tiles_to_remove[t] then - item.tiles[i] = nil - end - end - end - end - end - - for k,e in pairs(data.raw["character"]) do - remove_from_mapping(e.footstep_particle_triggers) - remove_from_mapping(e.synced_footstep_particle_triggers) - remove_from_mapping(e.footprint_particles) - end - - for k,e in pairs(data.raw["car"]) do - remove_from_mapping(e.track_particle_triggers) - end - - if data.raw["fire"] then - for k, fire in pairs(data.raw["fire"]) do - if fire.burnt_patch_alpha_variations then - for i, v in pairs(fire.burnt_patch_alpha_variations) do - if v and v.tile and tiles_to_remove[v.tile] then - fire.burnt_patch_alpha_variations[i] = nil - end - end - end - end - end - - if water_tile_type_names then - for i = #water_tile_type_names, 1, -1 do - if tiles_to_remove[water_tile_type_names[i]] then - table.remove(water_tile_type_names, i) - end - end - end -end - -local remove = table.remove -util.remove_from_list = function(list, value) - for k, v in pairs (list) do - if v == value then - remove(list, k) - return true - end - end - return false -end - -util.list_to_map = function(list) - local map = {} - --Here I am trusting you not to give a list of junk. - for k, value in pairs(list) do - map[value] = true - end - return map -end - -return util \ No newline at end of file From 88be7bc578656550d21ab15f9eee218e11f7d1db Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 15:29:38 +0530 Subject: [PATCH 21/35] naively move setup tools to lua manager --- fle/env/instance.py | 243 +---------------------------------------- fle/env/lua_manager.py | 242 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 241 deletions(-) diff --git a/fle/env/instance.py b/fle/env/instance.py index 275916484..fb046e898 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -1,13 +1,8 @@ import atexit import enum -import functools -import importlib -import inspect import os import signal import threading -import traceback -import types from concurrent.futures import TimeoutError from pathlib import Path from timeit import default_timer as timer @@ -106,7 +101,7 @@ def __init__( # Load the python controllers that correspond to the Lua scripts self.lua_script_manager.load_init_into_game("initialise") - self.setup_tools(self.lua_script_manager) + self.lua_script_manager.setup_tools(self) if inventory is None: inventory = {} @@ -124,7 +119,7 @@ def __init__( **self.lua_script_manager.lib_scripts, **self.lua_script_manager.tool_scripts, } - self.setup_tools(self.lua_script_manager) + self.lua_script_manager.setup_tools(self) self.initialise(fast, all_technologies_researched, clear_entities) self.initial_score, goal = self.first_namespace.score() @@ -268,108 +263,6 @@ def connect_to_server(self, address, tcp_port): print(f"Connected to {address} client at tcp/{tcp_port}.") return rcon_client, address - def setup_tools(self, lua_script_manager): - """ - Load Python controllers from valid tool directories (those containing both client.py and server.lua) - """ - tool_dir = _get_dir("tools") - self.controllers = {} - - def snake_to_camel(snake_str): - return "".join(word.capitalize() for word in snake_str.split("_")) - - # Create a function that wraps a tool's call method to execute hooks - def create_hook_wrapper(tool_name, original_callable): - from functools import wraps - - @wraps(original_callable) - def wrapper(*args, **kwargs): - # Execute pre-tool hooks - try: - self.execute_pre_tool_hooks( - tool_name, original_callable, *args, **kwargs - ) - except Exception as e: - print(f"Error in pre-tool hook for {tool_name}: {e}") - - # Execute the original callable - result = original_callable(*args, **kwargs) - - # Execute post-tool hooks - try: - self.execute_post_tool_hooks(tool_name, original_callable, result) - except Exception as e: - print(f"Error in post-tool hook for {tool_name}: {e}") - - return result - - return wrapper - - # Walk through all subdirectories - for dirpath, _, filenames in os.walk(tool_dir): - # Skip the root directory - if dirpath == tool_dir: - continue - - # Check if this is a valid tool directory - server_file = os.path.join(dirpath, "server.lua") - client_file = os.path.join(dirpath, "client.py") - - if os.path.isfile(server_file) and os.path.isfile(client_file): - # Get the tool name from the directory - tool_name = os.path.basename(dirpath) - - directory_name = Path(dirpath).parent.name - # Load the Python module - module_spec = importlib.util.spec_from_file_location( - tool_name, - client_file, - # str(Path(client_file)) - ) - module = importlib.util.module_from_spec(module_spec) - module_spec.loader.exec_module(module) - - class_name = snake_to_camel(tool_name) - - # Handle special case renames - if tool_name == "place_entity": - class_name = "PlaceObject" - if tool_name == "score": - class_name = "Reward" - - try: - for i in range(self.num_agents): - # Get and instantiate the controller class - callable_class = getattr(module, class_name) - callable_instance = callable_class( - lua_script_manager, self.namespaces[i] - ) - - # Create a wrapper that will execute hooks - wrapped_instance = create_hook_wrapper( - tool_name.lower(), callable_instance - ) - - # Store the controller and add it to namespace - self.controllers[tool_name.lower()] = callable_instance - - if directory_name == "admin": - # If this is an admin method, we hide it in the namespace by adding a shebang - setattr( - self.namespaces[i], - f"_{tool_name.lower()}", - wrapped_instance, - ) - else: - setattr( - self.namespaces[i], tool_name.lower(), wrapped_instance - ) - - except Exception as e: - raise Exception( - f"Could not instantiate {class_name} from {client_file}. {e}" - ) - def eval_with_error(self, expr, agent_idx=0, timeout=60): """Evaluate an expression with a timeout, and return the result without error handling""" @@ -450,138 +343,6 @@ def get_warnings(self, seconds=10): else: return [] - def register_post_tool_hook(self, tool_name, callback=None): - """ - Register a hook to be called after a specific tool is executed. - Can be used as a regular function or as a decorator. - - Args: - tool_name (str): Name of the tool to hook into - callback (callable, optional): Function to call after the tool is executed. - Will receive the tool instance and the result as arguments. - - Returns: - If used as a regular function (with callback provided), returns the callback. - If used as a decorator (without callback), returns a decorator function. - """ - # When used as a decorator without parentheses: @register_post_tool_hook - if tool_name is not None and callback is None and callable(tool_name): - callback = tool_name - tool_name = callback.__name__ - if not hasattr(self, "post_tool_hooks"): - self.post_tool_hooks = {} - if tool_name not in self.post_tool_hooks: - self.post_tool_hooks[tool_name] = [] - self.post_tool_hooks[tool_name].append(callback) - return callback - - # When used as a decorator with arguments: @register_post_tool_hook("tool_name") - if callback is None: - - def decorator(func): - if not hasattr(self, "post_tool_hooks"): - self.post_tool_hooks = {} - if tool_name not in self.post_tool_hooks: - self.post_tool_hooks[tool_name] = [] - self.post_tool_hooks[tool_name].append(func) - return func - - return decorator - - # When used as a regular function: register_post_tool_hook("tool_name", callback_func) - if not callable(callback): - raise TypeError("Callback must be callable") - - if not hasattr(self, "post_tool_hooks"): - self.post_tool_hooks = {} - if tool_name not in self.post_tool_hooks: - self.post_tool_hooks[tool_name] = [] - - self.post_tool_hooks[tool_name].append(callback) - return callback - - def register_pre_tool_hook(self, tool_name, callback=None): - """ - Register a hook to be called before a specific tool is executed. - Can be used as a regular function or as a decorator. - - Args: - tool_name (str): Name of the tool to hook into - callback (callable, optional): Function to call before the tool is executed. - Will receive the tool instance and the arguments as parameters. - - Returns: - If used as a regular function (with callback provided), returns the callback. - If used as a decorator (without callback), returns a decorator function. - """ - # When used as a decorator without parentheses: @register_pre_tool_hook - if tool_name is not None and callback is None and callable(tool_name): - callback = tool_name - tool_name = callback.__name__ - if not hasattr(self, "pre_tool_hooks"): - self.pre_tool_hooks = {} - if tool_name not in self.pre_tool_hooks: - self.pre_tool_hooks[tool_name] = [] - self.pre_tool_hooks[tool_name].append(callback) - return callback - - # When used as a decorator with arguments: @register_pre_tool_hook("tool_name") - if callback is None: - - def decorator(func): - if not hasattr(self, "pre_tool_hooks"): - self.pre_tool_hooks = {} - if tool_name not in self.pre_tool_hooks: - self.pre_tool_hooks[tool_name] = [] - self.pre_tool_hooks[tool_name].append(func) - return func - - return decorator - - # When used as a regular function: register_pre_tool_hook("tool_name", callback_func) - if not callable(callback): - raise TypeError("Callback must be callable") - - if not hasattr(self, "pre_tool_hooks"): - self.pre_tool_hooks = {} - if tool_name not in self.pre_tool_hooks: - self.pre_tool_hooks[tool_name] = [] - - self.pre_tool_hooks[tool_name].append(callback) - return callback - - def execute_post_tool_hooks(self, tool_name, tool_instance, result): - """ - Execute all hooks registered for a tool after it has been executed. - - Args: - tool_name (str): Name of the tool - tool_instance: The tool instance that was executed - result: The result of the tool execution - """ - if tool_name in self.post_tool_hooks: - for callback in self.post_tool_hooks[tool_name]: - try: - callback(tool_instance, result) - except Exception as e: - print(f"Error in post-tool hook for {tool_name}: {e}") - - def execute_pre_tool_hooks(self, tool_name, tool_instance, *args, **kwargs): - """ - Execute all hooks registered for a tool before it is executed. - - Args: - tool_name (str): Name of the tool - tool_instance: The tool instance to be executed - *args, **kwargs: The arguments passed to the tool - """ - if tool_name in self.pre_tool_hooks: - for callback in self.pre_tool_hooks[tool_name]: - try: - callback(tool_instance, *args, **kwargs) - except Exception as e: - print(f"Error in pre-tool hook for {tool_name}: {e}") - def cleanup(self): # Close the RCON connection if hasattr(self, "rcon_client") and self.rcon_client: diff --git a/fle/env/lua_manager.py b/fle/env/lua_manager.py index 0c1975269..a1d939f49 100644 --- a/fle/env/lua_manager.py +++ b/fle/env/lua_manager.py @@ -1,5 +1,7 @@ import hashlib import json + +import importlib import os from pathlib import Path @@ -167,3 +169,243 @@ def _get_game_checksums(self, rcon_client): "/sc rcon.print(global.get_lua_script_checksums())" ) return json.loads(response) + + def setup_tools(self, instance): + """ + Load Python controllers from valid tool directories (those containing both client.py and server.lua) + """ + tool_dir = _get_dir("tools") + instance.controllers = {} + + def snake_to_camel(snake_str): + return "".join(word.capitalize() for word in snake_str.split("_")) + + # Create a function that wraps a tool's call method to execute hooks + def create_hook_wrapper(tool_name, original_callable): + from functools import wraps + + @wraps(original_callable) + def wrapper(*args, **kwargs): + # Execute pre-tool hooks + try: + self.execute_pre_tool_hooks( + instance, tool_name, original_callable, *args, **kwargs + ) + except Exception as e: + print(f"Error in pre-tool hook for {tool_name}: {e}") + + # Execute the original callable + result = original_callable(*args, **kwargs) + + # Execute post-tool hooks + try: + self.execute_post_tool_hooks( + instance, tool_name, original_callable, result + ) + except Exception as e: + print(f"Error in post-tool hook for {tool_name}: {e}") + + return result + + return wrapper + + # Walk through all subdirectories + for dirpath, _, filenames in os.walk(tool_dir): + # Skip the root directory + if dirpath == tool_dir: + continue + + # Check if this is a valid tool directory + server_file = os.path.join(dirpath, "server.lua") + client_file = os.path.join(dirpath, "client.py") + + if os.path.isfile(server_file) and os.path.isfile(client_file): + # Get the tool name from the directory + tool_name = os.path.basename(dirpath) + + directory_name = Path(dirpath).parent.name + # Load the Python module + module_spec = importlib.util.spec_from_file_location( + tool_name, + client_file, + # str(Path(client_file)) + ) + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + + class_name = snake_to_camel(tool_name) + + # Handle special case renames + if tool_name == "place_entity": + class_name = "PlaceObject" + if tool_name == "score": + class_name = "Reward" + + try: + for i in range(instance.num_agents): + # Get and instantiate the controller class + callable_class = getattr(module, class_name) + callable_instance = callable_class(self, instance.namespaces[i]) + + # Create a wrapper that will execute hooks + wrapped_instance = create_hook_wrapper( + tool_name.lower(), callable_instance + ) + + # Store the controller and add it to namespace + instance.controllers[tool_name.lower()] = callable_instance + + if directory_name == "admin": + # If this is an admin method, we hide it in the namespace by adding a shebang + setattr( + instance.namespaces[i], + f"_{tool_name.lower()}", + wrapped_instance, + ) + else: + setattr( + instance.namespaces[i], + tool_name.lower(), + wrapped_instance, + ) + + except Exception as e: + raise Exception( + f"Could not instantiate {class_name} from {client_file}. {e}" + ) + + @staticmethod + def register_post_tool_hook(instance, tool_name, callback=None): + """ + Register a hook to be called after a specific tool is executed. + Can be used as a regular function or as a decorator. + + Args: + tool_name (str): Name of the tool to hook into + callback (callable, optional): Function to call after the tool is executed. + Will receive the tool instance and the result as arguments. + + Returns: + If used as a regular function (with callback provided), returns the callback. + If used as a decorator (without callback), returns a decorator function. + """ + # When used as a decorator without parentheses: @register_post_tool_hook + if tool_name is not None and callback is None and callable(tool_name): + callback = tool_name + tool_name = callback.__name__ + if not hasattr(instance, "post_tool_hooks"): + instance.post_tool_hooks = {} + if tool_name not in instance.post_tool_hooks: + instance.post_tool_hooks[tool_name] = [] + instance.post_tool_hooks[tool_name].append(callback) + return callback + + # When used as a decorator with arguments: @register_post_tool_hook("tool_name") + if callback is None: + + def decorator(func): + if not hasattr(instance, "post_tool_hooks"): + instance.post_tool_hooks = {} + if tool_name not in instance.post_tool_hooks: + instance.post_tool_hooks[tool_name] = [] + instance.post_tool_hooks[tool_name].append(func) + return func + + return decorator + + # When used as a regular function: register_post_tool_hook("tool_name", callback_func) + if not callable(callback): + raise TypeError("Callback must be callable") + + if not hasattr(instance, "post_tool_hooks"): + instance.post_tool_hooks = {} + if tool_name not in instance.post_tool_hooks: + instance.post_tool_hooks[tool_name] = [] + + instance.post_tool_hooks[tool_name].append(callback) + return callback + + @staticmethod + def register_pre_tool_hook(instance, tool_name, callback=None): + """ + Register a hook to be called before a specific tool is executed. + Can be used as a regular function or as a decorator. + + Args: + tool_name (str): Name of the tool to hook into + callback (callable, optional): Function to call before the tool is executed. + Will receive the tool instance and the arguments as parameters. + + Returns: + If used as a regular function (with callback provided), returns the callback. + If used as a decorator (without callback), returns a decorator function. + """ + # When used as a decorator without parentheses: @register_pre_tool_hook + if tool_name is not None and callback is None and callable(tool_name): + callback = tool_name + tool_name = callback.__name__ + if not hasattr(instance, "pre_tool_hooks"): + instance.pre_tool_hooks = {} + if tool_name not in instance.pre_tool_hooks: + instance.pre_tool_hooks[tool_name] = [] + instance.pre_tool_hooks[tool_name].append(callback) + return callback + + # When used as a decorator with arguments: @register_pre_tool_hook("tool_name") + if callback is None: + + def decorator(func): + if not hasattr(instance, "pre_tool_hooks"): + instance.pre_tool_hooks = {} + if tool_name not in instance.pre_tool_hooks: + instance.pre_tool_hooks[tool_name] = [] + instance.pre_tool_hooks[tool_name].append(func) + return func + + return decorator + + # When used as a regular function: register_pre_tool_hook("tool_name", callback_func) + if not callable(callback): + raise TypeError("Callback must be callable") + + if not hasattr(instance, "pre_tool_hooks"): + instance.pre_tool_hooks = {} + if tool_name not in instance.pre_tool_hooks: + instance.pre_tool_hooks[tool_name] = [] + + instance.pre_tool_hooks[tool_name].append(callback) + return callback + + @staticmethod + def execute_post_tool_hooks(instance, tool_name, tool_instance, result): + """ + Execute all hooks registered for a tool after it has been executed. + + Args: + tool_name (str): Name of the tool + tool_instance: The tool instance that was executed + result: The result of the tool execution + """ + if tool_name in instance.post_tool_hooks: + for callback in instance.post_tool_hooks[tool_name]: + try: + callback(tool_instance, result) + except Exception as e: + print(f"Error in post-tool hook for {tool_name}: {e}") + + @staticmethod + def execute_pre_tool_hooks(instance, tool_name, tool_instance, *args, **kwargs): + """ + Execute all hooks registered for a tool before it is executed. + + Args: + tool_name (str): Name of the tool + tool_instance: The tool instance to be executed + *args, **kwargs: The arguments passed to the tool + """ + if tool_name in instance.pre_tool_hooks: + for callback in instance.pre_tool_hooks[tool_name]: + try: + callback(tool_instance, *args, **kwargs) + except Exception as e: + print(f"Error in pre-tool hook for {tool_name}: {e}") From 6fac24020c07f47c0a4ddd51eaec904ab81814f3 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 15:38:24 +0530 Subject: [PATCH 22/35] formatting --- fle/commons/models/game_state.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fle/commons/models/game_state.py b/fle/commons/models/game_state.py index 44519cd46..ab8817f5c 100644 --- a/fle/commons/models/game_state.py +++ b/fle/commons/models/game_state.py @@ -198,9 +198,9 @@ def to_raw(self) -> str: def to_instance(self, instance): """Restore game state to a Factorio instance""" # Load entity state to all instances (since it's shared) - assert ( - instance.num_agents == self.num_agents - ), f"GameState can only be restored to a multiagent instance with the same number of agents (num_agents={self.num_agents})" + assert instance.num_agents == self.num_agents, ( + f"GameState can only be restored to a multiagent instance with the same number of agents (num_agents={self.num_agents})" + ) instance.first_namespace._load_entity_state(self.entities, decompress=True) # Set inventory for each player From d589fd83e1b296d33405536a5e3ea1aa1059cc6d Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 15:39:38 +0530 Subject: [PATCH 23/35] formatting --- fle/env/instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fle/env/instance.py b/fle/env/instance.py index fb046e898..4d0823b8d 100644 --- a/fle/env/instance.py +++ b/fle/env/instance.py @@ -13,7 +13,7 @@ from fle.env.lua_manager import LuaScriptManager from fle.env.namespace import FactorioNamespace -from fle.env.utils.rcon import _lua2python, _get_dir +from fle.env.utils.rcon import _lua2python from fle.commons.models.research_state import ResearchState from factorio_rcon import RCONClient from fle.commons.models.game_state import GameState From 1b084dbbcea0f16db800773c7d2143ba147ecf38 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 16:08:18 +0530 Subject: [PATCH 24/35] fix --- tests/entities/test_rockets.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/entities/test_rockets.py b/tests/entities/test_rockets.py index 471709d4e..84dc8d998 100644 --- a/tests/entities/test_rockets.py +++ b/tests/entities/test_rockets.py @@ -1,4 +1,3 @@ -import json from fle.env.entities import Position, Direction, EntityStatus from fle.env.game_types import Resource, Prototype import pytest @@ -145,7 +144,6 @@ def test_rocket_launch(game): "rocket-fuel": 112, "low-density-structure": 112, } - inventory_items_json = json.dumps(inventory_items) game._set_inventory(1, inventory_items) # Verify initial state From 36dba957b4919c1fb30b319815dc3d4d3a926a31 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 17:24:06 +0530 Subject: [PATCH 25/35] trying something --- .github/workflows/factorio-test.yml | 38 ----------------------------- 1 file changed, 38 deletions(-) diff --git a/.github/workflows/factorio-test.yml b/.github/workflows/factorio-test.yml index 3407983c7..8f03796e5 100644 --- a/.github/workflows/factorio-test.yml +++ b/.github/workflows/factorio-test.yml @@ -36,48 +36,10 @@ jobs: cd fle/cluster bash run-envs.sh start -n 4 - - name: Check server status - run: | - cd fle/cluster - docker compose -f docker-compose.yml ps - docker compose -f docker-compose.yml logs factorio_0 - - name: Install Python dependencies run: | uv sync --all-extras --dev - - name: Debug container IPs and ports - run: | - echo "=== Docker containers ===" - docker ps -a - - echo -e "\n=== Container port mappings ===" - docker ps --format "table {{.Names}}\t{{.Ports}}" - - echo -e "\n=== Listening ports on host ===" - sudo netstat -tlnp | grep -E ':(270|34197)' || echo "No matching ports found" - - - - name: Test RCON connectivity - run: | - echo "=== Testing RCON connectivity ===" - # First, let's see what port the container is actually exposing - CONTAINER_ID=$(docker ps -q -f name=factorio_1) - if [ -n "$CONTAINER_ID" ]; then - echo "Found container ID: $CONTAINER_ID" - docker inspect $CONTAINER_ID | grep -A 10 "Ports" - fi - - # Try common RCON ports - for port in 27000 27016 27017 27018 27019 27020; do - echo -n "Testing port $port: " - if nc -z -w 2 localhost $port 2>/dev/null; then - echo "✓ Open" - else - echo "✗ Closed" - fi - done - - name: Run Python tests (4 workers) run: | uv run pytest -n 4 --dist=load -v -s --tb=short tests/actions/ From ce1fd46d6ca482609e67e6c1649c793309cfe6d3 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 23:15:54 +0530 Subject: [PATCH 26/35] use self.player_index --- fle/commons/models/game_state.py | 7 +++---- fle/env/tools/admin/set_inventory/client.py | 4 ++-- tests/actions/test_craft.py | 2 +- tests/actions/test_pickup_entity.py | 4 ++-- tests/conftest.py | 2 +- tests/entities/test_rockets.py | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/fle/commons/models/game_state.py b/fle/commons/models/game_state.py index ab8817f5c..380bcc882 100644 --- a/fle/commons/models/game_state.py +++ b/fle/commons/models/game_state.py @@ -205,8 +205,8 @@ def to_instance(self, instance): # Set inventory for each player if self.inventories: - for i in range(instance.num_agents): - instance.first_namespace._set_inventory(i + 1, self.inventories[i]) + for namespace in instance.namespaces: + namespace._set_inventory(self.inventories[i]) # Restore research state if present (only need to do this once) if self.research: # Only do this for the first instance @@ -220,8 +220,7 @@ def to_instance(self, instance): # Merge pickled namespace with existing persistent_vars for each player if self.namespaces: - for i in range(instance.num_agents): - namespace = self.namespaces[i] + for namespace in instance.namespaces: if namespace: restored_vars = pickle.loads(namespace) if ( diff --git a/fle/env/tools/admin/set_inventory/client.py b/fle/env/tools/admin/set_inventory/client.py index 6efce9fe8..2831f2b07 100644 --- a/fle/env/tools/admin/set_inventory/client.py +++ b/fle/env/tools/admin/set_inventory/client.py @@ -7,10 +7,10 @@ class SetInventory(Tool): def __init__(self, connection, game_state): super().__init__(connection, game_state) - def __call__(self, player_index: int, inventory: Dict[str, Any]) -> bool: + def __call__(self, inventory: Dict[str, Any]) -> bool: """ Sets the inventory for an agent character """ inventory_json = json.dumps(inventory) - response, elapsed = self.execute(player_index, inventory_json) + response, elapsed = self.execute(self.player_index, inventory_json) return True diff --git a/tests/actions/test_craft.py b/tests/actions/test_craft.py index a9ef391ef..7f411f304 100644 --- a/tests/actions/test_craft.py +++ b/tests/actions/test_craft.py @@ -33,7 +33,7 @@ def test_craft_with_full_inventory(game): """ Test crafting when inventory is full """ - game._set_inventory(1, {"iron-plate": 100, "coal": 10000}) + game._set_inventory({"iron-plate": 100, "coal": 10000}) try: result = game.craft_item(Prototype.IronGearWheel, 1) assert False, ( diff --git a/tests/actions/test_pickup_entity.py b/tests/actions/test_pickup_entity.py index e3b9df3c0..1b33a3073 100644 --- a/tests/actions/test_pickup_entity.py +++ b/tests/actions/test_pickup_entity.py @@ -32,11 +32,11 @@ def test_pickup_item_full_inventory(game): Uses existing inventory items but maximizes stacks to test true full inventory. """ # Clear inventory completely first - game._set_inventory(1, {"wooden-chest": 1}) + game._set_inventory({"wooden-chest": 1}) placement_position = Position(x=0, y=0) game.move_to(placement_position) chest = game.place_entity(Prototype.WoodenChest, position=placement_position) - game._set_inventory(1, {"coal": 10000}) + game._set_inventory({"coal": 10000}) try: result = game.pickup_entity(chest) assert False, ( diff --git a/tests/conftest.py b/tests/conftest.py index cd7d6b6b3..35b063a72 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -184,7 +184,7 @@ def _configure_game( updated = dict(inventory) if persist_inventory: instance.initial_inventory = updated - instance.first_namespace._set_inventory(1, updated) + instance.first_namespace._set_inventory(updated) return instance.namespace diff --git a/tests/entities/test_rockets.py b/tests/entities/test_rockets.py index 84dc8d998..24e94eeb2 100644 --- a/tests/entities/test_rockets.py +++ b/tests/entities/test_rockets.py @@ -144,7 +144,7 @@ def test_rocket_launch(game): "rocket-fuel": 112, "low-density-structure": 112, } - game._set_inventory(1, inventory_items) + game._set_inventory(inventory_items) # Verify initial state # assert silo.status == EntityStatus.NORMAL From ffbbb65ba0efcfbe262bd1af6956027a9d85f6af Mon Sep 17 00:00:00 2001 From: hrshtt Date: Fri, 22 Aug 2025 23:21:05 +0530 Subject: [PATCH 27/35] fixed indexing --- fle/commons/models/game_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fle/commons/models/game_state.py b/fle/commons/models/game_state.py index 380bcc882..027f7c9c1 100644 --- a/fle/commons/models/game_state.py +++ b/fle/commons/models/game_state.py @@ -205,8 +205,8 @@ def to_instance(self, instance): # Set inventory for each player if self.inventories: - for namespace in instance.namespaces: - namespace._set_inventory(self.inventories[i]) + for namespace, inventory in zip(instance.namespaces, self.inventories): + namespace._set_inventory(inventory) # Restore research state if present (only need to do this once) if self.research: # Only do this for the first instance From ba488e15e25874d424be0cb6005112a74b1ec841 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 11:29:28 +0530 Subject: [PATCH 28/35] naive module declaration --- fle/env/mods/alerts.lua | 9 ++++++++- fle/env/mods/checksum.lua | 9 ++++++++- fle/env/mods/connection_points.lua | 9 ++++++++- fle/env/mods/initialise.lua | 8 ++++++++ fle/env/mods/recipe_fluid_connection_mappings.lua | 9 ++++++++- fle/env/mods/serialize.lua | 8 ++++++++ fle/env/mods/utils.lua | 9 ++++++++- fle/env/tools/admin/clear_collision_boxes/server.lua | 9 ++++++++- fle/env/tools/admin/clear_entities/server.lua | 9 ++++++++- fle/env/tools/admin/create_agent_characters/server.lua | 9 ++++++++- fle/env/tools/admin/extend_collision_boxes/server.lua | 9 ++++++++- fle/env/tools/admin/get_factory_centroid/server.lua | 8 ++++++++ fle/env/tools/admin/get_factory_centroid/server2.lua | 9 ++++++++- fle/env/tools/admin/get_messages/server.lua | 9 ++++++++- fle/env/tools/admin/get_path/server.lua | 9 ++++++++- fle/env/tools/admin/get_production_stats/server.lua | 8 ++++++++ fle/env/tools/admin/inspect_entities/server.lua | 9 ++++++++- fle/env/tools/admin/load_blueprint/server.lua | 8 ++++++++ fle/env/tools/admin/load_entity_state/server.lua | 9 ++++++++- fle/env/tools/admin/load_research_state/server.lua | 9 ++++++++- fle/env/tools/admin/regenerate_resources/server.lua | 9 ++++++++- fle/env/tools/admin/render/server.lua | 8 ++++++++ fle/env/tools/admin/render_message/server.lua | 8 ++++++++ fle/env/tools/admin/request_path/server.lua | 9 ++++++++- fle/env/tools/admin/reset/server.lua | 8 ++++++++ fle/env/tools/admin/save_blueprint/server.lua | 9 ++++++++- fle/env/tools/admin/save_entity_state/server.lua | 8 ++++++++ fle/env/tools/admin/save_research_state/server.lua | 9 ++++++++- fle/env/tools/admin/set_inventory/server.lua | 9 ++++++++- fle/env/tools/agent/can_place_entity/server.lua | 9 ++++++++- fle/env/tools/agent/connect_entities/server.lua | 9 ++++++++- fle/env/tools/agent/craft_item/server.lua | 8 ++++++++ fle/env/tools/agent/extract_item/server.lua | 9 ++++++++- fle/env/tools/agent/get_connection_amount/server.lua | 8 ++++++++ fle/env/tools/agent/get_entities/server.lua | 9 ++++++++- fle/env/tools/agent/get_entity/server.lua | 8 ++++++++ fle/env/tools/agent/get_prototype_recipe/server.lua | 8 ++++++++ fle/env/tools/agent/get_research_progress/server.lua | 9 ++++++++- fle/env/tools/agent/get_resource_patch/server.lua | 9 ++++++++- fle/env/tools/agent/harvest_resource/server.lua | 9 ++++++++- fle/env/tools/agent/insert_item/server.lua | 9 ++++++++- fle/env/tools/agent/inspect_inventory/server.lua | 9 ++++++++- fle/env/tools/agent/launch_rocket/server.lua | 9 ++++++++- fle/env/tools/agent/move_to/server.lua | 9 ++++++++- fle/env/tools/agent/nearest/server.lua | 8 ++++++++ fle/env/tools/agent/nearest_buildable/server.lua | 8 ++++++++ fle/env/tools/agent/pickup_entity/server.lua | 9 ++++++++- fle/env/tools/agent/place_entity/server.lua | 9 ++++++++- fle/env/tools/agent/place_entity_next_to/server.lua | 8 ++++++++ fle/env/tools/agent/print/server.lua | 9 ++++++++- fle/env/tools/agent/rotate_entity/server.lua | 9 ++++++++- fle/env/tools/agent/score/server.lua | 9 ++++++++- fle/env/tools/agent/send_message/server.lua | 9 ++++++++- fle/env/tools/agent/set_entity_recipe/server.lua | 8 ++++++++ fle/env/tools/agent/set_research/server.lua | 9 ++++++++- fle/env/tools/agent/sleep/server.lua | 9 ++++++++- 56 files changed, 448 insertions(+), 39 deletions(-) diff --git a/fle/env/mods/alerts.lua b/fle/env/mods/alerts.lua index 1221e83cd..2093df682 100644 --- a/fle/env/mods/alerts.lua +++ b/fle/env/mods/alerts.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.alerts = {} -- Define a function to check if the transport belt is blocked @@ -409,4 +413,7 @@ global.get_alerts = function(seconds) end -- Register the on_tick function to the on_tick event -script.on_event(defines.events.on_tick, on_tick) \ No newline at end of file +script.on_event(defines.events.on_tick, on_tick) +end + +M.initialize() diff --git a/fle/env/mods/checksum.lua b/fle/env/mods/checksum.lua index cd2c73dbe..245cb667c 100644 --- a/fle/env/mods/checksum.lua +++ b/fle/env/mods/checksum.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() if not global.__lua_script_checksums then global.__lua_script_checksums = {} end @@ -12,4 +16,7 @@ end global.clear_lua_script_checksums = function() global.__lua_script_checksums = {} -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/mods/connection_points.lua b/fle/env/mods/connection_points.lua index 31ece9561..4a4015067 100644 --- a/fle/env/mods/connection_points.lua +++ b/fle/env/mods/connection_points.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- This is primarily used to get the connection points of entities for the purpose of blocking them during pathing. -- This is to prevent bad cases where connections are blocked by belts / pipes etc. @@ -292,4 +296,7 @@ global.utils.get_refinery_connection_points = function(refinery) end return positions -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index 89ba09449..f2f95d5de 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() --- initialise.lua --- This file is used to initialise the global variables and functions. --- Ensure this is loaded first. Any variables or functions defined here will be available to all other scripts. @@ -43,3 +47,7 @@ if global.debug == nil then rendering = false -- Flag to toggle debug rendering of polygons and shapes } end + +end + +M.initialize() diff --git a/fle/env/mods/recipe_fluid_connection_mappings.lua b/fle/env/mods/recipe_fluid_connection_mappings.lua index c94bfd388..06ebdf8f6 100644 --- a/fle/env/mods/recipe_fluid_connection_mappings.lua +++ b/fle/env/mods/recipe_fluid_connection_mappings.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Helper function to get fluid type from ingredient/product local function get_fluid_type(item) if item.type == "fluid" then @@ -171,4 +175,7 @@ global.utils.get_chemical_plant_fluid_mappings = function(entity, recipe) inputs = input_points, outputs = output_points } -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/mods/serialize.lua b/fle/env/mods/serialize.lua index 7839573ec..bc8b00065 100644 --- a/fle/env/mods/serialize.lua +++ b/fle/env/mods/serialize.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Library for serializing items in Factorio -- Based on code from playerManager and trainTeleports @@ -1392,3 +1396,7 @@ global.utils.serialize_entity = function(entity) return serialized end + +end + +M.initialize() diff --git a/fle/env/mods/utils.lua b/fle/env/mods/utils.lua index a7b3ca2b9..4bebd4c48 100644 --- a/fle/env/mods/utils.lua +++ b/fle/env/mods/utils.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.utils.remove_enemies = function () game.forces["enemy"].kill_all_units() -- Removes all biters game.map_settings.enemy_expansion.enabled = false -- Stops biters from expanding @@ -256,4 +260,7 @@ function global.utils.inspect(player, radius, position) entity_data = filtered_entity_data return entity_data -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/clear_collision_boxes/server.lua b/fle/env/tools/admin/clear_collision_boxes/server.lua index 6d24e29d7..77e3fe0c5 100644 --- a/fle/env/tools/admin/clear_collision_boxes/server.lua +++ b/fle/env/tools/admin/clear_collision_boxes/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.clear_collision_boxes = function(player_index) local player = global.agent_characters[player_index] if not player then return end @@ -39,4 +43,7 @@ global.actions.clear_collision_boxes = function(player_index) entity.destroy() end end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/clear_entities/server.lua b/fle/env/tools/admin/clear_entities/server.lua index ea8224337..3cc63851e 100644 --- a/fle/env/tools/admin/clear_entities/server.lua +++ b/fle/env/tools/admin/clear_entities/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.clear_entities = function(player_index) local function clear_area_of_entities(player, area, force_filter) local surface = player.surface @@ -62,4 +66,7 @@ global.actions.clear_entities = function(player_index) reset_character_inventory(player) player.force.reset() return 1 -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/create_agent_characters/server.lua b/fle/env/tools/admin/create_agent_characters/server.lua index 6d16ac369..38e4be141 100644 --- a/fle/env/tools/admin/create_agent_characters/server.lua +++ b/fle/env/tools/admin/create_agent_characters/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Function to convert HSV to RGB local function hsv_to_rgb(h, s, v) @@ -61,4 +65,7 @@ global.actions.create_agent_characters = function(num_agents) -- Set the first character as the main player player = global.agent_characters[1] player.surface.always_day=true -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/extend_collision_boxes/server.lua b/fle/env/tools/admin/extend_collision_boxes/server.lua index 02c859524..744d70d49 100644 --- a/fle/env/tools/admin/extend_collision_boxes/server.lua +++ b/fle/env/tools/admin/extend_collision_boxes/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Helper function for pumpjack fluid positions --local function get_pumpjack_connection_positions(pumpjack) -- local positions = {} @@ -198,4 +202,7 @@ global.actions.extend_collision_boxes = function(player_index, start_x, start_y, global.clearance_entities[player_index] = created return true -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/get_factory_centroid/server.lua b/fle/env/tools/admin/get_factory_centroid/server.lua index edb18b325..427e93b3c 100644 --- a/fle/env/tools/admin/get_factory_centroid/server.lua +++ b/fle/env/tools/admin/get_factory_centroid/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Initialize global camera tracking variables in your script's initialization -- Put this in your script's on_init or when you initialize your global table function initialize_camera_tracking(initial_position) @@ -196,3 +200,7 @@ function update_camera_position() local dz = global.camera.target_zoom - global.camera.zoom global.camera.zoom = global.camera.zoom + dz * global.camera.zoom_smoothing end + +end + +M.initialize() diff --git a/fle/env/tools/admin/get_factory_centroid/server2.lua b/fle/env/tools/admin/get_factory_centroid/server2.lua index 5e158b5dd..f3803ca1f 100644 --- a/fle/env/tools/admin/get_factory_centroid/server2.lua +++ b/fle/env/tools/admin/get_factory_centroid/server2.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Helper function to calculate factory bounds function calculate_factory_bounds(force) local min_x = math.huge @@ -114,4 +118,7 @@ global.actions.get_factory_centroid = function(player) } return stats -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/get_messages/server.lua b/fle/env/tools/admin/get_messages/server.lua index b6f2de2f6..b25b16f01 100644 --- a/fle/env/tools/admin/get_messages/server.lua +++ b/fle/env/tools/admin/get_messages/server.lua @@ -1 +1,8 @@ --- Left blank intentionally - this functionality is handled by the A2A protocol \ No newline at end of file +local M = {} +M.events = {} + +function M.initialize() +-- Left blank intentionally - this functionality is handled by the A2A protocol +end + +M.initialize() diff --git a/fle/env/tools/admin/get_path/server.lua b/fle/env/tools/admin/get_path/server.lua index eb0a2e1f7..bc408073c 100644 --- a/fle/env/tools/admin/get_path/server.lua +++ b/fle/env/tools/admin/get_path/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Function to get the path as a JSON object global.actions.get_path = function(request_id) local request_data = global.path_requests[request_id] @@ -34,4 +38,7 @@ global.actions.get_path = function(request_id) waypoints = waypoints }) end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/get_production_stats/server.lua b/fle/env/tools/admin/get_production_stats/server.lua index ae38dbb2c..6de1e4fb4 100644 --- a/fle/env/tools/admin/get_production_stats/server.lua +++ b/fle/env/tools/admin/get_production_stats/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.production_stats = function(player) local production_diff = {} local consumption_diff = {} @@ -46,3 +50,7 @@ global.actions.reset_production_stats = function(player) global.crafted_items = {} end + +end + +M.initialize() diff --git a/fle/env/tools/admin/inspect_entities/server.lua b/fle/env/tools/admin/inspect_entities/server.lua index 4eafc05cf..327bf1334 100644 --- a/fle/env/tools/admin/inspect_entities/server.lua +++ b/fle/env/tools/admin/inspect_entities/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.inspect_entities = function(player_index, radius, position_x, position_y) local player = global.agent_characters[player_index] @@ -120,4 +124,7 @@ global.actions.inspect_entities = function(player_index, radius, position_x, pos end return dump(result) -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/load_blueprint/server.lua b/fle/env/tools/admin/load_blueprint/server.lua index 8209f62f2..4a210b370 100644 --- a/fle/env/tools/admin/load_blueprint/server.lua +++ b/fle/env/tools/admin/load_blueprint/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.load_blueprint = function(player_index, bp, offset_x, offset_y) --- https://forums.factorio.com/viewtopic.php?t=111437 --- by CharacterOverflow @ Wed Feb 21, 2024 2:34 pm @@ -62,3 +66,7 @@ global.actions.load_blueprint = function(player_index, bp, offset_x, offset_y) return stack_id end + +end + +M.initialize() diff --git a/fle/env/tools/admin/load_entity_state/server.lua b/fle/env/tools/admin/load_entity_state/server.lua index 8d78b6ec2..9a74a8dcf 100644 --- a/fle/env/tools/admin/load_entity_state/server.lua +++ b/fle/env/tools/admin/load_entity_state/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Helper function to unquote strings local function unquote_string(str) if not str then return nil end @@ -286,4 +290,7 @@ global.actions.load_entity_state = function(player, stored_json_data) end return true -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/load_research_state/server.lua b/fle/env/tools/admin/load_research_state/server.lua index 966ea298a..44cee4c4c 100644 --- a/fle/env/tools/admin/load_research_state/server.lua +++ b/fle/env/tools/admin/load_research_state/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Load research state global.actions.load_research_state = function(player_index, research_state) local player = global.agent_characters[player_index] @@ -45,4 +49,7 @@ global.actions.load_research_state = function(player_index, research_state) end return true -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/regenerate_resources/server.lua b/fle/env/tools/admin/regenerate_resources/server.lua index 0c49c7021..9f8825771 100644 --- a/fle/env/tools/admin/regenerate_resources/server.lua +++ b/fle/env/tools/admin/regenerate_resources/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.regenerate_resources = function(player_index) local player = global.agent_characters[player_index] local surface = player.surface @@ -34,4 +38,7 @@ global.actions.regenerate_resources2 = function(player_index) e.update_connections() end return 1 -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/render/server.lua b/fle/env/tools/admin/render/server.lua index 0f7481627..ddbd5ed55 100644 --- a/fle/env/tools/admin/render/server.lua +++ b/fle/env/tools/admin/render/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.render = function(player_index, method, arg1, arg2, arg3, arg4) local player = global.agent_characters[player_index] local position, bounding_box, radius @@ -155,3 +159,7 @@ global.actions.render = function(player_index, method, arg1, arg2, arg3, arg4) return dump(render_data) end + +end + +M.initialize() diff --git a/fle/env/tools/admin/render_message/server.lua b/fle/env/tools/admin/render_message/server.lua index 2a1df3622..eeb34a032 100644 --- a/fle/env/tools/admin/render_message/server.lua +++ b/fle/env/tools/admin/render_message/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.render_message = function(player_index, message) -- Get color based on player index local color = {r = 1, g = 1, b = 1} -- Default white @@ -16,3 +20,7 @@ global.actions.render_message = function(player_index, message) game.print(message, color) return true end + +end + +M.initialize() diff --git a/fle/env/tools/admin/request_path/server.lua b/fle/env/tools/admin/request_path/server.lua index 1f31d3452..10778da15 100644 --- a/fle/env/tools/admin/request_path/server.lua +++ b/fle/env/tools/admin/request_path/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Store created entities globally if not global.clearance_entities then global.clearance_entities = {} @@ -115,4 +119,7 @@ script.on_event(defines.events.on_script_path_request_finished, function(event) global.paths[event.id] = "not_found" -- log("Path not found for request ID: " .. event.id) end -end) \ No newline at end of file +end) +end + +M.initialize() diff --git a/fle/env/tools/admin/reset/server.lua b/fle/env/tools/admin/reset/server.lua index 5f442f31e..a5591221e 100644 --- a/fle/env/tools/admin/reset/server.lua +++ b/fle/env/tools/admin/reset/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() local function safe_json_to_table(json) if not json or json == '' then return {} end local ok, result = pcall(function() @@ -60,3 +64,7 @@ global.actions.reset = function(inventories_json, reset_position, all_technologi return 1 end + +end + +M.initialize() diff --git a/fle/env/tools/admin/save_blueprint/server.lua b/fle/env/tools/admin/save_blueprint/server.lua index 09be818ec..b45d2e2fd 100644 --- a/fle/env/tools/admin/save_blueprint/server.lua +++ b/fle/env/tools/admin/save_blueprint/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.save_blueprint = function(player_index) local player = global.agent_characters[player_index] local force = player.force @@ -58,4 +62,7 @@ global.actions.save_blueprint = function(player_index) bp.clear() return dump({blueprint='\"'..stack_string..'\"', center_x=center_x, center_y=center_y}) -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/save_entity_state/server.lua b/fle/env/tools/admin/save_entity_state/server.lua index f05c37864..7b91800d0 100644 --- a/fle/env/tools/admin/save_entity_state/server.lua +++ b/fle/env/tools/admin/save_entity_state/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Helper to ensure all numbers are serializable local function serialize_number(num) if num == math.huge then @@ -349,3 +353,7 @@ global.actions.save_entity_state = function(player_index, distance, player_entit end return entity_array end + +end + +M.initialize() diff --git a/fle/env/tools/admin/save_research_state/server.lua b/fle/env/tools/admin/save_research_state/server.lua index 728708532..dcfe4e0eb 100644 --- a/fle/env/tools/admin/save_research_state/server.lua +++ b/fle/env/tools/admin/save_research_state/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.save_research_state = function(player_index) local player = global.agent_characters[player_index] local force = player.force @@ -59,4 +63,7 @@ global.actions.save_research_state = function(player_index) end end return research_state -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/admin/set_inventory/server.lua b/fle/env/tools/admin/set_inventory/server.lua index 174c9fd46..1836641eb 100644 --- a/fle/env/tools/admin/set_inventory/server.lua +++ b/fle/env/tools/admin/set_inventory/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.set_inventory = function(player_index, item_names_and_counts_json) local player = global.agent_characters[player_index] player.clear_items_inside() @@ -8,4 +12,7 @@ global.actions.set_inventory = function(player_index, item_names_and_counts_json for item, count in pairs(item_names_and_counts) do player.get_main_inventory().insert{name=item, count=count} end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/can_place_entity/server.lua b/fle/env/tools/agent/can_place_entity/server.lua index 561f0d849..00ca3f41c 100644 --- a/fle/env/tools/agent/can_place_entity/server.lua +++ b/fle/env/tools/agent/can_place_entity/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.can_place_entity = function(player_index, entity, direction, x, y) local player = global.agent_characters[player_index] local position = {x = x, y = y} @@ -77,4 +81,7 @@ function get_entity_direction(entity, direction) else return cardinals[direction % 4] end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/connect_entities/server.lua b/fle/env/tools/agent/connect_entities/server.lua index cac4a5468..899c1def0 100644 --- a/fle/env/tools/agent/connect_entities/server.lua +++ b/fle/env/tools/agent/connect_entities/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- connect_entities local MAX_SERIALIZATION_ITERATIONS = 1000 -- Maximum iterations for serializing belt groups @@ -1304,4 +1308,7 @@ global.actions.connect_entities = function(player_index, source_x, source_y, tar end return result -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/craft_item/server.lua b/fle/env/tools/agent/craft_item/server.lua index 225b0b41b..4ba256cc1 100644 --- a/fle/env/tools/agent/craft_item/server.lua +++ b/fle/env/tools/agent/craft_item/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.craft_item = function(player_index, entity, count) local player = global.agent_characters[player_index] @@ -211,3 +215,7 @@ global.actions.craft_item = function(player_index, entity, count) count, entity, final_error)) end end + +end + +M.initialize() diff --git a/fle/env/tools/agent/extract_item/server.lua b/fle/env/tools/agent/extract_item/server.lua index 1d00a2fda..62914721d 100644 --- a/fle/env/tools/agent/extract_item/server.lua +++ b/fle/env/tools/agent/extract_item/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Helper function to check all possible inventories of an entity local function get_entity_item_count(entity, item_name) local inventory_types = { @@ -192,4 +196,7 @@ global.actions.extract_item = function(player_index, extract_item, count, x, y, -- This should rarely happen given our prior checks error("\"Failed to extract " .. extract_item .. "\"") end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/get_connection_amount/server.lua b/fle/env/tools/agent/get_connection_amount/server.lua index e69de29bb..ca6e9778f 100644 --- a/fle/env/tools/agent/get_connection_amount/server.lua +++ b/fle/env/tools/agent/get_connection_amount/server.lua @@ -0,0 +1,8 @@ +local M = {} +M.events = {} + +function M.initialize() + +end + +M.initialize() diff --git a/fle/env/tools/agent/get_entities/server.lua b/fle/env/tools/agent/get_entities/server.lua index 0b1440d8e..5bebd49f9 100644 --- a/fle/env/tools/agent/get_entities/server.lua +++ b/fle/env/tools/agent/get_entities/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.get_entities = function(player_index, radius, entity_names_json, position_x, position_y) local player = global.agent_characters[player_index] local position @@ -34,4 +38,7 @@ global.actions.get_entities = function(player_index, radius, entity_names_json, end end return dump(result) -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/get_entity/server.lua b/fle/env/tools/agent/get_entity/server.lua index deb63415e..9f2fc331b 100644 --- a/fle/env/tools/agent/get_entity/server.lua +++ b/fle/env/tools/agent/get_entity/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.get_entity = function(player_index, entity, x, y) local player = global.agent_characters[player_index] local position = {x=x, y=y} @@ -46,3 +50,7 @@ global.actions.get_entity = function(player_index, entity, x, y) error("\"No entity of type " .. entity .. " found at the specified position.\"") end end + +end + +M.initialize() diff --git a/fle/env/tools/agent/get_prototype_recipe/server.lua b/fle/env/tools/agent/get_prototype_recipe/server.lua index e3e7c41f9..51e1bad82 100644 --- a/fle/env/tools/agent/get_prototype_recipe/server.lua +++ b/fle/env/tools/agent/get_prototype_recipe/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.get_prototype_recipe = function(player_index, recipe_name) local player = global.agent_characters[player_index] local recipe = player.force.recipes[recipe_name] @@ -8,3 +12,7 @@ global.actions.get_prototype_recipe = function(player_index, recipe_name) return serialized end + +end + +M.initialize() diff --git a/fle/env/tools/agent/get_research_progress/server.lua b/fle/env/tools/agent/get_research_progress/server.lua index ba15fff0f..005a0a9e2 100644 --- a/fle/env/tools/agent/get_research_progress/server.lua +++ b/fle/env/tools/agent/get_research_progress/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.get_research_progress = function(player_index, technology_name) local player = global.agent_characters[player_index] local force = player.force @@ -44,4 +48,7 @@ global.actions.get_research_progress = function(player_index, technology_name) end return ingredients -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/get_resource_patch/server.lua b/fle/env/tools/agent/get_resource_patch/server.lua index 7b59db8e8..0ef75167c 100644 --- a/fle/env/tools/agent/get_resource_patch/server.lua +++ b/fle/env/tools/agent/get_resource_patch/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.get_resource_patch = function(player_index, resource, x, y, radius) local player = global.agent_characters[player_index] local position = {x = x, y = y} @@ -102,4 +106,7 @@ global.actions.get_resource_patch = function(player_index, resource, x, y, radiu render_box(bounding_box) return {bounding_box = bounding_box, size = total_resource} end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/harvest_resource/server.lua b/fle/env/tools/agent/harvest_resource/server.lua index 9cea09536..bccf63a28 100644 --- a/fle/env/tools/agent/harvest_resource/server.lua +++ b/fle/env/tools/agent/harvest_resource/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() --- global.actions.harvest_resource(player_index, x, y, count, radius) local function calculate_mining_ticks(entity) local mining_time = entity.prototype.mineable_properties.mining_time or 1 @@ -619,4 +623,7 @@ global.actions.get_resource_name_at_position = function(player_index, x, y) entity_name = entities[1].name end return entity_name -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/insert_item/server.lua b/fle/env/tools/agent/insert_item/server.lua index a98cf25be..92627f112 100644 --- a/fle/env/tools/agent/insert_item/server.lua +++ b/fle/env/tools/agent/insert_item/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Function to get inventory fullness information local function get_inventory_info(entity) if entity.get_inventory then @@ -241,4 +245,7 @@ global.actions.insert_item = function(player_index, insert_item, count, x, y, ta ) error(error_msg) end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/inspect_inventory/server.lua b/fle/env/tools/agent/inspect_inventory/server.lua index cd15c44e9..9d3eb9994 100644 --- a/fle/env/tools/agent/inspect_inventory/server.lua +++ b/fle/env/tools/agent/inspect_inventory/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.inspect_inventory = function(player_index, is_character_inventory, x, y, entity, all_players) local position = {x=x, y=y} local player = global.agent_characters[player_index] @@ -132,4 +136,7 @@ global.actions.inspect_inventory = function(player_index, is_character_inventory error("Could not get inventory of entity at "..x..", "..y) end end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/launch_rocket/server.lua b/fle/env/tools/agent/launch_rocket/server.lua index 5da6ead5f..e77590c79 100644 --- a/fle/env/tools/agent/launch_rocket/server.lua +++ b/fle/env/tools/agent/launch_rocket/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Function to find a rocket silo at the given position local function find_rocket_silo(surface, position) local silo = surface.find_entities_filtered{ @@ -41,4 +45,7 @@ global.actions.launch_rocket = function(x, y) silo.launch_rocket() game.print("Rocket launched successfully!") return true -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/move_to/server.lua b/fle/env/tools/agent/move_to/server.lua index a03dce61f..ff0815929 100644 --- a/fle/env/tools/agent/move_to/server.lua +++ b/fle/env/tools/agent/move_to/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- move_to -- Register the tick handler when the module is loaded @@ -262,4 +266,7 @@ global.actions.get_walking_queue_length = function(player_index) return #global.walking_queues[player_index].positions end return 0 -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/nearest/server.lua b/fle/env/tools/agent/nearest/server.lua index 430c9a1d7..ef849f133 100644 --- a/fle/env/tools/agent/nearest/server.lua +++ b/fle/env/tools/agent/nearest/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.nearest = function(player_index, resource) local player = global.agent_characters[player_index] @@ -69,3 +73,7 @@ global.actions.nearest = function(player_index, resource) return find_nearest(player, normalized_resource) end + +end + +M.initialize() diff --git a/fle/env/tools/agent/nearest_buildable/server.lua b/fle/env/tools/agent/nearest_buildable/server.lua index c4c7ed83f..eb3ec8bcc 100644 --- a/fle/env/tools/agent/nearest_buildable/server.lua +++ b/fle/env/tools/agent/nearest_buildable/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Cache math functions local floor = math.floor local ceil = math.ceil @@ -187,3 +191,7 @@ global.actions.nearest_buildable = function(player_index, entity_name, bounding_ return spiral_search() end + +end + +M.initialize() diff --git a/fle/env/tools/agent/pickup_entity/server.lua b/fle/env/tools/agent/pickup_entity/server.lua index f5225e88d..3de8be69f 100644 --- a/fle/env/tools/agent/pickup_entity/server.lua +++ b/fle/env/tools/agent/pickup_entity/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.pickup_entity = function(player_index, x, y, entity) local player = global.agent_characters[player_index] local position = {x=x, y=y} @@ -151,4 +155,7 @@ global.actions.pickup_entity = function(player_index, x, y, entity) end return {} -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/place_entity/server.lua b/fle/env/tools/agent/place_entity/server.lua index 2ad9ea40d..0d3228b73 100644 --- a/fle/env/tools/agent/place_entity/server.lua +++ b/fle/env/tools/agent/place_entity/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() -- Helper to convert surface direction to entity direction local function surface_to_entity_direction(surface_dir) -- In Factorio, offshore pumps face opposite to placement direction @@ -356,4 +360,7 @@ global.actions.place_entity = function(player_index, entity, direction, x, y, ex local result = slow_place() return result end -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/place_entity_next_to/server.lua b/fle/env/tools/agent/place_entity_next_to/server.lua index 28c2f6328..ce169f367 100644 --- a/fle/env/tools/agent/place_entity_next_to/server.lua +++ b/fle/env/tools/agent/place_entity_next_to/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() local function validate_mining_drill_placement(surface, position, entity_name) -- Check if the entity is a mining drill local prototype = game.entity_prototypes[entity_name] @@ -358,3 +362,7 @@ global.actions.place_entity_next_to = function(player_index, entity, ref_x, ref_ error("Not enough items in inventory.") end end + +end + +M.initialize() diff --git a/fle/env/tools/agent/print/server.lua b/fle/env/tools/agent/print/server.lua index 124d2842b..2c9c453ca 100644 --- a/fle/env/tools/agent/print/server.lua +++ b/fle/env/tools/agent/print/server.lua @@ -1,4 +1,11 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.print = function(message) message = dump(message) return '"'..message..'"' -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/rotate_entity/server.lua b/fle/env/tools/agent/rotate_entity/server.lua index 2d0e7f1c2..bacbfa4c7 100644 --- a/fle/env/tools/agent/rotate_entity/server.lua +++ b/fle/env/tools/agent/rotate_entity/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.rotate_entity = function(player_index, x, y, direction, entity) local player = global.agent_characters[player_index] local position = {x=x, y=y} -- Round to nearest tile @@ -61,4 +65,7 @@ global.actions.rotate_entity = function(player_index, x, y, direction, entity) local serialized = global.utils.serialize_entity(closest_entity) return serialized -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/score/server.lua b/fle/env/tools/agent/score/server.lua index efa49b53b..d29fc713b 100644 --- a/fle/env/tools/agent/score/server.lua +++ b/fle/env/tools/agent/score/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() local function get_total_production_counts(production_statistics) local produced = production_statistics.input_counts local consumed = production_statistics.output_counts @@ -368,4 +372,7 @@ global.actions.score = function() return dump(production_score) end return dump(production_score) -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/send_message/server.lua b/fle/env/tools/agent/send_message/server.lua index b6f2de2f6..b25b16f01 100644 --- a/fle/env/tools/agent/send_message/server.lua +++ b/fle/env/tools/agent/send_message/server.lua @@ -1 +1,8 @@ --- Left blank intentionally - this functionality is handled by the A2A protocol \ No newline at end of file +local M = {} +M.events = {} + +function M.initialize() +-- Left blank intentionally - this functionality is handled by the A2A protocol +end + +M.initialize() diff --git a/fle/env/tools/agent/set_entity_recipe/server.lua b/fle/env/tools/agent/set_entity_recipe/server.lua index d57a87849..ea185dfb6 100644 --- a/fle/env/tools/agent/set_entity_recipe/server.lua +++ b/fle/env/tools/agent/set_entity_recipe/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.set_entity_recipe = function(player_index, recipe_name, x, y) local player = global.agent_characters[player_index] local surface = player.surface @@ -46,3 +50,7 @@ global.actions.set_entity_recipe = function(player_index, recipe_name, x, y) end end + +end + +M.initialize() diff --git a/fle/env/tools/agent/set_research/server.lua b/fle/env/tools/agent/set_research/server.lua index cdb262e7d..eeadf2c08 100644 --- a/fle/env/tools/agent/set_research/server.lua +++ b/fle/env/tools/agent/set_research/server.lua @@ -1,3 +1,7 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.set_research = function(player_index, technology_name) local player = global.agent_characters[player_index] local force = player.force @@ -71,4 +75,7 @@ global.actions.set_research = function(player_index, technology_name) end return ingredients -end \ No newline at end of file +end +end + +M.initialize() diff --git a/fle/env/tools/agent/sleep/server.lua b/fle/env/tools/agent/sleep/server.lua index 5373f331a..0c968504f 100644 --- a/fle/env/tools/agent/sleep/server.lua +++ b/fle/env/tools/agent/sleep/server.lua @@ -1,6 +1,13 @@ +local M = {} +M.events = {} + +function M.initialize() global.actions.sleep = function(ticks_elapsed) if ticks_elapsed > 0 then global.elapsed_ticks = global.elapsed_ticks + ticks_elapsed end return game.tick -end \ No newline at end of file +end +end + +M.initialize() From bf32166a0c6a61c47733a44cd95c81bb5aab40bd Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 11:42:01 +0530 Subject: [PATCH 29/35] proper modules + replace --- fle/env/lua_manager.py | 7 +++++++ fle/env/mods/alerts.lua | 2 +- fle/env/mods/checksum.lua | 2 +- fle/env/mods/connection_points.lua | 2 +- fle/env/mods/initialise.lua | 2 +- fle/env/mods/recipe_fluid_connection_mappings.lua | 2 +- fle/env/mods/serialize.lua | 2 +- fle/env/mods/utils.lua | 2 +- fle/env/tools/admin/clear_collision_boxes/server.lua | 2 +- fle/env/tools/admin/clear_entities/server.lua | 2 +- fle/env/tools/admin/create_agent_characters/server.lua | 2 +- fle/env/tools/admin/extend_collision_boxes/server.lua | 2 +- fle/env/tools/admin/get_factory_centroid/server.lua | 2 +- fle/env/tools/admin/get_factory_centroid/server2.lua | 2 +- fle/env/tools/admin/get_messages/server.lua | 2 +- fle/env/tools/admin/get_path/server.lua | 2 +- fle/env/tools/admin/get_production_stats/server.lua | 2 +- fle/env/tools/admin/inspect_entities/server.lua | 2 +- fle/env/tools/admin/load_blueprint/server.lua | 2 +- fle/env/tools/admin/load_entity_state/server.lua | 2 +- fle/env/tools/admin/load_research_state/server.lua | 2 +- fle/env/tools/admin/regenerate_resources/server.lua | 2 +- fle/env/tools/admin/render/server.lua | 2 +- fle/env/tools/admin/render_message/server.lua | 2 +- fle/env/tools/admin/request_path/server.lua | 2 +- fle/env/tools/admin/reset/server.lua | 2 +- fle/env/tools/admin/save_blueprint/server.lua | 2 +- fle/env/tools/admin/save_entity_state/server.lua | 2 +- fle/env/tools/admin/save_research_state/server.lua | 2 +- fle/env/tools/admin/set_inventory/server.lua | 2 +- fle/env/tools/agent/can_place_entity/server.lua | 2 +- fle/env/tools/agent/connect_entities/server.lua | 2 +- fle/env/tools/agent/craft_item/server.lua | 2 +- fle/env/tools/agent/extract_item/server.lua | 2 +- fle/env/tools/agent/get_connection_amount/server.lua | 2 +- fle/env/tools/agent/get_entities/server.lua | 2 +- fle/env/tools/agent/get_entity/server.lua | 2 +- fle/env/tools/agent/get_prototype_recipe/server.lua | 2 +- fle/env/tools/agent/get_research_progress/server.lua | 2 +- fle/env/tools/agent/get_resource_patch/server.lua | 2 +- fle/env/tools/agent/harvest_resource/server.lua | 2 +- fle/env/tools/agent/insert_item/server.lua | 2 +- fle/env/tools/agent/inspect_inventory/server.lua | 2 +- fle/env/tools/agent/launch_rocket/server.lua | 2 +- fle/env/tools/agent/move_to/server.lua | 2 +- fle/env/tools/agent/nearest/server.lua | 2 +- fle/env/tools/agent/nearest_buildable/server.lua | 2 +- fle/env/tools/agent/pickup_entity/server.lua | 2 +- fle/env/tools/agent/place_entity/server.lua | 2 +- fle/env/tools/agent/place_entity_next_to/server.lua | 2 +- fle/env/tools/agent/print/server.lua | 2 +- fle/env/tools/agent/rotate_entity/server.lua | 2 +- fle/env/tools/agent/score/server.lua | 2 +- fle/env/tools/agent/send_message/server.lua | 2 +- fle/env/tools/agent/set_entity_recipe/server.lua | 2 +- fle/env/tools/agent/set_research/server.lua | 2 +- fle/env/tools/agent/sleep/server.lua | 2 +- 57 files changed, 63 insertions(+), 56 deletions(-) diff --git a/fle/env/lua_manager.py b/fle/env/lua_manager.py index a1d939f49..3932c2fab 100644 --- a/fle/env/lua_manager.py +++ b/fle/env/lua_manager.py @@ -3,6 +3,7 @@ import importlib import os +import re from pathlib import Path from lupa.lua54 import LuaRuntime @@ -36,8 +37,12 @@ def __init__(self, rcon_client: RCONClient, cache_scripts: bool = False): self.lib_scripts = self.get_libs_to_load() self.lua = LuaRuntime(unpack_returned_tuples=True) + def sub_initialize(self, script): + return re.sub(r"(?m)^\s*return\s+M\s*$", "M.initialize()\nreturn M", script) + def init_action_checksums(self): checksum_init_script = _load_mods("checksum") + checksum_init_script = self.sub_initialize(checksum_init_script) response = self.rcon_client.send_command("/sc " + checksum_init_script) return response @@ -123,6 +128,7 @@ def get_tools_to_load(self): # Load the lua script content _, content = _load_script(lua_file) + content = self.sub_initialize(content) # Create a unique key combining tool and script name script_key = f"{tool_name}/{script_name}" if tool_name else script_name @@ -143,6 +149,7 @@ def get_libs_to_load(self): scripts_to_load = {} for filename in _get_lib_names(): name, content = _load_script(filename) + content = self.sub_initialize(content) if self.cache_scripts: checksum = self.calculate_checksum(content) diff --git a/fle/env/mods/alerts.lua b/fle/env/mods/alerts.lua index 2093df682..5b1b8f90b 100644 --- a/fle/env/mods/alerts.lua +++ b/fle/env/mods/alerts.lua @@ -416,4 +416,4 @@ end script.on_event(defines.events.on_tick, on_tick) end -M.initialize() +return M diff --git a/fle/env/mods/checksum.lua b/fle/env/mods/checksum.lua index 245cb667c..2cc7b6e4b 100644 --- a/fle/env/mods/checksum.lua +++ b/fle/env/mods/checksum.lua @@ -19,4 +19,4 @@ global.clear_lua_script_checksums = function() end end -M.initialize() +return M diff --git a/fle/env/mods/connection_points.lua b/fle/env/mods/connection_points.lua index 4a4015067..577b537b4 100644 --- a/fle/env/mods/connection_points.lua +++ b/fle/env/mods/connection_points.lua @@ -299,4 +299,4 @@ global.utils.get_refinery_connection_points = function(refinery) end end -M.initialize() +return M diff --git a/fle/env/mods/initialise.lua b/fle/env/mods/initialise.lua index f2f95d5de..0b39434d3 100644 --- a/fle/env/mods/initialise.lua +++ b/fle/env/mods/initialise.lua @@ -50,4 +50,4 @@ end end -M.initialize() +return M diff --git a/fle/env/mods/recipe_fluid_connection_mappings.lua b/fle/env/mods/recipe_fluid_connection_mappings.lua index 06ebdf8f6..c7a9c9999 100644 --- a/fle/env/mods/recipe_fluid_connection_mappings.lua +++ b/fle/env/mods/recipe_fluid_connection_mappings.lua @@ -178,4 +178,4 @@ global.utils.get_chemical_plant_fluid_mappings = function(entity, recipe) end end -M.initialize() +return M diff --git a/fle/env/mods/serialize.lua b/fle/env/mods/serialize.lua index bc8b00065..b55104e1d 100644 --- a/fle/env/mods/serialize.lua +++ b/fle/env/mods/serialize.lua @@ -1399,4 +1399,4 @@ end end -M.initialize() +return M diff --git a/fle/env/mods/utils.lua b/fle/env/mods/utils.lua index 4bebd4c48..67de3190c 100644 --- a/fle/env/mods/utils.lua +++ b/fle/env/mods/utils.lua @@ -263,4 +263,4 @@ function global.utils.inspect(player, radius, position) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/clear_collision_boxes/server.lua b/fle/env/tools/admin/clear_collision_boxes/server.lua index 77e3fe0c5..ce7d59f1d 100644 --- a/fle/env/tools/admin/clear_collision_boxes/server.lua +++ b/fle/env/tools/admin/clear_collision_boxes/server.lua @@ -46,4 +46,4 @@ global.actions.clear_collision_boxes = function(player_index) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/clear_entities/server.lua b/fle/env/tools/admin/clear_entities/server.lua index 3cc63851e..22c99ecaa 100644 --- a/fle/env/tools/admin/clear_entities/server.lua +++ b/fle/env/tools/admin/clear_entities/server.lua @@ -69,4 +69,4 @@ global.actions.clear_entities = function(player_index) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/create_agent_characters/server.lua b/fle/env/tools/admin/create_agent_characters/server.lua index 38e4be141..31af5f40d 100644 --- a/fle/env/tools/admin/create_agent_characters/server.lua +++ b/fle/env/tools/admin/create_agent_characters/server.lua @@ -68,4 +68,4 @@ global.actions.create_agent_characters = function(num_agents) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/extend_collision_boxes/server.lua b/fle/env/tools/admin/extend_collision_boxes/server.lua index 744d70d49..8e11874ff 100644 --- a/fle/env/tools/admin/extend_collision_boxes/server.lua +++ b/fle/env/tools/admin/extend_collision_boxes/server.lua @@ -205,4 +205,4 @@ global.actions.extend_collision_boxes = function(player_index, start_x, start_y, end end -M.initialize() +return M diff --git a/fle/env/tools/admin/get_factory_centroid/server.lua b/fle/env/tools/admin/get_factory_centroid/server.lua index 427e93b3c..7398a75e2 100644 --- a/fle/env/tools/admin/get_factory_centroid/server.lua +++ b/fle/env/tools/admin/get_factory_centroid/server.lua @@ -203,4 +203,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/admin/get_factory_centroid/server2.lua b/fle/env/tools/admin/get_factory_centroid/server2.lua index f3803ca1f..cecbcfe86 100644 --- a/fle/env/tools/admin/get_factory_centroid/server2.lua +++ b/fle/env/tools/admin/get_factory_centroid/server2.lua @@ -121,4 +121,4 @@ global.actions.get_factory_centroid = function(player) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/get_messages/server.lua b/fle/env/tools/admin/get_messages/server.lua index b25b16f01..a83b6e206 100644 --- a/fle/env/tools/admin/get_messages/server.lua +++ b/fle/env/tools/admin/get_messages/server.lua @@ -5,4 +5,4 @@ function M.initialize() -- Left blank intentionally - this functionality is handled by the A2A protocol end -M.initialize() +return M diff --git a/fle/env/tools/admin/get_path/server.lua b/fle/env/tools/admin/get_path/server.lua index bc408073c..7d4520045 100644 --- a/fle/env/tools/admin/get_path/server.lua +++ b/fle/env/tools/admin/get_path/server.lua @@ -41,4 +41,4 @@ global.actions.get_path = function(request_id) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/get_production_stats/server.lua b/fle/env/tools/admin/get_production_stats/server.lua index 6de1e4fb4..21f59bfbb 100644 --- a/fle/env/tools/admin/get_production_stats/server.lua +++ b/fle/env/tools/admin/get_production_stats/server.lua @@ -53,4 +53,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/admin/inspect_entities/server.lua b/fle/env/tools/admin/inspect_entities/server.lua index 327bf1334..e2b4f517d 100644 --- a/fle/env/tools/admin/inspect_entities/server.lua +++ b/fle/env/tools/admin/inspect_entities/server.lua @@ -127,4 +127,4 @@ global.actions.inspect_entities = function(player_index, radius, position_x, pos end end -M.initialize() +return M diff --git a/fle/env/tools/admin/load_blueprint/server.lua b/fle/env/tools/admin/load_blueprint/server.lua index 4a210b370..5e76eafeb 100644 --- a/fle/env/tools/admin/load_blueprint/server.lua +++ b/fle/env/tools/admin/load_blueprint/server.lua @@ -69,4 +69,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/admin/load_entity_state/server.lua b/fle/env/tools/admin/load_entity_state/server.lua index 9a74a8dcf..1541201cd 100644 --- a/fle/env/tools/admin/load_entity_state/server.lua +++ b/fle/env/tools/admin/load_entity_state/server.lua @@ -293,4 +293,4 @@ global.actions.load_entity_state = function(player, stored_json_data) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/load_research_state/server.lua b/fle/env/tools/admin/load_research_state/server.lua index 44cee4c4c..46ac20182 100644 --- a/fle/env/tools/admin/load_research_state/server.lua +++ b/fle/env/tools/admin/load_research_state/server.lua @@ -52,4 +52,4 @@ global.actions.load_research_state = function(player_index, research_state) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/regenerate_resources/server.lua b/fle/env/tools/admin/regenerate_resources/server.lua index 9f8825771..e801f5d72 100644 --- a/fle/env/tools/admin/regenerate_resources/server.lua +++ b/fle/env/tools/admin/regenerate_resources/server.lua @@ -41,4 +41,4 @@ global.actions.regenerate_resources2 = function(player_index) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/render/server.lua b/fle/env/tools/admin/render/server.lua index ddbd5ed55..7edb585df 100644 --- a/fle/env/tools/admin/render/server.lua +++ b/fle/env/tools/admin/render/server.lua @@ -162,4 +162,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/admin/render_message/server.lua b/fle/env/tools/admin/render_message/server.lua index eeb34a032..911801bfb 100644 --- a/fle/env/tools/admin/render_message/server.lua +++ b/fle/env/tools/admin/render_message/server.lua @@ -23,4 +23,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/admin/request_path/server.lua b/fle/env/tools/admin/request_path/server.lua index 10778da15..5682636a6 100644 --- a/fle/env/tools/admin/request_path/server.lua +++ b/fle/env/tools/admin/request_path/server.lua @@ -122,4 +122,4 @@ script.on_event(defines.events.on_script_path_request_finished, function(event) end) end -M.initialize() +return M diff --git a/fle/env/tools/admin/reset/server.lua b/fle/env/tools/admin/reset/server.lua index a5591221e..69eec6d6d 100644 --- a/fle/env/tools/admin/reset/server.lua +++ b/fle/env/tools/admin/reset/server.lua @@ -67,4 +67,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/admin/save_blueprint/server.lua b/fle/env/tools/admin/save_blueprint/server.lua index b45d2e2fd..e0915a796 100644 --- a/fle/env/tools/admin/save_blueprint/server.lua +++ b/fle/env/tools/admin/save_blueprint/server.lua @@ -65,4 +65,4 @@ global.actions.save_blueprint = function(player_index) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/save_entity_state/server.lua b/fle/env/tools/admin/save_entity_state/server.lua index 7b91800d0..969e9e099 100644 --- a/fle/env/tools/admin/save_entity_state/server.lua +++ b/fle/env/tools/admin/save_entity_state/server.lua @@ -356,4 +356,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/admin/save_research_state/server.lua b/fle/env/tools/admin/save_research_state/server.lua index dcfe4e0eb..2bfe88003 100644 --- a/fle/env/tools/admin/save_research_state/server.lua +++ b/fle/env/tools/admin/save_research_state/server.lua @@ -66,4 +66,4 @@ global.actions.save_research_state = function(player_index) end end -M.initialize() +return M diff --git a/fle/env/tools/admin/set_inventory/server.lua b/fle/env/tools/admin/set_inventory/server.lua index 1836641eb..6f7d366ed 100644 --- a/fle/env/tools/admin/set_inventory/server.lua +++ b/fle/env/tools/admin/set_inventory/server.lua @@ -15,4 +15,4 @@ global.actions.set_inventory = function(player_index, item_names_and_counts_json end end -M.initialize() +return M diff --git a/fle/env/tools/agent/can_place_entity/server.lua b/fle/env/tools/agent/can_place_entity/server.lua index 00ca3f41c..25afc7630 100644 --- a/fle/env/tools/agent/can_place_entity/server.lua +++ b/fle/env/tools/agent/can_place_entity/server.lua @@ -84,4 +84,4 @@ function get_entity_direction(entity, direction) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/connect_entities/server.lua b/fle/env/tools/agent/connect_entities/server.lua index 899c1def0..cea31dd0d 100644 --- a/fle/env/tools/agent/connect_entities/server.lua +++ b/fle/env/tools/agent/connect_entities/server.lua @@ -1311,4 +1311,4 @@ global.actions.connect_entities = function(player_index, source_x, source_y, tar end end -M.initialize() +return M diff --git a/fle/env/tools/agent/craft_item/server.lua b/fle/env/tools/agent/craft_item/server.lua index 4ba256cc1..4883e29e0 100644 --- a/fle/env/tools/agent/craft_item/server.lua +++ b/fle/env/tools/agent/craft_item/server.lua @@ -218,4 +218,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/agent/extract_item/server.lua b/fle/env/tools/agent/extract_item/server.lua index 62914721d..9e6f46c5f 100644 --- a/fle/env/tools/agent/extract_item/server.lua +++ b/fle/env/tools/agent/extract_item/server.lua @@ -199,4 +199,4 @@ global.actions.extract_item = function(player_index, extract_item, count, x, y, end end -M.initialize() +return M diff --git a/fle/env/tools/agent/get_connection_amount/server.lua b/fle/env/tools/agent/get_connection_amount/server.lua index ca6e9778f..c27e2b9e8 100644 --- a/fle/env/tools/agent/get_connection_amount/server.lua +++ b/fle/env/tools/agent/get_connection_amount/server.lua @@ -5,4 +5,4 @@ function M.initialize() end -M.initialize() +return M diff --git a/fle/env/tools/agent/get_entities/server.lua b/fle/env/tools/agent/get_entities/server.lua index 5bebd49f9..918435583 100644 --- a/fle/env/tools/agent/get_entities/server.lua +++ b/fle/env/tools/agent/get_entities/server.lua @@ -41,4 +41,4 @@ global.actions.get_entities = function(player_index, radius, entity_names_json, end end -M.initialize() +return M diff --git a/fle/env/tools/agent/get_entity/server.lua b/fle/env/tools/agent/get_entity/server.lua index 9f2fc331b..ff8e3a7d2 100644 --- a/fle/env/tools/agent/get_entity/server.lua +++ b/fle/env/tools/agent/get_entity/server.lua @@ -53,4 +53,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/agent/get_prototype_recipe/server.lua b/fle/env/tools/agent/get_prototype_recipe/server.lua index 51e1bad82..b3f8b623f 100644 --- a/fle/env/tools/agent/get_prototype_recipe/server.lua +++ b/fle/env/tools/agent/get_prototype_recipe/server.lua @@ -15,4 +15,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/agent/get_research_progress/server.lua b/fle/env/tools/agent/get_research_progress/server.lua index 005a0a9e2..254174b01 100644 --- a/fle/env/tools/agent/get_research_progress/server.lua +++ b/fle/env/tools/agent/get_research_progress/server.lua @@ -51,4 +51,4 @@ global.actions.get_research_progress = function(player_index, technology_name) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/get_resource_patch/server.lua b/fle/env/tools/agent/get_resource_patch/server.lua index 0ef75167c..f959f8d0a 100644 --- a/fle/env/tools/agent/get_resource_patch/server.lua +++ b/fle/env/tools/agent/get_resource_patch/server.lua @@ -109,4 +109,4 @@ global.actions.get_resource_patch = function(player_index, resource, x, y, radiu end end -M.initialize() +return M diff --git a/fle/env/tools/agent/harvest_resource/server.lua b/fle/env/tools/agent/harvest_resource/server.lua index bccf63a28..a4865118d 100644 --- a/fle/env/tools/agent/harvest_resource/server.lua +++ b/fle/env/tools/agent/harvest_resource/server.lua @@ -626,4 +626,4 @@ global.actions.get_resource_name_at_position = function(player_index, x, y) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/insert_item/server.lua b/fle/env/tools/agent/insert_item/server.lua index 92627f112..eb867b913 100644 --- a/fle/env/tools/agent/insert_item/server.lua +++ b/fle/env/tools/agent/insert_item/server.lua @@ -248,4 +248,4 @@ global.actions.insert_item = function(player_index, insert_item, count, x, y, ta end end -M.initialize() +return M diff --git a/fle/env/tools/agent/inspect_inventory/server.lua b/fle/env/tools/agent/inspect_inventory/server.lua index 9d3eb9994..4825fbe99 100644 --- a/fle/env/tools/agent/inspect_inventory/server.lua +++ b/fle/env/tools/agent/inspect_inventory/server.lua @@ -139,4 +139,4 @@ global.actions.inspect_inventory = function(player_index, is_character_inventory end end -M.initialize() +return M diff --git a/fle/env/tools/agent/launch_rocket/server.lua b/fle/env/tools/agent/launch_rocket/server.lua index e77590c79..be12e9ec1 100644 --- a/fle/env/tools/agent/launch_rocket/server.lua +++ b/fle/env/tools/agent/launch_rocket/server.lua @@ -48,4 +48,4 @@ global.actions.launch_rocket = function(x, y) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/move_to/server.lua b/fle/env/tools/agent/move_to/server.lua index ff0815929..7b4cba81b 100644 --- a/fle/env/tools/agent/move_to/server.lua +++ b/fle/env/tools/agent/move_to/server.lua @@ -269,4 +269,4 @@ global.actions.get_walking_queue_length = function(player_index) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/nearest/server.lua b/fle/env/tools/agent/nearest/server.lua index ef849f133..337bde5f6 100644 --- a/fle/env/tools/agent/nearest/server.lua +++ b/fle/env/tools/agent/nearest/server.lua @@ -76,4 +76,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/agent/nearest_buildable/server.lua b/fle/env/tools/agent/nearest_buildable/server.lua index eb3ec8bcc..358e3cba0 100644 --- a/fle/env/tools/agent/nearest_buildable/server.lua +++ b/fle/env/tools/agent/nearest_buildable/server.lua @@ -194,4 +194,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/agent/pickup_entity/server.lua b/fle/env/tools/agent/pickup_entity/server.lua index 3de8be69f..029db4902 100644 --- a/fle/env/tools/agent/pickup_entity/server.lua +++ b/fle/env/tools/agent/pickup_entity/server.lua @@ -158,4 +158,4 @@ global.actions.pickup_entity = function(player_index, x, y, entity) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/place_entity/server.lua b/fle/env/tools/agent/place_entity/server.lua index 0d3228b73..1cd1aa7a4 100644 --- a/fle/env/tools/agent/place_entity/server.lua +++ b/fle/env/tools/agent/place_entity/server.lua @@ -363,4 +363,4 @@ global.actions.place_entity = function(player_index, entity, direction, x, y, ex end end -M.initialize() +return M diff --git a/fle/env/tools/agent/place_entity_next_to/server.lua b/fle/env/tools/agent/place_entity_next_to/server.lua index ce169f367..8cd0d015c 100644 --- a/fle/env/tools/agent/place_entity_next_to/server.lua +++ b/fle/env/tools/agent/place_entity_next_to/server.lua @@ -365,4 +365,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/agent/print/server.lua b/fle/env/tools/agent/print/server.lua index 2c9c453ca..93ede4658 100644 --- a/fle/env/tools/agent/print/server.lua +++ b/fle/env/tools/agent/print/server.lua @@ -8,4 +8,4 @@ global.actions.print = function(message) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/rotate_entity/server.lua b/fle/env/tools/agent/rotate_entity/server.lua index bacbfa4c7..740a2ecb1 100644 --- a/fle/env/tools/agent/rotate_entity/server.lua +++ b/fle/env/tools/agent/rotate_entity/server.lua @@ -68,4 +68,4 @@ global.actions.rotate_entity = function(player_index, x, y, direction, entity) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/score/server.lua b/fle/env/tools/agent/score/server.lua index d29fc713b..5df5d82a9 100644 --- a/fle/env/tools/agent/score/server.lua +++ b/fle/env/tools/agent/score/server.lua @@ -375,4 +375,4 @@ global.actions.score = function() end end -M.initialize() +return M diff --git a/fle/env/tools/agent/send_message/server.lua b/fle/env/tools/agent/send_message/server.lua index b25b16f01..a83b6e206 100644 --- a/fle/env/tools/agent/send_message/server.lua +++ b/fle/env/tools/agent/send_message/server.lua @@ -5,4 +5,4 @@ function M.initialize() -- Left blank intentionally - this functionality is handled by the A2A protocol end -M.initialize() +return M diff --git a/fle/env/tools/agent/set_entity_recipe/server.lua b/fle/env/tools/agent/set_entity_recipe/server.lua index ea185dfb6..9719dc744 100644 --- a/fle/env/tools/agent/set_entity_recipe/server.lua +++ b/fle/env/tools/agent/set_entity_recipe/server.lua @@ -53,4 +53,4 @@ end end -M.initialize() +return M diff --git a/fle/env/tools/agent/set_research/server.lua b/fle/env/tools/agent/set_research/server.lua index eeadf2c08..4b19d8fc1 100644 --- a/fle/env/tools/agent/set_research/server.lua +++ b/fle/env/tools/agent/set_research/server.lua @@ -78,4 +78,4 @@ global.actions.set_research = function(player_index, technology_name) end end -M.initialize() +return M diff --git a/fle/env/tools/agent/sleep/server.lua b/fle/env/tools/agent/sleep/server.lua index 0c968504f..9491b2fd1 100644 --- a/fle/env/tools/agent/sleep/server.lua +++ b/fle/env/tools/agent/sleep/server.lua @@ -10,4 +10,4 @@ global.actions.sleep = function(ticks_elapsed) end end -M.initialize() +return M From a9d14363bf4820b9cd43011fce8a0873c95a060a Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 12:03:11 +0530 Subject: [PATCH 30/35] events decoupled --- fle/env/lua_manager.py | 3 +- fle/env/mods/alerts.lua | 36 +-- fle/env/mods/utils.lua | 36 +-- fle/env/tools/admin/request_path/server.lua | 22 +- .../tools/agent/harvest_resource/server.lua | 210 +++++++++--------- .../tools/agent/inspect_inventory/server.lua | 28 ++- fle/env/tools/agent/move_to/server.lua | 23 +- fle/env/tools/agent/place_entity/server.lua | 58 ++--- 8 files changed, 212 insertions(+), 204 deletions(-) diff --git a/fle/env/lua_manager.py b/fle/env/lua_manager.py index 3932c2fab..0a0b10431 100644 --- a/fle/env/lua_manager.py +++ b/fle/env/lua_manager.py @@ -38,7 +38,8 @@ def __init__(self, rcon_client: RCONClient, cache_scripts: bool = False): self.lua = LuaRuntime(unpack_returned_tuples=True) def sub_initialize(self, script): - return re.sub(r"(?m)^\s*return\s+M\s*$", "M.initialize()\nreturn M", script) + substitution_string = "M.initialize()\nfor e, f in pairs(M.events) do script.on_event(e, f) end\nreturn M" + return re.sub(r"(?m)^\s*return\s+M\s*$", substitution_string, script) def init_action_checksums(self): checksum_init_script = _load_mods("checksum") diff --git a/fle/env/mods/alerts.lua b/fle/env/mods/alerts.lua index 5b1b8f90b..34ffca850 100644 --- a/fle/env/mods/alerts.lua +++ b/fle/env/mods/alerts.lua @@ -369,6 +369,22 @@ function round_to_half(number) return math.floor(number * 2 + 0.5) / 2 end +-- Define a function to get alerts older than the number of seconds +global.get_alerts = function(seconds) + local current_tick = game.tick + local old_alerts = {} + + for key, alert in pairs(global.alerts) do + if current_tick - alert.tick > 60*seconds then + table.insert(old_alerts, alert) + global.alerts[key] = nil + end + end + + return old_alerts +end + +end -- Define a function to be called every tick local function on_tick(event) @@ -397,23 +413,9 @@ local function on_tick(event) end end --- Define a function to get alerts older than the number of seconds -global.get_alerts = function(seconds) - local current_tick = game.tick - local old_alerts = {} - - for key, alert in pairs(global.alerts) do - if current_tick - alert.tick > 60*seconds then - table.insert(old_alerts, alert) - global.alerts[key] = nil - end - end - - return old_alerts -end - -- Register the on_tick function to the on_tick event -script.on_event(defines.events.on_tick, on_tick) -end +M.events = { + [defines.events.on_tick] = on_tick +} return M diff --git a/fle/env/mods/utils.lua b/fle/env/mods/utils.lua index 67de3190c..96d3cb6bf 100644 --- a/fle/env/mods/utils.lua +++ b/fle/env/mods/utils.lua @@ -133,24 +133,6 @@ global.utils.avoid_entity = function(player_index, entity, position, direction) return false end -global.crafting_queue = {} - -script.on_event(defines.events.on_tick, function(event) - -- Iterate over the crafting queue and update the remaining ticks - for i, task in ipairs(global.crafting_queue) do - task.remaining_ticks = task.remaining_ticks - 1 - - -- If the crafting is finished, consume the ingredients, insert the crafted entity, and remove the task from the queue - if task.remaining_ticks <= 0 then - for _, ingredient in pairs(task.recipe.ingredients) do - task.player.remove_item({name = ingredient.name, count = ingredient.amount * task.count}) - end - task.player.insert({name = task.entity_name, count = task.count}) - table.remove(global.crafting_queue, i) - end - end -end) - function dump(o) if type(o) == 'table' then local s = '{ ' @@ -263,4 +245,22 @@ function global.utils.inspect(player, radius, position) end end +-- global.crafting_queue = {} + +-- script.on_event(defines.events.on_tick, function(event) +-- -- Iterate over the crafting queue and update the remaining ticks +-- for i, task in ipairs(global.crafting_queue) do +-- task.remaining_ticks = task.remaining_ticks - 1 + +-- -- If the crafting is finished, consume the ingredients, insert the crafted entity, and remove the task from the queue +-- if task.remaining_ticks <= 0 then +-- for _, ingredient in pairs(task.recipe.ingredients) do +-- task.player.remove_item({name = ingredient.name, count = ingredient.amount * task.count}) +-- end +-- task.player.insert({name = task.entity_name, count = task.count}) +-- table.remove(global.crafting_queue, i) +-- end +-- end +-- end) + return M diff --git a/fle/env/tools/admin/request_path/server.lua b/fle/env/tools/admin/request_path/server.lua index 5682636a6..8095384fe 100644 --- a/fle/env/tools/admin/request_path/server.lua +++ b/fle/env/tools/admin/request_path/server.lua @@ -85,20 +85,9 @@ global.actions.request_path = function(player_index, start_x, start_y, goal_x, g return request_id end --- Modify the pathfinding finished handler to clean up entities ---script.on_event(defines.events.on_script_path_request_finished, function(event) --- -- Clean up clearance entities --- if global.clearance_entities[event.id] then --- for _, entity in pairs(global.clearance_entities[event.id]) do --- if entity.valid then --- entity.destroy() --- end --- end --- global.clearance_entities[event.id] = nil --- end ---end) - -script.on_event(defines.events.on_script_path_request_finished, function(event) +end + +local path_request_finished = function(event) local request_data = global.path_requests[event.id] if not request_data then return @@ -119,7 +108,10 @@ script.on_event(defines.events.on_script_path_request_finished, function(event) global.paths[event.id] = "not_found" -- log("Path not found for request ID: " .. event.id) end -end) end +M.events = { + [defines.events.on_script_path_request_finished] = path_request_finished +} + return M diff --git a/fle/env/tools/agent/harvest_resource/server.lua b/fle/env/tools/agent/harvest_resource/server.lua index a4865118d..70a7d3554 100644 --- a/fle/env/tools/agent/harvest_resource/server.lua +++ b/fle/env/tools/agent/harvest_resource/server.lua @@ -78,7 +78,6 @@ local function start_mining_entity(player, entity) return nil end - local function add_entities_to_queue(queue, entities, count) local expected_yield = 0 local added = 0 @@ -106,109 +105,6 @@ local function add_entities_to_queue(queue, entities, count) return expected_yield end -script.on_nth_tick(15, function(event) - -- If no queues at all, just return - if not global.harvest_queues then return end - - for player_index, queue in pairs(global.harvest_queues) do - local player = global.agent_characters[player_index] - -- Skip if player not valid - if not player or not player.valid then goto continue end - - -- Already reached or exceeded our target? - if queue.total_yield >= queue.target_yield then - -- Remove this player's queue - global.harvest_queues[player_index] = nil - goto continue - end - - -- Check if player is still in resource reach distance - local dist_x = player.position.x - queue.mining_position.x - local dist_y = player.position.y - queue.mining_position.y - local sq_dist = (dist_x * dist_x) + (dist_y * dist_y) - local sq_reach = (player.resource_reach_distance * player.resource_reach_distance) - if sq_dist > sq_reach then - -- Too far away; do nothing for now - goto continue - end - - -- If there's no current mining, pick up the next entity - if not queue.current_mining then - local next_entity = table.remove(queue.entities, 1) - if not next_entity then - -- No more entities left - global.harvest_queues[player_index] = nil - goto continue - end - - -- Start mining - queue.current_mining = { - entity = next_entity, - start_tick = game.tick - } - else - -- We have a current entity being mined - local entity = queue.current_mining.entity - if not entity or not entity.valid or not entity.minable then - -- Entity no longer valid, skip - queue.current_mining = nil - goto continue - end - - local ticks_mining = game.tick - queue.current_mining.start_tick - if ticks_mining >= 30 then - -- Time to finish mining - local inv_before = player.get_main_inventory().get_contents() - local mined_ok = player.mine_entity(entity) -- Instantly mines & adds items - if mined_ok then - local inv_after = player.get_main_inventory().get_contents() - - -- Figure out how many items we actually gained - local items_added = 0 - for name, after_count in pairs(inv_after) do - local before_count = inv_before[name] or 0 - items_added = items_added + (after_count - before_count) - end - - if items_added > 0 then - -- Add to our queue's total_yield - local new_total = queue.total_yield + items_added - - if new_total > queue.target_yield then - -- We overshot. Remove the extras from the player's inventory. - local overshoot = new_total - queue.target_yield - -- We'll try to remove it from whatever items were gained. - -- If multiple resource types might drop, you'd handle them individually. - - local overshoot_left = overshoot - for name, after_count in pairs(inv_after) do - local before_count = inv_before[name] or 0 - local gained_this_item = (after_count - before_count) - if gained_this_item > 0 then - local to_remove = math.min(overshoot_left, gained_this_item) - local actually_removed = player.remove_item({name = name, count = to_remove}) - overshoot_left = overshoot_left - actually_removed - if overshoot_left <= 0 then - break - end - end - end - new_total = queue.target_yield - end - - queue.total_yield = new_total - end - end - - -- Clear current mining - queue.current_mining = nil - end - end - ::continue:: - end -end) - - local function check_inventory_space(player, entity, count) -- Get player's main inventory local inventory = player.get_main_inventory() @@ -626,4 +522,110 @@ global.actions.get_resource_name_at_position = function(player_index, x, y) end end +local function on_nth_tick(event) + -- If no queues at all, just return + if not global.harvest_queues then return end + + for player_index, queue in pairs(global.harvest_queues) do + local player = global.agent_characters[player_index] + -- Skip if player not valid + if not player or not player.valid then goto continue end + + -- Already reached or exceeded our target? + if queue.total_yield >= queue.target_yield then + -- Remove this player's queue + global.harvest_queues[player_index] = nil + goto continue + end + + -- Check if player is still in resource reach distance + local dist_x = player.position.x - queue.mining_position.x + local dist_y = player.position.y - queue.mining_position.y + local sq_dist = (dist_x * dist_x) + (dist_y * dist_y) + local sq_reach = (player.resource_reach_distance * player.resource_reach_distance) + if sq_dist > sq_reach then + -- Too far away; do nothing for now + goto continue + end + + -- If there's no current mining, pick up the next entity + if not queue.current_mining then + local next_entity = table.remove(queue.entities, 1) + if not next_entity then + -- No more entities left + global.harvest_queues[player_index] = nil + goto continue + end + + -- Start mining + queue.current_mining = { + entity = next_entity, + start_tick = game.tick + } + else + -- We have a current entity being mined + local entity = queue.current_mining.entity + if not entity or not entity.valid or not entity.minable then + -- Entity no longer valid, skip + queue.current_mining = nil + goto continue + end + + local ticks_mining = game.tick - queue.current_mining.start_tick + if ticks_mining >= 30 then + -- Time to finish mining + local inv_before = player.get_main_inventory().get_contents() + local mined_ok = player.mine_entity(entity) -- Instantly mines & adds items + if mined_ok then + local inv_after = player.get_main_inventory().get_contents() + + -- Figure out how many items we actually gained + local items_added = 0 + for name, after_count in pairs(inv_after) do + local before_count = inv_before[name] or 0 + items_added = items_added + (after_count - before_count) + end + + if items_added > 0 then + -- Add to our queue's total_yield + local new_total = queue.total_yield + items_added + + if new_total > queue.target_yield then + -- We overshot. Remove the extras from the player's inventory. + local overshoot = new_total - queue.target_yield + -- We'll try to remove it from whatever items were gained. + -- If multiple resource types might drop, you'd handle them individually. + + local overshoot_left = overshoot + for name, after_count in pairs(inv_after) do + local before_count = inv_before[name] or 0 + local gained_this_item = (after_count - before_count) + if gained_this_item > 0 then + local to_remove = math.min(overshoot_left, gained_this_item) + local actually_removed = player.remove_item({name = name, count = to_remove}) + overshoot_left = overshoot_left - actually_removed + if overshoot_left <= 0 then + break + end + end + end + new_total = queue.target_yield + end + + queue.total_yield = new_total + end + end + + -- Clear current mining + queue.current_mining = nil + end + end + ::continue:: + end +end + +M.events = { + [defines.events.on_tick] = on_nth_tick +} + return M diff --git a/fle/env/tools/agent/inspect_inventory/server.lua b/fle/env/tools/agent/inspect_inventory/server.lua index 4825fbe99..6dece51c9 100644 --- a/fle/env/tools/agent/inspect_inventory/server.lua +++ b/fle/env/tools/agent/inspect_inventory/server.lua @@ -7,7 +7,7 @@ global.actions.inspect_inventory = function(player_index, is_character_inventory local player = global.agent_characters[player_index] local surface = player.surface local is_fast = global.fast - local automatic_close = True + local automatic_close = true local function get_player_inventory_items(player) @@ -44,17 +44,9 @@ global.actions.inspect_inventory = function(player_index, is_character_inventory error("No valid entity at given coordinates.") end - if not is_fast then - player.opened = closest_entity - script.on_nth_tick(60, function() - if automatic_close == True then - if closest_entity and closest_entity.valid then - player.opened = nil - end - automatic_close = False - end - end) - end + if not is_fast then + player.opened = closest_entity + end if closest_entity.type == "furnace" then if not closest_entity or not closest_entity.valid then @@ -139,4 +131,16 @@ global.actions.inspect_inventory = function(player_index, is_character_inventory end end + +-- script.on_nth_tick(60, function() +-- if not global.fast then +-- if automatic_close then +-- if closest_entity and closest_entity.valid then +-- player.opened = nil +-- end +-- automatic_close = false +-- end +-- end +-- end) + return M diff --git a/fle/env/tools/agent/move_to/server.lua b/fle/env/tools/agent/move_to/server.lua index 7b4cba81b..fad7be851 100644 --- a/fle/env/tools/agent/move_to/server.lua +++ b/fle/env/tools/agent/move_to/server.lua @@ -4,15 +4,6 @@ M.events = {} function M.initialize() -- move_to --- Register the tick handler when the module is loaded -if not global.fast then - script.on_nth_tick(5, function(event) - if global.walking_queues then - global.actions.update_walking_queues() - end - end) -end - --local function get_direction(from_pos, to_pos) -- local dx = to_pos.x - from_pos.x -- local dy = to_pos.y - from_pos.y @@ -269,4 +260,18 @@ global.actions.get_walking_queue_length = function(player_index) end end +-- Register the tick handler when the module is loaded +local function on_nth_tick(event) + if not global.fast then + if global.walking_queues then + global.actions.update_walking_queues() + end + end +end + +M.events = { + [defines.events.on_tick] = on_nth_tick +} + return M + diff --git a/fle/env/tools/agent/place_entity/server.lua b/fle/env/tools/agent/place_entity/server.lua index 1cd1aa7a4..50ac0b698 100644 --- a/fle/env/tools/agent/place_entity/server.lua +++ b/fle/env/tools/agent/place_entity/server.lua @@ -156,34 +156,6 @@ global.actions.place_entity = function(player_index, entity, direction, x, y, ex -- Select the target position player.update_selected_entity(position) - -- Schedule the actual placement after delay - script.on_nth_tick(60, function(event) -- 30 ticks = 0.5 seconds - script.on_nth_tick(60, nil) -- Clear the scheduled event - - -- Verify conditions are still valid - validate_distance() - validate_inventory() - - -- Avoid entity at target position - global.utils.avoid_entity(player_index, entity, position) - - -- Perform the actual placement - local placed_entity = player.surface.create_entity{ - name = entity, - force = "player", - position = position, - direction = entity_direction, - } - - if placed_entity then - player.remove_item{name = entity, count = 1} - player.cursor_ghost = nil -- Clear the ghost - return global.utils.serialize_entity(placed_entity) - else - error("\"Failed to place entity after delay\"") - end - end) - return { pending = true } end @@ -363,4 +335,34 @@ global.actions.place_entity = function(player_index, entity, direction, x, y, ex end end +-- -- Schedule the actual placement after delay +-- script.on_nth_tick(60, function(event) -- 30 ticks = 0.5 seconds +-- if not global.fast then +-- script.on_nth_tick(60, nil) -- Clear the scheduled event + +-- -- Verify conditions are still valid +-- validate_distance() +-- validate_inventory() + +-- -- Avoid entity at target position +-- global.utils.avoid_entity(player_index, entity, position) + +-- -- Perform the actual placement +-- local placed_entity = player.surface.create_entity { +-- name = entity, +-- force = "player", +-- position = position, +-- direction = entity_direction, +-- } + +-- if placed_entity then +-- player.remove_item { name = entity, count = 1 } +-- player.cursor_ghost = nil -- Clear the ghost +-- return global.utils.serialize_entity(placed_entity) +-- else +-- error("\"Failed to place entity after delay\"") +-- end +-- end +-- end) + return M From 7ea482acf6b09c7f0011cf53c4a42d9c4abcafc2 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 13:45:36 +0530 Subject: [PATCH 31/35] removed log --- fle/env/mods/serialize.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/fle/env/mods/serialize.lua b/fle/env/mods/serialize.lua index b55104e1d..f501f2245 100644 --- a/fle/env/mods/serialize.lua +++ b/fle/env/mods/serialize.lua @@ -661,7 +661,6 @@ global.utils.serialize_entity = function(entity) end end - log(("status: %s (%s)"):format(name or "", tostring(s))) local serialized = { name = "\""..entity.name.."\"", position = entity.position, From 2ecdc1533e85f7bb2019dff18a70ef245adce9dc Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 13:46:17 +0530 Subject: [PATCH 32/35] stable modules, tests passing --- fle/env/lua_module_manager.py | 558 ++++++++++++++++++++++++++++++++++ 1 file changed, 558 insertions(+) create mode 100644 fle/env/lua_module_manager.py diff --git a/fle/env/lua_module_manager.py b/fle/env/lua_module_manager.py new file mode 100644 index 000000000..6129dc0a2 --- /dev/null +++ b/fle/env/lua_module_manager.py @@ -0,0 +1,558 @@ +import hashlib +import json +import shutil +import importlib +import os +from pathlib import Path + +from lupa.lua54 import LuaRuntime + +from factorio_rcon import RCONClient +from fle.env.utils.rcon import ( + _get_dir, + _get_mods_dir, + _get_lib_names, + _get_tool_names, + _load_mods, + _load_script, +) + +ROOT_DIR = Path(__file__).parent.parent.parent + +def get_control_lua(lib_names, admin_tool_names, agent_tool_names): + # Ensure 'libs.initialise' is at the top if present + lib_names = "',\n '".join(lib_names) + admin_tool_names = "',\n '".join(admin_tool_names) + agent_tool_names = "',\n '".join(agent_tool_names) + + return f""" +-- control.lua +-- This file is automatically generated by the LuaModuleManager +-- Do not edit this file manually + +util = require('util') +local module_names = {{ + -- libs first + '{lib_names}', + + -- admin tools + '{admin_tool_names}', + + -- agent tools + '{agent_tool_names}' +}} + +local loaded_modules = {{}} + +-- Load all modules and store them +log("Loading modules") +for _, name in ipairs(module_names) do + local ok, mod = pcall(require, name) + if not ok then + log("Error loading module: " .. name .. " - error: " .. mod) + else + log("Loaded module: " .. name .. " - success: " .. tostring(ok)) + loaded_modules[name] = mod -- Store the actual module object + end +end + +local function initialize_all() + log("Initializing all modules") + for name, module in pairs(loaded_modules) do -- Now iterating over actual modules + log('Initializing module: ' .. name) + if module.initialize then + local ok, err = pcall(module.initialize) + if ok then + log("Successfully initialized module: " .. name) + else + log("Error initializing module: " .. name .. " - error: " .. err) + end + end + end +end + + +local function register_events() + log("Registering events") + for name, module in pairs(loaded_modules) do + if module.events then + for event_id, handler in pairs(module.events) do + log('Registering event: ' .. tostring(event_id) .. ' from module: ' .. name) + script.on_event(event_id, handler) + end + end + end +end + +-- First tick handler for delayed initialization +local function on_first_tick(event) + -- Check if we need to initialize + if not global.fle_initialized then + log("Running delayed initialization on first tick") + initialize_all() + global.fle_initialized = true + end + -- Unregister this handler after first run + script.on_event(defines.events.on_tick, nil) + log("First tick initialization complete, on_tick handler removed") +end + +script.on_load(function() + log("Hii from on_load") + register_events() + + -- Only set up first-tick initialization if not already initialized + -- Note: global table might not be accessible during on_load in some cases, + -- so we'll register the handler and let it check the condition + log("Setting up first-tick initialization handler") + script.on_event(defines.events.on_tick, on_first_tick) +end) + +script.on_init(function() + log("Hii from on_init") + initialize_all() + register_events() + global.fle_initialized = true +end) + +script.on_configuration_changed(function() + log("Hii from on_configuration_changed") + initialize_all() + register_events() + global.fle_initialized = true +end) +""" + +class LuaModuleManager: + def __init__(self, rcon_client: RCONClient, cache_scripts: bool = False, start_fresh: bool = False): + self.rcon_client = rcon_client + self.cache_scripts = cache_scripts + # if not cache_scripts: + # self._clear_game_checksums(rcon_client) + # self.action_directory = _get_action_dir() + + self.lib_directory = _get_mods_dir() + # if cache_scripts: + # self.init_action_checksums() + # self.game_checksums = self._get_game_checksums(rcon_client) + + # self.tool_scripts = self.get_tools_to_load() + # self.lib_scripts = self.get_libs_to_load() + self.lua = LuaRuntime(unpack_returned_tuples=True) + self.module_dir = ROOT_DIR / ".fle" / "fle-mod" + if start_fresh: + shutil.rmtree(self.module_dir) + + def build_module(self): + self.module_dir.mkdir(parents=True, exist_ok=True) + tools_dir = self.module_dir / "tools" + (tools_dir / "agent").mkdir(parents=True, exist_ok=True) + (tools_dir / "admin").mkdir(parents=True, exist_ok=True) + libs_dir = self.module_dir / "libs" + libs_dir.mkdir(parents=True, exist_ok=True) + control_lua = self.module_dir / "control.lua" + lib_names = [] + admin_tool_names = [] + agent_tool_names = [] + for lib in _get_lib_names(): + lib = Path(lib) + lib_name = lib.stem + shutil.copy(lib, libs_dir / f"{lib_name}.lua") + if lib_name == "initialise": + lib_names.insert(0, f"libs.{lib_name}") + else: + lib_names.append(f"libs.{lib_name}") + + for tool in _get_tool_names(): + tool_type = "agent" if "agent" in tool else "admin" + tool = Path(tool) + tool_name = tool.parts[-2] + shutil.copy(tool, tools_dir / tool_type / f"{tool_name}.lua") + if tool_type == "admin": + admin_tool_names.append(f"tools.admin.{tool_name}") + else: + agent_tool_names.append(f"tools.agent.{tool_name}") + + with open(control_lua, "w") as f: + f.write(get_control_lua(lib_names, admin_tool_names, agent_tool_names)) + + + def init_action_checksums(self): + checksum_init_script = _load_mods("checksum") + response = self.rcon_client.send_command("/sc " + checksum_init_script) + return response + + def check_lua_syntax(self, script): + try: + self.lua.execute(script) + return True, None + except Exception as e: + if "attempt to index a nil value" in e.args[0]: + if "global" in e.args[0]: + return True, None + return False, e.args[0] + + def load_tool_into_game(self, name): + # Select scripts by exact tool directory, not prefix + tool_dirs = { + f"agent/{name}", + f"admin/{name}", + f"agent\\{name}", + f"admin\\{name}", + } + tool_scripts = [ + key for key in self.tool_scripts.keys() if os.path.dirname(key) in tool_dirs + ] + # Sort scripts so server.lua comes last + tool_scripts.sort(key=lambda x: x.endswith("server.lua")) + + for script_name in tool_scripts: + if script_name not in self.tool_scripts: + # attempt to load the script from the filesystem + script = _load_script(script_name) + self.tool_scripts[script_name] = script + + script = self.tool_scripts[script_name] + if self.cache_scripts: + checksum = self.calculate_checksum(script) + if ( + script_name in self.game_checksums + and self.game_checksums[script_name] == checksum + ): + continue + self.update_game_checksum(self.rcon_client, script_name, checksum) + # Keep local view in sync so later loads skip + self.game_checksums[script_name] = checksum + + correct, error = self.check_lua_syntax(script) + if not correct: + raise Exception(f"Syntax error in: {script_name}: {error}") + print(f"{self.rcon_client.port}: Loading action {script_name} into game") + + self.rcon_client.send_command("/sc " + script) + pass + + def load_init_into_game(self, name): + if name not in self.lib_scripts: + # attempt to load the script from the filesystem + script = _load_mods(name) + self.lib_scripts[name] = script + + script = self.lib_scripts[name] + if self.cache_scripts: + checksum = self.calculate_checksum(script) + if name in self.game_checksums and self.game_checksums[name] == checksum: + return + self.update_game_checksum(self.rcon_client, name, checksum) + + self.rcon_client.send_command("/sc " + script) + + def calculate_checksum(self, content: str) -> str: + return hashlib.md5(content.encode()).hexdigest() + + def get_tools_to_load(self): + scripts_to_load = {} + lua_files = ( + _get_tool_names() + ) # This returns all .lua files from previous modification + tool_dir = _get_dir("tools") + for lua_file in lua_files: + # Get the tool name from the directory path + rel_path = os.path.relpath(lua_file, Path(tool_dir)) + tool_name = os.path.dirname(rel_path) + script_name = os.path.basename(lua_file) + + # Load the lua script content + _, content = _load_script(lua_file) + + # Create a unique key combining tool and script name + script_key = f"{tool_name}/{script_name}" if tool_name else script_name + + if self.cache_scripts: + checksum = self.calculate_checksum(content) + if ( + script_key not in self.game_checksums + or self.game_checksums[script_key] != checksum + ): + scripts_to_load[script_key] = content + else: + scripts_to_load[script_key] = content + + return scripts_to_load + + def get_libs_to_load(self): + scripts_to_load = {} + for filename in _get_lib_names(): + name, content = _load_script(filename) + if self.cache_scripts: + checksum = self.calculate_checksum(content) + + if ( + name not in self.game_checksums + or self.game_checksums[name] != checksum + ): + scripts_to_load[name] = content + else: + scripts_to_load[name] = content + + return scripts_to_load + + def update_game_checksum(self, rcon_client, script_name: str, checksum: str): + rcon_client.send_command( + f"/sc global.set_lua_script_checksum('{script_name}', '{checksum}')" + ) + + def _clear_game_checksums(self, rcon_client): + rcon_client.send_command("/sc global.clear_lua_script_checksums()") + + def _get_game_checksums(self, rcon_client): + response = rcon_client.send_command( + "/sc rcon.print(global.get_lua_script_checksums())" + ) + return json.loads(response) + + def setup_tools(self, instance): + """ + Load Python controllers from valid tool directories (those containing both client.py and server.lua) + """ + tool_dir = _get_dir("tools") + instance.controllers = {} + + def snake_to_camel(snake_str): + return "".join(word.capitalize() for word in snake_str.split("_")) + + # Create a function that wraps a tool's call method to execute hooks + def create_hook_wrapper(tool_name, original_callable): + from functools import wraps + + @wraps(original_callable) + def wrapper(*args, **kwargs): + # Execute pre-tool hooks + try: + self.execute_pre_tool_hooks( + instance, tool_name, original_callable, *args, **kwargs + ) + except Exception as e: + print(f"Error in pre-tool hook for {tool_name}: {e}") + + # Execute the original callable + result = original_callable(*args, **kwargs) + + # Execute post-tool hooks + try: + self.execute_post_tool_hooks( + instance, tool_name, original_callable, result + ) + except Exception as e: + print(f"Error in post-tool hook for {tool_name}: {e}") + + return result + + return wrapper + + # Walk through all subdirectories + for dirpath, _, filenames in os.walk(tool_dir): + # Skip the root directory + if dirpath == tool_dir: + continue + + # Check if this is a valid tool directory + server_file = os.path.join(dirpath, "server.lua") + client_file = os.path.join(dirpath, "client.py") + + if os.path.isfile(server_file) and os.path.isfile(client_file): + # Get the tool name from the directory + tool_name = os.path.basename(dirpath) + + directory_name = Path(dirpath).parent.name + # Load the Python module + module_spec = importlib.util.spec_from_file_location( + tool_name, + client_file, + # str(Path(client_file)) + ) + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + + class_name = snake_to_camel(tool_name) + + # Handle special case renames + if tool_name == "place_entity": + class_name = "PlaceObject" + if tool_name == "score": + class_name = "Reward" + + try: + for i in range(instance.num_agents): + # Get and instantiate the controller class + callable_class = getattr(module, class_name) + callable_instance = callable_class(self, instance.namespaces[i]) + + # Create a wrapper that will execute hooks + wrapped_instance = create_hook_wrapper( + tool_name.lower(), callable_instance + ) + + # Store the controller and add it to namespace + instance.controllers[tool_name.lower()] = callable_instance + + if directory_name == "admin": + # If this is an admin method, we hide it in the namespace by adding a shebang + setattr( + instance.namespaces[i], + f"_{tool_name.lower()}", + wrapped_instance, + ) + else: + setattr( + instance.namespaces[i], + tool_name.lower(), + wrapped_instance, + ) + + except Exception as e: + raise Exception( + f"Could not instantiate {class_name} from {client_file}. {e}" + ) + + @staticmethod + def register_post_tool_hook(instance, tool_name, callback=None): + """ + Register a hook to be called after a specific tool is executed. + Can be used as a regular function or as a decorator. + + Args: + tool_name (str): Name of the tool to hook into + callback (callable, optional): Function to call after the tool is executed. + Will receive the tool instance and the result as arguments. + + Returns: + If used as a regular function (with callback provided), returns the callback. + If used as a decorator (without callback), returns a decorator function. + """ + # When used as a decorator without parentheses: @register_post_tool_hook + if tool_name is not None and callback is None and callable(tool_name): + callback = tool_name + tool_name = callback.__name__ + if not hasattr(instance, "post_tool_hooks"): + instance.post_tool_hooks = {} + if tool_name not in instance.post_tool_hooks: + instance.post_tool_hooks[tool_name] = [] + instance.post_tool_hooks[tool_name].append(callback) + return callback + + # When used as a decorator with arguments: @register_post_tool_hook("tool_name") + if callback is None: + + def decorator(func): + if not hasattr(instance, "post_tool_hooks"): + instance.post_tool_hooks = {} + if tool_name not in instance.post_tool_hooks: + instance.post_tool_hooks[tool_name] = [] + instance.post_tool_hooks[tool_name].append(func) + return func + + return decorator + + # When used as a regular function: register_post_tool_hook("tool_name", callback_func) + if not callable(callback): + raise TypeError("Callback must be callable") + + if not hasattr(instance, "post_tool_hooks"): + instance.post_tool_hooks = {} + if tool_name not in instance.post_tool_hooks: + instance.post_tool_hooks[tool_name] = [] + + instance.post_tool_hooks[tool_name].append(callback) + return callback + + @staticmethod + def register_pre_tool_hook(instance, tool_name, callback=None): + """ + Register a hook to be called before a specific tool is executed. + Can be used as a regular function or as a decorator. + + Args: + tool_name (str): Name of the tool to hook into + callback (callable, optional): Function to call before the tool is executed. + Will receive the tool instance and the arguments as parameters. + + Returns: + If used as a regular function (with callback provided), returns the callback. + If used as a decorator (without callback), returns a decorator function. + """ + # When used as a decorator without parentheses: @register_pre_tool_hook + if tool_name is not None and callback is None and callable(tool_name): + callback = tool_name + tool_name = callback.__name__ + if not hasattr(instance, "pre_tool_hooks"): + instance.pre_tool_hooks = {} + if tool_name not in instance.pre_tool_hooks: + instance.pre_tool_hooks[tool_name] = [] + instance.pre_tool_hooks[tool_name].append(callback) + return callback + + # When used as a decorator with arguments: @register_pre_tool_hook("tool_name") + if callback is None: + + def decorator(func): + if not hasattr(instance, "pre_tool_hooks"): + instance.pre_tool_hooks = {} + if tool_name not in instance.pre_tool_hooks: + instance.pre_tool_hooks[tool_name] = [] + instance.pre_tool_hooks[tool_name].append(func) + return func + + return decorator + + # When used as a regular function: register_pre_tool_hook("tool_name", callback_func) + if not callable(callback): + raise TypeError("Callback must be callable") + + if not hasattr(instance, "pre_tool_hooks"): + instance.pre_tool_hooks = {} + if tool_name not in instance.pre_tool_hooks: + instance.pre_tool_hooks[tool_name] = [] + + instance.pre_tool_hooks[tool_name].append(callback) + return callback + + @staticmethod + def execute_post_tool_hooks(instance, tool_name, tool_instance, result): + """ + Execute all hooks registered for a tool after it has been executed. + + Args: + tool_name (str): Name of the tool + tool_instance: The tool instance that was executed + result: The result of the tool execution + """ + if tool_name in instance.post_tool_hooks: + for callback in instance.post_tool_hooks[tool_name]: + try: + callback(tool_instance, result) + except Exception as e: + print(f"Error in post-tool hook for {tool_name}: {e}") + + @staticmethod + def execute_pre_tool_hooks(instance, tool_name, tool_instance, *args, **kwargs): + """ + Execute all hooks registered for a tool before it is executed. + + Args: + tool_name (str): Name of the tool + tool_instance: The tool instance to be executed + *args, **kwargs: The arguments passed to the tool + """ + if tool_name in instance.pre_tool_hooks: + for callback in instance.pre_tool_hooks[tool_name]: + try: + callback(tool_instance, *args, **kwargs) + except Exception as e: + print(f"Error in pre-tool hook for {tool_name}: {e}") + + +if __name__ == "__main__": + manager = LuaModuleManager(rcon_client=None, cache_scripts=False, start_fresh=True) + # print(manager.get_tools_to_load()) + manager.build_module() \ No newline at end of file From cf57b08ab1668c168656eaea6094e2a59607cf8e Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 13:47:17 +0530 Subject: [PATCH 33/35] formatting --- fle/env/lua_module_manager.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fle/env/lua_module_manager.py b/fle/env/lua_module_manager.py index 6129dc0a2..11dcb2baa 100644 --- a/fle/env/lua_module_manager.py +++ b/fle/env/lua_module_manager.py @@ -19,6 +19,7 @@ ROOT_DIR = Path(__file__).parent.parent.parent + def get_control_lua(lib_names, admin_tool_names, agent_tool_names): # Ensure 'libs.initialise' is at the top if present lib_names = "',\n '".join(lib_names) @@ -123,8 +124,14 @@ def get_control_lua(lib_names, admin_tool_names, agent_tool_names): end) """ + class LuaModuleManager: - def __init__(self, rcon_client: RCONClient, cache_scripts: bool = False, start_fresh: bool = False): + def __init__( + self, + rcon_client: RCONClient, + cache_scripts: bool = False, + start_fresh: bool = False, + ): self.rcon_client = rcon_client self.cache_scripts = cache_scripts # if not cache_scripts: @@ -176,7 +183,6 @@ def build_module(self): with open(control_lua, "w") as f: f.write(get_control_lua(lib_names, admin_tool_names, agent_tool_names)) - def init_action_checksums(self): checksum_init_script = _load_mods("checksum") response = self.rcon_client.send_command("/sc " + checksum_init_script) @@ -555,4 +561,4 @@ def execute_pre_tool_hooks(instance, tool_name, tool_instance, *args, **kwargs): if __name__ == "__main__": manager = LuaModuleManager(rcon_client=None, cache_scripts=False, start_fresh=True) # print(manager.get_tools_to_load()) - manager.build_module() \ No newline at end of file + manager.build_module() From 277c21ccd443f11220f71059f1e9f5eda1286bd8 Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 14:37:47 +0530 Subject: [PATCH 34/35] control lua template --- fle/env/control-template.lua | 95 +++++++++++++++++++++++++++++++ fle/env/lua_module_manager.py | 103 ++-------------------------------- 2 files changed, 101 insertions(+), 97 deletions(-) create mode 100644 fle/env/control-template.lua diff --git a/fle/env/control-template.lua b/fle/env/control-template.lua new file mode 100644 index 000000000..d95ee5f79 --- /dev/null +++ b/fle/env/control-template.lua @@ -0,0 +1,95 @@ +-- control.lua +-- This file is automatically generated by the LuaModuleManager +-- Do not edit this file manually + +util = require('util') +local module_names = {{ + -- libs first + '{lib_names}', + + -- admin tools + '{admin_tool_names}', + + -- agent tools + '{agent_tool_names}' +}} + +local loaded_modules = {} + +-- Load all modules and store them +log("Loading modules") +for _, name in ipairs(module_names) do + local ok, mod = pcall(require, name) + if not ok then + log("Error loading module: " .. name .. " - error: " .. mod) + else + log("Loaded module: " .. name .. " - success: " .. tostring(ok)) + loaded_modules[name] = mod -- Store the actual module object + end +end + +local function initialize_all() + log("Initializing all modules") + for name, module in pairs(loaded_modules) do -- Now iterating over actual modules + log('Initializing module: ' .. name) + if module.initialize then + local ok, err = pcall(module.initialize) + if ok then + log("Successfully initialized module: " .. name) + else + log("Error initializing module: " .. name .. " - error: " .. err) + end + end + end +end + + +local function register_events() + log("Registering events") + for name, module in pairs(loaded_modules) do + if module.events then + for event_id, handler in pairs(module.events) do + log('Registering event: ' .. tostring(event_id) .. ' from module: ' .. name) + script.on_event(event_id, handler) + end + end + end +end + +-- First tick handler for delayed initialization +local function on_first_tick(event) + -- Check if we need to initialize + if not global.fle_initialized then + log("Running delayed initialization on first tick") + initialize_all() + global.fle_initialized = true + end + -- Unregister this handler after first run + script.on_event(defines.events.on_tick, nil) + log("First tick initialization complete, on_tick handler removed") +end + +script.on_load(function() + log("Hii from on_load") + register_events() + + -- Only set up first-tick initialization if not already initialized + -- Note: global table might not be accessible during on_load in some cases, + -- so we'll register the handler and let it check the condition + log("Setting up first-tick initialization handler") + script.on_event(defines.events.on_tick, on_first_tick) +end) + +script.on_init(function() + log("Hii from on_init") + initialize_all() + register_events() + global.fle_initialized = true +end) + +script.on_configuration_changed(function() + log("Hii from on_configuration_changed") + initialize_all() + register_events() + global.fle_initialized = true +end) \ No newline at end of file diff --git a/fle/env/lua_module_manager.py b/fle/env/lua_module_manager.py index 11dcb2baa..983f83c6f 100644 --- a/fle/env/lua_module_manager.py +++ b/fle/env/lua_module_manager.py @@ -26,103 +26,12 @@ def get_control_lua(lib_names, admin_tool_names, agent_tool_names): admin_tool_names = "',\n '".join(admin_tool_names) agent_tool_names = "',\n '".join(agent_tool_names) - return f""" --- control.lua --- This file is automatically generated by the LuaModuleManager --- Do not edit this file manually - -util = require('util') -local module_names = {{ - -- libs first - '{lib_names}', - - -- admin tools - '{admin_tool_names}', - - -- agent tools - '{agent_tool_names}' -}} - -local loaded_modules = {{}} - --- Load all modules and store them -log("Loading modules") -for _, name in ipairs(module_names) do - local ok, mod = pcall(require, name) - if not ok then - log("Error loading module: " .. name .. " - error: " .. mod) - else - log("Loaded module: " .. name .. " - success: " .. tostring(ok)) - loaded_modules[name] = mod -- Store the actual module object - end -end - -local function initialize_all() - log("Initializing all modules") - for name, module in pairs(loaded_modules) do -- Now iterating over actual modules - log('Initializing module: ' .. name) - if module.initialize then - local ok, err = pcall(module.initialize) - if ok then - log("Successfully initialized module: " .. name) - else - log("Error initializing module: " .. name .. " - error: " .. err) - end - end - end -end - - -local function register_events() - log("Registering events") - for name, module in pairs(loaded_modules) do - if module.events then - for event_id, handler in pairs(module.events) do - log('Registering event: ' .. tostring(event_id) .. ' from module: ' .. name) - script.on_event(event_id, handler) - end - end - end -end - --- First tick handler for delayed initialization -local function on_first_tick(event) - -- Check if we need to initialize - if not global.fle_initialized then - log("Running delayed initialization on first tick") - initialize_all() - global.fle_initialized = true - end - -- Unregister this handler after first run - script.on_event(defines.events.on_tick, nil) - log("First tick initialization complete, on_tick handler removed") -end - -script.on_load(function() - log("Hii from on_load") - register_events() - - -- Only set up first-tick initialization if not already initialized - -- Note: global table might not be accessible during on_load in some cases, - -- so we'll register the handler and let it check the condition - log("Setting up first-tick initialization handler") - script.on_event(defines.events.on_tick, on_first_tick) -end) - -script.on_init(function() - log("Hii from on_init") - initialize_all() - register_events() - global.fle_initialized = true -end) - -script.on_configuration_changed(function() - log("Hii from on_configuration_changed") - initialize_all() - register_events() - global.fle_initialized = true -end) -""" + with open(ROOT_DIR / "fle" / "env" / "control-template.lua", "r") as f: + template = f.read() + template = template.replace("{lib_names}", lib_names) + template = template.replace("{admin_tool_names}", admin_tool_names) + template = template.replace("{agent_tool_names}", agent_tool_names) + return template class LuaModuleManager: From 08b92bc7865d4cbe8fa5a466eb57011b331608aa Mon Sep 17 00:00:00 2001 From: hrshtt Date: Sat, 23 Aug 2025 15:19:09 +0530 Subject: [PATCH 35/35] level safe but desync unsafe --- fle/env/control-template.lua | 38 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/fle/env/control-template.lua b/fle/env/control-template.lua index d95ee5f79..42d56a34c 100644 --- a/fle/env/control-template.lua +++ b/fle/env/control-template.lua @@ -3,7 +3,7 @@ -- Do not edit this file manually util = require('util') -local module_names = {{ +local module_names = { -- libs first '{lib_names}', @@ -12,7 +12,7 @@ local module_names = {{ -- agent tools '{agent_tool_names}' -}} +} local loaded_modules = {} @@ -53,31 +53,39 @@ local function register_events() script.on_event(event_id, handler) end end + if module.nth then + for nth, handler in pairs(module.nth) do + log('Registering nth-tick: ' .. tostring(nth) .. ' from module: ' .. name) + script.on_nth_tick(nth, handler) + end + end end end --- First tick handler for delayed initialization -local function on_first_tick(event) - -- Check if we need to initialize + +-- One-shot first tick initializer (safe fallback for existing saves) +local function first_tick_init(event) + -- Unregister ourselves immediately to avoid repeated runs + script.on_nth_tick(1, nil) if not global.fle_initialized then - log("Running delayed initialization on first tick") + log("Running delayed initialization on first tick via on_nth_tick(1)") initialize_all() + register_events() global.fle_initialized = true end - -- Unregister this handler after first run - script.on_event(defines.events.on_tick, nil) - log("First tick initialization complete, on_tick handler removed") + log("First tick initialization complete; on_nth_tick(1) handler removed") end script.on_load(function() log("Hii from on_load") + -- Rebuild all event subscriptions (safe in on_load; does not mutate game state) register_events() - - -- Only set up first-tick initialization if not already initialized - -- Note: global table might not be accessible during on_load in some cases, - -- so we'll register the handler and let it check the condition - log("Setting up first-tick initialization handler") - script.on_event(defines.events.on_tick, on_first_tick) + + -- If this save predates our init, schedule a deterministic one-shot init + if not global.fle_initialized then + log("Scheduling first-tick initialization via on_nth_tick(1)") + script.on_nth_tick(1, first_tick_init) + end end) script.on_init(function()