require "util" require "Blueprint" trigger_event = { ["concrete-lamppost"] = true, ["big-electric-pole"] = true, --["curved-power-rail"] = true, --["straight-power-rail"] = true, --["5d-power-rail-water"] = true, --["5d-curved-power-rail-water"] = true } --local direction ={ N=0, NE=1, E=2, SE=3, S=4, SW=5, W=6, NW=7} input2dir = {[0]=-1,[1]=0,[2]=1} --[traveldir] ={[raildir] signalOffset = { [0] = { [0] = {pos={x=1.5,y=0.5}, dir=4} }, [1] = { [3] = {pos={x=1.5,y=1.5}, dir=5}, [7] = {pos={x=0.5,y=0.5}, dir=5} }, [2] = { [2] = {pos={x=-0.5,y=1.5}, dir=6} }, [3] = { [1]={pos={x=-0.5,y=0.5}, dir=7}, [5]={pos={x=-1.5,y=1.5}, dir=7} }, [4] = { [0] = {pos={x=-1.5,y=-0.5}, dir=0} }, [5] = { [3]={pos={x=-0.5,y=-0.5}, dir=1}, [7]={pos={x=-1.5,y=-1.5}, dir=1} }, [6] = { [2] = {pos={x=0.5,y=-1.5}, dir=2} }, [7] = { [1]={pos={x=1.5,y=-1.5}, dir=3}, [5]={pos={x=0.5,y=-0.5}, dir=3} }, } real_signalOffset = { [0] = { [0] = {x=1.5,y=0.5} }, [1] = { [3] = {x=1,y=1}, [7] = {x=1,y=1} }, [2] = { [2] = {x=-0.5,y=1.5} }, [3] = { [1]={x=-1,y=1}, [5]={x=-1,y=1} }, [4] = { [0] = {x=-1.5,y=-0.5} }, [5] = { [3]={x=-1,y=-1}, [7]={x=-1,y=-1} }, [6] = { [2] = {x=0.5,y=-1.5} }, [7] = { [1]={x=1,y=-1}, [5]={x=1,y=-1} }, } -- [traveldir%2][raildir] local signalOffsetCurves = { [0] = { [0] = {pos={x=2.5,y=3.5}, dir=4}, [1] = {pos={x=0.5,y=3.5}, dir=4} }, } local math = math function round(num, idp) local mult = 10 ^ (idp or 0) return math.floor(num*mult +0.5)/mult end function get_signal_weight(rail, settings) local weight = rail.name == settings.rail.curved and settings.curvedWeight or 1 if rail.name ~= settings.rail.curved then if rail.direction % 2 == 1 then weight = 0.75 elseif rail.direction == 0 then weight = 1.26 end end return weight end function addPos(p1,p2) if not p1.x then error("Invalid position", 2) end if p2 and not p2.x then error("Invalid position 2", 2) end local p2 = p2 or {x=0,y=0} return {x=p1.x+p2.x, y=p1.y+p2.y} end function subPos(p1,p2) local p2 = p2 or {x=0,y=0} return {x=p1.x-p2.x, y=p1.y-p2.y} end local rot = {} for i=0,7 do local rad = i* (math.pi/4) rot[rad] = {cos=math.cos(rad),sin=math.sin(rad)} end function rotate(pos, rad) if not rot[rad] then error("rot["..rad.."]",2) end local cos, sin = rot[rad].cos, rot[rad].sin local r = {{x=cos,y=-sin},{x=sin,y=cos}} local ret = {x=0,y=0} ret.x = pos.x * r[1].x + pos.y * r[1].y ret.y = pos.x * r[2].x + pos.y * r[2].y return ret end function pos2Str(pos) if not pos then error("Position is nil", 2) end if not pos.x or not pos.y then pos = {x=0,y=0} end return util.positiontostr(pos) end function pos12toXY(pos) if not pos then error("nil pos", 2) end return {x=pos[1],y=pos[2]} end function fixPos(pos) local ret = {} if not pos then error("Position is nil", 2) end if pos[1] and pos[2] then return pos end if pos.x then ret[1] = pos.x end if pos.y then ret[2] = pos.y end return ret end function distance(pos1, pos2) if not pos1.x then error("invalid pos1", 2) end if not pos2.x then error("invalid pos2", 2) end return util.distance(pos1, pos2) end function expandPos(pos, range) local range = range or 0.5 if not pos or not pos.x then error("expandPos: invalid pos",3) end return {{pos.x - range, pos.y - range}, {pos.x + range, pos.y + range}} end -- defines a direction as a number from 0 to 7, with its opposite calculateable by adding 4 and modulo 8 function oppositedirection(direction) return (direction + 4) % 8 end function moveposition(pos, direction, distance) if not pos then error("Position is nil", 2) end if not pos[1] or not pos[2] then error("invalid position", 2) end if direction % 2 == 0 then return util.moveposition(pos, direction, distance) else local dirs = {[1] = {0,2}, [3] = {2,4}, [5] = {4,6}, [7] = {0,6}} return util.moveposition(util.moveposition(pos, dirs[direction][1], distance), dirs[direction][2], distance) end end local function moveRight(pos, direction, distance) local dir = (direction + 2) % 8 return pos12toXY(moveposition(fixPos(pos), dir, distance)) end local function moveLeft(pos, direction, distance) local dir = (direction + 6) % 8 return pos12toXY(moveposition(fixPos(pos), dir, distance)) end function diagonal_to_real_pos(rail) local data = { [1] = {x=0.5,y=-0.5}, [3] = {x=0.5,y=0.5}, [5] = {x=-0.5,y=0.5}, [7] = {x=-0.5,y=-0.5} } if rail.type and rail.type == "straight-rail" then local off = data[rail.direction] and data[rail.direction] or {x=0,y=0} return addPos(off, rail.position) else return rail.position end end function moveRail(rail, direction,distance) local data = { [1] = {x=0.5,y=-0.5}, [3] = {x=0.5,y=0.5}, [5] = {x=-0.5,y=0.5}, [7] = {x=-0.5,y=-0.5} } local distance = (rail.type == "straight-rail" and rail.direction % 2 == 1) and distance or distance*2 local off = data[rail.direction] and data[rail.direction] or {x=0,y=0} pos = addPos(off, rail.position) pos = pos12toXY(moveposition(fixPos(pos), direction, distance)) local newRail = {name=rail.name, type=rail.type, direction=rail.direction, position=pos, force=game.players[1].force} if rail.type == "straight-rail" and rail.direction%2 == 1 and distance % 2 == 1 then newRail.direction = (rail.direction+4)%8 end off = data[newRail.direction] or {x=0,y=0} pos = subPos(pos, off) newRail.position = pos return newRail end function move_right_forward(pos, direction, right,forward) local dir = (direction + 2) % 8 return pos12toXY(moveposition(moveposition(fixPos(pos), dir, right),direction,forward)) end function get_signal_for_rail(rail, traveldir, end_of_rail) local rail_pos = diagonal_to_real_pos(rail) local offset = real_signalOffset[traveldir][rail.direction] local pos = addPos(rail_pos, offset) local dir = (traveldir+4)%8 local signal = {name="rail-signal", position=pos,direction=dir} if rail.force then signal.force = rail.force end if end_of_rail and rail.direction % 2 == 0 then signal.position = move_right_forward(signal.position, traveldir, 0,1) end return signal end function saveBlueprint(player, poleType, type, bp) local psettings = Settings.loadByPlayer(player) if not psettings.activeBP then psettings.activeBP = {} end psettings.activeBP[type] = util.table.deepcopy(bp) end function protectedKey(ent) if ent.valid then return ent.name .. ":" .. ent.position.x..":"..ent.position.y..":"..ent.direction end return false end function get_item_name(some_name) if not global.item_names[some_name] then local name = false if game.item_prototypes[some_name] then name = game.item_prototypes[some_name].name elseif game.entity_prototypes[some_name] then local items = game.entity_prototypes[some_name].items_to_place_this local _, item = next(items) name = item.name else --it's a tile?! if some_name == "stone-path" then name = "stone-brick" end end if name then global.item_names[some_name] = name else error("Couldn't find item for:"..some_name,2) return nil end end return global.item_names[some_name] end --apiCalls = {find={item=0,tree=0,stone=0,other=0},canplace=0,create=0,count={item=0,tree=0,stone=0,other=0}} local RED = {r = 0.9} local GREEN = {g = 0.7} local YELLOW = {r = 0.8, g = 0.8} FARL = { curvePositions = { [0] = {straight={dir=0, off={x=1,y=3}}, diagonal = {dir=5, off={x=-1,y=-3}}}, [1] = {straight={dir=0, off={x=-1,y=3}}, diagonal = {dir=3, off={x=1,y=-3}}}, [2] = {straight={dir=2, off={x=-3,y=1}}, diagonal = {dir=7, off={x=3,y=-1}}}, [3] = {straight={dir=2, off={x=-3,y=-1}}, diagonal = {dir=5, off={x=3,y=1}}}, [4] = {straight={dir=0, off={x=-1,y=-3}}, diagonal = {dir=1, off={x=1,y=3}}}, [5] = {straight={dir=0, off={x=1,y=-3}}, diagonal = {dir=7, off={x=-1,y=3}}}, [6] = {straight={dir=2, off={x=3,y=-1}}, diagonal = {dir=3, off={x=-3,y=1}}}, [7] = {straight={dir=2, off={x=3,y=1}}, diagonal = {dir=1, off={x=-3,y=-1}}} }, new = function(player, ent) local vehicle local driver = false if ent then vehicle = ent else vehicle = player.vehicle driver = player end local new = { locomotive = vehicle, train=vehicle.train, driver=driver, active=false, lastrail=false, direction = false, input = 1, signalCount = {main=0}, cruise = false, cruiseInterrupt = 0, lastposition = false, surface = vehicle.surface, destroy = false, concrete_queue = {}, rail_queue = {} } new.settings = Settings.loadByPlayer(player) setmetatable(new, {__index=FARL}) return new end, onPlayerEnter = function(player) local i = FARL.findByLocomotive(player.vehicle) local farl if i then global.farl[i].driver = player global.farl[i].settings = Settings.loadByPlayer(player) global.farl[i].destroy = false farl = global.farl[i] else farl = FARL.new(player) table.insert(global.farl, farl) end farl.train = player.vehicle.train farl.frontmover = false for i,l in pairs(farl.train.locomotives.front_movers) do if l == farl.locomotive then farl.frontmover = true break end end if farl.settings.bulldozer and not farl:bulldozerModeAllowed() then farl.settings.bulldozer = false farl:print({"msg-bulldozer-error"}) farl:print({"msg-bulldozer-disabled"}) end if remote.interfaces.YARM and remote.interfaces.YARM.hide_expando then farl.settings.YARM_old_expando = remote.call("YARM", "hide_expando", player.index) end --apiCalls = {find={item=0,tree=0,stone=0,other=0},canplace=0,create=0,count={item=0,tree=0,stone=0,other=0}} end, onPlayerLeave = function(player, tick) for i,f in pairs(global.farl) do if f.driver and f.driver == player then f:deactivate() f.driver = false f.destroy = tick f.lastMove = nil f.railBelow = nil f.next_rail = nil if remote.interfaces.YARM and remote.interfaces.YARM.show_expando and f.settings.YARM_old_expando then remote.call("YARM", "show_expando", player.index) end --f.settings = false break end end --debugDump(apiCalls,true) --apiCalls = {find={item=0,tree=0,stone=0,other=0},canplace=0,create=0,count={item=0,tree=0,stone=0,other=0}} end, findByLocomotive = function(loco) for i,f in pairs(global.farl) do if f.locomotive == loco then return i end end return false end, findByPlayer = function(player) for i,f in pairs(global.farl) do if f.locomotive == player.vehicle then f.driver = player return f end end return false end, update = function(self, event) if not self.driver then return end if not self.train.valid then if self.locomotive.valid then self.train = self.locomotive.train else self:deactivate("Error (invalid train)") return end end self.cruiseInterrupt = self.driver.riding_state.acceleration self:cruiseControl() if self.active then self.input = self.driver.riding_state.direction --local next_rail = self:findNeighbour(below, self.direction, self.input) or self:get_connected_rail(below, false, self.direction) --if not next_rail then if not self.lastrail.valid then self:deactivate({"msg-error-2"}) return end local firstWagon = self.frontmover and self.train.carriages[1] or self.train.carriages[#self.train.carriages] if distance(self.lastrail.position, firstWagon.position) < 6 then --log("start update") --debugDump(#self.path, true) --if not self.last_moved then self.last_moved = game.tick end --local diff = game.tick - self.last_moved --self:print(diff.."@"..game.tick) --self.last_moved = game.tick self.acc = self.driver.riding_state.acceleration if ((self.acc ~= 3 and self.frontmover) or (self.acc ~=1 and not self.frontmover)) then --check if previous curve is far enough behind if input is the same if self.lastCurve and self.lastCurve.input == self.input and self.settings.parallelTracks and #self.settings.activeBP.straight.lanes > 0 and not self.settings.bulldozer then --self:print("curveblock:"..self.lastCurve.curveblock.."dist:"..self.lastCurve.dist) if self.lastCurve.dist < self.lastCurve.curveblock then self.input = 1 end end local newTravelDir, nextRail = self:getRail(self.lastrail, self.direction, self.input) if not nextRail then --log("Need extra rail "..serpent.block(self.lastrail)) newTravelDir, nextRail = self:getRail(self.lastrail, self.direction, 1) if not nextRail then --self:print("What happened?") return end end --log("start placeRails") local dir_, last = self:placeRails(nextRail, newTravelDir) --log("end placeRails") if dir_ then if not last.position and not last.name then self:deactivate({"msg-no-entity"}) return end self.protected_index = self.protected_index + 1 if self.protected_index == 7 then self.protected_index = 1 self.protected[self.protected_index] = {} self.protected_tiles[self.protected_index] = {} end --debugDump(self.protected_index,true) -- add created rail to path table.insert(self.path, {rail=last, travel_dir=newTravelDir, input = self.input}) -- remove rails behind train from path local behind = self:rail_behind_train() local found = false local max = #self.path for i=max,1,-1 do local rail = self.path[i].rail if rail == behind and i>1 then found = i end if found and found > i then local name = self.path[i].rail.name local rail = self.path[i].rail self:protect(rail) if self.settings.bulldozer then self:bulldoze_area(rail,self.path[i].travel_dir) if self.path[i].rail.destroy() then self:addItemToCargo(name, 1, true) else self:deactivate({"msg-cant-remove"}) return end end table.remove(self.path,i) end end --self:flyingText2("X", RED, true,self.path[1].rail.position) if last.type == "curved-rail" then self.lastCurve = {dist=-1, input=self.input, direction=self.direction, blocked={}, curveblock = 0} else self.lastCurve.dist = self.lastCurve.dist + 1 end if not self.settings.bulldozer then --add concrete to the queue to be placed in a tick without track placement (probably the next one) if self.settings.concrete then table.insert(self.concrete_queue, {travelDir = newTravelDir, rail={ direction=last.direction,type=last.type,name=last.name,position=addPos(last.position)}}) end --place rail entities (only walls for now), place on the mainrail euqal to the furthest lagging parallel track if self.settings.railEntities and #self.settings.activeBP.straight.lanes == 0 then local c = #self.path - 1 if c>0 and self.path[c] and self.path[c].rail.name ~= self.settings.rail.curved then local rail = self.path[c].rail table.insert(self.rail_queue, {travelDir = self.path[c].travel_dir, rail={direction=rail.direction,type=rail.type,name=rail.name,position=addPos(rail.position)}}) end end --debugLog("Path length: "..#self.path) if self.settings.poles and #self.path > 2 then local c = #self.path local rail = self.path[c-1].rail local dir = self.path[c-1].travel_dir --debugLog("calculating pole for rail@"..pos2Str(self.path[c-1].rail.position)) local rails = self:getPoleRails(rail, dir, self.path[c-2].travel_dir) if self.path[c-2].rail.name == self.settings.rail.curved and #rails > 0 then rails[1].range[1] = -1 end local bestpole, bestrail = self:getBestPole(self.lastPole, rails, "o") if bestpole then --debugLog("--pole: "..pos2Str(bestpole.p)) self.lastCheckPole = bestpole.p self.lastCheckDir = bestpole.dir self.lastCheckRail = bestrail --if global.debug_log then --self:flyingText2("bp", RED, true, bestpole.p) --self:flyingText2("br", RED, true, bestrail.position) --end else --self:print("should place pole") --self:flyingText2("sp", RED, true, self.lastCheckPole) if self.lastCheckPole.x then --debugLog("--Should be placing pole @"..pos2Str(self.lastCheckPole)) local status, err = pcall(function() self:placePole(self.lastCheckPole, self.lastCheckDir) end) if not status then error(err, 2) end end end end if self.settings.signals then self.signalCount.main = self.signalCount.main + get_signal_weight(last,self.settings) if last.type == "curved-rail" then self.signalCount.main = self.signalCount.main - self.lanes.max_lag[newTravelDir%2]-get_signal_weight(last,self.settings) end debugLog(self.signalCount, "signal count: ") if self:getCargoCount("rail-signal") > 0 then if self:placeSignal(newTravelDir,nextRail) then self.signalCount.main = 0 end else self:flyingText({"", "Out of ","rail-signal"}, YELLOW, true) end end if self.settings.parallelTracks and #self.settings.activeBP.straight.lanes > 0 then max = -1 local all_placed = 0 for i, l in pairs(self.settings.activeBP.straight.lanes) do if last.type == "curved-rail" then local traveldir = newTravelDir local block = self:placeParallelCurve(newTravelDir, last, i) if block then local lag = math.abs(self.lanes["d"..traveldir%2]["i"..self.input]["l"..i].lag)+block-1 max = lag > max and lag or max self.lastCurve.curveblock = max end else local placed = self:placeParallelTrack(newTravelDir, last, i) if placed then self.lanerails[i] = placed all_placed = self.lanerails[i] and all_placed+1 or all_placed end end end if self.settings.railEntities and all_placed == #self.settings.activeBP.straight.lanes then local lag = self.lanes.max_lag[newTravelDir%2] local c = #self.path - lag if c>0 and self.path[c] and self.lastCurve.dist > lag then local rail = self.path[c].rail rail = {direction=rail.direction,type=rail.type,name=rail.name,position=addPos(rail.position)} table.insert(self.rail_queue, {travelDir = self.path[c].travel_dir, rail=rail}) end end end end self.direction = newTravelDir self.lastrail = last --log("end update success") return else self:deactivate(last) --log("end update fail:"..last) return end end else -- if bulldoze mode is on, prepare the area for the next rail after self.lastrail according to input if self.settings.bulldozer or self.settings.maintenance then local _, next = self:getRail(self.lastrail, self.direction, 1) if next and self.already_prepared ~= pos2Str(next.position) then --self:flyingText2("R", RED,true, next.position) self.already_prepared = pos2Str(next.position) self:bulldoze_area(next, self.direction) end end if self.settings.concrete and #self.concrete_queue > 0 then for i, queue in pairs(self.concrete_queue) do self:placeConcrete(queue.travelDir, queue.rail) end self.concrete_queue = {} end if self.settings.railEntities and #self.rail_queue > 0 then for i,queue in pairs(self.rail_queue) do self:placeRailEntities(queue.travelDir, queue.rail) end self.rail_queue = {} end end end end, show_path = function(self) for i=1, #self.path do self:flyingText2(i, RED, true, diagonal_to_real_pos(self.path[i].rail)) --self:flyingText(i..":"..self.path[i].travel_dir, RED, true, self.path[i].rail.position) --debugDump(path[i].rail.position,true) end end, createBoundingBox = function(self, rail, direction) local bb = direction%2 == 1 and self.settings.activeBP.diagonal.boundingBox or self.settings.activeBP.straight.boundingBox local realpos = diagonal_to_real_pos(rail) local area = { move_right_forward(realpos,direction,bb.tl.x,bb.tl.y), move_right_forward(realpos, direction,bb.br.x,bb.br.y)} local tl = {x=math.min(area[1].x,area[2].x),y=math.min(area[1].y,area[2].y)} local br = {x=math.max(area[1].x,area[2].x),y=math.max(area[1].y,area[2].y)} return {tl, br} end, --prepare an area for entity so it can be placed prepareArea = function(self,entity,range, force) local pos = entity.position local area = (type(range) == "table") and range or false local range = (type(range) ~= "number") and 1.5 or false area = area and area or expandPos(pos,range) --if force or not self:genericCanPlace(entity) then --debugDump(area,true) --self:showArea2(area) --log(game.tick.." prepArea") self:removeTrees(area) self:pickupItems(area) self:removeStone(area) --log(game.tick.." prepArea done") --else --return true --end if entity and entity.name and not self:genericCanPlace(entity) then self:fillWater(area) end return self:genericCanPlace(entity) end, prepareAreaForCurve = function(self, newRail) local areas = clearAreas[newRail.direction%4] for i=1,2 do local area = areas[i] local tl, lr = fixPos(addPos(newRail.position,area[1])), fixPos(addPos(newRail.position,area[2])) area = {{tl[1]-1,tl[2]-1},{lr[1]+1,lr[2]+1}} self:removeTrees(area) self:pickupItems(area) self:removeStone(area) if self.settings.bulldozer or self.settings.maintenance then local types = {"straight-rail", "curved-rail", "rail-signal", "rail-chain-signal", "electric-pole", "lamp", "wall"} for _, t in pairs(types) do self:removeEntitiesFiltered({area=area, type=t}, self.protected) end end if not self:genericCanPlace(newRail) then self:fillWater(area) end end end, removeTrees = function(self, area) --apiCalls.count.tree = apiCalls.count.tree + 1 for _, entity in pairs(self.surface.find_entities_filtered{area = area, type = "tree"}) do local stat = global.statistics[self.locomotive.force.name].removed["tree-01"] or 0 global.statistics[self.locomotive.force.name].removed["tree-01"] = stat+1 if not godmode and self.settings.collectWood then local products=entity.prototype.mineable_properties.products --get the products of this tree if #products == 1 and products[1].name == "raw-wood" then self:addItemToCargo("raw-wood", 1) else local floor, round, random = math.floor, round, math.random for k,v in pairs(products) do if v.type == "item" then local amount if v.amount then amount = v.amount else local rounded_probability = 1 local rounded_random = 1 if v.probability < 1 then --determine whether or not we are adding this item to inventory based on it's drop probability rounded_probability = floor( round( v.probability, 2 ) ) rounded_random = round( random(), 2 ) end if rounded_probability >= rounded_random then amount = floor( random( v.amount_min, v.amount_max ) ) end end if amount then self:addItemToCargo( v.name, math.ceil( amount / 2 ) ) end end end end end entity.die() end end, removeStone = function(self, area) --apiCalls.count.stone = apiCalls.count.stone + 1 if removeStone then for _, entity in pairs(self.surface.find_entities_filtered{area = area, name = "stone-rock"}) do entity.die() local stat = global.statistics[self.locomotive.force.name].removed["stone-rock"] or 0 global.statistics[self.locomotive.force.name].removed["stone-rock"] = stat+1 end end end, -- args = {area=area, name="name"} or {area=area,type="type"} -- exclude: table with entities as keys removeEntitiesFiltered = function(self, args, exclude) local force = self.locomotive.force local neutral_force = game.forces.neutral for _, entity in pairs(self.surface.find_entities_filtered(args)) do if not self:isProtected(entity) and (entity.force == force or entity.force == neutral_force) then local item = false local name = entity.name if entity.prototype.items_to_place_this then local k, _ = next(entity.prototype.items_to_place_this) item = k end if trigger_event[name] then game.raise_event(defines.events.on_robot_pre_mined, {entity = entity}) end if not entity.destroy() then self:deactivate({"msg-cant-remove"}) return else if item and name ~= "concrete-lamp" then self:addItemToCargo(item, 1) local stat = global.statistics[self.locomotive.force.name].removed[item] or 0 global.statistics[self.locomotive.force.name].removed[item] = stat+1 end end end end end, fillWater = function(self, area) local status, err = pcall(function() -- check if bridging is turned on in settings if self.settings.bridge then -- following code mostly pulled from landfill mod itself and adjusted to fit local tiles = {} local st, ft = area[1],area[2] local dw, w = 0, 0 if not st[1] then st = fixPos(st) ft = fixPos(ft) end for x = st[1], ft[1], 1 do for y = st[2], ft[2], 1 do local tileName = self.surface.get_tile(x, y).name -- check that tile is water, if it is add it to a list of tiles to be changed to grass if tileName == "water" or tileName == "deepwater" then if tileName == "water" then w = w+1 else dw = dw+1 end table.insert(tiles,{name="grass", position={x, y}}) end end end self:replaceWater(tiles, w, dw) end end) if not status then debugDump(area,true) error(err, 3) end end, replaceWater = function(self, tiles, w, dw) -- check to make sure water tiles were found if #tiles ~= 0 then -- if they were calculate the minimum number of landfills to fill them in ( quick and dirty at the moment may need tweeking to prevent overusage) local lfills = math.ceil(w/2 + dw*1.5) lfills = lfills > 20 and 20 or lfills -- check to make sure there is enough landfill in the FARL and if there is apply the changes, remove landfill. if not then show error message if self:getCargoCount("concrete") >= lfills then self.surface.set_tiles(tiles) self:removeItemFromCargo("concrete", lfills) else self:print({"msg-not-enough-concrete", {"item-name.concrete"}}) end end end, placeConcrete = function(self, dir, rail) if rail.name == self.settings.rail.curved then return end local type = rail.direction % 2 == 1 and "diagonal" or "straight" local concrete = self.settings.activeBP[type].concrete if not concrete then return end local diff = dir % 2 == 0 and dir or dir-1 local rad = diff * (math.pi/4) local tiles = {} local pave = {} local w,dw = 0,0 local railpos = diagonal_to_real_pos(rail) --mirror directional concrete from color-coding local textured = {} textured["concrete-hazard-left"] = "concrete-hazard-right" textured["concrete-hazard-right"] = "concrete-hazard-left" textured["concrete-fire-left"] = "concrete-fire-right" textured["concrete-fire-right"] = "concrete-fire-left" for _, c in pairs(concrete) do local name = c.name if self.settings.mirrorConcrete and endsWith(name, "-left") or endsWith(name, "-right") then if (type == "straight" and dir % 4 == 2) or (type == "diagonal" and (dir == 3 or dir == 7)) then name = textured[name] or name end end local entity = {name = name} local offset = c.position offset = rotate(offset, rad) local pos = addPos(railpos, offset) self:protect_tile({x=pos.x,y=pos.y}) entity.position = pos pave[name] = pave[name] or {} --self:flyingText2(".", GREEN,true,entity.position) local tileName = self.surface.get_tile(pos.x, pos.y).name -- check that tile is water, if it is add it to a list of tiles to be changed to grass if tileName == "water" or tileName == "deepwater" then if self.settings.bridge then if tileName == "water" then w = w+1 else dw = dw+1 end table.insert(tiles,{name="grass", position={pos.x, pos.y}}) table.insert(pave[name], entity) end elseif tileName ~= name then table.insert(pave[name], entity) end end if self.settings.bridge then self:replaceWater(tiles, w, dw) end for name, p in pairs(pave) do local c = #p if self:getCargoCount(name) > c then if c > 0 then self.surface.set_tiles(p) self:removeItemFromCargo(name, c) local stat = global.statistics[self.locomotive.force.name].created[name] or 0 global.statistics[self.locomotive.force.name].created[name] = stat+c end else self:print({"msg-not-enough-concrete", {"item-name."..get_item_name(name)}}) end end end, removeConcrete = function(self, area) local status, err = pcall(function() local tiles = {} local st, ft = area[1],area[2] if not st[1] then st = fixPos(st) ft = fixPos(ft) end local counts = {} for x = st[1], ft[1], 1 do for y = st[2], ft[2], 1 do local tileName = self.surface.get_tile(x, y).name -- check that tile is placeable by the player if global.tiles[tileName] and not self:is_protected_tile({x=x,y=y}) then counts[tileName] = counts[tileName] or 0 table.insert(tiles,{name=self.replace_tile, position={x, y}}) counts[tileName] = counts[tileName] + 1 end end end self.surface.set_tiles(tiles) for name, c in pairs(counts) do local item = global.tiles[name] self:addItemToCargo(item, c, true) local stat = global.statistics[self.locomotive.force.name].removed[item] or 0 global.statistics[self.locomotive.force.name].removed[item] = stat+c end end) if not status then debugDump(area,true) error(err, 3) end end, pickupItems = function(self, area) --apiCalls.count.item = apiCalls.count.item + 1 if self.surface.count_entities_filtered{area = area, name = "item-on-ground"} > 0 then --apiCalls.find.item = apiCalls.find.item + 1 for _, entity in pairs(self.surface.find_entities_filtered{area = area, name="item-on-ground"}) do self:addItemToCargo(entity.stack.name, entity.stack.count, entity.stack.prototype.place_result) entity.destroy() end end end, find_tile = function(self, sarea1, sarea2, travel_dir) local tpos1 = move_right_forward(pos12toXY(fixPos(sarea1)),travel_dir,-1,0) local tpos2 = move_right_forward(pos12toXY(fixPos(sarea2)),travel_dir, 1,0) local tile1 = self.surface.get_tile(tpos1.x,tpos1.y) local tile2 = self.surface.get_tile(tpos2.x,tpos2.y) local found = false local count = 0 while count < 20 and not found do --self:flyingText2("1", RED,true,tpos1) --self:flyingText2("2", RED,true,tpos2) if tile1.name ~= "water" and tile1.name ~= "deepwater" and not global.tiles[tile1.name] then return tile1.name end if tile2.name ~= "water" and tile2.name ~= "deepwater" and not global.tiles[tile2.name] then return tile2.name end tpos1 = move_right_forward(tpos1,travel_dir,-1,0) tpos2 = move_right_forward(tpos2,travel_dir, 1,0) tile1 = self.surface.get_tile(tpos1.x,tpos1.y) tile2 = self.surface.get_tile(tpos2.x,tpos2.y) count = count+1 end return found end, bulldoze_area = function(self, rail, travel_dir) if rail.name == self.settings.rail.curved then self:prepareAreaForCurve(rail) return end local types = {"straight-rail", "curved-rail", "rail-signal", "rail-chain-signal", "electric-pole", "lamp", "wall"} local rtype = travel_dir % 2 == 0 and "straight" or "diagonal" local bp = self.settings.activeBP[rtype] if rtype == "straight" then local area = self:createBoundingBox(rail, travel_dir) local sarea1 = (travel_dir == 0 or travel_dir == 2) and area[1] or area[2] local sarea2 = (travel_dir == 0 or travel_dir == 2) and area[2] or area[1] local found = self:find_tile(sarea1, sarea2, travel_dir) if found then self.replace_tile = found end self:prepareArea(rail, area) self:removeConcrete(area) local area2 = expandPos(diagonal_to_real_pos(rail),1.5) for _, t in pairs(types) do self:removeEntitiesFiltered({area=area, type=t}, self.protected) self:removeEntitiesFiltered({area=area2,type=t}, self.protected) end else local lastPoint = bp.clearance_points[#bp.clearance_points] for i,p in pairs(bp.clearance_points) do local pos = move_right_forward(rail.position,travel_dir,p.x,0) local area = expandPos(pos, 1.5) if i == 1 then --local tpos = move_right_forward(pos12toXY(area[1]),travel_dir,-1,0) local sarea1 = area[1] local sarea2 = expandPos(move_right_forward(rail.position,travel_dir,lastPoint.x,0))[2] local found = self:find_tile(sarea1, sarea2, travel_dir) if found then self.replace_tile = found end end self:removeTrees(area) self:pickupItems(area) self:removeStone(area) self:removeConcrete(area) for _, t in pairs(types) do self:removeEntitiesFiltered({area=area, type=t}, self.protected) end end end end, getRail = function(self, lastRail, travelDir, input) if not travelDir or not input then error("no traveldir or input", 2) end if travelDir > 7 or travelDir < 0 then self:deactivate("Traveldir wrong: "..travelDir) return false,false end if input > 2 or input < 0 then self:deactivate("Input wrong: "..input) return travelDir, false end if not lastRail then error("no lastRail", 2) end local data = input_to_next_rail[travelDir][lastRail.type] if not data then error("no data", 2) end if not data[lastRail.direction] or not data[lastRail.direction][input] then if not data[lastRail.direction] then return travelDir, false end input = 1 end data = data[lastRail.direction][input] local name = data.type == "straight-rail" and self.settings.rail.straight or self.settings.rail.curved local newTravelDir = (travelDir + input2dir[input]) % 8 return newTravelDir, {name = name, position = addPos(lastRail.position, data.offset), direction=data.direction, type=data.type} end, cruiseControl = function(self) local acc = self.frontmover and defines.riding.acceleration.accelerating or defines.riding.acceleration.reversing local decc = self.frontmover and defines.riding.acceleration.reversing or defines.riding.acceleration.accelerating --local speed = math.abs(self.train.speed) local limit = self.active and self.settings.cruiseSpeed or 0.9 if self.cruise then if self.cruiseInterrupt == 2 then self:toggleCruiseControl() return end if self.train.speed < limit then self.driver.riding_state = {acceleration = acc, direction = self.driver.riding_state.direction} elseif self.active and self.train.speed > limit + 0.1 then self.driver.riding_state = {acceleration = decc, direction = self.driver.riding_state.direction} else self.driver.riding_state = {acceleration = 0, direction = self.driver.riding_state.direction} end else self.driver.riding_state = {acceleration = self.driver.riding_state.acceleration, direction = self.driver.riding_state.direction} end end, findNeighbours = function(self, rail, travelDir) local paths = {} local found = false if not travelDir then error("no travelDir", 2) end for i=0,2 do local newTravel, nrail = self:getRail(rail, travelDir, i) if type(newTravel) == "number" then local railEnt = self:findRail(nrail) if railEnt then --self:flyingText2("N", GREEN, true, railEnt.position) paths[i] = {newTravel, nrail, railEnt} found = true else paths[i] = false end else paths[i] = false end end return found and paths or false end, findNeighbour = function(self, rail, travelDir, input) local neighbour = false local _, nrail = self:getRail(rail, travelDir, input) if nrail then local railEnt = self:findRail(nrail) if railEnt then neighbour = railEnt end end return neighbour end, findRail = function(self, rail) local area = expandPos(rail.position,0.4) local found = false for i,r in pairs(self.surface.find_entities_filtered{area=area, name=rail.name}) do if r.position.x == rail.position.x and r.position.y == rail.position.y and r.direction == rail.direction then found = r break end end return found end, calculate_rail_data = function(self) local curves = { [0]={ [0]={name="curved-rail",type="curved-rail", direction=0,position={x=0,y=0}}, [2]={name="curved-rail",type="curved-rail", direction=1,position={x=2,y=2}}}, [1]={ [0]={name="curved-rail",type="curved-rail", direction=3,position={x=1,y=1}}, [2]={name="curved-rail",type="curved-rail", direction=3,position={x=1,y=1}}}} self.lanes.max_lag = {[0] = 0, [1]=0} self.lanes.min_block = {[0] = 0, [1]=0} for _, direction_self in pairs({0,1}) do self.lanes["d"..direction_self] = {} for _1, input_self in pairs({0,2}) do self.lanes["d"..direction_self]["i"..input_self] = {} local rail = curves[direction_self][input_self] local straight_lanes = self.settings.activeBP.straight.lanes for lane_index, lane in pairs(straight_lanes) do local direction = direction_self local original_dir = direction local s_lane = self.settings.activeBP.straight.lanes[lane_index] local d_lane = self.settings.activeBP.diagonal.lanes[lane_index] local input = input_self -- invert direction, input, distances for diagonal rails if direction%2 == 1 then local input2dir = {[0]=-1,[1]=0,[2]=1} direction = oppositedirection((direction+input2dir[input]) % 8) input = input == 2 and 0 or 2 s_lane = -1*s_lane d_lane = -1*d_lane end local new_curve = {name=rail.name, type=rail.type, direction=rail.direction, force=rail.force} local right = original_dir % 2 == 0 and s_lane*2 or s_lane --left hand turns need to go back, moving right already moves the diagonal rail part local forward = input == 2 and (s_lane-d_lane)*2 or (d_lane-s_lane)*2 new_curve.position = move_right_forward(rail.position, direction, right, forward) local lag = forward/2 local catchup local l,f,r = lag, forward if original_dir == 1 then right = -1*right lag = 2*right+d_lane lag = input == 2 and lag or -1*lag forward = -1*forward forward = forward/2 l,f,r = lag,-1*s_lane,forward if input_self == 2 then f=-1*f end catchup = l+f+r else catchup = l < 0 and l or f end catchup = catchup > 0 and catchup or 0 local block if original_dir == 0 then block = forward/2 else block = -1*forward/2 end local data = {forward=forward,right=right, lag=lag,catchup=catchup, block=block} self.lanes["d"..direction_self]["i"..input_self]["l"..lane_index] = data self.lanes.max_lag[direction_self] = self.lanes.max_lag[direction_self] < math.abs(lag) and math.abs(lag) or self.lanes.max_lag[direction_self] --debugDump(self.lanes.max_lag,true) end end end end, activate = function(self) local status, err = pcall(function() debugLog("Activating") self.lastrail = false self.signalCount = {main=0} self.protected = {} self.protected_index = 1 self.protected[self.protected_index] = {} self.protected_tiles = {} self.protected_tiles[self.protected_index] = {} self.frontmover = false -- self.last_signal = {} -- self.fake_signal_in = false -- self.fake_signalCount = {main=0} self.replace_tile = "grass" self.lastCurve = {dist=20, input=false, direction=0, blocked={}, curveblock = 0} for i,l in pairs(self.train.locomotives.front_movers) do if l == self.locomotive then self.frontmover = true break end end self.direction = self:calcTrainDir() self.lastrail = self:findLastRail() if not self.lastrail then self:deactivate({"msg-error-2"}, true) return end if self.lastrail.name == self.settings.rail.curved then self:deactivate({"msg-error-curves"}, true) return end if debugButton then self:print("TrainDir: "..self.direction) end local first_carriage, bulldoze_rail local same_orientation = false first_carriage = not self.frontmover and self.train.carriages[#self.train.carriages] or self.train.carriages[1] local orientation = first_carriage.orientation for i, carriage in pairs(self.train.carriages) do if carriage.orientation == orientation or (carriage.orientation+0.5)%1 == orientation then same_orientation = orientation else same_orientation = false break end end local _ self.path, _ = self:get_rails_below_train(same_orientation) --debugDump(#self.path,true) if self.settings.bulldozer then bulldoze_rail = self.frontmover and self.train.back_rail or self.train.front_rail if not same_orientation or bulldoze_rail.name == self.settings.rail.curved then debugDump("Train is on a curve somewhere, train doesn't like!",true) self:deactivate() return end end --self:flyingText("FR", YELLOW, true, self:rail_below_train().position) --self:show_path() for i=#self.path-1,#self.path-3,-1 do if self.settings.railEntities and not self.settings.bulldozer then local c = i if c>0 and self.path[c] and self.path[c].rail.name ~= self.settings.rail.curved then local rail = self.path[c].rail table.insert(self.rail_queue, {travelDir = self.path[c].travel_dir, rail={direction=rail.direction,type=rail.type,name=rail.name,position=addPos(rail.position)}}) end end end debugLog("--Path length:"..#self.path) --self:show_path() self:findLastPole(self.lastrail) self:protect(self.lastPole) --self:show_path() local last_signal, signal_rail = false, false local signal_index = false for i=#self.path,1,-1 do local rail = self.path[i].rail local dir = self.path[i].travel_dir last_signal, signal_rail = self:find_signal_rail(rail, dir) if last_signal then self.signalCount = {main=0} self:flyingText2( "S", GREEN, true, last_signal.position) signal_index = i break end end if not signal_index then self.signalCount.main = self.settings.signalDistance end --debugDump(signal_index,true) if signal_index and self.settings.signals and not self.settings.bulldozer then --local c = self.settings.maintenance and front_rail_index+1 or #self.path local c = #self.path for i=signal_index+1,c do local rail = self.path[i].rail local dir = self.path[i].travel_dir if self:getCargoCount("rail-signal") > 0 then if self:placeSignal(dir,rail) then self.signalCount.main = 0 end else self:flyingText({"", "Out of ","rail-signal"}, YELLOW, true) end self.signalCount.main = self.signalCount.main + get_signal_weight(rail,self.settings) end end --debugDump(self.signalCount,true) if last_signal and signal_rail then self:protect(last_signal) --self:flyingText2( "SR", GREEN, true, signal_rail.position) end --self:flyingText2( {"text-behind"}, RED, true, self:rail_behind_train().position) local bps = self.settings.activeBP local diag_lanes = bps.diagonal.lanes local straight_lanes = bps.straight.lanes --debugLog("--Lanes: vert: "..#straight_lanes.." diag: "..#diag_lanes) --debugLog("Poles: vert: "..bps.straight.pole.name.." diag: "..bps.diagonal.pole.name) self.lanes = {} if diag_lanes and straight_lanes and #diag_lanes ~= #straight_lanes then self:deactivate({"msg-error-track-mismatch"}) return end -- lane lag, curve calculations if diag_lanes and straight_lanes then self:calculate_rail_data() end self.signal_in = {} local mainCount = get_signal_weight(self.lastrail, self.settings) for i,l in pairs(straight_lanes) do local lane_data = self.lanes["d"..self.direction%2]["i0"]["l"..i] local lag = math.abs(lane_data.lag) if self.direction%2==1 and lane_data.right > 0 then lag = lag*mainCount end self.signalCount[i] = self.signalCount.main - lag --+ mainCount --self.signal_in[i] = lag end debugLog(self.signalCount, "--Signal count: ") debugLog(self.signal_in,"--Signals_in: ") self.lanerails ={} if self.settings.bulldozer and self.settings.concrete then local d = self.path[#self.path] --self:flyingText2(self.direction, RED,true,d.rail.position) self:placeConcrete(self.direction, {direction=d.rail.direction,type=d.rail.type,name=d.rail.name,position=addPos(d.rail.position)}) end self.active = true end) if not status then self:deactivate({"", {"msg-error-activating"}, err}) end end, find_signal_rail = function(self, rail, travel_dir) local signal = get_signal_for_rail(rail,travel_dir) if not signal then return end local signal_dir = signal.direction local signal_pos = signal.position local range = (travel_dir % 2 == 0) and 1 or 0.5 for _1, name in pairs({"rail-signal", "rail-chain-signal"}) do for _, entity in pairs(self.surface.find_entities_filtered{area = expandPos(signal_pos,range), name = name}) do if entity.direction == signal_dir then return entity, rail end end end end, get_rails_below_train = function(self, no_limit) local behind = self:rail_behind_train() local front = self:rail_below_train() local front_rail_index = 0 local next, dir = self.lastrail, self.direction local path = {} --self:flyingText2("f", RED, true, addPos(front.position,{x=0,y=-1})) --self:flyingText2("b", RED, true, behind.position) --self:flyingText2("n", RED, true, self.lastrail.position) local count = 0 dir = oppositedirection(dir) local limit = no_limit and 1000 or 30 while next and (count < limit) do --self:flyingText2(count, RED, true, next.position) table.insert(path, {rail = next, travel_dir = oppositedirection(dir)}) self:protect(next) if next == behind then break end next, dir = self:get_connected_rail(next, true, dir) count = count + 1 end -- if no_limit and next == behind then -- table.insert(path, {rail = next, travel_dir = oppositedirection(dir)}) -- self:protect(next) -- end local path2 = {} for i=#path,1,-1 do table.insert(path2,path[i]) if front == path[i] then front_rail_index = #path2 end end --debugDump("front index "..front_rail_index,true) return path2, front_rail_index end, deactivate = function(self, reason) self.active = false self.input = nil self.cruise = false self.path = nil -- if self.last_signal then -- for i, signal in pairs(self.last_signal) do -- if signal and signal.valid then -- signal.destroy() -- end -- end -- self.last_signal = nil -- end if reason then self:print({"", {"msg-deactivated"}, ": ", reason}) end self.lastrail = nil self.already_prepared = nil self.lastCurve = 0 self.direction = nil self.lastPole, self.lastCheckPole = nil,nil self.lastCheckRail = nil self.ccNetPole = nil self.protected = {} self.protected_tiles = {} self.protected_index = {} self.concrete_queue = {} self.rail_queue = {} end, toggleActive = function(self) if not self.active then self:activate() return else self:deactivate() end end, toggleCruiseControl = function(self) if not self.cruise then if self.driver and self.driver.riding_state then self.cruise = true local input = self.input or 1 self.driver.riding_state = {acceleration = 1, direction = input} end return else if self.driver and self.driver.riding_state then self.cruise = false local input = self.input or 1 self.driver.riding_state = {acceleration = self.driver.riding_state.acceleration, direction = input} end return end end, bulldozerModeAllowed = function(self) return (self.train.carriages[1].name == "farl" and self.train.carriages[#self.train.carriages].name == "farl") end, toggleBulldozer = function(self) if self.active then self:deactivate({"msg-changing-modes"}) end self.settings.bulldozer = not self.settings.bulldozer if self.settings.bulldozer and not self:bulldozerModeAllowed() then self.settings.bulldozer = false self:print({"msg-bulldozer-error"}) end self.settings.maintenance = false end, toggleMaintenance = function(self) if self.active then self:deactivate({"msg-changing-modes"}) end self.settings.maintenance = not self.settings.maintenance self.settings.bulldozer = false end, findLastRail = function(self) local test = self:rail_below_train() local last = test local count = 1 local limit = (self.settings.maintenance or self.settings.bulldozer) and 5 or 20 while test and count <= limit do --and test.name ~= self.settings.rail.curved do local rail = self:get_connected_rail(test, true, self.direction) if rail and count < limit then test = rail last = rail else break end count = count + 1 end if last then self:flyingText2({"text-front"}, RED, true, last.position) end return last end, addItemToCargo = function(self,item, count, place_result) count = count or 1 local remaining = count - self.train.insert({name=item, count=count}) if remaining > 0 and (self.settings.dropWood or place_result) then local position = self.surface.find_non_colliding_position("item-on-ground", self.driver.position, 100, 0.5) self.surface.create_entity{name = "item-on-ground", position = position, stack = {name = item, count = remaining}} end end, removeItemFromCargo = function(self,item, count) count = count or 1 if godmode then return count end if count == 0 then return 0 end return self.train.remove_item({name=get_item_name(item), count=count}) end, getCargoCount = function(self, item) local name = get_item_name(item) if godmode then return 9001 end return self.train.get_item_count(name) end, genericCanPlace = function(self, arg) if not arg.position or not arg.position.x or not arg.position.y then error("invalid position", 2) elseif not arg.name then error("no name", 2) end local name = arg.type == "entity-ghost" and arg.ghost_name or arg.name --apiCalls.canplace = apiCalls.canplace + 1 if not arg.direction then return self.surface.can_place_entity{name = name, position = arg.position} else return self.surface.can_place_entity{name = name, position = arg.position, direction = arg.direction} end end, genericPlace = function(self, arg, ignore) local canPlace if not ignore then canPlace = self:genericCanPlace(arg) else canPlace = true end local entity if canPlace then local force = arg.force or self.locomotive.force arg.force = force entity = self.surface.create_entity(arg) --apiCalls.create = apiCalls.create + 1 end if entity then if trigger_event[entity.name] then game.raise_event(defines.events.on_robot_built_entity, {created_entity = entity}) end local stat = global.statistics[entity.force.name].created[entity.name] or 0 global.statistics[entity.force.name].created[entity.name] = stat+1 self:protect(entity) --local diff = subPos(arg.position, entity.position) --if diff.x ~= 0 or diff.y~= 0 then --self:flyingText2("x", RED,true,entity.position) --self:print("Misplaced entity: "..entity.name) --self:print("arg:"..pos2Str(arg.position).." ent:"..pos2Str(entity.position)) --end end return canPlace, entity end, --parse blueprints -- chain signal: needs direction == 4, defines track that FARL drives on --normal signals: define signal position for other tracks parseBlueprints = function(self, bp) for j=1,#bp do local e = bp[j].get_blueprint_entities() if e then local bpType, rails, poles, box, offsets = Blueprint.group_entities(e) if not bpType then self:print(rails) return end if #poles > 0 then Blueprint.get_max_pole(poles, offsets) end if rails == 1 and not offsets.chain then local rail = offsets.rails[1] local traveldir = bpType == "straight" and 0 or 1 local c = signalOffset[traveldir][rail.direction] offsets.chain = {direction = c.dir, name = "rail-chain-signal", position = addPos(rail.position, c.pos)} end if offsets.chain and offsets.pole and bpType then local mainRail = false for i,rail in pairs(offsets.rails) do local traveldir = bpType == "straight" and 0 or 1 local signalOff = signalOffset[traveldir][rail.direction] local signalDir = signalOff.dir signalOff = signalOff.pos --local relChain = subPos(offsets.chain.position,rail.position) --local mainPos = subPos(relChain, signalOff) local pos = addPos(rail.position, signalOff) if not mainRail and pos.x == offsets.chain.position.x and pos.y == offsets.chain.position.y and signalDir == offsets.chain.direction then --if not mainRail and mainPos.x == 0 and mainPos.y == 0 and signalDir == offsets.chain.direction then rail.main = true mainRail = rail if rail.direction == 3 then rail = self:getRail(mainRail, 1, 1) end offsets.mainRail = rail end end if mainRail then local lamps = {} for _, l in pairs(offsets.poleEntities) do if l.name ~= "wooden-chest" then table.insert(lamps, {name=l.name, position=subPos(l.position, offsets.pole.position), direction = l.direction}) end end local railPos = mainRail.position if bpType == "diagonal" then railPos = diagonal_to_real_pos(mainRail) end offsets.pole.position = subPos(offsets.pole.position,railPos) local railEntities = {} for _, re in pairs(offsets.railEntities) do table.insert(railEntities, {name=re.name, position=subPos(re.position, railPos), direction = re.direction}) end local concrete = bp[j].get_blueprint_tiles() if concrete then local off = {x=0.5,y=0.5} local pos = subPos(railPos, off) for _, c in pairs(concrete) do table.insert(offsets.concrete, {name=c.name, position=subPos(c.position, pos)}) local position = c.position if box.tl.x > position.x then box.tl.x = position.x end if box.tl.y > position.y then box.tl.y = position.y end if box.br.x < position.x then box.br.x = position.x end if box.br.y < position.y then box.br.y = position.y end end end local bp_rails = {} local lanes = {} local known_rails = {} for _, l in pairs(offsets.rails) do if not l.main then local lane_distance local tmp = {name=l.name, position=subPos(l.position, mainRail.position), direction = l.direction, type=l.name} local move_dir = tmp.position.y < 0 and 1 or 5 if bpType == "diagonal" then lane_distance = subPos(diagonal_to_real_pos(l),diagonal_to_real_pos(mainRail)) lane_distance = lane_distance.x + lane_distance.y else lane_distance = tmp.position.x end lane_distance= lane_distance/2 if not known_rails[lane_distance] and lane_distance ~= 0 then table.insert(lanes, lane_distance) known_rails[lane_distance] = true end local altRail, _ if l.direction % 2 == 1 and mainRail.direction == l.direction then _, altRail = self:getRail(tmp, move_dir, 1) tmp = altRail end table.insert(bp_rails, tmp) end table.sort(lanes) end local signals = {} for index, l in pairs(offsets.signals) do local pos = subPos(l.position, offsets.chain.position) local reverse = (l.direction ~= offsets.chain.direction) if bpType == "straight" then if pos.x % 2 == 0 then reverse = false else reverse = true end end table.insert(signals, {name=l.name, position=pos, direction = l.direction, reverse = reverse}) end local tl = subPos(box.tl, diagonal_to_real_pos(mainRail)) local br = subPos(box.br, diagonal_to_real_pos(mainRail)) --forward tl.y = tl.y < -1.5 and tl.y or -1.5 br.y = br.y > 1.5 and br.y or 1.5 if bpType == "curve" then tl.y = tl.y < -3.5 and tl.y or -3.5 br.y = br.y > 3.5 and br.y or 3.5 end --right tl.x = tl.x < 0 and tl.x or -0.5 br.x = br.x > 0 and br.x or 0.5 --br.x = br.x + 1 local clearance_points = {} if bpType == "diagonal" then tl.x = tl.x - 1.5 tl.y = tl.y - 1.5 br.x = br.x + 1.5 br.y = br.y + 1.5 local tl2 = {x=tl.x+1.5,y=tl.y+1.5} local br2 = {x=br.x-1.5,y=br.y-1.5} for i=tl2.x,br2.x,1.5 do table.insert(clearance_points, {x=i,y=i}) end end --debugDump({tl=tl,br=br},true) local blueprint = { mainRail = mainRail, direction=mainRail.direction, pole = offsets.pole, poleEntities = lamps, rails = bp_rails, signals = signals, concrete = offsets.concrete, lanes = lanes, clearance_points = clearance_points, railEntities = railEntities} blueprint.boundingBox = {tl = tl, br = br} self.settings.activeBP[bpType] = blueprint if #bp_rails > 0 then self.settings.flipPoles = false end saveBlueprint(self.driver, bpType, blueprint) self:print({"msg-bp-saved", bpType, {"entity-name."..blueprint.pole.name}}) else self:print({"msg-bp-chain-direction"}) end else if not bpType then self:print({"msg-bp-rail-direction"}) elseif not offsets.chain then self:print({"msg-bp-chain-missing"}) else --if not offsets.pole then self:print({"msg-bp-pole-missing"}) end end end end end, placeRails = function(self, nextRail, newTravelDir) local newDir = nextRail.direction local newPos = nextRail.position local newRail = {name = nextRail.name, position = newPos, direction = newDir} if newRail.name == self.settings.rail.curved then self:prepareAreaForCurve(newRail) end local rtype = newDir % 2 == 0 and "straight" or "diagonal" local bp = self.settings.activeBP[rtype] local removeItem, removeAmount = newRail.name, 1 local canplace if self.settings.bulldozer and newRail.name ~= self.settings.rail.curved then self:bulldoze_area(newRail, newTravelDir) end if newRail.direction % 2 == 0 and newRail.name ~= self.settings.rail.curved then local area = self:createBoundingBox(newRail, newTravelDir) canplace = self:prepareArea(newRail, area) if not canplace then canplace = self:prepareArea(newRail) end --self:showArea(newRail, newTravelDir, area, 2) else canplace = self:prepareArea(newRail) end if newRail.direction % 2 == 1 and newRail.name == self.settings.rail.straight then for i,p in pairs(bp.clearance_points) do local pos = move_right_forward(newRail.position,newTravelDir,p.x,0) local area = expandPos(pos, 1.5) self:removeTrees(area) self:pickupItems(area) self:removeStone(area) end end local hasRail = self:getCargoCount(newRail.name) > 0 if not hasRail and newRail.name == self.settings.rail.curved then hasRail = self:getCargoCount(self.settings.rail.straight) >= 4 removeItem = self.settings.rail.straight removeAmount = 4 end if canplace and hasRail then newRail.force = self.locomotive.force local _, ent = self:genericPlace(newRail) if self.settings.electric then remote.call("dim_trains", "railCreated", newPos) end if ent then self:removeItemFromCargo(removeItem, removeAmount) else self:deactivate({"msg-no-entity"}) return false end return true, ent elseif not canplace then return false, {"msg-cant-place"} elseif not hasRail then return false, {"msg-out-of-rails"} end end, protect = function(self, ent) if self.settings.maintenance or self.settings.bulldozer then self.protected = self.protected or {} self.protected[self.protected_index] = self.protected[self.protected_index] or {} self.protected[self.protected_index][protectedKey(ent)] = ent end end, protect_tile = function(self, pos_) if self.settings.maintenance or self.settings.bulldozer then local pos = {x=pos_.x,y=pos_.y} if pos.x - round(pos.x) == 0 then pos.x = pos.x+0.5 end if pos.y - round(pos.y) == 0 then pos.y = pos.y+0.5 end self.protected_tiles = self.protected_tiles or {} self.protected_tiles[self.protected_index] = self.protected_tiles[self.protected_index] or {} self.protected_tiles[self.protected_index][pos.x..":"..pos.y] = true end end, isProtected = function(self, ent) local key = protectedKey(ent) if not self.protected then return false end for index, protected in pairs(self.protected) do if protected[key] == ent then return true end end for i, r in pairs(self.path) do if r.rail == ent then return true end end return false end, is_protected_tile = function(self, pos_) local pos = {x=pos_.x,y=pos_.y} if pos.x - round(pos.x) == 0 then pos.x = pos.x+0.5 end if pos.y - round(pos.y) == 0 then pos.y = pos.y+0.5 end local k = pos.x..":"..pos.y for index, protected in pairs(self.protected_tiles) do if protected[k] then return true end end return false end, protectRails = function(self, last_, dir_, limit) local last, dir = last_, dir_ local found, c = 0, 0 while found and c < limit do found = 0 local paths = self:findNeighbours(last, dir) if paths then for i=0,2 do local path = paths[i] if type(path) == "table" then if found == 0 then self:protect(path[3]) last = path[3] dir = path[1] end found = found + 1 end end end if found > 1 then self:deactivate("Junction detected") return false else if found == 0 then return false end end c = c + 1 end return true end, placeParallelCurve = function(self, traveldir, rail, lane_index) local direction = self.direction local input = self.input local s_lane = self.settings.activeBP.straight.lanes[lane_index] local d_lane = self.settings.activeBP.diagonal.lanes[lane_index] local catchup = self.lanes["d"..direction%2]["i"..input]["l"..lane_index].catchup local bi = self.input == 0 and 2 or 0 if catchup > 0 then local dir, last = direction, self.lanerails[lane_index] if last then for i=1,catchup do dir, last = self:getRail(last, dir, 1) if not last then break end if self:getCargoCount(last.name) > 0 then self:prepareArea(last) local _, ent = self:genericPlace(last) self.signalCount[lane_index] = self.signalCount[lane_index] + get_signal_weight(last,self.settings) if self.signal_in[lane_index] then self.signal_in[lane_index] = self.signal_in[lane_index] - 1 end -- if self.fake_signal_in and self.fake_signal_in[lane_index] then -- self.fake_signal_in[lane_index] = self.fake_signal_in[lane_index] - 1 -- end if self.settings.signals then if self:getCargoCount("rail-signal") > 0 then if self:placeParallelSignals(dir, last, lane_index) then self.signalCount[lane_index] = 0 end else self:flyingText({"", "Out of ","rail-signal"}, YELLOW, true) end end -- if global.fake_signals then -- if self.fake_signal_in and self.fake_signal_in[lane_index] and self.fake_signal_in[lane_index] < 1 and rail.name ~= self.settings.rail.curved then -- if self:place_fake_signal(dir, last, lane_index) then self.fake_signalCount[lane_index] = 0 end -- end -- end if not ent then self:print("Failed to create track @"..pos2Str(last.position)) self:flyingText2("E",RED,true, last.position) else self:removeItemFromCargo(ent.name, 1) end else self:deactivate({"msg-out-of-rails"}) return false end end end end -- invert direction, input, distances for diagonal rails if direction%2 == 1 then local input2dir = {[0]=-1,[1]=0,[2]=1} direction = oppositedirection((direction+input2dir[input]) % 8) input = input == 2 and 0 or 2 s_lane = -1*s_lane d_lane = -1*d_lane end local new_curve = {name=rail.name, type=rail.type, direction=rail.direction, force=rail.force} local right = s_lane*2 --left hand turns need to go back, moving right already moves the diagonal rail part local forward = input == 2 and (s_lane-d_lane)*2 or (d_lane-s_lane)*2 --debugDump("l"..lane_index.." c:"..catchup.." b:"..block,true) new_curve.position = move_right_forward(rail.position, direction, right, forward) self.lastCurve.blocked[lane_index] = self.lanes["d"..traveldir%2]["i"..bi]["l"..lane_index].lag local hasRail = self:getCargoCount(new_curve.name) > 0 local remove_item, remove_amount = new_curve.name, 1 if not hasRail and new_curve.name == self.settings.rail.curved then hasRail = self:getCargoCount(self.settings.rail.straight) >= 4 remove_item = self.settings.rail.straight remove_amount = 4 end if not hasRail then self:deactivate({"msg-out-of-rails"}) return false end self:prepareAreaForCurve(new_curve) local _, ent = self:genericPlace(new_curve) if not ent then self:print("Failed to create curve @"..pos2Str(new_curve.position)) else self.signalCount[lane_index] = self.signalCount[lane_index] + get_signal_weight(ent,self.settings) self:removeItemFromCargo(remove_item, remove_amount) end return self.lastCurve.blocked[lane_index] end, placeParallelTrack = function(self, traveldir, lastRail, lane_index) local s_lane = self.settings.activeBP.straight.lanes[lane_index] local d_lane = self.settings.activeBP.diagonal.lanes[lane_index] local new_rail = {name=lastRail.name, type=lastRail.type,direction=lastRail.direction, force=lastRail.force, position=lastRail.position} local lane = self.lanes["d"..traveldir%2]["i0"]["l"..lane_index] local right = traveldir%2==1 and d_lane or s_lane local lag = math.min(lane.lag, self.lanes["d"..traveldir%2]["i2"]["l"..lane_index].lag) new_rail = moveRail(new_rail,traveldir,lag) new_rail = moveRail(new_rail,(traveldir+2)%8,right) local hasRail = self:getCargoCount(new_rail.name) > 0 local blocked = false local block if not hasRail then self:deactivate({"msg-out-of-rails"}) return false end if self.lastCurve.input then block = self.lastCurve.blocked[lane_index] + math.abs(lag) blocked = self.lastCurve.dist < block end --self:flyingText2(self.lastCurve.dist.."<"..block,RED,true,new_rail.position) if self.signal_in[lane_index] then self.signal_in[lane_index] = self.signal_in[lane_index] - 1 end -- if self.fake_signal_in and self.fake_signal_in[lane_index] then -- self.fake_signal_in[lane_index] = self.fake_signal_in[lane_index] - 1 -- end if not blocked and hasRail then self:prepareArea(new_rail) local _, ent = self:genericPlace(new_rail) self.signalCount[lane_index] = self.signalCount[lane_index] + get_signal_weight(new_rail,self.settings) --self.lanes[lane_index].lastrail = ent or new_rail if self.settings.signals then if self:getCargoCount("rail-signal") > 0 then if self:placeParallelSignals(traveldir,new_rail, lane_index) then self.signalCount[lane_index] = 0 end else self:flyingText({"", "Out of ","rail-signal"}, YELLOW, true) end end -- if global.fake_signals then -- if self.fake_signal_in and self.fake_signal_in[lane_index] and self.fake_signal_in[lane_index] < 1 and rail.name ~= self.settings.rail.curved then -- if self:place_fake_signal(traveldir, new_rail, lane_index) then self.fake_signalCount[lane_index] = 0 end -- end -- end if not ent then self:print("Failed to create track @"..pos2Str(new_rail.position)) self:flyingText2("E",RED,true, new_rail.position) return new_rail else self:removeItemFromCargo(ent.name, 1) return ent end end end, placeParallelSignals = function(self,traveldir, rail, lane_index) --if self.signalCount[lane_index] > self.settings.signalDistance and rail.name ~= self.settings.rail.curved then if self.signal_in[lane_index] and self.signal_in[lane_index] < 1 and rail.name ~= self.settings.rail.curved then local signals = traveldir % 2 == 0 and self.settings.activeBP.straight.signals or self.settings.activeBP.diagonal.signals if signals and type(signals) == "table" and signals[lane_index] then local signal_data = signals[lane_index] local end_of_rail = signal_data.reverse local signal_traveldir = signal_data.reverse and oppositedirection(traveldir) or traveldir local signal = get_signal_for_rail(rail,signal_traveldir,end_of_rail) signal.force = self.locomotive.force self:prepareArea(signal) local success, entity = self:genericPlace(signal) if entity then self:protect(entity) self:removeItemFromCargo(signal.name, 1) self.signal_in[lane_index] = false return success, entity else --self:print("Can't place signal@"..pos2Str(pos)) return success, entity end end end end, mirrorEntity = function(self, pos, traveldir) local deg = 0 if traveldir == 0 then deg = 90 elseif traveldir == 1 then deg = -45 elseif traveldir == 2 then deg = 0 elseif traveldir == 3 then deg = 45 elseif traveldir == 4 then deg = -90 elseif traveldir == 5 then deg = -45 elseif traveldir == 6 then deg = 180 elseif traveldir == 7 then deg = 45 end local rad = (deg%360) * math.pi/180 local cos, sin = math.cos(2*rad) , math.sin(2*rad) local r = {{x=cos,y=sin},{x=sin,y=-cos}} local ret = {x=0,y=0} ret.x = pos.x * r[1].x + pos.y * r[1].y ret.y = pos.x * r[2].x + pos.y * r[2].y return ret end, calcPole = function(self,lastrail, traveldir) local status, err = pcall(function() local offset if not lastrail then error("no rail",2) end if not lastrail.name then error("calcPole: invalid rail", 2) end if lastrail.name ~= self.settings.rail.curved then local diagonal = traveldir % 2 == 1 and true or false local pole = not diagonal and self.settings.activeBP.straight.pole or self.settings.activeBP.diagonal.pole local pos = addPos(pole.position) local diff = not diagonal and traveldir or traveldir-1 local rad = diff * (math.pi/4) offset = rotate(pos, rad) if self.settings.flipPoles then offset = self:mirrorEntity(offset, traveldir) end if diagonal then local x,y = 0,0 -- 1 +x -y if lastrail.direction == 1 then x, y = 0.5, - 0.5 elseif lastrail.direction == 3 then x, y = 0.5, 0.5 -- 5 -x +y elseif lastrail.direction == 5 then x, y = - 0.5, 0.5 elseif lastrail.direction == 7 then x, y = - 0.5, - 0.5 end local railPos = {x=x,y=y} offset = addPos(railPos, offset) end return offset else --error("calcPole called with curved", 2) return false end end) if not status then self:deactivate("Error with calcPole: "..serpent.dump({lr=lastrail, tdir=traveldir}, {name="args", comment=false, sparse=false, sortkeys=true})) return false else return err end end, placePoleEntities = function(self,traveldir,pole) local poleEntities = traveldir % 2 == 0 and self.settings.activeBP.straight.poleEntities or self.settings.activeBP.diagonal.poleEntities local diff = traveldir % 2 == 0 and traveldir or traveldir-1 local rad = diff * (math.pi/4) if type(poleEntities) == "table" then for i=1,#poleEntities do if self:getCargoCount(poleEntities[i].name) > 0 then local offset = poleEntities[i].position offset = rotate(offset, rad) if self.settings.flipPoles then offset = self:mirrorEntity(offset, traveldir) end local pos = addPos(pole, offset) --debugDump(pos, true) local entity = {name = poleEntities[i].name, position = pos} if self:prepareArea(entity) then local _, ent = self:genericPlace{name = poleEntities[i].name, position = pos, direction=0,force = self.locomotive.force} if ent then self:protect(ent) self:removeItemFromCargo(poleEntities[i].name, 1) else self:deactivate("Trying to place "..poleEntities[i].name.." failed") end end end end end end, placeRailEntities = function(self,traveldir,rail) local railEntities = traveldir % 2 == 0 and self.settings.activeBP.straight.railEntities or self.settings.activeBP.diagonal.railEntities local diff = traveldir % 2 == 0 and traveldir or traveldir-1 local rad = diff * (math.pi/4) if type(railEntities) == "table" then for i=1,#railEntities do if self:getCargoCount(railEntities[i].name) > 1 then local offset = railEntities[i].position offset = rotate(offset, rad) local pos = addPos(diagonal_to_real_pos(rail), offset) --debugDump(pos, true) local entity = {name = railEntities[i].name, position = pos} if self:prepareArea(entity) then local _, ent = self:genericPlace{name = railEntities[i].name, position = pos, direction=0,force = self.locomotive.force} if ent then self:protect(ent) self:removeItemFromCargo(railEntities[i].name, 1) else self:deactivate("Trying to place "..railEntities[i].name.." failed") end end end end end end, connectCCNet = function(self, pole) if self.settings.ccNet and pole.neighbours.copper[1] and self.ccNetPole then if (self.settings.ccWires == 1 and self:getCargoCount("red-wire") > 0) or (self.settings.ccWires == 2 and self:getCargoCount("green-wire") > 0) or (self.settings.ccWires == 3 and (self:getCargoCount("red-wire") > 0 or self:getCargoCount("green-wire") > 0)) then local c local items if self.settings.ccWires == 1 then c = {defines.circuitconnector.red} items = {"red-wire"} elseif self.settings.ccWires == 2 then c = {defines.circuitconnector.green} items = {"green-wire"} else c = {defines.circuitconnector.red, defines.circuitconnector.green} items = {"red-wire", "green-wire"} end for i=1,#c do if self:getCargoCount(items[i]) > 0 then pole.connect_neighbour({target_entity = self.ccNetPole, wire=c[i]}) self:removeItemFromCargo(items[i], 1) end end end end self.ccNetPole = pole end, findClosestPole = function(self, minPos) local name = self.settings.activeBP.diagonal.pole.name --settings.medium and "medium-electric-pole" or "big-electric-pole" local reach = global.electric_poles[name]--self.settings.medium and 9 or 30 local tmp, ret, minDist = minPos, false, 100 for i,p in pairs(self.surface.find_entities_filtered{area=expandPos(tmp, reach), name=name}) do local dist = distance(p.position, tmp) --debugDump({dist=dist, minPos=minPos, p=p.position},true) if dist < minDist and (p.position.x ~= minPos.x and p.position.y ~= minPos.y) then minDist = dist ret = p end end return ret end, getPolePoints = function(self, rail) if not rail then error("no rail",2) end if not rail.r then error("no r", 3)end local checks = {} local offset = self:calcPole(rail.r, rail.dir) local polePos = addPos(rail.r.position, offset) if type(rail.range[1])~="number" then error("no table2", 4)end for j=rail.range[1],rail.range[2] do table.insert(checks, {dir=rail.dir, pos=moveposition({polePos.x, polePos.y}, rail.dir, j)}) end return checks end, getBestPole = function(self, lastPole, rails, foo) local name = self.settings.activeBP.diagonal.pole.name local reach = global.electric_poles[name] --local reach = self.settings.medium and 9 or 30 --local name = self.settings.medium and "medium-electric-pole" or "big-electric-pole" local max = -1 local maxPole, maxRail local points = {} if not rails then error("no rail",2) end if type(rails)~="table" then error("no table3", 3)end if not lastPole then error("nil pole", 3) end for j, rail in pairs(rails) do local polePoints = self:getPolePoints(rail) for i,pole in pairs(polePoints) do local pos = {x=pole.pos[1],y=pole.pos[2]} --if foo then self:flyingText2(foo, RED, true, pos) end local dist = distance(lastPole.position, pos) table.insert(points, {d=dist, p=pos, dir=pole.dir}) if dist >= max and dist <= reach then max = dist maxRail = rail.r maxPole = {d=dist,p=pos, dir=pole.dir} end end end return maxPole, maxRail end, getPoleRails = function(self, rail, newDir, oldDir) local rails if rail.name == self.settings.rail.curved then --range[1][2] = 0 --debugDump({old=ptraveldir,new=pnewDir},true) local tracks, tmp = FARL.curvePositions[rail.direction], {} tmp.d = {name=self.settings.rail.straight, direction=tracks.diagonal.dir, position=addPos(rail.position,tracks.diagonal.off)} tmp.s = {name=self.settings.rail.straight, direction=tracks.straight.dir, position=addPos(rail.position,tracks.straight.off)} local dDir, sDir if oldDir % 2 == 1 then sDir = newDir dDir = oldDir rails = {[1] = {r=tmp.s, dir=sDir, range={0,2}}, [2] = {r=tmp.d, dir=dDir, range={-1,1}}} else sDir = oldDir dDir = newDir rails = {[1] = {r=tmp.d, dir=dDir, range={0,2}}, [2] = {r=tmp.s, dir=sDir, range={-1,1}}} end else rails = {{r=rail, dir=newDir, range={0,1}}} end return rails end, placePole = function(self, polePos, poleDir) local name = self.settings.activeBP.diagonal.pole.name --local name = self.settings.medium and "medium-electric-pole" or "big-electric-pole" --debugDump(util.distance(pole.position, self.lastPole.position),true) local canPlace = self:prepareArea({name = name, position = polePos}) if not canPlace and self.surface.count_entities_filtered{area=expandPos(polePos,0.6),name=name} > 1 then canPlace = true debugLog("--found pole@"..pos2Str(polePos)) end local hasPole = self:getCargoCount(name) > 0 if canPlace and hasPole then local _, pole = self:genericPlace{name = name, position = polePos, force = self.locomotive.force} if pole then debugLog("--Placed pole@"..pos2Str(polePos)) if not pole.neighbours.copper[1] then self:flyingText({"msg-unconnected-pole"}, RED, true) end if self.settings.poleEntities then self:placePoleEntities(poleDir, polePos) end self:removeItemFromCargo(name, 1) self:connectCCNet(pole) local s_pole = {name=pole.name,position=pole.position} self.lastPole = s_pole self:protect(pole) return true else if not canPlace then debugDump("Can`t place pole@"..pos2Str(polePos),true) else self.lastPole = {name = name, position=polePos} end end else if not hasPole then self:flyingText({"","Out of ", "",name}, YELLOW, true, addPos(self.locomotive.position, {x=0,y=0})) end if not canPlace then debugDump("Can`t place pole@"..pos2Str(polePos),true) end end end, placeSignal = function(self,traveldir, rail) if self.signalCount.main > self.settings.signalDistance and rail.name ~= self.settings.rail.curved then --debugDump(self.signalCount,true) local signal = get_signal_for_rail(rail,traveldir) signal.force = self.locomotive.force self:prepareArea(signal) local success, entity = self:genericPlace(signal) if entity then self:protect(entity) self:removeItemFromCargo(signal.name, 1) --reset lane counter, so that it lines up self.signal_in = self.signal_in or {} local bptype = rail.direction%2==1 and "diagonal" or "straight" for i,l in pairs(self.settings.activeBP[bptype].lanes) do local lane_data = self.lanes["d"..self.direction%2]["i0"]["l"..i] self.signal_in[i] = math.abs(lane_data.lag) + 1 end return success, entity else --self:print("Can't place signal@"..pos2Str(pos)) return success, entity end end return nil end, place_fake_signal = function(self,traveldir, rail, lane_index) if lane_index then local signals = traveldir % 2 == 0 and self.settings.activeBP.straight.signals or self.settings.activeBP.diagonal.signals if signals and type(signals) == "table" and signals[lane_index] then local signal_data = signals[lane_index] traveldir = signal_data.reverse and (traveldir+4)%8 or traveldir end end local li = lane_index and lane_index or "main" local signal = get_signal_for_rail(rail, traveldir) signal.name = "fake-signal" signal.force = self.locomotive.force local success, entity = self:genericPlace(signal) if entity then if lane_index and self.fake_signal_in then self.fake_signal_in[lane_index] = false end if self.last_signal[li] and self.last_signal[li].valid then self.last_signal[li].destroy() end self.last_signal[li] = entity return success, entity end end, findLastPole = function(self, rail) local name = self.settings.activeBP.diagonal.pole.name local reach = global.electric_poles[name] --local name = self.settings.medium and "medium-electric-pole" or "big-electric-pole" --local reach = self.settings.medium and 9 or 30 local min, pole = 900, nil local pos = rail and rail.position or self.locomotive.position for i, p in pairs(self.surface.find_entities_filtered{area=expandPos(pos, reach), name=name}) do local dist = math.abs(distance(pos,p.position)) if min > dist then pole = p min = dist end end local lastrail = self.lastrail or self:findLastRail() local trainDir = self.direction or self:calcTrainDir() if not pole then local offset = {x=1,y=1} if lastrail.name ~= self.settings.rail.curved then offset = self:calcPole(lastrail, trainDir) else self:print("calcPole with curved2") end local tmp = moveposition(fixPos(offset), trainDir, 50) tmp.x, tmp.y = tmp[1],tmp[2] self.lastPole = {position=addPos(lastrail.position, tmp), name=name} self.lastCheckDir = trainDir self.lastCheckPole = addPos(lastrail.position, offset) self.lastCheckRail = lastrail debugLog("No pole found, using "..pos2Str(self.lastPole.position)..", from rail@"..pos2Str(lastrail.position)) --self:placePole(self.lastrail, trainDir) else debugLog("--Found pole: "..pos2Str(pole.position)) self.ccNetPole = pole local s_pole = {name=pole.name,position=pole.position} self.lastPole = s_pole self:flyingText2("p", GREEN, true, pole.position) local calcP = self:calcPole(lastrail,trainDir) if not calcP then return end local tmp = moveposition(fixPos(calcP), trainDir, -1) tmp.x, tmp.y = tmp[1], tmp[2] self.lastCheckPole = addPos(lastrail.position, tmp) self:flyingText2("cp", GREEN, true, self.lastCheckPole) self.lastCheckDir = trainDir self.lastCheckRail = lastrail end end, debugInfo = function(self) local locomotive = self.locomotive --if not self.active then self:activate() end self:print("Train@"..pos2Str(locomotive.position).." dir:"..self:calcTrainDir().." orient:"..locomotive.orientation) self:print("Frontmover: "..tostring(self.frontmover)) self:print("calcDir: "..self.locomotive.orientation * 8) self:print("calcDirRound: "..round(self.locomotive.orientation*8,0)) self:show_path() local bulldoze_rail = self.frontmover and self.train.back_rail or self.train.front_rail self:flyingText("B", GREEN, true, bulldoze_rail.position) local rail = self.train.front_rail if rail then --self:flyingText2("B", GREEN, true, rail.position) self:print("Rail@"..pos2Str(rail.position).." dir:"..rail.direction) local fixed = diagonal_to_real_pos(rail) if rail.direction % 2 == 1 then --self:flyingText2("F", GREEN, true, fixed) self:print("Fixed: "..pos2Str(fixed).." dir:"..rail.direction) end else self:print({"msg-no-rail"}) end local last = self:findLastRail() if last then self:print("Last@"..pos2Str(last.position).." dir:"..last.direction) end if self.lastpole then self:print("Pole@"..pos2Str(self.lastPole)) end end, calcTrainDir = function(self, ent) --local r = (self.locomotive.orientation > 0.99 and self.locomotive.orientation < 1) and 0 or self.locomotive.orientation if not ent then return round(self.locomotive.orientation*8,0) --math.floor(r * 8) else return round(ent.orientation*8,0) --math.floor(r * 8) end end, rail_below_train = function(self) return self.frontmover and self.train.front_rail or self.train.back_rail end, rail_behind_train = function(self) return self.frontmover and self.train.back_rail or self.train.front_rail end, get_connected_rail = function(self, rail, straight_only, travelDir) local dirs = {1,0,2} local ret = false if straight_only then dirs = {1} end for _, i in pairs(dirs) do local newTravel, nrail = self:getRail(rail, travelDir, i) if nrail and newTravel then ret = self:findRail(nrail) if ret then return ret, newTravel end end --ret = rail.get_connected_rail{rail_direction=dir, rail_connection_direction=i} --if ret then --return ret --end end return ret end, print = function(self, msg) if self.driver and self.driver.name ~= "farl_player" then self.driver.print(msg) else self:flyingText(msg, RED, true) end end, flyingText = function(self, line, color, show, pos) if show then pos = pos or addPos(self.locomotive.position, {x=0,y=-1}) color = color or RED self.surface.create_entity({name="flying-text", position=pos, text=line, color=color}) end end, flyingText2 = function(self, line, color, show, pos) if show then pos = pos and addPos(pos,{x=-0.5,y=-0.5}) or addPos(self.locomotive.position, {x=0,y=-1}) color = color or RED self.surface.create_entity({name="flying-text2", position=pos, text=line, color=color}) end end, create_overlay = function(self, position, duration) local tick = game.tick + 60*duration global.overlayStack[tick] = global.overlayStack[tick] or {} local overlay = self.surface.create_entity{name="farl_overlay", position = position} overlay.minable = false overlay.destructible = false table.insert(global.overlayStack[tick], overlay) end, showArea = function(self, rail, direction, area, duration, add) local bb = {tl=area[1],br=area[2]} local min_x = math.min(bb.tl.x, bb.br.x) local max_x = math.max(bb.tl.x, bb.br.x) local min_y = math.min(bb.tl.y, bb.br.y) local max_y = math.max(bb.tl.y, bb.br.y) min_x = math.ceil(min_x+0.5)-0.5 min_y = math.ceil(min_y+0.5)-0.5 max_x = math.ceil(max_x-0.5)+0.5 max_y = math.ceil(max_y-0.5)+0.5 for right=min_x,max_x do for forward=min_y,max_y do local pos = move_right_forward(diagonal_to_real_pos(rail),direction,right,forward) --self:create_overlay({x=right,y=forward},duration) self:create_overlay(pos, duration) end end -- for right=math.min(bb.tl.x,bb.br.x),math.max(bb.tl.x,bb.br.x) do -- for forward=math.min(bb.tl.y,bb.br.y),math.max(bb.tl.y,bb.br.y) do -- local pos = move_right_forward(diagonal_to_real_pos(rail),direction,right,forward) -- --self:create_overlay({x=right,y=forward},duration) -- self:create_overlay(pos, duration) -- end -- end end, showArea2 = function(self, area) --area[1] = tl, area[2]=br debugDump(area,true) for x=area[1].x,area[2].x do for y=area[1].y,area[2].y do self:create_overlay({x=x,y=y}, 1) end end end, } -- [traveldir][rail_type][rail_dir][input] = offset, new rail dir, new rail type input_to_next_rail = -- 0 to 4, 2 to 6: switch sign { -- North/South [0] = { ["straight-rail"] = {[0] = { [0] = {offset={x=-1,y=-5}, direction=0, type="curved-rail"}, [1] = {offset={x=0, y=-2}, direction=0, type="straight-rail"}, [2] = {offset={x=1,y=-5}, direction=1, type="curved-rail"}} }, ["curved-rail"] = { [4] = { [0] = {offset={x=-2,y=-8}, direction=0, type="curved-rail"}, [1] = {offset={x=-1,y=-5}, direction=0, type="straight-rail"}, [2] = {offset={x=0,y=-8}, direction=1, type="curved-rail"} }, [5] = { [0] = {offset={x=0,y=-8}, direction=0, type="curved-rail"}, [1] = {offset={x=1,y=-5}, direction=0, type="straight-rail"}, [2] = {offset={x=2,y=-8}, direction=1, type="curved-rail"} }, } }, [4] = { ["straight-rail"] = {[0] = { [0] = {offset={x=1,y=5}, direction=4, type="curved-rail"}, [1] = {offset={x=0, y=2}, direction=0, type="straight-rail"}, [2] = {offset={x=-1,y=5}, direction=5, type="curved-rail"}} }, ["curved-rail"] = { [0] = { [0] = {offset={x=2,y=8}, direction=4, type="curved-rail"}, [1] = {offset={x=1,y=5}, direction=0, type="straight-rail"}, [2] = {offset={x=0,y=8}, direction=5, type="curved-rail"} }, [1] = { [0] = {offset={x=0,y=8}, direction=4, type="curved-rail"}, [1] = {offset={x=-1,y=5}, direction=0, type="straight-rail"}, [2] = {offset={x=-2,y=8}, direction=5, type="curved-rail"} }, } }, -- East/West [2] = { ["straight-rail"] = {[2]={ [0] = {offset={x=5,y=-1}, direction=2, type="curved-rail"}, [1] = {offset={x=2, y=0}, direction=2, type="straight-rail"}, [2] = {offset={x=5,y=1}, direction=3, type="curved-rail"}} }, ["curved-rail"] = { [6] = { [0] = {offset={x=8,y=-2}, direction=2, type="curved-rail"}, [1] = {offset={x=5,y=-1}, direction=2, type="straight-rail"}, [2] = {offset={x=8,y=0}, direction=3, type="curved-rail"} }, [7] = { [0] = {offset={x=8,y=0}, direction=2, type="curved-rail"}, [1] = {offset={x=5,y=1}, direction=2, type="straight-rail"}, [2] = {offset={x=8,y=-2}, direction=3, type="curved-rail"} } } }, [6] = { ["straight-rail"] = {[2]={ [0] = {offset={x=-5,y=1}, direction=6, type="curved-rail"}, [1] = {offset={x=-2, y=0}, direction=2, type="straight-rail"}, [2] = {offset={x=-5,y=-1}, direction=7, type="curved-rail"}} }, ["curved-rail"] = { [2] = { [0] = {offset={x=-8,y=2}, direction=6, type="curved-rail"}, [1] = {offset={x=-5,y=1}, direction=2, type="straight-rail"}, [2] = {offset={x=-8,y=0}, direction=7, type="curved-rail"} }, [3] = { [0] = {offset={x=-8,y=0}, direction=6, type="curved-rail"}, [1] = {offset={x=-5,y=-1}, direction=2, type="straight-rail"}, [2] = {offset={x=-8,y=-2}, direction=7, type="curved-rail"} } } }, -- NE / SW [1] = { ["straight-rail"] = { [3] = { [0] = {offset={x=3,y=-3}, direction=5, type="curved-rail"}, [1] = {offset={x=2,y=0}, direction=7, type="straight-rail"} }, [7] = { [1] = {offset={x=0,y=-2}, direction=3, type="straight-rail"}, [2] = {offset={x=3,y=-3}, direction=6, type="curved-rail"} } }, ["curved-rail"] = { [1] = { [0] = {offset={x=4,y=-6}, direction=5, type="curved-rail"}, [1] = {offset={x=3,y=-3}, direction=7, type="straight-rail"}, }, [2] = { [1] = {offset={x=3,y=-3}, direction=3, type="straight-rail"}, [2] = {offset={x=6,y=-4}, direction=6, type="curved-rail"} } } }, [5] = { ["straight-rail"] = { [3] = { [1] = {offset={x=0,y=2}, direction=7, type="straight-rail"}, [2] = {offset={x=-3,y=3}, direction=2, type="curved-rail"} }, [7] = { [0] = {offset={x=-3,y=3}, direction=1, type="curved-rail"}, [1] = {offset={x=-2,y=0}, direction=3, type="straight-rail"}, } }, ["curved-rail"] = { [5] = { [0] = {offset={x=-4,y=6}, direction=1, type="curved-rail"}, [1] = {offset={x=-3,y=3}, direction=3, type="straight-rail"} }, [6] = { [1] = {offset={x=-3,y=3}, direction=7, type="straight-rail"}, [2] = {offset={x=-6,y=4}, direction=2, type="curved-rail"} } } }, -- SE / NW [3] = { ["straight-rail"] = { [1] = { [1] = {offset={x=2,y=0}, direction=5, type="straight-rail"}, [2] = {offset={x=3,y=3}, direction=0, type="curved-rail"} }, [5] = { [0] = {offset={x=3,y=3}, direction=7, type="curved-rail"}, [1] = {offset={x=0,y=2}, direction=1, type="straight-rail"}, } }, ["curved-rail"] = { [3] = { [0] = {offset={x=6,y=4}, direction=7, type="curved-rail"}, [1] = {offset={x=3,y=3}, direction=1, type="straight-rail"} }, [4] = { [1] = {offset={x=3,y=3}, direction=5, type="straight-rail"}, [2] = {offset={x=4,y=6}, direction=0, type="curved-rail"} } } }, [7] = { ["straight-rail"] = { [1] = { [0] = {offset={x=-3,y=-3}, direction=3, type="curved-rail"}, [1] = {offset={x=0,y=-2}, direction=5, type="straight-rail"} }, [5] = { [1] = {offset={x=-2,y=0}, direction=1, type="straight-rail"}, [2] = {offset={x=-3,y=-3}, direction=4, type="curved-rail"} } }, ["curved-rail"] = { [0] = { [1] = {offset={x=-3,y=-3}, direction=1, type="straight-rail"}, [2] = {offset={x=-4,y=-6}, direction=4, type="curved-rail"} }, [7] = { [0] = {offset={x=-6,y=-4}, direction=3, type="curved-rail"}, [1] = {offset={x=-3,y=-3}, direction=5, type="straight-rail"} } } }, } --clearArea[curveDir%4] clearAreas = { [0]={ {{x=-2.5,y=-3.5},{x=0.5,y=0.5}}, {{x=-0.5,y=-0.5},{x=2.5,y=3.5}} }, [1]={ {{x=-2.5,y=-0.5},{x=0.5,y=3.5}}, {{x=-0.5,y=-3.5},{x=2.5,y=0.5}} }, [2]={ {{x=-3.5,y=-0.5},{x=0.5,y=2.5}}, {{x=-0.5,y=-2.5},{x=3.5,y=0.5}} }, [3]={ {{x=-3.5,y=-2.5},{x=0.5,y=0.5}}, {{x=-0.5,y=-0.5},{x=3.5,y=2.5}}, } }