forked from jahands/FactorioMods_FactorioMaps
-
Notifications
You must be signed in to change notification settings - Fork 22
/
api.lua
271 lines (219 loc) · 8.61 KB
/
api.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
fm.API = {}
fm.API.startEvent = script.generate_event_name()
fm.API.linkData = {}
fm.API.hiddenSurfaces = {}
local ERRORPRETEXT = "\n\nFACTORIOMAPS HAS DETECTED AN INVALID USAGE OF THE FACTORIOMAPS API BY ANOTHER MOD.\nTHIS IS LIKELY NOT A PROBLEM WITH FACTORIOMAPS, BUT WITH THE OTHER MOD.\n\n"
local function NYI()
error("This API call is not yet implemented due to lack of demand. If you are looking at using this function, contact me! I will gladly implement it.")
end
-- resolve surface to surface object
local function resolveSurface(surface, default, errorText)
errorText = errorText or ""
if surface ~= nil then
if type(surface) == "string" or type(surface) == "number" then
surface = game.surfaces[surface]
if not surface then
error(ERRORPRETEXT .. errorText .. "surface does not exist\n")
elseif not surface.valid then
error(ERRORPRETEXT .. errorText .. "surface.valid is false\n")
end
return surface
else
error(ERRORPRETEXT .. errorText .. "surface is not a string or number\n")
end
else
if not default then
error(ERRORPRETEXT .. errorText .. "no surface specified\n")
else
return default
end
end
end
local roundMultiplier = 32
if fm.autorun and fm.autorun.HD then
roundMultiplier = 64
end
local function parseLocation(options, optionName, isArea, canHaveSurface, defaultSurface)
assert(options, "no options specified")
local obj = options[optionName]
assert(obj, "no '" .. optionName .. "' option specified")
assert(type(obj) == "table", "option '" .. optionName .. "' must be a table with coordinates")
local surface = nil
if canHaveSurface then
surface = resolveSurface(obj["surface"], defaultSurface, "option '" .. optionName .. "': ")
if obj["surface"] then
obj.surface = nil
end
end
for k, v in pairs(obj) do
if k ~= 1 and k ~= 2 then
error(ERRORPRETEXT .. "option '" .. optionName .. "': invalid key '" .. k .. "'\n")
end
end
if obj[1] and obj[2] then
if isArea then
return { parseLocation(obj, 1), parseLocation(obj, 2) }, surface
else
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")
end
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].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+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 (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-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
local function addLink(type, from, fromSurface, to, toSurface)
if fm.API.linkData[fromSurface.name] == nil then
fm.API.linkData[fromSurface.name] = {}
end
local newLink = {
type = type,
from = from,
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]
fm.API.linkData[fromSurface.name][linkIndex+1] = newLink
if type == "link_renderbox_area" then
populateRenderChain(newLink, linkIndex, fromSurface.name)
end
end
remote.add_interface("factoriomaps", {
get_start_capture_event_id = function()
return fm.API.startEvent
end,
link_box_point = function(options)
local from, fromSurface = parseLocation(options, "from", true, true)
local to, toSurface = parseLocation(options, "to", false, true, fromSurface)
addLink("link_box_point", from, fromSurface, to, toSurface)
end,
link_box_area = function(options)
local from, fromSurface = parseLocation(options, "from", true, true)
local to, toSurface = parseLocation(options, "to", true, true, fromSurface)
addLink("link_box_area", from, fromSurface, to, toSurface)
end,
link_renderbox_area = function(options)
local from, fromSurface = parseLocation(options, "from", true, true)
local to, toSurface = parseLocation(options, "to", true, true, fromSurface)
addLink("link_renderbox_area", from, fromSurface, to, toSurface)
end,
surface_set_hidden = function(surface, isHidden)
surface = resolveSurface(surface)
if isHidden == true or isHidden == nil then
for _, s in pairs(fm.API.hiddenSurfaces) do
if s == surface then
return
end
end
fm.API.hiddenSurfaces[#fm.API.hiddenSurfaces+1] = surface
elseif isHidden == false then
for i, s in pairs(fm.API.hiddenSurfaces) do
if s == surface then
fm.API.hiddenSurfaces.remove(i)
return
end
end
else
error(ERRORPRETEXT .. "invalid isHidden parameter\n")
end
end,
surface_set_default = function(surface)
surface = resolveSurface(surface)
if fm.autorun.mapInfo.defaultSurface ~= nil and fm.autorun.mapInfo.defaultSurface ~= surface.name then
error(ERRORPRETEXT .. "The default surface was set multiple times: '" .. fm.autorun.mapInfo.defaultSurface .. "', '" .. surface.name .. "'.\n")
end
fm.autorun.mapInfo.defaultSurface = surface.name
end,
surface_set_startpoint = function(options)
NYI();
end,
legend_link_point = function(directory, subdirectory, text)
NYI();
end,
legend_link_area = function(directory, subdirectory, text)
NYI();
end
})
function fm.API.pull()
script.raise_event(fm.API.startEvent, {})
--remote.remove_interface("factoriomaps")
end