Untitled diff

Created Diff never expires
11 removals
293 lines
19 additions
300 lines


require "config"
require "config"


local freq = 16
local freq = 16
local freq2 = freq ^ 2
local freq2 = freq ^ 2
local totalgen = 0
local totalgen = 0
local chunksize = 32
local chunksize = 32
local original_tree_count = 0
local original_tree_count = 0
local last_status="-"
local last_status="-"
local debug_text1="-"
local debug_text1="-"
local debug_text2="-"
local debug_text2="-"
local tile_whitelist= {"grass","dirt","landfill"} -- this should find any tile that has grass or dirt in the name, so "grass", "grass-dry" and "dirt-dark" etc.
local tile_whitelist= {"grass","dirt","landfill"} -- this should find any tile that has grass or dirt in the name, so "grass", "grass-dry" and "dirt-dark" etc.


local tree_names = {
local tree_names = {
"tree-01",
"tree-01",
"tree-02",
"tree-02",
"tree-02-red",
"tree-02-red",
"tree-03",
"tree-03",
"tree-04",
"tree-04",
"tree-05",
"tree-05",
"tree-06",
"tree-06",
"tree-06-brown",
"tree-06-brown",
"tree-07",
"tree-07",
"tree-08",
"tree-08",
"tree-08-brown",
"tree-08-brown",
"tree-08-red",
"tree-08-red",
"tree-09",
"tree-09",
"tree-09-brown",
"tree-09-brown",
"tree-09-red"
"tree-09-red"
}
}


local function fmod(a,m)
local function fmod(a,m)
return a - math.floor(a / m) * m
return a - math.floor(a / m) * m
end
end


local function log(msg)
local function log(msg)
if enable_debug_window then
if enable_debug_window then
print("NaturalTree mod:" .. msg)
print("NaturalTree mod:" .. msg)
end
end
end
end


-- Pollution was too high to spawn a tree at thepos, so kill up to 3 surrounding trees
-- Pollution was too high to spawn a tree at thepos, so kill up to 3 surrounding trees
local function kill_surrounding_trees(thepos)
local function kill_surrounding_trees(thepos)
bounds={{thepos[1]-5, thepos[2]-5}, {thepos[1]+5, thepos[2]+5}}
bounds={{thepos[1]-5, thepos[2]-5}, {thepos[1]+5, thepos[2]+5}}
strees=game.surfaces[1].find_entities_filtered{area = bounds, type="tree", limit= 3}
strees=game.surfaces[1].find_entities_filtered{area = bounds, type="tree", limit= 3}
for i=1,#strees do
for i=1,#strees do
log("Killed tree due to pollution at "..strees[i].position.x..","..strees[i].position.y)
log("Killed tree due to pollution at "..strees[i].position.x..","..strees[i].position.y)
strees[i].destroy()
strees[i].destroy()
end
end
end
end


-- search area for specified entity names, return true if there are any.
-- search area for specified entity names, return true if there are any.
local function test_entity(surface,area,names)
local function test_entity(surface,area,names)
for i = 1,#names do
for i = 1,#names do
if 0 ~= surface.count_entities_filtered{area = area, type = names[i]} then
if 0 ~= surface.count_entities_filtered{area = area, type = names[i]} then
return false
return false
end
end
end
end
return true
return true
end
end


-- is the tile at newpos valid and a white-listed tile and pollution low enough (true)
-- is the tile at newpos valid and a white-listed tile and pollution low enough (true)
local function test_tile(surface,newpos)
local function test_tile(surface,newpos)
tile = surface.get_tile(newpos[1],newpos[2])
tile = surface.get_tile(newpos[1],newpos[2])
if not tile.valid then
if not tile.valid then
return false
return false
end
end
for i = 1,#tile_whitelist do
for i = 1,#tile_whitelist do
if string.find(tile.name, tile_whitelist[i]) then
if string.find(tile.name, tile_whitelist[i]) then
if surface.get_pollution({newpos[1],newpos[2]}) < pollution_threshold then
if surface.get_pollution({newpos[1],newpos[2]}) < pollution_threshold then
return true
return true
else
else
kill_surrounding_trees(newpos)
kill_surrounding_trees(newpos)
end
end
end
end
end
end
return false
return false
end
end


-- is parameter a equal to any of the b's
-- is parameter a equal to any of the b's
local function eqany(a,b)
local function eqany(a,b)
for i = 1,#b do
for i = 1,#b do
if a == b[i] then
if a == b[i] then
return true
return true
end
end
end
end
return false
return false
end
end


-----------------------------------------
-----------------------------------------
local shuffle_src = {}
local shuffle_src = {}
local shuffle = {}
local shuffled = false


for i = 1,freq do
local function shuffle_it()
for j = 1,freq do
for i = 1,freq do
shuffle_src[i * freq + j] = i * freq + j
for j = 1,freq do
shuffle_src[i * freq + j] = i * freq + j
end
end

while 0 < #shuffle_src do
local p = math.random(1,#shuffle_src)
table.insert(shuffle, shuffle_src[p])
table.remove(shuffle_src, p)
end
end
end
end


local shuffle = {}

while 0 < #shuffle_src do
local p = math.random(1,#shuffle_src)
table.insert(shuffle, shuffle_src[p])
table.remove(shuffle_src, p)
end

-- Playermap is a 2-D map that indicates approximate location of player owned
-- Playermap is a 2-D map that indicates approximate location of player owned
-- entities. It is used for optimizing the algorithm to quickly determine proximity
-- entities. It is used for optimizing the algorithm to quickly determine proximity
-- of the player's properties which would be of player's interest.
-- of the player's properties which would be of player's interest.
-- Because LuaSurface.count_entities_filtered() is slow for large area, we want
-- Because LuaSurface.count_entities_filtered() is slow for large area, we want
-- to call it as few times as possible.
-- to call it as few times as possible.
-- This is similar in function as chunks, but playermap element is greater than
-- This is similar in function as chunks, but playermap element is greater than
-- chunks, because it's not good idea to make scripting languages like Lua
-- chunks, because it's not good idea to make scripting languages like Lua
-- calculating large set of data. Also we only need very rough estimation, so
-- calculating large set of data. Also we only need very rough estimation, so
-- chunk granularity is too fine for us.
-- chunk granularity is too fine for us.
local playermap_freq = 4
local playermap_freq = 4
local playermap = {}
local playermap = {}


local function update_player_map(m, surface)
local function update_player_map(m, surface)
local mm = m % #shuffle + 1
local mm = m % #shuffle + 1
local mx = shuffle[mm] % freq
local mx = shuffle[mm] % freq
local my = math.floor(shuffle[mm] / freq)
local my = math.floor(shuffle[mm] / freq)
for chunk in surface.get_chunks() do
for chunk in surface.get_chunks() do
if fmod(chunk.x + mx, freq) == 0 and fmod(chunk.y + my, freq) == 0 and
if fmod(chunk.x + mx, freq) == 0 and fmod(chunk.y + my, freq) == 0 and
0 < surface.count_entities_filtered{area = {{chunk.x * chunksize, chunk.y * chunksize}, {(chunk.x + 1) * chunksize, (chunk.y + 1) * chunksize}}, force = "player"} then
0 < surface.count_entities_filtered{area = {{chunk.x * chunksize, chunk.y * chunksize}, {(chunk.x + 1) * chunksize, (chunk.y + 1) * chunksize}}, force = "player"} then
local px = math.floor(chunk.x / 4)
local px = math.floor(chunk.x / 4)
local py = math.floor(chunk.y / 4)
local py = math.floor(chunk.y / 4)
if playermap[py] == nil then
if playermap[py] == nil then
playermap[py] = {}
playermap[py] = {}
end
end
playermap[py][px] = m
playermap[py][px] = m
end
end
end
end
if enable_debug_window then
if enable_debug_window then
local pp=game.players[1].position
local pp=game.players[1].position
local ptile = surface.get_tile(pp.x, pp.y)
local ptile = surface.get_tile(pp.x, pp.y)
local ppol=surface.get_pollution({pp.x, pp.y})
local ppol=surface.get_pollution({pp.x, pp.y})
debug_text1="Player is on " .. ptile.name
debug_text1="Player is on " .. ptile.name
debug_text2="Pollution:" .. math.floor(ppol)
debug_text2="Pollution:" .. math.floor(ppol)
end
end
end
end


function on_tick(event)
function on_tick(event)
if not shuffled then
shuffle_it()
shuffled = true
end

-- LuaSurface.count_entities_filtered() is slow, LuaForce.get_entity_count() is much faster, but
-- LuaSurface.count_entities_filtered() is slow, LuaForce.get_entity_count() is much faster, but
-- it needs entity name argument, not type. So we must repeat it for all types of trees.
-- it needs entity name argument, not type. So we must repeat it for all types of trees.
local function count_trees()
local function count_trees()
local c=0
local c=0
for i=1,#tree_names do
for i=1,#tree_names do
c = c + game.forces.neutral.get_entity_count(tree_names[i])
c = c + game.forces.neutral.get_entity_count(tree_names[i])
end
end
return c
return c
end
end


local function grow_trees(m)
local function grow_trees(m)
local num = 0
local num = 0
local allnum = 0
local allnum = 0
local str = ""
local str = ""
local mm = m % #shuffle + 1
local mm = m % #shuffle + 1
local mx = shuffle[mm] % freq
local mx = shuffle[mm] % freq
local my = math.floor(shuffle[mm] / freq)
local my = math.floor(shuffle[mm] / freq)
local surface = game.surfaces[1]
local surface = game.surfaces[1]
local totalc = 0
local totalc = 0
for chunk in surface.get_chunks() do
for chunk in surface.get_chunks() do
allnum = allnum + 1
allnum = allnum + 1


-- Check if any of player's entity is in proximity of this chunk.
-- Check if any of player's entity is in proximity of this chunk.
local function checkPlayerMap()
local function checkPlayerMap()
local px = math.floor(chunk.x / 4)
local px = math.floor(chunk.x / 4)
local py = math.floor(chunk.y / 4)
local py = math.floor(chunk.y / 4)
for y=-1,1 do
for y=-1,1 do
if playermap[py + y] then
if playermap[py + y] then
for x=-1,1 do
for x=-1,1 do
if playermap[py + y][px + x] and m < playermap[py + y][px + x] + freq2 then
if playermap[py + y][px + x] and m < playermap[py + y][px + x] + freq2 then
return true
return true
end
end
end
end
end
end
end
end
return false
return false
end
end


-- Grow trees on only the player's proximity since the player is not
-- Grow trees on only the player's proximity since the player is not
-- interested nor has means to observe deep in the unknown region.
-- interested nor has means to observe deep in the unknown region.
if fmod(chunk.x + mx, freq) == 0 and fmod(chunk.y + my, freq) == 0 and
if fmod(chunk.x + mx, freq) == 0 and fmod(chunk.y + my, freq) == 0 and
checkPlayerMap() then
checkPlayerMap() then
local area = {{chunk.x * chunksize, chunk.y * chunksize}, {(chunk.x + 1) * chunksize, (chunk.y + 1) * chunksize}}
local area = {{chunk.x * chunksize, chunk.y * chunksize}, {(chunk.x + 1) * chunksize, (chunk.y + 1) * chunksize}}
local c = surface.count_entities_filtered{area = area, type = "tree"}
local c = surface.count_entities_filtered{area = area, type = "tree"}
totalc = totalc + c
totalc = totalc + c
if 0 < c then
if 0 < c then
local trees = surface.find_entities_filtered{area = area, type = "tree"}
local trees = surface.find_entities_filtered{area = area, type = "tree"}
if 0 < #trees then
if 0 < #trees then
local nondeadtree = false
local nondeadtree = false
local tree = trees[math.random(#trees)]
local tree = trees[math.random(#trees)]
-- Draw trees until we get a non-dead tree.
-- Draw trees until we get a non-dead tree.
for try = 1,10 do
for try = 1,10 do
if not eqany(tree.name, {"dead-tree", "dry-tree", "dead-grey-trunk", "dry-hairy-tree", "dead-dry-hairy-tree"}) then
if not eqany(tree.name, {"dead-tree", "dry-tree", "dead-grey-trunk", "dry-hairy-tree", "dead-dry-hairy-tree"}) then
nondeadtree = true
nondeadtree = true
break
break
end
end
end
end
if nondeadtree then
if nondeadtree then
local newpos
local newpos
local success = false
local success = false
-- Try until randomly generated position does not block something.
-- Try until randomly generated position does not block something.
for try = 1,10 do
for try = 1,10 do
if tree.valid then
if tree.valid then
newpos = {tree.position.x + (math.random(-5,5)), tree.position.y + (math.random(-5,5))}
newpos = {tree.position.x + (math.random(-5,5)), tree.position.y + (math.random(-5,5))}
local newarea = {{newpos[1] - 1, newpos[2] - 1}, {newpos[1] + 1, newpos[2] + 1}}
local newarea = {{newpos[1] - 1, newpos[2] - 1}, {newpos[1] + 1, newpos[2] + 1}}
local newarea2 = {{newpos[1] - 2, newpos[2] - 2}, {newpos[1] + 2, newpos[2] + 2}}
local newarea2 = {{newpos[1] - 2, newpos[2] - 2}, {newpos[1] + 2, newpos[2] + 2}}
if 0 == surface.count_entities_filtered{area = newarea, type = "tree"} and
if 0 == surface.count_entities_filtered{area = newarea, type = "tree"} and
test_tile(surface, newpos) and
test_tile(surface, newpos) and
0 == surface.count_entities_filtered{area = newarea2, force = "player"} and
0 == surface.count_entities_filtered{area = newarea2, force = "player"} and
surface.can_place_entity{name = tree.name, position = newpos, force = tree.force} then
surface.can_place_entity{name = tree.name, position = newpos, force = tree.force} then
success = true
success = true
break
break
end
end
end
end
end
end
if success then
if success then
num = num + 1
num = num + 1
surface.create_entity{name = tree.name, position = newpos, force = tree.force}
surface.create_entity{name = tree.name, position = newpos, force = tree.force}
log("Created new tree '" .. tree.name .. "' at "..newpos[1]..","..newpos[2])
log("Created new tree '" .. tree.name .. "' at "..newpos[1]..","..newpos[2])
end
end
end
end
end
end
end
end
end
end
end
end
totalgen = totalgen + num
totalgen = totalgen + num
end
end


-- First, cache player map data by searching player owned entities.
-- First, cache player map data by searching player owned entities.
if game.tick % tree_expansion_frequency == 0 then
if game.tick % tree_expansion_frequency == 0 then
local m = math.floor(game.tick / tree_expansion_frequency)
local m = math.floor(game.tick / tree_expansion_frequency)
update_player_map(m, game.surfaces[1])
update_player_map(m, game.surfaces[1])


end
end


-- Delay the loop as half a phase of update_player_map to reduce
-- Delay the loop as half a phase of update_player_map to reduce
-- 'petit-freeze' duration as possible.
-- 'petit-freeze' duration as possible.
if math.floor(game.tick + tree_expansion_frequency / 2) % tree_expansion_frequency == 0 then
if math.floor(game.tick + tree_expansion_frequency / 2) % tree_expansion_frequency == 0 then
local m = math.floor(game.tick / tree_expansion_frequency)
local m = math.floor(game.tick / tree_expansion_frequency)


-- As number of trees grows, the growth rate decreases, maxes at max_trees.
-- As number of trees grows, the growth rate decreases, maxes at max_trees.
local numTrees = count_trees()
local numTrees = count_trees()
if numTrees < max_trees * tree_decrease_start or
if numTrees < max_trees * tree_decrease_start or
numTrees < max_trees * (tree_decrease_start + math.random() * (1 - tree_decrease_start)) then
numTrees < max_trees * (tree_decrease_start + math.random() * (1 - tree_decrease_start)) then
grow_trees(m)
grow_trees(m)
end
end


if enable_debug_window then
if enable_debug_window then
-- Return [rows,active,visited] playermap chunks
-- Return [rows,active,visited] playermap chunks
local function countPlayerMap()
local function countPlayerMap()
local ret = {0,0,0}
local ret = {0,0,0}
for i,v in pairs(playermap) do
for i,v in pairs(playermap) do
ret[1] = ret[1] + 1
ret[1] = ret[1] + 1
for j,w in pairs(v) do
for j,w in pairs(v) do
if m < w + freq2 then
if m < w + freq2 then
ret[2] = ret[2] + 1
ret[2] = ret[2] + 1
end
end
ret[3] = ret[3] + 1
ret[3] = ret[3] + 1
end
end
end
end
return ret
return ret
end
end


if not game.players[1].gui.left.trees then -- create GUI
if not game.players[1].gui.left.trees then -- create GUI
game.players[1].gui.left.add{type="frame", name="trees", caption="Debug Tree Info", direction="vertical"}
game.players[1].gui.left.add{type="frame", name="trees", caption="Debug Tree Info", direction="vertical"}
game.players[1].gui.left.trees.add{type="label",name="m",caption="Cycle: " .. m % #shuffle .. "/" .. #shuffle}
game.players[1].gui.left.trees.add{type="label",name="m",caption="Cycle: " .. m % #shuffle .. "/" .. #shuffle}
game.players[1].gui.left.trees.add{type="label",name="total",caption="Total trees: " .. count_trees()}
game.players[1].gui.left.trees.add{type="label",name="total",caption="Total trees: " .. count_trees()}
game.players[1].gui.left.trees.add{type="label",name="count",caption="Added trees: " .. totalgen}
game.players[1].gui.left.trees.add{type="label",name="count",caption="Added trees: " .. totalgen}
game.players[1].gui.left.trees.add{type="label",name="playermap",caption="info goes here"}
game.players[1].gui.left.trees.add{type="label",name="playermap",caption="info goes here"}
game.players[1].gui.left.trees.add{type="label",name="status1",caption="debug_text1 goes here"}
game.players[1].gui.left.trees.add{type="label",name="status1",caption="debug_text1 goes here"}
game.players[1].gui.left.trees.add{type="label",name="status2",caption="debug_text2 goes here"}
game.players[1].gui.left.trees.add{type="label",name="status2",caption="debug_text2 goes here"}
else -- Update GUI
else -- Update GUI
game.players[1].gui.left.trees.m.caption = "Cycle: " .. m % #shuffle .. "/" .. #shuffle
game.players[1].gui.left.trees.m.caption = "Cycle: " .. m % #shuffle .. "/" .. #shuffle
game.players[1].gui.left.trees.total.caption = "Total trees: " .. count_trees()
game.players[1].gui.left.trees.total.caption = "Total trees: " .. count_trees()
game.players[1].gui.left.trees.count.caption = "Added trees: " .. totalgen
game.players[1].gui.left.trees.count.caption = "Added trees: " .. totalgen
game.players[1].gui.left.trees.status1.caption = "status1=" .. debug_text1
game.players[1].gui.left.trees.status1.caption = "status1=" .. debug_text1
game.players[1].gui.left.trees.status2.caption = "status2=" .. debug_text2
game.players[1].gui.left.trees.status2.caption = "status2=" .. debug_text2
local cc = countPlayerMap()
local cc = countPlayerMap()
game.players[1].gui.left.trees.playermap.caption = "Playermap: (rows/active/visited) " .. cc[1] .. "/" .. cc[2] .. "/" .. cc[3]
game.players[1].gui.left.trees.playermap.caption = "Playermap: (rows/active/visited) " .. cc[1] .. "/" .. cc[2] .. "/" .. cc[3]
end
end
end
end
end
end
end
end


-- Register event handlers
-- Register event handlers
script.on_event(defines.events.on_tick, function(event) on_tick(event) end)
script.on_event(defines.events.on_tick, function(event) on_tick(event) end)