diff --git a/features/player_stats.lua b/features/player_stats.lua index b6d1f0e70..84395f4d8 100644 --- a/features/player_stats.lua +++ b/features/player_stats.lua @@ -133,7 +133,12 @@ local function robot_built_entity() end local function biter_kill_counter(event) - if event.entity.force.name == 'enemy' then + local entity = event.entity + if not entity or not entity.valid then + return + end + + if entity.force.name == 'enemy' then change_for_global(aliens_killed_name, 1) end end diff --git a/map_gen/maps/diggy/feature/coin_gathering.lua b/map_gen/maps/diggy/feature/coin_gathering.lua index 97796a994..03b62b0e9 100644 --- a/map_gen/maps/diggy/feature/coin_gathering.lua +++ b/map_gen/maps/diggy/feature/coin_gathering.lua @@ -59,6 +59,10 @@ function CoinGathering.register(config) Event.add(defines.events.on_entity_died, function (event) local entity = event.entity + if not entity or not entity.valid then + return + end + local force = entity.force if force.name ~= 'enemy' or random() > alien_coin_drop_chance then return diff --git a/map_gen/maps/diggy_experimental.lua b/map_gen/maps/diggy_experimental.lua new file mode 100644 index 000000000..814f7bc43 --- /dev/null +++ b/map_gen/maps/diggy_experimental.lua @@ -0,0 +1 @@ +require 'map_gen.maps.diggy_experimental.scenario'.register() diff --git a/map_gen/maps/diggy_experimental/config.lua b/map_gen/maps/diggy_experimental/config.lua new file mode 100644 index 000000000..71d0dbf82 --- /dev/null +++ b/map_gen/maps/diggy_experimental/config.lua @@ -0,0 +1,449 @@ +-- dependencies +local abs = math.abs + +-- Entities that spawns as keys with values denoting the chance between 0-1. -1 means it defaults to that. Default: {['sand-rock-big'] = -1, ['rock-big'] = 0.2, ['rock-huge'] = 0.6} +-- ['tree-01'] = -1, ['tree-02'] = 0.2, ['tree-03'] = 0.6 +-- They need to be in accending order +local diggy_entities = {['sand-rock-big'] = -1, ['rock-big'] = 0.18, ['tree-01'] = 0.375, ['tree-03'] = 0.5, ['tree-05'] = 0.625, ['tree-07'] = 0.75, ['tree-08'] = 0.875} + +-- this +local Config = { + -- a list of features to register and enable + -- to disable a feature, change the flag + features = { + -- creates a starting zone + starting_zone = { + enabled = true, + -- initial starting position size, higher values are not recommended + starting_size = 8, + -- where the market should spawn + market_spawn_position = {x = 0, y = 3}, + -- the entity the spawn is surrounded by, has to match one entity defined in diggy_entities (default: rock-big) + start_entity = 'tree-07', + -- the tile the spawn is made off + start_tile = 'grass' -- grass or dirt + }, + -- controls the Daylight (Default diggy: enabled = true) + night_time = { + enabled = false -- true = No Daylight, false = Day/night circle (Solar panels work) + }, + -- controls setting up the players + setup_player = { + enabled = true, + starting_items = { + {name = 'stone-wall', count = 12}, + {name = 'iron-gear-wheel', count = 8}, + {name = 'iron-plate', count = 16} + }, + + -- 0.01 bonus equals 1% in game. This value is recommended to be tweaked for single player + initial_mining_speed_bonus = 1, + + -- applied when _CHEATS is set to true and _DEBUG is NOT true. + -- see config.lua -> config.player_create.cheats for available options + cheats = { + enabled = true, + -- Sets the manual mining speed for the player force. A value of 1 = 100% faster. Setting it + -- to 0.5 would make it 50% faster than the base speed. + manual_mining_speed_modifier = 1000, + -- increase the amount of inventory slots for the player force + character_inventory_slots_bonus = 0, + -- increases the run speed of all characters for the player force + character_running_speed_modifier = 2, + -- a flat health bonus to the player force + character_health_bonus = 1000000, + -- starts with a fully slotted power armor mk2 + start_with_power_armor = true, + -- adds additional items to the player force when starting in addition to defined in start_items above + starting_items = {} + } + }, + -- core feature + diggy_hole = { + enabled = true, + -- initial damage per tick it damages a rock to mine, can be enhanced by robot_damage_per_mining_prod_level + robot_initial_mining_damage = 4, + -- damage added per level of mining productivity level research + robot_damage_per_mining_prod_level = 1, + + -- turn this setting on if you want to bring back landfill research, default is off due to griefing + allow_landfill_research = false, + + diggy_entities = diggy_entities, + + diggy_tile = 'grass' -- grass or dirt + }, + -- adds the ability to collapse caves + diggy_cave_collapse = { + enabled = false, + -- 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'] = 9, + ['stone-wall'] = 3, + ['sand-rock-big'] = 2, + ['rock-big'] = 2, + ['rock-huge'] = 2.5, + ['out-of-map'] = 1, + ['stone-path'] = 0.03, + ['concrete'] = 0.04, + ['hazard-concrete'] = 0.04, + ['refined-concrete'] = 0.06, + ['refined-hazard-concrete'] = 0.06 + }, + cracking_sounds = { + {'diggy.cracking_sound_1'}, + {'diggy.cracking_sound_2'} + }, + -- the entity that spawns on collapse, has to match one entity defined in diggy_entities (default: rock-big) + collapse_entity = 'rock-big', + -- the item that is used as icon for the collapse alert + collapse_item = 'stone' + }, + -- Adds the ability to drop coins and track how many are sent into space + coin_gathering = { + enabled = true, + -- value between 0 and 1, higher value means stronger variance between coordinates + noise_variance = 0.75, + -- minimum noise value to spawn a treasure chest, works best with a very high noise variance, + -- otherwise you risk spawning a lot of chests together + treasure_chest_noise_threshold = 0.69, + -- minimum distance from spawn where a chest can spawn + minimal_treasure_chest_distance = 25, + -- chances to receive a coin when mining + mining_coin_chance = 0.15, + mining_coin_amount = {min = 1, max = 5}, + -- lets you set the coin modifiers for aliens + -- the modifier value increases the upper random limit that biters can drop + alien_coin_modifiers = { + ['small-biter'] = 2, + ['small-spitter'] = 2, + ['small-worm-turret'] = 2, + ['medium-biter'] = 3, + ['medium-spitter'] = 3, + ['medium-worm-turret'] = 3, + ['big-biter'] = 5, + ['big-spitter'] = 5, + ['big-worm-turret'] = 5, + ['behemoth-biter'] = 7, + ['behemoth-spitter'] = 7 + }, + -- chance of aliens dropping coins between 0 and 1, where 1 is 100% + alien_coin_drop_chance = 0.28, + -- shows the chest locations, only use when debugging + display_chest_locations = false, + treasure_chest_raffle = { + ['coin'] = {chance = 1.00, min = 20, max = 255}, + ['stone'] = {chance = 0.20, min = 15, max = 40}, + ['copper-ore'] = {chance = 0.25, min = 30, max = 60}, + ['copper-plate'] = {chance = 0.10, min = 12, max = 25}, + ['iron-ore'] = {chance = 0.20, min = 10, max = 55}, + ['iron-plate'] = {chance = 0.10, min = 5, max = 25}, + ['steel-plate'] = {chance = 0.05, min = 3, max = 14}, + ['steel-furnace'] = {chance = 0.03, min = 1, max = 2}, + ['steam-engine'] = {chance = 0.03, min = 1, max = 2}, + ['coal'] = {chance = 0.30, min = 30, max = 55}, + ['concrete'] = {chance = 0.14, min = 10, max = 50}, + ['stone-brick'] = {chance = 0.14, min = 25, max = 75}, + ['stone-wall'] = {chance = 0.50, min = 1, max = 5}, + ['transport-belt'] = {chance = 0.10, min = 1, max = 5}, + ['fast-transport-belt'] = {chance = 0.07, min = 2, max = 7}, + ['express-transport-belt'] = {chance = 0.04, min = 4, max = 9}, + ['rail'] = {chance = 0.20, min = 7, max = 15}, + ['rail-signal'] = {chance = 0.05, min = 3, max = 8}, + ['rail-chain-signal'] = {chance = 0.05, min = 3, max = 8}, + ['firearm-magazine'] = {chance = 0.25, min = 35, max = 120}, + ['piercing-rounds-magazine'] = {chance = 0.10, min = 15, max = 35}, + ['gun-turret'] = {chance = 0.3, min = 1, max = 2}, + ['beacon'] = {chance = 0.01, min = 1, max = 2}, + ['effectivity-module'] = {chance = 0.03, min = 1, max = 2}, + ['effectivity-module-2'] = {chance = 0.01, min = 1, max = 2}, + ['productivity-module'] = {chance = 0.03, min = 1, max = 2}, + ['productivity-module-2'] = {chance = 0.01, min = 1, max = 2}, + ['speed-module'] = {chance = 0.03, min = 1, max = 2}, + ['speed-module-2'] = {chance = 0.01, min = 1, max = 2}, + ['small-lamp'] = {chance = 0.05, min = 1, max = 5} + } + }, + -- replaces the chunks with void + refresh_map = { + enabled = true + }, + -- automatically opens areas + simple_room_generator = { + enabled = true, + -- value between 0 and 1, higher value means stronger variance between coordinates + noise_variance = 0.066, + -- shows where rooms are located + display_room_locations = false, + -- minimum distance and noise range required for water to spawn + room_noise_minimum_distance = 9, + room_noise_ranges = { + {name = 'water', min = 0.84, max = 0.96}, + {name = 'water', min = 0.73, max = 0.81}, + {name = 'water', min = 0.54, max = 0.7}, + {name = 'dirt', min = 0.46, max = 0.53}, + {name = 'dirt', min = 0.37, max = 0.45} + }, + water = 'water' -- The water that spawns when a room opens (Diggy default: deepwater-green) + }, + -- responsible for resource spawning + scattered_resources = { + enabled = true, + -- determines how distance is measured + distance = function(x, y) + return abs(x) + abs(y) + end, + --distance = function (x, y) return math.sqrt(x * x + y * y) end, + + -- defines the weights of which resource_richness_value to spawn + resource_richness_weights = { + ['scarce'] = 440, + ['low'] = 350, + ['sufficient'] = 164, + ['good'] = 30, + ['plenty'] = 10, + ['jackpot'] = 6 + }, + -- 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} + }, + -- increases the amount of resources by flat multiplication to initial amount + -- highly suggested to use for fluids so their yield is reasonable + resource_type_scalar = { + ['crude-oil'] = 1500, + ['uranium-ore'] = 1.25 + }, + -- ============== + -- Debug settings + -- ============== + + -- shows the ore locations, only use when debugging (compound_cluster_mode) + display_ore_clusters = false, + -- ======================= + -- Scattered mode settings + -- ======================= + + -- creates scattered ore (single tiles) at random locations + scattered_mode = false, + -- defines the increased chance of spawning resources + -- calculated_probability = resource_probability + ((distance / scattered_distance_probability_modifier) / 100) + -- this means the chance increases by 1% every DISTANCE tiles up to the max_probability + scattered_distance_probability_modifier = 10, + -- min percentage of chance that resources will spawn after mining + scattered_min_probability = 0.01, + -- max chance of spawning resources based on resource_probability + calculated scattered_distance_probability_modifier + scattered_max_probability = 0.10, + -- 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 + scattered_distance_richness_modifier = 7, + -- multiplies probability only if cluster mode is enabled + scattered_cluster_probability_multiplier = 0.5, + -- multiplies yield only if cluster mode is enabled + scattered_cluster_yield_multiplier = 1.7, + -- weights per resource of spawning + scattered_resource_weights = { + ['coal'] = 160, + ['copper-ore'] = 215, + ['iron-ore'] = 389, + ['stone'] = 212, + ['uranium-ore'] = 21, + ['crude-oil'] = 3 + }, + -- minimum distance from the spawn point required before it spawns + scattered_minimum_resource_distance = { + ['coal'] = 16, + ['copper-ore'] = 18, + ['iron-ore'] = 18, + ['stone'] = 15, + ['uranium-ore'] = 86, + ['crude-oil'] = 57 + }, + -- ============================== + -- Compound cluster mode settings + -- ============================== + + -- creates compound clusters of ores defined by a layered ore-gen + cluster_mode = true, + -- spawns tendrils of ore with roughly 80% purity + ore_pattern = require 'map_gen.maps.diggy.orepattern.tendrils_impure' + + -- spawns some smaller dedicated and bigger mixed tendrils + --ore_pattern = require 'map_gen.maps.diggy.orepattern.tendrils', + + -- spawns clusters of ore similar to vanilla, but mixed + --ore_pattern = require 'map_gen.maps.diggy.orepattern.clusters', + }, + -- controls the alien spawning mechanic + alien_spawner = { + enabled = true, + + -- minimum distance from spawn before aliens can spawn + alien_minimum_distance = 40, + + -- chance of spawning aliens when mining from 0 to 1 + alien_probability = 0.05, + + -- each tile of void removed increases alien evolution by + evolution_per_void_removed = 0.0000024, + + -- initial evolution percentage, recommended to set to 0 for non-multiplayer setups + initial_evolution = 10, + + -- evolution over time value, leave nil to use vanilla settings + evolution_over_time_factor = 0.000008, + + -- spawns the following units when they die. To disable, remove the contents + -- any non-rounded number will turn into a chance to spawn an additional alien + -- example: 2.5 would spawn 2 for sure and 50% chance to spawn one additionally + hail_hydra = { + -- spitters + ['small-spitter'] = {['small-worm-turret'] = {min = 0.1, max = 0.8}}, + ['medium-spitter'] = {['medium-worm-turret'] = {min = 0.1, max = 0.8}}, + ['big-spitter'] = {['big-worm-turret'] = {min = 0.1, max = 0.8}}, + ['behemoth-spitter'] = {['behemoth-worm-turret'] = {min = 0.2, max = 0.8}}, + -- biters + ['medium-biter'] = {['small-biter'] = {min = 0.6, max = 1.5}}, + ['big-biter'] = {['medium-biter'] = {min = 0.6, max = 1.5}}, + ['behemoth-biter'] = {['big-biter'] = {min = 0.6, max = 2}}, + -- worms + ['small-worm-turret'] = {['small-biter'] = {min = 1, max = 2.5}}, + ['medium-worm-turret'] = { + ['small-biter'] = {min = 1, max = 2.5}, + ['medium-biter'] = {min = 0.3, max = 1.5} + }, + ['big-worm-turret'] = { + ['small-biter'] = {min = 1, max = 2.5}, + ['medium-biter'] = {min = 0.7, max = 1.5}, + ['big-biter'] = {min = 0.7, max = 2} + }, + ['behemoth-worm-turret'] = { + ['small-biter'] = {min = 1.5, max = 3}, + ['medium-biter'] = {min = 1.2, max = 2}, + ['big-biter'] = {min = 1, max = 2}, + ['behemoth-biter'] = {min = 0.7, max = 1.2} + } + } + }, + --Tracks players causing collapses + antigrief = { + enabled = true, + autojail = true, + allowed_collapses_first_hour = 4 + }, + experience = { + enabled = true, + -- controls the formula for calculating level up costs in stone sent to surface + difficulty_scale = 20, -- Diggy default 15. Higher increases experience requirement climb + first_lvl_xp = 350, -- Diggy default 350. This sets the price for the first level. + xp_fine_tune = 400, -- Diggy default 200. This value is used to fine tune the overall requirement climb without affecting the speed + cost_precision = 3, -- Diggy default 3. This sets the precision of the required experience to level up. E.g. 1234 becomes 1200 with precision 2 and 1230 with precision 3. + -- percentage * mining productivity level gets added to mining speed + mining_speed_productivity_multiplier = 5, + XP = { + ['common'] = 5, -- base XP rewarded for all types of diggy entities unless otherwise specified here (Diggy default: 5) + ['rocket_launch'] = 0.05, -- XP reward in percentage of total experience when a rocket launches (Diggy default: 0.05 which equals 5%) + ['rocket_launch_max'] = 500000, -- Max XP reward from rocket launches (Diggy default: 500000) + ['automation-science-pack'] = 4, + ['logistic-science-pack'] = 8, + ['chemical-science-pack'] = 15, + ['military-science-pack'] = 12, + ['production-science-pack'] = 25, + ['utility-science-pack'] = 50, + ['space-science-pack'] = 10, + ['enemy_killed'] = 10, -- Base XP for killing biters and spitters. + ['death-penalty'] = 0.0035, -- XP deduct in percentage of total experience when a player dies (Diggy default: 0.0035 which equals 0.35%) + --['cave-in-penalty'] = 100 -- XP lost every cave in. + ['infinity-research'] = 0.60 -- XP reward in percentage of the required experience from current level to next level (Diggy default: 0.60 which equals 60%) + }, + buffs = { + -- define new buffs here, they are handed out for each level + mining_speed = {value = 5, max = 10}, + inventory_slot = {value = 1, max = 100}, + -- double_level is the level interval for receiving a double bonus (Diggy default: 5 which equals every 5th level) + health_bonus = {value = 2.5, double_level = 5, max = 500} + }, + -- add or remove a table entry to add or remove a unlockable item from the market. + unlockables = { + {level = 2, price = 4, name = 'wood'}, + {level = 3, price = 5, name = 'stone-wall'}, + {level = 4, price = 20, name = 'pistol'}, + {level = 4, price = 5, name = 'firearm-magazine'}, + {level = 5, price = 100, name = 'light-armor'}, + {level = 6, price = 6, name = 'small-lamp'}, + {level = 6, price = 5, name = 'raw-fish'}, + {level = 8, price = 1, name = 'stone-brick'}, + {level = 10, price = 85, name = 'shotgun'}, + {level = 10, price = 4, name = 'shotgun-shell'}, + {level = 12, price = 200, name = 'heavy-armor'}, + {level = 14, price = 35, name = 'landfill'}, + {level = 15, price = 85, name = 'submachine-gun'}, + {level = 18, price = 10, name = 'piercing-rounds-magazine'}, + {level = 18, price = 8, name = 'piercing-shotgun-shell'}, + {level = 19, price = 2, name = 'rail'}, + {level = 20, price = 50, name = 'locomotive'}, + {level = 20, price = 350, name = 'modular-armor'}, + {level = 21, price = 5, name = 'rail-signal'}, + {level = 22, price = 5, name = 'rail-chain-signal'}, + {level = 23, price = 15, name = 'train-stop'}, + {level = 24, price = 35, name = 'cargo-wagon'}, + {level = 24, price = 35, name = 'fluid-wagon'}, + {level = 29, price = 750, name = 'power-armor'}, + {level = 30, price = 30, name = 'logistic-robot'}, + {level = 31, price = 200, name = 'personal-roboport-equipment'}, + {level = 32, price = 20, name = 'construction-robot'}, + {level = 34, price = 750, name = 'fusion-reactor-equipment'}, + {level = 35, price = 150, name = 'battery-equipment'}, + {level = 38, price = 250, name = 'exoskeleton-equipment'}, + {level = 40, price = 125, name = 'energy-shield-equipment'}, + {level = 42, price = 500, name = 'personal-laser-defense-equipment'}, + {level = 44, price = 1250, name = 'power-armor-mk2'}, + {level = 46, price = 750, name = 'battery-mk2-equipment'}, + {level = 48, price = 550, name = 'combat-shotgun'}, + {level = 51, price = 25, name = 'uranium-rounds-magazine'}, + {level = 63, price = 250, name = 'rocket-launcher'}, + {level = 63, price = 40, name = 'rocket'}, + {level = 71, price = 80, name = 'explosive-rocket'}, + {level = 78, price = 1000, name = 'satellite'}, + {level = 100, price = 1, name = 'iron-stick'} + }, + -- modifies the experience per alien type, higher is more xp + alien_experience_modifiers = { + ['small-biter'] = 2, + ['small-spitter'] = 2, + ['small-worm-turret'] = 2, + ['medium-biter'] = 3, + ['medium-spitter'] = 3, + ['medium-worm-turret'] = 3, + ['big-biter'] = 5, + ['big-spitter'] = 5, + ['big-worm-turret'] = 5, + ['behemoth-biter'] = 7, + ['behemoth-spitter'] = 7, + ['behemoth-worm-turret'] = 7 + } + }, + weapon_balance = { + enabled = true + } + } +} + +return Config diff --git a/map_gen/maps/diggy_experimental/feature/antigrief.lua b/map_gen/maps/diggy_experimental/feature/antigrief.lua new file mode 100644 index 000000000..17021ed69 --- /dev/null +++ b/map_gen/maps/diggy_experimental/feature/antigrief.lua @@ -0,0 +1,56 @@ +-- dependencies +local Event = require 'utils.event' +local Global = require 'utils.global' +local CaveCollapse = require 'map_gen.maps.diggy_experimental.feature.diggy_cave_collapse' +local Report = require 'features.report' +local format = string.format + +-- this +local Antigrief = {} + +local global_primitives = {} + +local allowed_collapses_first_hour = 0 + +local player_collapses = {} +local jailed_players = {} + +Global.register({ + player_collapses = player_collapses, + jailed_players = jailed_players, + global_primitives = global_primitives, +}, function(tbl) + player_collapses = tbl.player_collapses + jailed_players = tbl.jailed_players + global_primitives = tbl.global_primitives +end) + +global_primitives.autojail = false +global_primitives.last_collapse = 0 + +--[[-- + Registers all event handlers. +]] +function Antigrief.register(config) + global_primitives.autojail = config.autojail + allowed_collapses_first_hour = config.allowed_collapses_first_hour +end + + +Event.add(CaveCollapse.events.on_collapse, function(event) + local player_index = event.player_index + if player_index and global_primitives.last_collapse ~= game.tick then + global_primitives.last_collapse = game.tick + local count = player_collapses[player_index] or 0 + count = count + 1 + player_collapses[player_index] = count + local player = game.get_player(player_index) + if global_primitives.autojail and count > allowed_collapses_first_hour and player.online_time < 216000 and not jailed_players[player_index] then + Report.jail(player) + Report.report(nil, player, format('Caused %d collapses in the first hour', count)) + jailed_players[player_index] = true + end + end +end) + +return Antigrief diff --git a/map_gen/maps/diggy_experimental/feature/diggy_cave_collapse.lua b/map_gen/maps/diggy_experimental/feature/diggy_cave_collapse.lua new file mode 100644 index 000000000..f62c12617 --- /dev/null +++ b/map_gen/maps/diggy_experimental/feature/diggy_cave_collapse.lua @@ -0,0 +1,672 @@ +--[[-- info + Provides the ability to collapse caves when digging. +]] +-- dependencies +local Event = require 'utils.event' +local Template = require 'map_gen.maps.diggy_experimental.template' +local ScoreTracker = require 'utils.score_tracker' +local Debug = require 'map_gen.maps.diggy.debug' +local Task = require 'utils.task' +local Token = require 'utils.token' +local Global = require 'utils.global' +local CreateParticles = require 'features.create_particles' +local RS = require 'map_gen.shared.redmew_surface' +local table = require 'utils.table' +local random = math.random +local floor = math.floor +local pairs = pairs +local pcall = pcall +local is_diggy_rock = Template.is_diggy_rock +local template_insert = Template.insert +local raise_event = script.raise_event +local set_timeout = Task.set_timeout +local set_timeout_in_ticks = Task.set_timeout_in_ticks +local ceiling_crumble = CreateParticles.ceiling_crumble +local clear_table = table.clear_table +local collapse_rocks = {} +for k, _ in pairs(Template.diggy_rocks) do + table.insert(collapse_rocks, k) +end + +local collapse_rocks_size = #collapse_rocks +local cave_collapses_name = 'cave-collapses' + +-- this +local DiggyCaveCollapse = {} + +local config +local collapse_entity + +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 mask_init +local stress_map_check_stress_in_threshold +local support_beam_entities +local on_surface_created + +local stress_threshold_causing_collapse = 3.57 +local near_stress_threshold_causing_collapse = 3.3 -- just above the threshold of a normal 4 pillar grid + +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 +local collapse_alert = {type = 'item', name = 'stone'} + +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 = Event.generate_event_name('on_collapse_triggered'), + --[[-- + After a collapse + - position LuaPosition + - surface LuaSurface + - player_index Number (index of player that caused the collapse) + ]] + on_collapse = Event.generate_event_name('on_collapse') +} + +local function create_collapse_template(positions, surface) + local entities = {} + local entity_count = 0 + 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 + + for _, entity in pairs(find_entities_filtered({area = {position, {x + 1, y + 1}}})) do + pcall( + function() + local strength = support_beam_entities[entity.name] + if strength then + do_insert = false + else + entity.die() + end + end + ) + end + + if do_insert then + entity_count = entity_count + 1 + entities[entity_count] = {position = position, name = collapse_rocks[random(collapse_rocks_size)]} + end + end + + return entities +end + +local function create_collapse_alert(surface, position) + local target = surface.create_entity({position = position, name = collapse_entity}) + for _, player in pairs(game.connected_players) do + player.add_custom_alert(target, collapse_alert, {'diggy.cave_collapse'}, true) + end + target.destroy() +end + +local function collapse(args) + local position = args.position + local surface = args.surface + local positions = {} + local count = 0 + 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, + 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 + + create_collapse_alert(surface, position) + + template_insert(surface, {}, create_collapse_template(positions, surface)) + + raise_event(DiggyCaveCollapse.events.on_collapse, args) + ScoreTracker.change_for_global(cave_collapses_name, 1) +end + +local on_collapse_timeout_finished = Token.register(collapse) +local on_near_threshold = + Token.register( + function(params) + ceiling_crumble(params.surface, params.position) + end +) + +local function spawn_collapse_text(surface, position) + local color = { + r = 1, + g = random(1, 100) * 0.01, + b = 0 + } + + surface.create_entity( + { + name = 'tutorial-flying-text', + color = color, + text = config.cracking_sounds[random(#config.cracking_sounds)], + position = position + } + ) +end + +local function on_collapse_triggered(event) + 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 = collapse_entity}}) + return + end + spawn_collapse_text(surface, position) + 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 + if not entity or not entity.valid then + return + end + + local name = entity.name + local strength = support_beam_entities[name] + if strength then + local player_index + if not is_diggy_rock(name) then + player_index = event.player_index + end + stress_map_add(entity.surface, entity.position, strength, false, player_index) + end +end + +local function on_entity_died(event) + local entity = event.entity + if not entity or not entity.valid then + return + end + + local name = entity.name + local strength = support_beam_entities[name] + if strength then + local player_index + if not is_diggy_rock(name) then + local cause = event.cause + player_index = cause and cause.type == 'character' and cause.player and cause.player.index or nil + end + stress_map_add(entity.surface, entity.position, strength, false, player_index) + 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 + 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) + ScoreTracker.register(cave_collapses_name, {'diggy.score_cave_collapses'}, '[img=entity.assembler-wreck]') + + local global_to_show = global.config.score.global_to_show + global_to_show[#global_to_show + 1] = cave_collapses_name + + config = cfg + collapse_entity = cfg.collapse_entity + support_beam_entities = config.support_beam_entities + collapse_alert = {type = 'item', name = cfg.collapse_item} + + 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 + + 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.tile, 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) + local entity = event.entity + local name = entity.name + if is_diggy_rock(name) then + return + end + + if name == 'deconstructible-tile-proxy' or nil ~= support_beam_entities[name] then + entity.cancel_deconstruction(game.get_player(event.player_index).force) + 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(player_index), {'diggy.cave_collapse_warning'}) + 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 = RS.get_surface() + mask_disc_blur( + 0, + 0, + 10, + function(x, y, fraction) + Debug.print_grid_value(fraction, surface, {x = x, y = y}) + 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 +]] +---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) + 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 + + 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 + set_timeout_in_ticks(2, on_near_threshold, {surface = surface, position = {x = x, y = y}}) + end + end + if enable_stress_grid then + Debug.print_colored_grid_value(value, surface, {x = x, y = y}, 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) + local index = event.surface_index + + if stress_map_storage[index] then + clear_table(stress_map_storage[index]) + else + stress_map_storage[index] = {} + end + + local map = stress_map_storage[index] + + map['surface_index'] = 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 x number +---@param y number +---@param threshold number +---@param callback function +stress_map_check_stress_in_threshold = function(surface, x, y, threshold, callback) + local stress_map = stress_map_storage[surface.index] + local value = add_fraction(stress_map, x, y, 0, nil, surface) + + if (value >= stress_threshold_causing_collapse - threshold) then + callback(surface, x, y) + 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 + add_fraction(stress_map, x_start, y_start, factor, player_index, surface) + 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 + if value > 0.001 or value < -0.001 then + add_fraction(stress_map, x + x_start, y + y_start, value * factor, player_index, surface) + end + end + end +end + +DiggyCaveCollapse.stress_map_add = stress_map_add + +-- +-- MASK +-- + +mask_init = function(config) -- luacheck: ignore 431 (intentional upvalue shadow) + n = config.mask_size + local ring_weights = config.mask_relative_ring_weights + + ring_weight = ring_weights[1] + disc_weight = ring_weights[2] + center_weight = 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 value > 0.001 or value < -0.001 then + callback(x_start + x, y_start + y, value * factor) + end + end + end +end + +function DiggyCaveCollapse.get_extra_map_info() + return [[Cave Collapse, it might just collapse! +Place stone walls, stone paths and (refined) concrete to reinforce the mine. If you see cracks appear, run!]] +end + +Event.on_init( + function() + if global.config.redmew_surface.enabled then + on_surface_created({surface_index = RS.get_surface().index}) + end + end +) + +return DiggyCaveCollapse diff --git a/map_gen/maps/diggy_experimental/feature/diggy_hole.lua b/map_gen/maps/diggy_experimental/feature/diggy_hole.lua new file mode 100644 index 000000000..c7a77b25c --- /dev/null +++ b/map_gen/maps/diggy_experimental/feature/diggy_hole.lua @@ -0,0 +1,305 @@ +--[[-- 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 Global = require 'utils.global' +local Template = require 'map_gen.maps.diggy_experimental.template' +local ScoreTracker = require 'utils.score_tracker' +local Command = require 'utils.command' +local CreateParticles = require 'features.create_particles' +local Ranks = require 'resources.ranks' +local random = math.random +local tonumber = tonumber +local pairs = pairs +local is_diggy_rock = Template.is_diggy_rock +local destroy_rock = CreateParticles.destroy_rock +local mine_rock = CreateParticles.mine_rock +local raise_event = script.raise_event +local mine_size_name = 'mine-size' + +-- this +local DiggyHole = {} +local config +local diggy_entities +local diggy_tile + +-- keeps track of the amount of times per player when they mined with a full inventory in a row +local full_inventory_mining_cache = {} + +-- keeps track of the buffs for the bot mining mining_efficiency +local robot_mining = { + damage = 0, + active_modifier = 0, + research_modifier = 0 +} + +Global.register( + { + full_inventory_mining_cache = full_inventory_mining_cache, + bot_mining_damage = robot_mining + }, + function(tbl) + full_inventory_mining_cache = tbl.full_inventory_mining_cache + robot_mining = tbl.bot_mining_damage + end +) + +local function update_robot_mining_damage() + -- remove the current buff + local old_modifier = robot_mining.damage - robot_mining.active_modifier + + -- update the active modifier + robot_mining.active_modifier = robot_mining.research_modifier + + -- add the new active modifier to the non-buffed modifier + robot_mining.damage = old_modifier + robot_mining.active_modifier +end + +---Triggers a diggy diggy hole for a given sand-rock-big, rock-big or rock-huge. +---@param entity LuaEntity +local function diggy_hole(entity) + local tiles = {} + local rocks = {} + local surface = entity.surface + local position = entity.position + local x = position.x + local y = position.y + local get_tile = surface.get_tile + local out_of_map_found = {} + local count = 0 + + if (get_tile(x, y - 1).name == 'out-of-map') then + count = count + 1 + out_of_map_found[count] = {x = x, y = y - 1} + end + + if (get_tile(x + 1, y).name == 'out-of-map') then + count = count + 1 + out_of_map_found[count] = {x = x + 1, y = y} + end + + if (get_tile(x, y + 1).name == 'out-of-map') then + count = count + 1 + out_of_map_found[count] = {x = x, y = y + 1} + end + + if (get_tile(x - 1, y).name == 'out-of-map') then + out_of_map_found[count + 1] = {x = x - 1, y = y} + end + + for i = #out_of_map_found, 1, -1 do + local void_position = out_of_map_found[i] + if diggy_tile == 'grass' then + tiles[i] = {name = 'grass-' .. random(1, 4), position = void_position} + else + tiles[i] = {name = 'dirt-' .. random(1, 7), position = void_position} + end + local predicted = random() + for entity_name, chance in pairs(diggy_entities) do + if predicted > chance then + rocks[i] = {name = entity_name, position = void_position} + end + end + 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 = {} + local count = 0 + for _, tile in pairs(tiles) do + if (artificial_tiles[tile.old_tile.name]) then + count = count + 1 + new_tiles[count] = {name = 'dirt-' .. random(1, 7), position = tile.position} + end + end + + Template.insert(surface, new_tiles, {}) +end +Command.add( + 'diggy-clear-void', + { + description = {'command_description.diggy_clear_void'}, + arguments = {'left_top_x', 'left_top_y', 'width', 'height', 'surface_index'}, + debug_only = true, + required_rank = Ranks.admin + }, + function(arguments) + local left_top_x = tonumber(arguments.left_top_x) + local left_top_y = tonumber(arguments.left_top_y) + local width = tonumber(arguments.width) + local height = tonumber(arguments.height) + local tiles = {} + local count = 0 + for x = 0, width do + for y = 0, height do + count = count + 1 + tiles[count] = {name = 'dirt-' .. random(1, 7), position = {x = x + left_top_x, y = y + left_top_y}} + end + end + + Template.insert(game.surfaces[arguments.surface_index], tiles, {}) + end +) + +--[[-- + Registers all event handlers. +]] +function DiggyHole.register(cfg) + ScoreTracker.register(mine_size_name, {'diggy.score_mine_size'}, '[img=tile.out-of-map]') + + local global_to_show = global.config.score.global_to_show + global_to_show[#global_to_show + 1] = mine_size_name + + config = cfg + diggy_entities = cfg.diggy_entities + diggy_tile = cfg.diggy_tile + robot_mining.damage = cfg.robot_initial_mining_damage + + Event.add( + defines.events.on_entity_died, + function(event) + local entity = event.entity + if not entity or not entity.valid then + return + end + + local name = entity.name + if not is_diggy_rock(name) then + return + end + if event.cause then + destroy_rock(entity.surface.create_entity, 10, entity.position) + end + diggy_hole(entity) + end + ) + + Event.add( + defines.events.on_entity_damaged, + function(event) + local entity = event.entity + local name = entity.name + + if entity.health ~= 0 then + return + end + + if not is_diggy_rock(name) then + return + end + + raise_event(defines.events.on_entity_died, {entity = entity, cause = event.cause, force = event.force}) + entity.destroy() + end + ) + + Event.add( + defines.events.on_robot_mined_entity, + function(event) + local entity = event.entity + local name = entity.name + + if not is_diggy_rock(name) then + return + end + + local health = entity.health + health = health - robot_mining.damage + event.buffer.clear() + + local graphics_variation = entity.graphics_variation + local create_entity = entity.surface.create_entity + local position = entity.position + local force = event.robot.force + + if health < 1 then + raise_event(defines.events.on_entity_died, {entity = entity, force = force}) + mine_rock(create_entity, 6, position) + entity.destroy() + return + end + entity.destroy() + + local rock = create_entity({name = name, position = position}) + mine_rock(create_entity, 1, position) + rock.graphics_variation = graphics_variation + rock.order_deconstruction(force) + rock.health = health + end + ) + + Event.add( + defines.events.on_player_mined_entity, + function(event) + local entity = event.entity + local name = entity.name + if not is_diggy_rock(name) then + return + end + + event.buffer.clear() + + mine_rock(entity.surface.create_entity, 6, entity.position) + diggy_hole(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( + Template.events.on_void_removed, + function() + ScoreTracker.change_for_global(mine_size_name, 1) + end + ) + + local robot_damage_per_mining_prod_level = cfg.robot_damage_per_mining_prod_level + Event.add( + defines.events.on_research_finished, + function(event) + local new_modifier = event.research.force.mining_drill_productivity_bonus * 50 * robot_damage_per_mining_prod_level + + if (robot_mining.research_modifier == new_modifier) then + -- something else was researched + return + end + + robot_mining.research_modifier = new_modifier + update_robot_mining_damage() + end + ) +end + +function DiggyHole.on_init() + game.forces.player.technologies['landfill'].enabled = config.allow_landfill_research + game.forces.player.technologies['atomic-bomb'].enabled = false +end + +return DiggyHole diff --git a/map_gen/maps/diggy_experimental/feature/experience.lua b/map_gen/maps/diggy_experimental/feature/experience.lua new file mode 100644 index 000000000..be169aa01 --- /dev/null +++ b/map_gen/maps/diggy_experimental/feature/experience.lua @@ -0,0 +1,629 @@ +-- dependencies +local Event = require 'utils.event' +local Game = require 'utils.game' +local Global = require 'utils.global' +local Toast = require 'features.gui.toast' +local Template = require 'map_gen.maps.diggy_experimental.template' +local ForceControl = require 'features.force_control' +local ScoreTracker = require 'utils.score_tracker' +local Retailer = require 'features.retailer' +local Gui = require 'utils.gui' +local Utils = require 'utils.core' +local Color = require 'resources.color_presets' +local floor = math.floor +local log = math.log +local max = math.max +local insert = table.insert +local pairs = pairs +local add_experience = ForceControl.add_experience +local add_experience_percentage = ForceControl.add_experience_percentage +local remove_experience_percentage = ForceControl.remove_experience_percentage +local print_player_floating_text_position = Game.print_player_floating_text_position +local get_force_data = ForceControl.get_force_data +local set_item = Retailer.set_item +local disable_item = Retailer.disable_item +local enable_item = Retailer.enable_item +local experience_lost_name = 'experience-lost' +local is_diggy_rock = Template.is_diggy_rock + +-- this +local Experience = {} + +local mining_efficiency = { + active_modifier = 0, + research_modifier = 0, + level_modifier = 0 +} + +local inventory_slots = { + active_modifier = 0, + research_modifier = 0, + level_modifier = 0 +} + +local health_bonus = { + active_modifier = 0, + research_modifier = 0, + level_modifier = 0 +} + +Global.register( + { + mining_efficiency = mining_efficiency, + inventory_slots = inventory_slots, + health_bonus = health_bonus + }, + function(tbl) + mining_efficiency = tbl.mining_efficiency + inventory_slots = tbl.inventory_slots + health_bonus = tbl.health_bonus + end +) + +local config + +local gain_xp_color = Color.light_sky_blue +local lose_xp_color = Color.red +local unlocked_color = Color.black +local locked_color = Color.gray +local table_column_layout = {type = 'table', column_count = 3} + +local level_up_formula = (function(level_reached) + local difficulty_scale = floor(config.difficulty_scale) + local level_fine_tune = floor(config.xp_fine_tune) + local start_value = (floor(config.first_lvl_xp)) + local precision = (floor(config.cost_precision)) + local function formula(level) + return (floor((1.15 ^ (level * 0.1)) + difficulty_scale * (level) ^ 3 + level_fine_tune * (level) ^ 2 + start_value * (level) - difficulty_scale * (level) - level_fine_tune * (level))) + end + local value = formula(level_reached + 1) + local lower_value = formula(level_reached) + value = value - (value % (10 ^ (floor(log(value, 10)) - precision))) + if lower_value == 0 then + return value - lower_value + end + lower_value = lower_value - (lower_value % (10 ^ (floor(log(lower_value, 10)) - precision))) + return value - lower_value +end) + +local level_table = {} +---Get experience requirement for a given level +---Primarily used for the Experience GUI to display total experience required to unlock a specific item +---@param level number a number specifying the level +---@return number required total experience to reach supplied level +local function calculate_level_xp(level) + if level_table[level] == nil then + local value + if level == 1 then + value = level_up_formula(level - 1) + else + value = level_up_formula(level - 1) + calculate_level_xp(level - 1) + end + insert(level_table, level, value) + end + return level_table[level] +end +---Get a percentage of required experience between a level and the next level +---@param level number a number specifying the current level +---@return number a percentage of the required experience to level up from one level to the other +local function percentage_of_level_req(level, percentage) + return level_up_formula(level) * percentage +end + +---Updates the market contents based on the current level. +---@param force LuaForce the force which the unlocking requirement should be based of +function Experience.update_market_contents(force) + local current_level = get_force_data(force).current_level + local force_name = force.name + for _, prototype in pairs(config.unlockables) do + local prototype_level = prototype.level + if current_level < prototype_level then + disable_item(force_name, prototype.name, {'diggy.market_disabled', prototype_level}) + else + enable_item(force_name, prototype.name) + end + end +end + +---Updates a forces manual mining speed modifier. By removing active modifiers and re-adding +---@param force LuaForce the force of which will be updated +---@param level_up number a level if updating as part of a level up (optional) +function Experience.update_mining_speed(force, level_up) + local buff = config.buffs['mining_speed'] + if buff.max == nil or force.manual_mining_speed_modifier < buff.max then + level_up = level_up ~= nil and level_up or 0 + if level_up > 0 and buff ~= nil then + local level = get_force_data(force).current_level + local adjusted_value = floor(max(buff.value, 24 * 0.9 ^ level)) + local value = (buff.double_level ~= nil and level_up % buff.double_level == 0) and adjusted_value * 2 or adjusted_value + mining_efficiency.level_modifier = mining_efficiency.level_modifier + (value * 0.01) + end + -- remove the current buff + local old_modifier = force.manual_mining_speed_modifier - mining_efficiency.active_modifier + old_modifier = old_modifier >= 0 and old_modifier or 0 + -- update the active modifier + mining_efficiency.active_modifier = mining_efficiency.research_modifier + mining_efficiency.level_modifier + + -- add the new active modifier to the non-buffed modifier + force.manual_mining_speed_modifier = old_modifier + mining_efficiency.active_modifier + end +end + +---Updates a forces inventory slots. By removing active modifiers and re-adding +---@param force LuaForce the force of which will be updated +---@param level_up number a level if updating as part of a level up (optional) +function Experience.update_inventory_slots(force, level_up) + local buff = config.buffs['inventory_slot'] + if buff.max == nil or force.character_inventory_slots_bonus < buff.max then + level_up = level_up ~= nil and level_up or 0 + if level_up > 0 and buff ~= nil then + local value = (buff.double_level ~= nil and level_up % buff.double_level == 0) and buff.value * 2 or buff.value + inventory_slots.level_modifier = inventory_slots.level_modifier + value + end + + -- remove the current buff + local old_modifier = force.character_inventory_slots_bonus - inventory_slots.active_modifier + old_modifier = old_modifier >= 0 and old_modifier or 0 + -- update the active modifier + inventory_slots.active_modifier = inventory_slots.research_modifier + inventory_slots.level_modifier + + -- add the new active modifier to the non-buffed modifier + force.character_inventory_slots_bonus = old_modifier + inventory_slots.active_modifier + end +end + +---Updates a forces health bonus. By removing active modifiers and re-adding +---@param force LuaForce the force of which will be updated +---@param level_up number a level if updating as part of a level up (optional) +function Experience.update_health_bonus(force, level_up) + local buff = config.buffs['health_bonus'] + if buff.max == nil or force.character_health_bonus < buff.max then + level_up = level_up ~= nil and level_up or 0 + if level_up > 0 and buff ~= nil then + local value = (buff.double_level ~= nil and level_up % buff.double_level == 0) and buff.value * 2 or buff.value + health_bonus.level_modifier = health_bonus.level_modifier + value + end + + -- remove the current buff + local old_modifier = force.character_health_bonus - health_bonus.active_modifier + old_modifier = old_modifier >= 0 and old_modifier or 0 + -- update the active modifier + health_bonus.active_modifier = health_bonus.research_modifier + health_bonus.level_modifier + + -- add the new active modifier to the non-buffed modifier + force.character_health_bonus = old_modifier + health_bonus.active_modifier + end +end + +-- declaration of variables to prevent table look ups @see Experience.register +local common + +---Awards experience when a rock has been mined (increases by 1 XP every 5th level) +---@param event LuaEvent +local function on_player_mined_entity(event) + local entity = event.entity + if not entity or not entity.valid then + --Debug.print('Entity not valid') + return + end + local name = entity.name + local player_index = event.player_index + local force = game.get_player(player_index).force + local level = get_force_data(force).current_level + local exp = 0 + if is_diggy_rock(name) then + exp = common + floor(level / 5) + end + + if exp == 0 then + return + end + + local text = {'', '[img=entity/' .. name .. '] ', {'diggy.float_xp_gained_mine', exp}} + print_player_floating_text_position(player_index, text, gain_xp_color, 0, -0.5) + add_experience(force, exp) +end + +---Awards experience when a research has finished, based on ingredient cost of research +---@param event LuaEvent +local function on_research_finished(event) + local research = event.research + local force = research.force + local exp + if research.research_unit_count_formula ~= nil then + local force_data = get_force_data(force) + exp = percentage_of_level_req(force_data.current_level, config.XP['infinity-research']) + else + local award_xp = 0 + for _, ingredient in pairs(research.research_unit_ingredients) do + local name = ingredient.name + local reward = config.XP[name] + award_xp = award_xp + reward + end + exp = award_xp * research.research_unit_count + end + local text = {'', '[img=item/automation-science-pack] ', {'diggy.float_xp_gained_research', exp}} + for _, p in pairs(game.connected_players) do + local player_index = p.index + print_player_floating_text_position(player_index, text, gain_xp_color, -1, -0.5) + end + add_experience(force, exp) + + local current_modifier = mining_efficiency.research_modifier + local new_modifier = force.mining_drill_productivity_bonus * config.mining_speed_productivity_multiplier * 0.5 + + 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 * 50 -- 1 per level + + Experience.update_inventory_slots(force, 0) + Experience.update_mining_speed(force, 0) + Experience.update_health_bonus(force, 0) +end + +---Awards experience when a rocket has been launched based on percentage of total experience +---@param event LuaEvent +local function on_rocket_launched(event) + local force = event.rocket.force + + local exp = add_experience_percentage(force, config.XP['rocket_launch'], nil, config.XP['rocket_launch_max']) + local text = {'', '[img=item/satellite] ', {'diggy.float_xp_gained_rocket', exp}} + for _, p in pairs(game.connected_players) do + local player_index = p.index + print_player_floating_text_position(player_index, text, gain_xp_color, -1, -0.5) + end +end + +---Awards experience when a player kills an enemy, based on type of enemy +---@param event LuaEvent +local function on_entity_died(event) + local entity = event.entity + if not entity or not entity.valid then + return + end + local force = event.force + local cause = event.cause + local entity_name = entity.name + + --For bot mining and turrets + if not cause or not cause.valid or cause.type ~= 'character' then + local exp = 0 + local floating_text_position + + -- stuff killed by the player force, but not the player + if force and force.name == 'player' then + if cause and (cause.name == 'artillery-turret' or cause.name == 'gun-turret' or cause.name == 'laser-turret' or cause.name == 'flamethrower-turret') then + exp = config.XP['enemy_killed'] * (config.alien_experience_modifiers[entity_name] or 1) + floating_text_position = cause.position + else + local level = get_force_data(force).current_level + if is_diggy_rock(entity_name) then + exp = floor((common + level * 0.2) * 0.5) + end + floating_text_position = entity.position + end + end + + if exp > 0 then + Game.print_floating_text(entity.surface, floating_text_position, {'', '[img=entity/' .. entity_name .. '] ', {'diggy.float_xp_gained_kill', exp}}, gain_xp_color) + add_experience(force, exp) + end + + return + end + + if entity.force.name ~= 'enemy' then + return + end + + local exp = config.XP['enemy_killed'] * (config.alien_experience_modifiers[entity.name] or 1) + print_player_floating_text_position(cause.player.index, {'', '[img=entity/' .. entity_name .. '] ', {'diggy.float_xp_gained_kill', exp}}, gain_xp_color, -1, -0.5) + add_experience(force, exp) +end + +---Deducts experience when a player respawns, based on a percentage of total experience +---@param event LuaEvent +local function on_player_respawned(event) + local player = game.get_player(event.player_index) + local exp = remove_experience_percentage(player.force, config.XP['death-penalty'], 50) + local text = {'', '[img=entity.character]', {'diggy.float_xp_drain', exp}} + game.print({'diggy.player_drained_xp', player.name, exp}, lose_xp_color) + for _, p in pairs(game.connected_players) do + print_player_floating_text_position(p.index, text, lose_xp_color, -1, -0.5) + end + ScoreTracker.change_for_global(experience_lost_name, exp) +end + +local function redraw_title(data) + local force_data = get_force_data('player') + data.frame.caption = {'diggy.gui_total_xp', Utils.comma_value(force_data.total_experience)} +end + +local function apply_heading_style(style, width) + style.font = 'default-bold' + style.width = width +end + +local function redraw_heading(data, header) + local head_condition = (header == 1) + local frame = (head_condition) and data.experience_list_heading or data.buff_list_heading + local header_caption = (head_condition) and {'diggy.gui_reward_item'} or {'diggy.gui_reward_buff'} + Gui.clear(frame) + + local heading_table = frame.add(table_column_layout) + apply_heading_style(heading_table.add({type = 'label', caption = {'diggy.gui_requirement'}}).style, 100) + apply_heading_style(heading_table.add({type = 'label'}).style, 25) + apply_heading_style(heading_table.add({type = 'label', caption = header_caption}).style, 220) +end + +local function redraw_progressbar(data) + local force_data = get_force_data('player') + local flow = data.experience_progressbars + Gui.clear(flow) + + apply_heading_style( + flow.add( + { + type = 'label', + tooltip = {'diggy.gui_progress_tip', force_data.current_level, Utils.comma_value((force_data.total_experience - force_data.current_experience) + force_data.experience_level_up_cap), Utils.comma_value(force_data.experience_level_up_cap - force_data.current_experience)}, + name = 'Diggy.Experience.Frame.Progress.Level', + caption = {'diggy.gui_progress_caption'} + } + ).style + ) + local level_progressbar = flow.add({type = 'progressbar', tooltip = {'diggy.gui_progress_bar', floor(force_data.experience_percentage * 100) * 0.01}}) + level_progressbar.style.width = 350 + level_progressbar.value = force_data.experience_percentage * 0.01 +end + +local function redraw_table(data) + local experience_scroll_pane = data.experience_scroll_pane + Gui.clear(experience_scroll_pane) + + redraw_progressbar(data) + redraw_heading(data, 1) + + local last_level = 0 + local current_force_level = get_force_data('player').current_level + + for _, prototype in pairs(config.unlockables) do + local current_item_level = prototype.level + local first_item_for_level = current_item_level ~= last_level + local color + + if current_force_level >= current_item_level then + color = unlocked_color + else + color = locked_color + end + + local list = experience_scroll_pane.add(table_column_layout) + + local level_caption = '' + if first_item_for_level then + level_caption = {'diggy.gui_tabel_level', current_item_level} + end + + local level_column = + list.add( + { + type = 'label', + caption = level_caption, + tooltip = {'diggy.gui_tabel_xp', Utils.comma_value(calculate_level_xp(current_item_level))} + } + ) + level_column.style.minimal_width = 100 + level_column.style.font_color = color + + local spacer = + list.add( + { + type = 'flow' + } + ) + spacer.style.minimal_width = 25 + + local item_column = + list.add( + { + type = 'label', + caption = '[img=item/' .. prototype.name .. '] | ' .. prototype.name + } + ) + item_column.style.minimal_width = 200 + item_column.style.font_color = color + item_column.style.horizontal_align = 'left' + + last_level = current_item_level + end +end + +local function redraw_buff(data) + local buff_scroll_pane = data.buff_scroll_pane + Gui.clear(buff_scroll_pane) + + local all_levels_shown = false + for name, effects in pairs(config.buffs) do + local list = buff_scroll_pane.add(table_column_layout) + list.style.horizontal_spacing = 16 + + local level_caption = '' + if not all_levels_shown then + all_levels_shown = true + level_caption = {'diggy.gui_buff_level'} + end + + local level_label = list.add({type = 'label', caption = level_caption}) + level_label.style.minimal_width = 100 + level_label.style.font_color = unlocked_color + + local spacer = + list.add( + { + type = 'flow' + } + ) + spacer.style.minimal_width = 25 + + local buff_caption + local effect_value = effects.value + local effect_max = effects.max + if name == 'mining_speed' then + buff_caption = {'diggy.gui_buff_mining', effect_value, effect_max * 100} + elseif name == 'inventory_slot' then + buff_caption = {'diggy.gui_buff_inv', effect_value, effect_max} + elseif name == 'health_bonus' then + buff_caption = {'diggy.gui_buff_health', effect_value, effect_max} + else + buff_caption = {'diggy.gui_buff_other', effect_value, name} + end + + local buffs_label = list.add({type = 'label', caption = buff_caption}) + buffs_label.style.minimal_width = 220 + buffs_label.style.font_color = unlocked_color + end +end + +local function toggle(event) + local player = event.player + local left = player.gui.left + local frame = left['Diggy.Experience.Frame'] + + if (frame and event.trigger == nil) then + Gui.destroy(frame) + return + elseif (frame) then + local data = Gui.get_data(frame) + redraw_title(data) + redraw_progressbar(data) + redraw_table(data) + return + end + + frame = left.add({name = 'Diggy.Experience.Frame', type = 'frame', direction = 'vertical'}) + + local experience_progressbars = frame.add({type = 'flow', direction = 'vertical'}) + local experience_list_heading = frame.add({type = 'flow', direction = 'horizontal'}) + + local experience_scroll_pane = frame.add({type = 'scroll-pane'}) + experience_scroll_pane.style.maximal_height = 300 + + local buff_list_heading = frame.add({type = 'flow', direction = 'horizontal'}) + + local buff_scroll_pane = frame.add({type = 'scroll-pane'}) + buff_scroll_pane.style.maximal_height = 100 + + frame.add({type = 'button', name = 'Diggy.Experience.Button', caption = {'diggy.gui_close_btn'}}) + + local data = { + frame = frame, + experience_progressbars = experience_progressbars, + experience_list_heading = experience_list_heading, + experience_scroll_pane = experience_scroll_pane, + buff_list_heading = buff_list_heading, + buff_scroll_pane = buff_scroll_pane + } + + redraw_title(data) + redraw_table(data) + + redraw_heading(data, 2) + redraw_buff(data) + + Gui.set_data(frame, data) +end + +local function on_player_created(event) + game.get_player(event.player_index).gui.top.add( + { + name = 'Diggy.Experience.Button', + type = 'sprite-button', + sprite = 'entity/market', + tooltip = {'diggy.gui_experience_button_tip'} + } + ) +end + +Gui.allow_player_to_toggle_top_element_visibility('Diggy.Experience.Button') + +Gui.on_click('Diggy.Experience.Button', toggle) +Gui.on_custom_close( + 'Diggy.Experience.Frame', + function(event) + event.element.destroy() + end +) + +---Updates the experience progress gui for every player that has it open +local function update_gui() + local players = game.connected_players + for i = #players, 1, -1 do + local p = players[i] + local frame = p.gui.left['Diggy.Experience.Frame'] + + if frame and frame.valid then + local data = {player = p, trigger = 'update_gui'} + toggle(data) + end + end + + --Resets buffs if they have been set to 0 + local force = game.forces.player + Experience.update_inventory_slots(force, 0) + Experience.update_mining_speed(force, 0) + Experience.update_health_bonus(force, 0) +end + +function Experience.register(cfg) + ScoreTracker.register(experience_lost_name, {'diggy.score_experience_lost'}, '[img=recipe.artillery-targeting-remote]') + + local global_to_show = global.config.score.global_to_show + global_to_show[#global_to_show + 1] = experience_lost_name + + config = cfg + + --Adds the function on how to calculate level caps (When to level up) + local ForceControlBuilder = ForceControl.register(level_up_formula) + + --Adds a function that'll be executed at every level up + ForceControlBuilder.register_on_every_level( + function(level_reached, force) + Toast.toast_force(force, 10, {'diggy.toast_new_level', level_reached}) + Experience.update_inventory_slots(force, level_reached) + Experience.update_mining_speed(force, level_reached) + Experience.update_health_bonus(force, level_reached) + Experience.update_market_contents(force) + end + ) + + -- Events + Event.add(defines.events.on_player_mined_entity, on_player_mined_entity) + Event.add(defines.events.on_research_finished, on_research_finished) + Event.add(defines.events.on_rocket_launched, on_rocket_launched) + Event.add(defines.events.on_player_respawned, on_player_respawned) + Event.add(defines.events.on_entity_died, on_entity_died) + Event.add(defines.events.on_player_created, on_player_created) + Event.on_nth_tick(61, update_gui) + + -- Prevents table lookup thousands of times + common = config.XP['common'] +end + +function Experience.on_init() + --Adds the 'player' force to participate in the force control system. + local force = game.forces.player + ForceControl.register_force(force) + + local force_name = force.name + for _, prototype in pairs(config.unlockables) do + set_item(force_name, prototype) + end + + Experience.update_market_contents(force) +end + +return Experience diff --git a/map_gen/maps/diggy_experimental/feature/scattered_resources.lua b/map_gen/maps/diggy_experimental/feature/scattered_resources.lua new file mode 100644 index 000000000..72c26167b --- /dev/null +++ b/map_gen/maps/diggy_experimental/feature/scattered_resources.lua @@ -0,0 +1,263 @@ +--[[-- info + Provides the ability to spawn random ores all over the place. +]] + +-- dependencies +local Event = require 'utils.event' +local Debug = require 'map_gen.maps.diggy.debug' +local Template = require 'map_gen.maps.diggy_experimental.template' +local Perlin = require 'map_gen.shared.perlin_noise' +local Simplex = require 'map_gen.shared.simplex_noise' +local Utils = require 'utils.core' +local random = math.random +local sqrt = math.sqrt +local ceil = math.ceil +local min = math.min +local pairs = pairs +local template_resources = Template.resources + +-- this +local ScatteredResources = {} + +local function get_name_by_weight(collection, sum) + local pre_calculated = random() + local current = 0 + local target = pre_calculated * sum + + for name, weight in pairs(collection) do + current = current + weight + if (current >= target) then + return name + end + end + + Debug.print('Current \'' .. current .. '\' should be higher or equal to random \'' .. target .. '\'') +end + +--[[-- + Registers all event handlers. +]] +function ScatteredResources.register(config) + + -- source of noise for resource generation + -- index determines offset + -- '-1' is reserved for cluster mode + -- compound clusters use as many indexes as needed > 1 + local base_seed + local function seeded_noise(surface, x, y, index, sources) + base_seed = base_seed or surface.map_gen_settings.seed + surface.index + 4000 + local noise = 0 + for _, settings in pairs(sources) do + settings.type = settings.type or 'perlin' + settings.offset = settings.offset or 0 + if settings.type == 'zero' then + noise = noise + 0 + elseif settings.type == 'one' then + noise = noise + settings.weight * 1 + elseif settings.type == 'perlin' then + noise = noise + settings.weight * Perlin.noise(x/settings.variance, y/settings.variance, + base_seed + 2000*index + settings.offset) + elseif settings.type == 'simplex' then + noise = noise + settings.weight * Simplex.d2(x/settings.variance, y/settings.variance, + base_seed + 2000*index + settings.offset) + else + Debug.print('noise type \'' .. settings.type .. '\' not recognized') + end + + end + return noise + end + + -- global config values + + local resource_richness_weights = config.resource_richness_weights + local resource_richness_weights_sum = 0 + for _, weight in pairs(resource_richness_weights) do + resource_richness_weights_sum = resource_richness_weights_sum + weight + end + local resource_richness_values = config.resource_richness_values + local resource_type_scalar = config.resource_type_scalar + + -- scattered config values + local s_mode = config.scattered_mode + local s_dist_mod = config.scattered_distance_probability_modifier + local s_min_prob = config.scattered_min_probability + local s_max_prob = config.scattered_max_probability + local s_dist_richness = config.scattered_distance_richness_modifier + local s_cluster_prob = config.scattered_cluster_probability_multiplier + local s_cluster_mult = config.scattered_cluster_yield_multiplier + + local s_resource_weights = config.scattered_resource_weights + local s_resource_weights_sum = 0 + for _, weight in pairs(s_resource_weights) do + s_resource_weights_sum = s_resource_weights_sum + weight + end + local s_min_dist = config.scattered_minimum_resource_distance + + -- cluster config values + local cluster_mode = config.cluster_mode + + -- compound cluster spawning + local c_mode = config.cluster_mode + local c_clusters = config.ore_pattern + if 'table' ~= type(c_clusters) then + error('ore_pattern invalid') + end + local c_count = 0 + for _, cluster in pairs(c_clusters) do + c_count = c_count + 1 + cluster.weights_sum = 0 + -- ensure the cluster colors are valid otherwise it fails silently + -- and breaks things elsewhere + if cluster.color then + local c = cluster.color + if (not c.r) or (not c.g) or (not c.b) then + cluster.color = nil + elseif c.r < 0 or c.r > 1 or c.g < 0 or c.g > 1 or c.b < 0 or c.b > 1 then + cluster.color = nil + end + end + for _, weight in pairs(cluster.weights) do + cluster.weights_sum = cluster.weights_sum + weight + end + end + + local function spawn_cluster_resource(surface, x, y, cluster) + local distance = sqrt(x * x + y * y) + local resource_name = get_name_by_weight(cluster.weights, cluster.weights_sum) + if resource_name == 'skip' then + return false + end + + local cluster_distance = cluster.distances[resource_name] + if cluster_distance and distance < cluster_distance then + return false + end + + local range = resource_richness_values[get_name_by_weight(resource_richness_weights, resource_richness_weights_sum)] + local amount = random(range[1], range[2]) * (1 + ((distance / cluster.distance_richness) * 0.01)) * cluster.yield + + if resource_type_scalar[resource_name] then + amount = amount * resource_type_scalar[resource_name] + end + + template_resources(surface, {{name = resource_name, position = {x = x, y = y}, amount = ceil(amount)}}) + return true + end + + -- event registration + Event.add(Template.events.on_void_removed, function (event) + local position = event.position + local x = position.x + local y = position.y + local surface = event.surface + + local distance = config.distance(x, y) + + if c_mode then + for index,cluster in pairs(c_clusters) do + if distance >= cluster.min_distance and cluster.noise_settings.type ~= 'skip' then + if cluster.noise_settings.type == "connected_tendril" then + local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources) + if -1 * cluster.noise_settings.threshold < noise and noise < cluster.noise_settings.threshold then + if spawn_cluster_resource(surface, x, y, cluster) then + return -- resource spawned + end + end + elseif cluster.noise_settings.type == "fragmented_tendril" then + local noise1 = seeded_noise(surface, x, y, index, cluster.noise_settings.sources) + local noise2 = seeded_noise(surface, x, y, index, cluster.noise_settings.discriminator) + if -1 * cluster.noise_settings.threshold < noise1 and noise1 < cluster.noise_settings.threshold + and -1 * cluster.noise_settings.discriminator_threshold < noise2 + and noise2 < cluster.noise_settings.discriminator_threshold then + if spawn_cluster_resource(surface, x, y, cluster) then + return -- resource spawned + end + end + else + local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources) + if noise >= cluster.noise_settings.threshold then + if spawn_cluster_resource(surface, x, y, cluster) then + return -- resource spawned + end + end + end + end + end + end + + if s_mode then + local probability = min(s_max_prob, s_min_prob + 0.01 * (distance / s_dist_mod)) + + if (cluster_mode) then + probability = probability * s_cluster_prob + end + + if (probability > random()) then + -- spawn single resource point for scatter mode + local resource_name = get_name_by_weight(s_resource_weights, s_resource_weights_sum) + if resource_name == 'skip' or s_min_dist[resource_name] > distance then + return + end + + local range = resource_richness_values[get_name_by_weight(resource_richness_weights, resource_richness_weights_sum)] + local amount = random(range[1], range[2]) + amount = amount * (1 + ((distance / s_dist_richness) * 0.01)) + + if resource_type_scalar[resource_name] then + amount = amount * resource_type_scalar[resource_name] + end + + if (cluster_mode) then + amount = amount * s_cluster_mult + end + + Template.resources(surface, {{name = resource_name, position={x=x,y=y}, amount = ceil(amount)}}) + end + end + end) + + if (config.display_ore_clusters) then + local color = {} + 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 + for index,cluster in pairs(c_clusters) do + if cluster.noise_settings.type == "connected_tendril" then + local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources) + if -1 * cluster.noise_settings.threshold < noise and noise < cluster.noise_settings.threshold then + color[index] = color[index] or cluster.color or Utils.random_RGB + Debug.print_colored_grid_value('o' .. index, surface, {x = x, y = y}, nil, true, 0, color[index]) + end + elseif cluster.noise_settings.type == "fragmented_tendril" then + local noise1 = seeded_noise(surface, x, y, index, cluster.noise_settings.sources) + local noise2 = seeded_noise(surface, x, y, index, cluster.noise_settings.discriminator) + if -1 * cluster.noise_settings.threshold < noise1 and noise1 < cluster.noise_settings.threshold + and -1 * cluster.noise_settings.discriminator_threshold < noise2 + and noise2 < cluster.noise_settings.discriminator_threshold then + color[index] = color[index] or cluster.color or Utils.random_RGB + Debug.print_colored_grid_value('o' .. index, surface, {x = x, y = y}, nil, true, 0, color[index]) + end + elseif cluster.noise_settings.type ~= 'skip' then + local noise = seeded_noise(surface, x, y, index, cluster.noise_settings.sources) + if noise >= cluster.noise_settings.threshold then + color[index] = color[index] or cluster.color or Utils.random_RGB + Debug.print_colored_grid_value('o' .. index, surface, {x = x, y = y}, nil, true, 0, color[index]) + end + end + end + end + end + end) + end +end + +function ScatteredResources.get_extra_map_info() + 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/maps/diggy_experimental/feature/simple_room_generator.lua b/map_gen/maps/diggy_experimental/feature/simple_room_generator.lua new file mode 100644 index 000000000..f9e3067bf --- /dev/null +++ b/map_gen/maps/diggy_experimental/feature/simple_room_generator.lua @@ -0,0 +1,120 @@ +--[[-- info + Provides the ability to make a simple room with contents +]] + +-- dependencies +local Template = require 'map_gen.maps.diggy_experimental.template' +local Event = require 'utils.event' +local Debug = require 'map_gen.maps.diggy.debug' +local Task = require 'utils.task' +local Token = require 'utils.token' +local raise_event = script.raise_event +local pairs = pairs +local perlin_noise = require 'map_gen.shared.perlin_noise'.noise +local template_insert = Template.insert +local set_timeout_in_ticks = Task.set_timeout_in_ticks +local on_entity_died = defines.events.on_entity_died +-- this +local SimpleRoomGenerator = {} + +local diggy_water + +local do_spawn_tile = Token.register(function(params) + template_insert(params.surface, {params.tile}, {}) +end) + +local rocks_lookup = {} +for k, _ in pairs(Template.diggy_rocks) do + table.insert(rocks_lookup, k) +end + +local do_mine = Token.register(function(params) + local surface = params.surface + local position = params.position + local rocks = surface.find_entities_filtered({position = position, radius = 1, name = rocks_lookup}) + local rock_count = #rocks + if rock_count == 0 then + return + end + + for i = rock_count, 1, -1 do + local rock = rocks[i] + raise_event(on_entity_died, {entity = rock}) + rock.destroy() + end +end) + +local function handle_noise(name, surface, position) + set_timeout_in_ticks(1, do_mine, {surface = surface, position = position}) + + if 'dirt' == name then + return + end + + if 'water' == name then + -- water is slower because for some odd reason it doesn't always want to mine it properly + set_timeout_in_ticks(4, do_spawn_tile, { surface = surface, tile = {name = diggy_water, position = position}}) + 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 noise_variance = config.noise_variance + diggy_water = config.water + + local seed + local function get_noise(surface, x, y) + seed = seed or surface.map_gen_settings.seed + surface.index + 100 + return perlin_noise(x * noise_variance, y * noise_variance, seed) + end + + Event.add(Template.events.on_void_removed, function (event) + local position = event.position + local x = position.x + local y = position.y + + local distance_sq = x * x + y * y + + if (distance_sq <= room_noise_minimum_distance_sq) then + return + end + + local surface = event.surface + local noise = get_noise(surface, x, 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.display_room_locations) 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 + for _, noise_range in pairs(config.room_noise_ranges) do + local noise = get_noise(surface, x, y) + if (noise >= noise_range.min and noise <= noise_range.max) then + Debug.print_grid_value(noise_range.name, surface, {x = x, y = y}, nil, nil, true) + end + end + end + end + end) + end +end + +function SimpleRoomGenerator.get_extra_map_info() + return 'Simple Room Generator, digging around might open rooms!' +end + +return SimpleRoomGenerator diff --git a/map_gen/maps/diggy_experimental/feature/starting_zone.lua b/map_gen/maps/diggy_experimental/feature/starting_zone.lua new file mode 100644 index 000000000..a14a8baa0 --- /dev/null +++ b/map_gen/maps/diggy_experimental/feature/starting_zone.lua @@ -0,0 +1,110 @@ +--[[-- info + Provides the ability to create a pre-configured starting zone. +]] +-- dependencies +local Event = require 'utils.event' +local Token = require 'utils.token' +local Template = require 'map_gen.maps.diggy_experimental.template' +local Retailer = require 'features.retailer' +local DiggyCaveCollapse = require 'map_gen.maps.diggy_experimental.feature.diggy_cave_collapse' +local RS = require 'map_gen.shared.redmew_surface' + +local insert = table.insert +local random = math.random +local sqrt = math.sqrt +local floor = math.floor +local pairs = pairs +local raise_event = script.raise_event + +-- 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) + if event.surface ~= RS.get_surface() then + return + end + local start_point_area = {{-0.9, -0.9}, {0.9, 0.9}} + local start_point_cleanup = {{-0.9, -0.9}, {1.9, 1.9}} + local surface = event.surface + + -- hack to figure out whether the important chunks are generated via diggy.feature.refresh_map. + 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_cleanup, type = 'resource'})) do + entity.destroy() + end + + local tiles = {} + local rocks = {} + + local dirt_range = floor(starting_zone_size * 0.5) + local rock_range = starting_zone_size - 2 + local stress_hack = floor(starting_zone_size * 0.1) + + 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 > dirt_range) then + if config.start_tile == 'grass' then + insert(tiles, {name = 'grass-' .. random(1, 4), position = {x = x, y = y}}) + else + insert(tiles, {name = 'dirt-' .. random(1, 7), position = {x = x, y = y}}) + end + else + insert(tiles, {name = 'stone-path', position = {x = x, y = y}}) + end + + if (distance > rock_range) then + insert(rocks, {name = config.start_entity, position = {x = x, y = y}}) + end + + -- hack to avoid starting area from collapsing + if (distance > stress_hack) then + DiggyCaveCollapse.stress_map_add(surface, {x = x, y = y}, -0.5) + end + end + end + end + + Template.insert(surface, tiles, rocks) + + local position = config.market_spawn_position + local player_force = game.forces.player + + local market = surface.create_entity({name = 'market', position = position}) + market.destructible = false + + Retailer.set_market_group_label('player', 'Diggy Market') + Retailer.add_market('player', market) + + player_force.add_chart_tag( + surface, + { + text = 'Market', + position = position + } + ) + + raise_event(Template.events.on_placed_entity, {entity = market}) + + 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 + +return StartingZone diff --git a/map_gen/maps/diggy_experimental/readme.md b/map_gen/maps/diggy_experimental/readme.md new file mode 100644 index 000000000..f444341da --- /dev/null +++ b/map_gen/maps/diggy_experimental/readme.md @@ -0,0 +1,3 @@ +## RedMew - Diggy, Custom Scenario + +The documentation for Diggy has moved to https://github.com/Refactorio/RedMew/wiki/Map-guide%3A-Diggy diff --git a/map_gen/maps/diggy_experimental/scenario.lua b/map_gen/maps/diggy_experimental/scenario.lua new file mode 100644 index 000000000..fa242e75e --- /dev/null +++ b/map_gen/maps/diggy_experimental/scenario.lua @@ -0,0 +1,90 @@ +-- dependencies +local Config = require 'map_gen.maps.diggy_experimental.config' +local ScenarioInfo = require 'features.gui.info' +local RS = require 'map_gen.shared.redmew_surface' +local Event = require 'utils.event' +local type = type +local pairs = pairs + +require 'utils.table' +require 'utils.core' + +-- this +local Scenario = {} + +RS.set_first_player_position_check_override(true) -- forces players to spawn at 0,0 +RS.set_spawn_island_tile('stone-path') +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 enabled_type = type(if_enabled) + if ('function' ~= enabled_type) then + error('each_enabled_feature expects callback to be a function, given type: ' .. enabled_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() + if global.diggy_scenario_registered then + error('Cannot register the Diggy scenario multiple times.') + return + end + + -- disabled redmew features for diggy + local redmew_config = global.config + redmew_config.market.enabled = false + redmew_config.reactor_meltdown.enabled = false + redmew_config.hodor.enabled = false + redmew_config.paint.enabled = false + + + each_enabled_feature( + function(feature_name, feature_config) + local feature + + if not pcall(function() feature = require ('map_gen.maps.diggy_experimental.feature.' .. feature_name) end) then + feature = require ('map_gen.maps.diggy.feature.' .. feature_name) + end + 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 + ScenarioInfo.add_map_extra_info(feature.get_extra_map_info(feature_config) .. '\n') + end + + if ('function' == type(feature.on_init)) then + Event.on_init(feature.on_init) + end + end + ) + + local landfill_tiles = {'dirt-1','dirt-2','dirt-3','dirt-4','dirt-5','dirt-6','dirt-7'} + require ('map_gen.shared.change_landfill_tile')(landfill_tiles) + + ScenarioInfo.set_map_name('Diggy') + ScenarioInfo.set_map_description('Dig your way through!') + + global.diggy_scenario_registered = true +end + +return Scenario diff --git a/map_gen/maps/diggy_experimental/template.lua b/map_gen/maps/diggy_experimental/template.lua new file mode 100644 index 000000000..3ef0eefee --- /dev/null +++ b/map_gen/maps/diggy_experimental/template.lua @@ -0,0 +1,178 @@ +-- dependencies +local Task = require 'utils.task' +local Token = require 'utils.token' +local Event = require 'utils.event' +local Config = require 'map_gen.maps.diggy_experimental.config' +local min = math.min +local ceil = math.ceil +local raise_event = script.raise_event +local queue_task = Task.queue_task +local pairs = pairs +local pcall = pcall + +-- this +local Template = {} + +local tiles_per_call = 8 --how many tiles are inserted with each call of insert_action +local entities_per_call = 8 --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 = Event.generate_event_name('on_placed_entity'), + + --[[-- + Triggers when an 'out-of-map' tile is replaced by something else. + + {surface, old_tile={name, position={x, y}}} + ]] + on_void_removed = Event.generate_event_name('on_void_removed'), +} + +local on_void_removed = Template.events.on_void_removed +local on_placed_entity = Template.events.on_placed_entity + +local function insert_next_tiles(data) + local void_removed = {} + local void_removed_count = 0 + local surface = data.surface + local get_tile = surface.get_tile + local tiles = {} + local tile_count = 0 + local tile_iterator = data.tile_iterator + + pcall(function() + --use pcall to assure tile_iterator is always incremented, to avoid endless loops + for i = tile_iterator, min(tile_iterator + tiles_per_call - 1, data.tiles_n) do + local new_tile = data.tiles[i] + tile_count = tile_count + 1 + tiles[tile_count] = new_tile + + if new_tile.name ~= 'out-of-map' then + local current_tile = get_tile(new_tile.position.x, new_tile.position.y) + if current_tile.name == 'out-of-map' then + void_removed_count = void_removed_count + 1 + void_removed[void_removed_count] = {surface = surface, position = current_tile.position} + end + end + end + end) + + data.tile_iterator = tile_iterator + tiles_per_call + + surface.set_tiles(tiles) + + for i = 1, void_removed_count do + raise_event(on_void_removed, void_removed[i]) + end +end + +local function insert_next_entities(data) + local created_entities = {} + local created_entities_count = 0 + local surface = data.surface + local create_entity = surface.create_entity + local tick = game.tick + + 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 + created_entities_count = created_entities_count + 1 + local created_entity = create_entity(data.entities[i]) + created_entity.graphics_variation = (tick % 10) + 1 --math.random(1, 10) or something else (Just don't include variation 11 and 12) + created_entities[created_entities_count] = created_entity + end + end) + + data.entity_iterator = data.entity_iterator + entities_per_call + + for i = 1, created_entities_count do + raise_event(on_placed_entity, {entity = created_entities[i]}) + 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 _ = 1, 4 do + continue = insert_action(data) + if not continue then + return + end + end + + if continue then + queue_task(insert_token, data, total_calls - 4) + 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 + +Template.diggy_rocks = Config.features.diggy_hole.diggy_entities + +---Returns true if the entity name is that of a diggy rock. +---@param entity_name string +function Template.is_diggy_rock(entity_name) + for k, _ in pairs(Template.diggy_rocks) do + if entity_name == k then + return true + end + end + return false + --return entity_name == 'sand-rock-big' or entity_name == 'rock-big' or entity_name == 'rock-huge' +end + +return Template diff --git a/map_gen/shared/hail_hydra.lua b/map_gen/shared/hail_hydra.lua index 29481978e..373b252a4 100644 --- a/map_gen/shared/hail_hydra.lua +++ b/map_gen/shared/hail_hydra.lua @@ -72,6 +72,10 @@ local on_died = Token.register( function(event) local entity = event.entity + if not entity or not entity.valid then + return + end + local name = entity.name local hydra = spawn_table[name]