├── .gitignore ├── LICENSE ├── README.md ├── changelog.txt ├── config └── config.lua ├── control.lua ├── data-updates.lua ├── data.lua ├── graphics ├── entity │ ├── bots │ │ ├── flame_run-shadow.png │ │ ├── flame_run.png │ │ ├── hr-flame_run-shadow.png │ │ ├── hr-flame_run.png │ │ ├── hr-rifle_run-shadow.png │ │ ├── hr-rifle_run.png │ │ ├── hr-rocket_run-shadow.png │ │ ├── hr-rocket_run.png │ │ ├── hr-smg_run-shadow.png │ │ ├── hr-smg_run.png │ │ ├── hr-terminator_run-shadow.png │ │ ├── hr-terminator_run.png │ │ ├── rifle_run-shadow.png │ │ ├── rifle_run.png │ │ ├── rocket_run-shadow.png │ │ ├── rocket_run.png │ │ ├── smg_run-shadow.png │ │ ├── smg_run.png │ │ ├── terminator_run-shadow.png │ │ └── terminator_run.png │ ├── buildings │ │ ├── droid-assembler-shadow.png │ │ ├── droid-assembler.png │ │ ├── droid-counter-shadow.png │ │ ├── droid-counter.png │ │ ├── droid-settings-shadow.png │ │ ├── droid-settings.png │ │ ├── guard-station-shadow.png │ │ ├── guard-station.png │ │ ├── hr-droid-assembler-shadow.png │ │ ├── hr-droid-assembler.png │ │ ├── hr-droid-counter-shadow.png │ │ ├── hr-droid-counter.png │ │ ├── hr-droid-settings-shadow.png │ │ ├── hr-droid-settings.png │ │ ├── hr-guard-station-shadow.png │ │ ├── hr-guard-station.png │ │ ├── hr-loot-chest-shadow.png │ │ ├── hr-loot-chest.png │ │ ├── hr-patrol-pole-shadow.png │ │ ├── hr-patrol-pole.png │ │ ├── loot-chest-shadow.png │ │ └── loot-chest.png │ └── laser │ │ └── dual-laser.png ├── icons │ ├── defender.png │ ├── defender_unit_undep.png │ ├── destroyer.png │ ├── destroyer_unit_undep.png │ ├── distractor.png │ ├── distractor_unit_undep.png │ ├── droid-assembling-machine.png │ ├── droid-counter.png │ ├── droid-guard-station.png │ ├── droid-settings.png │ ├── droid_flame.png │ ├── droid_flame_undep.png │ ├── droid_rifle.png │ ├── droid_rifle_undep.png │ ├── droid_rocket.png │ ├── droid_rocket_undep.png │ ├── droid_smg.png │ ├── droid_smg_undep.png │ ├── loot-chest.png │ ├── pickup_tool.png │ ├── selection_tool.png │ ├── signal_droid_counter.png │ ├── signal_droid_flame_count.png │ ├── signal_droid_rifle_count.png │ ├── signal_droid_rocket_count.png │ ├── signal_droid_smg_count.png │ ├── signal_droid_terminator_count.png │ ├── signal_guard_size.png │ ├── signal_hunt_radius.png │ ├── signal_retreat_size.png │ ├── signal_squad_size.png │ ├── terminator.png │ ├── terminator_undep.png │ └── unit-selection.png └── technology │ ├── robotarmy-tech-defender-unit.png │ ├── robotarmy-tech-destroyer-unit.png │ ├── robotarmy-tech-distractor-unit.png │ ├── robotarmy-tech-droid-flame.png │ ├── robotarmy-tech-droid-rifle.png │ ├── robotarmy-tech-droid-rocket.png │ ├── robotarmy-tech-droid-smg.png │ ├── robotarmy-tech-droid-terminator.png │ └── robotarmy-tech-robotics.png ├── info.json ├── locale ├── de │ └── locale.cfg ├── en │ └── locale.cfg ├── pl │ └── locale.cfg ├── pt-BR │ └── locale.cfg └── ru │ └── locale.cfg ├── migrations ├── robotarmy_0.4.11.lua ├── robotarmy_0.4.12.lua └── robotarmy_0.4.16.lua ├── prototypes ├── DroidUnitList.lua ├── building.lua ├── corpses.lua ├── defender-unit.lua ├── destroyer-unit.lua ├── distractor-unit.lua ├── entity.lua ├── item.lua ├── projectiles │ └── projectiles.lua ├── recipe.lua ├── signals.lua └── technology.lua ├── robolib ├── 30log.lua ├── Squad.lua ├── SquadControl.lua ├── eventhandlers.lua ├── onload.lua ├── retreat.lua ├── robotarmyhelpers.lua ├── statistics.lua ├── targeting.lua └── util.lua ├── stdlib ├── area │ ├── area.lua │ ├── chunk.lua │ ├── position.lua │ └── tile.lua ├── config │ └── config.lua ├── core.lua ├── data │ ├── data.lua │ └── recipe.lua ├── entity │ ├── entity.lua │ └── inventory.lua ├── event │ ├── event.lua │ └── time.lua ├── game.lua ├── gui │ └── gui.lua ├── log │ └── logger.lua ├── string.lua ├── surface.lua ├── table.lua ├── time.lua └── trains │ └── trains.lua └── thumbnail.png /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | *~ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2024 Kyran Findlater 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robot Army mod for Factorio V2.0+ 2 | 3 | ## Version 4 | 2.4.21, 5 | Known to be compatible with Factorio v2.0.43. Requires at least v2.0 due to mod script API features used. 6 | 7 | 8 | ## Description 9 | A mod to add robot troop units and associated support buildings and items to produce and control them. Will allow the users to automate warfare against the Biters or other players if you are using a PvP scenario. 10 | 11 | Note the Unit Control mod works very well for controlling the Robot Army units and when active will disable normal automated squad control (to make it work smoother) 12 | 13 | Please see specific info about this mod's content [on the GitHub Wiki page](https://github.com/kyranf/robotarmyfactorio/wiki) 14 | 15 | The Factorio Forums thread can be found here: [Forum link](https://forums.factorio.com/viewtopic.php?f=97&t=23543) 16 | 17 | Mod portal link here: [Mod Portal](https://mods.factorio.com/mods/kyranzor/robotarmy) 18 | 19 | ## Credits 20 | * YuokiTani for the great models of the Droids 21 | * Klonan for his Combat Units and permission to integrate the mod into mine, and his great work with the Unit Control mods 22 | * Dauphin for his significant contributions to the codebase for squad AI and performance enhancements 23 | * Earendel for inspiration and code examples for the RTS-style control of units from the AAI Programmable Vehicles mod. 24 | * Felipe Bueno Aliski Alves for the Brazilian Portuguese translations 25 | * Varoga for the russian translations 26 | * cyril-orlov for the Factorio v2.0 migration work 27 | 28 | ## Donations 29 | If you are feeling generous or thankful for the work i've done, feel free to donate to me so I can bribe my wife with chocolate to help her put up with my late nights! Click the image below: 30 | [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/KyranF) 31 | Or simply use this: [Donate](https://www.paypal.me/KyranF) -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Version: 2.4.21 2 | Date: 2025.04.01 3 | Changes: 4 | - completely removed patrol poles from the mod, will replace with something else in the future, maybe. 5 | Bugfixes: 6 | - Fixed german locale file encoding 7 | - added more unitgroup/commandable validity checks to reduce crashing 8 | --------------------------------------------------------------------------------------------------- 9 | Version: 2.4.20 10 | Date: 2025.01.05 11 | Bugfixes: 12 | - Fixed code error causing crash on load or newgame 13 | --------------------------------------------------------------------------------------------------- 14 | Version: 2.4.19 15 | Date: 2025.01.05 16 | Changes: 17 | - Adding mod conflict for Combat Robot Overhaul mod (#190) 18 | - temporarily disabled patrol post processing to prevent crashing user's games, until a new system can be made. (#189) 19 | Bugfixes: 20 | - Fixed flamebot not able to be manufactured due to stack size changes for light armour (#191) 21 | - Fixed new api change for spill-item-stack when making multiple settings modules (#188) 22 | --------------------------------------------------------------------------------------------------- 23 | Version: 2.4.18 24 | Date: 2024.12.13 25 | Changes: 26 | - ported to support v2.0, thanks greatly to Cyril Orlov 27 | - changed target type to search&destroy for more direct fighting behaviours. can change target type back to 3 if you want. 28 | - reducing flame droid fire resist from 100% to 95%, so they can eventually kill eachother if they get into friendly fire or against other forces 29 | Bugfixes: 30 | - much better AI update rates which you can tune in the config.lua sanity-check seconds and distance multiplier. 31 | --------------------------------------------------------------------------------------------------- 32 | Version: 0.4.17 33 | Date: 2022.06.06 34 | Changes: 35 | - adding Polish translation thanks to Ziomek24 36 | - updated Russian translation by Varoga, issue #180 37 | - reducing flame droid fire resist from 100% to 95%, so they can eventually kill eachother if they get into friendly fire or against other forces 38 | Bugfixes: 39 | - bugfix for invalid unit group during attack orders with command tool 40 | --------------------------------------------------------------------------------------------------- 41 | Version: 0.4.16 42 | Date: 2021.10.23 43 | Changes: 44 | - (snouz) 45 | - 9 new technologies 46 | - Buildings visual adjustments (combinators, loot chest, patrol pole). 47 | - New building icons. 48 | - Sorted recipe/item order. 49 | - Description improvements. 50 | - (kyranzor) 51 | - fixing issue #173 52 | - adjusting migration for 0.4.12 to consume less RAM again. 53 | --------------------------------------------------------------------------------------------------- 54 | Version: 0.4.15 55 | Date: 2021.10.23 56 | Bugfixes: 57 | - removed old migration file which causes script issues 58 | --------------------------------------------------------------------------------------------------- 59 | Version: 0.4.14 60 | Date: 2021.10.22 61 | Changes: 62 | - snouz joining force to work on the mod. 63 | - Added HR versions for all robots (Upscaled using ESRGAN). 64 | - Removed the artificial tint to apply some custom ones in the images. 65 | - Attacking bots now use 22 directions animation instead of 8. 66 | - Standardized and redrew shadows. 67 | - Removed unused images and optimized PNGs. 68 | - New 64px icons for bots and signals. 69 | - Deployment icons now don't display in player crafting. 70 | - Standardized/renamed changelog (so it displays in the mod portal). 71 | - New mod thumbnail. 72 | - HR version of buildings (temporary upscale). 73 | - Visually shifted robots up, so it looks more natural. 74 | - Some code and file structure improvements. 75 | Bugfixes: 76 | - Fixed flying bots shadows, using HR version from vanilla. 77 | - Removed visible "deploy" dummy items. 78 | --------------------------------------------------------------------------------------------------- 79 | Version: 0.4.13 80 | Bugfixes: 81 | - attempting to fix github issue #164 82 | - attempting to fix github issue #172 83 | - fixing migration script issue with the 0.4.12 migration where it would chew up peoples RAM and break their computers. made it do +-1000 tiles instead of +-15000 tiles for each surface, and each force. 84 | --------------------------------------------------------------------------------------------------- 85 | Version: 0.4.12 86 | Changes: 87 | - added migration script to detect and add all droid units to script-tracked array 88 | - added AI distraction custom commands for selecting nearest target instead of random target (overriding default ai behaviour of biters) thanks to Klonan 89 | Locale: 90 | - added Brazilian Portuguese language translations thanks to Felipe Bueno Aliski Alves 91 | - added Russian language translations thanks to Varoga on Github 92 | --------------------------------------------------------------------------------------------------- 93 | Version: 0.4.11 94 | Changes: 95 | - added a for-each-force loop instead of specific forces, during init routine to set up each force 96 | - added a handleOnScriptRaisedBuilt function for script-spawned entity event handling. 97 | - adding migration file to help players who add mod after starting game to auto detect existing techs and unlock recipes properly for them. 98 | --------------------------------------------------------------------------------------------------- 99 | Version: 0.4.10 100 | Changes: 101 | - if unit control is active, don't run the runtime logic for assemblers squad management/merging/retreat and assembler-centric targetting, and don't run the squad logic at all. This should help in-progress games which add unit control halfway through. 102 | - added ai_settings to all droid units and flying units to try to prevent units from being deleted if they fail a command, and for them to attempt to spread out from eachother and not stack up. 103 | --------------------------------------------------------------------------------------------------- 104 | Version: 0.4.9 105 | Changes: 106 | - upgrading the version requirement of Unit Control (optional mod) to 3.10 for supporting the new API 107 | - fixes to how the mod uses the new Unit Control API for raising unit events from known deployers. 108 | - adding Guard Station to deployer supported for Unit Control 109 | - reducing how/when the guard and assembler stations try to run logic when spawning units while Unit Control is active. Basically all droid processing is halted. 110 | --------------------------------------------------------------------------------------------------- 111 | Version: 0.4.8 112 | Bugfixes: 113 | - fixes to the Droid Counter module and the Settings Module code, which had issues with referencing parameters tables which have changed in recent history. instead of control_behaviours.parameters.parameters, there's only one layer of parameters table now. Yay! Means I can get rid of the comment I had in the code, "-- ridiculous, we have to do parameters WHY WUBE WHY" 114 | --------------------------------------------------------------------------------------------------- 115 | Version: 0.4.7 116 | Bugfixes: 117 | - removed migration file for v.0.3.5, super old and not needed, was causing issues with referring to old/out-dated tech names too. 118 | --------------------------------------------------------------------------------------------------- 119 | Version: 0.4.6 120 | Changes: 121 | - Compatible with 1.1.1 122 | - deployable flying robots unlocked at same tech level as their capsule friends. defender, distractor, and destroyer at the same tech levels, rather than all of them ready as soon as you get Defender tech. 123 | --------------------------------------------------------------------------------------------------- 124 | Version: 0.4.5 125 | Changes: 126 | - Compatible with 0.18.27 (by using the new raise_built flag instead of calling script raise entity event). 127 | - biter factions should no longer cause a tick error for squads/forces 128 | --------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /config/config.lua: -------------------------------------------------------------------------------- 1 | 2 | TICK_UPDATE_SQUAD_AI = 60 -- 60 ticks per second, how many ticks between updating squad AI (finding new targets, moving back into position, etc) 3 | DEFAULT_SQUAD_RADIUS = 2 -- how wide their attack_area radius is. not really used honestly.. 4 | SOLDIER_MAX_AMMO = 100 -- unused, might be used later to simulate having to come back and resupply. 5 | SQUAD_SIZE_MIN_BEFORE_HUNT = 10 -- how many droids are required in a squad before they are commanded to attack nearest target. 6 | -- override-able from settings combinator 7 | SQUAD_SIZE_MIN_BEFORE_RETREAT = 2 -- if a squad has been hunting and is down to this amount of guys left, head to nearest droid assembler for backup. 8 | -- override-able from settings combinator 9 | SQUAD_CHECK_RANGE = 20 -- range in tiles when a droid is spawned to check for existing squad to join, else creates its own squad 10 | SQUAD_HUNT_RADIUS = 5000 -- range in tiles, as a radius from squad. override-able from settings combinator 11 | AT_ASSEMBLER_RANGE = 20 -- range in tiles where we consider a droid or squad to be 'at' an assembler. 12 | MERGE_RANGE = 20 13 | 14 | ASSEMBLER_UPDATE_TICKRATE = 120 -- how often does the droid assembler building check for spawnable droid items in the output inv. 15 | -- how fast to spawn a droid once it's been actually assembled. 16 | 17 | BOT_COUNTERS_UPDATE_TICKRATE = 60 -- how often does the robot army combinator count droids and update combinator signals? 18 | LONE_WOLF_CLEANUP_SCRIPT_PERIOD = 18000 -- how often to find and deal with droids that are "wanderers" and not in a squad. NOT USED YET 19 | GUARD_STATION_GARRISON_SIZE = 10 -- limit to how many a guard station will spawn based on counting nearby droids 20 | 21 | USE_TELEPORTATION_FIX = 1 --if player is not looking, teleport stuck droids back to their squad-mates so they can keep moving 22 | PLAYER_VIEW_RADIUS = 60 -- this is very simplistic, but it's a start. helps avoid having teleporting droids while the player is looking. 23 | 24 | SANITY_CHECK_PERIOD_SECONDS = 10 25 | SANITY_CHECK_PATH_DISTANCE_DIV_FACTOR = 5000 -- every extra 500 tiles should give us an extra SANITY_CHECK_PERIOD_SECONDS 26 | PRINT_SQUAD_DEATH_MESSAGES = 1 --if you want it to tell you when squad x completely dies 27 | -- PRINT_SQUAD_MERGE_MESSAGES = 0 28 | ASSEMBLER_MERGE_TICKRATE = 180 29 | 30 | SQUAD_UNITGROUP_FAILURE_DISTANCE_ESTIMATE = 40 31 | UG_FAILURE_RECENCY_TICKS = 300 --unit group command failure timeout 32 | MAX_CONSECUTIVE_UNITGROUP_FAILURES_BEFORE_RETREAT = 2 --how many times they totally fail before the squad disbands/retreats 33 | 34 | GLOBAL_TARGETING_TYPE = 1 -- see targeting.lua for type options 35 | DEFAULT_KEEP_RADIUS_CLEAR = 500 -- in tiles from nearest assembler to squad. squad will keep this area clear as priority, and then check hunt radius. 36 | 37 | --CONFIG SETTINGS FOR THOSE WHO WANT TO SCALE THE DAMAGE AND HEALTH OF DROIDS 38 | HEALTH_SCALAR = 1.0 -- scales health by this value, default 1.0. 0.5 gives 50% health, 2.0 doubles their health etc. 39 | 40 | DAMAGE_SCALAR = 1.0 -- scales base damage by this value. default is 1.0. 0.5 makes 50% less base damage. 41 | -- 1.5 gives 50% more base damage. remember, technologies apply multipliers to the base damage so this value should take 42 | -- that into consideration. 43 | 44 | ARTIFACT_GRAB_RADIUS = 30 --now obsolete, artifact grab script is not used now, as of Factorio 0.15+ 45 | 46 | GRAB_ARTIFACTS = 0 --set this to 1 and you should have access to the loot chests, enables artifact grabbing AI routine. save CPU and keep this off if you don't need it. 47 | -------------------------------------------------------------------------------- /control.lua: -------------------------------------------------------------------------------- 1 | require("util") 2 | require("config.config") -- config for squad control mechanics - important for anyone using 3 | require("robolib.util") -- some utility functions not necessarily related to robot army mod 4 | require("robolib.robotarmyhelpers") -- random helper functions related to the robot army mod 5 | require("robolib.Squad") -- allows us to control squads, add entities to squads, etc. 6 | require("robolib.eventhandlers") 7 | require("robolib.onload") 8 | require("prototypes.DroidUnitList") -- so we know what is spawnable 9 | require("stdlib/log/logger") 10 | require("stdlib/game") 11 | 12 | LOGGER = Logger.new("robotarmy", "robot_army_logs", false, {log_ticks = false}) 13 | 14 | storage.runOnce = false 15 | 16 | function init_robotarmy() 17 | LOGGER.log("Robot Army mod Init script running...") 18 | 19 | if not storage.Squads then 20 | storage.Squads = {} 21 | end 22 | 23 | if not storage.uniqueSquadId then 24 | storage.uniqueSquadId = {} 25 | end 26 | 27 | if not storage.DroidAssemblers then 28 | storage.DroidAssemblers = {} 29 | end 30 | 31 | if not storage.droidCounters then 32 | storage.droidCounters = {} 33 | end 34 | 35 | if not storage.lootChests then 36 | storage.lootChests = {} 37 | end 38 | 39 | if storage.rallyBeacons then 40 | storage.rallyBeacons = nil 41 | end 42 | 43 | if not storage.droidGuardStations then 44 | storage.droidGuardStations = {} 45 | end 46 | 47 | if not storage.updateTable then 48 | storage.updateTable = {} 49 | end 50 | 51 | if not storage.units then 52 | storage.units = {} 53 | end 54 | 55 | --deal with player force as default set-up process 56 | event = {} --event stub, to match function inputs 57 | for _, v in pairs(game.forces) do 58 | event.force = v 59 | handleForceCreated(event) 60 | end 61 | LOGGER.log("Robot Army mod Init script finished...") 62 | game.print("Robot Army mod Init completed!") 63 | end 64 | 65 | 66 | script.on_init(init_robotarmy) 67 | 68 | script.on_event(defines.events.on_force_created, handleForceCreated) 69 | script.on_event(defines.events.on_built_entity, handleOnBuiltEntity) 70 | script.on_event(defines.events.on_robot_built_entity, handleOnRobotBuiltEntity) 71 | script.on_event(defines.events.script_raised_built, handleOnScriptRaisedBuilt) 72 | script.on_event(defines.events.on_ai_command_completed, AiCommandCompleteHandler) 73 | 74 | function playerSelectedArea(event) 75 | reportSelectedUnits(event, false) 76 | end 77 | 78 | function playerAltSelectedArea(event) 79 | reportSelectedUnits(event, true) 80 | end 81 | 82 | script.on_event(defines.events.on_player_selected_area, playerSelectedArea) 83 | script.on_event(defines.events.on_player_alt_selected_area, playerAltSelectedArea) 84 | 85 | -- this on tick handler will get replaced on the first tick after 'live' migrations have run 86 | script.on_event(defines.events.on_tick, bootstrap_migration_on_first_tick) 87 | script.on_event(defines.events.on_player_joined_game, onPlayerJoined) 88 | script.on_configuration_changed(handleModChanges) -------------------------------------------------------------------------------- /data-updates.lua: -------------------------------------------------------------------------------- 1 | if _G.bobmods and data.raw.item["basic-circuit-board"] then 2 | --bobmods is present so lets just make use of his lib function 3 | _G.bobmods.lib.recipe.replace_ingredient("droid-rifle","electronic-circuit" , "basic-circuit-board") 4 | end 5 | 6 | -- The base game acid splashes are OP. 7 | -- Just turn off the damage and sticker on ground effect. 8 | 9 | for k, fire in pairs (data.raw.fire) do 10 | if fire.name:find("acid%-splash%-fire") then 11 | fire.on_damage_tick_effect = nil 12 | end 13 | end -------------------------------------------------------------------------------- /data.lua: -------------------------------------------------------------------------------- 1 | require("config.config") 2 | require("prototypes.technology") 3 | require("prototypes.item") -- any buildable or placable object/entity needs this 4 | require("prototypes.building") 5 | require("prototypes.entity") -- any buildable or placable object/entity needs this 6 | require("prototypes.recipe") 7 | require("prototypes.signals") 8 | require("prototypes.projectiles.projectiles") 9 | require("prototypes.corpses") 10 | require("prototypes.destroyer-unit") 11 | require("prototypes.distractor-unit") 12 | require("prototypes.defender-unit") 13 | -------------------------------------------------------------------------------- /graphics/entity/bots/flame_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/flame_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/flame_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/flame_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-flame_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-flame_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-flame_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-flame_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-rifle_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-rifle_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-rifle_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-rifle_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-rocket_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-rocket_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-rocket_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-rocket_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-smg_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-smg_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-smg_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-smg_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-terminator_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-terminator_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/hr-terminator_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/hr-terminator_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/rifle_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/rifle_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/rifle_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/rifle_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/rocket_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/rocket_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/rocket_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/rocket_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/smg_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/smg_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/smg_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/smg_run.png -------------------------------------------------------------------------------- /graphics/entity/bots/terminator_run-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/terminator_run-shadow.png -------------------------------------------------------------------------------- /graphics/entity/bots/terminator_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/bots/terminator_run.png -------------------------------------------------------------------------------- /graphics/entity/buildings/droid-assembler-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/droid-assembler-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/droid-assembler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/droid-assembler.png -------------------------------------------------------------------------------- /graphics/entity/buildings/droid-counter-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/droid-counter-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/droid-counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/droid-counter.png -------------------------------------------------------------------------------- /graphics/entity/buildings/droid-settings-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/droid-settings-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/droid-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/droid-settings.png -------------------------------------------------------------------------------- /graphics/entity/buildings/guard-station-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/guard-station-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/guard-station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/guard-station.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-droid-assembler-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-droid-assembler-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-droid-assembler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-droid-assembler.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-droid-counter-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-droid-counter-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-droid-counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-droid-counter.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-droid-settings-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-droid-settings-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-droid-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-droid-settings.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-guard-station-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-guard-station-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-guard-station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-guard-station.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-loot-chest-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-loot-chest-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-loot-chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-loot-chest.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-patrol-pole-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-patrol-pole-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/hr-patrol-pole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/hr-patrol-pole.png -------------------------------------------------------------------------------- /graphics/entity/buildings/loot-chest-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/loot-chest-shadow.png -------------------------------------------------------------------------------- /graphics/entity/buildings/loot-chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/buildings/loot-chest.png -------------------------------------------------------------------------------- /graphics/entity/laser/dual-laser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/entity/laser/dual-laser.png -------------------------------------------------------------------------------- /graphics/icons/defender.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/defender.png -------------------------------------------------------------------------------- /graphics/icons/defender_unit_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/defender_unit_undep.png -------------------------------------------------------------------------------- /graphics/icons/destroyer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/destroyer.png -------------------------------------------------------------------------------- /graphics/icons/destroyer_unit_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/destroyer_unit_undep.png -------------------------------------------------------------------------------- /graphics/icons/distractor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/distractor.png -------------------------------------------------------------------------------- /graphics/icons/distractor_unit_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/distractor_unit_undep.png -------------------------------------------------------------------------------- /graphics/icons/droid-assembling-machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid-assembling-machine.png -------------------------------------------------------------------------------- /graphics/icons/droid-counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid-counter.png -------------------------------------------------------------------------------- /graphics/icons/droid-guard-station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid-guard-station.png -------------------------------------------------------------------------------- /graphics/icons/droid-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid-settings.png -------------------------------------------------------------------------------- /graphics/icons/droid_flame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_flame.png -------------------------------------------------------------------------------- /graphics/icons/droid_flame_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_flame_undep.png -------------------------------------------------------------------------------- /graphics/icons/droid_rifle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_rifle.png -------------------------------------------------------------------------------- /graphics/icons/droid_rifle_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_rifle_undep.png -------------------------------------------------------------------------------- /graphics/icons/droid_rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_rocket.png -------------------------------------------------------------------------------- /graphics/icons/droid_rocket_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_rocket_undep.png -------------------------------------------------------------------------------- /graphics/icons/droid_smg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_smg.png -------------------------------------------------------------------------------- /graphics/icons/droid_smg_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/droid_smg_undep.png -------------------------------------------------------------------------------- /graphics/icons/loot-chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/loot-chest.png -------------------------------------------------------------------------------- /graphics/icons/pickup_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/pickup_tool.png -------------------------------------------------------------------------------- /graphics/icons/selection_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/selection_tool.png -------------------------------------------------------------------------------- /graphics/icons/signal_droid_counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_droid_counter.png -------------------------------------------------------------------------------- /graphics/icons/signal_droid_flame_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_droid_flame_count.png -------------------------------------------------------------------------------- /graphics/icons/signal_droid_rifle_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_droid_rifle_count.png -------------------------------------------------------------------------------- /graphics/icons/signal_droid_rocket_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_droid_rocket_count.png -------------------------------------------------------------------------------- /graphics/icons/signal_droid_smg_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_droid_smg_count.png -------------------------------------------------------------------------------- /graphics/icons/signal_droid_terminator_count.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_droid_terminator_count.png -------------------------------------------------------------------------------- /graphics/icons/signal_guard_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_guard_size.png -------------------------------------------------------------------------------- /graphics/icons/signal_hunt_radius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_hunt_radius.png -------------------------------------------------------------------------------- /graphics/icons/signal_retreat_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_retreat_size.png -------------------------------------------------------------------------------- /graphics/icons/signal_squad_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/signal_squad_size.png -------------------------------------------------------------------------------- /graphics/icons/terminator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/terminator.png -------------------------------------------------------------------------------- /graphics/icons/terminator_undep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/terminator_undep.png -------------------------------------------------------------------------------- /graphics/icons/unit-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/icons/unit-selection.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-defender-unit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-defender-unit.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-destroyer-unit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-destroyer-unit.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-distractor-unit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-distractor-unit.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-droid-flame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-droid-flame.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-droid-rifle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-droid-rifle.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-droid-rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-droid-rocket.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-droid-smg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-droid-smg.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-droid-terminator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-droid-terminator.png -------------------------------------------------------------------------------- /graphics/technology/robotarmy-tech-robotics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/graphics/technology/robotarmy-tech-robotics.png -------------------------------------------------------------------------------- /info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "robotarmy", 3 | "version": "2.4.21", 4 | "title": "Robot Army", 5 | "author": "Kyranzor", 6 | "contact": "kyranzor@gmail.com", 7 | "homepage": "https://github.com/kyranf/robotarmyfactorio", 8 | "description": "Manufacture and deploy automated robot soldiers to attack your foes and defend your factory!", 9 | "dependencies": [ 10 | "base >= 2.0.0", 11 | "? Unit_Control >= 0.3.10", 12 | "! CombatRobotsOverhaul" 13 | ], 14 | "factorio_version": "2.0" 15 | } -------------------------------------------------------------------------------- /locale/de/locale.cfg: -------------------------------------------------------------------------------- 1 | [item-name] 2 | droid-smg=Kampfdroide (nicht eingesetzt) 3 | droid-smg-dummy=Kampfdroide (einsetzbar) 4 | droid-rocket=Raketendroide (nicht eingesetzt) 5 | droid-rocket-dummy=Raketendroide (einsetzbar) 6 | droid-rifle=Uhrwerk Gewehrroboter (nicht eingesetzt) 7 | droid-rifle-dummy=Uhrwerk Gewehrroboter (einsetzbar) 8 | droid-assembling-machine=Robotmontagemaschine 9 | terminator=Terminator 10 | terminator-dummy=Terminator (einsetzbar) 11 | droid-counter=Roboter Aktivitätsmodul 12 | loot-chest=Armee Beutekiste 13 | droid-guard-station=Roboterwachstation 14 | droid-flame=Flamebot (undeployed) 15 | droid-settings=Droid Settings Module 16 | 17 | 18 | [entity-name] 19 | droid-smg=Battle Droid 20 | droid-rocket=Rocket Droid 21 | droid-rifle=Uhrwerk Gewehrroboter 22 | droid-assembling-machine=Robotmontagemaschine 23 | terminator=Terminator 24 | droid-counter=Roboter Aktivitätsmodul 25 | loot-chest=Armee Beutekiste 26 | droid-guard-station=Roboterwachstation 27 | droid-flame=Flamebot 28 | droid-settings=Droid Settings Module 29 | 30 | 31 | [recipe-name] 32 | droid-smg=Kampfdroide (nicht eingesetzt) 33 | droid-rocket=Raketendroide (nicht eingesetzt) 34 | droid-smg-deploy=Kampfdroide (eingesetzt) 35 | droid-rifle=Uhrwerk Gewehrroboter (nicht eingesetzt) 36 | droid-rifle-deploy=Uhrwerk Gewehrroboter (eingesetzt) 37 | droid-rocket-deploy=Raketendroide (eingesetzt) 38 | terminator=Terminator (nicht eingesetzt) 39 | terminator-deploy=Terminator (eingesetzt) 40 | droid-counter=Roboter Aktivitätsmodul 41 | loot-chest=Armee Beutekiste 42 | droid-guard-station=Roboterwachstation 43 | droid-flame=Flamebot (Undeployed) 44 | droid-flame-deploy=Flamebot (Deployed) 45 | droid-settings=Droid Settings Module 46 | 47 | 48 | [virtual-signal-name] 49 | signal-droid-alive-count=Anzahl aktiver Roboter 50 | signal-droid-rifle-count=Clockwork Riflebot Count 51 | signal-droid-smg-count=Battle Droid Count 52 | signal-droid-rocket-count=Rocket Droid Count 53 | signal-droid-terminator-count=Terminator Count 54 | signal-droid-flame-count=Firebot Count 55 | signal-hunt-radius=Squad Hunt Radius 56 | signal-guard-size=Guard Station Squad Size 57 | signal-retreat-size=Retreat Size 58 | signal-squad-size=Hunting Squad Size -------------------------------------------------------------------------------- /locale/en/locale.cfg: -------------------------------------------------------------------------------- 1 | [item-name] 2 | droid-rifle=Clockwork Riflebot 3 | droid-smg=Battle Droid 4 | droid-rocket=Rocket Droid 5 | droid-flame=Flamebot 6 | terminator=Terminator 7 | defender-unit=Defender Robot Unit 8 | distractor-unit=Distractor Robot Unit 9 | destroyer-unit=Destroyer Robot Unit 10 | droid-rifle-dummy=Clockwork Riflebot (deployable) 11 | droid-smg-dummy=Battle Droid (deployable) 12 | droid-rocket-dummy=Rocket Droid (deployable) 13 | droid-flame-dummy=Flamebot (deployable) 14 | terminator-dummy=Terminator (deployable) 15 | defender-unit-dummy= Defender Robot Unit (deployable) 16 | distractor-unit-dummy=Distractor Robot Unit (deployable) 17 | destroyer-unit-dummy=Destroyer Robot Unit (deployable) 18 | 19 | droid-assembling-machine=Droid Assembler 20 | droid-guard-station=Droid Guard Station 21 | droid-counter=Droid Activity Module 22 | droid-settings=Droid Settings Module 23 | loot-chest=Army Loot Chest 24 | droid-pickup-tool=Droid Pick-Up Tool 25 | droid-selection-tool=Droid Squad Command Tool 26 | 27 | [recipe-name] 28 | droid-rifle-deploy=Deploy Clockwork Riflebot 29 | droid-smg-deploy=Deploy Battle Droid 30 | droid-rocket-deploy=Deploy Rocket Droid 31 | droid-flame-deploy=Deploy Flamebot 32 | terminator-deploy=Deploy Terminator 33 | defender-unit-deploy=Deploy Defender Robot Unit 34 | distractor-unit-deploy=Deploy Distractor Robot Unit 35 | destroyer-unit-deploy=Deploy Destroyer Robot Unit 36 | 37 | [item-description] 38 | droid-rifle=Long range slow-reload rifle-equipped droid. Good in swarms. 39 | droid-smg=Battle Droid, mounted with sub-machine gun. 40 | droid-rocket=Rocket launcher equipped droid. Shoots slowly but stongly and from afar. 41 | droid-flame=Flamethrower armed droid with a flame-resistant layer. 42 | terminator=Most powerful battle droid. Almost unstoppable in groups. 43 | defender-unit=Defender Robot Unit (undeployed), insert this to a droid assembler with recipe set to spawn this type or place on the ground to spawn. 44 | distractor-unit=Distractor Robot Unit (undeployed), insert this to a droid assembler with recipe set to spawn this type or place on the ground to spawn. 45 | destroyer-unit=Destroyer Robot Unit (undeployed), insert this to a droid assembler with recipe set to spawn this type or place on the ground to spawn. 46 | droid-rifle-dummy=Used as trigger to spawn unit near assembler. Put this in the output of assembler to use it. Normally you don't want to take these out. 47 | droid-smg-dummy=Used as trigger to spawn unit near assembler. Put this in the output of assembler to use it. Normally you don't want to take these out. 48 | droid-rocket-dummy=Used as trigger to spawn unit near assembler. Put this in the output of assembler to use it. Normally you don't want to take these out. 49 | droid-flame-dummy= Used as trigger to spawn unit near assembler. Put this in the output of assembler to use it. Normally you don't want to take these out. 50 | terminator-dummy=Used as trigger to spawn unit near assembler. Put this in the output of assembler to use it. Normally you don't want to take these out. 51 | defender-unit-dummy=Used as trigger to spawn unit near assembler. put this in the output of assembler to use it. Normally you don't want to take these out. 52 | distractor-unit-dummy=Used as trigger to spawn unit near assembler. put this in the output of assembler to use it. Normally you don't want to take these out. 53 | destroyer-unit-dummy=Used as trigger to spawn unit near assembler. put this in the output of assembler to use it. Normally you don't want to take these out. 54 | 55 | droid-assembling-machine=Set the recipe to the type of droid to spawn, then insert the undeployed droid item and it will be spawned nearby and join/create a squad. 56 | droid-guard-station=Droid Guard Station, spawns a squad which stay close to the station and act as guards. The station will maintain the squad size to a set amount and replace them as they die. 57 | droid-counter=Constant combinator outputing the number of each type of deployed droids. 58 | droid-settings=Set settings for your droid army by outputting signals.\nOnly one can be constructed. 59 | loot-chest=Droid quads automatically collect alien artifacts for you, teleports them in the __ITEM__loot-chest__. 60 | droid-pickup-tool=drag a box around a bunch of friendly units to pick them up instantly. 61 | droid-selection-tool=left click close to a squad to select (members will turn green when selected). shift-left-click to command to attack-move to that location. left click away from squad to deselect. Put it as icon on map to designate global attack target. 62 | 63 | [entity-name] 64 | droid-rifle=Clockwork Riflebot 65 | droid-smg=Battle Droid 66 | droid-rocket=Rocket Droid 67 | droid-assembling-machine=Droid Assembler 68 | terminator=Terminator 69 | droid-counter=Droid Activity Module 70 | loot-chest=Army Loot Chest 71 | droid-guard-station=Droid Guard Station 72 | droid-flame=Flamebot 73 | droid-settings=Droid Settings Module 74 | defender-unit=Defender Robot Unit 75 | distractor-unit=Distractor Robot Unit 76 | destroyer-unit=Destroyer Robot Unit 77 | 78 | [entity-description] 79 | droid-rifle=Long range slow-reload rifle-equipped droid. Good in swarms. 80 | droid-smg=Battle Droid, mounted with sub-machine gun. 81 | droid-rocket=Rocket launcher equipped droid. Shoots slowly but stongly and from afar. 82 | droid-flame=Flamethrower armed droid with a flame-resistant layer. 83 | terminator=Most powerful battle droid. Almost unstoppable in groups. 84 | defender-unit=Defender Robot Unit (undeployed), insert this to a droid assembler with recipe set to spawn this type or place on the ground to spawn. 85 | distractor-unit=Distractor Robot Unit (undeployed), insert this to a droid assembler with recipe set to spawn this type or place on the ground to spawn. 86 | destroyer-unit=Destroyer Robot Unit (undeployed), insert this to a droid assembler with recipe set to spawn this type or place on the ground to spawn. 87 | droid-assembling-machine=Set the recipe to the type of droid to spawn, then insert the undeployed droid item and it will be spawned nearby and join/create a squad. 88 | droid-guard-station=Droid Guard Station, spawns a squad which stay close to the station and act as guards. The station will maintain the squad size to a set amount and replace them as they die. 89 | droid-counter=Constant combinator outputing the number of each type of deployed droids. 90 | droid-settings=Set settings for your droid army by outputting signals.\nOnly one can be constructed. 91 | loot-chest=Droid quads automatically collect alien artifacts for you, teleports them in the __ITEM__loot-chest__. 92 | droid-pickup-tool=drag a box around a bunch of friendly units to pick them up instantly. 93 | droid-selection-tool=left click close to a squad to select (members will turn green when selected). shift-left-click to command to attack-move to that location. left click away from squad to deselect. Put it as icon on map to designate global attack target. 94 | 95 | [virtual-signal-name] 96 | signal-droid-alive-count=Droids Alive Count 97 | signal-droid-rifle-count=Clockwork Riflebot Count 98 | signal-droid-smg-count=Battle Droid Count 99 | signal-droid-rocket-count=Rocket Droid Count 100 | signal-droid-terminator-count=Terminator Count 101 | signal-droid-flame-count=Firebot Count 102 | signal-hunt-radius=Squad Hunt Radius 103 | signal-guard-size=Guard Station Squad Size 104 | signal-retreat-size=Retreat Size 105 | signal-squad-size=Hunting Squad Size 106 | 107 | [technology-name] 108 | robotarmy-tech-robotics=Army Robotics 109 | robotarmy-tech-droid-rifle=Clockwork Riflebot 110 | robotarmy-tech-droid-smg=Battle Droid 111 | robotarmy-tech-droid-rocket=Rocket Droid 112 | robotarmy-tech-droid-flame=Flamebot 113 | robotarmy-tech-droid-terminator=Terminator 114 | robotarmy-tech-defender-unit=Defender Robot Unit 115 | robotarmy-tech-distractor-unit=Distractor Robot Unit 116 | robotarmy-tech-destroyer-unit=Destroyer Robot Unit 117 | 118 | [technology-description] 119 | robotarmy-tech-robotics=Unlocks various droid techs and some basic robotics buildings. 120 | robotarmy-tech-droid-rifle=Unlocks the Clockwork Riflebot. 121 | robotarmy-tech-droid-smg=Unlocks the Battle Droid. 122 | robotarmy-tech-droid-rocket=Unlocks the Rocket Droid. 123 | robotarmy-tech-droid-flame=Unlocks the Flamebot. 124 | robotarmy-tech-droid-terminator=Unlocks the Terminator. 125 | robotarmy-tech-defender-unit=Unlocks the Defender Robot Unit. 126 | robotarmy-tech-distractor-unit=Unlocks the Distractor Robot Unit. 127 | robotarmy-tech-destroyer-unit=Unlocks the Destroyer Robot Unit. 128 | -------------------------------------------------------------------------------- /locale/pl/locale.cfg: -------------------------------------------------------------------------------- 1 | [item-name] 2 | basic-constructor=Droid inżynier 3 | droid-rifle=Droid lekkii 4 | droid-smg=Droid bojowy 5 | droid-rocket=Droid rakietowy 6 | droid-flame=Droid ogniowy 7 | terminator=Terminator 8 | defender-unit=Latający obrońca 9 | distractor-unit=Latający rozpraszacz 10 | destroyer-unit=Latający niszczyciel 11 | droid-assembling-machine=Monter robotów 12 | droid-counter=Aktywny moduł droidów 13 | loot-chest=Skrzynia z łupami wojskowymi 14 | droid-guard-station=Stacja strażnicza droidów 15 | droid-settings=Moduł ustawień droidów 16 | construction-warehouse=Magazyn budowlany 17 | droid-pickup-tool=Narzędzie do zbierania droidów 18 | 19 | [item-description] 20 | basic-constructor=Wykorzystuje zawartość pobliskich __ITEM__construction-warehouse__s do naprawy budynków i budowania duchów. 21 | droid-rifle=Droid uzbrojony w karabin o powolnym przeładowaniu dalekiego zasięgu. Dobry w grupach. 22 | droid-smg=Droid bojowy z karabinem maszynowym. 23 | droid-rocket=Droid wyposażony w wyrzutnię rakiet. Strzela powoli, ale mocno i z daleka. 24 | droid-flame=Droid uzbrojony w miotacz ognia z pancerzem ognioodpornym. 25 | terminator=Najpotężniejszy droid bojowy. Prawie nie do powstrzymania w grupach. 26 | defender-unit=Szybko latający robot bojowy z karabinem maszynowym. 27 | distractor-unit=Szybko latający robot bojowy z karabinem laserowym. 28 | destroyer-unit=Szybko latający robot bojowy z elektrycznym karabinem. 29 | droid-assembling-machine=Rozmieszcza roboty umieszczone w środku. 30 | droid-counter=Stały kombinator wyświetlający liczbę każdego rodzaju rozmieszczonych droidów. 31 | loot-chest=Droidy kłady automatycznie zbierają dla ciebie artefakty obcych, teleportują je do __ITEM__skrzyni z łupami__. 32 | droid-guard-station=Określona liczba droidów umieszczonych w __ITEM__droid-guard-station__ pojawi się w pobliżu i pozostanie w określonym obwodzie. Droidy są zastępowane, gdy zostaną zniszczone. 33 | droid-settings=Ustaw ustawienia dla swojej armii droidów, wysyłając sygnał.\Tylko jeden może zostać skonstruowany. 34 | construction-warehouse=Ograniczona skrzynia dostawcy dla __ITEM__basic-constructor__. 35 | droid-pickup-tool=Przeciągnij pole wokół kilku przyjaznych jednostek, aby natychmiast je podnieść. 36 | droid-selection-tool=Kliknij lewym przyciskiem myszy w pobliżu oddziału, aby wybrać (członkowie zmienią kolor na zielony po wybraniu). Lewy shift - kliknij na polecenie, aby zaatakować/przenieść się do tej lokalizacji. Kliknij lewym przyciskiem z dala od składu, aby odznaczyć. Umieść go jako ikonę na mapie, aby wskazać globalny cel ataku. 37 | 38 | [entity-name] 39 | basic-constructor=Droid inżynier 40 | droid-rifle=Droid lekkii 41 | droid-smg=Droid bojowy 42 | droid-rocket=Droid rakietowy 43 | droid-flame=Droid ogniowy 44 | terminator=Terminator 45 | defender-unit=Latający obrońca 46 | distractor-unit=Latający rozpraszacz 47 | destroyer-unit=Latający niszczyciel 48 | construction-warehouse=Magazyn budowlany 49 | droid-assembling-machine=Monter robotów 50 | droid-guard-station=Stacja strażnicza droidów 51 | droid-counter=Aktywny moduł droidów 52 | droid-settings=Moduł ustawień droidów 53 | loot-chest=Skrzynia z łupami wojskowymi 54 | 55 | 56 | [entity-description] 57 | basic-constructor=Wykorzystuje zawartość pobliskich __ITEM__construction-warehouse__s do naprawy budynków i budowania duchów. 58 | droid-rifle=Droid uzbrojony w karabin o powolnym przeładowaniu dalekiego zasięgu. Dobry w grupach. 59 | droid-smg=Droid bojowy z karabinem maszynowym. 60 | droid-rocket=Droid wyposażony w wyrzutnię rakiet. Strzela powoli, ale mocno i z daleka. 61 | droid-flame=Droid uzbrojony w miotacz ognia z pancerzem ognioodpornym. 62 | terminator=Najpotężniejszy droid bojowy. Prawie nie do powstrzymania w grupach. 63 | defender-unit=Szybko latający robot bojowy z karabinem maszynowym. 64 | distractor-unit=Szybko latający robot bojowy z karabinem laserowym. 65 | destroyer-unit=Szybko latający robot bojowy z elektrycznym karabinem. 66 | droid-assembling-machine=Monter robotów 67 | droid-guard-station=Stacja strażnicza droidów 68 | droid-counter=Aktywny moduł droidów 69 | droid-settings=Moduł ustawień droidów 70 | loot-chest=Skrzynia z łupami wojskowymi 71 | droid-pickup-tool=Przeciągnij pole wokół kilku przyjaznych jednostek, aby natychmiast je podnieść. 72 | droid-selection-tool=Kliknij lewym przyciskiem myszy w pobliżu oddziału, aby wybrać (członkowie zmienią kolor na zielony po wybraniu). Lewy shift - kliknij na polecenie, aby zaatakować/przenieść się do tej lokalizacji. Kliknij lewym przyciskiem z dala od składu, aby odznaczyć. 73 | 74 | [virtual-signal-name] 75 | signal-droid-alive-count=Liczba żywych droidów 76 | signal-droid-rifle-count=Liczba droidów lekkich 77 | signal-droid-smg-count=Liczba droidów bojowych 78 | signal-droid-rocket-count=Liczba droidów rakietowych 79 | signal-droid-terminator-count=Liczba terminatorów 80 | signal-droid-flame-count=Liczba droidów ogniowych 81 | signal-droid-engineer-count=Liczba droidów inżynierów 82 | signal-hunt-radius=Promień polowania drużyny 83 | signal-guard-size=Rozmiar oddziału posterunku straży droidów 84 | signal-retreat-size=Rozmiar odwrotu 85 | signal-squad-size=Rozmiar oddziału kładów 86 | signal-keep-clear-radius=Zachowaj czysty promień drużyny 87 | 88 | [mod-setting-name] 89 | Squad-Hunt-Size=Rozmiar polowania drużyny 90 | Squad-Retreat-Size=Rozmiar odwrotu oddziału 91 | Squad-Hunt-Radius=Promień polowania drużyny 92 | Guard-Station-Garrison-Size=Wielkość garnizonu posterunku straży 93 | Squad-Keep-Clear-Radius=Zachowaj czysty promień drużyny 94 | Attack-Targeting-Type=Typ celu ataku 95 | Droid-Health-Modifier=Modyfikator zdrowia droidów 96 | Droid-Damage-Modifier=Modyfikator obrażeń droidów 97 | Engineer-Droid-Repair-Range=Zasięg naprawy droidów inżynierskich 98 | Engineer-Droid-Construction-Check-Radius=Promień kontrolny konstrukcji droidów inżynierskich 99 | Counter-Update-Tickrate=Wskaźnik aktualizacji licznika 100 | Print-Squad-Death-Messages=Wydrukuj wiadomości o śmierci oddziału 101 | 102 | [mod-setting-description] 103 | Squad-Hunt-Size=Rozmiar polowania drużyny 104 | Squad-Retreat-Size=Rozmiar odwrotu oddziału 105 | Squad-Hunt-Radius=Promień polowania drużyny 106 | Guard-Station-Garrison-Size=Wielkość garnizonu posterunku straży 107 | Squad-Keep-Clear-Radius=Zachowaj czysty promień drużyny 108 | Attack-Targeting-Type=Typ celu ataku 109 | Droid-Health-Modifier=Modyfikator zdrowia droidów 110 | Droid-Damage-Modifier=Modyfikator obrażeń droidów 111 | Engineer-Droid-Repair-Range=Zasięg naprawy droidów inżynierskich 112 | Engineer-Droid-Construction-Check-Radius=Promień kontrolny konstrukcji droidów inżynierskich 113 | Counter-Update-Tickrate=Wskaźnik aktualizacji licznika 114 | Print-Squad-Death-Messages=Wydrukuj wiadomości o śmierci oddziału 115 | 116 | [technology-name] 117 | robotarmy-tech-robotics=Robotyka wojskowa 118 | robotarmy-tech-droid-basic-constructor=Droid inżynier 119 | robotarmy-tech-droid-rifle=Droid lekkii 120 | robotarmy-tech-droid-smg=Droid bojowy 121 | robotarmy-tech-droid-rocket=Droid rakietowy 122 | robotarmy-tech-droid-flame=Droid ogniowy 123 | robotarmy-tech-droid-terminator=Terminator 124 | robotarmy-tech-defender-unit=Latający obrońca 125 | robotarmy-tech-distractor-unit=Latający rozpraszacz 126 | robotarmy-tech-destroyer-unit=Latający niszczyciel 127 | 128 | [technology-description] 129 | robotarmy-tech-robotics=Odblokowuje różne technologie droidów i niektóre podstawowe budynki robotów. 130 | robotarmy-tech-droid-basic-constructor=Odblokuj droida inżyniera. 131 | robotarmy-tech-droid-rifle=Odblokuj droida lekkiego. 132 | robotarmy-tech-droid-smg=Odblokuj droida bojowego. 133 | robotarmy-tech-droid-rocket=Odblokuj droida rakietowego. 134 | robotarmy-tech-droid-flame=Odblokuj droida ogniowego. 135 | robotarmy-tech-droid-terminator=Odblokuj terminatora. 136 | robotarmy-tech-defender-unit=Odblokuj latającego robota obrońce. 137 | robotarmy-tech-distractor-unit=Odblokuj robota latającego rozpraszacz. 138 | robotarmy-tech-destroyer-unit=Odblokuj robota latającego niszczyciel. 139 | -------------------------------------------------------------------------------- /locale/pt-BR/locale.cfg: -------------------------------------------------------------------------------- 1 | [item-name] 2 | droid-smg=Robo de batalha (não posicionado) 3 | droid-smg-dummy=Robo de batalha (Posicionavel) 4 | droid-rocket=Robo Bazuca (não posicionado) 5 | droid-rocket-dummy=Robo Bazuca (Posicionavel) 6 | droid-rifle=Robo Mecanico (não posicionado) 7 | droid-rifle-dummy=Robo Mecanico (Posicionavel) 8 | droid-assembling-machine=Montador de Robos 9 | terminator=Exterminador 10 | terminator-dummy=Exterminador (Posicionavel) 11 | droid-counter=Modulo de atividade dos Droids 12 | loot-chest=Bau de saque do exercito 13 | droid-guard-station=Estação de Guarda dos Robos 14 | droid-flame=Robo lança chamas (não posicionado) 15 | droid-flame-dummy=Robo lança chamas (Posicionavel) 16 | droid-settings=Modulo de Configuração dos Robos 17 | defender-unit=Unidade de Defesa (Robo) (não posicionado) 18 | defender-unit-dummy= Unidade de Defesa (Robo) (Posicionavel) 19 | distractor-unit=Unidade de Robo de Distração (não posicionado) 20 | distractor-unit-dummy=Unidade de Robo de Distração (Posicionavel) 21 | destroyer-unit=Unidade de Robo Destroyer (não posicionado) 22 | destroyer-unit-dummy=Unidade de Robo Destroyer (Posicionavel) 23 | droid-pickup-tool=Ferramenta para pegar Droids e Robos 24 | droid-selection-tool=Ferramenta para Comandar os Droids e Robos 25 | 26 | [item-description] 27 | droid-smg=Robo de batalha (não posicionado), Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão. 28 | droid-smg-dummy=Peças de um Robo Especifico para ser montado em um montador . 29 | droid-rocket=Robo Bazuca (não posicionado), Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão para spawnar. 30 | droid-rocket-dummy=Peças de um Robo Especifico para ser montado em um montador . 31 | droid-rifle=Robo Mecanico (não posicionado), Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão para spawnar. 32 | droid-rifle-dummy=Peças de um Robo Especifico para ser montado em um montador . 33 | droid-assembling-machine=Montador de Robos, Selecione a receita do tipo de droid que você quer montar, a maquina irá montar o android e spawnar automaticamente no local. 34 | terminator=Exterminador, Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão para spawnar. 35 | terminator-dummy=Peças de um Robo Especifico para ser montado em um montador . 36 | droid-counter=Modulo de atividade dos Droids, conta quantos robos ou droides esão vivos no mapa, usa sinais para ajudar no controle de população dos androides 37 | loot-chest=Bau de saque do exercito, Coloque isso no chão para permitir os robos coletarem artefatos para você, eles teleportam os artefatos diretamente nesta caixa, lembre se de esvaziar esta caixa antes que ela fique lotada eventualmente 38 | droid-guard-station=Estação de Guarda dos Robos, Spawna um esquadrão que fica perto da estação e agem como se fossem guardas, a estação vai manter o tamanho da esquadrão a um certo numero, assim se um robo ou droid morrer outro podera assumir no lugar 39 | droid-flame=Robo lança chamas (não posicionado), Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão para spawnar. 40 | droid-flame-dummy= Peças de um Robo Especifico para ser montado em um montador . 41 | droid-settings=Modulo de Configuração dos Robos, Use este modulo para configurar seus robos, definindo limites de producao e afins, alguns valores não são permitidos, isso afeta todos os montadores 42 | defender-unit=Unidade de Defesa (Robo) (não posicionado), Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão para spawnar. 43 | defender-unit-dummy= Peças de um Robo Especifico para ser montado em um montador . 44 | distractor-unit=Unidade de Robo de Distração (não posicionado), Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão para spawnar. 45 | distractor-unit-dummy= Peças de um Robo Especifico para ser montado em um montador . 46 | destroyer-unit=Unidade de Robo Destroyer (não posicionado), Insira esse item a um Montador de Robos com a receita deste tipo especifico de robos para spawnar ou apenas coloque o no chão para spawnar. 47 | destroyer-unit-dummy= Peças de um Robo Especifico para ser montado em um montador . 48 | droid-pickup-tool=uma ferramenta que serve para voce pegar seus robos instantaneamente 49 | droid-selection-tool=clique com o botão esquerdo do mouse para selecionar (robos ficarão verde quando selecionados). shift + botão esquerdo para comandar eles a atacarem ou moverem para um local, de um clique normal para deselecionar o esquadrão 50 | 51 | 52 | [entity-name] 53 | droid-smg=Robo de batalha 54 | droid-rocket=Robo Bazuca 55 | droid-rifle=Robo Mecanico 56 | droid-assembling-machine=Montador de Robos 57 | terminator=Exterminador 58 | droid-counter=Modulo de atividade dos Droids 59 | loot-chest=Bau de saque do exercito 60 | droid-guard-station=Estação de Guarda dos Robos 61 | droid-flame=Robo lança chamas 62 | droid-settings=Modulo de Configuração dos Robos 63 | defender-unit=Unidade de Defesa (Robo) 64 | distractor-unit=Unidade de Robo de Distração 65 | destroyer-unit=Unidade de Robo Destroyer 66 | 67 | [entity-description] 68 | droid-smg=Robo de batalha, com uma submetralhadora embutido, unidade boa pro mid-game 69 | droid-rocket=Robo Bazuca, com uma bazuca embutidoque é muito boa a longas distancias, dá um grande e consideravel dano em area, porem o tiro é lento fazendo o necessitar de proteção 70 | droid-rifle=Robo Mecanico, com um rifle embutido, atira a longa distancia, porem o tiro é lento, serve perfeitamente para ser usado como ducha de canhão pois é barato 71 | droid-assembling-machine=Montador de Robos 72 | terminator=Exterminador, A unidade mais forte e apelona que tem, é o terror encarnado em uma maquina 73 | droid-counter=Modulo de atividade dos Droids 74 | loot-chest=Bau de saque do exercito 75 | droid-guard-station=Estação de Guarda dos Robos 76 | droid-flame=Robo lança chamas, armado com um lança chamas e resistente ao fogo, muito bom para limpar hordas de inimigos, tome cuidado em usar esse robo em um ambiente com muitas arvores pois elas podem pegar fogo 77 | droid-settings=Modulo de Configuração dos Robos 78 | defender-unit=Unidade de Defesa (Robo), Rapido e 'Voador'. com uma metralhadora embutido 79 | distractor-unit=Unidade de Robo de Distração, Rapido e 'Voador'. usa uma arma que solta raio laser, uma boa ideia para destrair os inimigos enquanto o resto dos robos fazem a festa 80 | destroyer-unit=Unidade de Robo Destroyer, Rapido e 'Voador'. usa um raio eletrico para atacar 81 | droid-pickup-tool=uma ferramenta que serve para voce pegar seus robos instantaneamente 82 | droid-selection-tool=clique com o botão esquerdo do mouse para selecionar (robos ficarão verde quando selecionados). shift + botão esquerdo para comandar eles a atacarem ou moverem para um local, de um clique normal para deselecionar o esquadrão 83 | 84 | [recipe-name] 85 | droid-smg=Robo de batalha (não posicionado) 86 | droid-rocket=Robo Bazuca (não posicionado) 87 | droid-smg-deploy=Robo de batalha (Posicionado) 88 | droid-rifle=Robo Mecanico (não posicionado) 89 | droid-rifle-deploy=Robo Mecanico (Posicionado) 90 | droid-rocket-deploy=Robo Bazuca (Posicionado) 91 | terminator=Exterminador (não posicionado) 92 | terminator-deploy=Exterminador (Posicionado) 93 | droid-counter=Modulo de atividade dos Droids 94 | loot-chest=Bau de saque do exercito 95 | droid-guard-station=Estação de Guarda dos Robos 96 | droid-flame=Robo lança chamas (não posicionado) 97 | droid-flame-deploy=Robo lança chamas (Posicionado) 98 | droid-settings=Modulo de Configuração dos Robos 99 | defender-unit=Unidade de Defesa (Robo) (não posicionado) 100 | distractor-unit=Unidade de Robo de Distração (não posicionado) 101 | destroyer-unit=Unidade de Robo Destroyer (não posicionado) 102 | defender-unit-deploy=Unidade de Defesa (Robo) (Posicionado) 103 | distractor-unit-deploy=Unidade de Robo de Distração (Posicionado) 104 | destroyer-unit-deploy=Unidade de Robo Destroyer (Posicionado) 105 | droid-pickup-tool=Ferramenta para pegar Droids e Robos 106 | droid-selection-tool=Ferramenta para Comandar os Droids 107 | 108 | [virtual-signal-name] 109 | signal-droid-alive-count=Contador de Droids Vivos 110 | signal-droid-rifle-count=contador de Robos Mecanicos 111 | signal-droid-smg-count=contador de Robos de batalha 112 | signal-droid-rocket-count=contador de Robos Bazuca 113 | signal-droid-terminator-count=contador de Exterminadores 114 | signal-droid-flame-count=Contador de Robos lança chamas 115 | signal-hunt-radius=Distancia do esquadrão de ataque 116 | signal-guard-size=Tamanho do esquadrão de guarda 117 | signal-retreat-size=Tamanho de Retirada 118 | signal-squad-size=Tamanho do esquadrão de ataque 119 | -------------------------------------------------------------------------------- /migrations/robotarmy_0.4.11.lua: -------------------------------------------------------------------------------- 1 | require("config.config") 2 | game.reload_script() 3 | 4 | 5 | --ensure all force-specific tables and researches are handled/created 6 | for i, force in pairs(game.forces) do 7 | force.reset_recipes() 8 | force.reset_technologies() 9 | 10 | --force all of the known recipes to be enabled if the appropriate research is already done. 11 | if force.technologies["military"].researched then 12 | force.recipes["droid-rifle"].enabled = true 13 | force.recipes["droid-rifle-deploy"].enabled = true 14 | if (GRAB_ARTIFACTS == 1) then 15 | force.recipes["loot-chest"].enabled = true 16 | end 17 | force.recipes["droid-guard-station"].enabled = true 18 | force.recipes["droid-assembling-machine"].enabled = true 19 | force.recipes["droid-pickup-tool"].enabled = true 20 | force.recipes["droid-selection-tool"].enabled = true 21 | force.recipes["droid-counter"].enabled = true 22 | force.recipes["droid-settings"].enabled = true 23 | end 24 | 25 | 26 | if force.technologies["military-2"].researched then 27 | force.recipes["droid-smg"].enabled = true 28 | force.recipes["droid-smg-deploy"].enabled = true 29 | force.recipes["droid-rocket"].enabled = true 30 | force.recipes["droid-rocket-deploy"].enabled = true 31 | force.recipes["droid-flame"].enabled = true 32 | force.recipes["droid-flame-deploy"].enabled = true 33 | end 34 | 35 | if force.technologies["military-3"].researched then 36 | force.recipes["terminator"].enabled = true 37 | force.recipes["terminator-deploy"].enabled = true 38 | end 39 | 40 | if force.technologies["defender"].researched then 41 | force.recipes["defender-unit"].enabled = true 42 | force.recipes["defender-unit-deploy"].enabled = true 43 | end 44 | 45 | if force.technologies["distractor"].researched then 46 | force.recipes["distractor-unit"].enabled = true 47 | force.recipes["distractor-unit-deploy"].enabled = true 48 | end 49 | 50 | if force.technologies["destroyer"].researched then 51 | force.recipes["destroyer-unit"].enabled = true 52 | force.recipes["destroyer-unit-deploy"].enabled = true 53 | end 54 | 55 | end -------------------------------------------------------------------------------- /migrations/robotarmy_0.4.12.lua: -------------------------------------------------------------------------------- 1 | require("stdlib.game") 2 | require("prototypes.DroidUnitList") 3 | require("robolib.util") 4 | game.reload_script() 5 | 6 | if not storage.units then storage.units = {} end 7 | 8 | --prep list of names to check against for unit checking 9 | 10 | local names = {} 11 | for _, name in pairs(spawnable) do 12 | local entityName = convertToEntityNames(name) 13 | names[entityName] = 1 --put something in the names table at this key. 14 | end 15 | 16 | 17 | local numUnitsAdded = 0; 18 | 19 | 20 | Game.print_all(string.format("Robot army processing %d forces, in %d surfaces", #game.forces, #game.surfaces)) 21 | 22 | --ensure all force-specific tables and researches are handled/created 23 | for i, force_ in pairs(game.forces) do 24 | -- for each force 25 | for j, surface_ in pairs(game.surfaces) do 26 | 27 | -- get the list of units 28 | -- for each unit in the list, check the type and name is what we want, add to the list. 29 | local units = surface_.find_units({area = {{-500, -500}, {500, 500}}, force = force_.name, condition = "same"}) 30 | for _, unitFound in pairs(units) do 31 | if names[unitFound.name] then 32 | if unitFound.valid then 33 | if not storage.units[unitFound.unit_number] then 34 | 35 | storage.units[unitFound.unit_number] = unitFound -- reference to the LuaEntity with a lookup via the unit number. 36 | numUnitsAdded = numUnitsAdded + 1 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | 44 | Game.print_all(string.format("units added in robot army migration script: %d", numUnitsAdded)) 45 | Game.print_all("Note that any units away from starting area (+-500 tiles) should be re-deployed for improved AI behaviours.") -------------------------------------------------------------------------------- /migrations/robotarmy_0.4.16.lua: -------------------------------------------------------------------------------- 1 | require("config.config") 2 | game.reload_script() 3 | 4 | 5 | --ensure all force-specific tables and researches are handled/created 6 | for i, force in pairs(game.forces) do 7 | force.reset_recipes() 8 | force.reset_technologies() 9 | 10 | force.recipes["droid-rifle"].enabled = false 11 | force.recipes["droid-rifle-deploy"].enabled = false 12 | force.recipes["droid-guard-station"].enabled = false 13 | force.recipes["droid-assembling-machine"].enabled = false 14 | force.recipes["droid-pickup-tool"].enabled = false 15 | force.recipes["droid-selection-tool"].enabled = false 16 | force.recipes["droid-counter"].enabled = false 17 | force.recipes["droid-settings"].enabled = false 18 | force.recipes["droid-smg"].enabled = false 19 | force.recipes["droid-smg-deploy"].enabled = false 20 | force.recipes["droid-rocket"].enabled = false 21 | force.recipes["droid-rocket-deploy"].enabled = false 22 | force.recipes["droid-flame"].enabled = false 23 | force.recipes["droid-flame-deploy"].enabled = false 24 | force.recipes["terminator"].enabled = false 25 | force.recipes["terminator-deploy"].enabled = false 26 | force.recipes["defender-unit"].enabled = false 27 | force.recipes["defender-unit-deploy"].enabled = false 28 | force.recipes["distractor-unit"].enabled = false 29 | force.recipes["distractor-unit-deploy"].enabled = false 30 | force.recipes["destroyer-unit"].enabled = false 31 | force.recipes["destroyer-unit-deploy"].enabled = false 32 | if (GRAB_ARTIFACTS == 1) then 33 | force.recipes["loot-chest"].enabled = false 34 | end 35 | end -------------------------------------------------------------------------------- /prototypes/DroidUnitList.lua: -------------------------------------------------------------------------------- 1 | require("robolib.util") 2 | 3 | 4 | --which of our custom entities can be spawned by the Droid Assemblers? 5 | spawnable = {"droid-smg", "droid-rocket", "droid-rifle", "terminator", "droid-flame", "defender-unit", "distractor-unit", "destroyer-unit"} 6 | 7 | --which of our custom entities can join squads? not necessarily all droids will be using the squad mechanic 8 | squadCapable = {"droid-smg", "droid-rocket", "droid-rifle", "terminator", "droid-flame", "defender-unit", "distractor-unit", "destroyer-unit"} 9 | 10 | -- lets pre-adjust the strings now, for fast matching in run-time, and only when we get a match do we convert back to the spawnable entity names. 11 | for _, name in pairs(spawnable) do 12 | name = convertToMatchable(name) 13 | end 14 | for _, name in pairs(squadCapable) do 15 | name = convertToMatchable(name) 16 | end 17 | -- remember to call convertToEntityNames before referencing these entries in spawning or other related uses -------------------------------------------------------------------------------- /prototypes/building.lua: -------------------------------------------------------------------------------- 1 | local ICONPATH = "__robotarmy__/graphics/icons/" 2 | local BUILPATH = "__robotarmy__/graphics/entity/buildings/" 3 | 4 | require("config.config") 5 | 6 | local droidAssembler = { 7 | type = "assembling-machine", 8 | name = "droid-assembling-machine", 9 | icon_size = 64, 10 | is_deployer = true, 11 | icon = ICONPATH .. "droid-assembling-machine.png", 12 | flags = {"placeable-neutral", "placeable-player", "player-creation"}, 13 | minable = {hardness = 0.2, mining_time = 0.5, result = "droid-assembling-machine"}, 14 | max_health = 400, 15 | corpse = "big-remnants", 16 | dying_explosion = "medium-explosion", 17 | resistances = 18 | { 19 | { 20 | type = "fire", 21 | percent = 70 22 | }, 23 | { 24 | type = "acid", 25 | percent = 70 26 | } 27 | }, 28 | fluid_boxes = 29 | { 30 | { 31 | volume=100, 32 | production_type = "input", 33 | pipe_picture = assembler2pipepictures(), 34 | pipe_covers = pipecoverspictures(), 35 | base_area = 10, 36 | base_level = -1, 37 | pipe_connections = {{ 38 | flow_direction = "input", 39 | direction=0, 40 | position = {0, -1.2} 41 | }} 42 | }, 43 | { 44 | volume=100, 45 | production_type = "output", 46 | pipe_picture = assembler2pipepictures(), 47 | pipe_covers = pipecoverspictures(), 48 | base_area = 10, 49 | base_level = 1, 50 | pipe_connections = {{ 51 | flow_direction = "output", 52 | direction=8, 53 | position = {0, 1.2} 54 | }} 55 | }, 56 | }, 57 | fluid_boxes_off_when_no_fluid_recipe = false, 58 | collision_box = {{-1.2, -1.2}, {1.2, 1.2}}, 59 | selection_box = {{-1.5, -1.5}, {1.5, 1.5}}, 60 | graphics_set = { 61 | animation = 62 | { 63 | layers = 64 | { 65 | { 66 | filename = BUILPATH .. "droid-assembler.png", 67 | width = 156, 68 | height = 139, 69 | frame_count = 1, 70 | line_length = 1, 71 | hr_version = { 72 | filename = BUILPATH .. "hr-droid-assembler.png", 73 | width = 312, 74 | height = 278, 75 | frame_count = 1, 76 | line_length = 1, 77 | scale = 0.5, 78 | } 79 | }, 80 | { 81 | filename = BUILPATH .. "droid-assembler-shadow.png", 82 | width = 156, 83 | height = 139, 84 | frame_count = 1, 85 | line_length = 1, 86 | draw_as_shadow = true, 87 | hr_version = { 88 | filename = BUILPATH .. "hr-droid-assembler-shadow.png", 89 | width = 312, 90 | height = 278, 91 | frame_count = 1, 92 | line_length = 1, 93 | draw_as_shadow = true, 94 | scale = 0.5, 95 | } 96 | } 97 | } 98 | } 99 | }, 100 | open_sound = { filename = "__base__/sound/machine-open.ogg", volume = 0.85 }, 101 | close_sound = { filename = "__base__/sound/machine-close.ogg", volume = 0.75 }, 102 | vehicle_impact_sound = { filename = "__base__/sound/car-metal-impact.ogg", volume = 0.65 }, 103 | working_sound = 104 | { 105 | sound = 106 | { 107 | { 108 | filename = "__base__/sound/assembling-machine-t2-1.ogg", 109 | volume = 0.8 110 | }, 111 | { 112 | filename = "__base__/sound/assembling-machine-t2-2.ogg", 113 | volume = 0.8 114 | }, 115 | }, 116 | idle_sound = { filename = "__base__/sound/idle1.ogg", volume = 0.6 }, 117 | apparent_volume = 1.5, 118 | }, 119 | crafting_categories = {"droids"}, 120 | crafting_speed = 1.0, 121 | energy_source = 122 | { 123 | type = "electric", 124 | usage_priority = "secondary-input", 125 | emissions = 0.04 / 2.5 126 | }, 127 | energy_usage = "300kW", 128 | ingredient_count = 3, 129 | module_specification = 130 | { 131 | module_slots = 3 132 | }, 133 | allowed_effects = {"consumption", "speed", "pollution"} 134 | } 135 | 136 | local guardStation = { 137 | type = "assembling-machine", 138 | name = "droid-guard-station", 139 | icon_size = 64, 140 | is_deployer = true, 141 | icon = ICONPATH .. "droid-guard-station.png", 142 | flags = {"placeable-neutral", "placeable-player", "player-creation"}, 143 | minable = {hardness = 0.2, mining_time = 0.5, result = "droid-guard-station"}, 144 | max_health = 400, 145 | corpse = "big-remnants", 146 | dying_explosion = "medium-explosion", 147 | resistances = 148 | { 149 | { 150 | type = "fire", 151 | percent = 70 152 | }, 153 | { 154 | type = "acid", 155 | percent = 70 156 | } 157 | }, 158 | collision_box = {{-1.7, -1.7}, {1.7, 1.7}}, 159 | selection_box = {{-2, -2}, {2, 2}}, 160 | graphics_set = { 161 | animation = 162 | { 163 | layers = 164 | { 165 | { 166 | filename = BUILPATH .. "guard-station.png", 167 | width = 208, 168 | height = 228, 169 | frame_count = 1, 170 | line_length = 1, 171 | hr_version = { 172 | filename = BUILPATH .. "hr-guard-station.png", 173 | width = 416, 174 | height = 456, 175 | frame_count = 1, 176 | line_length = 1, 177 | scale = 0.5, 178 | } 179 | }, 180 | { 181 | filename = BUILPATH .. "guard-station-shadow.png", 182 | width = 208, 183 | height = 228, 184 | frame_count = 1, 185 | line_length = 1, 186 | draw_as_shadow = true, 187 | hr_version = { 188 | filename = BUILPATH .. "hr-guard-station-shadow.png", 189 | width = 416, 190 | height = 456, 191 | frame_count = 1, 192 | line_length = 1, 193 | draw_as_shadow = true, 194 | scale = 0.5, 195 | } 196 | } 197 | } 198 | } 199 | }, 200 | open_sound = { filename = "__base__/sound/machine-open.ogg", volume = 0.85 }, 201 | close_sound = { filename = "__base__/sound/machine-close.ogg", volume = 0.75 }, 202 | vehicle_impact_sound = { filename = "__base__/sound/car-metal-impact.ogg", volume = 0.65 }, 203 | working_sound = 204 | { 205 | sound = 206 | { 207 | { 208 | filename = "__base__/sound/assembling-machine-t2-1.ogg", 209 | volume = 0.8 210 | }, 211 | { 212 | filename = "__base__/sound/assembling-machine-t2-2.ogg", 213 | volume = 0.8 214 | }, 215 | }, 216 | idle_sound = { filename = "__base__/sound/idle1.ogg", volume = 0.6 }, 217 | apparent_volume = 1.5, 218 | }, 219 | crafting_categories = {"droids"}, 220 | crafting_speed = 1.0, 221 | energy_source = 222 | { 223 | type = "electric", 224 | usage_priority = "secondary-input", 225 | emissions = 0.04 / 2.5 226 | }, 227 | energy_usage = "300kW", 228 | ingredient_count = 1, 229 | module_specification = 230 | { 231 | module_slots = 0 232 | }, 233 | allowed_effects = {"consumption", "speed", "pollution"} 234 | } 235 | 236 | 237 | data:extend({droidAssembler,guardStation}) -------------------------------------------------------------------------------- /prototypes/corpses.lua: -------------------------------------------------------------------------------- 1 | data:extend({ 2 | { 3 | type = "corpse", 4 | name = "robot-corpse", 5 | icon_size = 64, 6 | icon = "__base__/graphics/icons/medium-biter-corpse.png", 7 | selectable_in_game = false, 8 | selection_box = {{-1, -1}, {1, 1}}, 9 | flags = {"placeable-neutral", "placeable-off-grid", "building-direction-8-way", "not-on-map"}, 10 | subgroup="corpses", 11 | order = "c[corpse]-a[biter]-b[medium]", 12 | dying_speed = 0.04, 13 | time_before_removed = 15 * 60 * 60, 14 | final_render_layer = "corpse", 15 | animation = 16 | { 17 | layers = 18 | { 19 | { 20 | filename = "__base__/graphics/entity/defender-robot/defender-robot.png", 21 | width = 1, 22 | height = 1, 23 | frame_count = 16, 24 | direction_count = 16, 25 | --shift = {scale * 0.546875, scale * 0.21875}, 26 | priority = "very-low", 27 | --scale = scale, 28 | } 29 | } 30 | } 31 | } 32 | 33 | }) 34 | -------------------------------------------------------------------------------- /prototypes/defender-unit.lua: -------------------------------------------------------------------------------- 1 | require("config.config") 2 | 3 | local defenderUnitAnim = 4 | { 5 | layers = 6 | { 7 | { 8 | filename = "__base__/graphics/entity/defender-robot/defender-robot.png", 9 | priority = "high", 10 | line_length = 16, 11 | width = 32, 12 | height = 33, 13 | frame_count = 1, 14 | animation_speed = 1, 15 | direction_count = 16, 16 | shift = util.by_pixel(0, 0.25), 17 | y = 33, 18 | hr_version = 19 | { 20 | filename = "__base__/graphics/entity/defender-robot/hr-defender-robot.png", 21 | priority = "high", 22 | line_length = 16, 23 | width = 56, 24 | height = 59, 25 | frame_count = 1, 26 | animation_speed = 1, 27 | direction_count = 16, 28 | shift = util.by_pixel(0, 0.25), 29 | y = 59, 30 | scale = 0.5 31 | } 32 | }, 33 | { 34 | filename = "__base__/graphics/entity/defender-robot/defender-robot-mask.png", 35 | priority = "high", 36 | line_length = 16, 37 | width = 18, 38 | height = 16, 39 | frame_count = 1, 40 | animation_speed = 1, 41 | direction_count = 16, 42 | shift = util.by_pixel(0, -4.75), 43 | apply_runtime_tint = true, 44 | y = 16, 45 | hr_version = 46 | { 47 | filename = "__base__/graphics/entity/defender-robot/hr-defender-robot-mask.png", 48 | priority = "high", 49 | line_length = 16, 50 | width = 28, 51 | height = 21, 52 | frame_count = 1, 53 | animation_speed = 1, 54 | direction_count = 16, 55 | shift = util.by_pixel(0, -4.75), 56 | apply_runtime_tint = true, 57 | y = 21, 58 | scale = 0.5 59 | } 60 | }, 61 | { 62 | filename = "__base__/graphics/entity/defender-robot/defender-robot-shadow.png", 63 | priority = "high", 64 | line_length = 16, 65 | width = 45, 66 | height = 26, 67 | frame_count = 1, 68 | animation_speed = 1, 69 | direction_count = 16, 70 | shift = util.by_pixel(25.5, 19), 71 | draw_as_shadow = true, 72 | hr_version = 73 | { 74 | filename = "__base__/graphics/entity/defender-robot/hr-defender-robot-shadow.png", 75 | priority = "high", 76 | line_length = 16, 77 | width = 88, 78 | height = 50, 79 | frame_count = 1, 80 | animation_speed = 1, 81 | direction_count = 16, 82 | shift = util.by_pixel(25.5, 19), 83 | scale = 0.5, 84 | draw_as_shadow = true 85 | } 86 | } 87 | } 88 | } 89 | 90 | data:extend({ 91 | { 92 | type = "unit", 93 | name = "defender-unit", 94 | icon_size = 64, 95 | icon = "__base__/graphics/icons/defender.png", 96 | flags = {"placeable-player", "player-creation", "placeable-off-grid"}, 97 | subgroup = "creatures", 98 | has_belt_immunity = true, 99 | max_health = 65 * HEALTH_SCALAR, 100 | minable = {hardness = 0.1, mining_time = 0.1, result = "defender-unit"}, 101 | alert_when_damaged = false, 102 | order = "b-b-a", 103 | resistances = 104 | { 105 | { 106 | type = "physical", 107 | decrease = 4, 108 | }, 109 | { 110 | type = "acid", 111 | decrease = 1, 112 | percent = 30 113 | }, 114 | }, 115 | healing_per_tick = 0, 116 | collision_box = nil, 117 | collision_mask = {layers={ghost = true}}, 118 | selection_box = {{-0.3, -0.3}, {0.3, 0.3}}, 119 | sticker_box = {{-0.1, -0.1}, {0.1, 0.1}}, 120 | distraction_cooldown = 300, 121 | ai_settings = 122 | { 123 | allow_destroy_when_commands_fail = false, 124 | do_separation = true 125 | }, 126 | attack_parameters = 127 | { 128 | type = "projectile", 129 | ammo_category = "bullet", 130 | cooldown = 20, 131 | projectile_center = {0, 1}, 132 | projectile_creation_distance = 0.6, 133 | range = 12, 134 | min_attack_distance = 8, 135 | sound = make_light_gunshot_sounds(), 136 | ammo_type = 137 | { 138 | category = "bullet", 139 | action = 140 | { 141 | type = "direct", 142 | action_delivery = 143 | { 144 | type = "instant", 145 | source_effects = 146 | { 147 | type = "create-explosion", 148 | entity_name = "explosion-gunshot-small" 149 | }, 150 | target_effects = 151 | { 152 | { 153 | type = "create-entity", 154 | entity_name = "explosion-hit" 155 | }, 156 | { 157 | type = "damage", 158 | damage = { amount = 5 * DAMAGE_SCALAR , type = "physical"} 159 | } 160 | } 161 | } 162 | } 163 | }, 164 | animation = defenderUnitAnim, 165 | }, 166 | vision_distance = 45, 167 | radar_range = 1, 168 | can_open_gates = true, 169 | movement_speed = 0.2, 170 | distance_per_frame = 0.15, 171 | -- in pu 172 | absorptions_to_join_attack={}, 173 | corpse = "robot-corpse", 174 | dying_explosion = "explosion", 175 | working_sound = 176 | { 177 | sound = 178 | { 179 | { filename = "__base__/sound/flying-robot-1.ogg", volume = 0.6 }, 180 | { filename = "__base__/sound/flying-robot-2.ogg", volume = 0.6 }, 181 | { filename = "__base__/sound/flying-robot-3.ogg", volume = 0.6 }, 182 | { filename = "__base__/sound/flying-robot-4.ogg", volume = 0.6 }, 183 | { filename = "__base__/sound/flying-robot-5.ogg", volume = 0.6 } 184 | }, 185 | max_sounds_per_type = 3, 186 | --audible_distance_modifier = 0.5, 187 | probability = 1 / (3 * 60) -- average pause between the sound is 3 seconds 188 | }, 189 | dying_sound = 190 | { 191 | { 192 | filename = "__base__/sound/fight/robot-explosion-1.ogg", 193 | volume = 0.5 194 | }, 195 | { 196 | filename = "__base__/sound/fight/robot-explosion-2.ogg", 197 | volume = 0.5 198 | } 199 | }, 200 | run_animation = defenderUnitAnim, 201 | }, 202 | 203 | }) 204 | 205 | -------------------------------------------------------------------------------- /prototypes/distractor-unit.lua: -------------------------------------------------------------------------------- 1 | require("config.config") 2 | 3 | local distractorUnitAnim = 4 | { 5 | layers = 6 | { 7 | { 8 | filename = "__base__/graphics/entity/distractor-robot/distractor-robot.png", 9 | priority = "high", 10 | line_length = 16, 11 | width = 38, 12 | height = 33, 13 | frame_count = 1, 14 | direction_count = 16, 15 | shift = util.by_pixel(0, -2.5), 16 | y = 33, 17 | hr_version = 18 | { 19 | filename = "__base__/graphics/entity/distractor-robot/hr-distractor-robot.png", 20 | priority = "high", 21 | line_length = 16, 22 | width = 72, 23 | height = 62, 24 | frame_count = 1, 25 | direction_count = 16, 26 | shift = util.by_pixel(0, -2.5), 27 | y = 62, 28 | scale = 0.5 29 | } 30 | }, 31 | { 32 | filename = "__base__/graphics/entity/distractor-robot/distractor-robot-mask.png", 33 | priority = "high", 34 | line_length = 16, 35 | width = 24, 36 | height = 21, 37 | frame_count = 1, 38 | direction_count = 16, 39 | shift = util.by_pixel(0, -6.25), 40 | apply_runtime_tint = true, 41 | y = 21, 42 | hr_version = 43 | { 44 | filename = "__base__/graphics/entity/distractor-robot/hr-distractor-robot-mask.png", 45 | priority = "high", 46 | line_length = 16, 47 | width = 42, 48 | height = 37, 49 | frame_count = 1, 50 | direction_count = 16, 51 | shift = util.by_pixel(0, -6.25), 52 | apply_runtime_tint = true, 53 | y = 37, 54 | scale = 0.5 55 | } 56 | }, 57 | { 58 | filename = "__base__/graphics/entity/distractor-robot/distractor-robot-shadow.png", 59 | priority = "high", 60 | line_length = 16, 61 | width = 49, 62 | height = 30, 63 | frame_count = 1, 64 | direction_count = 16, 65 | shift = util.by_pixel(32.5, 19), 66 | draw_as_shadow = true, 67 | hr_version = 68 | { 69 | filename = "__base__/graphics/entity/distractor-robot/hr-distractor-robot-shadow.png", 70 | priority = "high", 71 | line_length = 16, 72 | width = 96, 73 | height = 59, 74 | frame_count = 1, 75 | direction_count = 16, 76 | shift = util.by_pixel(32.5, 19.25), 77 | scale = 0.5, 78 | draw_as_shadow = true 79 | } 80 | } 81 | } 82 | } 83 | 84 | data:extend({ 85 | { 86 | type = "unit", 87 | name = "distractor-unit", 88 | icon_size = 64, 89 | icon = "__base__/graphics/icons/distractor.png", 90 | flags = {"placeable-player", "player-creation", "placeable-off-grid"}, 91 | subgroup = "creatures", 92 | has_belt_immunity = true, 93 | max_health = 85 * HEALTH_SCALAR, 94 | minable = {hardness = 0.1, mining_time = 0.1, result = "distractor-unit"}, 95 | alert_when_damaged = false, 96 | order = "b-b-b", 97 | resistances = 98 | { 99 | { 100 | type = "physical", 101 | decrease = 4, 102 | }, 103 | { 104 | type = "acid", 105 | decrease = 1, 106 | percent = 30 107 | }, 108 | }, 109 | healing_per_tick = 0, 110 | collision_box = {{0, 0}, {0, 0}}, 111 | selection_box = {{-0.3, -0.3}, {0.3, 0.3}}, 112 | sticker_box = {{-0.1, -0.1}, {0.1, 0.1}}, 113 | distraction_cooldown = 300, 114 | ai_settings = 115 | { 116 | allow_destroy_when_commands_fail = false, 117 | do_separation = true 118 | }, 119 | attack_parameters = 120 | { 121 | type = "projectile", 122 | ammo_category = "laser", 123 | cooldown = 20, 124 | damage_modifier = 0.7, 125 | projectile_center = {0, 0.6}, 126 | projectile_creation_distance = 0.6, 127 | range = 18, 128 | min_attack_distance = 12, 129 | sound = make_laser_sounds(), 130 | ammo_type = 131 | { 132 | category = "laser", 133 | action = 134 | { 135 | type = "direct", 136 | action_delivery = 137 | { 138 | type = "projectile", 139 | projectile = "laser", 140 | starting_speed = 1 141 | } 142 | } 143 | }, 144 | animation = distractorUnitAnim 145 | }, 146 | vision_distance = 45, 147 | radar_range = 1, 148 | can_open_gates = true, 149 | movement_speed = 0.2, 150 | distance_per_frame = 0.15, 151 | -- in pu 152 | absorptions_to_join_attack={}, 153 | corpse = "robot-corpse", 154 | dying_explosion = "explosion", 155 | working_sound = 156 | { 157 | sound = 158 | { 159 | { filename = "__base__/sound/flying-robot-1.ogg", volume = 0.6 }, 160 | { filename = "__base__/sound/flying-robot-2.ogg", volume = 0.6 }, 161 | { filename = "__base__/sound/flying-robot-3.ogg", volume = 0.6 }, 162 | { filename = "__base__/sound/flying-robot-4.ogg", volume = 0.6 }, 163 | { filename = "__base__/sound/flying-robot-5.ogg", volume = 0.6 } 164 | }, 165 | max_sounds_per_type = 3, 166 | --audible_distance_modifier = 0.5, 167 | probability = 1 / (3 * 60) -- average pause between the sound is 3 seconds 168 | }, 169 | dying_sound = 170 | { 171 | { 172 | filename = "__base__/sound/fight/robot-explosion-1.ogg", 173 | volume = 0.5 174 | }, 175 | { 176 | filename = "__base__/sound/fight/robot-explosion-2.ogg", 177 | volume = 0.5 178 | } 179 | }, 180 | run_animation = distractorUnitAnim, 181 | }, 182 | }) -------------------------------------------------------------------------------- /prototypes/item.lua: -------------------------------------------------------------------------------- 1 | local ICONPATH = "__robotarmy__/graphics/icons/" 2 | 3 | local a = 4 | { 5 | { 6 | type = "item-subgroup", 7 | name = "robotarmy-droids", 8 | group = "combat", 9 | order = "c-2", 10 | }, 11 | 12 | { 13 | type = "recipe-category", 14 | name = "droids", 15 | }, 16 | 17 | { 18 | type = "item", 19 | name = "droid-rifle", 20 | icon = ICONPATH .. "droid_rifle_undep.png", 21 | icon_size = 64, 22 | flags = {}, 23 | place_result = "droid-rifle", 24 | stack_size = 25, 25 | subgroup = "robotarmy-droids", 26 | order = "z[droid]-b", 27 | }, 28 | { 29 | type = "item", 30 | name = "droid-smg", 31 | icon = ICONPATH .. "droid_smg_undep.png", 32 | icon_size = 64, 33 | flags = {}, 34 | place_result = "droid-smg", 35 | stack_size = 25, 36 | subgroup = "robotarmy-droids", 37 | order = "z[droid]-c", 38 | }, 39 | { 40 | type = "item", 41 | name = "droid-rocket", 42 | icon = ICONPATH .. "droid_rocket_undep.png", 43 | icon_size = 64, 44 | flags = {}, 45 | place_result = "droid-rocket", 46 | stack_size = 25, 47 | subgroup = "robotarmy-droids", 48 | order = "z[droid]-d", 49 | }, 50 | { 51 | type = "item", 52 | name = "droid-flame", 53 | icon = ICONPATH .. "droid_flame_undep.png", 54 | icon_size = 64, 55 | flags = {}, 56 | place_result = "droid-flame", 57 | stack_size = 25, 58 | subgroup = "robotarmy-droids", 59 | order = "z[droid]-e", 60 | }, 61 | { 62 | type = "item", 63 | name = "terminator", 64 | icon = ICONPATH .. "terminator_undep.png", 65 | icon_size = 64, 66 | flags = {}, 67 | place_result = "terminator", 68 | stack_size = 25, 69 | subgroup = "robotarmy-droids", 70 | order = "z[droid]-f", 71 | }, 72 | { 73 | type = "item", 74 | name = "defender-unit", 75 | icon = ICONPATH .. "defender_unit_undep.png", 76 | icon_size = 64, 77 | flags = {}, 78 | place_result = "defender-unit", 79 | stack_size = 25, 80 | subgroup = "robotarmy-droids", 81 | order = "z[droid]-g", 82 | }, 83 | { 84 | type = "item", 85 | name = "distractor-unit", 86 | icon = ICONPATH .. "distractor_unit_undep.png", 87 | icon_size = 64, 88 | flags = {}, 89 | place_result = "distractor-unit", 90 | stack_size = 25, 91 | subgroup = "robotarmy-droids", 92 | order = "z[droid]-h", 93 | }, 94 | { 95 | type = "item", 96 | name = "destroyer-unit", 97 | icon = ICONPATH .. "destroyer_unit_undep.png", 98 | icon_size = 64, 99 | flags = {}, 100 | place_result = "destroyer-unit", 101 | stack_size = 25, 102 | subgroup = "robotarmy-droids", 103 | order = "z[droid]-i", 104 | }, 105 | 106 | 107 | 108 | 109 | { 110 | type = "item", 111 | name = "terminator-dummy", 112 | icon_size = 64, 113 | icon = ICONPATH .. "terminator.png", 114 | flags = {}, 115 | order = "z-z", 116 | subgroup = "capsule", 117 | place_result = "", 118 | stack_size = 1, 119 | }, 120 | { 121 | type = "item", 122 | name = "droid-rocket-dummy", 123 | icon_size = 64, 124 | icon = ICONPATH .. "droid_rocket.png", 125 | flags = {}, 126 | order = "z-z", 127 | subgroup = "capsule", 128 | place_result = "", 129 | stack_size = 1, 130 | }, 131 | { 132 | type = "item", 133 | name = "droid-rifle-dummy", 134 | icon_size = 64, 135 | icon = ICONPATH .. "droid_rifle.png", 136 | flags = {}, 137 | order = "z-z", 138 | subgroup = "capsule", 139 | place_result = "", 140 | stack_size = 1, 141 | }, 142 | { 143 | type = "item", 144 | name = "droid-flame-dummy", 145 | icon_size = 64, 146 | icon = ICONPATH .. "droid_flame.png", 147 | flags = {}, 148 | order = "z-z", 149 | subgroup = "capsule", 150 | place_result = "", 151 | stack_size = 1, 152 | }, 153 | { 154 | type = "item", 155 | name = "droid-smg-dummy", 156 | icon_size = 64, 157 | icon = ICONPATH .. "droid_smg.png", 158 | flags = {}, 159 | order = "z-z", 160 | subgroup = "capsule", 161 | place_result = "", 162 | stack_size = 1, 163 | }, 164 | { 165 | type = "item", 166 | name = "defender-unit-dummy", 167 | icon_size = 64, 168 | icon = ICONPATH .. "defender.png", 169 | flags = {}, 170 | order = "z-z", 171 | subgroup = "capsule", 172 | place_result = "", 173 | stack_size = 1, 174 | }, 175 | { 176 | type = "item", 177 | name = "distractor-unit-dummy", 178 | icon_size = 64, 179 | icon = ICONPATH .. "distractor.png", 180 | flags = {}, 181 | order = "z-z", 182 | subgroup = "capsule", 183 | place_result = "", 184 | stack_size = 1, 185 | }, 186 | { 187 | type = "item", 188 | name = "destroyer-unit-dummy", 189 | icon_size = 64, 190 | icon = ICONPATH .. "destroyer.png", 191 | flags = {}, 192 | order = "z-z", 193 | subgroup = "capsule", 194 | place_result = "", 195 | stack_size = 1, 196 | }, 197 | 198 | { 199 | type = "item", 200 | name = "droid-assembling-machine", 201 | icon_size = 64, 202 | icon = ICONPATH .. "droid-assembling-machine.png", 203 | flags = {}, 204 | subgroup = "production-machine", 205 | order = "b[droid-assembling-machine]", 206 | place_result = "droid-assembling-machine", 207 | stack_size = 50, 208 | }, 209 | { 210 | type = "item", 211 | name = "droid-guard-station", 212 | icon_size = 64, 213 | icon = ICONPATH .. "droid-guard-station.png", 214 | flags = {}, 215 | subgroup = "production-machine", 216 | order = "b[droid-guard-station]", 217 | place_result = "droid-guard-station", 218 | stack_size = 50, 219 | }, 220 | { 221 | type = "item", 222 | name = "droid-counter", 223 | icon_size = 64, 224 | icon = ICONPATH .. "droid-counter.png", 225 | flags = {}, 226 | subgroup = "circuit-network", 227 | place_result="droid-counter", 228 | order = "b[combinators]-e[droid-counter]", 229 | stack_size = 50, 230 | }, 231 | { 232 | type = "item", 233 | name = "droid-settings", 234 | icon_size = 64, 235 | icon = ICONPATH .. "droid-settings.png", 236 | flags = {}, 237 | subgroup = "circuit-network", 238 | place_result="droid-settings", 239 | order = "b[combinators]-e[droid-settings]", 240 | stack_size = 50, 241 | }, 242 | { 243 | type = "item", 244 | name = "loot-chest", 245 | icon_size = 64, 246 | icon = ICONPATH .. "loot-chest.png", 247 | flags = {}, 248 | subgroup = "storage", 249 | place_result="loot-chest", 250 | order = "a[items]-c[loot-chest]", 251 | stack_size = 50, 252 | }, 253 | { 254 | -- This allows loading the selection-tool type item when mods are removed 255 | type = "selection-tool", 256 | name = "droid-selection-tool", 257 | icon_size = 64, 258 | icon = ICONPATH .. "selection_tool.png", 259 | flags = {}, 260 | subgroup = "tool", 261 | order = "d[droid-control]-s[selection]", 262 | stack_size = 1, 263 | stackable = false, 264 | select={ 265 | border_color = { r = 0, g = 1, b = 0 }, 266 | mode = {"same-force"}, 267 | cursor_box_type = "not-allowed", 268 | }, 269 | alt_select={ 270 | border_color = { r = 0, g = 1, b = 0 }, 271 | mode = {"same-force"}, 272 | cursor_box_type = "not-allowed" 273 | }, 274 | }, 275 | -- This allows loading the selection-tool type item when mods are removed 276 | { 277 | type = "selection-tool", 278 | name = "droid-pickup-tool", 279 | icon_size = 64, 280 | icon = ICONPATH .. "pickup_tool.png", 281 | flags = {}, 282 | subgroup = "tool", 283 | order = "d[droid-control]-p[pickup]", 284 | stack_size = 1, 285 | stackable = false, 286 | select={ 287 | border_color = { r = 1, g = 0, b = 0 }, 288 | mode = {"blueprint"}, 289 | cursor_box_type = "not-allowed", 290 | }, 291 | alt_select={ 292 | border_color = { r = 1, g = 0, b = 0 }, 293 | mode = {"blueprint"}, 294 | cursor_box_type = "not-allowed" 295 | }, 296 | 297 | }, 298 | } 299 | for _, v in pairs(a) do 300 | data:extend({v}) 301 | end -------------------------------------------------------------------------------- /prototypes/projectiles/projectiles.lua: -------------------------------------------------------------------------------- 1 | --require the config file for damage scalar 2 | require("config.config") 3 | 4 | local dual_laser = 5 | { 6 | type = "projectile", 7 | name = "laser-dual", 8 | flags = {"not-on-map"}, 9 | acceleration = 0.01, 10 | action = 11 | { 12 | type = "direct", 13 | action_delivery = 14 | { 15 | type = "instant", 16 | target_effects = 17 | { 18 | { 19 | type = "create-entity", 20 | entity_name = "laser-bubble" 21 | }, 22 | { 23 | type = "damage", 24 | damage = {amount = 30*DAMAGE_SCALAR, type = "laser"} 25 | } 26 | } 27 | } 28 | }, 29 | light = {intensity = 0.7, size = 20}, 30 | animation = 31 | { 32 | filename = "__robotarmy__/graphics/entity/laser/dual-laser.png", 33 | tint = {r=1.0, g=0.0, b=0.0}, 34 | frame_count = 1, 35 | width = 24, 36 | height = 33, 37 | priority = "high", 38 | blend_mode = "additive" 39 | }, 40 | speed = 0.3 41 | } 42 | 43 | local droid_rocket = 44 | { 45 | type = "projectile", 46 | name = "droid-explosive-rocket", 47 | flags = {"not-on-map"}, 48 | acceleration = 0.035, 49 | action = 50 | { 51 | type = "direct", 52 | action_delivery = 53 | { 54 | type = "instant", 55 | target_effects = 56 | { 57 | { 58 | type = "create-entity", 59 | entity_name = "explosion" 60 | }, 61 | { 62 | type = "damage", 63 | damage = {amount = 275*DAMAGE_SCALAR, type = "explosion"} 64 | }, 65 | { 66 | type = "create-entity", 67 | entity_name = "small-scorchmark", 68 | check_buildability = true 69 | } 70 | } 71 | } 72 | }, 73 | light = {intensity = 0.5, size = 4}, 74 | animation = require("__base__.prototypes.entity.rocket-projectile-pictures").animation({0.5, 1.0, 0.05}), 75 | shadow = require("__base__.prototypes.entity.rocket-projectile-pictures").shadow, 76 | smoke = require("__base__.prototypes.entity.rocket-projectile-pictures").smoke 77 | } 78 | 79 | 80 | data:extend({dual_laser, droid_rocket}) -------------------------------------------------------------------------------- /prototypes/signals.lua: -------------------------------------------------------------------------------- 1 | local ICONPATH = "__robotarmy__/graphics/icons/" 2 | 3 | data:extend( 4 | { 5 | { 6 | type = "virtual-signal", 7 | name = "signal-droid-alive-count", 8 | icon_size = 64, 9 | icon = ICONPATH .. "signal_droid_counter.png", 10 | subgroup = "virtual-signal-number", 11 | order = "r[droid]-[01]" 12 | }, 13 | { 14 | type = "virtual-signal", 15 | name = "signal-droid-rifle-count", 16 | icon_size = 64, 17 | icon = ICONPATH .. "signal_droid_rifle_count.png", 18 | subgroup = "virtual-signal-number", 19 | order = "r[droid]-[02]" 20 | }, 21 | { 22 | type = "virtual-signal", 23 | name = "signal-droid-smg-count", 24 | icon_size = 64, 25 | icon = ICONPATH .. "signal_droid_smg_count.png", 26 | subgroup = "virtual-signal-number", 27 | order = "r[droid]-[03]" 28 | }, 29 | { 30 | type = "virtual-signal", 31 | name = "signal-droid-rocket-count", 32 | icon_size = 64, 33 | icon = ICONPATH .. "signal_droid_rocket_count.png", 34 | subgroup = "virtual-signal-number", 35 | order = "r[droid]-[04]" 36 | }, 37 | { 38 | type = "virtual-signal", 39 | name = "signal-droid-flame-count", 40 | icon_size = 64, 41 | icon = ICONPATH .. "signal_droid_flame_count.png", 42 | subgroup = "virtual-signal-number", 43 | order = "r[droid]-[05]" 44 | }, 45 | { 46 | type = "virtual-signal", 47 | name = "signal-droid-terminator-count", 48 | icon_size = 64, 49 | icon = ICONPATH .. "signal_droid_terminator_count.png", 50 | subgroup = "virtual-signal-number", 51 | order = "r[droid]-[06]" 52 | }, 53 | 54 | --settings signals 55 | { 56 | type = "virtual-signal", 57 | name = "signal-hunt-radius", 58 | icon_size = 64, 59 | icon = ICONPATH .. "signal_hunt_radius.png", 60 | subgroup = "virtual-signal-number", 61 | order = "r[droid]-[07]" 62 | }, 63 | { 64 | type = "virtual-signal", 65 | name = "signal-guard-size", 66 | icon_size = 64, 67 | icon = ICONPATH .. "signal_guard_size.png", 68 | subgroup = "virtual-signal-number", 69 | order = "r[droid]-[08]" 70 | }, 71 | { 72 | type = "virtual-signal", 73 | name = "signal-retreat-size", 74 | icon_size = 64, 75 | icon = ICONPATH .. "signal_retreat_size.png", 76 | subgroup = "virtual-signal-number", 77 | order = "r[droid]-[09]" 78 | }, 79 | { 80 | type = "virtual-signal", 81 | name = "signal-squad-size", 82 | icon_size = 64, 83 | icon = ICONPATH .. "signal_squad_size.png", 84 | subgroup = "virtual-signal-number", 85 | order = "r[droid]-[10]" 86 | }, 87 | 88 | }) -------------------------------------------------------------------------------- /prototypes/technology.lua: -------------------------------------------------------------------------------- 1 | local TECHPATH = "__robotarmy__/graphics/technology/" 2 | 3 | data:extend({ 4 | 5 | --[[ 6 | { 7 | type = "technology", 8 | name = "robotarmy-tech-", 9 | icon_size = 256, icon_mipmaps = 4, 10 | icon = TECHPATH .. ".png", 11 | prerequisites = {""}, 12 | unit = 13 | { 14 | count = 100, 15 | ingredients = 16 | { 17 | {"automation-science-pack", 1}, 18 | {"logistic-science-pack", 1}, 19 | {"chemical-science-pack", 1}, 20 | {"military-science-pack", 1}, 21 | {"utility-science-pack", 1} 22 | }, 23 | time = 30 24 | }, 25 | effects = 26 | { 27 | { 28 | type = "unlock-recipe", 29 | recipe = "" 30 | }, 31 | }, 32 | order = "c-c-c" 33 | }, 34 | ]] 35 | 36 | ---------------------------------------------------------- 37 | -----------------------BASE TECH-------------------------- 38 | ---------------------------------------------------------- 39 | 40 | { 41 | type = "technology", 42 | name = "robotarmy-tech-robotics", 43 | icon_size = 256, icon_mipmaps = 4, 44 | icon = TECHPATH .. "robotarmy-tech-robotics.png", 45 | prerequisites = {"military"}, 46 | unit = 47 | { 48 | count = 20, 49 | ingredients = {{"automation-science-pack", 1}}, 50 | time = 30 51 | }, 52 | effects = 53 | { 54 | { 55 | type = "unlock-recipe", 56 | recipe = "droid-guard-station" 57 | }, 58 | { 59 | type = "unlock-recipe", 60 | recipe = "droid-assembling-machine" 61 | }, 62 | { 63 | type = "unlock-recipe", 64 | recipe = "droid-pickup-tool" 65 | }, 66 | { 67 | type = "unlock-recipe", 68 | recipe = "droid-selection-tool" 69 | }, 70 | { 71 | type = "unlock-recipe", 72 | recipe = "droid-counter" 73 | }, 74 | { 75 | type = "unlock-recipe", 76 | recipe = "droid-settings" 77 | }, 78 | }, 79 | order = "c-c-a" 80 | }, 81 | 82 | ---------------------------------------------------------- 83 | ----------------------DROID TECH-------------------------- 84 | ---------------------------------------------------------- 85 | 86 | { 87 | type = "technology", 88 | name = "robotarmy-tech-droid-rifle", 89 | icon_size = 256, icon_mipmaps = 4, 90 | icon = TECHPATH .. "robotarmy-tech-droid-rifle.png", 91 | prerequisites = {"robotarmy-tech-robotics"}, 92 | unit = 93 | { 94 | count = 40, 95 | ingredients = {{"automation-science-pack", 1}}, 96 | time = 30 97 | }, 98 | effects = 99 | { 100 | { 101 | type = "unlock-recipe", 102 | recipe = "droid-rifle" 103 | }, 104 | { 105 | type = "unlock-recipe", 106 | recipe = "droid-rifle-deploy" 107 | }, 108 | }, 109 | order = "c-c-b" 110 | }, 111 | { 112 | type = "technology", 113 | name = "robotarmy-tech-droid-smg", 114 | icon_size = 256, icon_mipmaps = 4, 115 | icon = TECHPATH .. "robotarmy-tech-droid-smg.png", 116 | prerequisites = {"military-2", "robotarmy-tech-robotics"}, 117 | unit = 118 | { 119 | count = 40, 120 | ingredients = 121 | { 122 | {"automation-science-pack", 1}, 123 | {"logistic-science-pack", 1} 124 | }, 125 | time = 30 126 | }, 127 | effects = 128 | { 129 | { 130 | type = "unlock-recipe", 131 | recipe = "droid-smg" 132 | }, 133 | { 134 | type = "unlock-recipe", 135 | recipe = "droid-smg-deploy" 136 | }, 137 | }, 138 | order = "c-c-c" 139 | }, 140 | { 141 | type = "technology", 142 | name = "robotarmy-tech-droid-rocket", 143 | icon_size = 256, icon_mipmaps = 4, 144 | icon = TECHPATH .. "robotarmy-tech-droid-rocket.png", 145 | prerequisites = {"military-2", "robotarmy-tech-robotics"}, 146 | unit = 147 | { 148 | count = 60, 149 | ingredients = 150 | { 151 | {"automation-science-pack", 1}, 152 | {"logistic-science-pack", 1} 153 | }, 154 | time = 30 155 | }, 156 | effects = 157 | { 158 | { 159 | type = "unlock-recipe", 160 | recipe = "droid-rocket" 161 | }, 162 | { 163 | type = "unlock-recipe", 164 | recipe = "droid-rocket-deploy" 165 | }, 166 | }, 167 | order = "c-c-d" 168 | }, 169 | { 170 | type = "technology", 171 | name = "robotarmy-tech-droid-flame", 172 | icon_size = 256, icon_mipmaps = 4, 173 | icon = TECHPATH .. "robotarmy-tech-droid-flame.png", 174 | prerequisites = {"flamethrower", "robotarmy-tech-robotics"}, 175 | unit = 176 | { 177 | count = 80, 178 | ingredients = 179 | { 180 | {"automation-science-pack", 1}, 181 | {"logistic-science-pack", 1}, 182 | {"military-science-pack", 1} 183 | }, 184 | time = 30 185 | }, 186 | effects = 187 | { 188 | { 189 | type = "unlock-recipe", 190 | recipe = "droid-flame" 191 | }, 192 | { 193 | type = "unlock-recipe", 194 | recipe = "droid-flame-deploy" 195 | }, 196 | }, 197 | order = "c-c-e" 198 | }, 199 | 200 | { 201 | type = "technology", 202 | name = "robotarmy-tech-droid-terminator", 203 | icon_size = 256, icon_mipmaps = 4, 204 | icon = TECHPATH .. "robotarmy-tech-droid-terminator.png", 205 | prerequisites = {"military-3", "robotarmy-tech-robotics"}, 206 | unit = 207 | { 208 | count = 150, 209 | ingredients = 210 | { 211 | {"automation-science-pack", 1}, 212 | {"logistic-science-pack", 1}, 213 | {"chemical-science-pack", 1}, 214 | {"military-science-pack", 1} 215 | }, 216 | time = 45 217 | }, 218 | effects = 219 | { 220 | { 221 | type = "unlock-recipe", 222 | recipe = "terminator" 223 | }, 224 | { 225 | type = "unlock-recipe", 226 | recipe = "terminator-deploy" 227 | }, 228 | }, 229 | order = "c-c-f" 230 | }, 231 | 232 | ---------------------------------------------------------- 233 | ------------------FLYING DROID TECH----------------------- 234 | ---------------------------------------------------------- 235 | 236 | { 237 | type = "technology", 238 | name = "robotarmy-tech-defender-unit", 239 | icon_size = 256, icon_mipmaps = 4, 240 | icon = TECHPATH .. "robotarmy-tech-defender-unit.png", 241 | prerequisites = {"robotarmy-tech-robotics", "defender"}, 242 | unit = 243 | { 244 | count = 150, 245 | ingredients = 246 | { 247 | {"automation-science-pack", 1}, 248 | {"logistic-science-pack", 1}, 249 | {"military-science-pack", 1} 250 | }, 251 | time = 45 252 | }, 253 | effects = 254 | { 255 | { 256 | type = "unlock-recipe", 257 | recipe = "defender-unit" 258 | }, 259 | { 260 | type = "unlock-recipe", 261 | recipe = "defender-unit-deploy" 262 | }, 263 | }, 264 | order = "c-c-g" 265 | }, 266 | { 267 | type = "technology", 268 | name = "robotarmy-tech-distractor-unit", 269 | icon_size = 256, icon_mipmaps = 4, 270 | icon = TECHPATH .. "robotarmy-tech-distractor-unit.png", 271 | prerequisites = {"robotarmy-tech-robotics", "distractor"}, 272 | unit = 273 | { 274 | count = 250, 275 | ingredients = 276 | { 277 | {"automation-science-pack", 1}, 278 | {"logistic-science-pack", 1}, 279 | {"chemical-science-pack", 1}, 280 | {"military-science-pack", 1} 281 | }, 282 | time = 45 283 | }, 284 | effects = 285 | { 286 | { 287 | type = "unlock-recipe", 288 | recipe = "distractor-unit" 289 | }, 290 | { 291 | type = "unlock-recipe", 292 | recipe = "distractor-unit-deploy" 293 | }, 294 | }, 295 | order = "c-c-h" 296 | }, 297 | { 298 | type = "technology", 299 | name = "robotarmy-tech-destroyer-unit", 300 | icon_size = 256, icon_mipmaps = 4, 301 | icon = TECHPATH .. "robotarmy-tech-destroyer-unit.png", 302 | prerequisites = {"robotarmy-tech-robotics", "destroyer"}, 303 | unit = 304 | { 305 | count = 400, 306 | ingredients = 307 | { 308 | {"automation-science-pack", 1}, 309 | {"logistic-science-pack", 1}, 310 | {"chemical-science-pack", 1}, 311 | {"military-science-pack", 1}, 312 | {"utility-science-pack", 1} 313 | }, 314 | time = 45 315 | }, 316 | effects = 317 | { 318 | { 319 | type = "unlock-recipe", 320 | recipe = "destroyer-unit" 321 | }, 322 | { 323 | type = "unlock-recipe", 324 | recipe = "destroyer-unit-deploy" 325 | }, 326 | }, 327 | order = "c-c-i" 328 | }, 329 | 330 | }) 331 | 332 | 333 | if (GRAB_ARTIFACTS == 1) then 334 | table.insert(data.raw.technology["robotarmy-tech-robotics"].effects, {type = "unlock-recipe", recipe = "loot-chest"} ) 335 | end -------------------------------------------------------------------------------- /robolib/30log.lua: -------------------------------------------------------------------------------- 1 | local assert, pairs, type, tostring, setmetatable = assert, pairs, type, tostring, setmetatable 2 | local baseMt, _instances, _classes, _class = {}, setmetatable({},{__mode='k'}), setmetatable({},{__mode='k'}) 3 | local function assert_class(class, method) assert(_classes[class], ('Wrong method call. Expected class:%s.'):format(method)) end 4 | local function deep_copy(t, dest, aType) t = t or {}; local r = dest or {} 5 | for k,v in pairs(t) do 6 | if aType~=nil and type(v)==aType then r[k] = type(v) == 'table' and deep_copy(v) or v 7 | elseif aType==nil then r[k] = type(v) == 'table' and k~= '__index' and deep_copy(v) or v 8 | end 9 | end; return r 10 | end 11 | local function instantiate(self,...) 12 | assert_class(self, 'new(...) or class(...)'); local instance = {class = self}; _instances[instance] = tostring(instance); deep_copy(self, instance, 'table'); instance.mixins = nil; setmetatable(instance,self) 13 | if self.init then if type(self.init) == 'table' then deep_copy(self.init, instance) else self.init(instance, ...) end; end; return instance 14 | end 15 | local function extend(self, name, extra_params) 16 | assert_class(self, 'extend(...)'); local heir = {}; _classes[heir] = tostring(heir); deep_copy(extra_params, deep_copy(self, heir)); 17 | heir.name, heir.__index, heir.super = extra_params and extra_params.name or name, heir, self; return setmetatable(heir,self) 18 | end 19 | baseMt = { __call = function (self,...) return self:new(...) end, __tostring = function(self,...) 20 | if _instances[self] then return ("instance of '%s' (%s)"):format(rawget(self.class,'name') or '?', _instances[self]) end 21 | return _classes[self] and ("class '%s' (%s)"):format(rawget(self,'name') or '?',_classes[self]) or self 22 | end}; _classes[baseMt] = tostring(baseMt); setmetatable(baseMt, {__tostring = baseMt.__tostring}) 23 | local class = {isClass = function(class, ofsuper) local isclass = not not _classes[class]; if ofsuper then return isclass and (class.super == ofsuper) end; return isclass end, isInstance = function(instance, ofclass) 24 | local isinstance = not not _instances[instance]; if ofclass then return isinstance and (instance.class == ofclass) end; return isinstance end}; _class = function(name, attr) 25 | local c = deep_copy(attr); c.mixins=setmetatable({},{__mode='k'}); _classes[c] = tostring(c); c.name, c.__tostring, c.__call = name or c.name, baseMt.__tostring, baseMt.__call 26 | c.include = function(self,mixin) assert_class(self, 'include(mixin)'); self.mixins[mixin] = true; return deep_copy(mixin, self, 'function') end 27 | c.new, c.extend, c.__index, c.includes = instantiate, extend, c, function(self,mixin) assert_class(self,'includes(mixin)') return not not (self.mixins[mixin] or (self.super and self.super:includes(mixin))) end 28 | c.extends = function(self, class) assert_class(self, 'extends(class)') local super = self; repeat super = super.super until (super == class or super == nil); return class and (super == class) end 29 | return setmetatable(c, baseMt) end; class._DESCRIPTION = '30 lines library for object orientation in Lua'; class._VERSION = '30log v1.0.0'; class._URL = 'http://github.com/Yonaba/30log'; class._LICENSE = 'MIT LICENSE ' 30 | return setmetatable(class,{__call = function(_,...) return _class(...) end }) -------------------------------------------------------------------------------- /robolib/SquadControl.lua: -------------------------------------------------------------------------------- 1 | require("config.config") 2 | require("util") 3 | require("robolib.Squad") 4 | require("robolib.util") 5 | require("robolib.retreat") 6 | require("stdlib/log/logger") 7 | require("stdlib/game") 8 | require("robolib.targeting") 9 | 10 | function updateSquad(squad) 11 | if squadStillExists(squad) then -- if not, that means this squad has been deleted 12 | --LOGGER.log(string.format( "AI for squadref %d in tick table index %d is being executed now...", squadref, tickProcessIndex) ) 13 | --CHECK IF SQUAD IS A GUARD SQUAD, AND CHOOSE WHICH AI FUNCTION TO CALL 14 | if squad.command.type == commands.guard then 15 | executeGuardAI(squad) 16 | elseif not squad.rally then 17 | if not script.active_mods["Unit_Control"] then 18 | executeBattleAI(squad) 19 | end 20 | else 21 | squad = validateSquadIntegrity(squad) 22 | end 23 | 24 | --revealChunksBySquad(squad) -- NOW HANDLED BY UNIT PROTOTYPES WITH radar_range = 1. 25 | if (GRAB_ARTIFACTS == 1) then 26 | grabArtifactsBySquad(squad) --disabled as of 0.15 where alien artifacts are no longer dropped! 27 | end 28 | end 29 | end 30 | 31 | function executeBattleAI(squad) 32 | local attacking = isAttacking(squad) 33 | if attacking then 34 | -- squad.command.state_changed_since_last_command = true 35 | if not squad.command.state_changed_since_last_command then 36 | squad.command.state_changed_since_last_command = true 37 | LOGGER.log(string.format("Squad %d is attacking - once it no longer is attacking, it will need an order.", squad.squadID)) 38 | end 39 | end 40 | if (not attacking) and (squad.command.state_changed_since_last_command or squadOrderNeedsRefresh(squad)) then 41 | squad, issue_command = validateSquadIntegrity(squad) 42 | if not squad or not issue_command then return end 43 | LOGGER.log(string.format("Squad %d Needs orders of some kind (last: %d) at tick %d", 44 | squad.squadID, squad.command.type, game.tick)) 45 | if shouldHunt(squad) then 46 | orderSquadToHunt(squad) 47 | else 48 | orderSquadToRetreat(squad) 49 | end 50 | end 51 | end 52 | 53 | function orderSquadToHunt(squad) 54 | local target = chooseTarget(squad) 55 | if target then 56 | orderSquadToAttack(squad, target.position) 57 | else 58 | orderSquadToWander(squad, squad.unitGroup.position) 59 | end 60 | end 61 | 62 | function executeGuardAI(squad) 63 | local surface = getSquadSurface(squad) 64 | 65 | if not surface then 66 | --LOGGER.log(string.format("ERROR: Surface for squad ID %d is missing or can't be determined! guardAIUpdate", squad.squadID)) 67 | return 68 | end 69 | 70 | if squad.command.tick + SANITY_CHECK_PERIOD_SECONDS * 60 < game.tick then 71 | -- validate, but then wait a while before validating again 72 | squad.command.tick = game.tick 73 | validateSquadIntegrity(squad) 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /robolib/onload.lua: -------------------------------------------------------------------------------- 1 | require("robolib.Squad") 2 | require("stdlib/log/logger") 3 | require("stdlib/game") 4 | 5 | 6 | function bootstrap_migration_on_first_tick(event) 7 | 8 | 9 | -- substitute the 'normal' tick handler, and run it manually this time 10 | script.on_event(defines.events.on_tick, handleTick) 11 | handleTick(event) 12 | end 13 | 14 | 15 | function global_ensureTablesExist() 16 | if not storage.updateTable then storage.updateTable = {} end 17 | if not storage.Squads then storage.Squads = {} end 18 | if not storage.AssemblerRetreatTables then storage.AssemblerRetreatTables = {} end 19 | if not storage.AssemblerNearestEnemies then storage.AssemblerNearestEnemies = {} end 20 | if not storage.DroidAssemblers then storage.DroidAssemblers = {} end 21 | if not storage.droidGuardStations then storage.droidGuardStations = {} end 22 | end 23 | 24 | 25 | function migrateForce(fkey, force) 26 | LOGGER.log(string.format("Migrating force %s...", force.name)) 27 | global_fixupTickTablesForForceName(force.name) 28 | for skey, squad in pairs(storage.Squads[force.name]) do 29 | migrateSquad(skey, squad) 30 | end 31 | 32 | migrateDroidAssemblersTo_0_2_4(force) 33 | end 34 | 35 | 36 | function migrateSquad(skey, squad) 37 | migrateSquadTo_0_2_4(squad) 38 | end 39 | 40 | 41 | function migrateSquadTo_0_2_4(squad) 42 | squad.members.size = nil -- removing old 'size' table entry 43 | 44 | if not squad.command then 45 | -- this shouldn't happen, but just in case... 46 | squad.command = makeCommandTable(commands.hunt) 47 | elseif type(squad.command) ~= "table" then 48 | -- this is the normal migration path 49 | LOGGER.log(string.format("Migrating squad %d command table", squad.squadID)) 50 | local pos = getSquadPos(squad) 51 | squad.command = makeCommandTable(squad.command, pos, pos) 52 | end 53 | 54 | squad.unitGroupFailures = squad.unitGroupFailures or 0 55 | squad.numMembers = squad.numMembers or 0 56 | 57 | if not squad.mostRecentUnitGroupRemovalTick then 58 | squad.mostRecentUnitGroupRemovalTick = {} 59 | for key, soldier in pairs(squad.members) do 60 | squad.mostRecentUnitGroupRemovalTick[key] = 1 61 | end 62 | end 63 | 64 | if not squad.nextUnitGroupFailureResponse then 65 | squad.nextUnitGroupFailureResponse = ugFailureResponses.repeatOrder 66 | squad.unitGroupFailureTick = 0 67 | end 68 | 69 | -- put squad in tick tables if not there already 70 | local found = false 71 | for tkey, tickTable in pairs(storage.updateTable[squad.force.name]) do 72 | if table.contains(tickTable, squad.squadID) then 73 | found = true 74 | break 75 | end 76 | end 77 | if not found then 78 | squad = validateSquadIntegrity(squad) 79 | if squad then 80 | LOGGER.log(string.format("Inserting squad %d of size %d into tickTables", squad.squadID, squad.numMembers)) 81 | table.insert(storage.updateTable[squad.force.name][squad.squadID % 60 + 1], squad.squadID) 82 | end 83 | end 84 | end 85 | 86 | 87 | function migrateDroidAssemblersTo_0_2_4(force) 88 | -- index these by their globally unique "unit_number" instead. 89 | local forceAssemblers = storage.DroidAssemblers[force.name] 90 | local assemblerNearestEnemies = storage.AssemblerNearestEnemies[force.name] 91 | for dkey, assembler in pairs(forceAssemblers) do 92 | if not assembler or not assembler.valid then 93 | forceAssemblers[dkey] = nil 94 | elseif dkey ~= assembler.unit_number then 95 | forceAssemblers[dkey] = nil 96 | LOGGER.log(string.format("Moving assembler to new index %d from %d", assembler.unit_number, dkey)) 97 | forceAssemblers[assembler.unit_number] = assembler 98 | end 99 | if assembler and assembler.valid and not assemblerNearestEnemies[assembler.unit_number] then 100 | assemblerNearestEnemies[assembler.unit_number] = {lastChecked = 0, 101 | enemy = nil, 102 | distance = 0} 103 | end 104 | end 105 | end 106 | 107 | 108 | function global_fixupTickTablesForForceName(force_name) 109 | if not storage.updateTable[force_name] then storage.updateTable[force_name] = {} end 110 | 111 | --check if the table has the 1st tick in it. if not, then go through and fill the table 112 | if not storage.updateTable[force_name][1] then 113 | fillTableWithTickEntries(storage.updateTable[force_name]) -- make sure it has got the 1-60 tick entries initialized 114 | end 115 | 116 | if not storage.DroidAssemblers[force_name] then 117 | storage.DroidAssemblers[force_name] = {} 118 | end 119 | if not storage.AssemblerRetreatTables[force_name] then 120 | storage.AssemblerRetreatTables[force_name] = {} 121 | end 122 | if not storage.AssemblerNearestEnemies[force_name] then 123 | storage.AssemblerNearestEnemies[force_name] = {} 124 | end 125 | if not storage.droidGuardStations[force_name] then 126 | storage.droidGuardStations[force_name] = {} 127 | end 128 | if not storage.droidCounters[force_name] then 129 | storage.droidCounters[force_name] = {} 130 | end 131 | if not storage.lootChests[force_name] then 132 | storage.lootChests[force_name] = {} 133 | end 134 | if not storage.uniqueSquadId[force_name] then 135 | storage.uniqueSquadId[force_name] = {} 136 | end 137 | 138 | if not storage.updateTable[force_name] or not storage.Squads[force_name] then 139 | -- this is a more-or-less fatal error 140 | -- in the condition of a new game, and you haven't placed a squad yet, can have issues with player force not having the squad table init yet. 141 | storage.Squads[force_name] = {} 142 | return false 143 | 144 | --disabling below code for now 145 | --[[Game.print_all("Update Table or squad table for force is missing! Can't run update functions - force name:") 146 | Game.print_all(force_name) 147 | if not storage.updateTable[force_name] then 148 | Game.print_all("missing update table...") 149 | end 150 | 151 | if not storage.Squads[force_name] then 152 | Game.print_all("missing squad table...") 153 | end 154 | return false]]-- 155 | end 156 | return true 157 | end 158 | -------------------------------------------------------------------------------- /robolib/retreat.lua: -------------------------------------------------------------------------------- 1 | require("config.config") 2 | require("util") 3 | require("robolib.Squad") 4 | require("robolib.util") 5 | require("robolib.targeting") 6 | require("stdlib/log/logger") 7 | require("stdlib/game") 8 | 9 | -- this efficient AND reliable retreat logic has gotten so large that it really deserves its own file. 10 | 11 | 12 | function orderSquadToRetreat(squad) 13 | local assembler, distance = findClosestAssemblerToPosition( 14 | storage.DroidAssemblers[squad.force.name], squad.unitGroup.position) 15 | local currentPos = squad.unitGroup.position 16 | 17 | 18 | if not squad.unitGroup then return end; 19 | if not squad.unitGroup.valid then return end; 20 | 21 | if assembler then 22 | local retreatPos = getDroidSpawnLocation(assembler) 23 | if not retreatPos then 24 | LOGGER.log("ERROR: Failed to find a droid spawn position near the found assembler!") 25 | orderSquadToWander(squad, currentPos) 26 | return -- we failed to retreat, but eventually we'll get ordered to do so again.. 27 | end 28 | distance = util.distance(retreatPos, currentPos) 29 | 30 | squad.command.type = commands.assemble -- takes us out of hunt mode until we're big enough 31 | 32 | if distance > AT_ASSEMBLER_RANGE then 33 | LOGGER.log(string.format("Ordering squad %d of size %d near (%d,%d) to retreat %d m to assembler %d near (%d,%d)", 34 | squad.squadID, squad.numMembers, currentPos.x, currentPos.y, 35 | distance, assembler.unit_number, retreatPos.x, retreatPos.y)) 36 | -- issue an actual retreat command 37 | squad.command.dest = retreatPos 38 | squad.command.distance = distance 39 | if squad.unitGroup.valid then 40 | debugSquadOrder(squad, "RETREAT TO ASSEMBLER", retreatPos) 41 | setGoThenWanderCompoundCommand(squad.unitGroup, retreatPos) 42 | squad.unitGroup.start_moving() 43 | end 44 | end 45 | 46 | addSquadToRetreatTables(squad, assembler) 47 | squad.command.tick = game.tick 48 | squad.command.pos = currentPos 49 | squad.command.state_changed_since_last_command = false 50 | else 51 | local msg = string.format("There are no droid assemblers to which squad %d can retreat. You should build at least one.", 52 | squad.squadID) 53 | LOGGER.log(msg) 54 | Game.print_force(squad.force, msg) 55 | orderSquadToWander(squad, currentPos) 56 | end 57 | end 58 | 59 | 60 | function addSquadToRetreatTables(squad, targetAssembler) 61 | -- look for nearby assemblers and add squad to those tables as well 62 | retreatAssemblers = findNearbyAssemblers(storage.DroidAssemblers[squad.force.name], 63 | targetAssembler.position, AT_ASSEMBLER_RANGE) 64 | local forceRetreatTables = storage.AssemblerRetreatTables[squad.force.name] 65 | for i=1, #retreatAssemblers do -- cool/faster iteration syntax for list-like table 66 | local assembler = retreatAssemblers[i] 67 | LOGGER.log(string.format("Inserting squad %d into retreat table of assembler %d at (%d,%d)", 68 | squad.squadID, assembler.unit_number, assembler.position.x, assembler.position.y)) 69 | if not forceRetreatTables[assembler.unit_number] then 70 | forceRetreatTables[assembler.unit_number] = {} 71 | end 72 | forceRetreatTables[assembler.unit_number][squad.squadID] = squad 73 | end 74 | end 75 | 76 | 77 | -- returns false if the assembler is invalid, or no valid squads were in the squad list. 78 | -- false therefore indicates that this assembler may be removed from its parent list. 79 | function checkRetreatAssemblerForMergeableSquads(assembler, squads) 80 | local mergeableSquad = nil 81 | local mergeableSquadDist = nil 82 | -- LOGGER.log(string.format("Trying to merge retreating squads near assembler at (%d,%d)", 83 | -- assembler.position.x, assembler.position.y)) 84 | local squadCount = 0 85 | local squadCloseCount = 0 86 | for squadID, squad in pairs(squads) do 87 | if not squadStillExists(squad) then 88 | squads[squadID] = nil 89 | elseif shouldHunt(squad) then 90 | squads[squadID] = nil 91 | squad.state_changed_since_last_command = true 92 | else 93 | local squadPos = getSquadPos(squad) 94 | squadCount = squadCount + 1 95 | local dist = util.distance(squadPos, assembler.position) 96 | if dist < MERGE_RANGE then 97 | -- then this squad is close enough to be merged, if it is still valid 98 | if validateSquadIntegrity(squad) then 99 | squadCloseCount = squadCloseCount + 1 100 | -- LOGGER.log(string.format( 101 | -- " ---- Squad %d sz %d is near its retreating assembler.", 102 | -- squad.squadID, squad.numMembers)) 103 | if mergeableSquad then -- we already found a mergeable squad nearby 104 | -- are we close enough to this other squad to merge? 105 | if util.distance(mergeableSquad.unitGroup.position, 106 | squadPos) < MERGE_RANGE 107 | then 108 | mergeableSquad = mergeSquads(squad, mergeableSquad) 109 | if shouldHunt(mergeableSquad) then 110 | squads[squadID] = nil 111 | mergeableSquad = nil 112 | mergeableSquadDist = nil 113 | end 114 | elseif mergeableSquadDist > dist then 115 | -- our previous 'mergeableSquad' is a little too far away, 116 | -- and may have retreated to a different, nearby assembler. 117 | -- Since this merge was not okay, we will instead choose the current 118 | -- squad, which is closer to the assembler, as the 'mergeableSquad' for 119 | -- any future merge attempts. 120 | mergeableSquad = squad 121 | mergeableSquadDist = dist 122 | end 123 | else -- set first 'mergeable squad' 124 | mergeableSquad = squad 125 | mergeableSquadDist = dist 126 | end 127 | else 128 | -- squad is invalid. don't check again 129 | squads[squadID] = nil 130 | end 131 | end 132 | end 133 | end 134 | 135 | -- LOGGER.log(string.format("Assembler merge examined %d squads, of which %d were near this assembler at (%d,%d)", 136 | -- squadCount, squadCloseCount, assembler.position.x, assembler.position.y)) 137 | if squadCount == 0 then return false else return true end 138 | end 139 | -------------------------------------------------------------------------------- /robolib/statistics.lua: -------------------------------------------------------------------------------- 1 | --session statistics 2 | ses_statistics = { 3 | sessionStartTick = 0, 4 | squadsCreated = 0, 5 | squadsDeleted = 0, 6 | createUnitGroupSuccesses = 0, 7 | createUnitGroupFailures = 0, 8 | soldierUnitGroupDepartures = 0, 9 | soldierUnitGroupReadds = 0, 10 | soldierSquadDepartures = 0, 11 | teleports = 0, 12 | failedTeleports = 0, 13 | disbands = 0, 14 | merges = 0, 15 | wanders = 0, 16 | enemySearches = 0, 17 | commandsIssued = 0, 18 | unitGroupFailures = 0, 19 | } 20 | 21 | 22 | function log_session_statistics(force) 23 | local seconds = (game.tick - ses_statistics.sessionStartTick) / 60 + 1 24 | local minutes = (game.tick - ses_statistics.sessionStartTick) / 3600 + 1 25 | local totals_msg = string.format( 26 | "TOTALS: sc %d, sd %d, cugf %d, sugd %d, sugr %d, ssd %d, t %d, ft %d, d %d, m %d, w %d, ES %d, cmds %d, F %d, secs %d", 27 | ses_statistics.squadsCreated, 28 | ses_statistics.squadsDeleted, 29 | ses_statistics.createUnitGroupFailures, 30 | ses_statistics.soldierUnitGroupDepartures, 31 | ses_statistics.soldierUnitGroupReadds, 32 | ses_statistics.soldierSquadDepartures, 33 | ses_statistics.teleports, 34 | ses_statistics.failedTeleports, 35 | ses_statistics.disbands, 36 | ses_statistics.merges, 37 | ses_statistics.wanders, 38 | ses_statistics.enemySearches, 39 | ses_statistics.commandsIssued, 40 | ses_statistics.unitGroupFailures, 41 | seconds) 42 | local rates_msg = string.format( 43 | "RATE/M: sc %d, sd %d, cugf %d, sugd %d, sugr %d, ssd %d, t %d, ft %d, d %d, m %d, w %d, ES %d, cmds %d, F %d, mins %d", 44 | ses_statistics.squadsCreated/minutes, 45 | ses_statistics.squadsDeleted/minutes, 46 | ses_statistics.createUnitGroupFailures/minutes, 47 | ses_statistics.soldierUnitGroupDepartures/minutes, 48 | ses_statistics.soldierUnitGroupReadds/minutes, 49 | ses_statistics.soldierSquadDepartures/minutes, 50 | ses_statistics.teleports/minutes, 51 | ses_statistics.failedTeleports/minutes, 52 | ses_statistics.disbands/minutes, 53 | ses_statistics.merges/minutes, 54 | ses_statistics.wanders/minutes, 55 | ses_statistics.enemySearches/minutes, 56 | ses_statistics.commandsIssued/minutes, 57 | ses_statistics.unitGroupFailures/minutes, 58 | minutes) 59 | LOGGER.log(totals_msg) 60 | LOGGER.log(rates_msg) 61 | if DEBUG then 62 | Game.print_force(force, totals_msg) 63 | Game.print_force(force, rates_msg) 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /robolib/targeting.lua: -------------------------------------------------------------------------------- 1 | -- This file is for logic dealing with how 'Hunter' squads choose their targets 2 | require("robolib.statistics") 3 | 4 | targetingTypes = { 5 | searchAndDestroy = 1, 6 | defendAssembler = 2, 7 | hybridKeepRadiusClear = 3, 8 | } 9 | 10 | 11 | function chooseTarget(squad) 12 | local targetPos = nil 13 | -- get targeting type 14 | local targetingType = getTargetingType(squad) 15 | if targetingType == targetingTypes.defendAssembler then 16 | targetPos = findDefendAssemblerTarget(squad) 17 | elseif targetingType == targetingTypes.hybridKeepRadiusClear then 18 | targetPos = findHybridKeepRadiusClearTarget(squad) 19 | else -- search and destroy 20 | LOGGER.log(string.format("Squad %d Searching for nearest target", squad.squadID)) 21 | targetPos = findNearestTarget(squad) 22 | end 23 | return targetPos 24 | end 25 | 26 | 27 | function getTargetingType(squad) 28 | -- for now, it's a global 29 | return GLOBAL_TARGETING_TYPE 30 | -- later, it might be per-squad 31 | end 32 | 33 | 34 | -- look for the enemy closest to the nearest assembler, if any 35 | function findDefendAssemblerTarget(squad) 36 | local huntRadius = getForceHuntRange(squad.force) 37 | local assembler, distance = findClosestAssemblerToPosition( 38 | storage.DroidAssemblers[squad.force.name], 39 | squad.unitGroup.position) 40 | local huntOrigin = squad.unitGroup.position 41 | if assembler then 42 | huntOrigin = assembler.position 43 | end 44 | ses_statistics.enemySearches = ses_statistics.enemySearches + 1 45 | return squad.unitGroup.surface.find_nearest_enemy({position=huntOrigin, 46 | max_distance=huntRadius, 47 | force=squad.force}) 48 | end 49 | 50 | 51 | -- basically, find the nearest enemy and go get it 52 | function findNearestTarget(squad) 53 | local huntRadius = getForceHuntRange(squad.force) 54 | local nearestEnemy = squad.unitGroup.surface.find_nearest_enemy( 55 | {position=squad.unitGroup.position, 56 | max_distance=huntRadius, 57 | force=squad.force}) 58 | ses_statistics.enemySearches = ses_statistics.enemySearches + 1 59 | return nearestEnemy 60 | end 61 | 62 | 63 | HYBRID_BACKTRACK_FACTOR = 2 64 | 65 | -- find the nearest enemy, unless there's a nearby assembler being threatened! 66 | function findHybridKeepRadiusClearTarget(squad) 67 | local msg = string.format("Looking for hybrid target for squad %d", squad.squadID) 68 | LOGGER.log(msg) 69 | local assembler, distance = findClosestAssemblerToPosition( 70 | storage.DroidAssemblers[squad.force.name], 71 | squad.unitGroup.position) 72 | if assembler then 73 | local ANEtable = storage.AssemblerNearestEnemies[squad.force.name][assembler.unit_number] 74 | 75 | if not ANEtable.enemy then 76 | findAssemblerNearestEnemies(assembler, ANEtable) -- we have never found an enemy.. so lets find the first one. 77 | elseif ANEtable.enemy and not ANEtable.enemy.valid then 78 | findAssemblerNearestEnemies(assembler, ANEtable) 79 | end 80 | local nearestEnemyToAssembler = ANEtable.enemy 81 | if (nearestEnemyToAssembler and nearestEnemyToAssembler.valid) then 82 | local keepRadiusClear = getAssemblerKeepRadiusClear(assembler) 83 | if ANEtable.distance < keepRadiusClear then 84 | msg = string.format("Squad %d targeting assembler target at (%d,%d) to keep radius %d clear", 85 | squad.squadID, nearestEnemyToAssembler.position.x, 86 | nearestEnemyToAssembler.position.y, keepRadiusClear) 87 | LOGGER.log(msg) 88 | return nearestEnemyToAssembler 89 | else 90 | local nearestEnemyToSquad = findNearestTarget(squad) 91 | if not nearestEnemyToSquad then 92 | msg = string.format("Squad %d targeting assembler target at (%d,%d) because " .. 93 | "there is no nearby alternative target.", squad.squadID, 94 | nearestEnemyToAssembler.position.x, 95 | nearestEnemyToAssembler.position.y) 96 | LOGGER.log(msg) 97 | return nearestEnemyToAssembler 98 | else 99 | -- if the squad's nearest enemy is more than HYBRID_BACKTRACK_FACTOR times as far 100 | -- from the assembler as its target, we should backtrack to attack the assembler's target. 101 | local squadEnemyDist = util.distance(nearestEnemyToSquad.position, assembler.position) 102 | if ANEtable.distance * HYBRID_BACKTRACK_FACTOR < squadEnemyDist then 103 | msg = string.format("Squad %d targeting assembler target at (%d,%d), distances %d and %d", 104 | squad.squadID, nearestEnemyToAssembler.position.x, 105 | nearestEnemyToAssembler.position.y, 106 | squadEnemyDist, ANEtable.distance) 107 | LOGGER.log(msg) 108 | return nearestEnemyToAssembler 109 | else 110 | msg = string.format("Squad %d targeting nearest squad target at (%d,%d)", 111 | squad.squadID, nearestEnemyToSquad.position.x, 112 | nearestEnemyToSquad.position.y) 113 | LOGGER.log(msg) 114 | return nearestEnemyToSquad 115 | end 116 | end 117 | end 118 | end 119 | end 120 | return findNearestTarget(squad) 121 | end 122 | 123 | 124 | function findAssemblerNearestEnemies(assembler, ANEtable) 125 | local msg = string.format("Looking for nearest enemy for assembler %d...", 126 | assembler.unit_number) 127 | LOGGER.log(msg) 128 | ANEtable.enemy = assembler.surface.find_nearest_enemy( 129 | {position=assembler.position, 130 | max_distance=getAssemblerKeepRadiusClear(assembler), 131 | force=assembler.force}) 132 | ses_statistics.enemySearches = ses_statistics.enemySearches + 1 133 | ANEtable.lastChecked = game.tick 134 | if ANEtable.enemy then 135 | ANEtable.distance = util.distance(assembler.position, ANEtable.enemy.position) 136 | msg = string.format("Found enemy %d at (%d,%d), distance %d", 137 | ANEtable.enemy.unit_number, 138 | ANEtable.enemy.position.x, ANEtable.enemy.position.y, 139 | ANEtable.distance) 140 | LOGGER.log(msg) 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /robolib/util.lua: -------------------------------------------------------------------------------- 1 | require("stdlib/game") 2 | require("stdlib/log/logger") 3 | 4 | 5 | --examines the given table, and if it finds a nil element it will remove it 6 | --from the table. 7 | function removeNilsFromTable(tableIN) 8 | for i, element in pairs(tableIN) do 9 | if element == nil then 10 | table.remove(tableIN, i) 11 | end 12 | end 13 | end 14 | 15 | 16 | function table.contains(table, element) 17 | for _, value in pairs(table) do 18 | if value == element then 19 | return true 20 | end 21 | end 22 | return false 23 | end 24 | 25 | 26 | function table.countNonNil(table) 27 | 28 | if not table then return 0 end 29 | 30 | local count = 0 31 | for _, element in pairs(table) do 32 | if element then 33 | count = count + 1 34 | end 35 | end 36 | return count 37 | end 38 | 39 | 40 | function table.countValidElements(inputTable) 41 | local count = 0 42 | inputTable["size"] = nil -- we're no longer keeping size in the members table. 43 | for key, element in pairs(inputTable) do 44 | if element and element.valid then 45 | count = count + 1 46 | end 47 | end 48 | return count 49 | end 50 | 51 | 52 | -- from http://lua-users.org/wiki/CopyTable 53 | function shallowcopy(orig) 54 | local orig_type = type(orig) 55 | local copy 56 | if orig_type == 'table' then 57 | copy = {} 58 | for orig_key, orig_value in pairs(orig) do 59 | copy[orig_key] = orig_value 60 | end 61 | else -- number, string, boolean, etc 62 | copy = orig 63 | end 64 | return copy 65 | end 66 | 67 | 68 | function setContains(set, key) 69 | return set[key] ~= nil 70 | end 71 | 72 | 73 | function stripchars(str, chrs) 74 | local s = str:gsub("["..chrs.."]", '') 75 | return s 76 | end 77 | 78 | 79 | function repchars(str, chrs, newchrs) 80 | local s = string.gsub(str, chrs, newchrs ) 81 | return s 82 | end 83 | 84 | 85 | function convertToMatchable(str) 86 | local s = repchars(str, "%-", "0") 87 | return s 88 | end 89 | 90 | 91 | function convertToEntityNames(str) 92 | local s = repchars(str, "0", "-") 93 | return s 94 | 95 | end 96 | 97 | 98 | --any new global tables we need to add, just add them in here and it will be easier to maintain. not used yet. 99 | -- function checkGlobalTableInitStates() 100 | -- storage.Squads = storage.Squads or {} 101 | -- storage.uniqueSquadId = storage.uniqueSquadId or {} 102 | -- storage.DroidAssemblers = storage.DroidAssemblers or {} 103 | -- storage.droidCounters = storage.droidCounters or {} 104 | -- storage.lootChests = storage.lootChests or {} 105 | -- storage.droidGuardStations = storage.droidGuardStations or {} 106 | -- local forceList = game.forces 107 | -- for _, force in pairs(forceList) do 108 | -- storage.droidGuardStations[force.name] = storage.droidGuardStations[force.name] or {} 109 | -- storage.Squads[force.name] = storage.Squads[force.name] or {} 110 | -- storage.DroidAssemblers[force.name] = storage.DroidAssemblers[force.name] or {} 111 | -- storage.droidCounters[force.name] = storage.droidCounters[force.name] or {} 112 | -- storage.lootChests[force.name] = storage.lootChests[force.name] or {} 113 | -- storage.uniqueSquadId[force.name] = storage.uniqueSquadId[force.name] or 1 114 | -- end 115 | -- end 116 | 117 | 118 | -- TODO: improve this to use zoom level and stuff 119 | function global_canAnyPlayersSeeThisEntity(entity) 120 | for key, player in pairs(game.players) do 121 | if player and util.distance(player.position, entity.position) < PLAYER_VIEW_RADIUS then 122 | return true 123 | end 124 | end 125 | return false 126 | end 127 | 128 | 129 | --waypointList is a list of LuaPositions, 130 | function getClosestEntity(position, entityList) 131 | local dist = 0 132 | local distance = 999999 133 | local closestEntity = nil 134 | for index, entity in pairs(entityList) do 135 | --distance between the droid assembler and the squad 136 | if entity and entity.valid then 137 | dist = util.distance(entity.position, position) 138 | if dist <= distance then 139 | closestEntity = entity 140 | distance = dist 141 | end 142 | end 143 | end 144 | return closestEntity 145 | end 146 | 147 | 148 | --input is a sub-table of storage.updateTable, and is the table for a particular force 149 | function fillTableWithTickEntries(inputTable) 150 | -- Game.print_all("filling update tick table") 151 | for i = 1, 60 do 152 | inputTable[i] = {} 153 | end 154 | end 155 | 156 | 157 | function global_getLeastFullTickTable(force) 158 | if not storage.updateTable then storage.updateTable = {} end 159 | if not storage.updateTable[force.name] then storage.updateTable[force.name] = {} end 160 | 161 | --check if the table has the 1st tick in it. if not, then go through and fill the table 162 | if not storage.updateTable[force.name][1] then 163 | fillTableWithTickEntries(storage.updateTable[force.name]) -- make sure it has got the 0-59 tick entries initialized 164 | end 165 | 166 | local forceTickTable = storage.updateTable[force.name] 167 | --the forceTickTable consists of 60 entries, from 1 to 60 representing 60 ticks. 168 | --each entry is indexed by tick. 169 | --each entry is another table, which consists of any number of squad references 170 | 171 | local lowestCount = false 172 | local lowestIndex = 0 173 | for tick, tickTable in pairs(forceTickTable) do 174 | local count = table.countNonNil(tickTable) 175 | if not lowestCount or count < lowestCount then 176 | lowestCount = count 177 | lowestIndex = tick 178 | end 179 | end 180 | return lowestIndex 181 | end 182 | -------------------------------------------------------------------------------- /stdlib/area/chunk.lua: -------------------------------------------------------------------------------- 1 | --- Chunk module 2 | ---

A chunk represents a 32x32 area of a surface in factorio.

3 | -- @module Chunk 4 | 5 | require 'stdlib/core' 6 | require 'stdlib/area/position' 7 | 8 | Chunk = {} 9 | MAX_UINT = 4294967296 10 | 11 | --- Calculates the chunk coordinates for the tile position given 12 | -- @param position to calculate the chunk for 13 | -- @return the chunk position as a table 14 | -- @usage 15 | ----local chunk_x = Chunk.from_position(pos).x 16 | function Chunk.from_position(position) 17 | position = Position.to_table(position) 18 | local x = math.floor(position.x) 19 | local y = math.floor(position.y) 20 | local chunk_x = bit32.arshift(x, 5) 21 | if x < 0 then 22 | chunk_x = chunk_x - MAX_UINT 23 | end 24 | local chunk_y = bit32.arshift(y, 5) 25 | if y < 0 then 26 | chunk_y = chunk_y - MAX_UINT 27 | end 28 | return {x = chunk_x, y = chunk_y} 29 | end 30 | 31 | --- Converts a chunk to the area it contains 32 | -- @param chunk_pos to convert to an area 33 | -- @return area that chunk is valid for 34 | function Chunk.to_area(chunk_pos) 35 | fail_if_missing(chunk_pos, "missing chunk_pos argument") 36 | chunk_pos = Position.to_table(chunk_pos) 37 | 38 | local left_top = { x = chunk_pos.x * 32, y = chunk_pos.y * 32 } 39 | return { left_top = left_top, right_bottom = Position.offset(left_top, 32, 32) } 40 | end 41 | 42 | --- Gets user data from the chunk, stored in a mod's global data. 43 | ---

The data will persist between loads

44 | -- @param surface the surface to look up data for 45 | -- @param chunk_pos the chunk coordinates to look up data for 46 | -- @param default_value (optional) to set and return if no data exists 47 | -- @return the data, or nil if no data exists for the chunk 48 | function Chunk.get_data(surface, chunk_pos, default_value) 49 | fail_if_missing(surface, "missing surface argument") 50 | fail_if_missing(chunk_pos, "missing chunk_pos argument") 51 | if not storage._chunk_data then 52 | if not default_value then return nil end 53 | storage._chunk_data = {} 54 | end 55 | 56 | local idx = Chunk.get_index(surface, chunk_pos) 57 | local val = storage._chunk_data[idx] 58 | if not val then 59 | storage._chunk_data[idx] = default_value 60 | val = default_value 61 | end 62 | 63 | return val, idx 64 | end 65 | 66 | --- Sets user data on the chunk, stored in a mod's global data. 67 | ---

The data will persist between loads

68 | -- @param surface the surface to look up data for 69 | -- @param chunk_pos the chunk coordinates to look up data for 70 | -- @param data the data to set (or nil to erase the data for the chunk) 71 | -- @return the previous data associated with the chunk, or nil if the chunk had no previous data 72 | function Chunk.set_data(surface, chunk_pos, data) 73 | fail_if_missing(surface, "missing surface argument") 74 | fail_if_missing(chunk_pos, "missing chunk_pos argument") 75 | if not storage._chunk_data then storage._chunk_data = {} end 76 | 77 | local idx = Chunk.get_index(surface, chunk_pos) 78 | local prev = storage._chunk_data[idx] 79 | storage._chunk_data[idx] = data 80 | 81 | return prev 82 | end 83 | 84 | --- Calculates and returns a stable, deterministic, unique integer id for the given chunk_pos 85 | ---

The id will not change once calculated

86 | -- @param surface the chunk is on 87 | -- @param chunk_pos of the chunk 88 | function Chunk.get_index(surface, chunk_pos) 89 | fail_if_missing(surface, "missing surface argument") 90 | fail_if_missing(chunk_pos, "missing chunk_pos argument") 91 | if not storage._next_chunk_index then storage._next_chunk_index = 0 end 92 | if not storage._chunk_indexes then storage._chunk_indexes = {} end 93 | 94 | if type(surface) == "string" then 95 | surface = game.surfaces[surface] 96 | end 97 | local surface_idx = surface.index 98 | if not storage._chunk_indexes[surface_idx] then storage._chunk_indexes[surface_idx] = {} end 99 | 100 | local surface_chunks = storage._chunk_indexes[surface_idx] 101 | if not surface_chunks[chunk_pos.x] then surface_chunks[chunk_pos.x] = {} end 102 | if not surface_chunks[chunk_pos.x][chunk_pos.y] then 103 | surface_chunks[chunk_pos.x][chunk_pos.y] = storage._next_chunk_index 104 | storage._next_chunk_index = storage._next_chunk_index + 1 105 | end 106 | 107 | return surface_chunks[chunk_pos.x][chunk_pos.y] 108 | end 109 | -------------------------------------------------------------------------------- /stdlib/area/position.lua: -------------------------------------------------------------------------------- 1 | --- Position module 2 | -- @module Position 3 | 4 | Position = {} 5 | 6 | require 'stdlib/core' 7 | 8 | 9 | --- Creates a table representing the position from x and y 10 | -- @param x x-position 11 | -- @param y y-position 12 | -- @return Position 13 | function Position.construct(x, y) 14 | fail_if_missing(x, "missing x position argument") 15 | fail_if_missing(y, "missing y position argument") 16 | return { x = x, y = y } 17 | end 18 | 19 | --- Creates a position that is a copy of the given position 20 | -- @param pos the position to copy 21 | -- @return Position 22 | function Position.copy(pos) 23 | fail_if_missing(pos, "missing position argument") 24 | pos = Position.to_table(pos) 25 | return { x = pos.x, y = pos.y } 26 | end 27 | 28 | --- Creates a position that is offset by x,y coordinate pair 29 | -- @param pos the position to offset 30 | -- @param x the amount to offset the position in the x direction 31 | -- @param y the amount to offset the position in the y direction 32 | -- @return a new position, offset by the x,y coordinates 33 | function Position.offset(pos, x, y) 34 | fail_if_missing(pos, "missing position argument") 35 | fail_if_missing(x, "missing x-coordinate value") 36 | fail_if_missing(y, "missing y-coordinate value") 37 | 38 | if #pos == 2 then 39 | return { x = pos[1] + x, y = pos[2] + y } 40 | else 41 | return { x = pos.x + x, y = pos.y + y } 42 | end 43 | end 44 | 45 | --- Adds 2 positions 46 | -- @param pos1 the first position 47 | -- @param pos2 the second position 48 | -- @return a new position 49 | function Position.add(pos1, pos2) 50 | fail_if_missing(pos1, "missing first position argument") 51 | fail_if_missing(pos2, "missing second position argument") 52 | 53 | pos1 = Position.to_table(pos1) 54 | pos2 = Position.to_table(pos2) 55 | return { x = pos1.x + pos2.x, y = pos1.y + pos2.y} 56 | end 57 | 58 | --- Subtracts 2 positions 59 | -- @param pos1 the first position 60 | -- @param pos2 the second position 61 | -- @return a new position 62 | function Position.subtract(pos1, pos2) 63 | fail_if_missing(pos1, "missing first position argument") 64 | fail_if_missing(pos2, "missing second position argument") 65 | 66 | pos1 = Position.to_table(pos1) 67 | pos2 = Position.to_table(pos2) 68 | return { x = pos1.x - pos2.x, y = pos1.y - pos2.y } 69 | end 70 | 71 | --- Translates a position in the given direction 72 | -- @param pos the position to translate 73 | -- @param direction in which direction to translate (see defines.direction) 74 | -- @param distance distance of the translation 75 | -- @return the translated position 76 | function Position.translate(pos, direction, distance) 77 | fail_if_missing(pos, "missing position argument") 78 | fail_if_missing(direction, "missing direction argument") 79 | fail_if_missing(distance, "missing distance argument") 80 | 81 | pos = Position.to_table(pos) 82 | 83 | if direction == defines.direction.north then 84 | return { x = pos.x, y = pos.y - distance } 85 | elseif direction == defines.direction.northeast then 86 | return { x = pos.x + distance, y = pos.y - distance } 87 | elseif direction == defines.direction.east then 88 | return { x = pos.x + distance, y = pos.y } 89 | elseif direction == defines.direction.southeast then 90 | return { x = pos.x + distance, y = pos.y + distance } 91 | elseif direction == defines.direction.south then 92 | return { x = pos.x, y = pos.y + distance } 93 | elseif direction == defines.direction.southwest then 94 | return { x = pos.x - distance, y = pos.y + distance } 95 | elseif direction == defines.direction.west then 96 | return { x = pos.x - distance, y = pos.y } 97 | elseif direction == defines.direction.northwest then 98 | return { x = pos.x - distance, y = pos.y - distance } 99 | end 100 | end 101 | 102 | --- Expands a position to a square area 103 | -- @param pos the position to expand into an area 104 | -- @param radius half the side length of the area 105 | -- @return a bounding box 106 | function Position.expand_to_area(pos, radius) 107 | fail_if_missing(pos, "missing position argument") 108 | fail_if_missing(radius, "missing radius argument") 109 | 110 | if #pos == 2 then 111 | return { left_top = { x = pos[1] - radius, y = pos[2] - radius }, right_bottom = { x = pos[1] + radius, y = pos[2] + radius } } 112 | end 113 | return { left_top = { x = pos.x - radius, y = pos.y - radius}, right_bottom = { x = pos.x + radius, y = pos.y + radius } } 114 | end 115 | 116 | --- Calculates the Euclidean distance squared between two positions, useful when sqrt is not needed 117 | -- @param pos1 the first position 118 | -- @param pos2 the second position 119 | -- @return the square of the Euclidean distance 120 | function Position.distance_squared(pos1, pos2) 121 | fail_if_missing(pos1, "missing first position argument") 122 | fail_if_missing(pos2, "missing second position argument") 123 | 124 | pos1 = Position.to_table(pos1) 125 | pos2 = Position.to_table(pos2) 126 | local axbx = pos1.x - pos2.x 127 | local ayby = pos1.y - pos2.y 128 | return axbx * axbx + ayby * ayby 129 | end 130 | 131 | --- Calculates the Euclidean distance between two positions 132 | -- @param pos1 the first position 133 | -- @param pos2 the second position 134 | -- @return the square of the Euclidean distance 135 | function Position.distance(pos1, pos2) 136 | fail_if_missing(pos1, "missing first position argument") 137 | fail_if_missing(pos2, "missing second position argument") 138 | 139 | return math.sqrt(Position.distance_squared(pos1, pos2)) 140 | end 141 | 142 | --- Calculates the manhatten distance between two positions 143 | -- @param pos1 the first position 144 | -- @param pos2 the second position 145 | -- @return the square of the Euclidean distance 146 | function Position.manhattan_distance(pos1, pos2) 147 | fail_if_missing(pos1, "missing first position argument") 148 | fail_if_missing(pos2, "missing second position argument") 149 | pos1 = Position.to_table(pos1) 150 | pos2 = Position.to_table(pos2) 151 | 152 | return math.abs(pos2.x - pos1.x) + math.abs(pos2.y - pos1.y) 153 | end 154 | 155 | -- see: https://en.wikipedia.org/wiki/Machine_epsilon 156 | Position._epsilon = 1.19e-07 157 | 158 | --- Whether 2 positions are equal 159 | -- @param pos1 the first position 160 | -- @param pos2 the second position 161 | -- @return true if positions are equal 162 | function Position.equals(pos1, pos2) 163 | if not pos1 or not pos2 then return false end 164 | -- optimize for a shallow equality check first 165 | if pos1 == pos2 then return true end 166 | 167 | local epsilon = Position._epsilon 168 | local abs = math.abs 169 | if #pos1 == 2 and #pos2 == 2 then 170 | return abs(pos1[1] - pos2[1]) < epsilon and abs(pos1[2] - pos2[2]) < epsilon 171 | elseif #pos1 == 2 and #pos2 == 0 then 172 | return abs(pos1[1] - pos2.x) < epsilon and abs(pos1[2] - pos2.y) < epsilon 173 | elseif #pos1 == 0 and #pos2 == 2 then 174 | return abs(pos1.x - pos2[1]) < epsilon and abs(pos1.y - pos2[2]) < epsilon 175 | elseif #pos1 == 0 and #pos2 == 0 then 176 | return abs(pos1.x - pos2.x) < epsilon and abs(pos1.y - pos2.y) < epsilon 177 | end 178 | 179 | return false 180 | end 181 | 182 | --- Converts a position in the array format to a position in the table format 183 | -- @param pos_arr the position to convert 184 | -- @return a converted position, { x = pos_arr[1], y = pos_arr[2] } 185 | function Position.to_table(pos_arr) 186 | fail_if_missing(pos_arr, "missing position argument") 187 | 188 | if #pos_arr == 2 then 189 | return { x = pos_arr[1], y = pos_arr[2] } 190 | end 191 | return pos_arr 192 | end 193 | 194 | --- Converts a position to a string 195 | -- @param pos the position to convert 196 | -- @return string representation of pos 197 | function Position.tostring(pos) 198 | fail_if_missing(pos, "missing position argument") 199 | if #pos == 2 then 200 | return "Position {x = " .. pos[1] .. ", y = " .. pos[2] .. "}" 201 | else 202 | return "Position {x = " .. pos.x .. ", y = " .. pos.y .. "}" 203 | end 204 | end 205 | 206 | return Position 207 | -------------------------------------------------------------------------------- /stdlib/area/tile.lua: -------------------------------------------------------------------------------- 1 | --- Tile module 2 | ---

A tile represents a 1x1 area on a surface in factorio 3 | -- @module Tile 4 | 5 | require 'stdlib/core' 6 | require 'stdlib/area/position' 7 | require 'stdlib/area/chunk' 8 | 9 | Tile = {} 10 | MAX_UINT = 4294967296 11 | 12 | --- Calculates the tile coordinates for the position given 13 | -- @param position to calculate the tile for 14 | -- @return the tile position 15 | function Tile.from_position(position) 16 | position = Position.to_table(position) 17 | return {x = math.floor(position.x), y = math.floor(position.y)} 18 | end 19 | 20 | --- Converts a tile position to the area it contains 21 | -- @param tile_pos to convert to an area 22 | -- @return area that tile is valid for 23 | function Tile.to_area(tile_pos) 24 | fail_if_missing(tile_pos, "missing tile_pos argument") 25 | tile_pos = Tile.from_position(tile_pos) 26 | 27 | return { left_top = tile_pos, right_bottom = Position.offset(tile_pos, 1, 1) } 28 | end 29 | 30 | --- Creates a list of tile positions for all adjacent tiles (N, E, S, W) or (N, NE, E, SE, S, SW, W, NW) if diagonal is true 31 | -- @param surface to examine for adjacent tiles 32 | -- @param position the center tile position, to search around 33 | -- @param diagonal (optional: defaults to false) whether to include diagonal tiles 34 | -- @param tile_name (optional) whether to restrict adjacent tiles to one particular tile name (e.g 'water-tile') 35 | -- @return list of tile positions adjacent to the given position 36 | function Tile.adjacent(surface, position, diagonal, tile_name) 37 | fail_if_missing(surface, "missing surface argument") 38 | fail_if_missing(position, "missing position argument") 39 | 40 | local offsets = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}} 41 | if diagonal then 42 | offsets = {{0, 1}, {1, 1}, {1, 0}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}} 43 | end 44 | local adjacent_tiles = {} 45 | for _, offset in pairs(offsets) do 46 | local adj_pos = Position.add(position, offset) 47 | if tile_name then 48 | local tile = surface.get_tile(adj_pos.x, adj_pos.y) 49 | if tile and tile.name == tile_name then 50 | table.insert(adjacent_tiles, adj_pos) 51 | end 52 | else 53 | table.insert(adjacent_tiles, adj_pos) 54 | end 55 | end 56 | return adjacent_tiles 57 | end 58 | 59 | --- Gets user data from the tile, stored in a mod's global data. 60 | ---

The data will persist between loads

61 | -- @param surface the surface to look up data for 62 | -- @param tile_pos the tile coordinates to look up data for 63 | -- @param default_value (optional) to set and return if no data exists 64 | -- @return the data, or nil if no data exists for the chunk 65 | function Tile.get_data(surface, tile_pos, default_value) 66 | fail_if_missing(surface, "missing surface argument") 67 | fail_if_missing(tile_pos, "missing tile_pos argument") 68 | if not storage._tile_data then 69 | if not default_value then return nil end 70 | storage._tile_data = {} 71 | end 72 | local chunk_idx = Chunk.get_index(surface, Chunk.from_position(tile_pos)) 73 | if not storage._tile_data[chunk_idx] then 74 | if not default_value then return nil end 75 | storage._tile_data[chunk_idx] = {} 76 | end 77 | 78 | local chunk_tiles = storage._tile_data[chunk_idx] 79 | if not chunk_tiles then return nil end 80 | 81 | local idx = Tile.get_index(tile_pos) 82 | local val = chunk_tiles[idx] 83 | if not val then 84 | chunk_tiles[idx] = default_value 85 | val = default_value 86 | end 87 | 88 | return val 89 | end 90 | 91 | --- Sets user data on the tile, stored in a mod's global data. 92 | ---

The data will persist between loads

93 | -- @param surface the surface to look up data for 94 | -- @param tile_pos the chunk coordinates to look up data for 95 | -- @param data the data to set (or nil to erase the data for the tile) 96 | -- @return the previous data associated with the tile, or nil if the tile had no previous data 97 | function Tile.set_data(surface, tile_pos, data) 98 | fail_if_missing(surface, "missing surface argument") 99 | fail_if_missing(tile_pos, "missing tile_pos argument") 100 | if not storage._tile_data then storage._tile_data = {} end 101 | 102 | local chunk_idx = Chunk.get_index(surface, Chunk.from_position(tile_pos)) 103 | if not storage._tile_data[chunk_idx] then storage._tile_data[chunk_idx] = {} end 104 | 105 | local chunk_tiles = storage._tile_data[chunk_idx] 106 | local idx = Tile.get_index(tile_pos) 107 | local prev = chunk_tiles[idx] 108 | chunk_tiles[idx] = data 109 | 110 | return prev 111 | end 112 | 113 | --- Calculates and returns a stable, deterministic integer id for the given tile_pos 114 | ---

Tile id will not change once calculated

115 | ---

Tile ids are only unique for the chunk they are in, they may repeat across a surface.

116 | -- @param tile_pos 117 | -- @return the tile index 118 | function Tile.get_index(tile_pos) 119 | fail_if_missing(tile_pos, "missing tile_pos argument") 120 | return bit32.band(bit32.bor(bit32.lshift(bit32.band(tile_pos.x, 0x1F), 5), bit32.band(tile_pos.y, 0x1F)), 0x3FF) 121 | end 122 | -------------------------------------------------------------------------------- /stdlib/config/config.lua: -------------------------------------------------------------------------------- 1 | --- Config module 2 | -- @module Config 3 | 4 | require 'stdlib/core' 5 | require 'stdlib/string' 6 | require 'stdlib/table' 7 | 8 | ----------------------------------------------------------------------- 9 | --Setup repeated code for use in sub functions here 10 | ----------------------------------------------------------------------- 11 | local reservedCharacters = '`~!@#$%^&*+=|;:/\\\'",?()[]{}<>' 12 | local testReservedCharacters = function(path) 13 | local reservedCharacters = reservedCharacters 14 | for c in reservedCharacters:gmatch('.') do 15 | if path:find(c, 1, true) then 16 | return c 17 | end 18 | end 19 | return nil 20 | end 21 | 22 | Config = {} 23 | 24 | --- Creates a new Config object 25 | -- to ease the management of a config table. 26 | -- @param config_table [required] The table to be managed. 27 | -- @return the Config instance for managing config_table 28 | -- 29 | -- @usage --[Use a global table for config that persists across game save/loads] 30 | --CONFIG = Config.new(storage.testtable) 31 | -- 32 | -- @usage --[You can also create a temporary scratch pad config] 33 | --CONFIG = Config.new({}) -- Temporary scratch pad 34 | -- 35 | -- @usage --[Setting data in Config] 36 | --CONFIG = Config.new(storage.testtable) 37 | --CONFIG.set("your.path.here", "myvalue") 38 | -- 39 | -- @usage --[Getting data out of Config] 40 | --CONFIG = Config.new(storage.testtable) 41 | --my_data = CONFIG.get("your.path.here") 42 | -- 43 | -- @usage --[Getting data out of Config with a default to use if path is not found in Config] 44 | --CONFIG = Config.new(storage.testtable) 45 | --my_data = CONFIG.get("your.path.here", "Your Default here") 46 | -- 47 | -- @usage --[Deleting a path from Config] 48 | --CONFIG = Config.new(storage.testtable) 49 | --CONFIG.delete("your.path.here") 50 | -- 51 | -- @usage --[Checking if a path exists in Config] 52 | --CONFIG = Config.new(storage.testtable) 53 | --CONFIG.is_set("your.path.here") 54 | function Config.new(config_table) 55 | if not config_table then 56 | error("config_table is a required parameter.", 2) 57 | elseif type(config_table) ~= "table" then 58 | error("config_table must be a table. Was given [" .. type(options) .. "]", 2) 59 | elseif type(config_table.get) == "function" then 60 | error("Config can't manage another Config object", 2) 61 | end 62 | 63 | ----------------------------------------------------------------------- 64 | --Setup the Config object 65 | ----------------------------------------------------------------------- 66 | local Config = {} 67 | 68 | --- Get a stored config value. 69 | -- @param path [required] a string representing the variable to retrieve 70 | -- @param default (optional) value to be used if path is nil 71 | -- @return value at path or nil if not found and no default given 72 | function Config.get(path, default) 73 | if type(path) ~= "string" or path:is_empty() then error("path is invalid", 2) end 74 | 75 | local config = config_table 76 | 77 | local c = testReservedCharacters(path) 78 | if c ~= nil then error("path '" .. path .. "' contains the reserved character '" .. c .. "'", 2) end 79 | 80 | local pathParts = path:split('.') 81 | local part = config; 82 | local value = nil; 83 | 84 | for key = 1, #pathParts, 1 do 85 | local partKey = pathParts[key] 86 | if (type(part) ~= "table") then 87 | value = nil; 88 | break; 89 | end 90 | 91 | value = part[partKey]; 92 | part = part[partKey]; 93 | end 94 | 95 | if (type(value) == "table") then 96 | --Force break references. 97 | return table.deepcopy(value); 98 | elseif (value ~= nil) then 99 | return value; 100 | else 101 | return default; 102 | end 103 | end 104 | 105 | --- Set a stored config value. 106 | -- @param path [required] a string, config path to set 107 | -- @param data (optional) Value to set path to. If nil it behaves identical to Config.delete() 108 | -- @return number 0 on failure; number of affected paths on success 109 | function Config.set(path, data) 110 | if type(path) ~= "string" or path:is_empty() then error("path is invalid", 2) end 111 | 112 | local config = config_table 113 | 114 | local c = testReservedCharacters(path) 115 | if c ~= nil then error("path contains the reserved character '" .. c .. "'", 2) end 116 | 117 | local pathParts = path:split('.') 118 | local part = config; 119 | local value = nil; 120 | 121 | for key = 1, #pathParts - 1, 1 do 122 | local partKey = pathParts[key] 123 | if (type(part[partKey]) ~= "table") then 124 | part[partKey] = {}; 125 | end 126 | 127 | value = part[partKey]; 128 | part = part[partKey]; 129 | end 130 | 131 | part[pathParts[#pathParts]] = data; 132 | 133 | return 1; 134 | end 135 | 136 | --- Delete a stored config value. 137 | -- @param path a string, config path to delete 138 | -- @return number 0 on failure; number of affected paths on success 139 | function Config.delete(path) 140 | if type(path) ~= "string" or path:is_empty() then error("path is invalid", 2) end 141 | 142 | local config = config_table 143 | 144 | local c = testReservedCharacters(path) 145 | if c ~= nil then error("path contains the reserved character '" .. c .. "'", 2) end 146 | 147 | local pathParts = path:split('.') 148 | local part = config 149 | local value = nil 150 | 151 | for key = 1, #pathParts - 1, 1 do 152 | local partKey = pathParts[key] 153 | if (type(part[partKey]) ~= "table") then 154 | return 0 155 | end 156 | 157 | part = part[partKey] 158 | end 159 | 160 | if part[pathParts[#pathParts]] == nil then 161 | return 0 162 | else 163 | part[pathParts[#pathParts]] = nil 164 | return 1 165 | end 166 | end 167 | 168 | --- Test the existence of a stored config value. 169 | -- @param path a string, config path to test 170 | -- @return boolean, true on success, false otherwise 171 | function Config.is_set(path) 172 | if type(path) ~= "string" or path:is_empty() then error("path is invalid", 2) end 173 | 174 | return Config.get(path) ~= nil 175 | end 176 | 177 | return Config 178 | end 179 | 180 | return Config 181 | -------------------------------------------------------------------------------- /stdlib/core.lua: -------------------------------------------------------------------------------- 1 | --- Core module 2 | -- @module Core 3 | 4 | --- Errors if the variable evaluates to false, with an optional msg 5 | -- @param var variable to evaluate 6 | -- @param msg (optional) message 7 | function fail_if_missing(var, msg) 8 | if not var then 9 | if msg then 10 | error(msg, 3) 11 | else 12 | error("Missing value", 3) 13 | end 14 | end 15 | return false 16 | end 17 | -------------------------------------------------------------------------------- /stdlib/data/data.lua: -------------------------------------------------------------------------------- 1 | --- Data module 2 | -- @module Data 3 | 4 | require 'stdlib/core' 5 | require 'stdlib/string' 6 | require 'stdlib/table' 7 | 8 | Data = {} 9 | 10 | --- Selects all data values where the key matches the selector pattern. 11 | -- The selector pattern is divided into groups. The pattern should have a colon character `:` to denote the selection for each group. 12 | --
The first group is for the class of the data type (item, recipe, entity-type, etc) 13 | --
The second group is for the name of the data element, and is optional. If missing, all elements matching prior groups are returned. 14 | --

For more granular selectors, see other modules, such as Recipe.select. 15 | -- @usage Data.select('recipe') -- returns a table with all recipes 16 | -- @usage Data.select('recipe:steel.*') -- returns a table with all recipes whose name matches 'steel.*' 17 | -- @param pattern to search with 18 | -- @return table containing the elements matching the selector pattern, or an empty table if there was no matches 19 | function Data.select(pattern) 20 | fail_if_missing(pattern, "missing pattern argument") 21 | 22 | local parts = string.split(pattern, ":") 23 | local category_pattern = table.first(parts) 24 | local results = {} 25 | for category, values in pairs(data.raw) do 26 | if string.match(category, category_pattern) then 27 | local element_pattern = #parts > 1 and parts[2] or '.*' 28 | -- escape the '-' in names 29 | element_pattern = string.gsub(element_pattern, "%-", "%%-") 30 | for element_name, element in pairs(values) do 31 | if string.match(element_name, element_pattern) then 32 | table.insert(results, element) 33 | end 34 | end 35 | end 36 | end 37 | setmetatable(results, Data._select_metatable.new(results)) 38 | return results 39 | end 40 | 41 | -- this metatable is set on recipes, to control access to ingredients and results 42 | Data._select_metatable = {} 43 | Data._select_metatable.new = function(selection) 44 | local self = { } 45 | self.__index = function(tbl, key) 46 | if key == 'apply' then 47 | return function(k, v) 48 | table.each(tbl, function(obj) 49 | obj[k] = v 50 | end) 51 | return tbl 52 | end 53 | end 54 | end 55 | self.__newindex = function(tbl, key, value) 56 | table.each(tbl, function(obj) 57 | obj[key] = value 58 | end) 59 | end 60 | 61 | return self 62 | end 63 | -------------------------------------------------------------------------------- /stdlib/data/recipe.lua: -------------------------------------------------------------------------------- 1 | --- Recipe module 2 | -- @module Recipe 3 | 4 | require 'stdlib/data/data' 5 | 6 | Recipe = {} 7 | 8 | --- Selects all recipe values where the key matches the selector pattern. 9 | -- The selector pattern is divided into groups. The pattern should have a colon character `:` to denote the selection for each group. 10 | --
The first group is for the name of the recipe element 11 | --
The second group is for the name of keys inside of the recipe element, and is optional. If missing, all elements matching prior groups are returned. 12 | --
The third group is for the name of values inside of the recipe element, and is optional. If missing, all elements matching prior groups are returned. 13 | --

Selectors without a colon `:` separator are assumed to select all values in the first group. 14 | -- @usage Recipe.select('.*') -- returns a table with all recipes, equivalent to Data.select('recipe:.*') 15 | -- @usage Recipe.select('steel.*') -- returns a table with all recipes whose name matches 'steel.*' 16 | -- @usage Recipe.select('steel.*:ingredients') -- returns a table with all ingredients from all recipes whose name matches 'steel.*' 17 | -- @usage Recipe.select('steel.*:ingredients:iron-plate') -- returns a table with all iron-plate ingredient objects, from all recipes whose name matches 'steel.*' 18 | -- @param pattern to search with 19 | -- @return table containing the elements matching the selector pattern, or an empty table if there was no matches 20 | function Recipe.select(pattern) 21 | fail_if_missing(pattern, "missing pattern argument") 22 | 23 | local results = {} 24 | local parts = string.split(pattern, ":") 25 | local inner_field_pattern = #parts > 1 and parts[2] or nil 26 | 27 | if inner_field_pattern then 28 | -- Data.select --> { { recipe }, { recipe } } 29 | for _, recipe in pairs(Data.select('recipe:' .. pattern)) do 30 | for field_key, field_value in pairs(recipe) do 31 | -- field_key --> ingredients, field_value --> { { 'copper-ore', 1} } 32 | if string.match(field_key, inner_field_pattern) then 33 | local contents_field_pattern = #parts > 2 and parts[3] or nil 34 | if contents_field_pattern then 35 | -- escape the '-' in names 36 | contents_field_pattern = string.gsub(contents_field_pattern, "%-", "%%-") 37 | 38 | -- ex: field_value --> { { 'copper-ore', 1} } 39 | for _, content_value in pairs(field_value) do 40 | -- ex: content_value --> { 'copper-ore', 1} 41 | for _, content in pairs(content_value) do 42 | -- ex: content --> 'copper-ore', 1 43 | if string.match(content, contents_field_pattern) then 44 | Recipe.format_items({recipe}) 45 | table.insert(results, content_value) 46 | end 47 | end 48 | end 49 | else 50 | for _, value in pairs(field_value) do 51 | setmetatable(value, Recipe._item_metatable.new(value)) 52 | table.insert(results, value) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | else 59 | return Recipe.format_items(Data.select('recipe:' .. pattern)) 60 | end 61 | setmetatable(results, Data._select_metatable.new(results)) 62 | return results 63 | end 64 | 65 | -- this metatable is set on recipes, to control access to ingredients and results 66 | Recipe._item_metatable = {} 67 | Recipe._item_metatable.new = function(item) 68 | local self = { } 69 | self.__index = function(tbl, key) 70 | if type(key) == 'number' then 71 | local keys = { 'name', 'amount' } 72 | local val = rawget(tbl, keys[key]) 73 | -- amount defaults to one 74 | if not val and keys[key] == 'amount' then 75 | return 1 76 | end 77 | return val 78 | elseif type(key) == 'string' then 79 | local keys = { name = 1, amount = 2 } 80 | local val = rawget(tbl, keys[key]) 81 | -- amount defaults to one 82 | if not val and key == 'amount' then 83 | return 1 84 | end 85 | return val 86 | end 87 | return rawget(tbl, key) 88 | end 89 | 90 | self.__newindex = function(tbl, key, value) 91 | if type(key) == 'number' and #tbl == 0 then 92 | local keys = { 'name', 'amount' } 93 | rawset(tbl, keys[key], value) 94 | elseif type(key) == 'string' and #tbl > 0 then 95 | local keys = { name = 1, amount = 2 } 96 | rawset(tbl, keys[key], value) 97 | else 98 | return rawset(tbl, key, value) 99 | end 100 | end 101 | 102 | return self 103 | end 104 | 105 | function Recipe.format_items(recipes) 106 | recipes = recipes or data.raw.recipe 107 | table.each(recipes, function(recipe, recipe_name) 108 | if recipe.ingredients and type(recipe.ingredients) == 'table' then 109 | table.each(recipe.ingredients, function(ingredient) setmetatable(ingredient, Recipe._item_metatable.new(ingredient)) end) 110 | end 111 | if recipe.results and type(recipe.results) == 'table' then 112 | table.each(recipe.results, function(result) setmetatable(result, Recipe._item_metatable.new(result)) end) 113 | end 114 | end) 115 | return recipes 116 | end 117 | -------------------------------------------------------------------------------- /stdlib/entity/entity.lua: -------------------------------------------------------------------------------- 1 | --- Entity module 2 | -- @module Entity 3 | 4 | require 'stdlib/core' 5 | require 'stdlib/surface' 6 | require 'stdlib/area/area' 7 | 8 | Entity = {} 9 | 10 | --- Converts an entity and its selection_box to the area around it 11 | -- @param entity to convert to an area 12 | -- @return area that entity selection_box is valid for 13 | function Entity.to_selection_area(entity) 14 | fail_if_missing(entity, "missing entity argument") 15 | 16 | local pos = entity.position 17 | local bb = entity.prototype.selection_box 18 | return Area.offset(bb, pos) 19 | end 20 | 21 | --- Converts an entity and its collision_box to the area around it 22 | -- @param entity to convert to an area 23 | -- @return area that entity collision_box is valid for 24 | function Entity.to_collision_area(entity) 25 | fail_if_missing(entity, "missing entity argument") 26 | 27 | local pos = entity.position 28 | local bb = entity.prototype.collision_box 29 | return Area.offset(bb, pos) 30 | end 31 | 32 | --- Tests whether an entity has access to the field 33 | -- @param entity to test field access 34 | -- @param field_name that should be tested for 35 | -- @return true if the entity has access to the field, false if the entity threw an exception accessing the field 36 | function Entity.has(entity, field_name) 37 | fail_if_missing(entity, "missing entity argument") 38 | fail_if_missing(field_name, "missing field name argument") 39 | 40 | local status = pcall(function() return entity[field_name]; end) 41 | return status 42 | end 43 | 44 | --- Gets user data from the entity, stored in a mod's global data. 45 | ---

The data will persist between loads, and will be removed for an entity when it becomes invalid

46 | -- @param entity the entity to look up data for 47 | -- @return the data, or nil if no data exists for the entity 48 | function Entity.get_data(entity) 49 | fail_if_missing(entity, "missing entity argument") 50 | if not storage._entity_data then return nil end 51 | 52 | local unit_number = entity.unit_number 53 | if unit_number then 54 | return storage._entity_data[unit_number] 55 | else 56 | local entity_name = entity.name 57 | if not storage._entity_data[entity_name] then return nil end 58 | 59 | local entity_category = storage._entity_data[entity_name] 60 | for _, entity_data in pairs(entity_category) do 61 | if Entity._are_equal(entity_data.entity, entity) then 62 | return entity_data.data 63 | end 64 | end 65 | return nil 66 | end 67 | end 68 | 69 | --- Sets user data on the entity, stored in a mod's global data. 70 | ---

The data will persist between loads, and will be removed for an entity when it becomes invalid

71 | -- @param entity the entity to set data for 72 | -- @param data the data to set, or nil to delete the data associated with the entity 73 | -- @return the previous data associated with the entity, or nil if the entity had no previous data 74 | function Entity.set_data(entity, data) 75 | fail_if_missing(entity, "missing entity argument") 76 | 77 | if not storage._entity_data then storage._entity_data = {} end 78 | 79 | local unit_number = entity.unit_number 80 | if unit_number then 81 | local prev = storage._entity_data[unit_number] 82 | storage._entity_data[unit_number] = data 83 | return prev 84 | else 85 | local entity_name = entity.name 86 | if not storage._entity_data[entity_name] then 87 | storage._entity_data[entity_name] = {} 88 | end 89 | 90 | local entity_category = storage._entity_data[entity_name] 91 | 92 | for i = #entity_category, 1, -1 do 93 | local entity_data = entity_category[i] 94 | if not entity_data.entity.valid then 95 | table.remove(entity_category, i) 96 | end 97 | if Entity._are_equal(entity_data.entity, entity) then 98 | local prev = entity_data.data 99 | if data then 100 | entity_data.data = data 101 | else 102 | table.remove(entity_category, i) 103 | end 104 | return prev 105 | end 106 | end 107 | table.insert(entity_category, { entity = entity, data = data }) 108 | end 109 | return nil 110 | end 111 | 112 | --- Freezes an entity, by making it inactive, inoperable, and non-rotatable, or unfreezes by doing the reverse. 113 | -- @param entity the entity to freeze or unfreeze 114 | -- @param mode (optional) if true, freezes the entity, if false, unfreezes the entity. If not specified, is true. 115 | -- @return entity passed into it 116 | function Entity.set_frozen(entity, mode) 117 | fail_if_missing(entity, "missing entity argument") 118 | mode = mode == false and true or false 119 | entity.active = mode 120 | entity.operable = mode 121 | entity.rotatable = mode 122 | return entity 123 | end 124 | 125 | --- Makes an entity indestructible, so that it can not be damaged or mined by the player or enemy factions 126 | -- @param entity the entity to set indestructible 127 | -- @param mode (optional) if true, makes the entity indestructible, if false, makes the entity destructable. If not specified, is true. 128 | -- @return entity passed into it 129 | function Entity.set_indestructible(entity, mode) 130 | fail_if_missing(entity, "missing entity argument") 131 | mode = mode == false and true or false 132 | entity.minable = mode 133 | entity.destructible = mode 134 | return entity 135 | end 136 | 137 | --- Tests if two entities are equal 138 | --

If they don't have reference equality and entity_a has an 'equals' function, 139 | -- it will be called with entity_b as the first argument

140 | -- @tparam table entity_a 141 | -- @tparam table entity_b 142 | -- @treturn bool 143 | function Entity._are_equal(entity_a, entity_b) 144 | if entity_a == nil then 145 | return entity_a == entity_b 146 | elseif entity_a == entity_b then 147 | return true 148 | elseif Entity.has(entity_a, "equals") and entity_a.equals ~= nil then 149 | return entity_a.equals(entity_b) 150 | else 151 | return false 152 | end 153 | end 154 | 155 | return Entity 156 | -------------------------------------------------------------------------------- /stdlib/entity/inventory.lua: -------------------------------------------------------------------------------- 1 | --- Inventory module 2 | -- @module Inventory 3 | 4 | Inventory = {} 5 | 6 | require 'stdlib/core' 7 | require 'stdlib/entity/entity' 8 | 9 | --- Copies an inventory contents to a destination inventory 10 | -- @param src source inventory to copy from 11 | -- @param dest destination inventory, to copy to 12 | -- @return an array of SimpleItemStacks of left over items that could not be copied. 13 | function Inventory.copy_inventory(src, dest) 14 | fail_if_missing(src, "missing source inventory") 15 | fail_if_missing(dest, "missing destination inventory") 16 | 17 | local left_over = {} 18 | for i = 1, #src do 19 | local stack = src[i] 20 | if stack and stack.valid and stack.valid_for_read then 21 | local copy_of_item_stack = { name = stack.name, count = stack.count, health = stack.health or nil, durability = stack.durability or nil } 22 | -- allow valid/valid_for_read calls, without setting the real fields 23 | setmetatable(copy_of_item_stack, { __index = { valid = true, valid_for_read = true }}) 24 | 25 | -- ammo is a special case field, accessing it on non-ammo itemstacks causes an exception 26 | if stack.prototype.ammo_type then 27 | copy_of_item_stack.ammo = stack.ammo or nil 28 | end 29 | 30 | local inserted = dest.insert(copy_of_item_stack) 31 | local amt_not_inserted = stack.count - inserted 32 | if amt_not_inserted > 0 then 33 | table.insert(left_over, cur_stack) 34 | end 35 | end 36 | end 37 | return left_over 38 | end 39 | 40 | return Inventory 41 | -------------------------------------------------------------------------------- /stdlib/event/event.lua: -------------------------------------------------------------------------------- 1 | --- Event module 2 | -- @module Event 3 | 4 | require 'stdlib/core' 5 | require 'stdlib/game' 6 | 7 | Event = { 8 | _registry = {}, 9 | core_events = { 10 | init = -1, 11 | load = -2, 12 | configuration_changed = -3, 13 | _register = function(id) 14 | if id == Event.core_events.init then 15 | script.on_init(function() 16 | Event.dispatch({name = Event.core_events.init, tick = game.tick}) 17 | end) 18 | elseif id == Event.core_events.load then 19 | script.on_load(function() 20 | Event.dispatch({name = Event.core_events.load, tick = -1}) 21 | end) 22 | elseif id == Event.core_events.configuration_changed then 23 | script.on_configuration_changed(function(data) 24 | Event.dispatch({name = Event.core_events.configuration_changed, tick = game.tick, data = data}) 25 | end) 26 | end 27 | end 28 | } 29 | } 30 | 31 | --- Registers a function for a given event 32 | -- @param event or array containing events to register 33 | -- @param handler Function to call when event is triggered 34 | -- @return #Event 35 | function Event.register(event, handler) 36 | fail_if_missing(event, "missing event argument") 37 | 38 | if type(event) == "number" then 39 | event = {event} 40 | end 41 | 42 | for _, event_id in pairs(event) do 43 | fail_if_missing(event_id, "missing event id") 44 | if handler == nil then 45 | Event._registry[event_id] = nil 46 | script.on_event(event_id, nil) 47 | else 48 | if not Event._registry[event_id] then 49 | Event._registry[event_id] = {} 50 | 51 | if event_id >= 0 then 52 | script.on_event(event_id, Event.dispatch) 53 | else 54 | Event.core_events._register(event_id) 55 | end 56 | end 57 | table.insert(Event._registry[event_id], handler) 58 | end 59 | end 60 | return Event 61 | end 62 | 63 | --- Calls the registerd handlers 64 | -- @param event LuaEvent as created by game.raise_event 65 | function Event.dispatch(event) 66 | fail_if_missing(event, "missing event argument") 67 | if Event._registry[event.name] then 68 | for _, handler in pairs(Event._registry[event.name]) do 69 | local metatbl = { __index = function(tbl, key) if key == '_handler' then return handler else return rawget(tbl, key) end end } 70 | setmetatable(event, metatbl) 71 | local success, err = pcall(handler, event) 72 | if not success then 73 | -- may be nil in on_load 74 | if _G.game then 75 | if Game.print_all(err) == 0 then 76 | -- no players received the message, force a real error so someone notices 77 | error(err) 78 | end 79 | else 80 | -- no way to handle errors cleanly when the game is not up 81 | error(err) 82 | end 83 | return 84 | end 85 | if err then 86 | return 87 | end 88 | end 89 | end 90 | end 91 | 92 | --- Removes the handler from the event 93 | -- @param event event or array containing events to remove the handler 94 | -- @param handler to remove 95 | -- @return #Event 96 | function Event.remove(event, handler) 97 | fail_if_missing(event, "missing event argument") 98 | fail_if_missing(handler, "missing handler argument") 99 | 100 | if type(event) == "number" then 101 | event = {event} 102 | end 103 | 104 | for _, event_id in pairs(event) do 105 | fail_if_missing(event_id, "missing event id") 106 | if Event._registry[event_id] then 107 | for i=#Event._registry[event_id], 1, -1 do 108 | if Event._registry[event_id][i] == handler then 109 | table.remove(Event._registry[event_id], i) 110 | end 111 | end 112 | if #Event._registry[event_id] == 0 then 113 | Event._registry[event_id] = nil 114 | script.on_event(event_id, nil) 115 | end 116 | end 117 | end 118 | return Event 119 | end 120 | 121 | return Event 122 | -------------------------------------------------------------------------------- /stdlib/event/time.lua: -------------------------------------------------------------------------------- 1 | --- Time Event module 2 | -- @module Event.Time 3 | 4 | require 'stdlib/event/event' 5 | require 'stdlib/time' 6 | 7 | Event.Time = {} 8 | Event.Time._last_change = {} 9 | 10 | --All times are offset by 0.5 11 | --This is because both EvoGUI and MoWeather already apply that offset. 12 | --Following the precedent to remain consistent. 13 | --Actually, this little snippet is even borrowed from EvoGUI. 14 | if remote.interfaces.MoWeather then 15 | -- assume MoWeather's getdaytime is sane 16 | function Event.Time.get_day_time(surface_name_or_index) return remote.call("MoWeather", "getdaytime", surface_name_or_index) end 17 | else 18 | -- 0.5 is midnight; let's make days *start* at midnight instead. 19 | function Event.Time.get_day_time(surface_name_or_index) return game.surfaces[surface_name_or_index].daytime + 0.5 end 20 | end 21 | 22 | --- @field Fires whenever it becomes midday/noon on a surface 23 | Event.Time.midday = script.generate_event_name() 24 | --- @field Fires whenever it becomes midnight on a surface 25 | Event.Time.midnight = script.generate_event_name() 26 | --- @field Fires whenever the sunrises on a surface 27 | Event.Time.sunrise = script.generate_event_name() 28 | --- @field Fires whenever the sunsets on a surface 29 | Event.Time.sunset = script.generate_event_name() 30 | --- @field Fires every hour for a surface 31 | Event.Time.hourly = script.generate_event_name() 32 | --- @field Fires every minute for a surface 33 | Event.Time.minutely = script.generate_event_name() 34 | --- @field Fires every day for a surface 35 | Event.Time.daily = script.generate_event_name() 36 | 37 | Event.register(defines.events.on_tick, function(event) 38 | for idx, surface in pairs(game.surfaces) do 39 | local day_time = math.fmod(Event.Time.get_day_time(idx), 1) 40 | local day_time_minutes = math.floor(day_time * 24 * 60) 41 | 42 | if day_time_minutes ~= Event.Time._last_change[idx] then 43 | Event.Time._last_change[idx] = day_time_minutes 44 | game.raise_event(Event.Time.minutely, {surface = surface}) 45 | 46 | if day_time_minutes % 60 == 0 then 47 | game.raise_event(Event.Time.hourly, {surface = surface}) 48 | end 49 | 50 | if day_time_minutes == 0 then 51 | game.raise_event(Event.Time.daily, {surface = surface}) 52 | game.raise_event(Event.Time.midnight, {surface = surface}) 53 | end 54 | 55 | -- These are not 100% accurate but within 5-10 Nauvis minutes of the real thing. 56 | -- 105 (1:45AM) Brightness starts to increase 57 | -- 265 (4:25AM) Flashlight clicks off 58 | if day_time_minutes == 265 then 59 | game.raise_event(Event.Time.sunrise, {surface = surface}) 60 | end 61 | 62 | if day_time_minutes == 720 then 63 | game.raise_event(Event.Time.midday, {surface = surface}) 64 | end 65 | 66 | -- These are not 100% accurate but within 5-10 Nauvis minutes of the real thing. 67 | -- 1070 (5:50PM) Brightness starts to decrease 68 | -- 1160 (7:20PM) Flashlight clicks on 69 | if day_time_minutes == 1160 then 70 | game.raise_event(Event.Time.sunset, {surface = surface}) 71 | end 72 | end 73 | end 74 | end) 75 | -------------------------------------------------------------------------------- /stdlib/game.lua: -------------------------------------------------------------------------------- 1 | --- Game module 2 | -- @module Game 3 | 4 | Game = {} 5 | Game.VALID_FILTER = function(v) 6 | return v.valid 7 | end 8 | 9 | --- Messages all players currently connected to the game 10 | -- @param msg message to send to players 11 | -- @param condition (optional) optional condition to be true for the player to be messaged 12 | -- @return the number of players who received the message. Offline players are not counted as having received the message. 13 | function Game.print_all(msg, condition) 14 | local num = 0 15 | for _, player in pairs(game.players) do 16 | if player.valid then 17 | if condition == nil or select(2, pcall(condition, player)) then 18 | player.print(msg) 19 | if player.connected then 20 | num = num + 1 21 | end 22 | end 23 | end 24 | end 25 | return num 26 | end 27 | 28 | --- Messages all players with the given force connected to the game 29 | -- Deprecated for Factorio 0.14+, see force.print(msg) instead. 30 | -- @param force (may be force name string, or force object) the players with the given force to message 31 | -- @param msg message to send to players 32 | -- @return the number of players who received the message 33 | function Game.print_force(force, msg) 34 | local force_name 35 | if type(force) == "string" then 36 | force_name = force 37 | else 38 | force_name = force.name 39 | end 40 | return Game.print_all(msg, function(player) 41 | return player.force.name == force_name 42 | end) 43 | end 44 | 45 | --- Messages all players with the given surface connected to the game 46 | -- Deprecated for Factorio 0.14+, see surface.print(msg) instead. 47 | -- @param surface the players with the given surface to message 48 | -- @param msg message to send to players 49 | -- @return the number of players who received the message 50 | function Game.print_surface(surface, msg) 51 | local surface_name 52 | if type(surface) == "string" then 53 | surface_name = surface 54 | else 55 | surface_name = surface.name 56 | end 57 | return Game.print_all(msg, function(player) 58 | return player.surface.name == surface_name 59 | end) 60 | end 61 | 62 | return Game 63 | -------------------------------------------------------------------------------- /stdlib/gui/gui.lua: -------------------------------------------------------------------------------- 1 | --- Gui module 2 | -- @module Gui 3 | 4 | require 'stdlib/event/event' 5 | 6 | Gui = {} 7 | -- Factorio's gui events are so monolithic we need a special event system for it. 8 | Gui.Event = { 9 | _registry = {}, 10 | _dispatch = {} 11 | } 12 | 13 | --- Registers a function for a given event and matching gui element pattern 14 | -- @param event Valid values are defines.event.on_gui_* 15 | -- @param gui_element_pattern the name or string regular expression to match the gui element 16 | -- @param handler Function to call when event is triggered 17 | -- @return #Gui.Event 18 | function Gui.Event.register(event, gui_element_pattern, handler) 19 | fail_if_missing(event, "missing event name argument") 20 | fail_if_missing(gui_element_pattern, "missing gui name or pattern argument") 21 | 22 | if type(gui_element_pattern) ~= "string" then 23 | error("gui_element_pattern argument must be a string") 24 | end 25 | 26 | if handler == nil then 27 | Gui.Event.remove(event, gui_element_pattern) 28 | return Gui.Event 29 | end 30 | 31 | if not Gui.Event._registry[event] then 32 | Gui.Event._registry[event] = {} 33 | end 34 | Gui.Event._registry[event][gui_element_pattern] = handler 35 | 36 | -- Use custom Gui event dispatcher to pass off the event to the correct sub-handler 37 | if not Gui.Event._dispatch[event] then 38 | Event.register(event, Gui.Event.dispatch) 39 | Gui.Event._dispatch[event] = true 40 | end 41 | 42 | return Gui.Event 43 | end 44 | 45 | --- Calls the registered handlers 46 | -- @param event LuaEvent as created by game.raise_event 47 | function Gui.Event.dispatch(event) 48 | fail_if_missing(event, "missing event argument") 49 | 50 | local gui_element = event.element 51 | if gui_element and gui_element.valid then 52 | local gui_element_name = gui_element.name; 53 | local gui_element_state = nil; 54 | local gui_element_text = nil; 55 | 56 | if event.name == defines.events.on_gui_checked_state_changed then 57 | gui_element_state = gui_element.state 58 | end 59 | 60 | if event.name == defines.events.on_gui_text_changed then 61 | gui_element_text = gui_element.text 62 | end 63 | 64 | for gui_element_pattern, handler in pairs(Gui.Event._registry[event.name]) do 65 | local match_str = string.match(gui_element_name, gui_element_pattern) 66 | if match_str ~= nil then 67 | local new_event = { tick = event.tick, name = event.name, _handler = handler, match = match_str, element = gui_element, state=gui_element_state, text=gui_element_text, player_index = event.player_index , _event = event} 68 | local success, err = pcall(handler, new_event) 69 | if not success then 70 | Game.print_all(err) 71 | end 72 | end 73 | end 74 | end 75 | end 76 | 77 | --- Removes the handler with matching gui element pattern from the event 78 | -- @param event Valid values are defines.event.on_gui_* 79 | -- @param gui_element_pattern the name or string regular expression to remove the handler for 80 | -- @return #Gui.Event 81 | function Gui.Event.remove(event, gui_element_pattern) 82 | fail_if_missing(event, "missing event argument") 83 | fail_if_missing(gui_element_pattern, "missing gui_element_pattern argument") 84 | 85 | if type(gui_element_pattern) ~= "string" then 86 | error("gui_element_pattern argument must be a string") 87 | end 88 | 89 | local function tablelength(T) 90 | local count = 0 91 | for _ in pairs(T) do count = count + 1 end 92 | return count 93 | end 94 | 95 | if Gui.Event._registry[event] then 96 | if Gui.Event._registry[event][gui_element_pattern] then 97 | Gui.Event._registry[event][gui_element_pattern] = nil 98 | end 99 | if tablelength(Gui.Event._registry[event]) == 0 then 100 | Event.remove(event, Gui.Event.dispatch) 101 | Gui.Event._registry[event] = nil 102 | Gui.Event._dispatch[event] = false 103 | end 104 | end 105 | return Gui.Event 106 | end 107 | 108 | --- Registers a function for a given gui element name or pattern when the element is clicked 109 | -- @param gui_element_pattern the name or string regular expression to match the gui element 110 | -- @param handler Function to call when gui element is clicked 111 | -- @return #Gui 112 | function Gui.on_click(gui_element_pattern, handler) 113 | Gui.Event.register(defines.events.on_gui_click, gui_element_pattern, handler) 114 | return Gui 115 | end 116 | 117 | --- Registers a function for a given gui element name or pattern when the element checked state changes 118 | -- @param gui_element_pattern the name or string regular expression to match the gui element 119 | -- @param handler Function to call when gui element checked state changes 120 | -- @return #Gui 121 | function Gui.on_checked_state_changed(gui_element_pattern, handler) 122 | Gui.Event.register(defines.events.on_gui_checked_state_changed, gui_element_pattern, handler) 123 | return Gui 124 | end 125 | 126 | --- Registers a function for a given gui element name or pattern when the element text changes 127 | -- @param gui_element_pattern the name or string regular expression to match the gui element 128 | -- @param handler Function to call when gui element text changes 129 | -- @return #Gui 130 | function Gui.on_text_changed(gui_element_pattern, handler) 131 | Gui.Event.register(defines.events.on_gui_text_changed, gui_element_pattern, handler) 132 | return Gui 133 | end 134 | -------------------------------------------------------------------------------- /stdlib/log/logger.lua: -------------------------------------------------------------------------------- 1 | --- Logger module 2 | -- @module Logger 3 | 4 | Logger = {} 5 | 6 | --- Creates a new logger object.

7 | -- In debug mode, the logger writes immediately. Otherwise the loggers buffers lines. 8 | -- The logger flushes after 60 seconds has elapsed since the last message. 9 | --

10 | -- When loggers are created, a table of options may be specified. The valid options are: 11 | -- 12 | -- log_ticks -- whether to include the game tick timestamp in logs. Defaults to false. 13 | -- file_extension -- a string that overides the default 'log' file extension. 14 | -- force_append -- each time a logger is created, it will always append, instead of 15 | -- -- the default behavior, which is to write out a new file, then append 16 | -- 17 | -- 18 | -- @usage 19 | --LOGGER = Logger.new('cool_mod_name') 20 | --LOGGER.log("this msg will be logged!") 21 | -- 22 | -- @usage 23 | --LOGGER = Logger.new('cool_mod_name', 'test', true) 24 | --LOGGER.log("this msg will be logged and written immediately in test.log!") 25 | -- 26 | -- @usage 27 | --LOGGER = Logger.new('cool_mod_name', 'test', true, { file_extension = data }) 28 | --LOGGER.log("this msg will be logged and written immediately in test.data!") 29 | -- 30 | -- @param mod_name [required] the name of the mod to create the logger for 31 | -- @param log_name (optional, default: 'main') the name of the logger 32 | -- @param debug_mode (optional, default: false) toggles the debug state of logger. 33 | -- @param options (optional) table with optional arguments 34 | -- @return the logger instance 35 | function Logger.new(mod_name, log_name, debug_mode, options) 36 | if not mod_name then 37 | error("Logger must be given a mod_name as the first argument") 38 | end 39 | if not log_name then 40 | log_name = "main" 41 | end 42 | if not options then 43 | options = {} 44 | end 45 | local Logger = {mod_name = mod_name, log_name = log_name, debug_mode = debug_mode, buffer = {}, last_written = 0, ever_written = false} 46 | 47 | --- Logger options 48 | Logger.options = { 49 | log_ticks = options.log_ticks or false, -- whether to add the ticks in the timestamp, default false 50 | file_extension = options.file_extension or 'log', -- extension of the file, default: log 51 | force_append = options.force_append or false, -- append the file on first write, default: false 52 | } 53 | Logger.file_name = 'logs/' .. Logger.mod_name .. '/' .. Logger.log_name .. '.' .. Logger.options.file_extension 54 | Logger.ever_written = Logger.options.force_append 55 | 56 | --- Logs a message 57 | -- @param msg a string, the message to log 58 | -- @return true if the message was written, false if it was queued for a later write 59 | function Logger.log(msg) 60 | local format = string.format 61 | if _G.game then 62 | local tick = game.tick 63 | local floor = math.floor 64 | local time_s = floor(tick/60) 65 | local time_minutes = floor(time_s/60) 66 | local time_hours = floor(time_minutes/60) 67 | 68 | if Logger.options.log_ticks then 69 | table.insert(Logger.buffer, format("%02d:%02d:%02d.%02d: %s\n", time_hours, time_minutes % 60, time_s % 60, tick - time_s*60, msg)) 70 | else 71 | table.insert(Logger.buffer, format("%02d:%02d:%02d: %s\n", time_hours, time_minutes % 60, time_s % 60, msg)) 72 | end 73 | 74 | -- write the log every minute 75 | if (Logger.debug_mode or (tick - Logger.last_written) > 3600) then 76 | return Logger.write() 77 | end 78 | else 79 | table.insert(Logger.buffer, format("00:00:00: %s\n", msg)) 80 | end 81 | return false 82 | end 83 | 84 | --- Writes out all buffered messages immediately 85 | -- @return true if there any messages were written, false if not 86 | function Logger.write() 87 | if _G.game then 88 | Logger.last_written = game.tick 89 | helpers.write_file(Logger.file_name, table.concat(Logger.buffer), Logger.ever_written) 90 | Logger.buffer = {} 91 | Logger.ever_written = true 92 | return true 93 | end 94 | return false 95 | end 96 | 97 | return Logger 98 | end 99 | 100 | return Logger 101 | -------------------------------------------------------------------------------- /stdlib/string.lua: -------------------------------------------------------------------------------- 1 | --- String module 2 | -- @module string 3 | 4 | --- Returns a copy of the string with any leading or trailing whitespace from the string removed. 5 | -- @param s the string to remove leading or trailing whitespace from 6 | -- @return a copy of the string without leading or trailing whitespace 7 | function string.trim(s) 8 | return (s:gsub("^%s*(.-)%s*$", "%1")) 9 | end 10 | 11 | --- Tests if a string starts with a given substring 12 | -- @param s the string to check for the start substring 13 | -- @param start the substring to test for 14 | -- @return true if the start substring was found in the string 15 | function string.starts_with(s, start) 16 | return string.find(s, start, 1, true) == 1 17 | end 18 | 19 | --- Tests if a string ends with a given substring 20 | -- @param s the string to check for the end substring 21 | -- @param ends the substring to test for 22 | -- @return true if the end substring was found in the string 23 | function string.ends_with(s, ends) 24 | return #s >= #ends and string.find(s, ends, #s - #ends + 1, true) and true or false 25 | end 26 | 27 | --- Tests if a string contains a given substring 28 | -- @param s the string to check for the substring 29 | -- @param ends the substring to test for 30 | -- @return true if the substring was found in the string 31 | function string.contains(s, ends) 32 | return s and string.find(s, ends) ~= nil 33 | end 34 | 35 | --- Tests whether a string is empty 36 | -- @param s the string to test 37 | function string.is_empty(s) 38 | return s == nil or s == '' 39 | end 40 | 41 | --- Splits a string into a table 42 | --

43 | -- Note: Empty split substrings are not included in the resulting table. 44 | -- For example, string.split('foo.bar...', '.', false) results in the table {'foo', 'bar'} 45 | -- @param s the string to split 46 | -- @param sep (optional) the separator to use. The period character, `.`, is the default separator. 47 | -- @param pattern whether to interpret the separator as a lua pattern or plaintext for the string split 48 | -- @return the table 49 | function string.split(s, sep, pattern) 50 | local sep, fields = sep or ".", {} 51 | sep = sep ~= "" and sep or "." 52 | sep = not pattern and string.gsub(sep, "([^%w])", "%%%1") or sep 53 | 54 | local fields = {} 55 | local start_idx, end_idx = string.find(s, sep) 56 | local last_find = 1 57 | local len = string.len(sep) 58 | while start_idx do 59 | local substr = string.sub(s, last_find, start_idx - 1) 60 | if string.len(substr) > 0 then 61 | table.insert(fields, string.sub(s, last_find, start_idx - 1)) 62 | end 63 | last_find = end_idx + 1 64 | start_idx, end_idx = string.find(s, sep, end_idx + 1) 65 | end 66 | local substr = string.sub(s, last_find) 67 | if string.len(substr) > 0 then 68 | table.insert(fields, string.sub(s, last_find)) 69 | end 70 | return fields 71 | end 72 | -------------------------------------------------------------------------------- /stdlib/surface.lua: -------------------------------------------------------------------------------- 1 | --- Surface module 2 | -- @module Surface 3 | 4 | require 'stdlib/core' 5 | require 'stdlib/area/area' 6 | 7 | Surface = {} 8 | 9 | --- Flexible, safe lookup function for surfaces.

10 | -- May be given a string, the name of a surface, or may be given a table with surface names, 11 | -- may be given the a surface object, or may be given a table of surface objects, or 12 | -- may be given nil.

13 | -- Returns an array of surface objects of all valid, existing surfaces 14 | -- If a surface does not exist for the surface, it is ignored, if no surfaces 15 | -- are given, an empty array is returned. 16 | -- @param surface to lookup 17 | -- @treturn LuaSurface[] the list of surfaces looked up 18 | function Surface.lookup(surface) 19 | if not surface then 20 | return {} 21 | end 22 | if type(surface) == 'string' then 23 | if game.surfaces[surface] then 24 | return {game.surfaces[surface]} 25 | end 26 | return {} 27 | end 28 | local result = {} 29 | for _, surface_item in pairs(surface) do 30 | if type(surface_item) == 'string' then 31 | if game.surfaces[surface_item] then 32 | table.insert(result, game.surfaces[surface_item]) 33 | end 34 | elseif type(surface_item) == 'table' and surface_item['__self'] then 35 | table.insert(result, surface_item) 36 | end 37 | end 38 | return result 39 | end 40 | 41 | --- Given search criteria, a table that contains a name or type of entity to search for, 42 | -- and optionally surface or force, searches all loaded chunks for the entities that 43 | -- match the critera. 44 | -- @usage 45 | ----Surface.find_all_entities({ type = 'unit', surface = 'nauvis', area = {{-1000,20},{-153,2214}}) --returns a list containing all unit entities on the nauvis surface in the given area 46 | -- @tparam table search_criteria a table of criteria. Must contain either the *name* or *type* or *force* of an entity. May contain *surface* or *force* or *area*. 47 | -- @treturn table an array of all entities that matched the criteria 48 | function Surface.find_all_entities(search_criteria) 49 | fail_if_missing(search_criteria, "missing search_criteria argument") 50 | if search_criteria.name == nil and search_criteria.type == nil and search_criteria.force == nil and search_criteria.area == nil then 51 | error("Missing search criteria field: name or type or force or area of entity", 2) 52 | end 53 | 54 | local surface_list = Surface.lookup(search_criteria.surface) 55 | if search_criteria.surface == nil then 56 | surface_list = game.surfaces 57 | end 58 | 59 | local result = {} 60 | 61 | for _, surface in pairs(surface_list) do 62 | local entities = surface.find_entities_filtered( 63 | { 64 | area = search_criteria.area, 65 | name = search_criteria.name, 66 | type = search_criteria.type, 67 | force = search_criteria.force 68 | }) 69 | for _, entity in pairs(entities) do 70 | table.insert(result, entity) 71 | end 72 | end 73 | 74 | return result 75 | end 76 | 77 | --- determine surface extension 78 | -- returns Area covering entire extension of this surface 79 | -- useful, if you compare total number of chunks with number of chunks of this area 80 | -- @tparam LuaSurface surface 81 | -- @treturn table Area 82 | function Surface.get_surface_bounds(surface) 83 | fail_if_missing(surface, "missing surface value") 84 | local x1, y1, x2, y2 = 0, 0, 0, 0 85 | 86 | for chunk in surface.get_chunks() do 87 | if chunk.x < x1 then 88 | x1 = chunk.x 89 | elseif chunk.x > x2 then 90 | x2 = chunk.x 91 | end 92 | if chunk.y < y1 then 93 | y1 = chunk.y 94 | elseif chunk.y > y2 then 95 | y2 = chunk.y 96 | end 97 | end 98 | 99 | return Area.construct(x1*32, y1*32, x2*32, y2*32) 100 | end 101 | 102 | 103 | return Surface 104 | -------------------------------------------------------------------------------- /stdlib/time.lua: -------------------------------------------------------------------------------- 1 | --- Time module 2 | -- @module Time 3 | 4 | Time = {} 5 | 6 | --- @field the number of factorio ticks in a second 7 | Time.SECOND = 60 8 | 9 | --- @field the number of factorio ticks in a minute 10 | Time.MINUTE = Time.SECOND * 60 11 | 12 | --- @field the number of factorio ticks in an hour 13 | Time.HOUR = Time.MINUTE * 60 14 | 15 | --- @field the number of factorio ticks in a day 16 | Time.DAY = Time.MINUTE * 60 17 | 18 | --- @field the number of factorio ticks in a week 19 | Time.WEEK = Time.DAY * 7 20 | -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyranf/robotarmyfactorio/e9175f20e1731e3805eb89859a3d0c296b77affd/thumbnail.png --------------------------------------------------------------------------------