surface::set_tiles() to add water can turn water to land

Bugs that are actually features.
Post Reply
User avatar
Mylon
Filter Inserter
Filter Inserter
Posts: 513
Joined: Sun Oct 23, 2016 11:42 pm
Contact:

surface::set_tiles() to add water can turn water to land

Post by Mylon »

In working on my Global Warming mod I tried expanding lakes a few tiles at a time. In doing so, I noticed land tiles were appearing inside of the lake!

The result after a few iterations looks like this:

Image

I'm only adding water tiles to the table I'm passing to set_tiles() but land is appearing instead. In watching my script, water creeps outward but land also creeps inward.

Code: Select all

WATER_THRESHOLD = 5000
RAY_DISTANCE = 500

function runOnce()
	if not global.lakes then
		-- These are threshhold tiles.
		global.shore = {}
		updateLakes()
		
		global.deserts = {}
		updateDeserts()
				
	end
end

function waterWorld(event)
	if not global.lakes then
		runOnce()
	end
	-- Run once every 5 minutes.
	--if event.tick % (60 * 60 * 5) == 0 then
	if event.tick % (120) == 0 then
		local retries = 0
		local pollution = getPollution()
		--For minutes * pollution / WATER_THRESHHOLD, create a new water tile.
		local tides = {}
		for i = 1, pollution / WATER_THRESHOLD, 1 do
			local shore = findShore()
			if shore and shore.valid then
				if checkTile(shore) then -- Is tile still valid?  Check if a wall was built or landfill added.
					table.insert(tides, {name = "water", position = {shore.position.x, shore.position.y}})
				else
					i = i-1 --Try again.
					retries  = retries + 1
				end
				if retries >= 10 then
					game.print("Global Warming: Not enough tiles found to convert.")
					return
				end
			end
		end
		-- Set tiles to water and add them to the list of candidates
		-- TODO: look for anything that can die()
		if #tides > 0 then
			game.surfaces[1].set_tiles(tides)
			for i, tide in pairs(tides) do
				local tile = game.surfaces[1].get_tile(tide.position[1], tide.position[2])
				-- table.insert(global.shore, tile)
				game.print("Converted " .. #tides .. " tiles.")
			end
		end
	end
end

function desertification()
	return
end

function getPollution()
	-- Iterate over all chunks and sum pollution.
	--if event.tick % (60 * 60 *5+ 1) == 0 then
		local pollution = 0
		-- local surface = game.surfaces[1]
		for chunk in game.surfaces[1].get_chunks() do
			pollution = pollution + game.surfaces[1].get_pollution({chunk.x, chunk.y})
		end
		return pollution
	--end
end

function findShore()
	--Pick a random point and a random direction and travel for RAY_DISTANCE units checking for a transition from water_tile to ground_tile or vice versa.
	local chunks = game.surfaces[1].get_chunks()
	for n = 1, 20, 1 do --Make 20 attempts
		
		--local chunk = chunks[math.random(1,#chunks)]
		local chunk = {}
		chunk.x = math.random(-50,50)
		chunk.y = math.random(-50,50)
		local tile = game.surfaces[1].get_tile(chunk.x + math.random(-15,15), chunk.y + math.random(-15,15))
		if not tile then 
			game.print("Global Warming: Tile not valid.")
			return nil
		end
		if not tile.valid then
			game.print("Global Warming: Tile not valid.")
			return nil
		end
		local dx = 0
		local dy = 0
		local direction = math.random(1, 4)
		if direction == 1 then
			dy = -1
		end
		if direction == 2 then
			dx = 1
		end
		if direction == 3 then
			dy = 1
		end
		if direction == 4 then
			dx = -1
		end	
		for i = 0, RAY_DISTANCE, 1 do
			local tileNext = game.surfaces[1].get_tile(tile.position.x + dx, tile.position.y + dy)
			if checkTile(tileNext, direction) then
				return tileNext
			else
				if tileNext and tileNext.valid then
					tile = tileNext
				else
					return nil
				end
			end
		end
	end
	game.print("GlobalWarming: Failed to find a valid lake.")
	return nil
end	

function updateLakes()
	-- Changing to a raycasting method
	return
	-- Iterate over entire map
	-- Create an array of likely candidates.
	-- Let's do this the expensive way and see what happens!
	-- for chunk in game.surfaces[1].get_chunks() do
		-- -- Chunk size assumed to be 32
		
		-- local candidates = {}
		-- for xx = -15, 16, 1 do
			-- local breakloop = false
			-- for yy = -15, 16, 1 do
				-- local tile = game.surfaces[1].get_tile(chunk.x + xx, chunk.y + yy)
				-- if checkTile(tile) then
					-- table.insert(global.shore, tile)
				-- end
			-- -- if breakloop then
				-- -- break
			-- -- end
			-- end
		-- end
	-- end
end

function updateDeserts()
	return
end

function checkChunk(area)
	return -- Changed to a raycasting method
	-- local tiles = game.surfaces[1].find_entities_filtered{area = area, type = "tile"}
	-- for i, tile in pairs(tiles) do
		-- if checkTile(tile) then
			-- table.insert(global.shore, tile)
		-- end
	-- end
end

--Check tile to see if it's a ground tile next to water with no wall on it.  If so, return true
function checkTile(tile, dir)
	if tile and tile.valid then
		if tile.collides_with("ground-tile") then
			if not checkWall(tile) then
				
				local neigh
				-- north
				neigh = game.surfaces[1].get_tile(tile.position.x, tile.position.y -1)
				if neigh and neigh.valid and neigh.collides_with("water-tile") then
					return true
				end
				-- east
				neigh = game.surfaces[1].get_tile(tile.position.x+1, tile.position.y )
				if neigh and neigh.valid and neigh.collides_with("water-tile") then
					return true
				end
				-- south
				neigh = game.surfaces[1].get_tile(tile.position.x, tile.position.y +1)
				if neigh and neigh.valid and neigh.collides_with("water-tile") then
					return true
				end
				-- west
				neigh = game.surfaces[1].get_tile(tile.position.x-1, tile.position.y)
				if neigh and neigh.valid and neigh.collides_with("water-tile") then
					return true
				end
			end
			-- Still here?  Not a candidate
		end
	end
	return false
end

function checkWall(tile)
	local walls = game.surfaces[1].find_entities_filtered{area = {{tile.position.x-0.49, tile.position.y-0.49}, {tile.position.x+0.49, tile.position.y+0.49}}, type = "wall"}
	if #walls == 0 then
		return false
	else
		return true
	end
end

script.on_init(function() 
	runOnce()
end)

script.on_event(defines.events.on_tick, function(event)
	waterWorld(event)
	desertification(event)
end)

script.on_event(defines.events.on_chunk_generated, function(event)
	checkChunk(event.area)
end)

User avatar
steinio
Smart Inserter
Smart Inserter
Posts: 2633
Joined: Sat Mar 12, 2016 4:19 pm
Contact:

Re: surface::set_tiles() to add water can turn water to land

Post by steinio »

But it looks great :)
Image

Transport Belt Repair Man

View unread Posts

Rseding91
Factorio Staff
Factorio Staff
Posts: 13209
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: surface::set_tiles() to add water can turn water to land

Post by Rseding91 »

Yes, that's the tile correction logic for making sure coastlines don't abruptly change to land/water.

It was never designed for fine-grain control like you're trying to do.
If you want to get ahold of me I'm almost always on Discord.

User avatar
Mylon
Filter Inserter
Filter Inserter
Posts: 513
Joined: Sun Oct 23, 2016 11:42 pm
Contact:

Re: surface::set_tiles() to add water can turn water to land

Post by Mylon »

@Steinio: It is pretty awesome for generating a swamp biome!

Post Reply

Return to “Not a bug”