--factorio cave miner - mewmew made this
--you can use /c map_pregen() to generate the world before playing to reduce any map rendering lags
--!! use /c map_pregen_end() after it is done generating !!

--[[ 
To do(maybe):
add rare tiny ore spots underneath rocks
tooltip for efficency and fortune
player list coin fix
default value of spawner kill evolution set
spawn dome rougher border
explosives filled chest explosion
cliff explosives > rock mining
lamp power consumption
ore always drops with stone
treasure chests get better with distance
random game events // earthquake, biters, rock treasure, messages
]]--

local simplex_noise = require 'map_gen.shared.simplex_noise'
local Event = require 'utils.event'
local market_items = require "cave_miner_market_items"

local function create_cave_miner_button(player)
	local button = player.gui.top.add({ type = "sprite-button", name = "caver_miner_stats_toggle_button", sprite = "item/iron-axe" })
	button.style.minimal_height = 38
	button.style.minimal_width = 38
	button.style.top_padding = 2
	button.style.left_padding = 4
	button.style.right_padding = 4
	button.style.bottom_padding = 2
end

local function create_cave_miner_stats_gui(player)	
	if player.gui.top["caver_miner_stats_frame"] then
		player.gui.top["caver_miner_stats_frame"].destroy()
	end
	local captions = {}
	local caption_style = {{"font", "default-bold"}, {"font_color",{ r=0.63, g=0.63, b=0.63}}, {"top_padding",2}, {"left_padding",0},{"right_padding",0},{"minimal_width",0}}
	local stat_numbers = {}
	local stat_number_style = {{"font", "default-bold"}, {"font_color",{ r=0.77, g=0.77, b=0.77}}, {"top_padding",2}, {"left_padding",0},{"right_padding",0},{"minimal_width",0}}
	local separators = {}
	local separator_style = {{"font", "default-bold"}, {"font_color",{ r=0.15, g=0.15, b=0.89}}, {"top_padding",2}, {"left_padding",2},{"right_padding",2},{"minimal_width",0}}
	
	local frame = player.gui.top.add { type = "frame", name = "caver_miner_stats_frame" }
	
	local t = frame.add { type = "table", column_count = 11 }	
	
	captions[1] = t.add { type = "label", caption = "Ores mined:" }	
	stat_numbers[1] = t.add { type = "label", caption = global.stats_ores_found }
			
	separators[1] = t.add { type = "label", caption = "|"}
	
	captions[2] = t.add { type = "label", caption = "Rocks broken:" }
	stat_numbers[2] = t.add { type = "label", caption = global.stats_rocks_brocken }
					
	if game.forces.player.manual_mining_speed_modifier > 0 or game.forces.player.mining_drill_productivity_bonus > 0 then
	
		separators[2] = t.add { type = "label", caption = "|"}
		
		captions[3] = t.add { type = "label", caption = "Efficiency" }
		local str = "+"
		str = str .. tostring(game.forces.player.manual_mining_speed_modifier * 100)
		str = str .. "%"		
		stat_numbers[3] = t.add { type = "label", caption = str }
		
		separators[3] = t.add { type = "label", caption = "|"}
		
		captions[5] = t.add { type = "label", caption = "Fortune" }	
		local str = "+"
		str = str .. tostring(game.forces.player.mining_drill_productivity_bonus * 100)
		str = str .. "%"	
		stat_numbers[4] = t.add { type = "label", caption = str }
		
	end	
	
	for _, s in pairs (caption_style) do
		for _, l in pairs (captions) do
			l.style[s[1]] = s[2]
		end
	end
	for _, s in pairs (stat_number_style) do
		for _, l in pairs (stat_numbers) do
			l.style[s[1]] = s[2]
		end
	end
	for _, s in pairs (separator_style) do
		for _, l in pairs (separators) do
			l.style[s[1]] = s[2]
		end
	end
	stat_numbers[1].style.minimal_width = 9 * string.len(tostring(global.stats_ores_found))
	stat_numbers[2].style.minimal_width = 9 * string.len(tostring(global.stats_rocks_brocken))
end

local function refresh_gui()
	for _, player in pairs(game.connected_players) do
		local frame = player.gui.top["caver_miner_stats_frame"]
		if (frame) then
			frame.destroy()
			create_cave_miner_stats_gui(player)					
		end
	end
end

local function treasure_chest(position)
	local p = game.surfaces[1].find_non_colliding_position("wooden-chest",position, 2,0.5)
	if not p then return end
	local e = game.surfaces[1].create_entity {name="wooden-chest",position=p, force="player"}
	e.minable = false
	local i = e.get_inventory(defines.inventory.chest)
	for x = 1, math.random(1,6), 1 do
		local loot = global.treasure_chest_raffle_table[math.random(1,#global.treasure_chest_raffle_table)]
		i.insert(loot)
	end		
end

local function secret_shop(pos)
	local secret_market_items = {		
    {price = {{"raw-fish", math.random(250,450)}}, offer = {type = 'give-item', item = 'combat-shotgun'}},
    {price = {{"raw-fish", math.random(250,450)}}, offer = {type = 'give-item', item = 'flamethrower'}},
    {price = {{"raw-fish", math.random(75,125)}}, offer = {type = 'give-item', item = 'rocket-launcher'}},
    {price = {{"raw-fish", math.random(2,4)}}, offer = {type = 'give-item', item = 'piercing-rounds-magazine'}},
    {price = {{"raw-fish", math.random(14,28)}}, offer = {type = 'give-item', item = 'uranium-rounds-magazine'}},  
    {price = {{"raw-fish", math.random(9,20)}}, offer = {type = 'give-item', item = 'piercing-shotgun-shell'}},
    {price = {{"raw-fish", math.random(6,12)}}, offer = {type = 'give-item', item = 'flamethrower-ammo'}},
    {price = {{"raw-fish", math.random(10,20)}}, offer = {type = 'give-item', item = 'rocket'}},
    {price = {{"raw-fish", math.random(15,25)}}, offer = {type = 'give-item', item = 'explosive-rocket'}},        
    {price = {{"raw-fish", math.random(15,30)}}, offer = {type = 'give-item', item = 'explosive-cannon-shell'}},
    {price = {{"raw-fish", math.random(25,45)}}, offer = {type = 'give-item', item = 'explosive-uranium-cannon-shell'}},   
    {price = {{"raw-fish", math.random(20,40)}}, offer = {type = 'give-item', item = 'cluster-grenade'}}, 
	{price = {{"raw-fish", math.random(1,3)}}, offer = {type = 'give-item', item = 'land-mine'}},	
    {price = {{"raw-fish", math.random(250,500)}}, offer = {type = 'give-item', item = 'modular-armor'}},
    {price = {{"raw-fish", math.random(1500,3000)}}, offer = {type = 'give-item', item = 'power-armor'}},
	{price = {{"raw-fish", math.random(20000,25000)}}, offer = {type = 'give-item', item = 'power-armor-mk2'}},
    {price = {{"raw-fish", math.random(4500,9000)}}, offer = {type = 'give-item', item = 'fusion-reactor-equipment'}},
    {price = {{"raw-fish", math.random(50,100)}}, offer = {type = 'give-item', item = 'battery-equipment'}},
    {price = {{"raw-fish", math.random(700,1100)}}, offer = {type = 'give-item', item = 'battery-mk2-equipment'}},
    {price = {{"raw-fish", math.random(400,700)}}, offer = {type = 'give-item', item = 'belt-immunity-equipment'}},
    {price = {{"raw-fish", math.random(12000,16000)}}, offer = {type = 'give-item', item = 'night-vision-equipment'}},
    {price = {{"raw-fish", math.random(300,500)}}, offer = {type = 'give-item', item = 'exoskeleton-equipment'}},
    {price = {{"raw-fish", math.random(350,500)}}, offer = {type = 'give-item', item = 'personal-roboport-equipment'}},
    {price = {{"raw-fish", math.random(25,50)}}, offer = {type = 'give-item', item = 'construction-robot'}},
    {price = {{"raw-fish", math.random(250,450)}}, offer = {type = 'give-item', item = 'energy-shield-equipment'}},
    {price = {{"raw-fish", math.random(350,550)}}, offer = {type = 'give-item', item = 'personal-laser-defense-equipment'}},    
    {price = {{"raw-fish", math.random(125,250)}}, offer = {type = 'give-item', item = 'railgun'}},
    {price = {{"raw-fish", math.random(2,4)}}, offer = {type = 'give-item', item = 'railgun-dart'}},
	{price = {{"raw-fish", math.random(100,175)}}, offer = {type = 'give-item', item = 'loader'}},
	{price = {{"raw-fish", math.random(200,350)}}, offer = {type = 'give-item', item = 'fast-loader'}},
	{price = {{"raw-fish", math.random(400,600)}}, offer = {type = 'give-item', item = 'express-loader'}}
	}
	local surface = game.surfaces[1]										
	local market = surface.create_entity {name = "market", position = pos}
	market.destructible = false	
	local market_items_to_add = math.random(8,12)
	while market_items_to_add >= 0 do		
		local i = math.random(1,#secret_market_items)
		if secret_market_items[i] then
			market.add_market_item(secret_market_items[i])
			market_items_to_add = market_items_to_add - 1
			secret_market_items[i] = nil
		end
	end
end

local function on_chunk_generated(event)
	if not global.noise_seed then global.noise_seed = math.random(1,5000000) end	
	local surface = game.surfaces[1]	
	local noise = {}
	local tiles = {}	
	local enemy_building_positions = {}
	local enemy_worm_positions = {}
	local rock_positions = {}
	local fish_positions = {}
	local treasure_chest_positions = {}
	local secret_shop_locations = {}
	local extra_tree_positions = {}
	local tile_to_insert = false
	local entity_has_been_placed = false
	local pos_x = 0
	local pos_y = 0
	local tile_distance_to_center = 0			
	local entities = surface.find_entities(event.area)
	for _, e in pairs(entities) do
		if e.type == "resource" or e.type == "tree" or e.force.name == "enemy" then
			e.destroy()				
		end
	end	
	local noise_seed_add = 25000
	local current_noise_seed_add = noise_seed_add	
	local m1 = 0.12
	local m2 = 0.10
	local m3 = 0.07
	
	for x = 0, 31, 1 do
		for y = 0, 31, 1 do			
			pos_x = event.area.left_top.x + x
			pos_y = event.area.left_top.y + y
			tile_distance_to_center = pos_x^2 + pos_y^2
									
			noise[1] = simplex_noise.d2(pos_x/350, pos_y/350,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[2] = simplex_noise.d2(pos_x/200, pos_y/200,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[3] = simplex_noise.d2(pos_x/50, pos_y/50,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[4] = simplex_noise.d2(pos_x/20, pos_y/20,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add			
			local cave_noise = noise[1] + noise[2]*0.3 + noise[3]*0.1 + noise[4]*0.02
			
			noise[1] = simplex_noise.d2(pos_x/120, pos_y/120,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[2] = simplex_noise.d2(pos_x/60, pos_y/60,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[3] = simplex_noise.d2(pos_x/40, pos_y/40,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[4] = simplex_noise.d2(pos_x/20, pos_y/20,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add			
			local cave_noise_2 = noise[1] + noise[2]*0.4 + noise[3]*0.25 + noise[4]*0.1
			
			noise[1] = simplex_noise.d2(pos_x/50, pos_y/50,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[2] = simplex_noise.d2(pos_x/30, pos_y/30,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[3] = simplex_noise.d2(pos_x/20, pos_y/20,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add
			noise[4] = simplex_noise.d2(pos_x/10, pos_y/10,global.noise_seed+current_noise_seed_add)
			current_noise_seed_add = current_noise_seed_add + noise_seed_add			
			local cave_noise_3 = noise[1] + noise[2]*0.5 + noise[3]*0.25 + noise[4]*0.1
			
			current_noise_seed_add = noise_seed_add	
			
			tile_to_insert = false	
			if tile_distance_to_center > global.spawn_dome_size then												
			
				if tile_distance_to_center > (global.spawn_dome_size + 5000) * (cave_noise_3 * 0.05 + 1.1) then
					if cave_noise > 1 then
						tile_to_insert = "deepwater"
						table.insert(fish_positions, {pos_x,pos_y})
					else
						if cave_noise > 0.98 then 
							tile_to_insert = "water"
						else
							if cave_noise > 0.82 then
								tile_to_insert = "grass-1"
								table.insert(enemy_building_positions, {pos_x,pos_y})
								--tile_to_insert = "grass-4"
								--if cave_noise > 0.88 then tile_to_insert = "grass-2" end
								if cave_noise > 0.94 then
									table.insert(extra_tree_positions, {pos_x,pos_y})
									table.insert(secret_shop_locations, {pos_x,pos_y})									
								end								
							else
								if cave_noise > 0.72 then
									tile_to_insert = "dirt-6"
									if cave_noise < 0.79 then table.insert(rock_positions, {pos_x,pos_y}) end
								end
							end
						end
					end
				end
				
				if tile_to_insert == false then
					if cave_noise < m1 and cave_noise > m1*-1 then
						tile_to_insert = "dirt-7"
						table.insert(enemy_worm_positions, {pos_x,pos_y})
						if cave_noise_2 > 0.85 and tile_distance_to_center > global.spawn_dome_size + 40000 then
							if math.random(1,48) == 1 then
								local p = surface.find_non_colliding_position("crude-oil",{pos_x,pos_y}, 5,1)
								local e = surface.create_entity {name="crude-oil", position={pos_x,pos_y}, amount=math.floor(math.random(25000+tile_distance_to_center*0.5,50000+tile_distance_to_center),0)}
							end
						end
					else
						if cave_noise_2 < m2 and cave_noise_2 > m2*-1 and cave_noise > (m2*-1) - 0.3 then
							tile_to_insert = "dirt-4"
							table.insert(treasure_chest_positions, {pos_x,pos_y})								
						else
							if cave_noise_3 < m3 and cave_noise_3 > m3*-1 and cave_noise_2 < m2+0.3 and cave_noise_2 > (m2*-1)-0.3 then
								tile_to_insert = "dirt-2"
								table.insert(treasure_chest_positions, {pos_x,pos_y})
							end
						end
					end
				end
				
				if tile_distance_to_center < global.spawn_dome_size * (cave_noise_3 * 0.05 + 1.1)  then									
					tile_to_insert = "dirt-7"
				end
			else
				if tile_distance_to_center < 750 * (1 + cave_noise_3 * 0.8) then
					tile_to_insert = "water"
					table.insert(fish_positions, {pos_x,pos_y})
				else
					tile_to_insert = "grass-1"
				end				
			end
			
			if tile_distance_to_center < global.spawn_dome_size and tile_distance_to_center > global.spawn_dome_size - 500 and tile_to_insert == "grass-1" then
				table.insert(rock_positions, {pos_x,pos_y})	
			end			
			if tile_to_insert == "dirt-7" or tile_to_insert == "dirt-4" or tile_to_insert == "dirt-2" then	
				 table.insert(rock_positions, {pos_x,pos_y})				
			end
			
			if tile_to_insert == false then
				table.insert(tiles, {name = "out-of-map", position = {pos_x,pos_y}}) 
			else
				table.insert(tiles, {name = tile_to_insert, position = {pos_x,pos_y}}) 
			end
		end							
	end
	surface.set_tiles(tiles,true)
		
	for _, p in pairs(treasure_chest_positions) do	
		if math.random(1,200)==1 then 
			treasure_chest(p)
		end
	end
		
	for _, p in pairs(enemy_building_positions) do	
		if math.random(1,50)==1 then
			local pos = surface.find_non_colliding_position("biter-spawner", p, 16, 1)
			if pos then
				if math.random(1,3) == 1 then
					surface.create_entity {name="spitter-spawner",position=pos}
				else
					surface.create_entity {name="biter-spawner",position=pos}
				end
			end			
		end
	end	
	
	for _, p in pairs(enemy_worm_positions) do		
		if math.random(1,300)==1 then
			local tile_distance_to_center = math.sqrt(p[1]^2 + p[2]^2)
			if tile_distance_to_center > global.worm_free_zone_radius then						
				local raffle_index = math.ceil((tile_distance_to_center-global.worm_free_zone_radius)*0.01, 0)
				if raffle_index < 1 then raffle_index = 1 end
				if raffle_index > 10 then raffle_index = 10 end					
				local entity_name = global.worm_raffle_table[raffle_index][math.random(1,#global.worm_raffle_table[raffle_index])]												
				surface.create_entity {name=entity_name, position=p}
			end
		end
	end	
	
	for _, p in pairs(fish_positions) do	
		if math.random(1,16)==1 then
			if surface.can_place_entity({name="fish",position=p}) then
				surface.create_entity {name="fish",position=p}
			end
		end
	end
		
	for _, p in pairs(secret_shop_locations) do	
		if math.random(1,10)==1 then
			if surface.count_entities_filtered{area={{p[1]-125,p[2]-125},{p[1]+125,p[2]+125}}, name="market", limit=1} == 0 then
				secret_shop(p)
			end
		end
	end	
	
	for _, p in pairs(extra_tree_positions) do	
		if math.random(1,20)==1 then 
			surface.create_entity {name="tree-02",position=p}
		end
	end		
	
	for _, p in pairs(rock_positions) do			
		if math.random(1,3) ~= 1 then
			surface.create_entity {name=global.rock_raffle[math.random(1,#global.rock_raffle)], position=p}
		end
	end
	
	local decorative_names = {}
	for k,v in pairs(game.decorative_prototypes) do
		if v.autoplace_specification then
		  decorative_names[#decorative_names+1] = k
		end
	 end	 
	surface.regenerate_decorative(decorative_names, {{x=math.floor(event.area.left_top.x/32),y=math.floor(event.area.left_top.y/32)}})		
end

local function on_player_joined_game(event)
	local surface = game.surfaces[1]	
	local player = game.players[event.player_index]
	if not global.cave_miner_init_done then		 
		local p = surface.find_non_colliding_position("player", {0,-40}, 10, 1)
		game.forces["player"].set_spawn_position(p,surface)
		player.teleport(p)
		surface.daytime = 0.5
		surface.freeze_daytime = 1
		game.forces["player"].technologies["landfill"].enabled = false
		game.forces["player"].technologies["night-vision-equipment"].enabled = false
		
		global.spawn_dome_size = 20000
		
		global.rock_inhabitants = {}
		global.rock_inhabitants[1] = {"small-biter"}
		global.rock_inhabitants[2] = {"small-biter","small-biter","small-biter","small-biter","small-biter","medium-biter"}
		global.rock_inhabitants[3] = {"small-biter","small-biter","small-biter","small-biter","medium-biter","medium-biter"}
		global.rock_inhabitants[4] = {"small-biter","small-biter","small-biter","medium-biter","medium-biter","small-spitter"}
		global.rock_inhabitants[5] = {"small-biter","small-biter","medium-biter","medium-biter","medium-biter","small-spitter"}
		global.rock_inhabitants[6] = {"small-biter","small-biter","medium-biter","medium-biter","big-biter","small-spitter"}
		global.rock_inhabitants[7] = {"small-biter","small-biter","medium-biter","medium-biter","big-biter","medium-spitter"}
		global.rock_inhabitants[8] = {"small-biter","medium-biter","medium-biter","medium-biter","big-biter","medium-spitter"}
		global.rock_inhabitants[9] = {"small-biter","medium-biter","medium-biter","big-biter","big-biter","medium-spitter"}
		global.rock_inhabitants[10] = {"medium-biter","medium-biter","medium-biter","big-biter","big-biter","big-spitter"}
		global.rock_inhabitants[11] = {"medium-biter","medium-biter","big-biter","big-biter","big-biter","big-spitter"}
		global.rock_inhabitants[12] = {"medium-biter","big-biter","big-biter","big-biter","big-biter","big-spitter"}
		global.rock_inhabitants[13] = {"big-biter","big-biter","big-biter","big-biter","big-biter","big-spitter"}
		global.rock_inhabitants[14] = {"big-biter","big-biter","big-biter","big-biter","behemoth-biter","big-spitter"}
		global.rock_inhabitants[15] = {"big-biter","big-biter","big-biter","behemoth-biter","behemoth-biter","big-spitter"}
		global.rock_inhabitants[16] = {"big-biter","big-biter","big-biter","behemoth-biter","behemoth-biter","behemoth-spitter"}
		global.rock_inhabitants[17] = {"big-biter","big-biter","behemoth-biter","behemoth-biter","behemoth-biter","behemoth-spitter"}
		global.rock_inhabitants[18] = {"big-biter","behemoth-biter","behemoth-biter","behemoth-biter","behemoth-biter","behemoth-spitter"}
		global.rock_inhabitants[19] = {"behemoth-biter","behemoth-biter","behemoth-biter","behemoth-biter","behemoth-biter","behemoth-spitter"}
		global.rock_inhabitants[20] = {"behemoth-biter","behemoth-biter","behemoth-biter","behemoth-biter","behemoth-spitter","behemoth-spitter"}	
		
		global.rock_raffle = {"sand-rock-big","sand-rock-big","rock-big","rock-big","rock-big","rock-huge"}
		
		global.worm_raffle_table = {}
		global.worm_raffle_table[1] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret"}
		global.worm_raffle_table[2] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret"}
		global.worm_raffle_table[3] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret", "medium-worm-turret"}
		global.worm_raffle_table[4] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "small-worm-turret", "medium-worm-turret", "medium-worm-turret"}
		global.worm_raffle_table[5] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "medium-worm-turret", "medium-worm-turret", "medium-worm-turret"}
		global.worm_raffle_table[6] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "medium-worm-turret", "medium-worm-turret", "big-worm-turret"}
		global.worm_raffle_table[7] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "medium-worm-turret", "medium-worm-turret", "big-worm-turret"}
		global.worm_raffle_table[8] = {"small-worm-turret", "small-worm-turret", "small-worm-turret", "medium-worm-turret", "medium-worm-turret", "big-worm-turret"}
		global.worm_raffle_table[9] = {"small-worm-turret", "small-worm-turret", "medium-worm-turret", "medium-worm-turret", "big-worm-turret", "big-worm-turret"}
		global.worm_raffle_table[10] = {"small-worm-turret", "medium-worm-turret", "medium-worm-turret", "big-worm-turret", "big-worm-turret", "big-worm-turret"}						
		global.worm_free_zone_radius = math.sqrt(global.spawn_dome_size) + 40
		
		global.biter_spawn_schedule = {}
		
		global.treasure_chest_raffle_table = {}
		global.treasure_chest_loot_weights = {}
		table.insert(global.treasure_chest_loot_weights, {{name = 'iron-gear-wheel', count = 32},10})	
		table.insert(global.treasure_chest_loot_weights, {{name = 'coal', count = 3},8})
		table.insert(global.treasure_chest_loot_weights, {{name = 'copper-cable', count = 128},10})
		table.insert(global.treasure_chest_loot_weights, {{name = 'inserter', count = 16},4})		
		table.insert(global.treasure_chest_loot_weights, {{name = 'fast-inserter', count = 7},3})
		table.insert(global.treasure_chest_loot_weights, {{name = 'stack-filter-inserter', count = 2},1})
		table.insert(global.treasure_chest_loot_weights, {{name = 'stack-inserter', count = 2},1})
		table.insert(global.treasure_chest_loot_weights, {{name = 'burner-inserter', count = 16},6})
		table.insert(global.treasure_chest_loot_weights, {{name = 'electric-engine-unit', count = 11},3})		
		table.insert(global.treasure_chest_loot_weights, {{name = 'rocket-fuel', count = 1},3})
		table.insert(global.treasure_chest_loot_weights, {{name = 'empty-barrel', count = 3},7})
		table.insert(global.treasure_chest_loot_weights, {{name = 'lubricant-barrel', count = 1},3})
		table.insert(global.treasure_chest_loot_weights, {{name = 'crude-oil-barrel', count = 4},3})
		table.insert(global.treasure_chest_loot_weights, {{name = 'iron-stick', count = 3},8})
		table.insert(global.treasure_chest_loot_weights, {{name = "small-electric-pole", count = 13},9})
		table.insert(global.treasure_chest_loot_weights, {{name = "firearm-magazine", count = 32},8})
		table.insert(global.treasure_chest_loot_weights, {{name = 'grenade', count = 8},5})
		table.insert(global.treasure_chest_loot_weights, {{name = 'land-mine', count = 15},4})
		table.insert(global.treasure_chest_loot_weights, {{name = 'light-armor', count = 1},1})
		table.insert(global.treasure_chest_loot_weights, {{name = 'heavy-armor', count = 1},2})		
		table.insert(global.treasure_chest_loot_weights, {{name = 'pipe', count = 26},6})		
		table.insert(global.treasure_chest_loot_weights, {{name = 'wooden-chest', count = 1},3})
		table.insert(global.treasure_chest_loot_weights, {{name = 'burner-mining-drill', count = 1},1})
		table.insert(global.treasure_chest_loot_weights, {{name = 'iron-axe', count = 1},2})
		table.insert(global.treasure_chest_loot_weights, {{name = 'steel-axe', count = 1},3})
		table.insert(global.treasure_chest_loot_weights, {{name = 'raw-wood', count = 24},5})
		table.insert(global.treasure_chest_loot_weights, {{name = 'sulfur', count = 27},7})
		table.insert(global.treasure_chest_loot_weights, {{name = 'explosives', count = 36},6})
		table.insert(global.treasure_chest_loot_weights, {{name = 'shotgun', count = 1},2})
		table.insert(global.treasure_chest_loot_weights, {{name = 'stone-brick', count = 72},4})
		table.insert(global.treasure_chest_loot_weights, {{name = 'small-lamp', count = 4},4})
		table.insert(global.treasure_chest_loot_weights, {{name = 'rail', count = 57},4})	
		table.insert(global.treasure_chest_loot_weights, {{name = 'coin', count = 1},1})
		table.insert(global.treasure_chest_loot_weights, {{name = 'assembling-machine-1', count = 1},2})
		table.insert(global.treasure_chest_loot_weights, {{name = 'assembling-machine-2', count = 1},2})
		table.insert(global.treasure_chest_loot_weights, {{name = 'assembling-machine-3', count = 1},1})
		
		for _, t in pairs (global.treasure_chest_loot_weights) do
			for x = 1, t[2], 1 do
				table.insert(global.treasure_chest_raffle_table, t[1])
			end			
		end
		
		global.ore_spill_cap = 42
		global.stats_rocks_brocken = 0
		global.stats_ores_found = 0
		
		global.rock_mining_chance_weights = {}
		global.rock_mining_chance_weights[1] = {"iron-ore",25}
		global.rock_mining_chance_weights[2] = {"copper-ore",18}
		global.rock_mining_chance_weights[3] = {"coal",15}
		global.rock_mining_chance_weights[4] = {"stone",8}
		global.rock_mining_chance_weights[5] = {"uranium-ore",3}
		global.rock_mining_raffle_table = {}				
		for _, t in pairs (global.rock_mining_chance_weights) do
			for x = 1, t[2], 1 do
				table.insert(global.rock_mining_raffle_table, t[1])
			end			
		end

		global.darkness_threat_level = {}
		global.darkness_messages = {
		"Something is lurking in the dark...",
		"A shadow moves. I doubt it is friendly...",
		"The silence grows louder...",
		"Trust not your eyes. They are useless in the dark.",
		"The darkness hides only death. Turn back now.",
		"You hear noises...",
		"They chitter as if laughing, hungry for their next foolish meal...",
		"Despite what the radars tell you, it is not safe here...",
		"The shadows are moving...",
		"You feel like, something is watching you...",		
		}					
		
		global.cave_miner_init_done = true						
	end	
	if player.online_time < 10 then
		create_cave_miner_button(player)
		global.darkness_threat_level[player.name] = 0
		player.insert {name = 'pistol', count = 1}		
		player.insert {name = 'firearm-magazine', count = 16}			
		player.insert {name = 'iron-axe', count = 1}		
	end
	create_cave_miner_stats_gui(player)
end

local function spawn_cave_inhabitant(pos)
	if not pos.x then return nil end
	if not pos.y then return nil end
	local tile_distance_to_center = math.sqrt(pos.x^2 + pos.y^2)			
	local rock_inhabitants_index = math.ceil((tile_distance_to_center-math.sqrt(global.spawn_dome_size))*0.02, 0)
	if rock_inhabitants_index < 1 then rock_inhabitants_index = 1 end
	if rock_inhabitants_index > 20 then rock_inhabitants_index = 20 end					
	local entity_name = global.rock_inhabitants[rock_inhabitants_index][math.random(1,#global.rock_inhabitants[rock_inhabitants_index])]
	local p = game.surfaces[1].find_non_colliding_position(entity_name , pos, 7, 0.5)			
	if p then game.surfaces[1].create_entity {name=entity_name, position=p} end				
end

local function darkness_events()
	for _, p in pairs (game.connected_players) do		
		if global.darkness_threat_level[p.name] > 4 then						
			for x = 1, global.darkness_threat_level[p.name], 1 do
				spawn_cave_inhabitant(p.position)	
			end				
			local biters_found = game.surfaces[1].find_enemy_units(p.position, 12, "player")
			for _, biter in pairs(biters_found) do				
				biter.set_command({type=defines.command.attack, target=p.character, distraction=defines.distraction.none})					
			end
			p.character.damage(math.random(global.darkness_threat_level[p.name]*2,global.darkness_threat_level[p.name]*3),"enemy")
		end		
		if global.darkness_threat_level[p.name] == 2 then
			p.print(global.darkness_messages[math.random(1,#global.darkness_messages)],{ r=0.65, g=0.0, b=0.0})				
		end
		global.darkness_threat_level[p.name] = global.darkness_threat_level[p.name] + 1
	end
end

local function darkness_checks()
	for _, p in pairs (game.connected_players) do
		p.character.disable_flashlight()
		local tile_distance_to_center = math.sqrt(p.position.x^2 + p.position.y^2)
		if tile_distance_to_center < math.sqrt(global.spawn_dome_size) then
			global.darkness_threat_level[p.name] = 0
		else
			if p.character.driving == true then
				global.darkness_threat_level[p.name] = 0
			else
				local light_source_entities = game.surfaces[1].find_entities_filtered{area={{p.position.x-12,p.position.y-12},{p.position.x+12,p.position.y+12}}, name="small-lamp"}		
				for _, lamp in pairs (light_source_entities) do
					local circuit = lamp.get_or_create_control_behavior()
					if circuit then
						if lamp.energy > 50 and circuit.disabled == false then					
							global.darkness_threat_level[p.name] = 0
							break
						end
					else
						if lamp.energy > 50 then					
							global.darkness_threat_level[p.name] = 0
							break
						end
					end
				end
			end
		end
	end
end

local function on_tick(event)
		
		if game.tick % 30 == 0 then
			if global.biter_spawn_schedule then
				for x, b in pairs (global.biter_spawn_schedule) do
					if game.tick > b[1] then
						spawn_cave_inhabitant(b[2])
						global.biter_spawn_schedule[x] = nil
					end
				end
			end
		end

		if game.tick % 240 == 0 then
			darkness_checks()	
			darkness_events()	
		end		
		
		if game.tick == 30 then
			local surface = game.surfaces[1]						
			--p = game.forces["player"].get_spawn_position(surface)
			local p = game.surfaces[1].find_non_colliding_position("market",{0,-15},60,0.5)
			local market = surface.create_entity {name = "market", position = p}
			market.destructible = false
			
			for _, item in pairs(market_items.spawn) do
				market.add_market_item(item)
			end			
			surface.regenerate_entity({"tree-01", "tree-02","tree-03","tree-04","tree-05","tree-06","tree-07","tree-08","tree-09","dead-dry-hairy-tree","dead-grey-trunk","dead-tree-desert","dry-hairy-tree","dry-tree","rock-big","rock-huge"})			
		end
end

local function on_marked_for_deconstruction(event)
	if event.entity.name == "rock-huge" or event.entity.name == "rock-big" or event.entity.name == "sand-rock-big" then
		event.entity.cancel_deconstruction(game.players[event.player_index].force.name)
	end
end

local function pre_player_mined_item(event)

	if math.random(1,10) == 1 then
		if event.entity.name == "rock-huge" or event.entity.name == "rock-big" or event.entity.name == "sand-rock-big" then		
			for x = 1, math.random(6,10), 1 do
				table.insert(global.biter_spawn_schedule, {game.tick + 30*x, event.entity.position})		
			end			
		end
	end
	
	if event.entity.name == "rock-huge" or event.entity.name == "rock-big" or event.entity.name == "sand-rock-big" then			
		local tile_distance_to_center = math.sqrt(event.entity.position.x^2 + event.entity.position.y^2)
		local player = game.players[event.player_index]
		game.surfaces[1].spill_item_stack(player.position,{name = "raw-fish", count = math.random(3,4)},true)
		local bonus_amount = math.ceil((tile_distance_to_center - math.sqrt(global.spawn_dome_size)) * 0.4, 0) 
		if bonus_amount < 1 then bonus_amount = 0 end		
		local amount = (math.random(55,65) + bonus_amount)*(1+game.forces.player.mining_drill_productivity_bonus)
		amount = math.round(amount,0)
		global.stats_rocks_brocken = global.stats_rocks_brocken + 1
		global.stats_ores_found = global.stats_ores_found + amount		
		local mined_loot = global.rock_mining_raffle_table[math.random(1,#global.rock_mining_raffle_table)]
		if amount > global.ore_spill_cap then
			game.surfaces[1].spill_item_stack(event.entity.position,{name = mined_loot, count = global.ore_spill_cap},true)
			amount = amount - global.ore_spill_cap
			local i = player.insert {name = mined_loot, count = amount}
			amount = amount - i
			if amount > 0 then
				game.surfaces[1].spill_item_stack(event.entity.position,{name = mined_loot, count = amount},true)
			end
		else
			game.surfaces[1].spill_item_stack(event.entity.position,{name = global.rock_mining_raffle_table[math.random(1,#global.rock_mining_raffle_table)], count = amount},true)
		end
		refresh_gui()
	end
end

local function on_player_mined_entity(event)
	if event.entity.name == "rock-huge" or event.entity.name == "rock-big" or event.entity.name == "sand-rock-big" then
		event.buffer.clear()
	end
	if event.entity.name == "fish" then
		if math.random(1,2) == 1 then
			local player = game.players[event.player_index]	
			local health = player.character.health
			player.character.damage(math.random(50,150),"enemy")			
			if not player.character then
				game.print(player.name .. " should have kept their hands out of the foggy lake waters.",{ r=0.75, g=0.0, b=0.0} )
			else
				if health > 200 then
					player.print("You got bitten by an angry cave piranha.",{ r=0.75, g=0.0, b=0.0})
				else
					local messages = {"Ouch.. That hurt! Better be careful now.", "Just a fleshwound.", "Better keep those hands to yourself or you might loose them."}
					player.print(messages[math.random(1,#messages)],{ r=0.75, g=0.0, b=0.0})
				end
			end
		end
	end	
end

local function on_entity_damaged(event)
	if event.entity.name == "rock-huge" or event.entity.name == "rock-big" or event.entity.name == "sand-rock-big" then
		local rock_is_alive = true
		if event.force.name == "enemy" then 
			event.entity.health = event.entity.health + (event.final_damage_amount - 0.05)
			if event.entity.health <= event.final_damage_amount then				
				rock_is_alive = false
			end			
		end
		if event.entity.health <= 0 then rock_is_alive = false end		
		if rock_is_alive == false then			
			if event.force.name == "player" then	
				if math.random(1,6) == 1 then								
					for x = 1, math.random(8,12), 1 do
						table.insert(global.biter_spawn_schedule, {game.tick + 5*x, event.entity.position})		
					end						
				end
			end
			local p = event.entity.position				
			local drop_amount = math.random(10,20)
			event.entity.destroy()
			game.surfaces[1].spill_item_stack(p,{name = "stone", count = drop_amount},true)
			global.stats_rocks_brocken = global.stats_rocks_brocken + 1
			global.stats_ores_found = global.stats_ores_found + drop_amount
			refresh_gui()						
		end
	end	
end

local function on_player_respawned(event)
	local player = game.players[event.player_index]
	player.character.disable_flashlight()
end

local function on_research_finished(event)
	game.forces.player.manual_mining_speed_modifier = game.forces.player.mining_drill_productivity_bonus * 3
	game.forces.player.character_inventory_slots_bonus = game.forces.player.mining_drill_productivity_bonus * 500
	refresh_gui()
end

local function on_gui_click(event)
	if not (event and event.element and event.element.valid) then return end

	local player = game.players[event.element.player_index]
	local name = event.element.name
	local frame = player.gui.top["caver_miner_stats_frame"]

	if (name == "caver_miner_stats_toggle_button") and (frame == nil) then
		create_cave_miner_stats_gui(player)
	else
		if (name == "caver_miner_stats_toggle_button") then
			frame.destroy()
		end
	end
end

function map_pregen(radius)
	if not radius then radius = 1024 end
	game.create_force("map_pregen")
	game.players[1].force = game.forces["map_pregen"]
	game.forces.map_pregen.chart(game.surfaces[1],{{x = -1 * radius, y = -1 * radius}, {x = radius, y = radius}})	
end

function map_pregen_end()
	game.players[1].force = game.forces["player"]
end
	
function test()
	local x = 0
	if x == 1 then
		local surface = game.surfaces[1]
		game.player.cheat_mode=true
		game.speed = 1
		surface.daytime = 1
		game.player.force.research_all_technologies()
		game.forces["enemy"].evolution_factor = 0.2
		local chart = 200
		local surface = game.surfaces[1]	
		game.forces["player"].chart(surface, {lefttop = {x = chart*-1, y = chart*-1}, rightbottom = {x = chart, y = chart}})		
	end
end

Event.add(defines.events.on_gui_click, on_gui_click)
Event.add(defines.events.on_research_finished, on_research_finished)
Event.add(defines.events.on_player_respawned, on_player_respawned)
Event.add(defines.events.on_player_mined_entity, on_player_mined_entity)
Event.add(defines.events.on_entity_damaged, on_entity_damaged)
Event.add(defines.events.on_pre_player_mined_item, pre_player_mined_item)
Event.add(defines.events.on_marked_for_deconstruction, on_marked_for_deconstruction)
Event.add(defines.events.on_chunk_generated, on_chunk_generated)
Event.add(defines.events.on_tick, on_tick)	
Event.add(defines.events.on_player_joined_game, on_player_joined_game)