├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── action.cpp ├── action.hpp ├── area.cpp ├── area.hpp ├── clock.hpp ├── constants.h ├── defines.cpp ├── defines.h ├── doc ├── factorio_comm.md └── scheduler.md ├── entity.cpp ├── entity.h ├── eval └── viewports │ ├── README.md │ ├── dumb_O2.patch │ ├── dyn_O2.patch │ ├── dyn_noopt.patch │ ├── norm_O2.patch │ ├── norm_noopt.patch │ └── output1.txt.bz2 ├── factorio_io.cpp ├── factorio_io.h ├── filter.sh ├── goal.cpp ├── goal.hpp ├── graphics_definitions.h ├── gui ├── gui.cpp └── gui.h ├── inventory.cpp ├── inventory.hpp ├── item.h ├── item_storage.h ├── launch.sh ├── logging.cpp ├── logging.hpp ├── luamod ├── Windfisch_0.0.1 │ ├── control.lua │ ├── data-final-fixes.lua │ ├── data.lua │ ├── info.json │ └── prototypes │ │ └── .emptydir └── server-settings.json ├── main.cpp ├── mine_planning.cpp ├── mine_planning.h ├── mk_compile_commands_json.bash ├── multivariant.hpp ├── pathfinding.cpp ├── pathfinding.hpp ├── player.h ├── pos.hpp ├── prepare.sh ├── rcon-client.cpp ├── rcon.cpp ├── rcon.h ├── recipe.h ├── resource.hpp ├── safe_cast.hpp ├── sched.txt ├── scheduler.cpp ├── scheduler.hpp ├── screenshots ├── overview.png └── zoomed.png ├── split.hpp ├── test ├── .gitignore ├── claim.cpp ├── scheduler.cpp ├── scheduler.run.desired ├── worldlist.cpp └── worldlist.run.desired ├── unused.h ├── util.hpp ├── worldlist.hpp └── worldmap.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | .dummy.mk 2 | depend 3 | *.d 4 | *.o 5 | core 6 | gmon.out 7 | rcon-client 8 | botconfig.conf 9 | *.swp 10 | config.mk 11 | bot 12 | compile_commands.json 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | EXE=bot 4 | COMMONOBJECTS=factorio_io.o rcon.o area.o pathfinding.o defines.o action.o mine_planning.o inventory.o scheduler.o goal.o entity.o gui/gui.o logging.o # objects used for $(EXE) 5 | 6 | ALLTESTS=test/worldlist test/scheduler 7 | 8 | # all objects, including those for other targets (i.e. rcon-client) 9 | ALLOBJECTS=$(COMMONOBJECTS) main.o rcon-client.o $(addsuffix .o,$(ALLTESTS)) 10 | DEBUG=1 11 | 12 | 13 | MODNAME=Windfisch_0.0.1 14 | MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes/.emptydir) 15 | 16 | 17 | 18 | DEBUGFLAGS = -O2 -g -D_GLIBCXX_DEBUG -fsanitize=undefined,address -fno-omit-frame-pointer 19 | FASTFLAGS = -O2 -g 20 | CXXFLAGS_BASE = -std=c++17 21 | CFLAGS_BASE = -std=c99 22 | 23 | GUIFLAGS = -O2 24 | 25 | COMPILER ?= GCC 26 | ifeq ($(COMPILER),GCC) 27 | CC=gcc 28 | CXX=g++ 29 | WARNFLAGS=-Wall -Wextra -pedantic 30 | WARNFLAGS+=-Werror=return-type -Werror=implicit-fallthrough 31 | GUIFLAGS += 32 | else 33 | CC=clang 34 | CXX=clang++ 35 | WARNFLAGS=-Weverything -pedantic -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-c++98-c++11-compat -Wno-sign-conversion -Wno-sign-compare -Wno-padded -Wno-exit-time-destructors -Wno-global-constructors -Wno-weak-vtables -Wno-switch-enum -Wswitch -Wno-double-promotion -Wno-documentation-unknown-command -Wno-covered-switch-default 36 | WARNFLAGS+=-Werror=return-type -Werror=header-hygiene -Werror=implicit-fallthrough 37 | # -Wswitch-enum complains, even if there's a default: label. -Wswitch does not. 38 | GUIFLAGS += 39 | endif 40 | 41 | 42 | DEBUG ?= 1 43 | ifeq ($(DEBUG),1) 44 | FLAGS += $(DEBUGFLAGS) 45 | else 46 | FLAGS += $(FASTFLAGS) 47 | endif 48 | 49 | FLAGS += $(WARNFLAGS) 50 | CFLAGS = $(shell fltk-config --cflags --use-images) $(CFLAGS_BASE) $(FLAGS) 51 | CXXFLAGS = $(shell fltk-config --cxxflags --use-images) $(CXXFLAGS_BASE) $(FLAGS) 52 | LINK=$(CXX) 53 | LINKFLAGS=$(CXXFLAGS) 54 | LIBS=$(shell fltk-config --ldflags --use-images) 55 | 56 | MODDESTS=$(MODSRCS:luamod/%=$(FACTORIODIR)/mods/%) 57 | 58 | # Pseudotargets 59 | .PHONY: all clean run info mod help test build_tests run_tests 60 | 61 | all: compile_commands.json $(EXE) rcon-client 62 | 63 | clean: 64 | rm -f $(EXE) $(ALLOBJECTS) $(ALLOBJECTS:.o=.d) 65 | distclean: clean 66 | rm -f depend compile_commands.json 67 | 68 | test: build_tests run_tests 69 | build_tests: $(ALLTESTS) 70 | run_tests: $(addsuffix .run,$(ALLTESTS)) 71 | 72 | test/%.run: test/% 73 | @./$< > $@.out 74 | @if ! diff -u $@.desired $@.out; then echo "TEST $@ FAILED, see above"; false; else echo "TEST $@ PASSED"; true; fi 75 | 76 | test/worldlist: $(COMMONOBJECTS) test/worldlist.o 77 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@ 78 | 79 | test/scheduler: $(COMMONOBJECTS) test/scheduler.o scheduler.o 80 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@ 81 | 82 | 83 | help: 84 | @echo "Targets:" 85 | @echo " all -> build the program" 86 | @echo " run -> run the program, using the datafile in the factorio installation (see Configuration below)" 87 | @echo " Note: this will NOT install the mod or (re)create the datafile. Do this manually by" 88 | @echo " running 'make datafile' before." 89 | @echo " mod -> install the lua mod into the factorio installation (see Configuration below)" 90 | @echo " datafile -> update the lua mod and re-create its output datafile" 91 | @echo " clean -> all generated files (including depend. except config.mk)" 92 | @echo " info -> show an overview of the CFLAGS used" 93 | @echo " help -> show this help" 94 | @echo 95 | @echo "Configuration:" 96 | @echo " Edit the 'config.mk' file and adjust the factorio path." 97 | @echo " This path will be used by the 'run', 'mod' and 'outfile' targets" 98 | 99 | 100 | info: 101 | @echo DEBUGFLAGS = $(DEBUGFLAGS) 102 | @echo FASTFLAGS = $(FASTFLAGS) 103 | @echo DEBUG = $(DEBUG) 104 | @echo CXXFLAGS_BASE = $(CXXFLAGS_BASE) 105 | @echo CXXFLAGS = $(CXXFLAGS) 106 | @echo LDFLAGS= $(LDFLAGS) 107 | 108 | run: $(EXE) 109 | ./$(EXE) 110 | 111 | mod: $(MODDESTS) 112 | 113 | DATAFILE=$(FACTORIODIR)/script-output/output1.txt 114 | SAVEGAME=$(FACTORIODIR)/factorio-bot.zip 115 | SERVERSETTINGS=luamod/server-settings.json 116 | 117 | datafile: $(DATAFILE) 118 | echo 'made target $(DATAFILE)' 119 | 120 | rm_datafile: 121 | rm -f $(DATAFILE) 122 | savegame: $(SAVEGAME) 123 | 124 | $(SAVEGAME): 125 | $(FACTORIODIR)/bin/x64/factorio --create $@ 126 | 127 | .PRECIOUS: $(DATAFILE) 128 | 129 | $(DATAFILE): $(MODDESTS) $(SAVEGAME) $(SERVERSETTINGS) 130 | rm -f $(DATAFILE) 131 | cp $(SAVEGAME) $(SAVEGAME:.zip=.tmp.zip) 132 | ( sleep 5; ./rcon-client localhost 1234 trivial_password '/c remote.call("windfisch","whoami","server")'; sleep 1; ./rcon-client localhost 1234 trivial_password '/c remote.call("windfisch","whoami","server")' ) & 133 | -$(FACTORIODIR)/bin/x64/factorio --start-server $(SAVEGAME:.zip=.tmp.zip) --server-settings $(SERVERSETTINGS) --rcon-port 1234 --rcon-password trivial_password 134 | echo 135 | echo made $(DATAFILE) 136 | 137 | # Build system 138 | 139 | .PRECIOUS: config.mk 140 | config.mk: 141 | echo 'FACTORIODIR=/path/to/factorio' > $@ 142 | echo 'COMPILER=GCC' >> $@ 143 | echo '# possible values: GCC, clang' >> $@ 144 | 145 | $(FACTORIODIR)/mods/$(MODNAME)/%: luamod/$(MODNAME)/% 146 | mkdir -p $(dir $@) 147 | cp -a $< $@ 148 | 149 | 150 | 151 | include depend 152 | 153 | depend: $(ALLOBJECTS:.o=.d) 154 | cat $^ > $@ 155 | 156 | %.d: %.cpp 157 | $(CXX) $(CXXFLAGS) -MM -MT $(@:.d=.o) $< -o $@ 158 | 159 | gui/%.d: gui/%.cpp 160 | $(CXX) $(CXXFLAGS) -MM -MT $(@:.d=.o) $(GUIFLAGS) $< -o $@ 161 | 162 | gui/%.o: gui/%.cpp 163 | $(CXX) $(CXXFLAGS) $(GUIFLAGS) -c $< -o $@ 164 | 165 | %.o: %.cpp 166 | $(CXX) $(CXXFLAGS) -c $< -o $@ 167 | 168 | 169 | $(EXE): $(COMMONOBJECTS) main.o 170 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@ 171 | rcon-client: rcon-client.o rcon.o logging.o 172 | $(LINK) $(LINKFLAGS) $(LDFLAGS) $^ $(LIBS) -o $@ 173 | 174 | compile_commands.json: 175 | bash mk_compile_commands_json.bash > $@ 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Factorio Bot 2 | ============ 3 | 4 | This project tries to be a **fair** bot/AI for the game 5 | [Factorio](https://www.factorio.com) (version 0.16.51). 6 | 7 | "Fair" means that the bot only uses information that is available to a human 8 | player. Better planning algorithms or map memory is fine, but being able to 9 | "see" yet-undiscovered parts of the map, or "looking through the fog of war" 10 | is not considered "fair". 11 | 12 | The project is in a **very early** stage, in fact, it's not much more than a 13 | proof-of-concept that this can actually work. 14 | 15 | Features right now: 16 | 17 | * we can read out the map / buildings on the map 18 | * most buildings are drawn, if you zoom in far enough 19 | * we can plan walking paths 20 | * we can actually walk :) 21 | * place entities 22 | * mine entities 23 | * craft recipes 24 | * inventory updates 25 | * action scheduler / task concept 26 | 27 | 28 | Screenshots 29 | ----------- 30 | 31 | ![map overview](screenshots/overview.png) 32 | 33 | ![zoomed in](screenshots/zoomed.png) 34 | 35 | 36 | Setup 37 | ------ 38 | 39 | Things are a bit fiddly right now. If you are on Linux, then there are various 40 | helper scripts for you: 41 | 42 | `./prepare.sh` will unpack three factorio installations (server and two 43 | clients) and install the lua-part of the bot. 44 | 45 | `./launch.sh --start/--stop/--run {server,Nayru,Farore,offline}` will start/stop the 46 | corresponding instance. `--run` will start, wait for `^C` and then stop. `offline` is 47 | special in that it launches a single-player game that can only be used together with 48 | `--bot-offline`. 49 | 50 | `./launch.sh --bot` will start the bot. You can also use `--bot-offline` for a 51 | read-only version that only operates on the data file, without the need of a 52 | running server instance. 53 | 54 | If you're not on Linux, or if stuff doesn't work, read the internals below. 55 | 56 | 57 | Usage 58 | ----- 59 | 60 | Once the bot is running, you will see a map. Scroll with drag-and-drop, zoom 61 | with mousewheel. 62 | 63 | You can plan paths by first left-clicking the starting point, then 64 | right-clicking the destination. Your character's location (which should 65 | be the starting point) is denoted as a red dot on the map. 66 | 67 | A path will show up in the map, and your Factorio player will start moving. 68 | 69 | The `p` button switches between visualisation of the ore types and the ore-patch 70 | ids. `s` gives info about the current scheduler state, `r` recalculates the 71 | scheduler, `t` and `y` insert a high- or low-priority wood chopping task, 72 | respectively. `i` gives additional info about the entity under the cursor. 73 | 74 | For a quick demo, press (in that order): 75 | 76 | - `r`: the bot will walk and start chopping wood in the center of the map 77 | - `t`, `r`: the bot will interrupt what it was doing and chop wood in the 78 | north west. (After finishing, it will return to the center chopping task) 79 | - `y`, `r`: the bot will not interrupt what it was doing and once finished, 80 | will start a low priority wood chopping task in the south east. 81 | 82 | Bugs / Limitations 83 | ------------------ 84 | 85 | Walking will appear really "jumpy" in the Factorio client which is being 86 | "remote-controlled". A second player, following the remote-controlled one, 87 | will not see anything jumpy. 88 | 89 | Path planning will hang for a long time if no path can be found. 90 | 91 | Building / Requirements 92 | ----------------------- 93 | 94 | The bot is written in C++, using the C++17 standard. Dependencies are 95 | [Boost](http://boost.org) and [FLTK](http://fltk.org)-1.3.4 for the GUI. (On Ubuntu, 96 | run: `apt-get install libboost-dev libfltk1.3-dev` to get them) 97 | 98 | The build system used is GNU Make. So just type `make all` and you should™ be 99 | done. 100 | 101 | This will create two executables: `rcon-client` with the obvious job, and 102 | `bot`, which is the bot program. 103 | 104 | Internals 105 | --------- 106 | 107 | How the communication between the bot and a factorio game instance works in detail 108 | is explained [in this document](doc/factorio_comm.md). 109 | 110 | Also, there's a detailed description of the [task scheduler](doc/scheduler.md). 111 | 112 | 113 | License 114 | ======= 115 | 116 | Copyright (c) 2017, 2018 Florian Jung (flo@windfisch.org) 117 | 118 | *factorio-bot* is free software: you can redistribute it and/or 119 | modify it under the terms of the **GNU General Public License, 120 | version 3**, as published by the Free Software Foundation. 121 | 122 | *factorio-bot* is distributed in the hope that it will be useful, 123 | but WITHOUT ANY WARRANTY; without even the implied warranty of 124 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 125 | GNU General Public License for more details. 126 | 127 | You should have received a [copy of the GNU General Public License](LICENSE) 128 | along with factorio-bot. If not, see . 129 | 130 | Exceptions 131 | ---------- 132 | 133 | Note that some portions of the codebase are -- where explicitly noted in the 134 | source files -- additionally licensed under the terms of the MIT license. 135 | 136 | 137 | 138 | See also 139 | ======== 140 | 141 | Related to this is my [production flow optimizer project](https://github.com/Windfisch/production-flow). 142 | -------------------------------------------------------------------------------- /action.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include "action.hpp" 20 | #include "pathfinding.hpp" 21 | #include "constants.h" 22 | #include 23 | #include 24 | #include "factorio_io.h" 25 | #include 26 | #include "logging.hpp" 27 | 28 | using namespace std; 29 | 30 | namespace action { 31 | Registry registry; 32 | 33 | void Registry::start_action(const std::shared_ptr& action) 34 | { 35 | Logger log("action_registry"); 36 | if (auto primitive_action = std::dynamic_pointer_cast(action)) 37 | { 38 | log << "inserting action #" << primitive_action->id << " into the registry" << endl; 39 | (*this)[primitive_action->id] = primitive_action; 40 | } 41 | action->start(); 42 | } 43 | 44 | void Registry::cleanup() 45 | { 46 | for (iterator iter = begin(); iter != end();) 47 | { 48 | if (iter->second.expired()) 49 | iter = erase(iter); 50 | else 51 | ++iter; 52 | } 53 | } 54 | 55 | shared_ptr Registry::get(int id) 56 | { 57 | auto iter = find(id); 58 | if (iter == end()) 59 | return nullptr; 60 | else 61 | return iter->second.lock(); 62 | } 63 | 64 | int PrimitiveAction::id_counter; 65 | 66 | void PrimitiveAction::start() 67 | { 68 | Logger log("action"); 69 | 70 | execute_impl(); 71 | item_balance_t negative_balance; 72 | item_balance_t balance = inventory_balance_on_launch(); 73 | std::copy_if (balance.begin(), balance.end(), std::inserter(negative_balance, negative_balance.end()), [](auto elem){return elem.second < 0;} ); 74 | 75 | game->players[player].inventory.apply(negative_balance, owner); 76 | log << "Launched action " << str() << ". Extrapolated inventory is now" << endl; 77 | game->players[player].inventory.dump(); 78 | } 79 | 80 | void WalkTo::start() 81 | { 82 | std::vector waypoints = a_star(game->players[player].position.to_int(), destination, game->walk_map, allowed_distance, min_distance); 83 | subactions.push_back(unique_ptr(new WalkWaypoints(game,player,nullopt, waypoints))); 84 | 85 | registry.start_action(subactions[0]); 86 | } 87 | 88 | void WalkWaypoints::execute_impl() 89 | { 90 | Logger log("verbose"); 91 | log << "WalkWaypoints from " << waypoints[0].str() << " to " << waypoints.back().str() << endl; 92 | game->set_waypoints(id, player, waypoints); 93 | } 94 | 95 | void MineObject::execute_impl() 96 | { 97 | game->set_mining_target(id, player, obj); 98 | } 99 | 100 | void MineObject::abort() 101 | { 102 | game->unset_mining_target(player); 103 | finished = true; 104 | } 105 | 106 | void CraftRecipe::execute_impl() 107 | { 108 | game->start_crafting(id, player, recipe->name, count); 109 | } 110 | 111 | void PlaceEntity::execute_impl() 112 | { 113 | game->place_entity(player, item->name, entity.pos, entity.direction); 114 | game->register_pending_entity(game->get_tick() + 60 /* FIXME MAGIC NUMBER */, entity); 115 | finished = true; 116 | } 117 | 118 | void PutToInventory::execute_impl() 119 | { 120 | game->insert_to_inventory(player, item->name, amount, entity, inventory_type); 121 | finished = true; 122 | } 123 | 124 | void TakeFromInventory::execute_impl() 125 | { 126 | game->remove_from_inventory(player, item->name, amount, entity, inventory_type); 127 | finished = true; 128 | } 129 | 130 | std::pair WalkTo::walk_result(Pos current_position) const 131 | { 132 | // TODO FIXME: if we've a detailed path, return that one 133 | return pair(destination.center(), chrono::milliseconds( int(1000*(current_position - destination.center()).len() / WALKING_SPEED) )); 134 | } 135 | 136 | std::pair WalkWaypoints::walk_result(Pos current_position) const 137 | { 138 | if (waypoints.empty()) 139 | return pair(current_position, Clock::duration(0)); 140 | 141 | float seconds = 0.f; 142 | for (size_t i = 0; i < waypoints.size() - 1; i++) 143 | { 144 | const Pos& from = waypoints[i]; 145 | const Pos& to = waypoints[i+1]; 146 | seconds += float((from-to).len() / WALKING_SPEED); 147 | } 148 | 149 | return pair(waypoints.back(), chrono::milliseconds(int(1000*seconds))); 150 | } 151 | 152 | item_balance_t MineObject::inventory_balance() const 153 | { 154 | return obj.proto->mine_results; 155 | } 156 | 157 | item_balance_t CompoundAction::inventory_balance() const 158 | { 159 | item_balance_t result; 160 | for (const auto& action : subactions) 161 | { 162 | for (const auto& [item,amount] : action->inventory_balance()) 163 | result[item] += amount; 164 | } 165 | return result; 166 | } 167 | 168 | item_balance_t CraftRecipe::inventory_balance_on_launch() const 169 | { 170 | item_balance_t result; 171 | for (const auto& ingredient : recipe->ingredients) 172 | result[ingredient.item] -= ingredient.amount; 173 | return result; 174 | } 175 | 176 | string CompoundAction::str() const 177 | { 178 | string result = "CompoundAction{"; 179 | for (size_t i=0; istr(); 184 | } 185 | result+="}"; 186 | return result; 187 | } 188 | 189 | string WalkTo::str() const 190 | { 191 | return "WalkTo("+destination.str()+"±"+to_string(allowed_distance)+")"; 192 | } 193 | 194 | string WalkWaypoints::str() const 195 | { 196 | return "WalkWaypoints(" + join_string(waypoints, [](Pos p) { return p.str(); }) + ")"; 197 | } 198 | 199 | string MineObject::str() const 200 | { 201 | return "MineObject(" + obj.str() + ")"; 202 | } 203 | 204 | string PlaceEntity::str() const 205 | { 206 | return "PlaceEntity(" + item->name + " -> " + entity.str() + ")"; 207 | } 208 | 209 | string PutToInventory::str() const 210 | { 211 | return "PutToInventory(" + to_string(amount) + "x " + item->name + " -> " + entity.str() + ")"; 212 | } 213 | 214 | string TakeFromInventory::str() const 215 | { 216 | return "TakeFromInventory(" + to_string(amount) + "x " + item->name + " <- " + entity.str() + ")"; 217 | } 218 | 219 | string CraftRecipe::str() const 220 | { 221 | return "CraftRecipe(" + to_string(count) + "x " + recipe->name + ")"; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /action.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "pos.hpp" 28 | #include "entity.h" 29 | #include "resource.hpp" 30 | #include "unused.h" 31 | #include "defines.h" 32 | #include "item.h" 33 | #include "clock.hpp" 34 | 35 | class FactorioGame; 36 | 37 | namespace action 38 | { 39 | struct PrimitiveAction; 40 | struct ActionBase; 41 | 42 | using action_id_t = int; 43 | 44 | struct Registry : public std::unordered_map> 45 | { 46 | void start_action(const std::shared_ptr& action); 47 | void cleanup(); 48 | std::shared_ptr get(action_id_t id); 49 | }; 50 | extern Registry registry; // FIXME this should be in FactorioGame 51 | 52 | struct ActionBase 53 | { 54 | friend struct Registry; 55 | 56 | virtual bool is_finished() const = 0; 57 | private: virtual void start() = 0; public: // FIXME ugly 58 | virtual void tick() = 0; 59 | virtual void abort() {} 60 | virtual void on_mined_item(std::string type, int count) { UNUSED(type); UNUSED(count); } 61 | virtual ~ActionBase() = default; 62 | virtual std::string str() const = 0; 63 | 64 | virtual std::optional first_pos() const { return std::nullopt; } 65 | 66 | /** returns the position of the player after having executed this action, and the time required 67 | * for doing so, under the assumption that we're at current_position now. */ 68 | virtual std::pair walk_result(Pos current_position) const = 0; 69 | 70 | /** Inventory balance after having fully executed this action. 71 | * returns a map of (item, balance) pairs, where a positive balance signifies 72 | * receiving this item, negative means consuming the item */ 73 | virtual item_balance_t inventory_balance() const = 0; 74 | 75 | /** Inventory balance when launching this action. E.g. for CraftAction, 76 | * this returns negative balance for the ingredients, but does not report 77 | * the products yet (because they won't be added immediately at launch) 78 | * 79 | * `tick` needs to be the timestamp of the inventory to extrapolate. */ 80 | virtual item_balance_t extrapolate_inventory(int tick) const = 0; 81 | }; 82 | 83 | struct CompoundAction : public ActionBase 84 | { 85 | std::vector< std::shared_ptr > subactions; 86 | size_t current_subaction = 0; 87 | 88 | CompoundAction() {} 89 | 90 | std::string str() const; 91 | 92 | std::optional first_pos() const 93 | { 94 | for (const auto& action : subactions) 95 | { 96 | if (std::optional pos = action->first_pos(); pos.has_value()) 97 | return pos; 98 | } 99 | return std::nullopt; 100 | } 101 | 102 | void tick() 103 | { 104 | if (!is_finished()) 105 | { 106 | subactions[current_subaction]->tick(); 107 | 108 | if (subactions[current_subaction]->is_finished()) 109 | { 110 | current_subaction++; 111 | 112 | if (!is_finished()) 113 | registry.start_action(subactions[current_subaction]); 114 | } 115 | } 116 | } 117 | 118 | bool is_finished() const 119 | { 120 | return subactions.size() == current_subaction; 121 | } 122 | 123 | private: void start() // FIXME ugly 124 | 125 | { 126 | if (!subactions.empty()) 127 | registry.start_action(subactions[0]); 128 | } 129 | public: 130 | 131 | // dispatch events 132 | void on_mined_item(std::string type, int count) 133 | { 134 | // FIXME shouldn't this be subaction[current_subaction] only? 135 | for (auto& subaction : subactions) 136 | subaction->on_mined_item(type, count); 137 | } 138 | 139 | std::pair walk_result(Pos current_position) const 140 | { 141 | Pos pos = current_position; 142 | Clock::duration sum(0); 143 | for (const auto& action : subactions) 144 | { 145 | auto [pos_after, duration] = action->walk_result(pos); 146 | assert(duration.count() >= 0); 147 | pos = pos_after; 148 | sum += duration; 149 | assert(sum.count() >= 0); 150 | } 151 | return std::pair(pos,sum); 152 | } 153 | 154 | item_balance_t inventory_balance() const; 155 | item_balance_t extrapolate_inventory(int tick) const 156 | { 157 | if (is_finished()) 158 | return {}; 159 | else 160 | return subactions[current_subaction]->extrapolate_inventory(tick); 161 | } 162 | }; 163 | 164 | struct WalkTo : public CompoundAction 165 | { 166 | Area_f destination; 167 | double allowed_distance; 168 | double min_distance; 169 | 170 | FactorioGame* game; 171 | int player; 172 | 173 | std::string str() const; 174 | 175 | std::optional first_pos() const 176 | { 177 | return destination.center(); 178 | } 179 | 180 | WalkTo(FactorioGame* game_, int player_, Area_f destination_, double allowed_distance_ = 1., double min_distance_ = 0.) { game = game_; player = player_; destination = destination_; allowed_distance = allowed_distance_; min_distance = min_distance_; } 181 | [[deprecated]] WalkTo(FactorioGame* game_, int player_, Pos destination_, double allowed_distance_ = 1.) : WalkTo(game_, player_, Area_f(destination_,destination_), allowed_distance_) {} 182 | private: void start(); public: // FIXME ugly 183 | 184 | 185 | std::pair walk_result(Pos current_position) const; // TODO FIXME implement 186 | }; 187 | 188 | // TODO: primitive action list 189 | 190 | struct PrimitiveAction : public ActionBase 191 | { 192 | int player; 193 | FactorioGame* game; 194 | std::optional owner; 195 | 196 | static action_id_t id_counter; 197 | bool finished = false; 198 | 199 | action_id_t id; 200 | PrimitiveAction(FactorioGame* game_, int player_, std::optional owner_) { game = game_; player = player_; id = id_counter++; owner = owner_;} 201 | 202 | bool is_finished() const 203 | { 204 | return finished; 205 | } 206 | 207 | private: void start(); public: // FIXME ugly 208 | 209 | 210 | void tick() {} 211 | 212 | int confirmed_tick = 0; // TODO FIXME set properly! 213 | 214 | virtual item_balance_t inventory_balance_on_launch() const = 0; 215 | 216 | item_balance_t extrapolate_inventory(int tick) const 217 | { 218 | if (confirmed_tick && tick >= confirmed_tick) 219 | return {}; 220 | else 221 | return inventory_balance_on_launch(); 222 | } 223 | 224 | private: virtual void execute_impl() = 0; 225 | }; 226 | 227 | struct WalkWaypoints : public PrimitiveAction 228 | { 229 | WalkWaypoints(FactorioGame* game, int player, std::optional owner, const std::vector& waypoints_) : PrimitiveAction(game,player,owner), waypoints(waypoints_) {} 230 | std::vector waypoints; 231 | 232 | std::string str() const; 233 | 234 | std::optional first_pos() const 235 | { 236 | if (waypoints.empty()) return std::nullopt; 237 | else return waypoints[0]; 238 | } 239 | 240 | item_balance_t inventory_balance() const { return {}; } 241 | item_balance_t inventory_balance_on_launch() const { return {}; } 242 | 243 | std::pair walk_result(Pos current_position) const; 244 | 245 | private: void execute_impl(); 246 | }; 247 | 248 | struct MineObject : public PrimitiveAction // TODO amount (default: 0, means "mine it until gone") 249 | { 250 | MineObject(FactorioGame* game, int player, std::optional owner, Entity obj_) : PrimitiveAction(game,player,owner), obj(obj_) {} 251 | Entity obj; 252 | 253 | std::string str() const; 254 | 255 | item_balance_t inventory_balance() const; 256 | item_balance_t inventory_balance_on_launch() const { return {}; } 257 | 258 | std::pair walk_result(Pos current_position) const 259 | { 260 | return std::pair(current_position, std::chrono::seconds(1)); 261 | } 262 | 263 | private: void execute_impl(); 264 | private: void abort(); 265 | }; 266 | 267 | struct PlaceEntity : public PrimitiveAction 268 | { 269 | PlaceEntity(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, const Entity& entity_) : PrimitiveAction(game,player,owner), entity(entity_), item(item_) {} 270 | [[deprecated]] PlaceEntity(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, Pos_f pos_, dir4_t direction_=NORTH) : PrimitiveAction(game,player,owner), entity(pos_, item_->place_result, direction_), item(item_) {} 271 | 272 | Entity entity; 273 | const ItemPrototype* item; 274 | 275 | std::string str() const; 276 | 277 | item_balance_t inventory_balance() const { return {{item, -1}}; } 278 | item_balance_t inventory_balance_on_launch() const { return inventory_balance(); } 279 | 280 | std::pair walk_result(Pos current_position) const 281 | { 282 | return std::pair(current_position, std::chrono::seconds(1)); 283 | } 284 | 285 | private: void execute_impl(); 286 | }; 287 | 288 | struct PutToInventory : public PrimitiveAction 289 | { 290 | const ItemPrototype* item; 291 | int amount; 292 | Entity entity; 293 | inventory_t inventory_type; 294 | 295 | std::string str() const; 296 | 297 | PutToInventory(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, int amount_, Entity entity_, inventory_t inventory_type_) : 298 | PrimitiveAction(game,player,owner), item(item_), amount(amount_), entity(entity_), inventory_type(inventory_type_) { } 299 | 300 | item_balance_t inventory_balance() const { return {{item, -amount}}; } 301 | item_balance_t inventory_balance_on_launch() const { return inventory_balance(); } 302 | 303 | std::pair walk_result(Pos current_position) const 304 | { 305 | return std::pair(current_position, std::chrono::seconds(1)); 306 | } 307 | private: void execute_impl(); 308 | }; 309 | 310 | struct TakeFromInventory : public PrimitiveAction 311 | { 312 | const ItemPrototype* item; 313 | int amount; 314 | Entity entity; 315 | inventory_t inventory_type; 316 | 317 | TakeFromInventory(FactorioGame* game, int player, std::optional owner, const ItemPrototype* item_, int amount_, Entity entity_, inventory_t inventory_type_) : 318 | PrimitiveAction(game,player,owner), item(item_), amount(amount_), entity(entity_), inventory_type(inventory_type_) { } 319 | 320 | std::string str() const; 321 | 322 | item_balance_t inventory_balance() const { return {{item, amount}}; } 323 | item_balance_t inventory_balance_on_launch() const { return inventory_balance(); } 324 | 325 | std::pair walk_result(Pos current_position) const 326 | { 327 | return std::pair(current_position, std::chrono::seconds(1)); 328 | } 329 | private: void execute_impl(); 330 | }; 331 | 332 | struct CraftRecipe : public PrimitiveAction 333 | { 334 | CraftRecipe(FactorioGame* game, int player, std::optional owner, const Recipe* recipe_, int count_=1) : PrimitiveAction(game,player,owner), recipe(recipe_), count(count_) {} 335 | 336 | const Recipe* recipe; 337 | int count; 338 | 339 | std::string str() const; 340 | 341 | item_balance_t inventory_balance() const { throw std::runtime_error("not implemented"); } 342 | item_balance_t inventory_balance_on_launch() const; 343 | 344 | std::pair walk_result(Pos) const 345 | { 346 | throw std::runtime_error("not implemented"); 347 | } 348 | private: void execute_impl(); 349 | }; 350 | } // namespace action 351 | -------------------------------------------------------------------------------- /area.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include "area.hpp" 22 | 23 | using std::string; 24 | using std::sscanf; 25 | using std::min; 26 | using std::abs; 27 | 28 | template <> 29 | Area_::Area_(string str) 30 | { 31 | sscanf(str.c_str(), "%i,%i;%i,%i", &left_top.x, &left_top.y, &right_bottom.x, &right_bottom.y); 32 | } 33 | 34 | template <> 35 | Area_::Area_(string str) 36 | { 37 | sscanf(str.c_str(), "%lf,%lf;%lf,%lf", &left_top.x, &left_top.y, &right_bottom.x, &right_bottom.y); 38 | } 39 | 40 | template 41 | string Area_::str() const 42 | { 43 | return left_top.str() + " -- " + right_bottom.str(); 44 | } 45 | 46 | double distance(Pos_f a, Area_f b) 47 | { 48 | if (b.contains(a)) return 0.; 49 | 50 | if (b.left_top.x <= a.x && a.x <= b.right_bottom.x) 51 | return min(abs(a.y - b.left_top.y), abs(a.y - b.right_bottom.y)); 52 | if (b.left_top.y <= a.y && a.y <= b.right_bottom.y) 53 | return min(abs(a.x - b.left_top.x), abs(a.x - b.right_bottom.x)); 54 | 55 | Pos_f right_top( b.right_bottom.x, b.left_top.y ); 56 | Pos_f left_bottom( b.left_top.x, b.right_bottom.y ); 57 | return min( 58 | min( (a-b.left_top).len(), (a-b.right_bottom).len() ), 59 | min( (a-right_top).len(), (a-left_bottom).len() )); 60 | } 61 | 62 | template struct Area_; 63 | template struct Area_; 64 | -------------------------------------------------------------------------------- /area.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "pos.hpp" 25 | #include "defines.h" 26 | #include 27 | #include 28 | 29 | template struct Area_ 30 | { 31 | Pos_ left_top; 32 | Pos_ right_bottom; 33 | 34 | Area_() {} 35 | Area_(T x1, T y1, T x2, T y2) : left_top(x1,y1), right_bottom(x2,y2) {} 36 | Area_(const Pos_& lt, const Pos_& rb) : left_top(lt), right_bottom(rb) {} 37 | Area_(const std::vector>& positions) 38 | { 39 | assert(!positions.empty()); 40 | // calculate the bounding box 41 | left_top = positions[0]; 42 | right_bottom = positions[0]; 43 | for (const auto& p : positions) 44 | { 45 | if (p.x < left_top.x) 46 | left_top.x = p.x; 47 | if (p.x >= right_bottom.x) 48 | right_bottom.x = p.x+1; // FIXME what to do with floats here :/? 49 | if (p.y < left_top.y) 50 | left_top.y = p.y; 51 | if (p.y >= right_bottom.y) 52 | right_bottom.y = p.y+1; 53 | } 54 | } 55 | Area_(std::string str); 56 | template Area_(const Area_& other) : left_top(other.left_top), right_bottom(other.right_bottom) {} 57 | 58 | std::string str() const; 59 | 60 | bool operator==(const Area_& other) const { return this->left_top==other.left_top && this->right_bottom==other.right_bottom; } 61 | 62 | bool empty() const { return !(left_top <= right_bottom); } 63 | 64 | void normalize() 65 | { 66 | if (left_top.x > right_bottom.x) 67 | std::swap(left_top.x, right_bottom.x); 68 | if (left_top.y > right_bottom.y) 69 | std::swap(left_top.y, right_bottom.y); 70 | } 71 | 72 | Pos_ center() const { return (left_top + right_bottom) / 2; } 73 | 74 | bool contains(const Pos_& p) const { return left_top <= p && p < right_bottom; } 75 | bool contains_x(T x) const { return left_top.x <= x && x < right_bottom.x; } 76 | bool contains_y(T y) const { return left_top.y <= y && y < right_bottom.y; } 77 | 78 | T diameter() const { return std::max(right_bottom.x - left_top.x, right_bottom.y - left_top.y); } 79 | T radius_around(Pos_ p) const 80 | { 81 | Pos_ rel_left_top = left_top - p; 82 | Pos_ rel_right_bottom = right_bottom - p; 83 | return std::max( std::max(rel_left_top.x, rel_left_top.y), std::max(rel_right_bottom.x, rel_right_bottom.y) ); 84 | } 85 | 86 | Pos_ size() const { return right_bottom - left_top; } 87 | 88 | template typename std::enable_if< std::is_floating_point::value, Area_ >::type 89 | /*Area_*/ outer() const 90 | { 91 | return Area_( Pos_(int(std::floor(left_top.x)), int(std::floor(left_top.y))), 92 | Pos_(int(std::ceil(right_bottom.x)), int(std::ceil(right_bottom.y))) ); 93 | } 94 | 95 | [[nodiscard]] Area_ rotate(dir4_t rot) const // assumes that the box is originally in NORTH orientation. i.e., passing rot=NORTH will do nothing, passing EAST will rotate 90deg clockwise etc 96 | { 97 | assert(rot == NORTH || rot == EAST || rot == SOUTH || rot == WEST); 98 | switch (rot) 99 | { 100 | case NORTH: return *this; 101 | case EAST: return Area_( Pos_(-right_bottom.y, left_top.x), Pos_(-left_top.y, right_bottom.x) ); 102 | case SOUTH: return Area_( Pos_(-right_bottom.x, -right_bottom.y), Pos_(-left_top.x, -left_top.y) ); 103 | case WEST: return Area_( Pos_(left_top.y, -right_bottom.x), Pos_(right_bottom.y, -left_top.x) ); 104 | } 105 | // can't be reached 106 | assert(false); 107 | } 108 | 109 | [[nodiscard]] Area_ shift(Pos_ offset) const { return Area_(left_top+offset, right_bottom+offset); } 110 | [[nodiscard]] Area_ expand(T radius) const { return Area_(left_top-Pos_(radius,radius), right_bottom+Pos_(radius,radius)); } 111 | 112 | template [[nodiscard]] typename std::enable_if< std::is_integral::value, Area_ >::type expand(Pos_ point) const 113 | { 114 | return expand(Area_(point, point+Pos_(1,1))); 115 | } 116 | [[nodiscard]] Area_ expand(Area_ other) const { return Area_( std::min(left_top.x, other.left_top.x), std::min(left_top.y, other.left_top.y), std::max(right_bottom.x, other.right_bottom.x), std::max(right_bottom.y, other.right_bottom.y) ); } 117 | [[nodiscard]] T radius() const { return std::max( std::max( left_top.x, left_top.y), std::max( right_bottom.x, right_bottom.y) ); } 118 | [[nodiscard]] Area_ intersect(Area_ other) const 119 | { 120 | return Area_( 121 | Pos_( 122 | std::max(this->left_top.x, other.left_top.x), 123 | std::max(this->left_top.y, other.left_top.y) 124 | ), 125 | Pos_( 126 | std::min(this->right_bottom.x, other.right_bottom.x), 127 | std::min(this->right_bottom.y, other.right_bottom.y) 128 | ) 129 | ); 130 | } 131 | [[nodiscard]] bool intersects(Area_ other) const { return !intersect(other).empty(); } 132 | }; 133 | 134 | typedef Area_ Area; 135 | typedef Area_ Area_f; 136 | 137 | template <> Area_::Area_(std::string str); 138 | template <> Area_::Area_(std::string str); 139 | extern template struct Area_; 140 | extern template struct Area_; 141 | 142 | double distance(Pos_f a, Area_f b); 143 | -------------------------------------------------------------------------------- /clock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | using Clock = std::chrono::steady_clock; 5 | -------------------------------------------------------------------------------- /constants.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | constexpr double WALKING_SPEED = 8.9021; // in tiles per second. 4 | // source: https://wiki.factorio.com/Talk:Exoskeleton#Movement_speed_experiments 5 | 6 | -------------------------------------------------------------------------------- /defines.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include "defines.h" 20 | #include 21 | 22 | using namespace std; 23 | 24 | string inventory_names[] = { 25 | "defines.inventory.fuel", 26 | "defines.inventory.burnt_result", 27 | "defines.inventory.chest", 28 | "defines.inventory.furnace_source", 29 | "defines.inventory.furnace_result", 30 | "defines.inventory.furnace_modules", 31 | "defines.inventory.player_quickbar", 32 | "defines.inventory.player_main", 33 | "defines.inventory.player_guns", 34 | "defines.inventory.player_ammo", 35 | "defines.inventory.player_armor", 36 | "defines.inventory.player_tools", 37 | "defines.inventory.player_vehicle", 38 | "defines.inventory.player_trash", 39 | "defines.inventory.god_quickbar", 40 | "defines.inventory.god_main", 41 | "defines.inventory.roboport_robot", 42 | "defines.inventory.roboport_material", 43 | "defines.inventory.robot_cargo", 44 | "defines.inventory.robot_repair", 45 | "defines.inventory.assembling_machine_input", 46 | "defines.inventory.assembling_machine_output", 47 | "defines.inventory.assembling_machine_modules", 48 | "defines.inventory.lab_input", 49 | "defines.inventory.lab_modules", 50 | "defines.inventory.mining_drill_modules", 51 | "defines.inventory.item_main", 52 | "defines.inventory.rocket_silo_rocket", 53 | "defines.inventory.rocket_silo_result", 54 | "defines.inventory.car_trunk", 55 | "defines.inventory.car_ammo", 56 | "defines.inventory.cargo_wagon", 57 | "defines.inventory.turret_ammo", 58 | "defines.inventory.beacon_modules" 59 | }; 60 | 61 | static inventory_flag_t IN = {true, false}; 62 | static inventory_flag_t OUT = {false, true}; 63 | static inventory_flag_t INOUT = {true, true}; 64 | static inventory_flag_t NONE = {false, false}; 65 | 66 | inventory_flag_t inventory_flags[] = { 67 | IN, //"defines.inventory.fuel", 68 | OUT, //"defines.inventory.burnt_result", 69 | INOUT, //"defines.inventory.chest", 70 | IN, //"defines.inventory.furnace_source", 71 | OUT, //"defines.inventory.furnace_result", 72 | NONE, //"defines.inventory.furnace_modules", 73 | NONE, //"defines.inventory.player_quickbar", 74 | NONE, //"defines.inventory.player_main", 75 | NONE, //"defines.inventory.player_guns", 76 | NONE, //"defines.inventory.player_ammo", 77 | NONE, //"defines.inventory.player_armor", 78 | NONE, //"defines.inventory.player_tools", 79 | NONE, //"defines.inventory.player_vehicle", 80 | NONE, //"defines.inventory.player_trash", 81 | NONE, //"defines.inventory.god_quickbar", 82 | NONE, //"defines.inventory.god_main", 83 | NONE, //"defines.inventory.roboport_robot", 84 | NONE, //"defines.inventory.roboport_material", 85 | NONE, //"defines.inventory.robot_cargo", 86 | NONE, //"defines.inventory.robot_repair", 87 | IN, //"defines.inventory.assembling_machine_input", 88 | OUT, //"defines.inventory.assembling_machine_output", 89 | NONE, //"defines.inventory.assembling_machine_modules", 90 | IN, //"defines.inventory.lab_input", 91 | NONE, //"defines.inventory.lab_modules", 92 | NONE, //"defines.inventory.mining_drill_modules", 93 | NONE, //"defines.inventory.item_main", 94 | NONE, //"defines.inventory.rocket_silo_rocket", 95 | NONE, //"defines.inventory.rocket_silo_result", 96 | INOUT, //"defines.inventory.car_trunk", 97 | INOUT, //"defines.inventory.car_ammo", 98 | INOUT, //"defines.inventory.cargo_wagon", 99 | IN, //"defines.inventory.turret_ammo", 100 | NONE, //"defines.inventory.beacon_modules" 101 | }; 102 | 103 | const unordered_map inventory_types { 104 | {"defines.inventory.fuel", INV_FUEL}, 105 | {"defines.inventory.burnt_result", INV_BURNT_RESULT}, 106 | {"defines.inventory.chest", INV_CHEST}, 107 | {"defines.inventory.furnace_source", INV_FURNACE_SOURCE}, 108 | {"defines.inventory.furnace_result", INV_FURNACE_RESULT}, 109 | {"defines.inventory.furnace_modules", INV_FURNACE_MODULES}, 110 | {"defines.inventory.player_quickbar", INV_PLAYER_QUICKBAR}, 111 | {"defines.inventory.player_main", INV_PLAYER_MAIN}, 112 | {"defines.inventory.player_guns", INV_PLAYER_GUNS}, 113 | {"defines.inventory.player_ammo", INV_PLAYER_AMMO}, 114 | {"defines.inventory.player_armor", INV_PLAYER_ARMOR}, 115 | {"defines.inventory.player_tools", INV_PLAYER_TOOLS}, 116 | {"defines.inventory.player_vehicle", INV_PLAYER_VEHICLE}, 117 | {"defines.inventory.player_trash", INV_PLAYER_TRASH}, 118 | {"defines.inventory.god_quickbar", INV_GOD_QUICKBAR}, 119 | {"defines.inventory.god_main", INV_GOD_MAIN}, 120 | {"defines.inventory.roboport_robot", INV_ROBOPORT_ROBOT}, 121 | {"defines.inventory.roboport_material", INV_ROBOPORT_MATERIAL}, 122 | {"defines.inventory.robot_cargo", INV_ROBOT_CARGO}, 123 | {"defines.inventory.robot_repair", INV_ROBOT_REPAIR}, 124 | {"defines.inventory.assembling_machine_input", INV_ASSEMBLING_MACHINE_INPUT}, 125 | {"defines.inventory.assembling_machine_output", INV_ASSEMBLING_MACHINE_OUTPUT}, 126 | {"defines.inventory.assembling_machine_modules", INV_ASSEMBLING_MACHINE_MODULES}, 127 | {"defines.inventory.lab_input", INV_LAB_INPUT}, 128 | {"defines.inventory.lab_modules", INV_LAB_MODULES}, 129 | {"defines.inventory.mining_drill_modules", INV_MINING_DRILL_MODULES}, 130 | {"defines.inventory.item_main", INV_ITEM_MAIN}, 131 | {"defines.inventory.rocket_silo_rocket", INV_ROCKET_SILO_ROCKET}, 132 | {"defines.inventory.rocket_silo_result", INV_ROCKET_SILO_RESULT}, 133 | {"defines.inventory.car_trunk", INV_CAR_TRUNK}, 134 | {"defines.inventory.car_ammo", INV_CAR_AMMO}, 135 | {"defines.inventory.cargo_wagon", INV_CARGO_WAGON}, 136 | {"defines.inventory.turret_ammo", INV_TURRET_AMMO}, 137 | {"defines.inventory.beacon_modules", INV_BEACON_MODULES} 138 | }; 139 | -------------------------------------------------------------------------------- /defines.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | 24 | enum dir4_t 25 | { 26 | NORTH=0, 27 | EAST, 28 | SOUTH, 29 | WEST, 30 | 31 | TOP=NORTH, 32 | RIGHT=EAST, 33 | BOTTOM=SOUTH, 34 | LEFT=WEST 35 | }; 36 | 37 | enum dir8_t 38 | { 39 | d8_NORTH=0, 40 | d8_NORTHEAST, 41 | d8_EAST, 42 | d8_SOUTHEAST, 43 | d8_SOUTH, 44 | d8_SOUTHWEST, 45 | d8_WEST, 46 | d8_NORTHWEST 47 | }; 48 | 49 | enum inventory_t 50 | { 51 | INV_MIN = 0, 52 | INV_FUEL = 0, 53 | INV_BURNT_RESULT, 54 | INV_CHEST, 55 | INV_FURNACE_SOURCE, 56 | INV_FURNACE_RESULT, 57 | INV_FURNACE_MODULES, 58 | INV_PLAYER_QUICKBAR, 59 | INV_PLAYER_MAIN, 60 | INV_PLAYER_GUNS, 61 | INV_PLAYER_AMMO, 62 | INV_PLAYER_ARMOR, 63 | INV_PLAYER_TOOLS, 64 | INV_PLAYER_VEHICLE, 65 | INV_PLAYER_TRASH, 66 | INV_GOD_QUICKBAR, 67 | INV_GOD_MAIN, 68 | INV_ROBOPORT_ROBOT, 69 | INV_ROBOPORT_MATERIAL, 70 | INV_ROBOT_CARGO, 71 | INV_ROBOT_REPAIR, 72 | INV_ASSEMBLING_MACHINE_INPUT, 73 | INV_ASSEMBLING_MACHINE_OUTPUT, 74 | INV_ASSEMBLING_MACHINE_MODULES, 75 | INV_LAB_INPUT, 76 | INV_LAB_MODULES, 77 | INV_MINING_DRILL_MODULES, 78 | INV_ITEM_MAIN, 79 | INV_ROCKET_SILO_ROCKET, 80 | INV_ROCKET_SILO_RESULT, 81 | INV_CAR_TRUNK, 82 | INV_CAR_AMMO, 83 | INV_CARGO_WAGON, 84 | INV_TURRET_AMMO, 85 | INV_BEACON_MODULES, 86 | INV_MAX 87 | }; 88 | 89 | struct inventory_flag_t 90 | { 91 | bool put:1; // we may put to the inventory 92 | bool take:1; // we may take from the inventory 93 | }; 94 | 95 | extern inventory_flag_t inventory_flags[]; 96 | 97 | extern std::string inventory_names[]; 98 | extern const std::unordered_map inventory_types; 99 | -------------------------------------------------------------------------------- /doc/factorio_comm.md: -------------------------------------------------------------------------------- 1 | Bridging between the Bot and Factorio 2 | ===================================== 3 | 4 | Interacting with Factorio isn't too easy. Factorio is [moddable](http://lua-api.factorio.com) 5 | with [Lua](https://www.lua.org) (good), but due to how it's working 6 | internally, a lot of I/O functions are disabled (bad). 7 | 8 | We're basically forced to do the following: 9 | 10 | * have the Lua mod write out all information into a file under 11 | `factorio/script-output/`, 12 | * parse this file, do things, be artifically intelligent, 13 | * and then establish a 14 | [RCON](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) 15 | connection to Factorio and issue our commands 16 | 17 | Additionally, gathering all map information is a quite CPU-intensive task, so 18 | we wish to do this only on the server (since the clients won't write out the 19 | datafile, there's no need for them to figure out what to write in the first 20 | place). This means that we have to do **different** behaviour on different 21 | Factorio nodes, which isn't intended in Factorio. 22 | 23 | The way we're doing this is: After starting of a server/client, we tell the 24 | game via RCON who has just joined. The nodes can then figure out whether it 25 | was themselves who just joined, and thus remember their own name. This 26 | finally enables us to writeout only on the server. 27 | 28 | The start up procedure is as follows: 29 | 30 | * start the server 31 | * the server will write "server" into its `script-output/players_connected.txt` 32 | * send `/c remote.call('windfisch','whoami','server')` via RCON 33 | * now the server knows that it is the server 34 | * start zero or more clients. the server will write "clientname" into 35 | `players_connected.txt`. Send `/c remote.call(...)` with the appropriate 36 | name. 37 | * start the bot (`./test`), telling it where the `script-output/output1.txt` 38 | file is. 39 | * the bot will read the file, show a GUI, and RCON your commands into the 40 | server. 41 | 42 | Note that the bot actually accepts the file **prefix**, i.e. 43 | `script-output/output`. (This is because, in a later version, we will rotate 44 | through multiple output files, in order to limit file size.) 45 | 46 | -------------------------------------------------------------------------------- /doc/scheduler.md: -------------------------------------------------------------------------------- 1 | The Scheduler 2 | ============= 3 | 4 | The scheduler is, together with the [goal manager](goal_manager.md), the core 5 | of the factorio-bot. It computes the order in which actions from the tasks 6 | are to be executed, manages the crafting queue and collects items as necessary. 7 | 8 | The scheduler is instanced *per player*, any inter-player-coordination must 9 | be implemented outside. 10 | 11 | Problem statement 12 | ----------------- 13 | 14 | In factorio, there are two separate threads of activity: The *crafting* thread 15 | and the *walking* thread. Any tasks that have to be performed usually require 16 | both: E.g., building a new iron mine not only requires the bot to *walk* to the 17 | location and actually place the entities on the ground (both of which happen in 18 | the walking thread), but also to *craft* or *collect* the required items first. 19 | Collecting them also occupies the walking thread, while crafting them would be 20 | handled in the *crafting thread*. 21 | 22 | **(P1)** For an efficient operation, crafting and walking operations should be 23 | parallelized as much as possible. For example, if a high-priority mine-building 24 | task must craft a lot of mines first, it would be wasted time if all other task 25 | would wait for it to complete. Instead, the bot could perform a lower-priority 26 | task that only requires *walking/building*, efficiently parallelizing with the 27 | crafting process. 28 | 29 | **(P2)** However, the lower-priority task should not (significantly) delay the 30 | high priority task, which could happen e.g. if its start location was on the 31 | opposite side of the map. 32 | 33 | **(P3)** Additionally, a lower-priority task which could well fit in the idle 34 | gap should be able to execute, even if it needs one tiny craft to succeed. In 35 | this case, the a lower-priority task should be able to perform its crafts 36 | *before* a high priority task, if it doens't delay it for too long. 37 | 38 | Concepts 39 | -------- 40 | 41 | The scheduler makes use of the following core concepts: 42 | 43 | - **Tasks**: A task consists of a list of *actions*, together with a *start* 44 | and *end location* and a list of *required items*. The scheduler must fulfill 45 | all requirements of the task, i.e. either collect or craft all required items, 46 | and move the player close enough to the task's start location. Every task has 47 | a *priority* attached to it. 48 | - **Tagged Inventory**/**claims**: Each item in the player's inventory can be 49 | *claimed* by a task, meaning that it's reserved for use solely by this task. 50 | A task may only use items it has claimed. **Unclaimed items** can not be used 51 | by a task, but can be claimed by any task. 52 | - **Crafting list**: Each task has a crafting list, which is initially empty 53 | and managed by the scheduler. For each required item of a task, the scheduler 54 | decides whether to claim it from unclaimed items in the inventory, to collect 55 | it or to craft it. In the latter case, it's added to the task's crafting list. 56 | - **Crafting order**: The scheduler keeps a list of which task may craft first. 57 | This is required to fulfill *(P3)*, allowing low-prio-but-short tasks to craft 58 | first. 59 | 60 | A task is either **runnable**, **eventually runnable** or **not runnable**. 61 | It's *runnable* iif it has all required items, and *eventually runnable* iif it 62 | will have all required items after all of its crafts, which will be eventually 63 | performed given the current crafting order. If it's not *eventually runnable*, 64 | a task is considered *not runnable*. 65 | 66 | Any *eventually runnable* task can be assigned a **crafting eta**. This is the 67 | time, given the current crafting order and assuming that all crafts in this 68 | crafting order will be performed, that it will take until this task becomes 69 | *runnable*. 70 | 71 | A task that is *not runnable* has **required items w.r.t. its crafting list**, 72 | which equates of the minimum item list that, after all of its crafts would be 73 | performed, can supply its *required items*. 74 | 75 | Implementation 76 | -------------- 77 | 78 | ### Task selection 79 | 80 | We start with an empty **timetable**. A timetable is **valid**, iif all tasks 81 | in it can start no later than a small grace time after their scheduled start, 82 | considering task durations and the time spent from walking from one task to 83 | another. A timetable is **executable**, iif the first task it contains can 84 | start right now (or very soon, at least). 85 | 86 | All pending tasks, ordered by decreasing priority, are considered. If the 87 | considered task is **runnable** or **eventually runnable** (after possibly 88 | assigning unclaimed items to it), we try to insert it, starting at its 89 | **crafting eta**, into the timetable. If otherwise the considered task is **not 90 | runnable** (even after assignin unclaimed items to it), we unassign any 91 | previously claimed items, and then calculate a **crafting list** and a 92 | corresponding **collector tour** through the map, collecting all *required 93 | items w.r.t. the task's crafting list*. We then try to **donate** all 94 | *unclaimed items* to the collector tour. The tour cannot be nonempty at this 95 | point (otherwise the task would have been eventually runnable in the first 96 | place). 97 | 98 | **Donating** items to a collector tour tries to shorten a tour as much as 99 | possible by removing waypoints. The items that would have been collected at 100 | these waypoints are instead taken from the unclaimed items in the inventory. 101 | However, as few as possible items are claimed from the inventory. An example: 102 | The collector tour would visit the chest at location A, and take 300 iron from 103 | it. Then, it would visit the chest at B, and take 100 more iron, and 200 copper 104 | from there. We donate 340 iron and 170 copper. This is not sufficient to remove 105 | the stop at B. However, it's sufficient to remove the stop at A. We therefore 106 | remove stop A, and claim 300 iron to the tasks that would have been served by 107 | this stop. Afterwards, we have 40 unclaimed iron and 170 unclaimed copper in 108 | the inventory. 109 | 110 | We prefer to keep a **buffer of items** in our inventory. If that buffer saves 111 | us from having to walk somewhere to fetch items, we use it. If we must walk 112 | there anyway, we do not use the buffer. **TODO**: *That's dumb. Better use the 113 | buffer as far as possible, but then refill it there. Idea: a collector tour 114 | attempts to ensure that N items of a type are there *after* these tasks have 115 | finished. Unclaimed items are instantly assigned to tasks, in order of their 116 | priority. After the collector task has finished, our buffers are full again.* 117 | 118 | #### FIXME: maybe that's the solution: 119 | Whenever the task scheduler runs, there are no claimed items (because there are 120 | no currently running tasks). 121 | 122 | When a task can run with what we have in our inventory (without creating debt), 123 | it may do so. If not, then for each item that would create debt, we collect it, 124 | with an aftermath of N items (not zero). I.e. we collect more than we need, to 125 | re-fill our buffer. (Also, for items that would suffice, we check whether 126 | re-filling them would cause nearly-zero cost; if so, we refill them.) 127 | 128 | We then try to add lower-priority tasks to the collector task, if they don't 129 | extend its original length by more than 10%. 130 | 131 | Then, existing inventory is claimed to the tasks depending on their actual 132 | priority. (The same is true for items that get collected.) 133 | Yet crafts are scheduled in their order of the grocery-store-sorter. 134 | 135 | Donations can still occur. As far as the inventory is concerned, they're 136 | just handled like collects, i.e. they're assigned in the order of task 137 | priorities. If they can save us from visiting a station, then we update the 138 | path accordingly. 139 | 140 | #### end fixme 141 | 142 | 143 | 144 | If the 145 | resulting timetable would be valid, the insertion is performed. If the 146 | timetable has become **executable**, its first task is returned. Otherwise, 147 | the next pending task is considered. 148 | 149 | It can happen that no task can be selected. 150 | 151 | -------------------------------------------------------------------------------- /entity.cpp: -------------------------------------------------------------------------------- 1 | #include "entity.h" 2 | 3 | double EntityPrototype::max_collision_box_size; 4 | -------------------------------------------------------------------------------- /entity.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | #include "area.hpp" 21 | #include "pos.hpp" 22 | #include "multivariant.hpp" 23 | #include "defines.h" 24 | #include "inventory.hpp" 25 | #include 26 | #include 27 | #include 28 | 29 | struct ContainerData 30 | { 31 | MultiInventory inventories; 32 | bool fuel_is_output = false; 33 | }; 34 | 35 | struct MachineData : public ContainerData {}; 36 | struct MiningDrillData : public ContainerData {}; 37 | 38 | using mvu = multivariant_utils>; 39 | 40 | struct ItemPrototype; 41 | 42 | struct EntityPrototype 43 | { 44 | static double max_collision_box_size; 45 | 46 | std::string name; 47 | std::string type; 48 | Area_f collision_box; 49 | bool collides_player; 50 | bool collides_object; 51 | bool mineable; 52 | std::vector< std::pair > mine_results_str; 53 | item_balance_t mine_results; // filled in later by resolve_item_references() 54 | size_t data_kind; 55 | 56 | EntityPrototype(const std::string& name_, const std::string& type_, const std::string& collision_str, const Area_f& collision_box_, bool mineable_, std::vector< std::pair > mine_results_str_) : 57 | name(name_), 58 | type(type_), 59 | collision_box(collision_box_), 60 | collides_player(collision_str.find('P') != std::string::npos), 61 | collides_object(collision_str.find('O') != std::string::npos), 62 | mineable(mineable_), 63 | mine_results_str(mine_results_str_), 64 | data_kind(mvu::invalid_index) 65 | { 66 | max_collision_box_size = std::max(max_collision_box_size, collision_box_.radius_around(Pos_f(0,0))); 67 | 68 | if (type_ == "container" || type_ == "logistic-container") 69 | data_kind = mvu::index(); 70 | else if (type_ == "mining-drill") 71 | data_kind = mvu::index(); 72 | else if (type_ == "assembling-machine") 73 | data_kind = mvu::index(); 74 | else if (type_ == "furnace") 75 | data_kind = mvu::index(); 76 | } 77 | }; 78 | 79 | struct Entity 80 | { 81 | Pos_f get_pos() const { return pos; } // WorldList wants to call this. 82 | Area_f get_extent() const { return collision_box(); } // WorldList wants to call this. 83 | static double get_max_extent() { return EntityPrototype::max_collision_box_size; } // same 84 | 85 | Pos_f pos; 86 | const EntityPrototype* proto = nullptr; 87 | dir4_t direction = NORTH; 88 | 89 | std::string str() const { return proto->name+"@"+pos.str(); } 90 | 91 | refcount_base* data_ptr; 92 | 93 | //bool operator==(const Entity& that) const { return this->pos==that.pos && this->proto==that.proto && this->direction==that.direction && this->data_ptr == that.data_ptr; } 94 | constexpr bool mostly_equals(const Entity& that) const { return this->pos==that.pos && this->proto==that.proto; } 95 | 96 | struct mostly_equals_comparator 97 | { 98 | constexpr bool operator()(const Entity &lhs, const Entity &rhs) const 99 | { 100 | return lhs.mostly_equals(rhs); 101 | } 102 | }; 103 | 104 | void takeover_data(Entity& that) 105 | { 106 | assert (this->proto == that.proto); 107 | 108 | release_data(); 109 | this->data_ptr = that.data_ptr; 110 | that.data_ptr = nullptr; 111 | } 112 | 113 | Area_f collision_box() const { return proto->collision_box.rotate(direction).shift(pos); } 114 | 115 | template T& data() 116 | { 117 | return *mvu::get(proto->data_kind, data_ptr); 118 | } 119 | template T* data_or_null() 120 | { 121 | return mvu::get_or_null(proto->data_kind, data_ptr); 122 | } 123 | template const T& data() const 124 | { 125 | return *mvu::get(proto->data_kind, data_ptr); 126 | } 127 | template const T* data_or_null() const 128 | { 129 | return mvu::get_or_null(proto->data_kind, data_ptr); 130 | } 131 | 132 | void make_unique() 133 | { 134 | data_ptr = mvu::clone(proto->data_kind, data_ptr); 135 | } 136 | 137 | ~Entity() 138 | { 139 | release_data(); 140 | } 141 | 142 | 143 | struct nullent_tag {}; 144 | 145 | Entity(nullent_tag, const Pos_f& pos_ = Pos_f()) : pos(pos_), proto(nullptr), data_ptr(nullptr) 146 | { 147 | } 148 | 149 | Entity(const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH) 150 | : pos(pos_), proto(proto_), direction(direction_), data_ptr(mvu::make(proto_->data_kind)) 151 | { 152 | if (data_ptr) 153 | data_ptr->refcount = 1; 154 | } 155 | 156 | Entity(const Entity& e) 157 | { 158 | pos = e.pos; 159 | proto = e.proto; 160 | direction = e.direction; 161 | data_ptr = e.data_ptr; 162 | if (data_ptr) 163 | data_ptr->refcount++; 164 | } 165 | 166 | Entity(Entity&& e) noexcept 167 | { 168 | pos = std::move(e.pos); 169 | proto = std::move(e.proto); 170 | direction = std::move(e.direction); 171 | data_ptr = e.data_ptr; 172 | e.data_ptr = nullptr; 173 | } 174 | 175 | Entity& operator=(const Entity& e) 176 | { 177 | release_data(); 178 | 179 | pos = e.pos; 180 | proto = e.proto; 181 | direction = e.direction; 182 | data_ptr = e.data_ptr; 183 | if (data_ptr) 184 | data_ptr->refcount++; 185 | 186 | return *this; 187 | } 188 | Entity& operator=(Entity&& e) 189 | { 190 | release_data(); 191 | 192 | pos = std::move(e.pos); 193 | proto = std::move(e.proto); 194 | direction = std::move(e.direction); 195 | data_ptr = e.data_ptr; 196 | e.data_ptr = nullptr; 197 | 198 | return *this; 199 | } 200 | 201 | private: 202 | void release_data() 203 | { 204 | if (data_ptr) 205 | { 206 | data_ptr->refcount--; 207 | if (data_ptr->refcount == 0) 208 | mvu::del(proto->data_kind, data_ptr); 209 | } 210 | } 211 | }; 212 | 213 | struct DesiredEntity : public Entity 214 | { 215 | std::weak_ptr corresponding_actual_entity; 216 | std::shared_ptr get_actual(); 217 | 218 | DesiredEntity(const Entity& ent) : Entity(ent) {} 219 | DesiredEntity(const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH) 220 | : Entity(pos_, proto_, direction_) {} 221 | }; 222 | 223 | struct PlannedEntity : public DesiredEntity 224 | { 225 | int level; // at which facility-level to place this 226 | 227 | PlannedEntity(int level_, const Entity& ent) : DesiredEntity(ent), level(level_) {} 228 | PlannedEntity(int level_, const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH) 229 | : DesiredEntity(pos_, proto_, direction_), level(level_) {} 230 | PlannedEntity(const Pos_f& pos_, const EntityPrototype* proto_, dir4_t direction_=NORTH) 231 | : DesiredEntity(pos_, proto_, direction_), level(-1) {} 232 | }; 233 | 234 | template Area_f bounding_box(const container_type& container) 235 | { 236 | bool first = true; 237 | Area_f result; 238 | for (const auto& thing : container) 239 | { 240 | if (first) result = Area_f(thing.pos, thing.pos); 241 | else result = result.expand(thing.pos); 242 | first=false; 243 | } 244 | return result; 245 | } 246 | -------------------------------------------------------------------------------- /eval/viewports/README.md: -------------------------------------------------------------------------------- 1 | Viewport Evaluation 2 | =================== 3 | 4 | Question 5 | -------- 6 | 7 | is `WorldMap::Viewport` worth the effort, or is `WorldMap::DumbViewport` 8 | good enough? 9 | 10 | 11 | Experiment 12 | ---------- 13 | 14 | Try to find a path, measure time. Try dumb and normal viewport with and 15 | without compiler optimisation. 16 | 17 | Tested with `./launch.sh --bot-offline` with `output1.txt` 18 | 19 | 20 | Results 21 | ------- 22 | 23 | based on 661e1e38d24f 24 | 25 | - Dumb + no optimisation = no patch: 17715ms 26 | - Normal + no opt = `norm_noopt.patch`: 10220ms 27 | - Dynamic + no opt = `dyn_noopt.patch`: 9932ms 28 | 29 | - Dumb + `-O2` = `dumb_o2.patch`: 1652ms 30 | - Normal + `-O2` = `norm_O2.patch`: 491ms 31 | - Dynamic + `-O2` = `dyn_O2.patch`: 489ms 32 | 33 | 34 | Conclusion 35 | ---------- 36 | 37 | `Viewport` is worth the effort, and growing the viewport dynamically, 38 | on-demand is equally fast `:)` 39 | -------------------------------------------------------------------------------- /eval/viewports/dumb_O2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index 6ff1b97..3a8a4b6 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -13,7 +13,7 @@ MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes 6 | 7 | 8 | 9 | -DEBUGFLAGS = -g -D_GLIBCXX_DEBUG #-fsanitize=undefined,address 10 | +DEBUGFLAGS = -g -O2 -D_GLIBCXX_DEBUG #-fsanitize=undefined,address 11 | FASTFLAGS = -O2 12 | CXXFLAGS_BASE = -std=c++14 13 | CFLAGS_BASE = -std=c99 14 | -------------------------------------------------------------------------------- /eval/viewports/dyn_O2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index 6ff1b97..3a8a4b6 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -13,7 +13,7 @@ MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes 6 | 7 | 8 | 9 | -DEBUGFLAGS = -g -D_GLIBCXX_DEBUG #-fsanitize=undefined,address 10 | +DEBUGFLAGS = -g -O2 -D_GLIBCXX_DEBUG #-fsanitize=undefined,address 11 | FASTFLAGS = -O2 12 | CXXFLAGS_BASE = -std=c++14 13 | CFLAGS_BASE = -std=c99 14 | diff --git a/factorio_io.cpp b/factorio_io.cpp 15 | index ab13378..40c8840 100644 16 | --- a/factorio_io.cpp 17 | +++ b/factorio_io.cpp 18 | @@ -447,7 +447,7 @@ struct ResourcePatch 19 | } 20 | }; 21 | 22 | -void FactorioGame::floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius) 23 | +void FactorioGame::floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius) 24 | { 25 | int id = next_free_resource_id++; 26 | 27 | diff --git a/factorio_io.h b/factorio_io.h 28 | index cfe4e75..0780b6f 100644 29 | --- a/factorio_io.h 30 | +++ b/factorio_io.h 31 | @@ -106,7 +106,7 @@ class FactorioGame 32 | void parse_players(const std::string& data); 33 | void parse_objects(const Area& area, const std::string& data); 34 | 35 | - void floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius); 36 | + void floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius); 37 | int next_free_resource_id = 1; 38 | 39 | public: 40 | diff --git a/pathfinding.cpp b/pathfinding.cpp 41 | index a993100..0d55e9d 100644 42 | --- a/pathfinding.cpp 43 | +++ b/pathfinding.cpp 44 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal) 45 | 46 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size) 47 | { 48 | - auto view = map.dumb_view(Pos(0,0)); 49 | + auto view = map.view(Pos(-584,-274), Pos(306,302), Pos(0,0)); 50 | 51 | assert(size<=1.); 52 | vector result; 53 | diff --git a/pos.hpp b/pos.hpp 54 | index c240297..c28f0b9 100644 55 | --- a/pos.hpp 56 | +++ b/pos.hpp 57 | @@ -46,6 +46,7 @@ struct Pos_ 58 | 59 | static Pos_ tile_to_chunk(const Pos_& p) { return Pos_(chunkidx(p.x), chunkidx(p.y)); } 60 | static Pos_ tile_to_chunk_ceil(const Pos_& p) { return Pos_(chunkidx(p.x+31), chunkidx(p.y+31)); } 61 | + static Pos_ chunk_to_tile(const Pos_& p) { return Pos_(p.x*32, p.y*32); } 62 | }; 63 | 64 | template <> inline Pos_ Pos_::operator/(int f) const { return Pos_(sane_div(x,f), sane_div(y,f)); } 65 | diff --git a/worldmap.hpp b/worldmap.hpp 66 | index 3145c32..5ed721f 100644 67 | --- a/worldmap.hpp 68 | +++ b/worldmap.hpp 69 | @@ -85,6 +85,11 @@ class WorldMap 70 | lefttop_chunk = Pos::tile_to_chunk(lefttop_tile); 71 | Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile); 72 | 73 | + // recalculate tile borders, because we can only deal with chunks. This means that 74 | + // the actually available area is larger than the requested area. 75 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk); 76 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk); 77 | + 78 | xlen_chunks = rightbot_chunk.x - lefttop_chunk.x; 79 | ylen_chunks = rightbot_chunk.y - lefttop_chunk.y; 80 | 81 | @@ -96,6 +101,48 @@ class WorldMap 82 | for (int y = 0; y < ylen_chunks; y++) 83 | chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y); 84 | } 85 | + 86 | + void extend_self(const Pos& new_lefttop_tile, const Pos& new_rightbot_tile) 87 | + { 88 | + Pos old_lefttop_chunk = lefttop_chunk; 89 | + int old_xlen_chunks = xlen_chunks; 90 | + int old_ylen_chunks = ylen_chunks; 91 | + std::vector old_chunkcache = std::move(chunkcache); 92 | + 93 | + 94 | + 95 | + lefttop_tile = new_lefttop_tile; 96 | + rightbot_tile = new_rightbot_tile; 97 | + 98 | + lefttop_chunk = Pos::tile_to_chunk(lefttop_tile); 99 | + Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile); 100 | + 101 | + // recalculate tile borders, because we can only deal with chunks. This means that 102 | + // the actually available area is larger than the requested area. 103 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk); 104 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk); 105 | + 106 | + xlen_chunks = rightbot_chunk.x - lefttop_chunk.x; 107 | + ylen_chunks = rightbot_chunk.y - lefttop_chunk.y; 108 | + 109 | + if (xlen_chunks <= 0 or ylen_chunks <= 0) 110 | + throw std::invalid_argument("invalid Viewport range"); 111 | + 112 | + chunkcache.resize(xlen_chunks * ylen_chunks); 113 | + for (int x = 0; x < xlen_chunks; x++) 114 | + for (int y = 0; y < ylen_chunks; y++) 115 | + { 116 | + int oldx = x + lefttop_chunk.x - old_lefttop_chunk.x; 117 | + int oldy = y + lefttop_chunk.y - old_lefttop_chunk.y; 118 | + 119 | + if (0 <= oldx && oldx < old_xlen_chunks && 120 | + 0 <= oldy && oldy < old_ylen_chunks) 121 | + chunkcache[x+y*xlen_chunks] = old_chunkcache[oldx+oldy*old_xlen_chunks]; 122 | + else 123 | + chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y); 124 | + } 125 | + } 126 | + 127 | public: 128 | typedef typename std::conditional::type reftype; 129 | 130 | @@ -104,12 +151,21 @@ class WorldMap 131 | return Viewport_(parent, lefttop_tile_, rightbot_tile_, origin); 132 | } 133 | 134 | - reftype at(int x, int y) const 135 | + reftype at(int x, int y) 136 | { 137 | int tilex = x+origin.x; 138 | int tiley = y+origin.y; 139 | if (!((tilex >= lefttop_tile.x) && (tiley >= lefttop_tile.y) && (tilex < rightbot_tile.x) && (tiley < rightbot_tile.y))) 140 | - throw std::invalid_argument("invalid coordinate for Viewport"); 141 | + { 142 | + Pos new_lefttop_tile(lefttop_tile); 143 | + Pos new_rightbot_tile(rightbot_tile); 144 | + if (tilex < lefttop_tile.x) new_lefttop_tile.x = tilex; 145 | + else if (tilex >= rightbot_tile.x) new_rightbot_tile.x = tilex+1; 146 | + if (tiley < lefttop_tile.y) new_lefttop_tile.y = tiley; 147 | + else if (tiley >= rightbot_tile.y) new_rightbot_tile.y = tiley+1; 148 | + 149 | + extend_self(new_lefttop_tile, new_rightbot_tile); 150 | + } 151 | int chunkx = chunkidx(tilex) - lefttop_chunk.x; 152 | int chunky = chunkidx(tiley) - lefttop_chunk.y; 153 | assert(chunkx >= 0); 154 | @@ -122,7 +178,7 @@ class WorldMap 155 | 156 | return (*chunkcache[chunkx+chunky*xlen_chunks])[relx][rely]; 157 | } 158 | - reftype at(const Pos& pos) const { return at(pos.x, pos.y); } 159 | + reftype at(const Pos& pos) { return at(pos.x, pos.y); } 160 | }; 161 | 162 | 163 | -------------------------------------------------------------------------------- /eval/viewports/dyn_noopt.patch: -------------------------------------------------------------------------------- 1 | diff --git a/factorio_io.cpp b/factorio_io.cpp 2 | index ab13378..40c8840 100644 3 | --- a/factorio_io.cpp 4 | +++ b/factorio_io.cpp 5 | @@ -447,7 +447,7 @@ struct ResourcePatch 6 | } 7 | }; 8 | 9 | -void FactorioGame::floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius) 10 | +void FactorioGame::floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius) 11 | { 12 | int id = next_free_resource_id++; 13 | 14 | diff --git a/factorio_io.h b/factorio_io.h 15 | index cfe4e75..0780b6f 100644 16 | --- a/factorio_io.h 17 | +++ b/factorio_io.h 18 | @@ -106,7 +106,7 @@ class FactorioGame 19 | void parse_players(const std::string& data); 20 | void parse_objects(const Area& area, const std::string& data); 21 | 22 | - void floodfill_resources(const WorldMap::Viewport& view, const Area& area, int x, int y, int radius); 23 | + void floodfill_resources(WorldMap::Viewport& view, const Area& area, int x, int y, int radius); 24 | int next_free_resource_id = 1; 25 | 26 | public: 27 | diff --git a/pathfinding.cpp b/pathfinding.cpp 28 | index a993100..0d55e9d 100644 29 | --- a/pathfinding.cpp 30 | +++ b/pathfinding.cpp 31 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal) 32 | 33 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size) 34 | { 35 | - auto view = map.dumb_view(Pos(0,0)); 36 | + auto view = map.view(Pos(-584,-274), Pos(306,302), Pos(0,0)); 37 | 38 | assert(size<=1.); 39 | vector result; 40 | diff --git a/pos.hpp b/pos.hpp 41 | index c240297..c28f0b9 100644 42 | --- a/pos.hpp 43 | +++ b/pos.hpp 44 | @@ -46,6 +46,7 @@ struct Pos_ 45 | 46 | static Pos_ tile_to_chunk(const Pos_& p) { return Pos_(chunkidx(p.x), chunkidx(p.y)); } 47 | static Pos_ tile_to_chunk_ceil(const Pos_& p) { return Pos_(chunkidx(p.x+31), chunkidx(p.y+31)); } 48 | + static Pos_ chunk_to_tile(const Pos_& p) { return Pos_(p.x*32, p.y*32); } 49 | }; 50 | 51 | template <> inline Pos_ Pos_::operator/(int f) const { return Pos_(sane_div(x,f), sane_div(y,f)); } 52 | diff --git a/worldmap.hpp b/worldmap.hpp 53 | index 3145c32..5ed721f 100644 54 | --- a/worldmap.hpp 55 | +++ b/worldmap.hpp 56 | @@ -85,6 +85,11 @@ class WorldMap 57 | lefttop_chunk = Pos::tile_to_chunk(lefttop_tile); 58 | Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile); 59 | 60 | + // recalculate tile borders, because we can only deal with chunks. This means that 61 | + // the actually available area is larger than the requested area. 62 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk); 63 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk); 64 | + 65 | xlen_chunks = rightbot_chunk.x - lefttop_chunk.x; 66 | ylen_chunks = rightbot_chunk.y - lefttop_chunk.y; 67 | 68 | @@ -96,6 +101,48 @@ class WorldMap 69 | for (int y = 0; y < ylen_chunks; y++) 70 | chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y); 71 | } 72 | + 73 | + void extend_self(const Pos& new_lefttop_tile, const Pos& new_rightbot_tile) 74 | + { 75 | + Pos old_lefttop_chunk = lefttop_chunk; 76 | + int old_xlen_chunks = xlen_chunks; 77 | + int old_ylen_chunks = ylen_chunks; 78 | + std::vector old_chunkcache = std::move(chunkcache); 79 | + 80 | + 81 | + 82 | + lefttop_tile = new_lefttop_tile; 83 | + rightbot_tile = new_rightbot_tile; 84 | + 85 | + lefttop_chunk = Pos::tile_to_chunk(lefttop_tile); 86 | + Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile); 87 | + 88 | + // recalculate tile borders, because we can only deal with chunks. This means that 89 | + // the actually available area is larger than the requested area. 90 | + lefttop_tile = Pos::chunk_to_tile(lefttop_chunk); 91 | + rightbot_tile = Pos::chunk_to_tile(rightbot_chunk); 92 | + 93 | + xlen_chunks = rightbot_chunk.x - lefttop_chunk.x; 94 | + ylen_chunks = rightbot_chunk.y - lefttop_chunk.y; 95 | + 96 | + if (xlen_chunks <= 0 or ylen_chunks <= 0) 97 | + throw std::invalid_argument("invalid Viewport range"); 98 | + 99 | + chunkcache.resize(xlen_chunks * ylen_chunks); 100 | + for (int x = 0; x < xlen_chunks; x++) 101 | + for (int y = 0; y < ylen_chunks; y++) 102 | + { 103 | + int oldx = x + lefttop_chunk.x - old_lefttop_chunk.x; 104 | + int oldy = y + lefttop_chunk.y - old_lefttop_chunk.y; 105 | + 106 | + if (0 <= oldx && oldx < old_xlen_chunks && 107 | + 0 <= oldy && oldy < old_ylen_chunks) 108 | + chunkcache[x+y*xlen_chunks] = old_chunkcache[oldx+oldy*old_xlen_chunks]; 109 | + else 110 | + chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y); 111 | + } 112 | + } 113 | + 114 | public: 115 | typedef typename std::conditional::type reftype; 116 | 117 | @@ -104,12 +151,21 @@ class WorldMap 118 | return Viewport_(parent, lefttop_tile_, rightbot_tile_, origin); 119 | } 120 | 121 | - reftype at(int x, int y) const 122 | + reftype at(int x, int y) 123 | { 124 | int tilex = x+origin.x; 125 | int tiley = y+origin.y; 126 | if (!((tilex >= lefttop_tile.x) && (tiley >= lefttop_tile.y) && (tilex < rightbot_tile.x) && (tiley < rightbot_tile.y))) 127 | - throw std::invalid_argument("invalid coordinate for Viewport"); 128 | + { 129 | + Pos new_lefttop_tile(lefttop_tile); 130 | + Pos new_rightbot_tile(rightbot_tile); 131 | + if (tilex < lefttop_tile.x) new_lefttop_tile.x = tilex; 132 | + else if (tilex >= rightbot_tile.x) new_rightbot_tile.x = tilex+1; 133 | + if (tiley < lefttop_tile.y) new_lefttop_tile.y = tiley; 134 | + else if (tiley >= rightbot_tile.y) new_rightbot_tile.y = tiley+1; 135 | + 136 | + extend_self(new_lefttop_tile, new_rightbot_tile); 137 | + } 138 | int chunkx = chunkidx(tilex) - lefttop_chunk.x; 139 | int chunky = chunkidx(tiley) - lefttop_chunk.y; 140 | assert(chunkx >= 0); 141 | @@ -122,7 +178,7 @@ class WorldMap 142 | 143 | return (*chunkcache[chunkx+chunky*xlen_chunks])[relx][rely]; 144 | } 145 | - reftype at(const Pos& pos) const { return at(pos.x, pos.y); } 146 | + reftype at(const Pos& pos) { return at(pos.x, pos.y); } 147 | }; 148 | 149 | 150 | -------------------------------------------------------------------------------- /eval/viewports/norm_O2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/Makefile b/Makefile 2 | index 6ff1b97..3a8a4b6 100644 3 | --- a/Makefile 4 | +++ b/Makefile 5 | @@ -13,7 +13,7 @@ MODSRCS=$(addprefix luamod/$(MODNAME)/,control.lua data.lua info.json prototypes 6 | 7 | 8 | 9 | -DEBUGFLAGS = -g -D_GLIBCXX_DEBUG #-fsanitize=undefined,address 10 | +DEBUGFLAGS = -g -O2 -D_GLIBCXX_DEBUG #-fsanitize=undefined,address 11 | FASTFLAGS = -O2 12 | CXXFLAGS_BASE = -std=c++14 13 | CFLAGS_BASE = -std=c99 14 | diff --git a/pathfinding.cpp b/pathfinding.cpp 15 | index a993100..355098a 100644 16 | --- a/pathfinding.cpp 17 | +++ b/pathfinding.cpp 18 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal) 19 | 20 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size) 21 | { 22 | - auto view = map.dumb_view(Pos(0,0)); 23 | + auto view = map.view(Pos(-800,-500), Pos(500,500), Pos(0,0)); 24 | 25 | assert(size<=1.); 26 | vector result; 27 | -------------------------------------------------------------------------------- /eval/viewports/norm_noopt.patch: -------------------------------------------------------------------------------- 1 | diff --git a/pathfinding.cpp b/pathfinding.cpp 2 | index a993100..355098a 100644 3 | --- a/pathfinding.cpp 4 | +++ b/pathfinding.cpp 5 | @@ -22,7 +22,7 @@ static double heuristic(const Pos& p, const Pos& goal) 6 | 7 | vector a_star(const Pos& start, const Pos& end, WorldMap& map, double size) 8 | { 9 | - auto view = map.dumb_view(Pos(0,0)); 10 | + auto view = map.view(Pos(-800,-500), Pos(500,500), Pos(0,0)); 11 | 12 | assert(size<=1.); 13 | vector result; 14 | -------------------------------------------------------------------------------- /eval/viewports/output1.txt.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windfisch/factorio-bot/ccef051f0d7db5b6cf9160e2bc5ef418b4bceabc/eval/viewports/output1.txt.bz2 -------------------------------------------------------------------------------- /factorio_io.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "pathfinding.hpp" 29 | #include "worldmap.hpp" 30 | #include "pos.hpp" 31 | #include "area.hpp" 32 | #include "rcon.h" 33 | #include "player.h" 34 | #include "resource.hpp" 35 | #include "entity.h" 36 | #include "item.h" 37 | #include "recipe.h" 38 | #include "action.hpp" 39 | #include "worldlist.hpp" 40 | #include "defines.h" 41 | #include "graphics_definitions.h" 42 | #include "item_storage.h" 43 | 44 | class FactorioGame 45 | { 46 | private: 47 | Rcon rcon; 48 | 49 | std::string line_buffer; 50 | 51 | std::string factorio_file_prefix; 52 | std::ifstream factorio_file; 53 | int factorio_file_id = 1; 54 | 55 | void change_file_id(int new_id); 56 | std::string factorio_file_name(); 57 | std::string remove_line_from_buffer(); 58 | 59 | double max_entity_radius = 0.; 60 | std::unordered_map< std::string, std::unique_ptr > entity_prototypes; 61 | std::unordered_map< std::string, std::unique_ptr > item_prototypes; 62 | std::unordered_map< std::string, std::unique_ptr > recipes; 63 | 64 | public: 65 | WorldList actual_entities; // list of entities that are actually there per chunk 66 | WorldList desired_entities; // list of entities that we expect to be there per chunk 67 | 68 | // the GraphicsDefinition has either one or four entries: north east south west. 69 | std::unordered_map< std::string, std::vector > graphics_definitions; 70 | 71 | private: 72 | /* desired_entities and actual_entities deviate because of the following reasons: 73 | * - trees are irrelevant for desired_entities and thus omitted 74 | * - an entity was scheduled to be built, but is still awaiting its construction 75 | * (walking there takes some time) 76 | * - an entity was destroyed by a biter attack 77 | * - an entity was reconfigured by another player 78 | */ 79 | 80 | void parse_graphics(const std::string& data); 81 | void parse_tiles(const Area& area, const std::string& data); 82 | void parse_resources(const Area& area, const std::string& data); 83 | void parse_entity_prototypes(const std::string& data); 84 | void parse_item_prototypes(const std::string& data); 85 | void parse_recipes(const std::string& data); 86 | void parse_action_completed(const std::string& data); 87 | void parse_players(const std::string& data); 88 | void parse_objects(const Area& area, const std::string& data); 89 | void parse_item_containers(const std::string& data); 90 | void update_walkmap(const Area& area); 91 | void parse_mined_item(const std::string& data); 92 | void parse_inventory_changed(const std::string& data); 93 | 94 | void resolve_references_to_items(); 95 | 96 | /** flood-fills the resource patch at (x,y), but only the part with a patch_id being 97 | * NOT_YET_ASSIGNED, generating a new ResourcePatch from it. Stops at already-assigned 98 | * entries of the same resource type, or at different types. Any adjacent same-type 99 | * patches ("neighbors") and the newly generated are merged together. The largest neighbor 100 | * is preserved and extended, while all other neighbors and the newly generated (temporary, 101 | * in that case) patch are deleted. 102 | * precondition: view.at(x,y).patch_id == NOT_YET_ASSIGNED 103 | * precondition: view is by at least radius larger than the NOT_YET_ASSIGNED entries inside it 104 | * postcondition: view.at(x,y)'s and adjacent coordinates' patch_id fields are changed from 105 | * NOT_YET_ASSIGNED to an actual value. 106 | */ 107 | void floodfill_resources(WorldMap::Viewport& view, const Area& area /* FIXME remove the area parameter */, int x, int y, int radius); 108 | int next_free_resource_id = 1; 109 | int last_tick = 0; 110 | 111 | 112 | struct best_before_entity_t 113 | { 114 | int last_valid_tick; 115 | Entity entity; 116 | }; 117 | std::vector pending_entities; 118 | 119 | /** changes the type of the resource-field 'entry' to new_type (which can be NONE, in which case 120 | * `entity` will be ignored). The resulting patch_id will always be NOT_YET_ASSIGNED. 121 | * The previous patch (if entry.type was not NONE) will have the `position` removed, and 122 | * will be removed from the patch list, if it would be empty after the operation. 123 | */ 124 | void update_resource_field(Resource& entry, Resource::type_t new_type, Pos position, Entity entity); 125 | 126 | public: 127 | FactorioGame(std::string prefix); 128 | void rcon_connect(std::string host, int port, std::string password); 129 | void rcon_call(std::string func, std::string args); 130 | void rcon_call(std::string func, int player_id, std::string args); 131 | void rcon_call(std::string func, int action_id, int player_id, std::string args); 132 | std::string read_packet(); 133 | 134 | /** parses a packet. returns true if this results in a consistent gamestate (i.e., on "tick" messages) */ 135 | bool parse_packet(const std::string& data); 136 | int get_tick() { return last_tick; } 137 | void register_pending_entity(int tick, const Entity& ent) { pending_entities.push_back({tick,ent}); } 138 | 139 | // never use these functions directly, use player actions instead 140 | void set_waypoints(int action_id, int player_id, const std::vector& waypoints); 141 | void set_mining_target(int action_id, int player_id, Entity target); 142 | void unset_mining_target(int player_id); 143 | void start_crafting(int action_id, int player_id, std::string recipe, int count=1); 144 | void place_entity(int player_id, std::string item_name, Pos_f pos, dir4_t direction); 145 | void insert_to_inventory(int player_id, std::string item_name, int amount, Entity entity, inventory_t inventory); 146 | void remove_from_inventory(int player_id, std::string item_name, int amount, Entity entity, inventory_t inventory); 147 | 148 | [[deprecated("set player actions instead")]] 149 | void walk_to(int id, const Pos& dest); 150 | 151 | WorldMap walk_map; 152 | WorldMap resource_map; 153 | std::set< std::shared_ptr > resource_patches; 154 | 155 | void resource_bookkeeping(const Area& area, WorldMap::Viewport resview); 156 | void assert_resource_consistency() const; // only for debugging purposes 157 | 158 | std::vector players; 159 | 160 | const EntityPrototype& get_entity_prototype(std::string name) const { return *entity_prototypes.at(name); } 161 | const ItemPrototype& get_item_prototype(std::string name) const { return *item_prototypes.at(name); } 162 | const Recipe* get_recipe(std::string name) const { return recipes.at(name).get(); } 163 | 164 | /** returns the best recipe for crafting the item */ 165 | const Recipe* get_recipe_for(const ItemPrototype*) const; 166 | /** returns a list of all recipes that produce this item */ 167 | const std::vector get_recipes_for(const ItemPrototype*) const; 168 | /** returns an item that has the entity as place_result */ 169 | const ItemPrototype* get_item_for(const EntityPrototype*) const; 170 | }; 171 | -------------------------------------------------------------------------------- /filter.sh: -------------------------------------------------------------------------------- 1 | tee log.txt | grep --line-buffered -ve '^\S*\(dump\|detail\|floodfill_resources\|pathfinding\|verbose\|action_registry\|core\.objects\|calculate_schedule\)\S*:' | sed 's/^\S*: //' 2 | -------------------------------------------------------------------------------- /goal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include "goal.hpp" 20 | #include "factorio_io.h" 21 | #include "util.hpp" 22 | #include "logging.hpp" 23 | 24 | #include 25 | 26 | using boost::make_iterator_range; 27 | 28 | using namespace std; 29 | 30 | const int RESOURCE_REACH = 2; // max distance for mining a tree, ore field or simple-entity 31 | const int REACH = 4; // max distance for mining a player-built entity // FIXME magic constant. move to common header 32 | const int SAFETY_DISTANCE = 2; // minimum distance for building a player-built entity 33 | 34 | static_assert(REACH > SAFETY_DISTANCE); 35 | 36 | namespace goal 37 | { 38 | 39 | bool GoalList::all_fulfilled(FactorioGame* game) const 40 | { 41 | for (const auto& goal : (*this)) 42 | if (!goal->fulfilled(game)) 43 | return false; 44 | 45 | return true; 46 | } 47 | 48 | vector> GoalList::calculate_actions(FactorioGame* game, int player, std::optional owner) const 49 | { 50 | vector> result; 51 | 52 | for (const auto& goal : (*this)) 53 | { 54 | auto actions = goal->calculate_actions(game, player, owner); 55 | result.insert(std::end(result), 56 | std::make_move_iterator(std::begin(actions)), 57 | std::make_move_iterator(std::end(actions))); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | bool PlaceEntity::fulfilled(FactorioGame* game) const 64 | { 65 | return game->actual_entities.search_or_null(entity) != nullptr; 66 | } 67 | vector> PlaceEntity::_calculate_actions(FactorioGame* game, int player, std::optional owner) const 68 | { 69 | const float CLEAR_MARGIN = 0.1f; // FIXME magic number 70 | vector> result; 71 | 72 | // clear the area from trees and rocks first 73 | Area_f area_to_clear = entity.collision_box().expand(CLEAR_MARGIN); 74 | for (const Entity& offending_entity : game->actual_entities.overlap_range(area_to_clear)) 75 | if (offending_entity.proto->type == "tree" || offending_entity.proto->type == "simple-entity") 76 | { 77 | result.push_back( make_unique(game, player, offending_entity.pos, RESOURCE_REACH) ); 78 | result.push_back( make_unique(game, player, nullopt, offending_entity) ); 79 | } 80 | 81 | // TODO FIXME: ensure that the player isn't in the way. 82 | result.push_back( make_unique(game, player, entity.collision_box(), REACH, SAFETY_DISTANCE) ); 83 | result.push_back( make_unique(game, player, owner, game->get_item_for(entity.proto), entity) ); 84 | return result; 85 | } 86 | 87 | bool RemoveEntity::fulfilled(FactorioGame* game) const 88 | { 89 | return game->actual_entities.search_or_null(entity) == nullptr; 90 | } 91 | vector> RemoveEntity::_calculate_actions(FactorioGame* game, int player, std::optional owner) const 92 | { 93 | vector> result; 94 | 95 | bool is_resource = entity.proto->type == "tree" || entity.proto->type == "simple-entity" || entity.proto->type == "resource"; 96 | 97 | result.push_back( make_unique(game, player, entity.pos, is_resource ? RESOURCE_REACH : REACH) ); 98 | result.push_back( make_unique(game, player, owner, entity) ); 99 | return result; 100 | } 101 | 102 | bool InventoryPredicate::fulfilled(FactorioGame* game) const 103 | { 104 | Entity* ent = game->actual_entities.search_or_null(entity); 105 | const MultiInventory& actual_inventory = ent ? ent->data().inventories : MultiInventory(); 106 | if (type == POSITIVE) 107 | { 108 | for (auto [item, amount] : desired_inventory) 109 | if (actual_inventory.get_or(item, inventory_type, 0) < amount) 110 | return false; 111 | return true; 112 | } 113 | else 114 | { 115 | for (const auto& [key, amount] : make_iterator_range(actual_inventory.inv_begin(inventory_type), actual_inventory.inv_end(inventory_type))) 116 | { 117 | if (get_or(desired_inventory, key.item) > amount) 118 | return false; 119 | } 120 | return true; 121 | } 122 | } 123 | vector> InventoryPredicate::_calculate_actions(FactorioGame* game, int player, std::optional owner) const 124 | { 125 | vector> result; 126 | result.push_back( make_unique(game, player, entity.pos) ); 127 | 128 | Entity* ent = game->actual_entities.search_or_null(entity); 129 | const MultiInventory& actual_inventory = ent ? ent->data().inventories : MultiInventory(); 130 | 131 | if (type == POSITIVE) 132 | { 133 | for (auto [item, desired_amount] : desired_inventory) 134 | { 135 | auto actual_amount = actual_inventory.get_or(item, inventory_type, 0); 136 | if (actual_amount < desired_amount) 137 | result.push_back(make_unique(game, player, owner, item, desired_amount-actual_amount, entity, inventory_type)); 138 | } 139 | } 140 | else 141 | { 142 | for (const auto& [key, actual_amount] : make_iterator_range(actual_inventory.inv_begin(inventory_type), actual_inventory.inv_end(inventory_type))) 143 | { 144 | auto desired_amount = get_or(desired_inventory, key.item); 145 | if (actual_amount > desired_amount) 146 | result.push_back(make_unique(game, player, owner, key.item, actual_amount-desired_amount, *ent, inventory_type)); 147 | } 148 | } 149 | 150 | return result; 151 | } 152 | 153 | string PlaceEntity::str() const 154 | { 155 | return "PlaceEntity(" + entity.str() + ")"; 156 | } 157 | 158 | string RemoveEntity::str() const 159 | { 160 | return "RemoveEntity(" + entity.str() + ")"; 161 | } 162 | 163 | string InventoryPredicate::str() const 164 | { 165 | string result = "InventoryPredicate(" + entity.str() + "[" + inventory_names[inventory_type] + "] -> at "; 166 | result += (type == POSITIVE ? "least " : "most "); 167 | result += desired_inventory.str() + ")"; 168 | return result; 169 | } 170 | 171 | void GoalList::dump() const 172 | { 173 | Logger log("goallist_dump"); 174 | log << "GoalList:" << endl; 175 | for (const auto& goal : (*this)) 176 | log << "\t" << goal->str() << endl; 177 | } 178 | void GoalList::dump(FactorioGame* game) const 179 | { 180 | Logger log("goallist_dump"); 181 | log << "GoalList:" << endl; 182 | for (const auto& goal : (*this)) 183 | log << "\t" << goal->str(game) << endl; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /goal.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "entity.h" 22 | #include "action.hpp" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | namespace goal 29 | { 30 | 31 | /** Interface for all immediate goals. An immediate goal is a predicate on the "relevant" game state, which consists of 32 | * the map's state (entities, their settings and inventories) and the players' special inventories, such as the axe. 33 | * 34 | * These goals can (and will) be reordered at will. 35 | */ 36 | struct GoalInterface 37 | { 38 | /** calculates the actions required to fulfill this goal. 39 | * 40 | * returns the empty list if fulfilled(FactorioGame* game)==true. 41 | */ 42 | virtual std::vector> calculate_actions(FactorioGame* game, int player, std::optional owner) const 43 | { 44 | if (!fulfilled(game)) 45 | return _calculate_actions(game, player, owner); 46 | else 47 | return {}; 48 | } 49 | 50 | /** internal helper to calculate the actions required to fulfill this goal. 51 | * Its result will be ignored, if fulfilled(FactorioGame* game)==true. */ 52 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional owner) const = 0; 53 | virtual bool fulfilled(FactorioGame* game) const = 0; 54 | virtual std::string str(FactorioGame* game) const 55 | { 56 | return (fulfilled(game) ? "[x] " : "[ ] ") + str(); 57 | } 58 | virtual std::string str() const = 0; 59 | virtual ~GoalInterface() = default; 60 | }; 61 | 62 | struct PlaceEntity : public GoalInterface 63 | { 64 | Entity entity; 65 | 66 | PlaceEntity(Entity e) : entity(e) {} 67 | 68 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional owner) const; 69 | virtual bool fulfilled(FactorioGame* game) const; 70 | std::string str() const; 71 | }; 72 | 73 | struct RemoveEntity : public GoalInterface 74 | { 75 | Entity entity; 76 | 77 | RemoveEntity(Entity e) : entity(e) {} 78 | 79 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional owner) const; 80 | virtual bool fulfilled(FactorioGame* game) const; 81 | std::string str() const; 82 | }; 83 | 84 | /* TODO 85 | struct SetupEntity : public GoalInterface 86 | { 87 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional owner) const; 88 | virtual bool fulfilled(FactorioGame* game) const; 89 | std::string str() const; 90 | }; 91 | */ 92 | 93 | struct InventoryPredicate : public GoalInterface 94 | { 95 | enum type_t { POSITIVE, NEGATIVE }; 96 | 97 | /** The entity whose inventory shall be modified */ 98 | Entity entity; 99 | 100 | /** The predicate type: POSITIVE means we want at least the item amounts from the list, 101 | * other additional items are fine. NEGATIVE means that we want at most the item amounts 102 | * from the list, and no items that aren't contained in the list. 103 | * 104 | * Example: POSITIVE and {"coal", 100} will refuel a furnace if appropriate, while 105 | * NEGATIVE and {} will empty a chest completely. */ 106 | type_t type; 107 | inventory_t inventory_type; 108 | 109 | /** the meaning of this depends on the type */ 110 | Inventory desired_inventory; 111 | 112 | 113 | 114 | 115 | 116 | InventoryPredicate(Entity ent, Inventory inv, inventory_t invtype, type_t type_ = POSITIVE) : 117 | entity(ent), type(type_), inventory_type(invtype), desired_inventory(inv) {} 118 | 119 | virtual std::vector> _calculate_actions(FactorioGame* game, int player, std::optional owner) const; 120 | virtual bool fulfilled(FactorioGame* game) const; 121 | std::string str() const; 122 | }; 123 | 124 | 125 | struct GoalList : public std::vector< std::shared_ptr > 126 | { 127 | /** returns a list of actions that will make all goals fulfilled, in arbitrary order. 128 | 129 | (Effectively, this might re-order its goals due to travelling-salesman) */ 130 | std::vector> calculate_actions(FactorioGame* game, int player, std::optional owner) const; 131 | 132 | /** returns whether all goals are fulfilled */ 133 | bool all_fulfilled(FactorioGame* game) const; 134 | void dump() const; 135 | void dump(FactorioGame* game) const; 136 | }; 137 | 138 | } // namespace goal 139 | -------------------------------------------------------------------------------- /graphics_definitions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct GraphicsDefinition 6 | { 7 | std::string filename; 8 | int x; 9 | int y; 10 | int width; 11 | int height; 12 | 13 | bool flip_x; 14 | bool flip_y; 15 | 16 | float shiftx; 17 | float shifty; 18 | 19 | float scale; 20 | 21 | // how to draw a thing: 22 | // 1. load the image denoted by filename 23 | // 2. crop it to the rectangle with topleft=(x,y) and size=(width,height) 24 | // 3. scale it by the factor "scale * desired_pixels_per_tile / 32" 25 | // 4. draw it, so that its center sits at tile position (entity.x,entity.y) + (shiftx, shifty) 26 | }; 27 | -------------------------------------------------------------------------------- /gui/gui.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include "../pos.hpp" 23 | 24 | class FactorioGame; 25 | 26 | namespace GUI 27 | { 28 | 29 | struct Color { 30 | uint8_t r,g,b; 31 | Color() : r(0),g(0),b(0){} 32 | Color(int rr,int gg,int bb):r(uint8_t(rr)),g(uint8_t(gg)),b(uint8_t(bb)){ assert(rr<256 && gg<256 && bb<256); } 33 | void blend(const Color& other, float alpha); 34 | }; 35 | 36 | Color color_hsv(double hue, double sat, double val); 37 | Color get_color(int id); 38 | 39 | class _MapGui_impl; 40 | 41 | class MapGui 42 | { 43 | public: 44 | MapGui(FactorioGame* game, const char* datapath); 45 | ~MapGui(); 46 | 47 | void line(Pos a, Pos b, Color c); 48 | void rect(Pos a, Pos b, Color c); 49 | void rect(Pos a, int size, Color c); 50 | void clear(); 51 | 52 | int key(); 53 | 54 | private: 55 | std::unique_ptr<_MapGui_impl> impl; 56 | }; 57 | 58 | double wait(double t); 59 | 60 | } //namespace GUI 61 | -------------------------------------------------------------------------------- /inventory.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include "scheduler.hpp" 20 | #include "inventory.hpp" 21 | #include "util.hpp" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "logging.hpp" 29 | 30 | using namespace sched; 31 | 32 | owner_names_t owner_names; 33 | std::string owner_names_t::get(owner_t owner) 34 | { 35 | return get_or(*this, owner, "[#"+std::to_string(owner)+"]"); 36 | } 37 | 38 | bool TaggedInventory::can_satisfy(const std::vector& items_, std::optional owner) 39 | { 40 | boost::container::flat_map items; 41 | for (auto [item, amount] : items_) 42 | items[item]+=amount; 43 | 44 | for (const auto& [item, amount] : items) 45 | if ((*this)[item].available_to(owner) < amount) 46 | return false; 47 | 48 | return true; 49 | } 50 | 51 | bool TaggedInventory::apply(const item_balance_t& bal, std::optional owner) 52 | { 53 | TaggedInventory& self = *this; 54 | 55 | for (const auto& [item, amount] : bal) 56 | if (amount < 0 && self[item].available_to(owner) < size_t(-amount)) 57 | return false; 58 | 59 | for (const auto& [item, amount] : bal) 60 | { 61 | bool ok = self[item].update(owner, amount); 62 | assert(ok); 63 | } 64 | 65 | return true; 66 | } 67 | 68 | bool Inventory::apply(const item_balance_t& bal) 69 | { 70 | Inventory& self = *this; 71 | 72 | for (const auto& [item, amount] : bal) 73 | if (amount < 0 && self[item] < size_t(-amount)) 74 | return false; 75 | 76 | for (const auto& [item, amount] : bal) 77 | self[item] += amount; 78 | 79 | return true; 80 | } 81 | 82 | bool Inventory::apply(const Recipe* recipe, bool already_started) 83 | { 84 | Inventory& self = *this; 85 | // FIXME deduplicate code with the other apply() 86 | 87 | if (!already_started) 88 | { 89 | for (const auto& ingredient : recipe->ingredients) 90 | { 91 | if (self[ingredient.item] < ingredient.amount) 92 | return false; 93 | } 94 | 95 | for (const auto& ingredient : recipe->ingredients) 96 | self[ingredient.item] -= ingredient.amount; 97 | } 98 | 99 | for (const auto& product : recipe->products) 100 | self[product.item] += product.amount; 101 | 102 | return true; 103 | } 104 | 105 | size_t TaggedAmount::n_claimed() const 106 | { 107 | size_t claimed = 0; 108 | for (const auto& claim : claims) 109 | claimed += claim.amount; 110 | return claimed; 111 | } 112 | 113 | size_t TaggedAmount::add_claim(owner_t owner, size_t n) 114 | { 115 | size_t available = amount - n_claimed(); 116 | if (available < n) 117 | n = available; 118 | 119 | claims.get(owner).amount += n; 120 | 121 | return n; 122 | } 123 | 124 | std::string Inventory::str() const 125 | { 126 | std::string result; 127 | bool first = true; 128 | for (auto [proto,amount] : (*this)) 129 | { 130 | result += (first ? ", " : "") + std::to_string(amount) + "x " + proto->name; 131 | first = false; 132 | } 133 | return result; 134 | } 135 | void Inventory::dump() const 136 | { 137 | Logger log("inventory_dump"); 138 | for (auto [proto,amount] : (*this)) if (amount) 139 | log << "\t" << proto->name << ": " << amount << std::endl; 140 | } 141 | 142 | void TaggedInventory::dump() const 143 | { 144 | Logger log("inventory_dump"); 145 | for (const auto& [proto, tagged_amount] : (*this)) if (tagged_amount.amount) 146 | { 147 | log << "\t" << proto->name << ": " << tagged_amount.amount << std::endl; 148 | for (const auto& claim : tagged_amount.claims) if (claim.amount) 149 | { 150 | log << "\t\t" << owner_names.get(claim.owner) << " <- " << claim.amount << std::endl; 151 | } 152 | } 153 | } 154 | 155 | void MultiInventory::dump() const 156 | { 157 | Logger log("inventory_dump"); 158 | std::vector types; 159 | for (const auto& [key, val] : container) 160 | { 161 | if (!contains_vec(types, key.inv)) 162 | types.push_back(key.inv); 163 | } 164 | for (inventory_t type : types) 165 | { 166 | log << inventory_names[type] << std::endl; 167 | get_inventory(type).dump(); 168 | } 169 | } 170 | 171 | Inventory MultiInventory::get_inventory(inventory_t type) const 172 | { 173 | Inventory result; 174 | for (const_inv_iterator_t it = inv_begin(type); it != inv_end(type); it++) 175 | result[it->first.item] = it->second; 176 | return result; 177 | } 178 | -------------------------------------------------------------------------------- /inventory.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "util.hpp" 22 | #include "defines.h" 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | struct Recipe; 33 | 34 | struct ItemPrototype; 35 | struct ItemStack; 36 | 37 | using owner_t = intptr_t; // FIXME 38 | 39 | // DEBUG only 40 | struct owner_names_t : public std::unordered_map 41 | { 42 | std::string get(owner_t owner); 43 | }; 44 | extern owner_names_t owner_names; 45 | 46 | struct TaggedAmount 47 | { 48 | struct Tag 49 | { 50 | owner_t owner; 51 | size_t amount; 52 | }; 53 | 54 | using claims_t = vector_map< Tag, owner_t, &Tag::owner >; 55 | 56 | size_t amount; 57 | claims_t claims; 58 | 59 | /** checks internal consistency */ 60 | void invariant() const 61 | { 62 | assert(n_claimed() <= amount); 63 | } 64 | 65 | /** returns the number of claimed items. amount - n_claimed() equals to the maximum amount a very-low-priority owner can claim */ 66 | size_t n_claimed() const; 67 | 68 | /** claims up to n items for `owner`, only considering unclaimed items. claims at 69 | * most as many items as are still available. Returns the number of actually 70 | * claimed items. 71 | */ 72 | size_t add_claim(owner_t owner, size_t n); 73 | 74 | /** adds / removes `n` items for `owner`, also updating the claims. If n is 75 | * negative and available_to(owner) is not sufficient, this function fails 76 | * by returning false. Otherwise it returns true. */ 77 | bool update(std::optional owner, int n) 78 | { 79 | if (n >= 0) 80 | { 81 | amount += n; 82 | if (owner.has_value()) 83 | add_claim(*owner, n); 84 | } 85 | else 86 | { 87 | if (available_to(owner) < size_t(-n)) 88 | return false; 89 | 90 | amount += n; // n is negative. 91 | if (owner.has_value()) 92 | { 93 | auto& claim = claims.get(*owner).amount; 94 | claim -= std::min(size_t(-n), claim); 95 | } 96 | } 97 | return true; 98 | } 99 | 100 | /** returns the amount of items claimed for the specified owner. */ 101 | size_t claimed_by(owner_t owner) const 102 | { 103 | auto idx = claims.find(owner); 104 | if (idx == SIZE_MAX) 105 | return 0; 106 | else 107 | return claims[idx].amount; 108 | } 109 | 110 | /** returns the amount of items that is unclaimed */ 111 | size_t unclaimed() const 112 | { 113 | assert(n_claimed() <= amount); 114 | return amount - n_claimed(); 115 | } 116 | 117 | /** returns the amount of items available to the specified owner. This includes claimed and free-for-use items. */ 118 | size_t available_to(std::optional owner) const 119 | { 120 | if (owner.has_value()) 121 | return unclaimed() + claimed_by(*owner); 122 | else 123 | return unclaimed(); 124 | } 125 | }; 126 | 127 | using item_balance_t = boost::container::flat_map; 128 | 129 | // this is a glorified vector> 130 | struct TaggedInventory : boost::container::flat_map 131 | { 132 | void dump() const; 133 | 134 | bool apply(const item_balance_t& bal, std::optional owner); 135 | bool can_satisfy(const std::vector& items, std::optional owner); 136 | }; 137 | 138 | struct Inventory : public boost::container::flat_map 139 | { 140 | /** applies the recipe. if successful, the inventory is altered and true is returned, otherwise the inventory remains unchanged and false is returned. 141 | If already_started == true, the ingredients are not removed, but the products are added. In this 142 | case, the function always returns true. 143 | */ 144 | bool apply(const Recipe*, bool already_started = false); 145 | 146 | bool apply(const item_balance_t& bal); 147 | 148 | 149 | /** constructs the empty inventory */ 150 | Inventory() : flat_map() {} 151 | 152 | 153 | Inventory(std::initializer_list il) : flat_map(il) {} 154 | 155 | Inventory& operator+=(const Inventory& other) 156 | { 157 | for (const auto& [key,value] : other) 158 | (*this)[key] += value; 159 | return *this; 160 | } 161 | Inventory& operator-=(const Inventory& other) 162 | { 163 | for (const auto& [key,value] : other) 164 | (*this)[key] -= value; 165 | return *this; 166 | } 167 | Inventory& operator*=(size_t mult) 168 | { 169 | for (auto& [key,value] : (*this)) 170 | value *= mult; 171 | return *this; 172 | } 173 | 174 | Inventory operator+(const Inventory& other) const 175 | { 176 | Inventory result = *this; 177 | result += other; 178 | 179 | return result; 180 | } 181 | 182 | Inventory operator-(const Inventory& other) const 183 | { 184 | Inventory result = *this; 185 | result -= other; 186 | return result; 187 | } 188 | 189 | Inventory operator*(size_t mult) const 190 | { 191 | Inventory result = *this; 192 | result *= mult; 193 | return result; 194 | } 195 | 196 | Inventory max(const Inventory& other) const 197 | { 198 | Inventory result = *this; 199 | for (const auto& [key,value] : other) 200 | result[key] = std::max(result[key], value); 201 | return result; 202 | } 203 | 204 | Inventory min(const Inventory& other) const 205 | { 206 | Inventory result = *this; 207 | for (const auto& [key,value] : other) 208 | { 209 | size_t amount = std::min(value, this->get_or(key, 0)); 210 | if (amount) 211 | result[key] = amount; 212 | } 213 | return result; 214 | } 215 | 216 | size_t get_or(const ItemPrototype* item, size_t default_value) const { 217 | auto it = find(item); 218 | if (it != end()) 219 | return it->second; 220 | else 221 | return default_value; 222 | } 223 | 224 | 225 | /** constructs an inventory from a TaggedInventory, considering only items claimed by the specified owner */ 226 | static Inventory get_claimed_by(const TaggedInventory& inv, owner_t owner) 227 | { 228 | Inventory result; 229 | for (const auto& entry : inv) 230 | if (size_t claimed = entry.second.claimed_by(owner)) 231 | result[entry.first] = claimed; 232 | return result; 233 | } 234 | /** constructs an inventory from a TaggedInventory, considering only unclaimed items */ 235 | static Inventory get_unclaimed(const TaggedInventory& inv) 236 | { 237 | Inventory result; 238 | for (const auto& entry : inv) 239 | if (size_t claimed = entry.second.unclaimed()) 240 | result[entry.first] = claimed; 241 | return result; 242 | } 243 | 244 | void dump() const; 245 | std::string str() const; 246 | }; 247 | 248 | inline Inventory operator*(size_t mult, const Inventory& inv) { return inv * mult; } 249 | 250 | struct MultiInventory 251 | { 252 | struct key_t 253 | { 254 | const ItemPrototype* item; 255 | inventory_t inv; 256 | 257 | bool operator< (const key_t& o) const 258 | { 259 | if (item < o.item) return true; 260 | if (item > o.item) return false; 261 | return inv < o.inv; 262 | } 263 | }; 264 | using container_t = boost::container::flat_map; 265 | using iterator_t = container_t::iterator; 266 | using item_iterator_t = iterator_t; 267 | using const_iterator_t = container_t::const_iterator; 268 | using const_item_iterator_t = const_iterator_t; 269 | 270 | struct Predicate 271 | { 272 | inventory_t inv; 273 | bool operator()(const container_t::value_type& x) 274 | { 275 | return x.first.inv == inv; 276 | } 277 | }; 278 | 279 | using inv_iterator_t = boost::filter_iterator; 280 | using const_inv_iterator_t = boost::filter_iterator; 281 | 282 | size_t& get(const ItemPrototype* item, inventory_t inv) { return container[key_t{item,inv}]; } 283 | size_t get_or(const ItemPrototype* item, inventory_t inv, size_t default_value) const { 284 | auto it = container.find(key_t{item,inv}); 285 | if (it != container.end()) 286 | return it->second; 287 | else 288 | return default_value; 289 | } 290 | 291 | iterator_t begin() { return container.begin(); } 292 | iterator_t end() { return container.end(); } 293 | 294 | item_iterator_t item_begin(const ItemPrototype* item) { return container.lower_bound(key_t{item, INV_MIN}); } 295 | item_iterator_t item_end(const ItemPrototype* item) { return container.upper_bound(key_t{item, INV_MAX}); } 296 | 297 | inv_iterator_t inv_begin(inventory_t inv) { 298 | return inv_iterator_t(Predicate{inv}, container.begin(), container.end()); 299 | } 300 | inv_iterator_t inv_end(inventory_t inv) { 301 | return inv_iterator_t(Predicate{inv}, container.end(), container.end()); 302 | } 303 | 304 | const_iterator_t begin() const { return container.begin(); } 305 | const_iterator_t end() const { return container.end(); } 306 | 307 | const_item_iterator_t item_begin(const ItemPrototype* item) const { return container.lower_bound(key_t{item, INV_MIN}); } 308 | const_item_iterator_t item_end(const ItemPrototype* item) const { return container.upper_bound(key_t{item, INV_MAX}); } 309 | 310 | const_inv_iterator_t inv_begin(inventory_t inv) const { 311 | return const_inv_iterator_t(Predicate{inv}, container.begin(), container.end()); 312 | } 313 | const_inv_iterator_t inv_end(inventory_t inv) const { 314 | return const_inv_iterator_t(Predicate{inv}, container.end(), container.end()); 315 | } 316 | 317 | void clear() { container.clear(); } 318 | 319 | std::pair insert(inventory_t inv, const ItemPrototype* item, size_t val) 320 | { 321 | return container.insert(std::pair{ key_t{item,inv}, val }); 322 | } 323 | 324 | void dump() const; 325 | Inventory get_inventory(inventory_t type) const; 326 | 327 | private: 328 | container_t container; 329 | }; 330 | -------------------------------------------------------------------------------- /item.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | #include "entity.h" 21 | #include 22 | 23 | 24 | struct ItemPrototype 25 | { 26 | std::string name; 27 | std::string type; 28 | 29 | const EntityPrototype* place_result; 30 | int stack_size; 31 | double fuel_value; 32 | double speed; 33 | double durability; 34 | 35 | ItemPrototype(std::string name_, std::string type_, const EntityPrototype* place_result_, 36 | int stack_size_, double fuel_value_, double speed_, double durability_) : 37 | name(name_), type(type_), place_result(place_result_), stack_size(stack_size_), 38 | fuel_value(fuel_value_), speed(speed_), durability(durability_) {} 39 | }; 40 | 41 | struct ItemStack 42 | { 43 | const ItemPrototype* proto; 44 | size_t amount; 45 | }; 46 | 47 | struct [[deprecated]] OwnedItemStack : public ItemStack 48 | { 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /item_storage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | #include "pos.hpp" 21 | #include "entity.h" 22 | #include "inventory.hpp" 23 | 24 | struct ItemStorage 25 | { 26 | Pos_f get_pos() const { return entity.pos; } // WorldList wants to call this. 27 | 28 | Entity entity; 29 | Inventory inventory; 30 | 31 | // TODO: last_update timestamp; prediction; distance-based updating (more frequently if close) 32 | // currently, we transmit all inventories of all chests with a fixed period; this will not scale. 33 | }; 34 | -------------------------------------------------------------------------------- /launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CONF=botconfig.conf 4 | 5 | set -e 6 | 7 | if [ ! -e $CONF ]; then 8 | echo "Error: $CONF doesn't exist. run ./prepare.sh first" 9 | exit 1 10 | fi 11 | 12 | if [ ! $# -eq 2 ] && [ ! x$1 == x--bot ] && [ ! x$1 == x--bot-offline ] && [ ! x$1 == x--newmap ] && [ ! x$1 == x--restoremap ]; then 13 | echo "Usage:" 14 | echo " $0 --start|--stop|--run server|clientname" 15 | echo " $0 --bot|--bot-offline" 16 | echo " $0 --newmap|--restoremap" 17 | echo "" 18 | echo "The first form will start or stop a Factorio server/client." 19 | echo "--start will not block and you must --stop manually." 20 | echo "--run will block, and upon pressing ^C it will automatically stop." 21 | echo "The second form will launch the bot." 22 | echo "Usually, you want to do:" 23 | echo " - $0 --start server" 24 | echo " - ($0 --start client)" 25 | echo " - $0 --start bot" 26 | echo "The third form will create a new map (overwriting both the current map" 27 | echo "and its backup), or will restore that backup respectively" 28 | exit 0 29 | fi 30 | 31 | ACTION=$1 32 | TARGET=$2 33 | 34 | source $CONF 35 | 36 | if [ ! "${MAP:0:1}" = "/" ]; then 37 | MAP="$INSTALLPATH/$MAP" 38 | fi 39 | 40 | echo "INSTALLPATH='$INSTALLPATH'" 41 | echo "MAP='$MAP'" 42 | 43 | case "$ACTION" in 44 | --start) ;; 45 | --stop) ;; 46 | --run) ;; 47 | --bot) ;; 48 | --bot-offline) ;; 49 | --newmap) ;; 50 | --restoremap) ;; 51 | *) 52 | echo "ERROR: action '$ACTION' not understood. run '$0 --help'" 53 | exit 1 54 | ;; 55 | esac 56 | 57 | 58 | if ( [ $ACTION == --start ] || [ $ACTION == --stop ] || [ $ACTION == --run ] ) && [ ! -d "$INSTALLPATH/$TARGET" ] && [ "$TARGET" != offline ]; then 59 | echo "ERROR: target '$TARGET' does not exist." 60 | echo -n ' available targets are '; ( cd "$INSTALLPATH" && echo */ | sed 's./..g'; ) 61 | exit 1 62 | fi 63 | 64 | if [ $ACTION == --newmap ]; then 65 | "$INSTALLPATH/server/bin/x64/factorio" --create "$INSTALLPATH/map.zip" 66 | cp "$INSTALLPATH/map.zip" "$INSTALLPATH/map.zip.orig" 67 | fi 68 | 69 | if [ $ACTION == --restoremap ]; then 70 | cp "$INSTALLPATH/map.zip.orig" "$INSTALLPATH/map.zip" 71 | fi 72 | 73 | 74 | if [ $ACTION == --bot ]; then 75 | DEBUGPREFIX="$2" 76 | $DEBUGPREFIX ./bot "$INSTALLPATH/server/script-output/output" "$INSTALLPATH/server/data" localhost "$RCON_PORT" "$RCON_PASS" 77 | exit 0 78 | fi 79 | 80 | if [ $ACTION == --bot-offline ]; then 81 | DEBUGPREFIX="$2" 82 | $DEBUGPREFIX ./bot "$INSTALLPATH/server/script-output/output" "$INSTALLPATH/server/data" 83 | exit 0 84 | fi 85 | 86 | 87 | PIDFILE="$INSTALLPATH/$TARGET.pid" 88 | JOINFILE="$INSTALLPATH/server/script-output/players_connected.txt" 89 | 90 | if [ $ACTION == --start ] || [ $ACTION == --run ]; then 91 | if [ -e "$PIDFILE" ]; then 92 | if kill -0 `cat "$PIDFILE"` 2>/dev/null; then 93 | echo "ERROR: pidfile '$PIDFILE' already exists. refusing to start." 94 | echo " if you are sure, that $TARGET isn't running already, you can remove it" 95 | exit 1 96 | else 97 | echo "WARNING: pidfile '$PIDFILE' exists, but contains invalid PID. deleting it" 98 | rm -v "$PIDFILE" 99 | fi 100 | fi 101 | 102 | echo "launching target '$TARGET'" 103 | rm -f "$JOINFILE" 104 | 105 | if [ $TARGET == offline ]; then 106 | TARGET=server 107 | rm -f "$INSTALLPATH/$TARGET/script-output"/*.txt 108 | "$INSTALLPATH/$TARGET"/bin/x64/factorio --load-game "$MAP" 109 | exit $? 110 | elif [ $TARGET == server ]; then 111 | rm -f "$INSTALLPATH/$TARGET/script-output"/*.txt 112 | "$INSTALLPATH/$TARGET"/bin/x64/factorio --start-server "$MAP" --rcon-port "$RCON_PORT" --rcon-password "$RCON_PASS" --server-settings "$INSTALLPATH/server-settings.json" & 113 | PID=$! 114 | else 115 | "$INSTALLPATH/$TARGET"/bin/x64/factorio --mp-connect localhost --disable-audio & 116 | PID=$! 117 | fi 118 | 119 | echo $PID > "$PIDFILE" 120 | 121 | # wait for $JOINFILE to appear and call whoami($TARGET) 122 | TIMEOUT=90 123 | for ((i=0; i < $TIMEOUT; i++)); do 124 | if [ $i == 30 ]; then 125 | echo "" 126 | echo "**************************************************************" 127 | echo "** STRANGE: client hasn't joined yet. that's unusually slow **" 128 | echo "**************************************************************" 129 | echo "" 130 | fi 131 | 132 | if [ ! -e "$JOINFILE" ]; then 133 | sleep 1 134 | else 135 | echo "" 136 | echo "" 137 | echo Client `cat "$JOINFILE"` "has joined while launching target '$TARGET'" 138 | sleep 3 139 | if [ "$TARGET" == server ]; then 140 | # Execute a dummy command to silence the warning about "using commands will 141 | # disable achievements". If we don't do this, the first command will be lost 142 | ./rcon-client localhost "$RCON_PORT" "$RCON_PASS" "/silent-command print('')" 143 | sleep 0.5 144 | fi 145 | ./rcon-client localhost "$RCON_PORT" "$RCON_PASS" "/c remote.call('windfisch','whoami','$TARGET')" 146 | break 147 | fi 148 | done 149 | if [ $i == $TIMEOUT ]; then 150 | echo "ERROR: timeouted" 151 | exit 1 152 | fi 153 | 154 | if [ $ACTION == --run ]; then 155 | trap '' SIGINT # don't let CTRL+C kill this script, but let it kill the wait 156 | wait $PID 157 | if kill -s SIGINT $PID &>/dev/null; then 158 | echo 159 | echo '******** SIGINT ********' 160 | echo 161 | wait $PID 162 | echo 163 | echo 'Factorio has been stopped' 164 | else 165 | echo 166 | echo 'Factorio exited' 167 | fi 168 | fi 169 | elif [ $ACTION == --stop ]; then 170 | if [ ! -e "$PIDFILE" ]; then 171 | echo "ERROR: pidfile '$PIDFILE' does not exist. can not stop anything" 172 | exit 1 173 | fi 174 | 175 | kill -s SIGINT `cat "$PIDFILE"` || echo "ERROR: pidfile '$PIDFILE' seems invalid. deleting it" 176 | rm "$PIDFILE" 177 | 178 | echo "stopped $TARGET" 179 | fi 180 | -------------------------------------------------------------------------------- /logging.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.hpp" 2 | std::vector Logger::stack; 3 | -------------------------------------------------------------------------------- /logging.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | class prefixbuf : public std::streambuf 7 | { 8 | std::string prefix; 9 | std::streambuf* sbuf; 10 | bool need_prefix; 11 | 12 | int sync() { 13 | return this->sbuf->pubsync(); 14 | } 15 | int overflow(int c) { 16 | if (c != std::char_traits::eof()) { 17 | if (this->need_prefix 18 | && !this->prefix.empty() 19 | && this->prefix.size() != this->sbuf->sputn(&this->prefix[0], this->prefix.size())) { 20 | return std::char_traits::eof(); 21 | } 22 | this->need_prefix = c == '\n'; 23 | } 24 | return this->sbuf->sputc(c); 25 | } 26 | 27 | public: 28 | prefixbuf(std::string const& prefix, std::streambuf* sbuf_) : prefix(prefix), sbuf(sbuf_), need_prefix(true) {} 29 | }; 30 | 31 | class Logger : private virtual prefixbuf, public std::ostream 32 | { 33 | public: 34 | Logger(std::string const& topic) 35 | : prefixbuf(get_prefix(topic) + ": ", std::cout.rdbuf()) 36 | , std::ios(static_cast(this)) 37 | , std::ostream(static_cast(this)) 38 | { 39 | stack.push_back(topic); 40 | } 41 | ~Logger() 42 | { 43 | stack.pop_back(); 44 | } 45 | private: 46 | static std::string get_prefix(std::string tail) 47 | { 48 | if (stack.empty()) return tail; 49 | std::string result = stack[0]; 50 | for (size_t i=1; i < stack.size(); i++) 51 | result += "." + stack[i]; 52 | return result + "." + tail; 53 | } 54 | 55 | static std::vector stack; 56 | }; 57 | -------------------------------------------------------------------------------- /luamod/Windfisch_0.0.1/data-final-fixes.lua: -------------------------------------------------------------------------------- 1 | local maxlen = 200 2 | local rawstr = serpent.dump(data.raw) 3 | 4 | local j = 0 5 | for i = 1, string.len(rawstr), maxlen do 6 | print (i.." / "..string.len(rawstr)) 7 | j = j + 1 8 | data:extend({{ 9 | type = "flying-text", 10 | name = "DATA_RAW"..j, 11 | time_to_live = 0, 12 | speed = 1, 13 | order = string.sub(rawstr,i,i+maxlen-1) 14 | }}); 15 | end 16 | data:extend({{ 17 | type = "flying-text", 18 | name = "DATA_RAW_LEN", 19 | time_to_live = 0, 20 | speed = 1, 21 | order = tostring(j) 22 | }}); 23 | -------------------------------------------------------------------------------- /luamod/Windfisch_0.0.1/data.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windfisch/factorio-bot/ccef051f0d7db5b6cf9160e2bc5ef418b4bceabc/luamod/Windfisch_0.0.1/data.lua -------------------------------------------------------------------------------- /luamod/Windfisch_0.0.1/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Windfisch", 3 | "version": "0.0.1", 4 | "title": "Windfisch's mod", 5 | "author": "Windfisch", 6 | "contact": "flo@windfisch.org", 7 | "homepage": "TODO", 8 | "description": "Windfisch's awesome mod", 9 | "factorio_version": "0.17", 10 | "license": "GPL3" 11 | } 12 | -------------------------------------------------------------------------------- /luamod/Windfisch_0.0.1/prototypes/.emptydir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windfisch/factorio-bot/ccef051f0d7db5b6cf9160e2bc5ef418b4bceabc/luamod/Windfisch_0.0.1/prototypes/.emptydir -------------------------------------------------------------------------------- /luamod/server-settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bot Test Game", 3 | "description": "A game for testing the bot", 4 | "tags": ["ai", "bot"], 5 | 6 | "_comment_max_players": "Maximum number of players allowed, admins can join even a full server. 0 means unlimited.", 7 | "max_players": 0, 8 | 9 | "_comment_visibility": ["public: Game will be published on the official Factorio matching server", 10 | "lan: Game will be broadcast on LAN"], 11 | "visibility": 12 | { 13 | "public": false, 14 | "lan": false 15 | }, 16 | 17 | "_comment_credentials": "Your factorio.com login credentials. Required for games with visibility public", 18 | "username": "", 19 | "password": "", 20 | 21 | "_comment_token": "Authentication token. May be used instead of 'password' above.", 22 | "token": "", 23 | 24 | "game_password": "", 25 | 26 | "_comment_require_user_verification": "When set to true, the server will only allow clients that have a valid Factorio.com account", 27 | "require_user_verification": false, 28 | 29 | "_comment_max_upload_in_kilobytes_per_second" : "optional, default value is 0. 0 means unlimited.", 30 | "max_upload_in_kilobytes_per_second": 0, 31 | 32 | "_comment_minimum_latency_in_ticks": "optional one tick is 16ms in default speed, default value is 0. 0 means no minimum.", 33 | "minimum_latency_in_ticks": 0, 34 | 35 | "_comment_ignore_player_limit_for_returning_players": "Players that played on this map already can join even when the max player limit was reached.", 36 | "ignore_player_limit_for_returning_players": false, 37 | 38 | "_comment_allow_commands": "possible values are, true, false and admins-only", 39 | "allow_commands": "true", 40 | 41 | "_comment_autosave_interval": "Autosave interval in minutes", 42 | "autosave_interval": 10, 43 | 44 | "_comment_autosave_slots": "server autosave slots, it is cycled through when the server autosaves.", 45 | "autosave_slots": 5, 46 | 47 | "_comment_afk_autokick_interval": "How many minutes until someone is kicked when doing nothing, 0 for never.", 48 | "afk_autokick_interval": 0, 49 | 50 | "_comment_auto_pause": "Whether should the server be paused when no players are present.", 51 | "auto_pause": false, 52 | 53 | "only_admins_can_pause_the_game": false, 54 | 55 | "_comment_autosave_only_on_server": "Whether autosaves should be saved only on server or also on all connected clients. Default is true.", 56 | "autosave_only_on_server": true, 57 | 58 | "_comment_admins": "List of case insensitive usernames, that will be promoted immediately", 59 | "admins": [] 60 | } 61 | -------------------------------------------------------------------------------- /mine_planning.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include "pos.hpp" 23 | #include "entity.h" 24 | #include "resource.hpp" 25 | 26 | class FactorioGame; 27 | 28 | std::vector plan_mine(const std::vector& positions, Pos destination, const FactorioGame& game); 29 | std::vector plan_mine(const std::vector& positions, Pos destination, unsigned side_max, const EntityPrototype* belt_proto, const EntityPrototype* machine_proto, int outerx = 4, int outery = 4); 30 | 31 | std::vector plan_early_mine(const ResourcePatch& patch, const FactorioGame* game, std::vector rig, Pos size, Area mining_area, dir4_t side); 32 | 33 | std::vector plan_early_smelter_rig(const ResourcePatch& patch, const FactorioGame* game); 34 | std::vector plan_early_chest_rig(const ResourcePatch& patch, const FactorioGame* game); 35 | std::vector plan_early_coal_rig(const ResourcePatch& patch, const FactorioGame* game); 36 | -------------------------------------------------------------------------------- /mk_compile_commands_json.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TARGET='' 4 | FILEEXTS='[^ ]*\.\(cpp\|c\|cc\|cxx\|h\|hpp\)' 5 | LANG=C 6 | 7 | echo '[' 8 | 9 | PRINTED_TARGETS=' ' 10 | 11 | FIRST=1 12 | make --trace -Bn | grep -v '\(Entering\|Leaving\) directory' | while read line; do 13 | if echo "$line" | grep -q -- '-MT'; then 14 | echo "ignoring make depend line" 1>&2 15 | continue 16 | fi 17 | 18 | if echo "$line" | grep -q 'update target'; then 19 | TMP_TARGETS="`echo "$line" | sed 's/^.*update target.*due to: \(.*\)$/\1/'`" 20 | TARGETS='' 21 | 22 | for TARGET in $TMP_TARGETS; do 23 | if ! echo "$TARGET" | grep -qi "$FILEEXTS"'$'; then 24 | echo "$TARGET is not interesting" 1>&2 25 | else 26 | TARGETS="$TARGETS $TARGET" 27 | fi 28 | done 29 | else 30 | if [ x"$TARGETS" == x ]; then 31 | TARGETS="`echo "$line" | grep -oi "$FILEEXTS" | tail -n1`" 32 | echo "no target found for line $line, let's hope that $TARGETS is right" 1>&2 33 | fi 34 | 35 | # strip off the filename: this line could be emitted for (say) a header file, 36 | # but with a command line for compiling a cpp file. the vim YCM plugin cannot 37 | # handle this, and ignores our settings for this file then. If there is no 38 | # contradicting compiler argument, it works. 39 | line=`echo "$line" | sed -e 's/\s'"$FILEEXTS"'//g'` 40 | escaped="`echo "$line" | sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g'`" 41 | escpwd="`pwd | sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g'`" 42 | 43 | for TARGET in $TARGETS; do 44 | if ! echo "$PRINTED_TARGETS" | grep -q "$TARGET"; then 45 | if [ x"$TARGET" != x ]; then 46 | if [ $FIRST == 1 ]; then 47 | FIRST=0 48 | else 49 | echo ',' 50 | fi 51 | 52 | echo ' { "directory": "'"$escpwd"'",' 53 | echo ' "command": "'"$escaped"'",' 54 | echo -n ' "file": "'"$TARGET"'" }' 55 | 56 | PRINTED_TARGETS="$PRINTED_TARGETS $TARGET" 57 | fi 58 | else 59 | echo "skipping $TARGET" 1>&2 60 | fi 61 | done 62 | fi 63 | done 64 | 65 | echo 66 | echo ']' 67 | -------------------------------------------------------------------------------- /multivariant.hpp: -------------------------------------------------------------------------------- 1 | /* This multivariant.hpp file is dual-licensed under both the 2 | * terms of the MIT and the GPL3 license. You can choose which 3 | * of those licenses you want to use. Note that the MIT option 4 | * only applies to this file, and not to the rest of 5 | * factorio-bot (unless stated otherwise). 6 | */ 7 | 8 | /* 9 | * Copyright (c) 2017, 2018 Florian Jung 10 | * 11 | * This file is part of factorio-bot. 12 | * 13 | * factorio-bot is free software: you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public License, 15 | * version 3, as published by the Free Software Foundation. 16 | * 17 | * factorio-bot is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with factorio-bot. If not, see . 24 | */ 25 | 26 | /* 27 | * MIT License 28 | * 29 | * Copyright (c) 2017, 2018 Florian Jung 30 | * 31 | * Permission is hereby granted, free of charge, to any person obtaining a 32 | * copy of this templated WorldList implementation and associated 33 | * documentation files (the "Software"), to deal in the Software without 34 | * restriction, including without limitation the rights to use, copy, modify, 35 | * merge, publish, distribute, sublicense, and/or sell copies of the 36 | * Software, and to permit persons to whom the Software is furnished to do 37 | * so, subject to the following conditions: 38 | * 39 | * The above copyright notice and this permission notice shall be included in 40 | * all copies or substantial portions of the Software. 41 | * 42 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 45 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 47 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 48 | * DEALINGS IN THE SOFTWARE. 49 | */ 50 | 51 | #include 52 | #include 53 | 54 | template struct typelist; 55 | 56 | struct refcount_base 57 | { 58 | std::size_t refcount; 59 | }; 60 | 61 | template struct refcounted : public refcount_base 62 | { 63 | T data; 64 | 65 | template refcounted(As&&... args) : data(std::forward(args)...) {} 66 | }; 67 | 68 | 69 | namespace detail { 70 | 71 | // typelist_index< T, typelist< S1, ..., Sn > >::value will be i if Si == T 72 | template struct typelist_index; 73 | template 74 | struct typelist_index> 75 | { 76 | static constexpr size_t value = 0; 77 | }; 78 | template 79 | struct typelist_index> 80 | { 81 | static constexpr size_t value = typelist_index>::value + 1; 82 | }; 83 | template 84 | struct typelist_index> 85 | { 86 | static_assert(sizeof(T)!=sizeof(T), "Type is not in list"); 87 | }; 88 | 89 | 90 | template struct make_helper; 91 | template 92 | struct make_helper, I, typelist> 93 | { 94 | static constexpr refcount_base* make(size_t type_idx, As&&... args) 95 | { 96 | if (type_idx == I) 97 | return static_cast( new refcounted(std::forward(args)...) ); 98 | else 99 | return make_helper, I+1, typelist>::make(type_idx, std::forward(args)...); 100 | } 101 | }; 102 | template 103 | struct make_helper, I, typelist> 104 | { 105 | static constexpr refcount_base* make(size_t, As...) 106 | { 107 | return nullptr; 108 | } 109 | }; 110 | 111 | template struct clone_helper; 112 | template 113 | struct clone_helper, I> 114 | { 115 | static constexpr refcount_base* clone(size_t type_idx, const refcount_base* orig) 116 | { 117 | if (type_idx == I) 118 | return static_cast( new refcounted(static_cast*>(orig)->data) ); 119 | else 120 | return clone_helper, I+1>::clone(type_idx, orig); 121 | } 122 | }; 123 | template 124 | struct clone_helper, I> 125 | { 126 | static constexpr refcount_base* clone(size_t, const refcount_base*) 127 | { 128 | return nullptr; 129 | } 130 | }; 131 | 132 | template struct delete_helper; 133 | template 134 | struct delete_helper, I> 135 | { 136 | static void del(size_t type_idx, refcount_base* ptr) 137 | { 138 | if (type_idx == I) 139 | delete static_cast*>(ptr); 140 | else 141 | delete_helper, I+1>::del(type_idx, ptr); 142 | } 143 | }; 144 | template 145 | struct delete_helper, I> 146 | { 147 | static void del(size_t, refcount_base*) 148 | { 149 | throw std::out_of_range("invalid type id"); 150 | } 151 | }; 152 | 153 | template struct is_a_helper; 154 | template 155 | struct is_a_helper, I, S> 156 | { 157 | static constexpr bool is_a(size_t type_idx) 158 | { 159 | if (type_idx == I) 160 | return std::is_base_of::value; 161 | else 162 | return is_a_helper, I+1, S>::is_a(type_idx); 163 | } 164 | }; 165 | template 166 | struct is_a_helper, I, S> 167 | { 168 | static constexpr bool is_a(size_t) 169 | { 170 | throw std::out_of_range("invalid type id"); 171 | return false; 172 | } 173 | }; 174 | 175 | 176 | 177 | // get_helper_2 is used to check whether S is convertible to T at all. 178 | // if yes (ok==true), then magic() checks for that type_idx; 179 | // if no, then magic() just ignores that type in the typelist 180 | // and instantiates get_helper_1 with the remainder of the list 181 | // (get_helper_1 will return nullptr/throw if the remainder is empty) 182 | template struct get_helper_1; 183 | template struct get_helper_2; 184 | 185 | template 186 | struct get_helper_2> 187 | { 188 | static constexpr T* magic(refcount_base* ptr, size_t type_idx) 189 | { 190 | if (type_idx == I) 191 | { 192 | return static_cast( &static_cast*>(ptr)->data ); 193 | } 194 | else 195 | { 196 | return get_helper_1>::magic(ptr, type_idx); 197 | } 198 | } 199 | }; 200 | template 201 | struct get_helper_2> 202 | { 203 | static constexpr T* magic(refcount_base* ptr, size_t type_idx) 204 | { 205 | return get_helper_1>::magic(ptr, type_idx); 206 | } 207 | }; 208 | 209 | template 210 | struct get_helper_1> 211 | : public get_helper_2::value, T, I, typelist> {}; 212 | template 213 | struct get_helper_1> 214 | { 215 | static constexpr T* magic(refcount_base*, size_t) 216 | { 217 | return nullptr; 218 | } 219 | }; 220 | 221 | template constexpr T* get_part (refcount_base* ptr, size_t type_idx) 222 | { 223 | return get_helper_1::magic(ptr, type_idx); 224 | } 225 | 226 | } 227 | 228 | template > struct multivariant_utils; 229 | template 230 | struct multivariant_utils, typelist> 231 | { 232 | static constexpr refcount_base* make(size_t type_idx, As&&... args) 233 | { 234 | return detail::make_helper, 0, typelist>::make(type_idx, std::forward(args)...); 235 | } 236 | static constexpr refcount_base* clone(size_t type_idx, const refcount_base* orig) 237 | { 238 | return detail::clone_helper, 0>::clone(type_idx, orig); 239 | } 240 | static constexpr void del(size_t type_idx, refcount_base* ptr) 241 | { 242 | return detail::delete_helper, 0>::del(type_idx, ptr); 243 | } 244 | template static constexpr T* get(size_t type_idx, refcount_base* ptr) 245 | { 246 | if (T* result = detail::get_helper_1>::magic(ptr, type_idx)) 247 | return result; 248 | else 249 | throw std::runtime_error("impossible cast requested"); 250 | } 251 | template static constexpr T* get_or_null(size_t type_idx, refcount_base* ptr) 252 | { 253 | return detail::get_helper_1>::magic(ptr, type_idx); 254 | } 255 | template static constexpr size_t index() 256 | { 257 | return detail::typelist_index>::value; 258 | } 259 | /** is_a( index() ) == true iif Bar is derived from or equal to Foo, and false otherwise */ 260 | template static constexpr bool is_a(size_t index); 261 | 262 | static constexpr size_t invalid_index = SIZE_MAX; 263 | }; 264 | -------------------------------------------------------------------------------- /pathfinding.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma GCC optimize "-O2" 20 | 21 | #define DEBUG_PATHFINDING 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #ifdef DEBUG_PATHFINDING 28 | #include "logging.hpp" 29 | #endif 30 | 31 | #include 32 | #include 33 | 34 | #include "pathfinding.hpp" 35 | #include "worldmap.hpp" 36 | #include "factorio_io.h" 37 | #include "pos.hpp" 38 | #include "area.hpp" 39 | 40 | using namespace std; 41 | using namespace pathfinding; 42 | 43 | 44 | /** controls the exactness-speed-tradeoff. 45 | * if set to 1.0, this equals the textbook A*-algorithm, which 46 | * requires that the heuristic may under- but never overestimate 47 | * the true distance to the goal. (the direct line as used fulfils 48 | * this). 49 | * if set to >1, this results in a potential overestimation of the 50 | * distance by that factor. This greatly speeds up the algorithm, at 51 | * the cost of up results worse by up to this factor. 52 | */ 53 | constexpr double OVERAPPROXIMATE=1.1; 54 | 55 | static double heuristic(const Pos& p, const Pos& goal) 56 | { 57 | Pos tmp = p-goal; 58 | return sqrt(tmp.x*tmp.x+tmp.y*tmp.y)*OVERAPPROXIMATE; // FIXME overapproximation for speed 59 | } 60 | 61 | vector cleanup_path(const vector& path) 62 | { 63 | vector result; 64 | 65 | if (path.empty()) 66 | return result; 67 | 68 | Pos dir(0,0); 69 | for (size_t i=1; i a_star(const Pos& start, const Area_f& end, WorldMap& map, double allowed_distance, double min_distance, double length_limit, double size) 81 | { 82 | return cleanup_path(a_star_raw(start, end, map, allowed_distance, min_distance, length_limit, size)); 83 | } 84 | 85 | vector a_star_raw(const Pos& start, const Area_f& end, WorldMap& map, double allowed_distance, double min_distance, double length_limit, double size) 86 | { 87 | #ifdef DEBUG_PATHFINDING 88 | Logger log("pathfinding"); 89 | #endif 90 | 91 | log << "a_star from " << start.str() << " to " << end.str() << " (allowed_distance=" << allowed_distance << ", min_distance=" << min_distance << ", length_limit="< result; 103 | 104 | boost::heap::binomial_heap openlist; 105 | 106 | vector needs_cleanup; 107 | 108 | view.at(start).openlist_handle = openlist.push(Entry(start,0.)); 109 | needs_cleanup.push_back(&view.at(start)); 110 | 111 | Logger verboselog("verbose"); 112 | int n_iterations = 0; 113 | while (!openlist.empty()) 114 | { 115 | verboselog << "in iteration #" << n_iterations << ": openlist has size " << openlist.size() << flush; 116 | auto current = openlist.top(); 117 | verboselog << ", top is " << current.pos.str() << ", f=" << current.f << endl; 118 | openlist.pop(); 119 | n_iterations++; 120 | 121 | if (current.f >= length_limit*OVERAPPROXIMATE) // this (and any subsequent) entry is guaranteed 122 | break; // to exceed the length_limit. 123 | 124 | if (distance(current.pos,end) <= allowed_distance && distance(current.pos,end) >= min_distance) 125 | { 126 | // found goal. 127 | 128 | Pos p = current.pos; 129 | 130 | result.push_back(p); 131 | 132 | while (p != start) 133 | { 134 | p = view.at(p).predecessor; 135 | result.push_back(p); 136 | } 137 | 138 | reverse(result.begin(), result.end()); 139 | 140 | #ifdef DEBUG_PATHFINDING 141 | for (auto pos : result) log << pos.str() << " - "; 142 | log<= size/2 && view.at(x, y).margins[WEST] >= size/2 && view.at(x-1,y).can_walk && view.at(x,y).can_walk; 166 | } 167 | else if (step.y == 0) // walking in horizontal direction 168 | { 169 | auto x = min(current.pos.x, successor.x); 170 | auto y = current.pos.y; 171 | can_walk = view.at(x, y-1).margins[SOUTH] >= size/2 && view.at(x, y).margins[NORTH] >= size/2 && view.at(x,y-1).can_walk && view.at(x,y).can_walk; 172 | } 173 | else // walking diagonally 174 | { 175 | auto x = min(current.pos.x, successor.x); 176 | auto y = min(current.pos.y, successor.y); 177 | const auto& v = view.at(x,y); 178 | can_walk = (v.margins[0]>=0.5 && v.margins[1]>=0.5 && v.margins[2]>=0.5 && v.margins[3]>=0.5) && v.can_walk && (view.at(x+step.x, y).can_walk || view.at(x, y+step.y).can_walk); 179 | } 180 | 181 | if (can_walk) 182 | { 183 | verboselog << "; " << successor.str() << flush; 184 | 185 | auto& succ = view.at(successor); 186 | if (succ.in_closedlist) 187 | continue; 188 | 189 | verboselog << "*" << flush; 190 | 191 | double cost = sqrt(step.x*step.x + step.y*step.y); 192 | double new_g = view.at(current.pos).g_val + cost; 193 | 194 | if (succ.openlist_handle != openlist_handle_t() && succ.g_val < new_g) // ignore this successor, when a better way is already known 195 | continue; 196 | 197 | verboselog << "!" << flush; 198 | 199 | double f = new_g + heuristic(successor, end.center()); 200 | succ.predecessor = current.pos; 201 | succ.g_val = new_g; 202 | 203 | verboselog << "f="<openlist_handle = pathfinding::openlist_handle_t(); 225 | w->in_closedlist = false; 226 | } 227 | 228 | #ifdef DEBUG_PATHFINDING 229 | log << "took " << n_iterations << " iterations or " << (n_iterations / max(1.0, (start-end.center()).len())) << " it/dist" << endl; 230 | #endif 231 | 232 | return result; 233 | } 234 | -------------------------------------------------------------------------------- /pathfinding.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include "pos.hpp" 24 | #include "area.hpp" 25 | #include "worldmap.hpp" 26 | 27 | namespace pathfinding 28 | { 29 | struct Entry 30 | { 31 | Pos pos; 32 | double f; // this is (exact best-known-yet distance to pos) + (estimated distance to target) 33 | 34 | Entry(const Pos& p, double f_) : pos(p), f(f_) {} 35 | bool operator<(const Entry& other) const { return f > other.f; } 36 | }; 37 | 38 | typedef boost::heap::binomial_heap::handle_type openlist_handle_t; 39 | 40 | struct walk_t 41 | { 42 | bool known; 43 | bool can_walk; 44 | bool can_cross; 45 | int tree_amount; 46 | double margins[4]; 47 | 48 | 49 | double g_val; 50 | Pos predecessor; 51 | openlist_handle_t openlist_handle; 52 | bool in_closedlist=false; 53 | 54 | walk_t() : known(false), can_walk(true), can_cross(true), tree_amount(0) {} 55 | bool water() const { return known && !can_walk; } // FIXME this is a hack 56 | bool land() const { return known && can_walk; } // FIXME same 57 | }; 58 | 59 | } 60 | 61 | std::vector cleanup_path(const std::vector& path); 62 | /* FIXME maybe deprecate those in favor of FactorioGame::blah? */ 63 | 64 | /** calculates a path from start into the disc around end, with outer radius allowed_distance 65 | * and inner radius min_distance. If length_limit is positive, the search will abort early 66 | * if the path is guaranteed to be longer than length_limit. Size specifies the width of 67 | * the character; 0.5 is usually a good value. */ 68 | std::vector a_star(const Pos& start, const Area_f& end, WorldMap& map, double allowed_distance=1., double min_distance=0., double length_limit=std::numeric_limits::infinity(), double size=0.5); 69 | std::vector a_star_raw(const Pos& start, const Area_f& end, WorldMap& map, double allowed_distance=1., double min_distance=0., double length_limit=std::numeric_limits::infinity(), double size=0.5); 70 | 71 | [[deprecated]] inline std::vector a_star(const Pos& start, const Pos& end, WorldMap& map, double allowed_distance=1., double min_distance=0., double length_limit=std::numeric_limits::infinity(), double size=0.5) { return a_star(start, Area_f(end,end), map, allowed_distance, min_distance, length_limit, size); } 72 | [[deprecated]] inline std::vector a_star_raw(const Pos& start, const Pos& end, WorldMap& map, double allowed_distance=1., double min_distance=0., double length_limit=std::numeric_limits::infinity(), double size=0.5) { return a_star_raw(start, Area_f(end,end), map, allowed_distance, min_distance, length_limit, size); } 73 | -------------------------------------------------------------------------------- /player.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include "pos.hpp" 22 | #include "action.hpp" 23 | #include "inventory.hpp" 24 | 25 | #include 26 | #include 27 | 28 | struct Player 29 | { 30 | size_t id; // TODO this should be a string (the player's name) probably? 31 | Pos_f position; 32 | bool connected; 33 | 34 | TaggedInventory inventory; 35 | 36 | /** sets the players actions, starts them (if not nullptr) (and aborts the previous, if any). 37 | * If called with nullptr, then it will only abort the previous action. */ 38 | void set_actions(std::shared_ptr a) 39 | { 40 | if (actions) 41 | actions->abort(); 42 | actions = a; 43 | if (actions) 44 | action::registry.start_action(actions); 45 | } 46 | 47 | /** ticks the player's action, if set. returns true, if the action has finished */ 48 | bool tick_actions() 49 | { 50 | if (actions) 51 | { 52 | if (actions->is_finished()) 53 | { 54 | actions = nullptr; 55 | return true; 56 | } 57 | else 58 | { 59 | actions->tick(); 60 | return false; 61 | } 62 | } 63 | else 64 | return false; 65 | } 66 | 67 | private: 68 | std::shared_ptr actions; 69 | 70 | friend class FactorioGame; 71 | }; 72 | -------------------------------------------------------------------------------- /pos.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | #include 21 | #include 22 | #include 23 | #include "util.hpp" 24 | 25 | constexpr int tileidx(int x) 26 | { 27 | return ((x % 32) + 32) % 32; 28 | } 29 | 30 | constexpr int chunkidx(int x) 31 | { 32 | return (x>=0) ? x/32 : (x-31)/32; 33 | } 34 | 35 | template 36 | struct Pos_ 37 | { 38 | T x,y; 39 | constexpr Pos_(T x_, T y_) : x(x_), y(y_) {} 40 | constexpr Pos_() : x(0), y(0) {} 41 | template constexpr Pos_(const Pos_& other) : x(T(other.x)), y(T(other.y)) {} 42 | 43 | constexpr Pos_ operator+(const Pos_& b) const { return Pos_(x+b.x, y+b.y); } 44 | constexpr Pos_ operator-(const Pos_& b) const { return Pos_(x-b.x, y-b.y); } 45 | constexpr Pos_ operator*(T f) const { return Pos_(x*f, y*f); } 46 | 47 | 48 | Pos_ operator/(T f) const; 49 | 50 | constexpr bool operator==(const Pos_ that) const 51 | { 52 | #pragma GCC diagnostic push 53 | #pragma GCC diagnostic ignored "-Wfloat-equal" 54 | return this->x == that.x && this->y == that.y; 55 | #pragma GCC diagnostic pop 56 | } 57 | 58 | constexpr bool operator!=(const Pos_ that) const { return !operator==(that); } 59 | 60 | constexpr bool operator< (const Pos_& that) const { return this->x < that.x && this->y < that.y; } 61 | constexpr bool operator<=(const Pos_& that) const { return this->x <= that.x && this->y <= that.y; } 62 | constexpr bool operator> (const Pos_& that) const { return this->x > that.x && this->y > that.y; } 63 | constexpr bool operator>=(const Pos_& that) const { return this->x >= that.x && this->y >= that.y; } 64 | 65 | constexpr bool less_rowwise(const Pos_& that) const { 66 | if (this->y < that.y) return true; 67 | if (this->y > that.y) return false; 68 | else return this->x < that.x; 69 | } 70 | constexpr bool less_columnwise(const Pos_& that) const { 71 | if (this->x < that.x) return true; 72 | if (this->x > that.x) return false; 73 | else return this->y < that.y; 74 | } 75 | 76 | double len() const { return std::sqrt(x*x+y*y); } 77 | 78 | std::string str() const { return std::to_string(x) + "," + std::to_string(y); } 79 | 80 | Pos_ to_int() const { return Pos_(int(std::round(x)), int(std::round(y))); } 81 | Pos_ to_int_floor() const { return Pos_(int(std::floor(x)), int(std::floor(y))); } 82 | Pos_ to_double() const { return Pos_(x,y); } 83 | 84 | static Pos_ tile_to_chunk(const Pos_& p) { return Pos_(chunkidx(p.x), chunkidx(p.y)); } 85 | static Pos_ tile_to_chunk_ceil(const Pos_& p) { return Pos_(chunkidx(p.x+31), chunkidx(p.y+31)); } 86 | static Pos_ chunk_to_tile(const Pos_& p) { return Pos_(p.x*32, p.y*32); } 87 | }; 88 | 89 | template <> inline Pos_ Pos_::operator/(int f) const { return Pos_(sane_div(x,f), sane_div(y,f)); } 90 | template <> inline Pos_ Pos_::operator/(double f) const { return Pos_(x/f,y/f); } 91 | 92 | typedef Pos_ Pos; 93 | typedef Pos_ Pos_f; 94 | 95 | namespace std { 96 | template <> struct hash 97 | { 98 | typedef Pos argument_type; 99 | typedef std::size_t result_type; 100 | result_type operator()(argument_type const& p) const 101 | { 102 | result_type const h1( std::hash{}(p.x) ); 103 | result_type const h2( std::hash{}(p.y) ); 104 | return h1 ^ (h2 << 1); 105 | } 106 | }; 107 | } 108 | 109 | #pragma GCC diagnostic push 110 | #pragma GCC diagnostic ignored "-Wmissing-variable-declarations" 111 | inline Pos directions4[4] = { Pos(0,-1), Pos(1,0), Pos(0,1), Pos(-1,0) }; 112 | #pragma GCC diagnostic pop 113 | -------------------------------------------------------------------------------- /prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CONF=botconfig.conf 4 | 5 | set -e 6 | 7 | if [ ! $# -eq 2 ]; then 8 | echo "Usage:" 9 | echo " $0 path/to/factorio_alpha_x64_0.14.22.tar.gz /installation/path" 10 | echo "" 11 | echo "This tool will install factorio to /installation/path/, prepare the mod and" 12 | echo "generate a config file into the current directory" 13 | exit 0 14 | fi 15 | 16 | if [ -e $CONF ]; then 17 | echo "Error: $CONF already exists, refusing to prepare anything." 18 | exit 1 19 | fi 20 | 21 | FACTORIO_TGZ="$1" 22 | INSTALLPATH="`realpath "$2"`" 23 | 24 | mkdir -p "$INSTALLPATH" 25 | 26 | # unpack factorio 27 | mkdir -p "$INSTALLPATH" 28 | tar -x -v -C "$INSTALLPATH" -f "$FACTORIO_TGZ" 29 | 30 | # install the mod 31 | mkdir -p "$INSTALLPATH/factorio/mods" 32 | ln -sr "./luamod/Windfisch_0.0.1" "$INSTALLPATH/factorio/mods/" 33 | cat > "$INSTALLPATH/factorio/mods/mod-list.json" << EOF 34 | { 35 | "mods": [ 36 | { 37 | "name": "base", 38 | "enabled": "true" 39 | }, 40 | { 41 | "name": "Windfisch", 42 | "enabled": "true" 43 | } 44 | ] 45 | } 46 | EOF 47 | 48 | 49 | # server installation 50 | mv "$INSTALLPATH/factorio" "$INSTALLPATH/server" 51 | 52 | cat > "$INSTALLPATH/server-settings.json" << EOF 53 | { 54 | "name": "Bot Test Game", 55 | "description": "A game for testing the bot", 56 | "tags": ["ai", "bot"], 57 | 58 | "_comment_max_players": "Maximum number of players allowed, admins can join even a full server. 0 means unlimited.", 59 | "max_players": 0, 60 | 61 | "_comment_visibility": ["public: Game will be published on the official Factorio matching server", 62 | "lan: Game will be broadcast on LAN"], 63 | "visibility": 64 | { 65 | "public": false, 66 | "lan": false 67 | }, 68 | 69 | "_comment_credentials": "Your factorio.com login credentials. Required for games with visibility public", 70 | "username": "", 71 | "password": "", 72 | 73 | "_comment_token": "Authentication token. May be used instead of 'password' above.", 74 | "token": "", 75 | 76 | "game_password": "", 77 | 78 | "_comment_require_user_verification": "When set to true, the server will only allow clients that have a valid Factorio.com account", 79 | "require_user_verification": false, 80 | 81 | "_comment_max_upload_in_kilobytes_per_second" : "optional, default value is 0. 0 means unlimited.", 82 | "max_upload_in_kilobytes_per_second": 0, 83 | 84 | "_comment_minimum_latency_in_ticks": "optional one tick is 16ms in default speed, default value is 0. 0 means no minimum.", 85 | "minimum_latency_in_ticks": 0, 86 | 87 | "_comment_ignore_player_limit_for_returning_players": "Players that played on this map already can join even when the max player limit was reached.", 88 | "ignore_player_limit_for_returning_players": false, 89 | 90 | "_comment_allow_commands": "possible values are, true, false and admins-only", 91 | "allow_commands": "true", 92 | 93 | "_comment_autosave_interval": "Autosave interval in minutes", 94 | "autosave_interval": 10, 95 | 96 | "_comment_autosave_slots": "server autosave slots, it is cycled through when the server autosaves.", 97 | "autosave_slots": 5, 98 | 99 | "_comment_afk_autokick_interval": "How many minutes until someone is kicked when doing nothing, 0 for never.", 100 | "afk_autokick_interval": 0, 101 | 102 | "_comment_auto_pause": "Whether should the server be paused when no players are present.", 103 | "auto_pause": false, 104 | 105 | "only_admins_can_pause_the_game": false, 106 | 107 | "_comment_autosave_only_on_server": "Whether autosaves should be saved only on server or also on all connected clients. Default is true.", 108 | "autosave_only_on_server": true, 109 | 110 | "_comment_admins": "List of case insensitive usernames, that will be promoted immediately", 111 | "admins": [] 112 | } 113 | EOF 114 | 115 | # the client directories are just hardlinked copies to save disk space 116 | # create a config for them with a different name 117 | for client in Nayru Farore; do 118 | cp -rlP --preserve=links "$INSTALLPATH/server" "$INSTALLPATH/$client" 119 | cat > "$INSTALLPATH/$client/player-data.json" << EOF 120 | { 121 | "latest-multiplayer-connections": [ 122 | { 123 | "address": "localhost" 124 | } 125 | ], 126 | "service-username": "$client", 127 | "service-token": "" 128 | } 129 | EOF 130 | done 131 | 132 | cat > $CONF << EOF 133 | INSTALLPATH='$INSTALLPATH' 134 | RCON_PORT=1234 135 | RCON_PASS=rcon123 136 | MAP=map.zip 137 | 138 | # MAP is either an absolute path, or relative to INSTALLPATH 139 | EOF 140 | 141 | "$INSTALLPATH/server/bin/x64/factorio" --create "$INSTALLPATH/map.zip" 142 | 143 | echo 144 | echo 145 | echo "installed factorio to '$INSTALLPATH/'." 146 | echo "created config file '$CONF'." 147 | echo "created map at '$INSTALLPATH/map.zip'." 148 | -------------------------------------------------------------------------------- /rcon-client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "rcon.h" 23 | 24 | using namespace std; 25 | 26 | int main(int argc, char** argv) 27 | { 28 | if (argc < 5) 29 | { 30 | cout << "Usage: " << argv[0] << " 'host' 'port' 'password' 'command'" << endl; 31 | return 1; 32 | } 33 | 34 | Rcon rcon(argv[1], atoi(argv[2]), argv[3]); 35 | cout << "result: " << rcon.sendrecv(argv[4]).data << endl; 36 | } 37 | -------------------------------------------------------------------------------- /rcon.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "logging.hpp" 31 | 32 | #include "rcon.h" 33 | 34 | 35 | static int connect_socket(const char* address, int port) 36 | { 37 | #pragma GCC diagnostic push 38 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" 39 | struct addrinfo hints = { 0 }; 40 | #pragma GCC diagnostic pop 41 | hints.ai_family = AF_UNSPEC; 42 | hints.ai_socktype = SOCK_STREAM; 43 | 44 | struct addrinfo *result, *rp; 45 | 46 | std::string portstr = std::to_string(port); 47 | 48 | int sock = -1; 49 | int s = getaddrinfo(address, portstr.c_str(), &hints, &result); 50 | if (s) 51 | throw std::runtime_error("getaddrinfo() failed"); 52 | 53 | for (rp = result; rp; rp=rp->ai_next) 54 | { 55 | sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 56 | if (sock == -1) 57 | continue; 58 | 59 | if (connect(sock, rp->ai_addr, rp->ai_addrlen) == -1) 60 | { 61 | close(sock); 62 | continue; 63 | } 64 | else 65 | { 66 | break; 67 | } 68 | } 69 | 70 | freeaddrinfo(result); 71 | 72 | if (sock < 0) 73 | throw std::runtime_error("could not establish a connection"); 74 | else 75 | return sock; 76 | } 77 | 78 | static void serialize_uint32(uint8_t* buf, uint32_t num) 79 | { 80 | buf[0] = (num ) & 0xFF; 81 | buf[1] = (num >> 8) & 0xFF; 82 | buf[2] = (num >> 16) & 0xFF; 83 | buf[3] = (num >> 24) & 0xFF; 84 | } 85 | 86 | static uint32_t deserialize_uint32(const uint8_t* buf) 87 | { 88 | return uint32_t(buf[0]) + 89 | (uint32_t(buf[1]) << 8) + 90 | (uint32_t(buf[2]) << 16) + 91 | (uint32_t(buf[3]) << 24); 92 | } 93 | 94 | static void recvn(int sockfd, uint8_t* buf, size_t len) 95 | { 96 | size_t i = 0; 97 | ssize_t n; 98 | while (i < len) 99 | { 100 | n = ::recv(sockfd, buf+i, len-i, 0); 101 | if (n < 0) 102 | throw std::system_error(errno, std::generic_category(), "recv failed"); 103 | else if (n == 0) 104 | throw std::runtime_error("remote closed the connection prematurely"); 105 | else 106 | i+=n; 107 | } 108 | } 109 | 110 | void Rcon::connect(std::string host, int port) 111 | { 112 | sockfd = connect_socket(host.c_str(), port); 113 | } 114 | 115 | void Rcon::connect(std::string host, int port, std::string password) 116 | { 117 | sockfd = connect_socket(host.c_str(), port); 118 | 119 | send(0xDEADBEEF, AUTH, password.c_str()); 120 | Packet reply = recv(); 121 | 122 | if (reply.id != 0xDEADBEEF || reply.type != AUTH_RESPONSE) 123 | throw std::runtime_error("unable to authenticate"); 124 | } 125 | 126 | void Rcon::Packet::dump() 127 | { 128 | Logger log("rcon_dump"); 129 | log << "id=" << id << ", type=" << type << ", data=" << data << ", datalen=" << data.length() << std::endl; 130 | } 131 | 132 | void Rcon::send(uint32_t id, pkgtype type, const std::string& data) 133 | { 134 | if (data.length() > MAXLEN - 10 || data.length() < 1) 135 | throw std::invalid_argument("illegal packet length in Rcon::send"); 136 | 137 | uint8_t buf[4+MAXLEN]; 138 | uint32_t len = static_cast(data.length()); 139 | 140 | serialize_uint32(buf , 10+len); 141 | serialize_uint32(buf+4, id); 142 | serialize_uint32(buf+8, type); 143 | memcpy(buf+12, data.c_str(), len); 144 | buf[12+len] = 0; 145 | buf[13+len] = 0; 146 | 147 | if (-1 == ::send(sockfd, buf, 14+len, 0)) 148 | throw std::system_error(errno, std::generic_category(), "send failed"); 149 | } 150 | 151 | Rcon::Packet Rcon::recv() { 152 | uint8_t lenbuf[4]; 153 | Packet pkg; 154 | 155 | recvn(sockfd, lenbuf, 4); 156 | 157 | size_t len = deserialize_uint32(lenbuf); 158 | if (len > MAXLEN) 159 | throw std::runtime_error("received packet of invalid length"); 160 | 161 | uint8_t pkgbuf[MAXLEN]; 162 | recvn(sockfd, pkgbuf, len); 163 | 164 | pkg.id = deserialize_uint32(pkgbuf + 0); 165 | pkg.type = static_cast(deserialize_uint32(pkgbuf + 4)); 166 | pkg.data = std::string(reinterpret_cast(pkgbuf+8), len-10); 167 | 168 | //pkg.dump(); 169 | 170 | return pkg; 171 | } 172 | 173 | Rcon::Packet Rcon::sendrecv(const std::string& data) 174 | { 175 | send(++curr_id, EXECCOMMAND, data); 176 | Packet pkg = recv(); 177 | if (pkg.id != curr_id) 178 | throw std::runtime_error("sendrecv() received invalid ID"); 179 | return pkg; 180 | } 181 | -------------------------------------------------------------------------------- /rcon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | class Rcon 23 | { 24 | private: 25 | int sockfd = -1; 26 | uint32_t curr_id = 0; 27 | 28 | public: 29 | static const int MAXLEN = 4096; 30 | 31 | Rcon() {} 32 | Rcon(std::string host, int port) { connect(host, port); } 33 | Rcon(std::string host, int port, std::string password) { connect(host, port, password); } 34 | 35 | bool connected() { return sockfd!=-1; } 36 | 37 | enum pkgtype 38 | { 39 | AUTH = 3, 40 | AUTH_RESPONSE = 2, 41 | EXECCOMMAND = 2, 42 | RESPONSE_VALUE = 0 43 | }; 44 | 45 | struct Packet 46 | { 47 | uint32_t id; 48 | pkgtype type; 49 | std::string data; 50 | 51 | Packet(uint32_t id_, pkgtype type_, const std::string& data_) : id(id_), type(type_), data(data_) {} 52 | Packet() {} 53 | 54 | void dump(); 55 | }; 56 | 57 | void connect(std::string host, int port); 58 | void connect(std::string host, int port, std::string password); 59 | 60 | void send(uint32_t id, pkgtype type, const std::string& data); 61 | Packet recv(); 62 | Packet sendrecv(const std::string& data); 63 | }; 64 | -------------------------------------------------------------------------------- /recipe.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include "item.h" 26 | #include "inventory.hpp" 27 | 28 | struct Recipe 29 | { 30 | struct ItemAmount 31 | { 32 | const ItemPrototype* item; 33 | double amount; // this is mostly int, but some recipes have probabilities. 34 | // in this case, the average value is stored here. 35 | 36 | ItemAmount(const ItemPrototype* item_, double amount_) : item(item_),amount(amount_) {} 37 | }; 38 | 39 | std::string name; 40 | bool enabled; 41 | double energy; 42 | 43 | std::chrono::milliseconds crafting_duration() const { 44 | return std::chrono::milliseconds(int(energy*1000.)); 45 | } 46 | 47 | std::vector ingredients; 48 | std::vector products; 49 | 50 | Inventory get_itemamounts(const std::vector& amounts) const 51 | { 52 | Inventory result; 53 | for (const auto& [item, amount] : amounts) 54 | { 55 | if (amount < 0. || double(size_t(amount)) != amount) 56 | throw std::runtime_error("item amounts must be integers"); 57 | result[item] += size_t(amount); 58 | } 59 | return result; 60 | } 61 | 62 | Inventory get_ingredients() const { return get_itemamounts(ingredients); } 63 | Inventory get_products() const { return get_itemamounts(products); } 64 | 65 | /** returns the balance for the specified item. positive values means 66 | * that the item is produced, negative means consumed. */ 67 | int balance_for(const ItemPrototype* item) const 68 | { 69 | int balance = 0; 70 | for (auto [product,amount] : products) 71 | if (product == item) 72 | balance += amount; 73 | 74 | for (auto [ingredient,amount] : ingredients) 75 | if (ingredient == item) 76 | balance -= amount; 77 | 78 | return balance; 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /resource.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "pos.hpp" 28 | #include "area.hpp" 29 | #include "entity.h" 30 | 31 | struct ResourcePatch; 32 | 33 | constexpr int NOT_YET_ASSIGNED = 0; // TODO FIXME 34 | 35 | struct Resource 36 | { 37 | enum type_t 38 | { 39 | NONE = 0, 40 | COAL, 41 | IRON, 42 | COPPER, 43 | STONE, 44 | OIL, 45 | URANIUM, 46 | OCEAN, 47 | N_RESOURCES 48 | }; 49 | static const std::unordered_map types; 50 | static const std::string typestr[]; 51 | 52 | enum floodfill_flag_t 53 | { 54 | FLOODFILL_NONE, 55 | FLOODFILL_QUEUED 56 | }; 57 | 58 | floodfill_flag_t floodfill_flag = FLOODFILL_NONE; 59 | 60 | type_t type; 61 | int patch_id; 62 | std::weak_ptr resource_patch; 63 | Entity entity; 64 | 65 | Resource(type_t t, int parent, Entity e) : type(t), patch_id(parent), entity(e) {} 66 | Resource() : type(NONE), patch_id(NOT_YET_ASSIGNED), entity(Entity::nullent_tag{}) {} 67 | }; 68 | 69 | struct ResourcePatch 70 | { 71 | std::vector positions; 72 | Resource::type_t type; 73 | int patch_id; 74 | Area bounding_box; 75 | 76 | ResourcePatch(const std::vector& positions_, Resource::type_t t, int id) : positions(std::move(positions_)), type(t), patch_id(id) 77 | { 78 | recalc_bounding_box(); 79 | } 80 | 81 | void merge_into(ResourcePatch& other) 82 | { 83 | assert(this->type == other.type); 84 | other.extend(this->positions); 85 | } 86 | 87 | void extend(const std::vector& newstuff) 88 | { 89 | positions.insert(positions.end(), newstuff.begin(), newstuff.end()); 90 | recalc_bounding_box(); 91 | } 92 | 93 | bool remove(Pos pos) // FIXME slow std::find usage 94 | { 95 | auto iter = std::find(positions.begin(), positions.end(), pos); 96 | if (iter == positions.end()) 97 | return false; 98 | unordered_erase(positions, iter); 99 | return true; 100 | } 101 | 102 | size_t size() const { return positions.size(); } 103 | 104 | private: 105 | void recalc_bounding_box() 106 | { 107 | bool first = true; 108 | for (const Pos& p : positions) 109 | { 110 | if (p.x < bounding_box.left_top.x || first) 111 | bounding_box.left_top.x = p.x; 112 | if (p.x >= bounding_box.right_bottom.x || first) 113 | bounding_box.right_bottom.x = p.x+1; 114 | if (p.y < bounding_box.left_top.y || first) 115 | bounding_box.left_top.y = p.y; 116 | if (p.y >= bounding_box.right_bottom.y || first) 117 | bounding_box.right_bottom.y = p.y+1; 118 | 119 | first = false; 120 | } 121 | } 122 | }; 123 | 124 | -------------------------------------------------------------------------------- /safe_cast.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | template // signed -> signed 7 | inline typename std::enable_if::value && std::is_signed::value, To>::type safe_cast(From value) 8 | { 9 | assert (value >= std::numeric_limits::min() && value <= std::numeric_limits::max()); 10 | return static_cast(value); 11 | } 12 | 13 | template // signed -> unsigned 14 | inline typename std::enable_if::value && std::is_unsigned::value, To>::type safe_cast(From value) 15 | { 16 | assert (value <= static_cast::type>(std::numeric_limits::max())); 17 | return static_cast(value); 18 | } 19 | 20 | template // unsigned -> signed 21 | inline typename std::enable_if::value && std::is_signed::value, To>::type safe_cast(From value) 22 | { 23 | assert (value >= 0 && static_cast::type>(value) <= std::numeric_limits::max()); 24 | return static_cast(value); 25 | } 26 | 27 | template // unsigned -> unsigned 28 | inline typename std::enable_if::value && std::is_unsigned::value, To>::type safe_cast(From value) 29 | { 30 | assert (value <= std::numeric_limits::max()); 31 | return static_cast(value); 32 | } 33 | -------------------------------------------------------------------------------- /sched.txt: -------------------------------------------------------------------------------- 1 | list< pair > inventory; 2 | 3 | prio_queue pending; Task active; 4 | für pending[0] -> active: 5 | 6 | - auflösen, wie wir an rohstoffe kommen (betrachte das inventar 7 | abzüglich alle fremdgeclaimte sachen aber plus alles was drin wäre, wenn die 8 | noch schedulededn Crafts durchgelaufen wären.) 9 | -> list 10 | - neuer Actions, direkt als erste, die nur Sachen 11 | einsammeln und ihnen direkt uns als Owner gibt. 12 | + list // aufpassen auf impliziten verbrauch 13 | + änderung am Inventory: 14 | - dinge claimen 15 | (falls wir interrupted werden, dann wird der 16 | einlast-algo sowieso erneut durchlaufen, und ggf 17 | geklaute dinge wiederbeschaffen) 18 | 19 | - auflösen, wie wir zum ort des Tasks kommen (und berechnen, wie lang 20 | das dauert) (das ist vermutlich ein TSP zusammen mit "rohstoffe 21 | sammeln") 22 | 23 | - list in die globale CraftPrioQueue einfügen, gemäß unserer 24 | Prio. 25 | -> es wird immer der oberste umsetzbare Eintrag gecraftet. 26 | es ist garantiert, dass jeder Eintrag irgendwann umgesetzt 27 | wird, weil irgendwann seine Requirements da sind. 28 | 29 | - dann werden sofort alle list-einträge abgearbeitet. 30 | - sobald fertig: wir kommen von active in waiting_for_craft. 31 | -> neuer (niederpriorer) task aus pending wird gesucht, der 32 | innerhalb unserer ETA abgearbeitet werden kann. 33 | (der MUSS entweder keine crafts haben, oder vor unseren 34 | crafts eingereiht werden können) 35 | -> der landet evtl. auch in waiting_for_craft... allerdings 36 | mit höherer craftingprio, damit wird er garantiert vor uns 37 | wieder entnommen 38 | 39 | Bei einem Interrupt durch höherprioren Task: 40 | - active -> pending gemäß seiner Prio (davor aber alle Collect-Actions 41 | wegräumen) 42 | - alle Crafts bleiben in der Crafting-Queue 43 | - alle Inventory-Claims bleiben geclaimt 44 | 45 | Beim Reschedule: 46 | - wir -> active 47 | - JETZT clearen wir all unsere ausstehenden Crafts und berechnen sie neu 48 | - berechnen Collects neu und fügen sie vorne an 49 | - claimen nach bedarf zeug (sollte nie passieren) 50 | -------------------------------------------------------------------------------- /screenshots/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windfisch/factorio-bot/ccef051f0d7db5b6cf9160e2bc5ef418b4bceabc/screenshots/overview.png -------------------------------------------------------------------------------- /screenshots/zoomed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windfisch/factorio-bot/ccef051f0d7db5b6cf9160e2bc5ef418b4bceabc/screenshots/zoomed.png -------------------------------------------------------------------------------- /split.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, 2018 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // TODO FIXME: iterable thingy 25 | 26 | static std::vector split(const std::string& data, char delim=' ') 27 | { 28 | std::istringstream str(data); 29 | std::string entry; 30 | std::vector result; 31 | 32 | if (data.empty()) 33 | return result; 34 | 35 | do { 36 | getline(str, entry, delim); 37 | result.push_back(std::move(entry)); 38 | } while (!str.eof()); 39 | 40 | return result; 41 | } 42 | 43 | template T convert(std::string const& s) 44 | { 45 | if constexpr (std::is_integral_v) 46 | return T(stoi(s)); 47 | else if constexpr (std::is_floating_point_v) 48 | return T(stod(s)); 49 | else if constexpr (std::is_same_v) 50 | return s; 51 | else 52 | return T(s); 53 | } 54 | 55 | template 56 | std::tuple unpack(std::vector const& v, std::index_sequence) 57 | { 58 | return {convert(v[Is])...}; 59 | } 60 | 61 | template std::tuple unpack(std::vector const& v) 62 | { 63 | return unpack(v, std::index_sequence_for{}); 64 | } 65 | 66 | template std::tuple unpack(const std::string& str, char delim=' ') 67 | { 68 | return unpack(split(str, delim)); 69 | } 70 | 71 | // Usage: auto [foo, bar, baz] = unpack(my_vector) 72 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | scheduler 3 | worldlist 4 | -------------------------------------------------------------------------------- /test/claim.cpp: -------------------------------------------------------------------------------- 1 | #include "../inventory.hpp" 2 | #include 3 | #include 4 | using namespace std; 5 | 6 | TaggedAmount am; 7 | shared_ptr tasks[10]; 8 | 9 | static void claim(int i, int n) 10 | { 11 | cout << "claiming " << n << " for task " << i << " gave " << am.claim(tasks[i], n); 12 | cout << "\tfree -> " << (am.amount - am.n_claimed()); 13 | for (auto& c : am.claims) 14 | cout << ", " << c.owner.lock()->priority << " -> " << c.amount; 15 | cout << endl; 16 | } 17 | 18 | int main() 19 | { 20 | for (int i=0; i<10; i++) 21 | { 22 | tasks[i] = make_shared(); 23 | tasks[i]->priority = i; 24 | } 25 | 26 | am.amount = 100; 27 | 28 | claim(5,20); 29 | claim(5,30); 30 | claim(2,40); 31 | claim(7,30); 32 | claim(3,5); 33 | claim(1,99); 34 | claim(0,300); 35 | claim(4,30); 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /test/worldlist.cpp: -------------------------------------------------------------------------------- 1 | /* This WorldList implementation is dual-licensed under both the 2 | * terms of the MIT and the GPL3 license. You can choose which 3 | * of those licenses you want to use. Note that the MIT option 4 | * only applies to this file, and not to the rest of 5 | * factorio-bot (unless stated otherwise). 6 | */ 7 | 8 | /* 9 | * Copyright (c) 2017, 2018 Florian Jung 10 | * 11 | * This file is part of factorio-bot. 12 | * 13 | * factorio-bot is free software: you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public License, 15 | * version 3, as published by the Free Software Foundation. 16 | * 17 | * factorio-bot is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with factorio-bot. If not, see . 24 | */ 25 | 26 | /* 27 | * MIT License 28 | * 29 | * Copyright (c) 2017, 2018 Florian Jung 30 | * 31 | * Permission is hereby granted, free of charge, to any person obtaining a 32 | * copy of this templated WorldList implementation and associated 33 | * documentation files (the "Software"), to deal in the Software without 34 | * restriction, including without limitation the rights to use, copy, modify, 35 | * merge, publish, distribute, sublicense, and/or sell copies of the 36 | * Software, and to permit persons to whom the Software is furnished to do 37 | * so, subject to the following conditions: 38 | * 39 | * The above copyright notice and this permission notice shall be included in 40 | * all copies or substantial portions of the Software. 41 | * 42 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 45 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 47 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 48 | * DEALINGS IN THE SOFTWARE. 49 | */ 50 | 51 | #include "../worldlist.hpp" 52 | #include "../entity.h" 53 | 54 | #include 55 | #include 56 | #include 57 | 58 | using namespace std; 59 | 60 | typedef WorldList WL; 61 | 62 | 63 | static void show(const WL& l); 64 | static void test_erase(WL l, size_t i); 65 | static void test_around(WL l, Pos_f center); 66 | static void test_around_erase(WL l, Pos_f center, size_t idx); 67 | static WL makeWL(); 68 | 69 | static void show(const WL& l) 70 | { 71 | WL::ConstWithinRange r(l.within_range( Area_f(0,0,100,100) )); 72 | for (auto& x : r) { cout << "\t" << x.pos.str() << endl; } 73 | } 74 | 75 | 76 | static void test_erase(WL l, size_t i) 77 | { 78 | cout << "erasing element #" << i << ": "; 79 | 80 | WL::WithinRange r = l.within_range( Area_f(0,0,100,100) ); 81 | 82 | WL::WithinRange::iterator it = r.begin(); 83 | advance(it, i); 84 | 85 | cout << "elem = " << it->pos.str(); 86 | 87 | it = l.erase(it); 88 | 89 | if (it == r.end()) 90 | cout << ", next = (end)" << endl; 91 | else 92 | cout << ", next = " << it->pos.str() << endl; 93 | 94 | show(l); 95 | cout << endl; 96 | } 97 | 98 | static void test_around(WL l, Pos_f center) 99 | { 100 | WL::Around s = l.around(center); 101 | 102 | int i=0; 103 | cout << "sorting around " << center.str() << endl; 104 | for (WL::Around::iterator it = s.begin(); it != s.end(); it++, i++) 105 | cout << "\t" << it->pos.str() << "\t(dist = " << (it->pos-center).len() << ")" << endl; 106 | 107 | cout << "\tthat's " << i << " objects" << endl; 108 | } 109 | 110 | static void test_around_erase(WL l, Pos_f center, size_t idx) 111 | { 112 | WL::Around s = l.around(center); 113 | 114 | int i=0; 115 | cout << "erasing "<pos.str() << "\t(dist = " << (it->pos-center).len() << ")" << endl; 123 | 124 | cout << "\tthat's " << i << " objects" << endl; 125 | } 126 | 127 | static EntityPrototype ent_proto("","","",{},true,{}); 128 | 129 | static WL makeWL() 130 | { 131 | WL l; 132 | l.insert(Entity(Pos_f(2.5,80.2), &ent_proto)); 133 | l.insert(Entity(Pos_f(0.4,0.2), &ent_proto)); 134 | l.insert(Entity(Pos_f(35.4,1.5), &ent_proto)); 135 | l.insert(Entity(Pos_f(99.4,1.5), &ent_proto)); 136 | l.insert(Entity(Pos_f(99.5,1.5), &ent_proto)); 137 | l.insert(Entity(Pos_f(100.6,1.5), &ent_proto)); 138 | l.insert(Entity(Pos_f(99.7,1.5), &ent_proto)); 139 | l.insert(Entity(Pos_f(100.8,1.5), &ent_proto)); 140 | l.insert(Entity(Pos_f(99.4,41.5), &ent_proto)); 141 | l.insert(Entity(Pos_f(99.5,41.5), &ent_proto)); 142 | l.insert(Entity(Pos_f(100.6,41.5), &ent_proto)); 143 | l.insert(Entity(Pos_f(99.7,41.5), &ent_proto)); 144 | l.insert(Entity(Pos_f(99.8,41.5), &ent_proto)); 145 | l.insert(Entity(Pos_f(2.5,5.2), &ent_proto)); 146 | l.insert(Entity(Pos_f(-4.2,4), &ent_proto)); 147 | l.insert(Entity(Pos_f(2.5,101), &ent_proto)); 148 | l.insert(Entity(Pos_f(101,1.0), &ent_proto)); 149 | return l; 150 | } 151 | 152 | int main() 153 | { 154 | cout << "original" << endl; 155 | show(makeWL()); 156 | cout << endl; 157 | 158 | for (size_t i=0; i<11; i++) 159 | test_erase(makeWL(), i); 160 | 161 | test_around(makeWL(), Pos_f(0.,0.)); 162 | test_around(makeWL(), Pos_f(70.,35.)); 163 | test_around(makeWL(), Pos_f(0.,-9999.)); 164 | 165 | test_around_erase(makeWL(), Pos_f(0.,0.), 2); 166 | } 167 | -------------------------------------------------------------------------------- /test/worldlist.run.desired: -------------------------------------------------------------------------------- 1 | original 2 | 0.400000,0.200000 3 | 2.500000,5.200000 4 | 35.400000,1.500000 5 | 99.400000,1.500000 6 | 99.500000,1.500000 7 | 99.700000,1.500000 8 | 99.400000,41.500000 9 | 99.500000,41.500000 10 | 99.700000,41.500000 11 | 99.800000,41.500000 12 | 2.500000,80.200000 13 | 14 | erasing element #0: elem = 0.400000,0.200000, next = 2.500000,5.200000 15 | 2.500000,5.200000 16 | 35.400000,1.500000 17 | 99.400000,1.500000 18 | 99.500000,1.500000 19 | 99.700000,1.500000 20 | 99.400000,41.500000 21 | 99.500000,41.500000 22 | 99.700000,41.500000 23 | 99.800000,41.500000 24 | 2.500000,80.200000 25 | 26 | erasing element #1: elem = 2.500000,5.200000, next = 35.400000,1.500000 27 | 0.400000,0.200000 28 | 35.400000,1.500000 29 | 99.400000,1.500000 30 | 99.500000,1.500000 31 | 99.700000,1.500000 32 | 99.400000,41.500000 33 | 99.500000,41.500000 34 | 99.700000,41.500000 35 | 99.800000,41.500000 36 | 2.500000,80.200000 37 | 38 | erasing element #2: elem = 35.400000,1.500000, next = 99.400000,1.500000 39 | 0.400000,0.200000 40 | 2.500000,5.200000 41 | 99.400000,1.500000 42 | 99.500000,1.500000 43 | 99.700000,1.500000 44 | 99.400000,41.500000 45 | 99.500000,41.500000 46 | 99.700000,41.500000 47 | 99.800000,41.500000 48 | 2.500000,80.200000 49 | 50 | erasing element #3: elem = 99.400000,1.500000, next = 99.500000,1.500000 51 | 0.400000,0.200000 52 | 2.500000,5.200000 53 | 35.400000,1.500000 54 | 99.500000,1.500000 55 | 99.700000,1.500000 56 | 99.400000,41.500000 57 | 99.500000,41.500000 58 | 99.700000,41.500000 59 | 99.800000,41.500000 60 | 2.500000,80.200000 61 | 62 | erasing element #4: elem = 99.500000,1.500000, next = 99.700000,1.500000 63 | 0.400000,0.200000 64 | 2.500000,5.200000 65 | 35.400000,1.500000 66 | 99.400000,1.500000 67 | 99.700000,1.500000 68 | 99.400000,41.500000 69 | 99.500000,41.500000 70 | 99.700000,41.500000 71 | 99.800000,41.500000 72 | 2.500000,80.200000 73 | 74 | erasing element #5: elem = 99.700000,1.500000, next = 99.400000,41.500000 75 | 0.400000,0.200000 76 | 2.500000,5.200000 77 | 35.400000,1.500000 78 | 99.400000,1.500000 79 | 99.500000,1.500000 80 | 99.400000,41.500000 81 | 99.500000,41.500000 82 | 99.700000,41.500000 83 | 99.800000,41.500000 84 | 2.500000,80.200000 85 | 86 | erasing element #6: elem = 99.400000,41.500000, next = 99.800000,41.500000 87 | 0.400000,0.200000 88 | 2.500000,5.200000 89 | 35.400000,1.500000 90 | 99.400000,1.500000 91 | 99.500000,1.500000 92 | 99.700000,1.500000 93 | 99.800000,41.500000 94 | 99.500000,41.500000 95 | 99.700000,41.500000 96 | 2.500000,80.200000 97 | 98 | erasing element #7: elem = 99.500000,41.500000, next = 99.800000,41.500000 99 | 0.400000,0.200000 100 | 2.500000,5.200000 101 | 35.400000,1.500000 102 | 99.400000,1.500000 103 | 99.500000,1.500000 104 | 99.700000,1.500000 105 | 99.400000,41.500000 106 | 99.800000,41.500000 107 | 99.700000,41.500000 108 | 2.500000,80.200000 109 | 110 | erasing element #8: elem = 99.700000,41.500000, next = 99.800000,41.500000 111 | 0.400000,0.200000 112 | 2.500000,5.200000 113 | 35.400000,1.500000 114 | 99.400000,1.500000 115 | 99.500000,1.500000 116 | 99.700000,1.500000 117 | 99.400000,41.500000 118 | 99.500000,41.500000 119 | 99.800000,41.500000 120 | 2.500000,80.200000 121 | 122 | erasing element #9: elem = 99.800000,41.500000, next = 2.500000,80.200000 123 | 0.400000,0.200000 124 | 2.500000,5.200000 125 | 35.400000,1.500000 126 | 99.400000,1.500000 127 | 99.500000,1.500000 128 | 99.700000,1.500000 129 | 99.400000,41.500000 130 | 99.500000,41.500000 131 | 99.700000,41.500000 132 | 2.500000,80.200000 133 | 134 | erasing element #10: elem = 2.500000,80.200000, next = (end) 135 | 0.400000,0.200000 136 | 2.500000,5.200000 137 | 35.400000,1.500000 138 | 99.400000,1.500000 139 | 99.500000,1.500000 140 | 99.700000,1.500000 141 | 99.400000,41.500000 142 | 99.500000,41.500000 143 | 99.700000,41.500000 144 | 99.800000,41.500000 145 | 146 | sorting around 0.000000,0.000000 147 | 0.400000,0.200000 (dist = 0.447214) 148 | 2.500000,5.200000 (dist = 5.76975) 149 | -4.200000,4.000000 (dist = 5.8) 150 | 35.400000,1.500000 (dist = 35.4318) 151 | 2.500000,80.200000 (dist = 80.239) 152 | 99.400000,1.500000 (dist = 99.4113) 153 | 99.500000,1.500000 (dist = 99.5113) 154 | 99.700000,1.500000 (dist = 99.7113) 155 | 100.600000,1.500000 (dist = 100.611) 156 | 100.800000,1.500000 (dist = 100.811) 157 | 101.000000,1.000000 (dist = 101.005) 158 | 2.500000,101.000000 (dist = 101.031) 159 | 99.400000,41.500000 (dist = 107.715) 160 | 99.500000,41.500000 (dist = 107.808) 161 | 99.700000,41.500000 (dist = 107.992) 162 | 99.800000,41.500000 (dist = 108.085) 163 | 100.600000,41.500000 (dist = 108.824) 164 | that's 17 objects 165 | sorting around 70.000000,35.000000 166 | 99.400000,41.500000 (dist = 30.11) 167 | 99.500000,41.500000 (dist = 30.2076) 168 | 99.700000,41.500000 (dist = 30.403) 169 | 99.800000,41.500000 (dist = 30.5007) 170 | 100.600000,41.500000 (dist = 31.2827) 171 | 99.400000,1.500000 (dist = 44.5714) 172 | 99.500000,1.500000 (dist = 44.6374) 173 | 99.700000,1.500000 (dist = 44.7699) 174 | 100.600000,1.500000 (dist = 45.3719) 175 | 100.800000,1.500000 (dist = 45.507) 176 | 101.000000,1.000000 (dist = 46.0109) 177 | 35.400000,1.500000 (dist = 48.1603) 178 | 2.500000,5.200000 (dist = 73.7854) 179 | 0.400000,0.200000 (dist = 77.8152) 180 | -4.200000,4.000000 (dist = 80.4154) 181 | 2.500000,80.200000 (dist = 81.236) 182 | 2.500000,101.000000 (dist = 94.4047) 183 | that's 17 objects 184 | sorting around 0.000000,-9999.000000 185 | 0.400000,0.200000 (dist = 9999.2) 186 | 101.000000,1.000000 (dist = 10000.5) 187 | 35.400000,1.500000 (dist = 10000.6) 188 | 99.400000,1.500000 (dist = 10001) 189 | 99.500000,1.500000 (dist = 10001) 190 | 99.700000,1.500000 (dist = 10001) 191 | 100.600000,1.500000 (dist = 10001) 192 | 100.800000,1.500000 (dist = 10001) 193 | -4.200000,4.000000 (dist = 10003) 194 | 2.500000,5.200000 (dist = 10004.2) 195 | 99.400000,41.500000 (dist = 10041) 196 | 99.500000,41.500000 (dist = 10041) 197 | 99.700000,41.500000 (dist = 10041) 198 | 99.800000,41.500000 (dist = 10041) 199 | 100.600000,41.500000 (dist = 10041) 200 | 2.500000,80.200000 (dist = 10079.2) 201 | 2.500000,101.000000 (dist = 10100) 202 | that's 17 objects 203 | erasing 2-closest item to 0.000000,0.000000 204 | 0.400000,0.200000 (dist = 0.447214) 205 | 2.500000,5.200000 (dist = 5.76975) 206 | 35.400000,1.500000 (dist = 35.4318) 207 | 2.500000,80.200000 (dist = 80.239) 208 | 99.400000,1.500000 (dist = 99.4113) 209 | 99.500000,1.500000 (dist = 99.5113) 210 | 99.700000,1.500000 (dist = 99.7113) 211 | 100.600000,1.500000 (dist = 100.611) 212 | 100.800000,1.500000 (dist = 100.811) 213 | 101.000000,1.000000 (dist = 101.005) 214 | 2.500000,101.000000 (dist = 101.031) 215 | 99.400000,41.500000 (dist = 107.715) 216 | 99.500000,41.500000 (dist = 107.808) 217 | 99.700000,41.500000 (dist = 107.992) 218 | 99.800000,41.500000 (dist = 108.085) 219 | 100.600000,41.500000 (dist = 108.824) 220 | that's 16 objects 221 | -------------------------------------------------------------------------------- /unused.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Florian Jung 3 | * 4 | * This file is part of factorio-bot. 5 | * 6 | * factorio-bot is free software: you can redistribute it and/or 7 | * modify it under the terms of the GNU General Public License, 8 | * version 3, as published by the Free Software Foundation. 9 | * 10 | * factorio-bot is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with factorio-bot. If not, see . 17 | */ 18 | 19 | #define UNUSED(expr) do { (void)(expr); } while (0) 20 | -------------------------------------------------------------------------------- /util.hpp: -------------------------------------------------------------------------------- 1 | /* These utility functions are dual-licensed under both the 2 | * terms of the MIT and the GPL3 license. You can choose which 3 | * of those licenses you want to use. Note that the MIT option 4 | * only applies to this file, and not to the rest of 5 | * factorio-bot (unless stated otherwise). 6 | */ 7 | 8 | /* 9 | * Copyright (c) 2017, 2018 Florian Jung 10 | * 11 | * This file is part of factorio-bot. 12 | * 13 | * factorio-bot is free software: you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public License, 15 | * version 3, as published by the Free Software Foundation. 16 | * 17 | * factorio-bot is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with factorio-bot. If not, see . 24 | */ 25 | 26 | /* 27 | * MIT License 28 | * 29 | * Copyright (c) 2017, 2018 Florian Jung 30 | * 31 | * Permission is hereby granted, free of charge, to any person obtaining a 32 | * copy of this templated WorldList implementation and associated 33 | * documentation files (the "Software"), to deal in the Software without 34 | * restriction, including without limitation the rights to use, copy, modify, 35 | * merge, publish, distribute, sublicense, and/or sell copies of the 36 | * Software, and to permit persons to whom the Software is furnished to do 37 | * so, subject to the following conditions: 38 | * 39 | * The above copyright notice and this permission notice shall be included in 40 | * all copies or substantial portions of the Software. 41 | * 42 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 45 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 47 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 48 | * DEALINGS IN THE SOFTWARE. 49 | */ 50 | 51 | #pragma once 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #pragma GCC diagnostic push 60 | #pragma GCC diagnostic ignored "-Wunused-function" 61 | 62 | template 63 | std::string join_string (const Container& container, Func function) 64 | { 65 | std::string result; 66 | bool first = true; 67 | for (const auto& x : container) 68 | { 69 | if (!first) result += ","; 70 | first = false; 71 | result += function(x); 72 | } 73 | return result; 74 | } 75 | 76 | constexpr int sane_div(int a, unsigned b) 77 | { 78 | return (a>=0) ? (a/b) : (signed(a-b+1)/signed(b)); 79 | } 80 | 81 | constexpr int sane_mod(int a, unsigned b) 82 | { 83 | return ((a%b)+b)%b; 84 | } 85 | 86 | auto min_element_if = [](auto& container, auto predicate, auto compare) 87 | { 88 | auto best = container.end(); 89 | 90 | for (auto it=container.begin(); it!=container.end(); it++) 91 | if (predicate(*it)) 92 | { 93 | if (best==container.end() || compare(*it, *best)) 94 | best = it; 95 | } 96 | 97 | return best; 98 | }; 99 | 100 | static std::string strpad(std::string s, size_t n) 101 | { 102 | if (s.length() >= n) return s; 103 | else return s + std::string(n-s.length(),' '); 104 | } 105 | 106 | template void accumulate_map(container_type& accumulator, const container_type& increment) 107 | { 108 | for (const auto& [key,value] : increment) 109 | { 110 | auto iter = accumulator.find(key); 111 | if (iter == accumulator.end()) 112 | accumulator[key] = value; 113 | else 114 | accumulator[key] += value; 115 | } 116 | } 117 | 118 | template iterator_type unordered_erase(container_type& container, iterator_type iter) 119 | { 120 | if (iter == container.end()-1) 121 | { 122 | container.pop_back(); 123 | return container.end(); 124 | } 125 | else 126 | { 127 | std::swap(*iter, container.back()); 128 | container.pop_back(); 129 | return iter; 130 | } 131 | } 132 | 133 | 134 | template > class vector_map : public std::vector 135 | { 136 | public: 137 | size_t find(K what) const 138 | { 139 | Comparator comp; 140 | 141 | for (size_t i=0; i< std::vector::size(); i++) 142 | if ( comp((*this)[i].*key, what) ) 143 | return i; 144 | return SIZE_MAX; 145 | } 146 | 147 | T& get(K what) 148 | { 149 | auto idx = find(what); 150 | if (idx == SIZE_MAX) 151 | { 152 | std::vector::emplace_back(); 153 | std::vector::back().*key = what; 154 | idx = std::vector::size() - 1; 155 | } 156 | return (*this)[idx]; 157 | } 158 | }; 159 | 160 | template 161 | inline bool equals(const std::weak_ptr& t, const std::weak_ptr& u) 162 | { 163 | return !t.owner_before(u) && !u.owner_before(t); 164 | } 165 | 166 | 167 | template 168 | inline bool equals(const std::weak_ptr& t, const std::shared_ptr& u) 169 | { 170 | return !t.owner_before(u) && !u.owner_before(t); 171 | } 172 | 173 | template struct weak_ptr_equal 174 | { 175 | inline bool operator()(const T& a, const U& b) 176 | { 177 | return equals(a,b); 178 | } 179 | }; 180 | 181 | template > 182 | std::vector< std::pair > compact_sequence(const Container& sequence) 183 | { 184 | std::vector< std::pair > result; 185 | 186 | if (sequence.empty()) 187 | return result; 188 | 189 | typename Container::value_type last = *sequence.begin(); 190 | size_t count = 0; 191 | 192 | for (const typename Container::value_type iter : sequence) 193 | { 194 | if (iter == last) 195 | count++; 196 | else 197 | { 198 | result.emplace_back(count, last); 199 | count = 1; 200 | last = iter; 201 | } 202 | } 203 | 204 | result.emplace_back(count, last); 205 | 206 | return result; 207 | } 208 | 209 | template typename MapContainer::mapped_type get_or(const MapContainer& container, typename MapContainer::key_type key, typename MapContainer::mapped_type default_value = typename MapContainer::mapped_type()) 210 | { 211 | if (auto iter = container.find(key); iter != container.end()) 212 | return iter->second; 213 | else 214 | return default_value; 215 | } 216 | 217 | template bool contains(const Container& container, const T& search_for) 218 | { 219 | return container.find(search_for) != container.end(); 220 | } 221 | template bool contains_vec(const Container& container, const T& search_for) 222 | { 223 | return find(container.begin(), container.end(), search_for) != container.end(); 224 | } 225 | 226 | inline int ceili(double f) { return int(std::ceil(f)); } 227 | inline int floori(double f) { return int(std::floor(f)); } 228 | 229 | #pragma GCC diagnostic pop 230 | -------------------------------------------------------------------------------- /worldmap.hpp: -------------------------------------------------------------------------------- 1 | /* This WorldMap implementation is dual-licensed under both the 2 | * terms of the MIT and the GPL3 license. You can choose which 3 | * of those licenses you want to use. Note that the MIT option 4 | * only applies to this file, and not to the rest of 5 | * factorio-bot (unless stated otherwise). 6 | */ 7 | 8 | /* 9 | * Copyright (c) 2017, 2018 Florian Jung 10 | * 11 | * This file is part of factorio-bot. 12 | * 13 | * factorio-bot is free software: you can redistribute it and/or 14 | * modify it under the terms of the GNU General Public License, 15 | * version 3, as published by the Free Software Foundation. 16 | * 17 | * factorio-bot is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with factorio-bot. If not, see . 24 | */ 25 | 26 | /* 27 | * MIT License 28 | * 29 | * Copyright (c) 2017, 2018 Florian Jung 30 | * 31 | * Permission is hereby granted, free of charge, to any person obtaining a 32 | * copy of this templated WorldMap implementation and associated 33 | * documentation files (the "Software"), to deal in the Software without 34 | * restriction, including without limitation the rights to use, copy, modify, 35 | * merge, publish, distribute, sublicense, and/or sell copies of the 36 | * Software, and to permit persons to whom the Software is furnished to do 37 | * so, subject to the following conditions: 38 | * 39 | * The above copyright notice and this permission notice shall be included in 40 | * all copies or substantial portions of the Software. 41 | * 42 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 45 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 47 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 48 | * DEALINGS IN THE SOFTWARE. 49 | */ 50 | 51 | #pragma once 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | 62 | #include "pos.hpp" 63 | 64 | template using Chunk = std::array< std::array< T, 32 >, 32 >; 65 | 66 | template 67 | class WorldMap 68 | { 69 | public: 70 | template 71 | class DumbViewport_ 72 | { 73 | friend class WorldMap; 74 | 75 | private: 76 | typedef typename std::conditional*, WorldMap*>::type parenttype; 77 | typedef typename std::conditional*, Chunk*>::type chunktype; 78 | 79 | parenttype parent; 80 | 81 | Pos origin; 82 | 83 | DumbViewport_(parenttype parent_, const Pos& origin_) : parent(parent_), origin(origin_) {} 84 | 85 | public: 86 | typedef typename std::conditional::type reftype; 87 | 88 | reftype at(int x, int y) const 89 | { 90 | int tilex = x+origin.x; 91 | int tiley = y+origin.y; 92 | int chunkx = chunkidx(tilex); 93 | int chunky = chunkidx(tiley); 94 | int relx = tileidx(tilex); 95 | int rely = tileidx(tiley); 96 | 97 | return (*parent->get_chunk(chunkx, chunky))[relx][rely]; 98 | } 99 | 100 | reftype at(const Pos& pos) const { return at(pos.x, pos.y); } 101 | }; 102 | 103 | template 104 | class Viewport_ 105 | { 106 | friend class WorldMap; 107 | private: 108 | typedef typename std::conditional*, WorldMap*>::type parenttype; 109 | typedef typename std::conditional*, Chunk*>::type chunktype; 110 | 111 | parenttype parent; 112 | 113 | Pos origin; 114 | Pos lefttop_tile, rightbot_tile; // DEBUG 115 | std::vector chunkcache; 116 | Pos lefttop_chunk; 117 | int xlen_chunks, ylen_chunks; 118 | 119 | Viewport_(parenttype parent_, const Pos& lefttop_tile_, const Pos& rightbot_tile_, const Pos& origin_) : parent(parent_), origin(origin_), lefttop_tile(lefttop_tile_), rightbot_tile(rightbot_tile_) 120 | { 121 | lefttop_chunk = Pos::tile_to_chunk(lefttop_tile); 122 | Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile); 123 | 124 | // recalculate tile borders, because we can only deal with chunks. This means that 125 | // the actually available area is larger than the requested area. 126 | lefttop_tile = Pos::chunk_to_tile(lefttop_chunk); 127 | rightbot_tile = Pos::chunk_to_tile(rightbot_chunk); 128 | 129 | xlen_chunks = rightbot_chunk.x - lefttop_chunk.x; 130 | ylen_chunks = rightbot_chunk.y - lefttop_chunk.y; 131 | 132 | if (xlen_chunks < 0 || ylen_chunks < 0) 133 | throw std::invalid_argument("invalid Viewport range"); 134 | 135 | chunkcache.resize(xlen_chunks * ylen_chunks); 136 | for (int x = 0; x < xlen_chunks; x++) 137 | for (int y = 0; y < ylen_chunks; y++) 138 | chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y); 139 | } 140 | 141 | void extend_self(const Pos& new_lefttop_tile, const Pos& new_rightbot_tile) 142 | { 143 | Pos old_lefttop_chunk = lefttop_chunk; 144 | int old_xlen_chunks = xlen_chunks; 145 | int old_ylen_chunks = ylen_chunks; 146 | std::vector old_chunkcache = std::move(chunkcache); 147 | 148 | 149 | 150 | lefttop_tile = new_lefttop_tile; 151 | rightbot_tile = new_rightbot_tile; 152 | 153 | lefttop_chunk = Pos::tile_to_chunk(lefttop_tile); 154 | Pos rightbot_chunk = Pos::tile_to_chunk_ceil(rightbot_tile); 155 | 156 | // recalculate tile borders, because we can only deal with chunks. This means that 157 | // the actually available area is larger than the requested area. 158 | lefttop_tile = Pos::chunk_to_tile(lefttop_chunk); 159 | rightbot_tile = Pos::chunk_to_tile(rightbot_chunk); 160 | 161 | xlen_chunks = rightbot_chunk.x - lefttop_chunk.x; 162 | ylen_chunks = rightbot_chunk.y - lefttop_chunk.y; 163 | 164 | if (xlen_chunks < 0 or ylen_chunks < 0) 165 | throw std::invalid_argument("invalid Viewport range"); 166 | 167 | chunkcache.resize(xlen_chunks * ylen_chunks); 168 | for (int x = 0; x < xlen_chunks; x++) 169 | for (int y = 0; y < ylen_chunks; y++) 170 | { 171 | int oldx = x + lefttop_chunk.x - old_lefttop_chunk.x; 172 | int oldy = y + lefttop_chunk.y - old_lefttop_chunk.y; 173 | 174 | if (0 <= oldx && oldx < old_xlen_chunks && 175 | 0 <= oldy && oldy < old_ylen_chunks) 176 | chunkcache[x+y*xlen_chunks] = old_chunkcache[oldx+oldy*old_xlen_chunks]; 177 | else 178 | chunkcache[x+y*xlen_chunks] = parent->get_chunk(x+lefttop_chunk.x,y+lefttop_chunk.y); 179 | } 180 | } 181 | 182 | public: 183 | typedef typename std::conditional::type reftype; 184 | 185 | Viewport_ extend(const Pos& lefttop_tile_, const Pos& rightbot_tile_) const 186 | { 187 | return Viewport_(parent, lefttop_tile_, rightbot_tile_, origin); 188 | } 189 | 190 | reftype at(int x, int y) 191 | { 192 | int tilex = x+origin.x; 193 | int tiley = y+origin.y; 194 | if (!((tilex >= lefttop_tile.x) && (tiley >= lefttop_tile.y) && (tilex < rightbot_tile.x) && (tiley < rightbot_tile.y))) 195 | { 196 | Pos new_lefttop_tile(lefttop_tile); 197 | Pos new_rightbot_tile(rightbot_tile); 198 | if (tilex < lefttop_tile.x) new_lefttop_tile.x = tilex; 199 | else if (tilex >= rightbot_tile.x) new_rightbot_tile.x = tilex+1; 200 | if (tiley < lefttop_tile.y) new_lefttop_tile.y = tiley; 201 | else if (tiley >= rightbot_tile.y) new_rightbot_tile.y = tiley+1; 202 | 203 | extend_self(new_lefttop_tile, new_rightbot_tile); 204 | } 205 | int chunkx = chunkidx(tilex) - lefttop_chunk.x; 206 | int chunky = chunkidx(tiley) - lefttop_chunk.y; 207 | assert(chunkx >= 0); 208 | assert(chunkx < xlen_chunks); 209 | assert(chunky >= 0); 210 | assert(chunky < ylen_chunks); 211 | 212 | int relx = tileidx(tilex); 213 | int rely = tileidx(tiley); 214 | 215 | return (*chunkcache[chunkx+chunky*xlen_chunks])[relx][rely]; 216 | } 217 | reftype at(const Pos& pos) { return at(pos.x, pos.y); } 218 | }; 219 | 220 | 221 | typedef DumbViewport_ ConstDumbViewport; 222 | typedef DumbViewport_ DumbViewport; 223 | typedef Viewport_ ConstViewport; 224 | typedef Viewport_ Viewport; 225 | 226 | Viewport view(const Pos& lefttop, const Pos& rightbot, const Pos& origin) 227 | { 228 | return Viewport(this, lefttop, rightbot, origin); 229 | } 230 | 231 | ConstViewport view(const Pos& lefttop, const Pos& rightbot, const Pos& origin) const 232 | { 233 | return ConstViewport(this, lefttop, rightbot, origin); 234 | } 235 | 236 | DumbViewport dumb_view(const Pos& origin) 237 | { 238 | return DumbViewport(this, origin); 239 | } 240 | 241 | ConstDumbViewport dumb_view(const Pos& origin) const 242 | { 243 | return ConstDumbViewport(this, origin); 244 | } 245 | 246 | Chunk* get_chunk(int x, int y) 247 | { 248 | Chunk* retval = &(storage[ Pos(x,y) ]); 249 | assert(retval != nullptr); 250 | return retval; 251 | } 252 | 253 | const Chunk* get_chunk(int x, int y) const 254 | { 255 | try 256 | { 257 | return &(storage.at( Pos(x,y) )); 258 | } 259 | catch (const std::out_of_range&) 260 | { 261 | return &dummy_chunk; 262 | } 263 | } 264 | 265 | const T& at(int x, int y) const 266 | { 267 | int chunkx = chunkidx(x); 268 | int chunky = chunkidx(y); 269 | int relx = tileidx(x); 270 | int rely = tileidx(y); 271 | 272 | return (*get_chunk(chunkx, chunky))[relx][rely]; 273 | } 274 | T& at(int x, int y) 275 | { 276 | int chunkx = chunkidx(x); 277 | int chunky = chunkidx(y); 278 | int relx = tileidx(x); 279 | int rely = tileidx(y); 280 | 281 | return (*get_chunk(chunkx, chunky))[relx][rely]; 282 | } 283 | const T& at(const Pos& pos) const { return at(pos.x, pos.y); } 284 | T& at(const Pos& pos) { return at(pos.x, pos.y); } 285 | 286 | private: 287 | std::unordered_map< Pos, Chunk > storage; 288 | Chunk dummy_chunk; 289 | }; 290 | --------------------------------------------------------------------------------