Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
L0laapk3 committed Aug 21, 2019
2 parents c37c9d8 + b6dd06b commit ea13ff6
Show file tree
Hide file tree
Showing 20 changed files with 1,212 additions and 450 deletions.
6 changes: 5 additions & 1 deletion API_ExampleMod_0.0.1/control.lua
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
require "factoriomaps"
require "factoriomaps"

-- a word of caution: you can only define these once, any subsequent definitions will override previous definitions.
script.on_init(handle_factoriomaps)
script.on_load(handle_factoriomaps)
26 changes: 11 additions & 15 deletions API_ExampleMod_0.0.1/factoriomaps.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
-- require this file in control.lua


-- this function is to be ran on script.on_init and on script.on_load.
local function handle_factoriomaps()
if remote.interfaces.factoriomaps then
script.on_event(remote.call("factoriomaps", "get_start_capture_event"), function()

-- note that this event only gets called when it starts capturing the world, so speed optimalisation of the code in this function is not important.

-- If you want to make the maps a bit more screenshot friendly, this is the place to do it.
-- Do not worry about being descructive to the map, if this event is called, FactorioMaps has already done
-- non-reversable damage to the map and every attempt is made to stop the player from overwriting their savefile.


-- example parameters:
Expand All @@ -28,36 +33,27 @@ local function handle_factoriomaps()
-- the 'to' parameter now has to be an area instead of a point, otherwise exactly the same.
remote.call("factoriomaps", "link_box_area", {
from = { {20, 10}, {30, 20}, surface = "nauvis" },
to = { surface = "Factory floor 1", {30, 30}, {40, 40} } -- both notations work.
to = { {30, 30}, {40, 40} }
})



-- link_renderbox_area: clickable box that renders the 'to' surface on the place of the 'from' surface.
remote.call("factoriomaps", "link_renderbox_area", {
from = { {40, 10}, {50, 20}, surface = "nauvis" },
to = { {30, 30}, {40, 40}, surface = "Factory floor 1" }
from = { {-2, -46}, {14, -30}, surface = "nauvis" },
to = { {-31, -31}, {31, 31}, surface = "Factory floor 1" }
})



-- link_renderbox_area: clickable box that renders the 'to' surface on the place of the 'from' surface.
remote.call("factoriomaps", "link_renderbox_area", {
from = { {30, 30}, {40, 40}, surface = "Factory floor 1" },
to = { {10, 10}, {40, 20}, surface = "nauvis" }
})



-- surface_set_hidden: This prevents the user from navigating "to" the surface. Links will also not work.
-- Parts of the surface can still be rendered using renderboxes.
-- parameters: surface (id (prefered) or name), hidden: boolean, default to true.
remote.call("factoriomaps", "surface_set_hidden", "Factory floor 1", true)
remote.call("factoriomaps", "surface_set_hidden", "Factory floor 2", true)
remote.call("factoriomaps", "surface_set_hidden", "Factory floor 3", true)
end



end)
end
end
script.on_init(handle_factoriomaps)
script.on_load(handle_factoriomaps)
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ Live demo: https://factoriomaps.com/beta/user/L0laapk3/megabase/index.html
Mod portal link: https://mods.factorio.com/mod/L0laapk3_FactorioMaps

# How to Install
**Note that since version 3, this program now only runs on python version 3.6 or higher instead of python 2.**
1. Download FactorioMaps to `%appdata%\mods\`, either from the [mod portal](https://mods.factorio.com/mod/L0laapk3_FactorioMaps) (The mod does not need to be enabled to work) and then unzipping it, or [downloading the git repo](https://github.com/L0laapk3/FactorioMaps/releases).
1. Install the latest version of [python 3.7](https://www.python.org/downloads/). (Do not install python 2.)
**Note that this program now only runs on 64 bit python version 3.6 or higher.**
1. Download FactorioMaps to `%appdata%\mods\`, either from the [mod portal](https://mods.factorio.com/mod/L0laapk3_FactorioMaps) (The mod does not need to be enabled to work) and then unzipping it, or from [the github releases page](https://github.com/L0laapk3/FactorioMaps/releases).
1. Install the latest version of [**64 bit** python 3.7](https://www.python.org/downloads/). (Do not install python 2.)
1. Recommended: [Add python to your environment variables](https://stackoverflow.com/a/4855685/3185280).
1. Install pip: Download the latest [get-pip.py](https://bootstrap.pypa.io/get-pip.py), and run it (`python get-pip.py` in the command line).
1. Install the following pip packages: `pip install Pillow psutil`.
1. Install the following pip packages: `pip install -r packages.txt`.

# How to Use
1. Make sure you close factorio before starting the process.
Expand All @@ -31,16 +31,18 @@ Mod portal link: https://mods.factorio.com/mod/L0laapk3_FactorioMaps

# Configuration
Heres a list of flags that `auto.py` can accept:
*Options with a \* do not have an effect when appending to existing timelapses.*

|                   flag                   | Description |
| --- | --- |
| `--dayonly` | Do not take nighttime screenshots (For now, this setting needs to be the same across one timeline). |
| `--nightonly` | Do not take daytime screenshots. |
| `--hd` | Take screenshots of resolution 64 x 64 pixels per in-game tile instead of 32 x 32 to match the resolution of the newer HD textures. |
| `--dayonly`*\** | Do not take nighttime screenshots (For now, this setting needs to be the same across one timeline). |
| `--nightonly`*\** | Do not take daytime screenshots. |
| `--hd`*\** | Take screenshots of resolution 64 x 64 pixels per in-game tile instead of 32 x 32 to match the resolution of the newer HD textures. |
| `--no-altmode` | Hides entity info (alt mode) |
| `--build-range=5.2` | The maximum range from buildings around which pictures are saved (in chunks, 32 by 32 in-game tiles). |
| `--connect-range=1.2` | The maximum range from connection buildings (rails, electric poles) around which pictures are saved. |
| `--tag-range=5.2` | The maximum range from mapview tags around which pictures are saved. |
| `--no-tags` | Hides map tags |
| `--build-range=5.2`*\** | The maximum range from buildings around which pictures are saved (in chunks, 32 by 32 in-game tiles). |
| `--connect-range=1.2`*\** | The maximum range from connection buildings (rails, electric poles) around which pictures are saved. |
| `--tag-range=5.2`*\** | The maximum range from mapview tags around which pictures are saved. |
| `--surface=nauvis` | Used to capture other surfaces. If left empty, the surface the player is standing on will be used. To capture multiple surfaces, use the argument multiple times: `--surface=nauvis --surface="Factory floor 1"`. To find out the names of surfaces, use the command `/c for _,s in pairs(game.surfaces) do game.print(s.name) end`. |
| `--factorio=PATH` | Use `factorio.exe` from *PATH* instead of attempting to find it in common locations. |
| `--modpath=PATH` | Use *PATH* as the mod folder. |
Expand All @@ -59,6 +61,10 @@ Heres a list of flags that `auto.py` can accept:

Image quality settings can be changed in the top of `zoom.py`.

# Result folder estimates
You can expect the resulting folders to take up approx. (very rough estimate) 15 times the savefile size per timestamp per daytime for day images and 10 times for night images. The intermediate total disk usage will be much higher, 10 times the final result or more. If this is a problem for you, go put a +1 on [#46](https://github.com/L0laapk3/FactorioMaps/issues/46).
Of course the processing time depends very heavely on your system specs, but a rough estimate is an hour per timestamp per daytime per 50 MB of savefile.

# Hosting this on a server
If you wish to host your map for other people to a server, you need to take into account the following considerations: (You can change these once in `index.html.template` and they will be used for all future snapshots.)
1. Of the files that this program generates, the files required to be hosted are:
Expand All @@ -68,6 +74,10 @@ If you wish to host your map for other people to a server, you need to take into
* All files in `lib\`.
All other files, including txt and other non-image files in `Images\`, are not used by the client. Some of them are temporary files, some of them are used as savestate to create additional snapshots on the timeline.

# Known mods that make use of the API to improve compability
* **Factorissimo** ⩾2.3.5: Able to render the inside of factory buildings recursively.
* Your mod? If you want to have a chat, you can always find me on discord: L0laapk3#2010

# Known limitations
* If you only have the steam version of factorio, steam will ask you to confirm the arguments everytime the script tries to start up. The popup window will sometimes not focus properly. Please press alt tab a couple of times until it shows up. The only way to get around this is to install the standalone version of factorio.
* If the program crashes while making a snapshot, it is very likely to leave timelines behind in a 'bricked' state and will probably mess up future snapshots. The easiest way is to simply start over and regenerate all the snapshots from old savefiles. If thats not a possibility, feel free to contact me on discord (L0laapk3#2010) or create an Issue, I'll do my best to help you out.
Expand Down
82 changes: 68 additions & 14 deletions api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ local function resolveSurface(surface, default, errorText)
end
end

local roundMultiplier = 32
if fm.autorun.HD then
roundMultiplier = 64
end

local function parseLocation(options, optionName, isArea, canHaveSurface, defaultSurface)

assert(options, "no options specified")
Expand All @@ -55,7 +60,7 @@ local function parseLocation(options, optionName, isArea, canHaveSurface, defaul
if isArea then
return { parseLocation(obj, 1), parseLocation(obj, 2) }, surface
else
return { obj[1], obj[2] }, surface
return { x = math.floor(obj[1] * roundMultiplier + 0.5) / roundMultiplier, y = math.floor(obj[2] * roundMultiplier + 0.5) / roundMultiplier }, surface
end
else
error(ERRORPRETEXT .. "option '" .. optionName .. "': invalid " .. (isArea and "area" or "point") .. " '" .. serpent.block(obj) .. "'\n")
Expand All @@ -67,39 +72,73 @@ end
-- because of unknown scaling (powers of 2 only allowed, this could change in the future), do not test which parts
-- of the renderbox are a problem, only test if any part of the renderbox can form a chain back to the origin.
local function hasPartialOverlap(a, b)
return b[2][1] > a[1][1] and b[1][1] < a[2][1]
and b[2][2] > a[1][2] and b[1][2] < a[2][2]
return b[2].x > a[1].x and b[1].x < a[2].x
and b[2].y > a[1].y and b[1].y < a[2].y
end
local function testChainCausality(link, sourceSurface, sourceIndex)
for _, nextLinkIndex in pairs(link.chain or {}) do
local nextLink = fm.API.linkData[link.toSurface][nextLinkIndex]
log(nextLinkIndex .. " " .. sourceIndex)
log(sourceSurface .. " " .. link.toSurface)
local nextLink = fm.API.linkData[link.toSurface][nextLinkIndex+1]
if (nextLinkIndex == sourceIndex and sourceSurface == link.toSurface) or not testChainCausality(nextLink, sourceSurface, sourceIndex) then
return false
end
end
return true
end
local function updateMaxZoomDifference(link, prevZoomFromSurfaces)
local newSurfaceZooms = {}
for surfaceName, prevZoom in pairs(prevZoomFromSurfaces) do
local newZoomDifference = prevZoom + link.zoomDifference

if link.maxZoomFromSurfaces[surfaceName] == nil or link.maxZoomFromSurfaces[surfaceName] < newZoomDifference then
newSurfaceZooms[surfaceName] = newZoomDifference
end
end

for surfaceName, newZoomDifference in pairs(newSurfaceZooms) do
link.maxZoomFromSurfaces[surfaceName] = newZoomDifference
end

for _, _ in pairs(newSurfaceZooms) do -- break right after, its like length > 0 but for objects
for _, nextLinkIndex in pairs(link.chain or {}) do
updateMaxZoomDifference(fm.API.linkData[link.toSurface][nextLinkIndex+1], newSurfaceZooms)
end
break
end
end
local function populateRenderChain(newLink, newLinkIndex, fromSurface)

-- scan if other links contain this link in their destination and update them
-- scan if other links contain this link in their destination and update them (for max zoom scale)
newLink.maxZoomFromSurfaces = {}
newLink.maxZoomFromSurfaces[fromSurface] = 0
for _, linkList in pairs(fm.API.linkData or {}) do
for i, link in pairs(linkList) do
if link.chain and link.toSurface == fromSurface and hasPartialOverlap(link.to, newLink.from) then

-- update that link chain to contain this link
link.chain[#link.chain+1] = newLinkIndex

-- update max zoom levels from each surface
for surfaceName, zoomDifference in pairs(link.maxZoomFromSurfaces) do
newLink.maxZoomFromSurfaces[surfaceName] = math.max(zoomDifference, newLink.maxZoomFromSurfaces[surfaceName] or 0)
end
end
end
end

-- increment all by this zoom step
for surfaceName, zoomDifference in pairs(newLink.maxZoomFromSurfaces) do
newLink.maxZoomFromSurfaces[surfaceName] = zoomDifference + newLink.zoomDifference
end

-- find other links that are in the destination of this link
newLink.chain = {}
for i, link in pairs(fm.API.linkData[newLink.toSurface] or {}) do
if hasPartialOverlap(newLink.to, link.from) then
newLink.chain[#newLink.chain+1] = i
newLink.chain[#newLink.chain+1] = i-1
if not testChainCausality(link, fromSurface, newLinkIndex) then
error(ERRORPRETEXT .. "Renderbox bad causality: can cause an infinite rendering loop\n")
end
updateMaxZoomDifference(link, newLink.maxZoomFromSurfaces)
end
end
end
Expand All @@ -115,9 +154,26 @@ local function addLink(type, from, fromSurface, to, toSurface)
to = to,
toSurface = toSurface.name
}

if type == "link_renderbox_area" then
local centerX = (from[1].x + from[2].x) / 2
local centerY = (from[1].y + from[2].y) / 2
local fromSizeX = from[2].x-from[1].x
local fromSizeY = from[2].y-from[1].y
local toSizeX = to[2].x-to[1].x
local toSizeY = to[2].y-to[1].y
newLink.zoomDifference = math.ceil(math.log(math.max(toSizeX/fromSizeX, toSizeY/fromSizeY)) / math.log(2))
local sizeMul = math.pow(2, -newLink.zoomDifference-1) -- -1 for additional division by 2
newLink.renderFrom = {
{ x = centerX - toSizeX * sizeMul, y = centerY - toSizeY * sizeMul },
{ x = centerX + toSizeX * sizeMul, y = centerY + toSizeY * sizeMul }
}
newLink.daynight = true
end

log("adding link type " .. type .. " from " .. fromSurface.name .. " to " .. toSurface.name)
local linkIndex = #fm.API.linkData[fromSurface.name]+1
fm.API.linkData[fromSurface.name][linkIndex] = newLink
local linkIndex = #fm.API.linkData[fromSurface.name]
fm.API.linkData[fromSurface.name][linkIndex+1] = newLink

if type == "link_renderbox_area" then
populateRenderChain(newLink, linkIndex, fromSurface.name)
Expand All @@ -128,7 +184,7 @@ end


remote.add_interface("factoriomaps", {
get_start_capture_event = function()
get_start_capture_event_id = function()
return fm.API.startEvent
end,
link_box_point = function(options)
Expand All @@ -147,7 +203,7 @@ remote.add_interface("factoriomaps", {
local from, fromSurface = parseLocation(options, "from", true, true)
local to, toSurface = parseLocation(options, "to", true, true, fromSurface)

local link = addLink("link_renderbox_area", from, fromSurface, to, toSurface)
addLink("link_renderbox_area", from, fromSurface, to, toSurface)
end,
surface_set_hidden = function(surface, isHidden)
surface = resolveSurface(surface)
Expand Down Expand Up @@ -177,7 +233,5 @@ function fm.API.pull()
script.raise_event(fm.API.startEvent, {})

remote.remove_interface("factoriomaps")

log(serpent.block(fm.API.linkData))
end

Loading

0 comments on commit ea13ff6

Please sign in to comment.