diff --git a/control.lua b/control.lua index 06a80ef4..3d01d42d 100644 --- a/control.lua +++ b/control.lua @@ -14,7 +14,7 @@ require 'follow' require 'autodeconstruct' require 'corpse_util' --require 'infinite_storage_chest' -require 'fish_market' +--require 'fish_market' require 'reactor_meltdown' require 'train_saviour' require 'map_gen.shared.perlin_noise' diff --git a/map_gen/Diggy/AlienEvolutionProgress.lua b/map_gen/Diggy/AlienEvolutionProgress.lua new file mode 100644 index 00000000..204efcd9 --- /dev/null +++ b/map_gen/Diggy/AlienEvolutionProgress.lua @@ -0,0 +1,150 @@ +--[[-- info + Original (javascript) version: https://hastebin.com/udakacavap.js + Can be tested against: https://wiki.factorio.com/Enemies#Spawn_chances_by_evolution_factor +]] + +-- dependencies +local Global = require 'utils.global' +local random = math.random + +-- this +local AlienEvolutionProgress = {} + +local alien_cache = { + biters = {}, + spitters = {}, +} + +Global.register({ + alien_cache = alien_cache, +}, function(tbl) + alien_cache = tbl.alien_cache +end) + +-- values are in the form {evolution, weight} +local biters = { + {'small-biter', {{0.0, 0.3}, {0.6, 0.0}}}, + {'medium-biter', {{0.2, 0.0}, {0.6, 0.3}, {0.7, 0.1}}}, + {'big-biter', {{0.5, 0.0}, {1.0, 0.4}}}, + {'behemoth-biter', {{0.9, 0.0}, {1.0, 0.3}}}, +} + +local spitters = { + {'small-biter', {{0.0, 0.3}, {0.35, 0.0}}}, + {'small-spitter', {{0.25, 0.0}, {0.5, 0.3}, {0.7, 0.0}}}, + {'medium-spitter', {{0.4, 0.0}, {0.7, 0.3}, {0.9, 0.1}}}, + {'big-spitter', {{0.5, 0.0}, {1.0, 0.4}}}, + {'behemoth-spitter', {{0.9, 0.0}, {1.0, 0.3}}}, +} + +local function lerp(low, high, pos) + local s = high[1] - low[1]; + local l = (pos - low[1]) / s; + return (low[2] * (1 - l)) + (high[2] * l) +end + +local function get_values(map, evo) + local result = {} + local sum = 0 + + for _, data in pairs(map) do + local list = data[2]; + local low = list[1]; + local high = list[#list]; + + for _, val in pairs(list) do + if(val[1] <= evo and val[1] > low[1]) then + low = val; + end + if(val[1] >= evo and val[1] < high[1]) then + high = val + end + end + + local val + if (evo <= low[1]) then + val = low[2] + elseif (evo >= high[1]) then + val = high[2]; + else + val = lerp(low, high, evo) + end + sum = sum + val; + + result[data[1]] = val; + end + + for index, _ in pairs(result) do + result[index] = result[index] / sum + end + + return result; +end + +local function get_name_by_random(collection) + local pre_calculated = random() + local current = 0 + + for name, probability in pairs(collection) do + current = current + probability + if (current >= pre_calculated) then + return name + end + end + + Debug.print('AlienEvolutionProgress.get_name_by_random: Current \'' .. current .. '\' should be higher or equal to random \'' .. pre_calculated .. '\'') +end + +function AlienEvolutionProgress.getBiterValues(evolution) + local evolution_cache_key = evolution * 100 + + if (nil == alien_cache.biters[evolution_cache_key]) then + alien_cache.biters[evolution_cache_key] = get_values(biters, evolution) + end + + return alien_cache.biters[evolution_cache_key] +end + +function AlienEvolutionProgress.getSpitterValues(evolution) + local evolution_cache_key = evolution * 100 + + if (nil == alien_cache.spitters[evolution_cache_key]) then + alien_cache.spitters[evolution_cache_key] = get_values(spitters, evolution) + end + + return alien_cache.spitters[evolution_cache_key] +end + +function AlienEvolutionProgress.getBitersByEvolution(total_biters, evolution) + local biters_calculated = {} + local map = AlienEvolutionProgress.getBiterValues(evolution) + + for i = 1, total_biters do + local name = get_name_by_random(map) + if (nil == biters_calculated[name]) then + biters_calculated[name] = 1 + else + biters_calculated[name] = biters_calculated[name] + 1 + end + end + + return biters_calculated +end + +function AlienEvolutionProgress.getSpittersByEvolution(total_spitters, evolution) + local spitters_calculated = {} + local map = AlienEvolutionProgress.getSpitterValues(evolution) + + for i = 1, total_spitters do + local name = get_name_by_random(map) + if (nil == spitters_calculated[name]) then + spitters_calculated[name] = 1 + else + spitters_calculated[name] = spitters_calculated[name] + 1 + end + end + + return spitters_calculated +end + +return AlienEvolutionProgress diff --git a/map_gen/Diggy/Config.lua b/map_gen/Diggy/Config.lua new file mode 100644 index 00000000..498b7f60 --- /dev/null +++ b/map_gen/Diggy/Config.lua @@ -0,0 +1,246 @@ +-- dependencies + +-- this +local Config = { + -- enable debug mode, shows extra messages + debug = false, + + -- allow cheats. Example: by default the player will have X mining speed + cheats = false, + + -- a list of features to register and enable + -- to disable a feature, change the flag + features = { + StartingZone = { + enabled = true, + + -- initial starting position size, values higher than 30 might break + starting_size = 8, + }, + SetupPlayer = { + enabled = true, + starting_items = { + {name = 'iron-axe', count = 1}, + {name = 'stone-wall', count = 10}, + }, + cheats = { + manual_mining_speed_modifier = 1000, + }, + }, + DiggyHole = { + enabled = true, + }, + DiggyCaveCollapse = { + enabled = true, + + -- adds per tile what the current stress is + enable_stress_grid = false, + + -- shows the mask on spawn + enable_mask_debug = false, + + --the size of the mask used + mask_size = 9, + + --how much the mask will effect tiles in the different rings of the mask + mask_relative_ring_weights = {2, 3, 4}, + + -- delay in seconds before the cave collapses + collapse_delay = 2.5, + + -- the threshold that will be applied to all neighbors on a collapse via a mask + collapse_threshold_total_strength = 16, + + support_beam_entities = { + ['market'] = 10, + ['stone-wall'] = 3.3, + ['sand-rock-big'] = 2.2, + ['out-of-map'] = 1.1, + ['stone-brick'] = 0.055, + ['stone-path'] = 0.055, + ['concrete'] = 0.33, + ['hazard-concrete-left'] = 0.33, + ['hazard-concrete-right'] = 0.33, + ['refined-concrete'] = 0.77, + ['refined-hazard-concrete-left'] = 0.77, + ['refined-hazard-concrete-right'] = 0.77, + }, + cracking_sounds = { + 'CRACK', + 'KRRRR', + } + }, + RefreshMap = { + enabled = true, + }, + SimpleRoomGenerator = { + enabled = true, + + -- value between 0 and 1, higher value means stronger variance between coordinates + noise_variance = 0.066, + + -- adds per tile what the current noise is + enable_noise_grid = false, + + -- minimum distance and noise range required for water to spawn + room_noise_minimum_distance = 9, + room_noise_ranges = { + {name = 'water', min = 0.54, max = 1}, + {name = 'dirt', min = 0.39, max = 0.53}, + }, + }, + ScatteredResources = { + enabled = true, + + -- percentage of resource added to the sum. 100 tiles means + -- 10% more resources with a distance_richness_modifier of 10 + -- 20% more resources with a distance_richness_modifier of 5 + distance_richness_modifier = 5, + + -- defines the increased chance of spawning resources + -- calculated_probability = resource_probability + ((distance / distance_probability_modifier) / 100) + distance_probability_modifier = 2, + + -- increases the amount of oil * oil_value_modifier + oil_value_modifier = 650, + + -- percentage of chance that resources will spawn after mining + resource_probability = 0.15, + + -- max chance of spawning resources based on resource_probability + calculated distance_probability_modifier + max_resource_probability = 0.45, + + -- chances per resource of spawning, sum must be 1.00 + resource_chances = { + ['coal'] = 0.21, + ['copper-ore'] = 0.30, + ['iron-ore'] = 0.26, + ['stone'] = 0.20, + ['uranium-ore'] = 0.02, + ['crude-oil'] = 0.01, + }, + + -- minimum distance from the spawn point required before it spawns + minimum_resource_distance = { + ['coal'] = 16, + ['copper-ore'] = 18, + ['iron-ore'] = 18, + ['stone'] = 15, + ['uranium-ore'] = 86, + ['crude-oil'] = 57, + }, + + -- defines the chance of which resource_richness_value to spawn, sum must be 1.00 + resource_richness_probability = { + ['scarce'] = 0.40, + ['low'] = 0.28, + ['sufficient'] = 0.16, + ['good'] = 0.10, + ['plenty'] = 0.04, + ['jackpot'] = 0.02, + }, + + -- defines the min and max range of ores to spawn + resource_richness_values = { + ['scarce'] = {1, 200}, + ['low'] = {201, 400}, + ['sufficient'] = {401, 750}, + ['good'] = {751, 1200}, + ['plenty'] = {1201, 2000}, + ['jackpot'] = {2001, 5000}, + }, + }, + AlienSpawner = { + enabled = false, + + -- minimum distance from spawn before aliens can spawn + alien_minimum_distance = 35, + + -- chance of spawning aliens when mining + alien_probability = 0.07, + }, + MarketExchange = { + enabled = true, + + -- percentage * mining productivity level gets added to mining speed + mining_speed_productivity_multiplier = 10, + + -- market config + market_spawn_position = {x = 0, y = 4}, + stone_to_surface_amount = 50, + currency_item = 'stone', + + unlockables = { + {stone = 50, type = 'buff', prototype = {name = 'mining_speed', value = 10}}, + {stone = 50, type = 'buff', prototype = {name = 'inventory_slot', value = 3}}, + {stone = 50, type = 'market', prototype = {price = 50, name = 'raw-fish'}}, + {stone = 50, type = 'market', prototype = {price = 175, name = 'steel-axe'}}, + + {stone = 250, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 250, type = 'buff', prototype = {name = 'inventory_slot', value = 2}}, + {stone = 250, type = 'market', prototype = {price = 50, name = 'small-electric-pole'}}, + {stone = 250, type = 'market', prototype = {price = 50, name = 'small-lamp'}}, + {stone = 250, type = 'market', prototype = {price = 25, name = 'stone-brick'}}, + {stone = 250, type = 'market', prototype = {price = 125, name = 'stone-wall'}}, + + {stone = 450, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 450, type = 'buff', prototype = {name = 'inventory_slot', value = 2}}, + {stone = 450, type = 'market', prototype = {price = 850, name = 'submachine-gun'}}, + {stone = 450, type = 'market', prototype = {price = 50, name = 'firearm-magazine'}}, + {stone = 450, type = 'market', prototype = {price = 500, name = 'light-armor'}}, + + {stone = 750, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 750, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 1250, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 1250, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 1750, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 1750, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 2500, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 2500, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 4000, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 4000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 6500, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 6500, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 8000, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 8000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + + {stone = 10000, type = 'buff', prototype = {name = 'mining_speed', value = 5}}, + {stone = 10000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 10000, type = 'market', prototype = {price = 750, name = 'heavy-armor'}}, + + {stone = 15000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 15000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 25000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 25000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + + {stone = 35000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 35000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + {stone = 35000, type = 'market', prototype = {price = 100, name = 'piercing-rounds-magazine'}}, + {stone = 35000, type = 'market', prototype = {price = 1500, name = 'modular-armor'}}, + + {stone = 50000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 50000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + + {stone = 75000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 75000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + + {stone = 100000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 100000, type = 'buff', prototype = {name = 'inventory_slot', value = 1}}, + + {stone = 125000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 150000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 175000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 200000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 225000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 250000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 275000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 300000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 350000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 400000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + {stone = 500000, type = 'buff', prototype = {name = 'mining_speed', value = 2}}, + }, + }, + }, +} + +return Config diff --git a/map_gen/Diggy/Debug.lua b/map_gen/Diggy/Debug.lua new file mode 100644 index 00000000..761e27f1 --- /dev/null +++ b/map_gen/Diggy/Debug.lua @@ -0,0 +1,121 @@ +-- dependencies +local min = math.min +local max = math.max +local floor = math.floor +local abs = math.abs + +-- this +local Debug = {} + +-- private state +local debug = false +local cheats = false + +function Debug.enable_debug() + debug = true +end + +function Debug.disable_debug() + debug = false +end + +function Debug.enable_cheats() + cheats = true +end + +function Debug.disable_cheats() + cheats = true +end + +global.message_count = 0 + +--[[-- + Shows the given message if _DEBUG == true. + + @param message string +]] +function Debug.print(message) + if type(message) ~= 'string' and type(message) ~= 'number' and type(message) ~= 'boolean' then message = type(message) end + global.message_count = global.message_count + 1 + if (debug) then + game.print('[' .. global.message_count .. '] ' .. tostring(message)) + log('[' .. global.message_count .. '] ' .. tostring(message)) + end +end + +--[[-- + Shows the given message if _DEBUG == true for a given position. + + @param x number + @param y number + @param message string +]] +function Debug.printPosition(position, message) + message = message or '' + if type(message) ~= 'string' and type(message) ~= 'number' and type(message) ~= 'boolean' then message = type(message) end + global.message_count = global.message_count + 1 + if (debug) then + game.print('[' .. global.message_count .. '] {x=' .. position.x .. ', y=' .. position.y .. '} ' .. tostring(message)) + end +end + +--[[-- + Executes the given callback if _DIGGY_CHEATS == true. + + @param callback function +]] +function Debug.cheat(callback) + if (cheats) then + callback() + end +end + +--[[-- + Prints a colored value on a location. + + @param value between -1 and 1 + @param surface LuaSurface + @param position Position {x, y} +]] +function Debug.print_grid_value(value, surface, position) + local r = max(1, value) + local g = 1 - abs(value) + local b = min(1, value) + + if (r > 0) then + r = 0 + end + + if (b < 0) then + b = 0 + end + + r = abs(r) + + local color = { r = r, g = g, b = b} + + -- round at precision of 2 + local text = floor(100 * value) / 100 + + if (0 == text) then + text = '0.00' + end + + local text_entity = surface.find_entity('flying-text', position) + + if text_entity then + text_entity.text = text + text_entity.color = color + + return + end + + surface.create_entity{ + name = 'flying-text', + color = color, + text = text, + position = position + }.active = false +end + +return Debug diff --git a/map_gen/Diggy/Feature/AlienSpawner.lua b/map_gen/Diggy/Feature/AlienSpawner.lua new file mode 100644 index 00000000..85f182a4 --- /dev/null +++ b/map_gen/Diggy/Feature/AlienSpawner.lua @@ -0,0 +1,58 @@ +--[[-- info + Provides the ability to spawn aliens. +]] + +-- dependencies +local Event = require 'utils.event' +local AlienEvolutionProgress = require 'map_gen.Diggy.AlienEvolutionProgress' +local Debug = require 'map_gen.Diggy.Debug' +local Template = require 'map_gen.Diggy.Template' +local insert = table.insert +local random = math.random + +-- this +local AlienSpawner = {} + +local function spawn_alien(surface, x, y) + local enemy_force = game.forces.enemy + local enemy_force_evolution = enemy_force.evolution_factor + local position = {x = x, y = y} + local biters = AlienEvolutionProgress.getBitersByEvolution(random(1, 2), enemy_force_evolution) + local spitters = AlienEvolutionProgress.getSpittersByEvolution(random(1, 2), enemy_force_evolution) + + local units = {} + for name, amount in pairs(biters) do + insert(units, {name = name, position = position, force = enemy_force, amount = amount}) + end + for name, amount in pairs(spitters) do + insert(units, {name = name, position = position, force = enemy_force, amount = amount}) + end + + Template.units(surface, units, 3) +end + +--[[-- + Registers all event handlers. +]] +function AlienSpawner.register(config) + local alien_minimum_distance_square = config.alien_minimum_distance ^ 2 + + Event.add(Template.events.on_void_removed, function(event) + local x = event.old_tile.position.x + local y = event.old_tile.position.y + + if (x^2 + y^2 < alien_minimum_distance_square or config.alien_probability < random()) then + return + end + + spawn_alien(event.surface, x, y) + end) +end + +function AlienSpawner.get_extra_map_info(config) + return [[Alien Spawner, aliens might spawn when mining! +Spawn chance: ]] .. (config.alien_probability * 100) .. [[% +Minimum spawn distance: ]] .. config.alien_minimum_distance .. ' tiles' +end + +return AlienSpawner diff --git a/map_gen/Diggy/Feature/DiggyCaveCollapse.lua b/map_gen/Diggy/Feature/DiggyCaveCollapse.lua new file mode 100644 index 00000000..3cb23979 --- /dev/null +++ b/map_gen/Diggy/Feature/DiggyCaveCollapse.lua @@ -0,0 +1,652 @@ +--[[-- info + Provides the ability to collapse caves when digging. +]] +-- dependencies +require 'utils.list_utils' + +local Event = require 'utils.event' +local Template = require 'map_gen.Diggy.Template' +local Debug = require 'map_gen.Diggy.Debug' +local Task = require 'utils.Task' +local Token = require 'utils.global_token' +local Global = require 'utils.global' +local insert = table.insert +local random = math.random +local floor = math.floor +local abs = math.abs + +-- 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_blur_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 = 1 + +local deconstruction_alert_message_shown = {} +local stress_map_storage = {} +local new_tile_map = {} +local collapse_positions_storage = {} +local cave_collapse_disabled = nil + + +Global.register({ + new_tile_map = new_tile_map, + stress_map_storage = stress_map_storage, + deconstruction_alert_message_shown = deconstruction_alert_message_shown, + collapse_positions_storage = collapse_positions_storage, + cave_collapse_disabled = cave_collapse_disabled, +}, function(tbl) + new_tile_map = tbl.new_tile_map + stress_map_storage = tbl.stress_map_storage + deconstruction_alert_message_shown = tbl.deconstruction_alert_message_shown + collapse_positions_storage = tbl.collapse_positions_storage + cave_collapse_disabled = tbl.cave_collapse_disabled +end) + +local defaultValue = 0 + +DiggyCaveCollapse.events = { + --[[-- + When stress at certain position is above the collapse threshold + - position LuaPosition + - surface LuaSurface + ]] + on_collapse_triggered = script.generate_event_name() +} + +local function create_collapse_template(positions, surface) + local entities = {} + local tiles = {} + local map = {} + for _, position in pairs(positions) do + local x = position.x + local y = position.y + map[x] = map[x] or {} + map[x][y] = map[x][y] or true + insert(tiles, {position = {x = x, y = y}, name = 'out-of-map'}) + end + + for x, y_tbl in pairs(map) do + for y, _ in pairs(y_tbl) do + if not map[x] or not map[x][y - 1] then + insert(entities, {position = {x = x, y = y - 1}, name = 'sand-rock-big'}) + end + if not map[x] or not map[x][y + 1] then + insert(entities, {position = {x = x, y = y + 1}, name = 'sand-rock-big'}) + end + if not map[x - 1] or not map[x - 1][y] then + insert(entities, {position = {x = x - 1, y = y}, name = 'sand-rock-big'}) + end + if not map[x + 1] or not map[x + 1][y] then + insert(entities, {position = {x = x + 1, y = y}, name = 'sand-rock-big'}) + end + end + end + + local find_entities_filtered = surface.find_entities_filtered + + for _, new_spawn in pairs({entities, tiles}) do + for _, tile in pairs(new_spawn) do + for _, entity in pairs(find_entities_filtered({position = tile.position})) do + pcall(function() + local strength = support_beam_entities[entity.name] + local position = entity.position + + entity.die() + if strength then + stress_map_blur_add(surface, position, strength) + end + end) + end + end + end + + return tiles, entities +end + +local function collapse(args) + local position = args.position + local surface = args.surface + local positions = {} + local tiles = {} + local entities + local strength = config.collapse_threshold_total_strength + mask_disc_blur( + position.x, position.y, + strength, + function(x, y, value) + stress_map_check_stress_in_threshold( + surface, + {x = x, y = y}, + value, + function(_, position) + insert(positions, position) + end + ) + end + ) + tiles, entities = create_collapse_template(positions, surface) + Template.insert(surface, tiles, entities) +end + +local on_collapse_timeout_finished = Token.register(collapse) + +local function spawn_cracking_sound_text(surface, position) + local text = config.cracking_sounds[random(1, #config.cracking_sounds)] + + local color = { + r = 1, + g = random(1, 100) / 100, + b = 0 + } + + for i = 1, #text do + local x_offset = (i - #text / 2 - 1) / 3 + local char = text:sub(i, i) + surface.create_entity { + name = 'flying-text', + color = color, + text = char, + position = {x = position.x + x_offset, y = position.y - ((i + 1) % 2) / 4} + }.active = true + end +end + +local function on_collapse_triggered(event) + + if 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 + Template.insert(surface, {}, {{position = position, name = 'sand-rock-big'}}) + return + end + spawn_cracking_sound_text(surface, position) + Task.set_timeout( + config.collapse_delay, + on_collapse_timeout_finished, + {surface = surface, position = position} + ) +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_blur_add(surface, tile.position, -1 * new_tile_strength) + end + + local old_tile_strength = support_beam_entities[tile.old_tile.name] + if (old_tile_strength) then + stress_map_blur_add(surface, tile.position, old_tile_strength) + end + end +end + +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_blur_add(surface, tile.position, strength) + 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_blur_add(surface, tile.position, strength) + end + end +end + +local function on_mined_entity(event) + local entity = event.entity + local strength = support_beam_entities[entity.name] + + if strength then + stress_map_blur_add(entity.surface, entity.position, strength) + 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_blur_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_blur_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.old_tile.position + if strength then + stress_map_blur_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 + +local function on_void_added(event) + local strength = support_beam_entities['out-of-map'] + if strength then + stress_map_blur_add(event.surface, event.old_tile.position, -1 * strength) + end +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 + + 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_robot_mined_entity, on_mined_entity) + 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_mined_entity) + Event.add(defines.events.on_player_mined_entity, on_mined_entity) + Event.add(Template.events.on_void_removed, on_void_removed) + Event.add(Template.events.on_void_added, on_void_added) + Event.add(defines.events.on_surface_created, on_surface_created) + + Event.add(defines.events.on_marked_for_deconstruction, function (event) + if (nil ~= support_beam_entities[event.entity.name]) then + event.entity.cancel_deconstruction(game.players[event.player_index].force) + end + end) + + Event.add(defines.events.on_pre_player_mined_item, function(event) + if (nil ~= deconstruction_alert_message_shown[event.player_index]) then + return + end + + if (nil ~= support_beam_entities[event.entity.name]) then + require 'popup'.player( + game.players[event.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. +]] + ) + deconstruction_alert_message_shown[event.player_index] = true + 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 + + 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 + cave_collapse_disabled = not cave_collapse_disabled + if cave_collapse_disabled then + game.print("Cave collapse: Disabled.") + else + game.print("Cave collapse: Enabled.") + 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 +]] +function add_fraction(stress_map, x, y, fraction) + local quadrant = 1 + if x < 0 then + quadrant = quadrant + 1 + x = -x + end + if y < 0 then + quadrant = quadrant + 2 + y = -y + end + + local quad_t = stress_map[quadrant] + + local x_t = quad_t[x] + if not x_t then + x_t = {} + quad_t[x] = x_t + end + + local value = x_t[y] + if not value then + value = defaultValue + end + + value = value + fraction + + x_t[y] = value + + if (fraction > 0 and value > stress_threshold_causing_collapse) then + if quadrant > 2 then + y = -y + end + if quadrant % 2 == 0 then + x = -x + end + script.raise_event( + DiggyCaveCollapse.events.on_collapse_triggered, + {surface = game.surfaces[stress_map.surface_index], position = {x = x, y = y}} + ) + end + if (enable_stress_grid) then + local surface = game.surfaces[stress_map.surface_index] + if quadrant > 2 then + y = -y + end + if quadrant % 2 == 0 then + x = -x + end + Debug.print_grid_value(value, surface, {x = x, y = y}) + end + return value +end + +function add_fraction_by_quadrant(stress_map, x, y, fraction, quadrant) + local x_t = quadrant[x] + if not x_t then + x_t = {} + quadrant[x] = x_t + end + + local value = x_t[y] + if not value then + value = defaultValue + end + + value = value + fraction + + x_t[y] = value + + if (fraction > 0 and value > stress_threshold_causing_collapse) then + local index = quadrant.index + if index > 2 then + y = -y + end + if index % 2 == 0 then + x = -x + end + script.raise_event( + DiggyCaveCollapse.events.on_collapse_triggered, + {surface = game.surfaces[stress_map.surface_index], position = {x = x, y = y}} + ) + end + if (enable_stress_grid) then + local surface = game.surfaces[stress_map.surface_index] + local index = quadrant.index + if index > 2 then + y = -y + end + if index % 2 == 0 then + x = -x + end + Debug.print_grid_value(value, surface, {x = x, y = y}) + end + return value +end + + +on_surface_created = function(event) + stress_map_storage[event.surface_index] = {} + + local map = stress_map_storage[event.surface_index] + + map['surface_index'] = event.surface_index + map[1] = {index = 1} + map[2] = {index = 2} + map[3] = {index = 3} + map[4] = {index = 4} +end + +--[[-- + Checks whether a tile's pressure is within a given threshold and calls the handler if not. + @param surface LuaSurface + @param position Position with x and y + @param number threshold + @param callback +]] +stress_map_check_stress_in_threshold = function(surface, position, threshold, callback) + local stress_map = stress_map_storage[surface.index] + local value = add_fraction(stress_map, position.x, position.y, 0) + + if (value >= stress_threshold_causing_collapse - threshold) then + callback(surface, position) + end +end + +stress_map_blur_add = function(surface, position, factor) + 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 radius > abs(x_start) or radius > abs(y_start) then + 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 + add_fraction(stress_map, x + x_start, y + y_start, value * factor) + end + end + end + else + local quadrant_n = 1 + if x_start < 0 then + quadrant_n = quadrant_n + 1 + x_start = -x_start + end + if y_start < 0 then + quadrant_n = quadrant_n + 2 + y_start = -y_start + end + local quadrant = stress_map[quadrant_n] + 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 + add_fraction_by_quadrant(stress_map, x + x_start, y + y_start, value * factor, quadrant) + end + end + end + end +end + +DiggyCaveCollapse.stress_map_blur_add = stress_map_blur_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 / 2) + + 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 diff --git a/map_gen/Diggy/Feature/DiggyHole.lua b/map_gen/Diggy/Feature/DiggyHole.lua new file mode 100644 index 00000000..c850daa7 --- /dev/null +++ b/map_gen/Diggy/Feature/DiggyHole.lua @@ -0,0 +1,108 @@ +--[[-- info + Provides the ability to "mine" through out-of-map tiles by destroying or + mining rocks next to it. +]] + +-- dependencies +local Event = require 'utils.event' +local Scanner = require 'map_gen.Diggy.Scanner' +local Template = require 'map_gen.Diggy.Template' +local Debug = require 'map_gen.Diggy.Debug' +local insert = table.insert +local random = math.random + +-- this +local DiggyHole = {} + +--[[-- + Triggers a diggy diggy hole for a given sand-rock-big. + + Will return true even if the tile behind it is immune. + + @param entity LuaEntity +]] +local function diggy_hole(entity) + if (entity.name ~= 'sand-rock-big') then + return + end + + local tiles = {} + local rocks = {} + local surface = entity.surface + + local out_of_map_found = Scanner.scan_around_position(surface, entity.position, 'out-of-map'); + + for _, position in pairs(out_of_map_found) do + insert(tiles, {name = 'dirt-' .. random(1, 7), position = position}) + insert(rocks, {name = 'sand-rock-big', position = position}) + end + + Template.insert(surface, tiles, rocks) +end + +local artificial_tiles = { + ['stone-brick'] = true, + ['stone-path'] = true, + ['concrete'] = true, + ['hazard-concrete-left'] = true, + ['hazard-concrete-right'] = true, + ['refined-concrete'] = true, + ['refined-hazard-concrete-left'] = true, + ['refined-hazard-concrete-right'] = true, +} + +local function on_mined_tile(surface, tiles) + local new_tiles = {} + + for _, tile in pairs(tiles) do + if (artificial_tiles[tile.old_tile.name]) then + insert(new_tiles, { name = 'dirt-' .. random(1, 7), position = tile.position}) + end + end + + Template.insert(surface, new_tiles, {}) +end + +local function on_built_tile(surface, item, old_tile_and_positions) + if ('landfill' ~= item.name) then + return + end + + local tiles = {} + for _, tile in pairs(old_tile_and_positions) do + insert(tiles, {name = 'dirt-' .. random(1, 7), position = tile.position}) + end + + Template.insert(surface, tiles) +end + +--[[-- + Registers all event handlers. +]] +function DiggyHole.register(config) + Event.add(defines.events.on_entity_died, function (event) + diggy_hole(event.entity) + end) + + Event.add(defines.events.on_player_mined_entity, function (event) + diggy_hole(event.entity) + end) + + Event.add(defines.events.on_robot_mined_tile, function(event) + on_mined_tile(event.robot.surface, event.tiles) + end) + + Event.add(defines.events.on_player_mined_tile, function(event) + on_mined_tile(game.surfaces[event.surface_index], event.tiles) + end) + + Event.add(defines.events.on_robot_built_tile, function (event) + on_built_tile(event.robot.surface, item, 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) +end + +return DiggyHole diff --git a/map_gen/Diggy/Feature/MarketExchange.lua b/map_gen/Diggy/Feature/MarketExchange.lua new file mode 100644 index 00000000..1b03f306 --- /dev/null +++ b/map_gen/Diggy/Feature/MarketExchange.lua @@ -0,0 +1,263 @@ +--[[-- info + Provides the ability to purchase items from the market. +]] + +-- dependencies +local Event = require 'utils.event' +local Token = require 'utils.global_token' +local Task = require 'utils.Task' +local Gui = require 'utils.gui' +local Debug = require 'map_gen.Diggy.Debug' +local Template = require 'map_gen.Diggy.Template' +local Global = require 'utils.global' + +-- this +local MarketExchange = {} + +local config = {} + +local stone_tracker = { + first_time_market_item = nil, + stone_sent_to_surface = 0, + previous_stone_sent_to_surface = 0, +} +local mining_efficiency = { + active_modifier = 0, + research_modifier = 0, + market_modifier = 0, +} +local inventory_slots = { + active_modifier = 0, + research_modifier = 0, + market_modifier = 0, +} + +Global.register({ + stone_tracker = stone_tracker, + mining_efficiency = mining_efficiency, + inventory_slots = inventory_slots, +}, function(tbl) + stone_tracker = tbl.stone_tracker + mining_efficiency = tbl.mining_efficiency + inventory_slots = tbl.inventory_slots +end) + +local on_market_timeout_finished = Token.register(function(params) + Template.market(params.surface, params.position, params.player_force, params.currency_item, {}) +end) + +local function update_mining_speed(force) + -- remove the current buff + local old_modifier = force.manual_mining_speed_modifier - mining_efficiency.active_modifier + + -- update the active modifier + mining_efficiency.active_modifier = mining_efficiency.research_modifier + mining_efficiency.market_modifier + + -- add the new active modifier to the non-buffed modifier + force.manual_mining_speed_modifier = old_modifier + mining_efficiency.active_modifier +end + +local function update_inventory_slots(force) + -- remove the current buff + local old_modifier = force.character_inventory_slots_bonus - inventory_slots.active_modifier + + -- update the active modifier + inventory_slots.active_modifier = inventory_slots.research_modifier + inventory_slots.market_modifier + + -- add the new active modifier to the non-buffed modifier + force.character_inventory_slots_bonus = old_modifier + inventory_slots.active_modifier +end + +local function update_market_contents(market) + local should_update_mining_speed = false + local should_update_inventory_slots = false + + if (nil ~= stone_tracker.first_time_market_item) then + market.add_market_item(stone_tracker.first_time_market_item) + stone_tracker.first_time_market_item = nil + end + + for _, unlockable in pairs(config.unlockables) do + local is_in_range = unlockable.stone > stone_tracker.previous_stone_sent_to_surface and unlockable.stone <= stone_tracker.stone_sent_to_surface + + -- only add the item to the market if it's between the old and new stone range + if (is_in_range and unlockable.type == 'market') then + market.add_market_item({ + price = {{config.currency_item, unlockable.prototype.price}}, + offer = {type = 'give-item', item = unlockable.prototype.name, count = 1} + }) + elseif (is_in_range and unlockable.type == 'buff' and unlockable.prototype.name == 'mining_speed') then + should_update_mining_speed = true + mining_efficiency.market_modifier = mining_efficiency.market_modifier + (unlockable.prototype.value / 100) + elseif (is_in_range and unlockable.type == 'buff' and unlockable.prototype.name == 'inventory_slot') then + should_update_inventory_slots = true + inventory_slots.market_modifier = inventory_slots.market_modifier + unlockable.prototype.value + end + end + + local force + + if (should_update_mining_speed) then + force = force or game.forces.player + update_mining_speed(force) + end + + if (should_update_inventory_slots) then + force = force or game.forces.player + update_inventory_slots(force) + end +end + +local function on_research_finished(event) + local force = game.forces.player + local current_modifier = mining_efficiency.research_modifier + local new_modifier = force.mining_drill_productivity_bonus * config.mining_speed_productivity_multiplier / 2 + + if (current_modifier == new_modifier) then + -- something else was researched + return + end + + mining_efficiency.research_modifier = new_modifier + inventory_slots.research_modifier = (force.mining_drill_productivity_bonus / 2) * 100 + + update_inventory_slots(force) + update_mining_speed(force) +end + +local function redraw_title(data) + data.frame.caption = stone_tracker.stone_sent_to_surface .. ' stone sent to the surface' +end + +local function redraw_list(data) + local market_scroll_pane = data.market_scroll_pane + Gui.clear(market_scroll_pane) + + for _, unlockable in pairs(config.unlockables) do + local is_unlocked = unlockable.stone <= stone_tracker.stone_sent_to_surface + local message + -- only add the item to the market if it's between the old and new stone range + if (unlockable.type == 'market') then + message = 'Market item: ' .. unlockable.prototype.name + elseif (unlockable.type == 'buff' and unlockable.prototype.name == 'mining_speed') then + message = 'Manual mining speed: +' .. unlockable.prototype.value .. '%' + elseif (unlockable.type == 'buff' and unlockable.prototype.name == 'inventory_slot') then + message = 'Inventory slot: +' .. unlockable.prototype.value + else + Debug.print('failed getting a message for: ' .. serpent.line(unlockable)) + end + + message = unlockable.stone .. ' stone: ' .. message + + local label = market_scroll_pane.add({type = 'label', caption = message}) + if (is_unlocked) then + label.style.font_color = {r = 1, g = 1, b = 1} + else + label.style.font_color = {r = 0.5, g = 0.5, b = 0.5} + end + end +end + +local function on_market_item_purchased(event) + if (1 ~= event.offer_index) then + return + end + + stone_tracker.previous_stone_sent_to_surface = stone_tracker.stone_sent_to_surface + stone_tracker.stone_sent_to_surface = stone_tracker.stone_sent_to_surface + (config.stone_to_surface_amount * event.count) + + update_market_contents(event.market) + + local frame = game.players[event.player_index].gui.center['Diggy.MarketExchange.Frame'] + + if frame and frame.valid then + local data = Gui.get_data(frame) + redraw_title(data) + redraw_list(data) + end +end + +local function on_placed_entity(event) + if ('market' ~= event.entity.name) then + return + end + + update_market_contents(event.entity) +end + +function MarketExchange.get_extra_map_info(config) + return 'Market Exchange, trade your stone or send it to the surface' +end + +local function toggle(event) + local player = event.player + local center = player.gui.center + local frame = center['Diggy.MarketExchange.Frame'] + + if (frame) then + Gui.destroy(frame) + return + end + + frame = center.add({name = 'Diggy.MarketExchange.Frame', type = 'frame', direction = 'vertical'}) + + local market_scroll_pane = frame.add({type = 'scroll-pane'}) + market_scroll_pane.style.maximal_height = 400 + + frame.add({ type = 'button', name = 'Diggy.MarketExchange.Button', caption = 'Close'}) + + local data = { + frame = frame, + market_scroll_pane = market_scroll_pane, + } + + redraw_title(data) + redraw_list(data) + + Gui.set_data(frame, data) + + player.opened = frame +end + +local function on_player_created(event) + game.players[event.player_index].gui.top.add({ + name = 'Diggy.MarketExchange.Button', + type = 'sprite-button', + sprite = 'item/stone', + }) +end + +Gui.on_click('Diggy.MarketExchange.Button', toggle) +Gui.on_custom_close('Diggy.MarketExchange.Frame', function (event) + event.element.destroy() +end) + +function MarketExchange.on_init() + Task.set_timeout_in_ticks(50, on_market_timeout_finished, { + surface = game.surfaces.nauvis, + position = config.market_spawn_position, + player_force = game.forces.player, + currency_item = config.currency_item, + }) + + update_mining_speed(game.forces.player) +end + +--[[-- + Registers all event handlers. +]] +function MarketExchange.register(cfg) + config = cfg + + stone_tracker.first_time_market_item = { + price = {{config.currency_item, 50}}, + offer = {type = 'nothing', effect_description = 'Send ' .. config.stone_to_surface_amount .. ' stone to the surface'} + } + + Event.add(defines.events.on_research_finished, on_research_finished) + Event.add(defines.events.on_market_item_purchased, on_market_item_purchased) + Event.add(Template.events.on_placed_entity, on_placed_entity) + Event.add(defines.events.on_player_created, on_player_created) +end + +return MarketExchange diff --git a/map_gen/Diggy/Feature/RefreshMap.lua b/map_gen/Diggy/Feature/RefreshMap.lua new file mode 100644 index 00000000..9918b08e --- /dev/null +++ b/map_gen/Diggy/Feature/RefreshMap.lua @@ -0,0 +1,40 @@ +--[[-- info + Provides the ability to refresh the map and generate darkness. +]] + +-- dependencies +local Event = require 'utils.event' +local insert = table.insert + +-- this +local RefreshMap = {} + +--[[-- + Registers all event handlers. +]] +function RefreshMap.register(config) + Event.add(defines.events.on_chunk_generated, function (event) + local tiles = {} + + for x = 0, 31, 1 do + for y = 0, 31, 1 do + local target_x = event.area.left_top.x + x + local target_y = event.area.left_top.y + y + local tile = 'out-of-map' + + if (target_x < 1 and target_y < 1 and target_x > -2 and target_y > -2) then + tile = 'lab-dark-1' + end + + insert(tiles, { + name = tile, + position = {x = target_x, y = target_y} + }) + end + end + + event.surface.set_tiles(tiles) + end) +end + +return RefreshMap diff --git a/map_gen/Diggy/Feature/ScatteredResources.lua b/map_gen/Diggy/Feature/ScatteredResources.lua new file mode 100644 index 00000000..ae903f63 --- /dev/null +++ b/map_gen/Diggy/Feature/ScatteredResources.lua @@ -0,0 +1,96 @@ +--[[-- info + Provides the ability to spawn random ores all over the place. +]] + +-- dependencies +local Event = require 'utils.event' +local Debug = require 'map_gen.Diggy.Debug' +local Template = require 'map_gen.Diggy.Template' +local random = math.random +local sqrt = math.sqrt +local ceil = math.ceil +local floor = math.floor + +-- this +local ScatteredResources = {} + +local function get_name_by_random(collection) + local pre_calculated = random() + local current = 0 + + for name, probability in pairs(collection) do + current = current + probability + if (current >= pre_calculated) then + return name + end + end + + Debug.print('Current \'' .. current .. '\' should be higher or equal to random \'' .. pre_calculated .. '\'') +end + +local function spawn_resource(config, surface, x, y, distance) + local resource_name = get_name_by_random(config.resource_chances) + + if (config.minimum_resource_distance[resource_name] > distance) then + return + end + + local min_max = config.resource_richness_values[get_name_by_random(config.resource_richness_probability)] + local amount = ceil(random(min_max[1], min_max[2]) * (1 + ((distance / config.distance_richness_modifier) / 100))) + + if ('crude-oil' == resource_name) then + amount = amount * config.oil_value_modifier + end + + local position = {x = x, y = y} + + Template.resources(surface, {{name = resource_name, position = position, amount = amount}}) +end + +--[[-- + Registers all event handlers. +]] +function ScatteredResources.register(config) + function sum(t) + local sum = 0 + for _, v in pairs(t) do + sum = sum + v + end + + return sum + end + + local resource_sum = sum(config.resource_chances) + if (1 ~= resource_sum) then + error('Expected a sum of 1.00, got \'' .. resource_sum .. '\' for config.feature.ScatteredResources.resource_chances.') + end + + local richness_sum = sum(config.resource_richness_probability) + if (1 ~= richness_sum) then + error('Expected a sum of 1.00, got \'' .. richness_sum .. '\' for config.feature.ScatteredResources.resource_richness_probability.') + end + + Event.add(Template.events.on_void_removed, function(event) + local x = event.old_tile.position.x + local y = event.old_tile.position.y + + local distance = floor(sqrt(x^2 + y^2)) + local calculated_probability = config.resource_probability + ((distance / config.distance_probability_modifier) / 100) + local probability = 0.7 + + if (calculated_probability < probability) then + probability = calculated_probability + end + + if (probability > random()) then + spawn_resource(config, event.surface, x, y, distance) + end + end) +end + +function ScatteredResources.get_extra_map_info(config) + return [[Scattered Resources, resources are everywhere! +Scans of the mine have shown greater amounts of resources to be deeper in the mine]] +end + +return ScatteredResources diff --git a/map_gen/Diggy/Feature/SetupPlayer.lua b/map_gen/Diggy/Feature/SetupPlayer.lua new file mode 100644 index 00000000..96ce99c0 --- /dev/null +++ b/map_gen/Diggy/Feature/SetupPlayer.lua @@ -0,0 +1,32 @@ +--[[-- info + Provides the ability to setup a player when first joined. +]] + +-- dependencies +local Event = require 'utils.event' +local Debug = require 'map_gen.Diggy.Debug' +local math_random = math.random + +-- this +local SetupPlayer = {} + +--[[-- + Registers all event handlers. +]] +function SetupPlayer.register(config) + Event.add(defines.events.on_player_created, function (event) + local player = game.players[event.player_index] + + for _, item in pairs(config.starting_items) do + player.insert(item) + end + + player.teleport({x = math_random(-4, 4) / 10, y = math_random(-4, 4) / 10}) + + Debug.cheat(function() + player.force.manual_mining_speed_modifier = config.cheats.manual_mining_speed_modifier + end) + end) +end + +return SetupPlayer diff --git a/map_gen/Diggy/Feature/SimpleRoomGenerator.lua b/map_gen/Diggy/Feature/SimpleRoomGenerator.lua new file mode 100644 index 00000000..210cd3ab --- /dev/null +++ b/map_gen/Diggy/Feature/SimpleRoomGenerator.lua @@ -0,0 +1,118 @@ +--[[-- info + Provides the ability to make a simple room with contents +]] + +-- dependencies +local Template = require 'map_gen.Diggy.Template' +local Perlin = require 'map_gen.shared.perlin_noise' +local Event = require 'utils.event' +local Debug = require'map_gen.Diggy.Debug' +local Task = require 'utils.Task' +local Token = require 'utils.global_token' +local Global = require 'utils.global' + +-- this +local SimpleRoomGenerator = {} + +local noise_used_map = {} + +Global.register({ + noise_used_map = noise_used_map, +}, function(tbl) + noise_used_map = tbl.noise_used_map +end) + +local do_spawn_tile = Token.register(function(params) + Template.insert(params.surface, {params.tile}, {}) +end) + +local do_mine = Token.register(function(params) + local sand_rocks = params.surface.find_entities_filtered({position = params.position, name = 'sand-rock-big'}) + + if (0 == #sand_rocks) then + Debug.printPosition(params.position, 'missing rock when trying to mine.') + return + end + + for _, rock in pairs(sand_rocks) do + rock.die() + end +end) + +local function handle_noise(name, surface, position) + Task.set_timeout_in_ticks(1, do_mine, {surface = surface, position = position}) + + if ('water' == name) then + -- water is slower because for some odd reason it doesn't always want to mine it properly + Task.set_timeout_in_ticks(4, do_spawn_tile, { surface = surface, tile = { name = 'deepwater-green', position = position}}) + return + end + + if ('dirt' == name) then + return + end + + error('No noise handled for type \'' .. name .. '\'') +end + +--[[-- + Registers all event handlers. +]] + +function SimpleRoomGenerator.register(config) + local room_noise_minimum_distance_sq = config.room_noise_minimum_distance * config.room_noise_minimum_distance + + local function get_noise(surface, x, y) + local seed = surface.map_gen_settings.seed + surface.index + return Perlin.noise(x * config.noise_variance, y * config.noise_variance, seed) + end + + Event.add(Template.events.on_void_removed, function (event) + local position = event.old_tile.position + local x = position.x + local y = position.y + + if (nil == noise_used_map[x]) then + noise_used_map[x] = {y = true} + elseif (nil == noise_used_map[x][y]) then + noise_used_map[x][y] = true + else + -- already used up noise at that point + return + end + + local distance_sq = position.x^2 + position.y^2 + + if (distance_sq <= room_noise_minimum_distance_sq) then + return + end + + local surface = event.surface + local noise = get_noise(surface, position.x, position.y) + + for _, noise_range in pairs(config.room_noise_ranges) do + if (noise >= noise_range.min and noise <= noise_range.max) then + handle_noise(noise_range.name, surface, position) + end + end + end) + + if (config.enable_noise_grid) then + Event.add(defines.events.on_chunk_generated, function (event) + local surface = event.surface + local area = event.area + + for x = area.left_top.x, area.left_top.x + 31 do + for y = area.left_top.y, area.left_top.y + 31 do + Debug.print_grid_value(get_noise(surface, x, y), surface, {x = x, y = y}) + end + end + end) + end +end + +function SimpleRoomGenerator.get_extra_map_info(config) + return 'Simple Room Generator, digging around might open rooms!' +end + +return SimpleRoomGenerator diff --git a/map_gen/Diggy/Feature/StartingZone.lua b/map_gen/Diggy/Feature/StartingZone.lua new file mode 100644 index 00000000..fda5b845 --- /dev/null +++ b/map_gen/Diggy/Feature/StartingZone.lua @@ -0,0 +1,83 @@ +--[[-- info + Provides the ability to create a pre-configured starting zone. +]] +-- dependencies +local Event = require 'utils.event' +local Token = require 'utils.global_token' +local Template = require 'map_gen.Diggy.Template' +local Debug = require 'map_gen.Diggy.Debug' +local DiggyCaveCollapse = require 'map_gen.Diggy.Feature.DiggyCaveCollapse' +local insert = table.insert +local random = math.random +local sqrt = math.sqrt +local floor = math.floor + +-- this +local StartingZone = {} + +--[[-- + Registers all event handlers. +]] +function StartingZone.register(config) + local callback_token + local starting_zone_size = config.starting_size + + local function on_chunk_generated(event) + local start_point_area = {{-0.9, -0.9}, {0.9, 0.9}} + local surface = event.surface + + -- hack to figure out whether the important chunks are generated via Diggy.Feature.RefreshMap. + if (4 ~= surface.count_tiles_filtered({start_point_area, name = 'lab-dark-1'})) then + return + end + + -- ensure a clean starting point + for _, entity in pairs(surface.find_entities_filtered({area = start_point_area, type = 'resource'})) do + entity.destroy() + end + + local tiles = {} + local rocks = {} + + for x = -starting_zone_size, starting_zone_size do + for y = -starting_zone_size, starting_zone_size do + local distance = floor(sqrt(x * x + y * y)) + + if (distance < starting_zone_size) then + if (distance > floor(starting_zone_size / 2)) then + insert(tiles, {name = 'dirt-' .. random(1, 7), position = {x = x, y = y}}) + else + insert(tiles, {name = 'stone-path', position = {x = x, y = y}}) + end + + if (distance > starting_zone_size - 2) then + insert(rocks, {name = 'sand-rock-big', position = {x = x, y = y}}) + end + + -- hack to avoid starting area from collapsing + if (distance > floor(starting_zone_size / 10)) then + DiggyCaveCollapse.stress_map_blur_add(surface, {x = x, y = y}, -0.3) + end + end + end + end + + Template.insert(event.surface, tiles, rocks) + + Event.remove_removable(defines.events.on_chunk_generated, callback_token) + end + + callback_token = Token.register(on_chunk_generated) + + Event.add_removable(defines.events.on_chunk_generated, callback_token) +end + +function StartingZone.on_init() + local surface = game.surfaces.nauvis + + surface.daytime = 0.5 + surface.freeze_daytime = 1 +end + + +return StartingZone diff --git a/map_gen/Diggy/Scanner.lua b/map_gen/Diggy/Scanner.lua new file mode 100644 index 00000000..03f8a646 --- /dev/null +++ b/map_gen/Diggy/Scanner.lua @@ -0,0 +1,42 @@ +-- dependencies +local insert = table.insert + +-- this +local Scanner = {} + +--[[-- + returns a list with all direct positions that contain tile_search. + + @param surface LuaSurface + @param position Position + @param tile_search string name of the tile to search for + @return table with 0~4 directions of which have the tile searched for adjacent +]] +function Scanner.scan_around_position(surface, position, tile_search) + local tile_found = {} + local get_tile = surface.get_tile + + -- north + if (tile_search == get_tile(position.x, position.y - 1).name) then + insert(tile_found, {x = position.x, y = position.y - 1}) + end + + -- east + if (tile_search == get_tile(position.x + 1, position.y).name) then + insert(tile_found, {x = position.x + 1, y = position.y}) + end + + -- south + if (tile_search == get_tile(position.x, position.y + 1).name) then + insert(tile_found, {x = position.x, y = position.y + 1}) + end + + -- west + if (tile_search == get_tile(position.x - 1, position.y).name) then + insert(tile_found, {x = position.x - 1, y = position.y}) + end + + return tile_found; +end + +return Scanner diff --git a/map_gen/Diggy/Scenario.lua b/map_gen/Diggy/Scenario.lua new file mode 100644 index 00000000..d07dbc67 --- /dev/null +++ b/map_gen/Diggy/Scenario.lua @@ -0,0 +1,91 @@ +-- dependencies +local Config = require 'map_gen.Diggy.Config' +local Debug = require 'map_gen.Diggy.Debug' +local ScenarioInfo = require 'info' +local Event = require 'utils.event' + +require 'utils.list_utils' +require 'utils.utils' + +-- this +local Scenario = {} + +global.diggy_scenario_registered = false + +--[[-- + Allows calling a callback for each enabled feature. + + Signature: callback(feature_name, Table feature_data) from {@see Config.features}. + + @param if_enabled function to be called if enabled +]] +local function each_enabled_feature(if_enabled) + local type = type(if_enabled) + if ('function' ~= type) then + error('each_enabled_feature expects callback to be a function, given type: ' .. type) + end + + for current_name, feature_data in pairs(Config.features) do + if (nil == feature_data.enabled) then + error('Feature ' .. current_name .. ' did not define the enabled property.') + end + + if (feature_data.enabled) then + if_enabled(current_name, feature_data) + end + end +end + +--[[-- + Register the events required to initialize the scenario. +]] +function Scenario.register(debug) + if global.diggy_scenario_registered then + error('Cannot register the Diggy scenario multiple times.') + return + end + + global.scenario.config.player_list.enable_coin_col = false + global.scenario.config.fish_market.enable = false + + if ('boolean' == type(debug)) then + Config.Debug = debug + end + + if (Config.debug) then + Debug.enable_debug() + end + + if (Config.cheats) then + Debug.enable_cheats() + end + + local extra_map_info = '' + + each_enabled_feature( + function(feature_name, feature_config) + local feature = require ('map_gen.Diggy.Feature.' .. feature_name) + if ('function' ~= type(feature.register)) then + error('Feature ' .. feature_name .. ' did not define a register function.') + end + + feature.register(feature_config) + + if ('function' == type(feature.get_extra_map_info)) then + extra_map_info = extra_map_info .. feature.get_extra_map_info(feature_config) .. '\n\n' + end + + if ('function' == type(feature.on_init)) then + Event.on_init(feature.on_init) + end + end + ) + + ScenarioInfo.set_map_name('Diggy') + ScenarioInfo.set_map_description('Dig your way through!') + ScenarioInfo.set_map_extra_info(extra_map_info) + + global.diggy_scenario_registered = true +end + +return Scenario diff --git a/map_gen/Diggy/Template.lua b/map_gen/Diggy/Template.lua new file mode 100644 index 00000000..7b7777a5 --- /dev/null +++ b/map_gen/Diggy/Template.lua @@ -0,0 +1,233 @@ +-- dependencies +local Task = require 'utils.Task' +local Token = require 'utils.global_token' +local Debug = require 'map_gen.Diggy.Debug' +local insert = table.insert +local min = math.min +local ceil = math.ceil +local raise_event = script.raise_event + +-- this +local Template = {} + +local tiles_per_call = 5 --how many tiles are inserted with each call of insert_action +local entities_per_call = 5 --how many entities are inserted with each call of insert_action + +Template.events = { + --[[-- + When an entity is placed via the template function. + - event.entity LuaEntity + ]] + on_placed_entity = script.generate_event_name(), + + --[[-- + Triggers when an 'out-of-map' tile is placed on something else. + + {surface, old_tile={name, position={x, y}}} + ]] + on_void_added = script.generate_event_name(), + + --[[-- + Triggers when an 'out-of-map' tile is replaced by something else. + + {surface, old_tile={name, position={x, y}}} + ]] + on_void_removed = script.generate_event_name(), +} + +local function insert_next_tiles(data) + local void_removed = {} + local void_added = {} + local surface = data.surface + local get_tile = surface.get_tile + local tiles = {} + + pcall( + function() + --use pcall to assure tile_iterator is always incremented, to avoid endless loops + for i = data.tile_iterator, min(data.tile_iterator + tiles_per_call - 1, data.tiles_n) do + local new_tile = data.tiles[i] + insert(tiles, new_tile) + local current_tile = get_tile(new_tile.position.x, new_tile.position.y) + local current_is_void = current_tile.name == 'out-of-map' + local new_is_void = new_tile.name == 'out-of-map' + + if (current_is_void and not new_is_void) then + insert( + void_removed, + {surface = surface, old_tile = {name = current_tile.name, position = current_tile.position}} + ) + end + + if (new_is_void and not current_is_void) then + insert( + void_added, + {surface = surface, old_tile = {name = current_tile.name, position = current_tile.position}} + ) + end + end + end + ) + + data.tile_iterator = data.tile_iterator + tiles_per_call + + surface.set_tiles(tiles) + + for _, event in pairs(void_removed) do + raise_event(Template.events.on_void_removed, event) + end + + for _, event in pairs(void_added) do + raise_event(Template.events.on_void_added, event) + end +end + +local function insert_next_entities(data) + local created_entities = {} + local surface = data.surface + local create_entity = surface.create_entity + + pcall( + function() + --use pcall to assure tile_iterator is always incremented, to avoid endless loops + for i = data.entity_iterator, min(data.entity_iterator + entities_per_call - 1, data.entities_n) do + local entity = data.entities[i] + local created_entity = create_entity(entity) + if (nil == created_entity) then + error('Failed creating entity ' .. entity.name .. ' on surface.') + end + + insert(created_entities, created_entity) + end + end + ) + + data.entity_iterator = data.entity_iterator + entities_per_call + + for _, entity in pairs(created_entities) do + raise_event(Template.events.on_placed_entity, {entity = entity}) + end + + return data.entity_iterator <= data.entities_n +end + +local function insert_action(data) + if data.tile_iterator <= data.tiles_n then + insert_next_tiles(data) + return true + end + + return insert_next_entities(data) +end + +local insert_token = Token.register(insert_action) + +--[[-- + Inserts a batch of tiles and then entities. + + @see LuaSurface.set_tiles + @see LuaSurface.entity + + @param surface LuaSurface to put the tiles and entities on + @param tiles table of tiles as required by set_tiles + @param entities table of entities as required by create_entity +]] +function Template.insert(surface, tiles, entities) + tiles = tiles or {} + entities = entities or {} + + local tiles_n = #tiles + local entities_n = #entities + local total_calls = ceil(tiles_n / tiles_per_call) + (entities_n / entities_per_call) + local data = { + tiles_n = tiles_n, + tile_iterator = 1, + entities_n = entities_n, + entity_iterator = 1, + surface = surface, + tiles = tiles, + entities = entities + } + + local continue = true + for i = 1, 4 do + continue = insert_action(data) + if not continue then + return + end + end + + if continue then + Task.queue_task(insert_token, data, total_calls - 4) + end +end + +--[[-- + Designed to spawn aliens, uses find_non_colliding_position. + + @see LuaSurface.entity + + @param surface LuaSurface to put the tiles and entities on + @param units table of entities as required by create_entity + @param non_colliding_distance int amount of tiles to scan around original position in case it's already taken +]] +function Template.units(surface, units, non_colliding_distance) + non_colliding_distance = non_colliding_distance or 1 + local create_entity = surface.create_entity + local find_non_colliding_position = surface.find_non_colliding_position + + for _, entity in pairs(units) do + local position = find_non_colliding_position(entity.name, entity.position, non_colliding_distance, 1) + + if (nil ~= position) then + entity.position = position + create_entity(entity) + else + Debug.printPosition(entity.position, "Failed to spawn '" .. entity.name .. "'") + end + end +end + +--[[-- + Designed to spawn resources. + + @see LuaSurface.entity + + @param surface LuaSurface to put the tiles and entities on + @param resources table of entities as required by create_entity +]] +function Template.resources(surface, resources) + local create_entity = surface.create_entity + for _, entity in pairs(resources) do + create_entity(entity) + end +end + +--[[-- + Designed to spawn a market. + + @param surface LuaSurface + @param position Position + @param force LuaForce + @param currency_item string + @param market_items Table +]] +function Template.market(surface, position, force, currency_item, market_inventory) + local market = surface.create_entity({name = 'market', position = position}) + local add_market_item = market.add_market_item + market.destructible = false + + for _, item in ipairs(market_inventory) do + add_market_item(item) + end + + force.add_chart_tag(surface, { + icon = {type = 'item', name = currency_item}, + text = ' Market', + position = position, + }) + + raise_event(Template.events.on_placed_entity, {entity = market}) +end + +return Template diff --git a/map_gen/combined/diggy.lua b/map_gen/combined/diggy.lua new file mode 100644 index 00000000..b71f5236 --- /dev/null +++ b/map_gen/combined/diggy.lua @@ -0,0 +1,2 @@ +-- authors Linaori, valansch +require 'map_gen.Diggy.Scenario'.register(_DEBUG) diff --git a/map_gen/shared/perlin_noise.lua b/map_gen/shared/perlin_noise.lua index 5045cd55..39d0d53e 100644 --- a/map_gen/shared/perlin_noise.lua +++ b/map_gen/shared/perlin_noise.lua @@ -57,7 +57,7 @@ function Perlin.noise(x, y, z) y = y or 0 z = z or 0 - -- This prevents integer inputs returning 0, which casues 'straight line' artifacts. + -- This prevents integer inputs returning 0, which causes 'straight line' artifacts. x = x - 0.55077056353912 y = y - 0.131357755512 z = z - 0.20474238274619 diff --git a/map_layout.lua b/map_layout.lua index 613dcd92..c3564fde 100644 --- a/map_layout.lua +++ b/map_layout.lua @@ -20,6 +20,7 @@ local tiles_per_tick = 32 --require "map_gen.combined.dagobah_swamp" --require "map_gen.combined.meteor_strike" --unfinished --require 'map_gen.combined.cave_miner.cave_miner' +--require "map_gen.combined.diggy" --presets--