Skip to content

Commit 7835a72

Browse files
committed
first commit, v0.0.1
1 parent 0fd206d commit 7835a72

File tree

4 files changed

+391
-0
lines changed

4 files changed

+391
-0
lines changed

Diff for: config.lua

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require 'defines'
2+
3+
-- times per second (max 60) that belts will be checked for overflow
4+
belt_polling_rate = 60

Diff for: control.lua

+369
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
require "config"
2+
3+
termbelts = {}
4+
curvebelts = {}
5+
6+
belt_polling_rate = math.max(math.min(belt_polling_rate,60),1)
7+
8+
local polling_cycles = math.floor(60/belt_polling_rate)
9+
10+
---[[
11+
local function print(...)
12+
return game.player.print(...)
13+
end
14+
--]]
15+
16+
--swap comment to toggle debug prints
17+
local function debug() end
18+
-- local debug = print
19+
20+
local function pos2s(pos)
21+
if pos then
22+
return pos.x..','..pos.y
23+
end
24+
return ''
25+
end
26+
27+
local belt_speeds = {basic=1,fast=2,express=3,faster=4,purple=5}
28+
29+
-- starting_entity is a belt(-like) entity, or a belt combinator
30+
-- lines are which side to follow, array of 2-8 booleans
31+
local function terminal_belt_lines(entity,entity_to_ignore)
32+
if entity==entity_to_ignore then return {} end
33+
if entity.type=="transport-belt-to-ground" and
34+
entity.belt_to_ground_type=="input" then
35+
if #entity.neighbours>0 then
36+
return {}
37+
else
38+
return {3,4}
39+
end
40+
end
41+
local dir = entity.direction
42+
local pos = entity.position
43+
local to_check = {}
44+
if entity.type=="splitter" then
45+
if dir==defines.direction.north then
46+
to_check = {{pos={x=pos.x-0.5,y=pos.y},lines={5,6}},{pos={x=pos.x+0.5,y=pos.y},lines={7,8}}}
47+
elseif dir==defines.direction.south then
48+
to_check = {{pos={x=pos.x+0.5,y=pos.y},lines={5,6}},{pos={x=pos.x-0.5,y=pos.y},lines={7,8}}}
49+
elseif dir==defines.direction.east then
50+
to_check = {{pos={x=pos.x,y=pos.y-0.5},lines={5,6}},{pos={x=pos.x,y=pos.y+0.5},lines={7,8}}}
51+
elseif dir==defines.direction.west then
52+
to_check = {{pos={x=pos.x,y=pos.y+0.5},lines={5,6}},{pos={x=pos.x,y=pos.y-0.5},lines={7,8}}}
53+
else
54+
debug("invalid direction for splitter entity")
55+
end
56+
elseif entity.type=="transport-belt-to-ground" and
57+
entity.belt_to_ground_type=="input" then
58+
to_check = {{pos=pos,lines={3,4}}}
59+
else
60+
to_check = {{pos=pos,lines={1,2}}}
61+
end
62+
if #to_check>0 then
63+
local result_lines = {}
64+
local function result(lines)
65+
for _,line in pairs(lines) do
66+
result_lines[#result_lines+1] = line
67+
end
68+
end
69+
-- following code originally copied from https://github.com/sparr/factorio-mod-belt-combinators
70+
for _,check in pairs(to_check) do
71+
debug("checking "..pos2s(check.pos))
72+
local tpos = {}
73+
if dir==defines.direction.north then
74+
tpos={x=check.pos.x,y=check.pos.y-1}
75+
elseif dir==defines.direction.south then
76+
tpos={x=check.pos.x,y=check.pos.y+1}
77+
elseif dir==defines.direction.east then
78+
tpos={x=check.pos.x+1,y=check.pos.y}
79+
elseif dir==defines.direction.west then
80+
tpos={x=check.pos.x-1,y=check.pos.y}
81+
else
82+
debug('nil target position')
83+
tpos=nil
84+
end
85+
local entities = game.get_surface(entity.surface.index).find_entities({tpos,tpos})
86+
local target = nil
87+
for _,candidate in pairs(entities) do
88+
if candidate ~= entity_to_ignore then
89+
if candidate.type == "transport-belt" or
90+
candidate.type == "transport-belt-to-ground" or
91+
candidate.type == "splitter" then
92+
target = candidate
93+
break
94+
end
95+
end
96+
end
97+
if target then
98+
debug("target found "..target.type)
99+
local function beltspeedword(name)
100+
local m = string.match("^(.*)-transport-belt",name)
101+
if not m then m = string.match("^(.*)-transport-belt-to-ground",name) end
102+
if not m then m = string.match("^(.*)-splitter",name) end
103+
return m
104+
end
105+
-- any feed into a slower belt can be terminal
106+
local bsw1 = beltspeedword(entity.name)
107+
local bsw2 = beltspeedword(target.name)
108+
if bsw1~=bsw2 then -- different speeds
109+
if (not belt_speeds[bsw1]) or (not belt_speeds[bsw2]) or -- unknown speed(s)
110+
belt_speeds[bsw1]>belt_speeds[bsw2] then -- slower
111+
result(check.lines)
112+
end
113+
-- nothing accepts connections from the front
114+
elseif math.abs(target.direction-dir)==4 then
115+
result(check.lines)
116+
-- underground belt outputs don't accept connections from behind
117+
elseif target.type=="transport-belt-to-ground" and
118+
target.belt_to_ground_type=="output" and
119+
target.direction==dir then
120+
result(check.lines)
121+
-- splitters don't accept connections from the side
122+
elseif target.type=="splitter" and target.direction~=dir then
123+
result(check.lines)
124+
else
125+
-- insertion from the side can be terminal
126+
if target.direction~=dir then
127+
local turn = false
128+
-- belts can be curves, anything else must side load
129+
if target.type=='transport-belt' then
130+
local belt_behind_target = false
131+
-- find a belt-like entity behind the target or on the far side
132+
local bpd = {}
133+
if target.direction==defines.direction.north then
134+
bpd[1]={pos={target.position.x,target.position.y+1},dir=target.direction}
135+
elseif target.direction==defines.direction.south then
136+
bpd[1]={pos={target.position.x,target.position.y-1},dir=target.direction}
137+
elseif target.direction==defines.direction.east then
138+
bpd[1]={pos={target.position.x-1,target.position.y},dir=target.direction}
139+
elseif target.direction==defines.direction.west then
140+
bpd[1]={pos={target.position.x+1,target.position.y},dir=target.direction}
141+
end
142+
if dir==defines.direction.north then
143+
bpd[2]={pos={pos.x,pos.y-2},dir=defines.direction.south}
144+
elseif dir==defines.direction.south then
145+
bpd[2]={pos={pos.x,pos.y+2},dir=defines.direction.north}
146+
elseif dir==defines.direction.east then
147+
bpd[2]={pos={pos.x+2,pos.y},dir=defines.direction.east}
148+
elseif dir==defines.direction.west then
149+
bpd[2]={pos={pos.x-2,pos.y},dir=defines.direction.west}
150+
end
151+
if #bpd>0 then
152+
for _,bpos in pairs(bpd) do
153+
local entities = game.get_surface(entity.surface.index).find_entities({bpos.pos,bpos.pos})
154+
for _,candidate in pairs(entities) do
155+
if candidate.type == "transport-belt" or
156+
candidate.type == "transport-belt-to-ground" or
157+
candidate.type == "splitter" then
158+
if candidate.direction == bpos.dir then
159+
belt_behind_target = true
160+
end
161+
break
162+
end
163+
end
164+
if belt_behind_target then break end
165+
end
166+
end
167+
if not belt_behind_target then
168+
turn = true
169+
if not curvebelts[target.position.y] then curvebelts[target.position.y]={} end
170+
curvebelts[target.position.y][target.position.x] = ((target.direction-dir+8)%8==2) and "right" or "left"
171+
end
172+
end
173+
if not turn then
174+
result(check.lines)
175+
end
176+
end
177+
end
178+
else
179+
result(check.lines)
180+
end
181+
end
182+
return result_lines
183+
end
184+
return {}
185+
end
186+
187+
local function onTick(event)
188+
if event.tick%polling_cycles == 0 then
189+
for y,row in pairs(termbelts) do
190+
for x,belt in pairs(row) do
191+
if not belt.entity or not belt.entity.valid then
192+
termbelts[y][x]=nil
193+
else
194+
local e = belt.entity
195+
local pos = e.position
196+
for _,line in pairs(belt.lines) do
197+
-- debug(pos2s(pos)..' line '..line..':')
198+
local line_caps = {}
199+
if e.type=="transport-belt" then
200+
if curvebelts[pos.y] and curvebelts[pos.y][pos.x]=="right" then
201+
line_caps={5,2}
202+
elseif curvebelts[pos.y] and curvebelts[pos.y][pos.x]=="left" then
203+
line_caps={2,5}
204+
else
205+
line_caps={4,4}
206+
end
207+
elseif e.type=="transport-belt-to-ground" then
208+
line_caps={2,2,4,4}
209+
elseif e.type=="splitter" then
210+
line_caps={nil,nil,nil,nil,2,2,2,2}
211+
end
212+
local tl = e.get_transport_line(line)
213+
local item_name = nil
214+
for name,count in pairs(tl.get_contents()) do
215+
-- debug(line..' '..name..' '..count)
216+
item_name = name
217+
end
218+
if tl.get_item_count()>=line_caps[line] then
219+
debug("overflow "..e.type.." "..line.." "..tl.get_item_count())
220+
-- overflow!
221+
local x,y = pos.x,pos.y
222+
local dir = e.direction
223+
if dir==defines.direction.north then
224+
y = y-0.75
225+
if e.type=="splitter" then
226+
if line==5 or line==6 then x = x-0.5 else x = x+0.5 end
227+
end
228+
elseif dir==defines.direction.south then
229+
y = y+0.75
230+
if e.type=="splitter" then
231+
if line==5 or line==6 then x = x+0.5 else x = x-0.5 end
232+
end
233+
elseif dir==defines.direction.east then
234+
x = x+0.75
235+
if e.type=="splitter" then
236+
if line==5 or line==6 then y = y-0.5 else y = y+0.5 end
237+
end
238+
elseif dir==defines.direction.west then
239+
x = x-0.75
240+
if e.type=="splitter" then
241+
if line==5 or line==6 then y = y+0.5 else y = y-0.5 end
242+
end
243+
end
244+
e.surface.spill_item_stack({x,y}, {name=item_name, count=1})
245+
if tl.remove_item({name=item_name, count=1})==0 then
246+
debug("failed to remove "..item_name)
247+
else
248+
debug("removed "..item_name.." at "..pos2s(pos))
249+
end
250+
end
251+
end
252+
end
253+
end
254+
end
255+
end
256+
end
257+
258+
local function lines2s(lines)
259+
out = '['
260+
for k,v in pairs(lines) do
261+
out = out .. v .. ','
262+
end
263+
out = out .. ']'
264+
return out
265+
end
266+
267+
local function check_and_update(entity,ignore_entity,just_one)
268+
if entity then
269+
local box = {}
270+
if just_one then
271+
box = {entity.position,entity.position}
272+
else
273+
box = {{entity.position.x-1.5,entity.position.y-1.5},{entity.position.x+1.5,entity.position.y+1.5}}
274+
end
275+
local entities = game.get_surface(entity.surface.index).find_entities(box)
276+
for _,candidate in pairs(entities) do
277+
if candidate.type == "transport-belt" or
278+
candidate.type == "transport-belt-to-ground" or
279+
candidate.type == "splitter" then
280+
if ignore_entity and candidate==entity then
281+
else
282+
local pos = candidate.position
283+
if not termbelts[pos.y] then termbelts[pos.y] = {} end
284+
t = terminal_belt_lines(candidate,ignore_entity and entity or nil)
285+
if #t>0 then
286+
termbelts[pos.y][pos.x] = {entity=candidate,lines=t}
287+
debug(pos2s(pos)..' terminal '..candidate.type..' '..lines2s(termbelts[pos.y][pos.x].lines))
288+
else
289+
termbelts[pos.y][pos.x] = nil
290+
debug(pos2s(pos)..' non-terminal '..candidate.type)
291+
end
292+
end
293+
end
294+
end
295+
end
296+
end
297+
298+
local function onPlaceEntity(event)
299+
local e = event.created_entity and event.created_entity or event.entity
300+
if e.type=="transport-belt" or
301+
e.type=="transport-belt-to-ground" or
302+
e.type=="splitter" then
303+
check_and_update(e,false,false)
304+
if e.type=="transport-belt-to-ground" and #e.neighbours>0 then
305+
check_and_update(e.neighbours[1],false,true)
306+
end
307+
end
308+
end
309+
310+
local function onRemoveEntity(event)
311+
local e = event.entity
312+
if e.type=="transport-belt" or
313+
e.type=="transport-belt-to-ground" or
314+
e.type=="splitter" then
315+
check_and_update(e,true,false)
316+
if e.type=="transport-belt-to-ground" and #e.neighbours>0 then
317+
check_and_update(e.neighbours[1],false,true)
318+
end
319+
end
320+
end
321+
322+
-- thanks to KeyboardHack on irc.freenode.net #factorio for this function
323+
local function find_all_entities(args)
324+
local entities = {}
325+
for _,surface in pairs(game.surfaces) do
326+
for chunk in surface.get_chunks() do
327+
local top, left = chunk.x * 32, chunk.y * 32
328+
local bottom, right = top + 32, left + 32
329+
args.area={{top, left}, {bottom, right}}
330+
for _, ent in pairs(surface.find_entities_filtered(args)) do
331+
entities[#entities+1] = ent
332+
end
333+
debug("checked chunk during initialisation")
334+
end
335+
end
336+
return entities
337+
end
338+
339+
local function onLoad()
340+
if global.terminal_belts==nil then
341+
global.terminal_belts={}
342+
global.curve_belts={}
343+
termbelts = global.terminal_belts
344+
curvebelts = global.curve_belts
345+
for _,type in pairs({"transport-belt","transport-belt-to-ground","splitter"}) do
346+
for _,e in pairs(find_all_entities{type=type}) do
347+
check_and_update(e,false,true)
348+
end
349+
end
350+
end
351+
352+
curvebelts = global.curve_belts
353+
termbelts = global.terminal_belts
354+
end
355+
356+
script.on_init(onLoad)
357+
script.on_configuration_changed(onLoad)
358+
script.on_load(onLoad)
359+
360+
script.on_event(defines.events.on_built_entity, onPlaceEntity)
361+
script.on_event(defines.events.on_robot_built_entity, onPlaceEntity)
362+
363+
script.on_event(defines.events.on_player_rotated_entity, onPlaceEntity)
364+
365+
script.on_event(defines.events.on_preplayer_mined_item, onRemoveEntity)
366+
script.on_event(defines.events.on_robot_pre_mined, onRemoveEntity)
367+
script.on_event(defines.events.on_entity_died, onRemoveEntity)
368+
369+
script.on_event(defines.events.on_tick, onTick)

Diff for: info.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "belt-overflow",
3+
"version": "0.0.1",
4+
"title": "Belt Overflow",
5+
"author": "Sparr",
6+
"homepage": "http://github.com/sparr/factorio-mod-belt-overflow",
7+
"contact": "[email protected]",
8+
"description": "Causes full belts to overflow at the end.",
9+
"dependencies": ["base >= 0.12.33"]
10+
}

Diff for: license.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Copyright (c) 2016 Clarence "Sparr" Risher
2+
Copyright (c) 2015 GopherAtl
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5+
6+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7+
8+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

0 commit comments

Comments
 (0)