2018-11-18 18:12:00 +02:00
--[[-- info
Provides the ability to collapse caves when digging .
] ]
-- dependencies
2018-11-26 03:07:03 +02:00
require ' utils.table '
2018-11-18 18:12:00 +02:00
local Event = require ' utils.event '
local Template = require ' map_gen.Diggy.Template '
local ScoreTable = require ' map_gen.Diggy.ScoreTable '
local Debug = require ' map_gen.Diggy.Debug '
2018-11-27 09:06:02 +02:00
local Task = require ' utils.Task '
2018-11-26 03:07:03 +02:00
local Token = require ' utils.token '
2018-11-18 18:12:00 +02:00
local Global = require ' utils.global '
local Game = require ' utils.game '
2018-11-29 20:11:12 +02:00
local CreateParticles = require ' features.create_particles '
2018-11-18 18:12:00 +02:00
local insert = table.insert
local random = math.random
local floor = math.floor
local abs = math.abs
2018-11-29 20:11:12 +02:00
local raise_event = script.raise_event
2018-11-18 18:12:00 +02:00
-- this
local DiggyCaveCollapse = { }
local config = { }
local n = 9
local radius = 0
local radius_sq = 0
local center_radius_sq = 0
local disc_radius_sq = 0
local center_weight
local disc_weight
local ring_weight
local disc_blur_sum = 0
local center_value = 0
local disc_value = 0
local ring_value = 0
local enable_stress_grid = 0
local stress_map_add
local mask_disc_blur
local stress_map_check_stress_in_threshold
local support_beam_entities
local on_surface_created
local stress_threshold_causing_collapse = 3.57
2018-11-29 20:11:12 +02:00
local near_stress_threshold_causing_collapse = 3.57 * 0.9
2018-11-18 18:12:00 +02:00
local show_deconstruction_alert_message = { }
local stress_map_storage = { }
local new_tile_map = { }
local collapse_positions_storage = { }
Global.register ( {
new_tile_map = new_tile_map ,
stress_map_storage = stress_map_storage ,
deconstruction_alert_message_shown = show_deconstruction_alert_message ,
collapse_positions_storage = collapse_positions_storage ,
} , function ( tbl )
new_tile_map = tbl.new_tile_map
stress_map_storage = tbl.stress_map_storage
show_deconstruction_alert_message = tbl.deconstruction_alert_message_shown
collapse_positions_storage = tbl.collapse_positions_storage
end )
local defaultValue = 0
2018-11-29 20:11:12 +02:00
local collapse_alert = { type = ' item ' , name = ' stone ' }
2018-11-18 18:12:00 +02:00
DiggyCaveCollapse.events = {
--[[--
When stress at certain position is above the collapse threshold
- position LuaPosition
- surface LuaSurface
- player_index Number ( index of player that caused the collapse )
] ]
on_collapse_triggered = script.generate_event_name ( ) ,
--[[--
After a collapse
- position LuaPosition
- surface LuaSurface
- player_index Number ( index of player that caused the collapse )
] ]
on_collapse = script.generate_event_name ( )
}
2018-11-30 21:30:50 +02:00
local collapse_rocks = { ' sand-rock-big ' , ' rock-big ' , ' rock-big ' }
local collapse_rocks_size = # collapse_rocks
2018-11-18 18:12:00 +02:00
local function create_collapse_template ( positions , surface )
local entities = { }
2018-11-29 20:11:12 +02:00
local entity_count = 0
2018-11-18 18:12:00 +02:00
local find_entities_filtered = surface.find_entities_filtered
for _ , position in pairs ( positions ) do
local x = position.x
local y = position.y
local do_insert = true
2018-11-30 21:30:50 +02:00
for _ , entity in pairs ( find_entities_filtered ( { area = { position , { x + 1 , y + 1 } } } ) ) do
2018-11-18 18:12:00 +02:00
pcall ( function ( )
local strength = support_beam_entities [ entity.name ]
if strength then
do_insert = false
else
entity.die ( )
end
end )
end
2018-11-29 20:11:12 +02:00
2018-11-18 18:12:00 +02:00
if do_insert then
2018-11-29 20:11:12 +02:00
entity_count = entity_count + 1
2018-11-30 21:30:50 +02:00
entities [ entity_count ] = { position = position , name = collapse_rocks [ random ( 1 , collapse_rocks_size ) ] }
2018-11-18 18:12:00 +02:00
end
end
2018-11-29 20:11:12 +02:00
2018-11-18 18:12:00 +02:00
return entities
end
local function create_collapse_alert ( surface , position )
2018-11-30 21:30:50 +02:00
local target = surface.create_entity { position = position , name = ' rock-big ' }
2018-11-29 20:11:12 +02:00
for _ , player in pairs ( game.connected_players ) do
player.add_custom_alert ( target , collapse_alert , ' Cave collapsed! ' , true )
2018-11-18 18:12:00 +02:00
end
target.destroy ( )
end
local function collapse ( args )
local position = args.position
local surface = args.surface
local positions = { }
2018-11-29 20:11:12 +02:00
local count = 0
2018-11-18 18:12:00 +02:00
local strength = config.collapse_threshold_total_strength
2018-11-29 20:11:12 +02:00
mask_disc_blur ( position.x , position.y , strength , function ( x , y , value )
stress_map_check_stress_in_threshold ( surface , x , y , value , function ( _ , c_x , c_y )
count = count + 1
positions [ count ] = { x = c_x , y = c_y }
end )
end )
if # positions == 0 then
return
end
2018-11-18 18:12:00 +02:00
create_collapse_alert ( surface , position )
2018-11-29 20:11:12 +02:00
Template.insert ( surface , { } , create_collapse_template ( positions , surface ) )
raise_event ( DiggyCaveCollapse.events . on_collapse , args )
2018-11-18 18:12:00 +02:00
ScoreTable.increment ( ' Cave collapse ' )
end
local on_collapse_timeout_finished = Token.register ( collapse )
2018-11-29 20:11:12 +02:00
local on_near_threshold = Token.register ( function ( params )
CreateParticles.ceiling_crumble ( params.surface , params.position )
end )
2018-11-18 18:12:00 +02:00
local function spawn_cracking_sound_text ( surface , position )
2018-11-26 06:17:12 +02:00
local text = table.get_random ( config.cracking_sounds , true )
2018-11-18 18:12:00 +02:00
local color = {
r = 1 ,
g = random ( 1 , 100 ) * 0.01 ,
b = 0
}
local create_entity = surface.create_entity
for i = 1 , # text do
local x_offset = ( i - # text / 2 - 1 ) / 3
local char = text : sub ( i , i )
create_entity {
name = ' flying-text ' ,
color = color ,
text = char ,
position = { x = position.x + x_offset , y = position.y - ( ( i + 1 ) % 2 ) * 0.25 }
} . active = true
end
end
local function on_collapse_triggered ( event )
if global.cave_collapse_disabled then return end --kill switch
local surface = event.surface
local position = event.position
local x = position.x
local y = position.y
local x_t = new_tile_map [ x ]
if x_t and x_t [ y ] then
2018-11-30 21:30:50 +02:00
Template.insert ( surface , { } , { { position = position , name = ' rock-big ' } } )
2018-11-18 18:12:00 +02:00
return
end
spawn_cracking_sound_text ( surface , position )
Task.set_timeout (
config.collapse_delay ,
on_collapse_timeout_finished ,
event
)
end
local function on_built_tile ( surface , new_tile , tiles )
local new_tile_strength = support_beam_entities [ new_tile.name ]
for _ , tile in pairs ( tiles ) do
if new_tile_strength then
stress_map_add ( surface , tile.position , - 1 * new_tile_strength , true )
end
local old_tile_strength = support_beam_entities [ tile.old_tile . name ]
if ( old_tile_strength ) then
stress_map_add ( surface , tile.position , old_tile_strength , true )
end
end
end
--It is impossible to track which player marked the tile for deconstruction
local function on_robot_mined_tile ( event )
local surface
for _ , tile in pairs ( event.tiles ) do
local strength = support_beam_entities [ tile.old_tile . name ]
if strength then
surface = surface or event.robot . surface
stress_map_add ( surface , tile.position , strength , true )
end
end
end
local function on_player_mined_tile ( event )
local surface = game.surfaces [ event.surface_index ]
for _ , tile in pairs ( event.tiles ) do
local strength = support_beam_entities [ tile.old_tile . name ]
if strength then
stress_map_add ( surface , tile.position , strength , true , event.player_index )
end
end
end
local function on_mined_entity ( event )
local entity = event.entity
local name = entity.name
local strength = support_beam_entities [ name ]
if strength then
2018-11-30 21:30:50 +02:00
local player_index
if name ~= ' sand-rock-big ' and name ~= ' rock-huge ' and name ~= ' rock-big ' then
player_index = event.player_index
end
stress_map_add ( entity.surface , entity.position , strength , false , player_index )
2018-11-18 18:12:00 +02:00
end
end
local function on_entity_died ( event )
local entity = event.entity
local name = entity.name
local strength = support_beam_entities [ name ]
if strength then
2018-11-29 21:59:02 +02:00
local player_index
2018-11-30 21:30:50 +02:00
if name ~= ' sand-rock-big ' and name ~= ' rock-huge ' and name ~= ' rock-big ' then
2018-11-29 21:59:02 +02:00
local cause = event.cause
player_index = cause and cause.player and cause.player . index or nil
end
stress_map_add ( entity.surface , entity.position , strength , false , player_index )
2018-11-18 18:12:00 +02:00
end
end
local function on_built_entity ( event )
local entity = event.created_entity
local strength = support_beam_entities [ entity.name ]
if strength then
stress_map_add ( entity.surface , entity.position , - 1 * strength )
end
end
local function on_placed_entity ( event )
local strength = support_beam_entities [ event.entity . name ]
if strength then
stress_map_add ( event.entity . surface , event.entity . position , - 1 * strength )
end
end
local on_new_tile_timeout_finished = Token.register ( function ( args )
local x_t = new_tile_map [ args.x ]
if x_t then
x_t [ args.y ] = nil --reset new tile status. This tile can cause a chain collapse now
end
end )
local function on_void_removed ( event )
local strength = support_beam_entities [ ' out-of-map ' ]
local position = event.position
if strength then
stress_map_add ( event.surface , position , strength )
end
local x = position.x
local y = position.y
--To avoid room collapse:
local x_t = new_tile_map [ x ]
if x_t then
x_t [ y ] = true
else
x_t = {
[ y ] = true
}
new_tile_map [ x ] = x_t
end
Task.set_timeout ( 3 , on_new_tile_timeout_finished , { x = x , y = y } )
end
--[[--
Registers all event handlers . ]
@ param global_config Table { @ see Diggy.Config } .
] ]
function DiggyCaveCollapse . register ( cfg )
config = cfg
support_beam_entities = config.support_beam_entities
if support_beam_entities [ ' stone-path ' ] then
support_beam_entities [ ' stone-brick ' ] = support_beam_entities [ ' stone-path ' ]
else
support_beam_entities [ ' stone-brick ' ] = nil
end
if support_beam_entities [ ' hazard-concrete ' ] then
support_beam_entities [ ' hazard-concrete-left ' ] = support_beam_entities [ ' hazard-concrete ' ]
support_beam_entities [ ' hazard-concrete-right ' ] = support_beam_entities [ ' hazard-concrete ' ]
else
support_beam_entities [ ' hazard-concrete-left ' ] = nil
support_beam_entities [ ' hazard-concrete-right ' ] = nil
end
if support_beam_entities [ ' refined-hazard-concrete ' ] then
support_beam_entities [ ' refined-hazard-concrete-left ' ] = support_beam_entities [ ' refined-hazard-concrete ' ]
support_beam_entities [ ' refined-hazard-concrete-right ' ] = support_beam_entities [ ' refined-hazard-concrete ' ]
else
support_beam_entities [ ' refined-hazard-concrete-left ' ] = nil
support_beam_entities [ ' refined-hazard-concrete-right ' ] = nil
end
ScoreTable.reset ( ' Cave collapse ' )
Event.add ( DiggyCaveCollapse.events . on_collapse_triggered , on_collapse_triggered )
Event.add ( defines.events . on_robot_built_entity , on_built_entity )
Event.add ( defines.events . on_robot_built_tile , function ( event )
on_built_tile ( event.robot . surface , event.item , event.tiles )
end )
Event.add ( defines.events . on_player_built_tile , function ( event )
on_built_tile ( game.surfaces [ event.surface_index ] , event.item , event.tiles )
end )
Event.add ( defines.events . on_robot_mined_tile , on_robot_mined_tile )
Event.add ( defines.events . on_player_mined_tile , on_player_mined_tile )
Event.add ( defines.events . on_built_entity , on_built_entity )
Event.add ( Template.events . on_placed_entity , on_placed_entity )
Event.add ( defines.events . on_entity_died , on_entity_died )
Event.add ( defines.events . on_player_mined_entity , on_mined_entity )
Event.add ( Template.events . on_void_removed , on_void_removed )
Event.add ( defines.events . on_surface_created , on_surface_created )
Event.add ( defines.events . on_marked_for_deconstruction , function ( event )
2018-11-26 22:37:41 +02:00
local entity = event.entity
local name = entity.name
2018-11-30 21:30:50 +02:00
if name == ' sand-rock-big ' or name == ' rock-huge ' and name ~= ' rock-big ' then
2018-11-22 21:43:23 +02:00
return
end
if name == ' deconstructible-tile-proxy ' or nil ~= support_beam_entities [ name ] then
2018-11-26 22:37:41 +02:00
entity.cancel_deconstruction ( Game.get_player_by_index ( event.player_index ) . force )
2018-11-18 18:12:00 +02:00
end
end )
Event.add ( defines.events . on_player_created , function ( event )
show_deconstruction_alert_message [ event.player_index ] = true
end )
Event.add ( defines.events . on_pre_player_mined_item , function ( event )
local player_index = event.player_index
if not show_deconstruction_alert_message [ player_index ] then
return
end
if ( nil ~= support_beam_entities [ event.entity . name ] ) then
require ' features.gui.popup ' . player (
Game.get_player_by_index ( player_index ) , [ [
Mining entities such as walls , stone paths , concrete
and rocks , can cause a cave - in , be careful miner !
Foreman ' s advice: Place a wall every 4th tile to
prevent a cave - in . Use stone paths and concrete
to reinforce it further .
] ]
)
show_deconstruction_alert_message [ player_index ] = nil
end
end )
enable_stress_grid = config.enable_stress_grid
on_surface_created ( { surface_index = 1 } )
mask_init ( config )
if ( config.enable_mask_debug ) then
local surface = game.surfaces . nauvis
mask_disc_blur ( 0 , 0 , 10 , function ( x , y , fraction )
Debug.print_grid_value ( fraction , surface , { x = x , y = y } )
end )
end
if config.enable_debug_commands then
commands.add_command ( ' test-tile-support-range ' , ' <tilename> <range> creates a square of tiles with length <range>. It is spawned one <range> north of the player. ' , function ( cmd )
2018-11-29 20:11:12 +02:00
local params = { }
for param in string.gmatch ( cmd.parameter , ' %S+ ' ) do
table.insert ( params , param )
end
local tilename = params [ 1 ]
local range = tonumber ( params [ 2 ] )
local position = { x = math.floor ( game.player . position.x ) , y = math.floor ( game.player . position.y ) - 5 * range - 1 }
local surface = game.player . surface
local tiles = { }
local entities = { }
for x = position.x , position.x + range * 5 do
for y = position.y , position.y + range * 5 do
if y % range + x % range == 0 then
insert ( entities , { name = ' stone-wall ' , position = { x = x , y = y } } )
end
insert ( tiles , { position = { x = x , y = y } , name = tilename } )
2018-11-18 18:12:00 +02:00
2018-11-29 20:11:12 +02:00
local strength = support_beam_entities [ tilename ]
if strength then
stress_map_add ( surface , { x = x , y = y } , - strength )
end
for _ , entity in pairs ( surface.find_entities_filtered ( { position = { x = x , y = y } } ) ) do
pcall ( function ( )
2018-11-29 20:18:06 +02:00
local local_strength = support_beam_entities [ entity.name ]
local local_position = entity.position
entity.die ( )
if strength then
stress_map_add ( surface , local_position , local_strength )
2018-11-29 20:11:12 +02:00
end
2018-11-29 20:18:06 +02:00
end )
2018-11-18 18:12:00 +02:00
end
end
end
2018-11-29 20:11:12 +02:00
Template.insert ( surface , tiles , entities )
end )
2018-11-18 18:12:00 +02:00
end
commands.add_command ( ' toggle-cave-collapse ' , ' Toggles cave collapse (admins only). ' , function ( )
pcall ( function ( ) --better safe than sorry
if not game.player or game.player . admin then
2018-11-29 20:27:25 +02:00
global.cave_collapse_disabled = not global.cave_collapse_disabled
if global.cave_collapse_disabled then
2018-11-29 20:11:12 +02:00
game.print ( ' Cave collapse: Disabled. ' )
2018-11-18 18:12:00 +02:00
else
2018-11-29 20:11:12 +02:00
game.print ( ' Cave collapse: Enabled. ' )
2018-11-18 18:12:00 +02:00
end
end
end )
end )
end
--
--STRESS MAP
--
--[[--
Adds a fraction to a given location on the stress_map . Returns the new
fraction value of that position .
@ param stress_map Table of { x , y }
@ param position Table with x and y
@ param number fraction
@ return number sum of old fraction + new fraction
] ]
2018-11-29 20:11:12 +02:00
---Adds a fraction to a given location on the stress_map. Returns the new fraction value of that position.
---@param stress_map table
---@param x number
---@param y number
---@param fraction number
---@param player_index number
---@param surface LuaSurface
local function add_fraction ( stress_map , x , y , fraction , player_index , surface )
2018-11-18 18:12:00 +02:00
x = 2 * floor ( x * 0.5 )
y = 2 * floor ( y * 0.5 )
local x_t = stress_map [ x ]
if not x_t then
x_t = { }
stress_map [ x ] = x_t
end
local value = x_t [ y ]
if not value then
value = defaultValue
end
value = value + fraction
x_t [ y ] = value
2018-11-29 20:11:12 +02:00
if fraction > 0 then
if value > stress_threshold_causing_collapse then
raise_event ( DiggyCaveCollapse.events . on_collapse_triggered , {
surface = surface ,
position = { x = x , y = y } ,
player_index = player_index
} )
elseif value > near_stress_threshold_causing_collapse then
Task.set_timeout_in_ticks ( 2 , on_near_threshold , { surface = surface , position = { x = x , y = y } } )
end
2018-11-18 18:12:00 +02:00
end
2018-11-29 20:11:12 +02:00
if enable_stress_grid then
2018-11-18 18:12:00 +02:00
Debug.print_colored_grid_value ( value , surface , { x = x , y = y } , 4 , 0.5 , false ,
value / stress_threshold_causing_collapse , { r = 0 , g = 1 , b = 0 } , { r = 1 , g = - 1 , b = 0 } ,
{ r = 0 , g = 1 , b = 0 } , { r = 1 , g = 1 , b = 1 } )
end
return value
end
on_surface_created = function ( event )
2018-11-29 20:11:12 +02:00
local index = event.surface_index
stress_map_storage [ index ] = { }
2018-11-18 18:12:00 +02:00
2018-11-29 20:11:12 +02:00
local map = stress_map_storage [ index ]
2018-11-18 18:12:00 +02:00
2018-11-29 20:11:12 +02:00
map [ ' surface_index ' ] = index
2018-11-18 18:12:00 +02:00
map [ 1 ] = { index = 1 }
map [ 2 ] = { index = 2 }
map [ 3 ] = { index = 3 }
map [ 4 ] = { index = 4 }
end
2018-11-29 20:11:12 +02:00
---Checks whether a tile's pressure is within a given threshold and calls the handler if not.
---@param surface LuaSurface
---@param x number
---@param y number
---@param threshold number
---@param callback function
stress_map_check_stress_in_threshold = function ( surface , x , y , threshold , callback )
2018-11-18 18:12:00 +02:00
local stress_map = stress_map_storage [ surface.index ]
2018-11-29 20:11:12 +02:00
local value = add_fraction ( stress_map , x , y , 0 , surface )
2018-11-18 18:12:00 +02:00
if ( value >= stress_threshold_causing_collapse - threshold ) then
2018-11-29 20:11:12 +02:00
callback ( surface , x , y )
2018-11-18 18:12:00 +02:00
end
end
stress_map_add = function ( surface , position , factor , no_blur , player_index )
local x_start = floor ( position.x )
local y_start = floor ( position.y )
local stress_map = stress_map_storage [ surface.index ]
if not stress_map then
return
end
if no_blur then
2018-11-29 20:11:12 +02:00
add_fraction ( stress_map , x_start , y_start , factor , player_index , surface )
2018-11-18 18:12:00 +02:00
return
end
for x = - radius , radius do
for y = - radius , radius do
local value = 0
local distance_sq = x * x + y * y
if distance_sq <= center_radius_sq then
value = center_value
elseif distance_sq <= disc_radius_sq then
value = disc_value
elseif distance_sq <= radius_sq then
value = ring_value
end
2018-11-29 20:11:12 +02:00
if value > 0.001 or value < - 0.001 then
add_fraction ( stress_map , x + x_start , y + y_start , value * factor , player_index , surface )
2018-11-18 18:12:00 +02:00
end
end
end
end
DiggyCaveCollapse.stress_map_add = stress_map_add
--
-- MASK
--
function mask_init ( config )
n = config.mask_size
ring_weight = config.mask_relative_ring_weights [ 1 ]
disc_weight = config.mask_relative_ring_weights [ 2 ]
center_weight = config.mask_relative_ring_weights [ 3 ]
radius = floor ( n * 0.5 )
radius_sq = ( radius + 0.2 ) * ( radius + 0.2 )
center_radius_sq = radius_sq / 9
disc_radius_sq = radius_sq * 4 / 9
for x = - radius , radius do
for y = - radius , radius do
local distance_sq = x * x + y * y
if distance_sq <= center_radius_sq then
disc_blur_sum = disc_blur_sum + center_weight
elseif distance_sq <= disc_radius_sq then
disc_blur_sum = disc_blur_sum + disc_weight
elseif distance_sq <= radius_sq then
disc_blur_sum = disc_blur_sum + ring_weight
end
end
end
center_value = center_weight / disc_blur_sum
disc_value = disc_weight / disc_blur_sum
ring_value = ring_weight / disc_blur_sum
end
--[[--
Applies a blur
Applies the disc in 3 discs : center , ( middle ) disc and ( outer ) ring .
The relative weights for tiles in a disc are :
center : 3 / 3
disc : 2 / 3
ring : 1 / 3
The sum of all values is 1
@ param x_start number center point
@ param y_start number center point
@ param factor the factor to multiply the cell value with ( value = cell_value * factor )
@ param callback function to execute on each tile within the mask callback ( x , y , value )
] ]
mask_disc_blur = function ( x_start , y_start , factor , callback )
x_start = floor ( x_start )
y_start = floor ( y_start )
for x = - radius , radius do
for y = - radius , radius do
local value = 0
local distance_sq = x * x + y * y
if distance_sq <= center_radius_sq then
value = center_value
elseif distance_sq <= disc_radius_sq then
value = disc_value
elseif distance_sq <= radius_sq then
value = ring_value
end
if abs ( value ) > 0.001 then
callback ( x_start + x , y_start + y , value * factor )
end
end
end
end
function DiggyCaveCollapse . get_extra_map_info ( config )
return [ [ Alien Spawner , aliens might spawn when mining !
Place stone walls , stone paths and ( refined ) concrete to reinforce the mine . If you see cracks appear , run ! ] ]
end
return DiggyCaveCollapse