diff --git a/features/create_particles.lua b/features/create_particles.lua index 74e8e1eab..29743b164 100644 --- a/features/create_particles.lua +++ b/features/create_particles.lua @@ -147,6 +147,23 @@ function CreateParticles.destroy_rock(create_particle, particle_count, position) end end +---@param create_particle function a reference to a surface.create_particle +---@param particle_count number particle count to spawn +---@param position Position +function CreateParticles.destroy_tree(create_particle, particle_count, position) + for _ = scale_floor(particle_count), 1, -1 do + settings.particles_spawned_buffer = settings.particles_spawned_buffer + 1 + create_particle({ + name = 'leaf-particle', + position = position, + movement = {random(-5, 5) * 0.01, random(-5, 5) * 0.01}, + height = random(9, 11) * 0.1, + vertical_speed = random(12, 14) * 0.01, + frame_speed = 1, + }) + end +end + ---@param create_particle function a reference to a surface.create_particle ---@param particle_count number particle count to spawn ---@param position Position @@ -181,7 +198,7 @@ function CreateParticles.mine_rock(create_particle, particle_count, position) end ----Creates a prototype for LuaSurface.create_entity +---Creates a prototype for LuaSurface.create_particle ---@param particle string name of the particle ---@param x number ---@param y number diff --git a/locale/de/redmew_maps.cfg b/locale/de/redmew_maps.cfg index 1e274cb55..4029a630a 100644 --- a/locale/de/redmew_maps.cfg +++ b/locale/de/redmew_maps.cfg @@ -93,3 +93,30 @@ cutscene_case_line6=Die folgende Einführung wird dir zum Einstieg helfen! cutscene_case_line7=Wenn du Probleme mit den Zwischensequenzen hast, lass es uns wissen cutscene_case0_line1=Dies ist der Startbereich +[black_forest] +float_xp_drain=-__1__ XP +float_xp_gained_kill=+__1__ XP +float_xp_gained_rocket=Rakete gestartet! +__1__ XP +float_xp_gained_research=Forschung abgeschlossen! +__1__ XP +float_xp_gained_mine=+__1__ XP +player_drained_xp=__1__ hat __2__ Erfahrung abgezogen. +market_disabled=Freigeschaltet bei Level:__1__ +gui_total_xp=__1__ Gesamterfahrung erhalten! +gui_reward_item=Belohnungsgegenstand +gui_reward_buff=Belohnungs Buff +gui_requirement=Anforderung +gui_progress_tip=Derzeit auf Level:__1__\nNächstes Level bei:__2__ xp\nVerbleibend:__3__ xp +gui_progress_caption=Fortschritt bis zum nächsten Level: +gui_progress_bar=__1__% xp zum nächsten Level +gui_tabel_level=Level __1__ +gui_tabel_xp=XP: __1__ +gui_buff_level=Alle Stufen +gui_buff_mining=+__1__% Abbau Geschwindigkeit (bis zu:__2__%) +gui_buff_inv=+__1__ Inventar Feld(er) (bis zu: __2__) +gui_buff_health=+__1__ max Gesundheit (bis zu: __2__) +gui_buff_other=+__1__ __2__ +gui_experience_button_tip=Black Forest Level Fortschritt +gui_close_btn=Schließen +toast_new_level=Dein Team hat Level __1__ erreicht! +score_mine_size=Mapgröße +score_experience_lost=Verlorene Erfahrung diff --git a/locale/en/redmew_maps.cfg b/locale/en/redmew_maps.cfg index 84042714b..696456570 100644 --- a/locale/en/redmew_maps.cfg +++ b/locale/en/redmew_maps.cfg @@ -177,3 +177,33 @@ biters_disabled=Launching the first [item=satellite] has killed all the biters. win=Congratulations! The map has been won. Restart the map with /restart satellite_launch=Launch another __1__ [item=satellite] to win the map. +[black_forest] +float_xp_drain=-__1__ XP +float_xp_gained_kill=+__1__ XP +float_xp_gained_rocket=Rocket launched! +__1__ XP +float_xp_gained_research=Research completed! +__1__ XP +float_xp_gained_mine=+__1__ XP + +player_drained_xp=__1__ drained __2__ experience. + +market_disabled=Unlocks at level: __1__ + +gui_total_xp=__1__ total experience earned! +gui_reward_item=Reward Item +gui_reward_buff=Reward Buff +gui_requirement=Requirement +gui_progress_tip=Currently at level: __1__\nNext level at: __2__ xp\nRemaining: __3__ xp +gui_progress_caption=Progress to next level: +gui_progress_bar=__1__% xp to next level +gui_tabel_level=level __1__ +gui_tabel_xp=XP: __1__ +gui_buff_level=All levels +gui_buff_mining=+__1__% mining speed (up to: __2__%) +gui_buff_inv=+__1__ inventory slot(s) (up to: __2__) +gui_buff_health=+__1__ max health (up to: __2__) +gui_buff_other=+__1__ __2__ +gui_experience_button_tip=Black Forest leveling progress +gui_close_btn=Close +toast_new_level=Your team has reached level __1__! +score_mine_size=Map size +score_experience_lost=Experience lost diff --git a/map_gen/maps/black_forest/config.lua b/map_gen/maps/black_forest/config.lua new file mode 100644 index 000000000..693b070d6 --- /dev/null +++ b/map_gen/maps/black_forest/config.lua @@ -0,0 +1,407 @@ +-- dependencies +local abs = math.abs + +-- 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} + }, + -- controls the Daylight (Default black_forest: enabled = true) + black_forest_flame = { + enabled = true + }, + black_forest_grow = { + enabled = true + }, + -- controls setting up the players + setup_player = { + enabled = true, + starting_items = { + {name = 'iron-gear-wheel', count = 8}, + {name = 'iron-plate', count = 16}, + {name = 'solar-panel', count = 1}, + {name = 'pistol', count = 1}, + {name = 'firearm-magazine', count = 25}, + {name = 'concrete', count = 100} + }, + + -- 0.01 bonus equals 1% in game. This value is recommended to be tweaked for single player + initial_mining_speed_bonus = 0.25, + + -- 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 + black_forest_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, + }, + -- 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.80, min = 3, 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, + river = true + }, + -- automatically opens areas + simple_room_generator = { + enabled = true, + -- value between 0 and 1, higher value means stronger variance between coordinates + noise_variance = 0.03, + -- shows where rooms are located + display_room_locations = false, + -- minimum distance and noise range required for water to spawn + room_noise_minimum_distance = 50, + room_noise_ranges = { + {name = 'deepwater', min = 0.44, max = 1}, + {name = 'water', min = 0.38, max = 0.44}, + {name = 'dirt', min = 0.35, max = 0.38} + } + }, + -- 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'] = 2, + ['copper-ore'] = 215, + ['iron-ore'] = 389, + ['stone'] = 150, + ['uranium-ore'] = 21, + ['crude-oil'] = 3 + }, + -- minimum distance from the spawn point required before it spawns + scattered_minimum_resource_distance = { + ['coal'] = 88, + ['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.black_forest.orepattern.tendrils_impure' + + -- spawns some smaller dedicated and bigger mixed tendrils + --ore_pattern = require 'map_gen.maps.black_forest.orepattern.tendrils', + + -- spawns clusters of ore similar to vanilla, but mixed + --ore_pattern = require 'map_gen.maps.black_forest.orepattern.clusters', + }, + -- controls the alien spawning mechanic + alien_spawner = { + enabled = true, + + -- minimum distance from spawn before aliens can spawn + alien_minimum_distance = 55, + + -- chance of spawning aliens when mining from 0 to 1 + alien_probability = 0.13, + + -- each tile of void removed increases alien evolution by + evolution_per_void_removed = 0.0000027, + + -- initial evolution percentage, recommended to set to 0 for non-multiplayer setups + initial_evolution = 1, + + -- 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 = false, + autojail = true, + allowed_collapses_first_hour = 40 + }, + experience = { + enabled = true, + -- controls the formula for calculating level up costs in stone sent to surface + difficulty_scale = 20, -- black_forest default 15. Higher increases experience requirement climb + first_lvl_xp = 350, -- black_forest default 350. This sets the price for the first level. + xp_fine_tune = 400, -- black_forest default 200. This value is used to fine tune the overall requirement climb without affecting the speed + cost_precision = 3, -- black_forest 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 = { + ['tree-01'] = 5, + ['tree-02'] = 5, + ['tree-03'] = 5, + ['sand-rock-big'] = 5, + ['rock-big'] = 5, + ['rock-huge'] = 10, + ['rocket_launch'] = 0.05, -- XP reward in percentage of total experience when a rocket launches (black_forest default: 0.05 which equals 5%) + ['rocket_launch_max'] = 500000, -- Max XP reward from rocket launches (black_forest 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 (black_forest 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 (black_forest 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 (black_forest 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 = 1, price = 1, name = 'stone-brick'}, + {level = 2, price = 5, name = 'stone-wall'}, + {level = 3, price = 20, name = 'pistol'}, + {level = 3, price = 5, name = 'firearm-magazine'}, + {level = 4, price = 2, name = 'rail'}, + {level = 4, price = 100, name = 'light-armor'}, + {level = 5, price = 6, name = 'small-lamp'}, + {level = 5, price = 100, name = 'locomotive'}, + {level = 6, price = 5, name = 'raw-fish'}, + {level = 8, price = 200, name = 'solar-panel'}, + {level = 9, price = 15, name = 'train-stop'}, + {level = 10, price = 80, name = 'cargo-wagon'}, + {level = 11, price = 5, name = 'rail-signal'}, + {level = 11, price = 5, name = 'rail-chain-signal'}, + {level = 12, price = 200, name = 'heavy-armor'}, + {level = 14, price = 175, name = 'landfill'}, + {level = 15, price = 85, name = 'submachine-gun'}, + {level = 18, price = 750, name = 'effectivity-module-3'}, + {level = 20, price = 750, name = 'productivity-module-3'}, + {level = 24, price = 750, name = 'speed-module-3'}, + {level = 20, price = 350, name = 'modular-armor'}, + {level = 21, price = 10000, name = 'flamethrower'}, + {level = 21, price = 1000, name = 'flamethrower-ammo'}, + {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 = 51, price = 25, name = 'uranium-rounds-magazine'}, + {level = 63, price = 37000, name = 'flamethrower-turret'}, + {level = 71, price = 80, name = 'explosive-rocket'}, + {level = 78, price = 1000, name = 'satellite'}, + {level = 99, price = 1027, name = 'wood'} + }, + -- 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/black_forest/debug.lua b/map_gen/maps/black_forest/debug.lua new file mode 100644 index 000000000..0c693cba9 --- /dev/null +++ b/map_gen/maps/black_forest/debug.lua @@ -0,0 +1,174 @@ +-- dependencies +local BaseDebug = require 'utils.debug' +local Color = require 'resources.color_presets' + +local min = math.min +local max = math.max +local floor = math.floor +local abs = math.abs + +-- this +local Debug = {} + +local default_base_color = Color.white +local default_delta_color = Color.black + +---@deprecated use 'utils.debug'.print instead +function Debug.print(message) + BaseDebug.print(message) +end + +---@deprecated use 'utils.debug'.print_position instead +function Debug.print_position(position, message) + BaseDebug.print_position(position, message) +end + +---@deprecated use 'utils.debug'.cheat instead +function Debug.cheat(callback) + BaseDebug.cheat(callback) +end + +--[[-- + Prints a colored value on a location. + + @param value between -1 and 1 + @param surface LuaSurface + @param position Position {x, y} + @param scale float + @param offset float + @param immutable bool if immutable, only set, never do a surface lookup, values never change +]] +function Debug.print_grid_value(value, surface, position, scale, offset, immutable) + local is_string = type(value) == 'string' + local color = default_base_color + local text = value + + if type(immutable) ~= 'boolean' then + immutable = false + end + + if not is_string then + scale = scale or 1 + offset = offset or 0 + position = {x = position.x + offset, y = position.y + offset} + local r = max(1, value) / scale + local g = 1 - abs(value) / scale + local b = min(1, value) / scale + + if (r > 0) then + r = 0 + end + + if (b < 0) then + b = 0 + end + + if (g < 0) then + g = 0 + end + + r = abs(r) + + color = { r = r, g = g, b = b} + + -- round at precision of 2 + text = floor(100 * value) * 0.01 + + if (0 == text) then + text = '0.00' + end + end + + if not immutable then + local text_entity = surface.find_entity('flying-text', position) + + if text_entity then + text_entity.text = text + text_entity.color = color + return + end + end + + surface.create_entity{ + name = 'flying-text', + color = color, + text = text, + position = position + }.active = false +end + +--[[-- + Prints a colored value on a location. When given a color_value and a delta_color, + will change the color of the text from the base to base + value * delta. This will + make the color of the text range from 'base_color' to 'base_color + delta_color' + as the color_value ranges from 0 to 1 + + @param value of number to be displayed + @param surface LuaSurface + @param position Position {x, y} + @param offset float position offset + @param immutable bool if immutable, only set, never do a surface lookup, values never change + @param color_value float How far along the range of values of colors the value is to be displayed + @param base_color {r,g,b} The color for the text to be if color_value is 0 + @param delta_color {r,g,b} The amount to correct the base_color if color_value is 1 + @param under_bound {r,g,b} The color to be used if color_value < 0 + @param over_bound {r,g,b} The color to be used if color_value > 1 +]] +function Debug.print_colored_grid_value(value, surface, position, offset, immutable, + color_value, base_color, delta_color, under_bound, over_bound) + local is_string = type(value) == 'string' + -- default values: + local color = base_color or default_base_color + local d_color = delta_color or default_delta_color + local u_color = under_bound or color + local o_color = over_bound or color + + if (color_value < 0) then + color = u_color + elseif (color_value > 1) then + color = o_color + else + color = { + r = color.r + color_value * d_color.r, + g = color.g + color_value * d_color.g, + b = color.b + color_value * d_color.b + } + end + + local text = value + + if type(immutable) ~= 'boolean' then + immutable = false + end + + if not is_string then + offset = offset or 0 + position = {x = position.x + offset, y = position.y + offset} + + -- round at precision of 2 + text = floor(100 * value) * 0.01 + + if (0 == text) then + text = '0.00' + end + end + + if not immutable then + local text_entity = surface.find_entity('flying-text', position) + + if text_entity then + text_entity.text = text + text_entity.color = color + return + end + end + + surface.create_entity{ + name = 'flying-text', + color = color, + text = text, + position = position + }.active = false +end + +return Debug diff --git a/map_gen/maps/black_forest/feature/alien_spawner.lua b/map_gen/maps/black_forest/feature/alien_spawner.lua new file mode 100644 index 000000000..108b75681 --- /dev/null +++ b/map_gen/maps/black_forest/feature/alien_spawner.lua @@ -0,0 +1,207 @@ +--[[-- info + Provides the ability to spawn aliens. +]] + +-- dependencies +require 'utils.table' +local Event = require 'utils.event' +local Global = require 'utils.global' +local Token = require 'utils.token' +local Task = require 'utils.task' +local AlienEvolutionProgress = require 'utils.alien_evolution_progress' +local Debug = require 'map_gen.maps.black_forest.debug' +local Template = require 'map_gen.maps.black_forest.template' +local CreateParticles = require 'features.create_particles' +local destroy_tree = CreateParticles.destroy_tree +local format_number = require 'util'.format_number +local random = math.random +local floor = math.floor +local ceil = math.ceil +local size = table.size +local pairs = pairs +local raise_event = script.raise_event +local get_aliens = AlienEvolutionProgress.get_aliens +local create_spawner_request = AlienEvolutionProgress.create_spawner_request +local set_timeout_in_ticks = Task.set_timeout_in_ticks + +-- this +local AlienSpawner = {} + +local config + +local memory = { + alien_collision_boxes = {}, +} +local locations_to_scan = { + {x = 0, y = -1.5}, -- up + {x = 1.5, y = 0}, -- right + {x = 0, y = 1.5}, -- bottom + {x = -1.5, y = 0}, -- left +} + +Global.register_init({ + memory = memory, +}, function(tbl) + for name, prototype in pairs(game.entity_prototypes) do + if prototype.type == 'unit' and prototype.subgroup.name == 'enemies' then + tbl.memory.alien_collision_boxes[name] = prototype.collision_box + end + end +end, function(tbl) + memory = tbl.memory +end) + +local trees_to_find = Template.black_forest_trees + +---Triggers mining at the collision_box of the alien, to free it +local do_alien_mining = Token.register(function(params) + local surface = params.surface + local create_entity = surface.create_entity + local find_non_colliding_position = surface.find_non_colliding_position + + local trees = surface.find_entities_filtered({area = params.clear_area, name = trees_to_find}) + + local tree_count = #trees + if tree_count > 0 then + -- with multiple trees opening at once, it will spawn less particles in total per tree + local particle_count + if tree_count == 1 then + particle_count = 15 + elseif tree_count == 2 then + particle_count = 10 + else + particle_count = 5 + end + + for tree_index = tree_count, 1, -1 do + local tree = trees[tree_index] + destroy_tree(surface.create_particle, particle_count, tree.position) + tree.destroy{raise_destroy = true} + end + end + + local spawn_location = params.spawn_location + -- amount is not used for aliens prototypes, it just carries along in the params + local amount = spawn_location.amount + while amount > 0 do + spawn_location.position = find_non_colliding_position(spawn_location.name, spawn_location.position, 2, 0.4) or spawn_location.position + create_entity(spawn_location) + amount = amount - 1 + end +end) + +---Spawns aliens given the parameters. +---@param aliens table index is the name, value is the amount of biters to spawn +---@param force LuaForce of the biters +---@param surface LuaSurface to spawn on +---@param x number +---@param y number +local function spawn_aliens(aliens, force, surface, x, y) + local position = {x = x, y = y} + local count_tiles_filtered = surface.count_tiles_filtered + + local spawn_count = 0 + local alien_collision_boxes = memory.alien_collision_boxes + + for name, amount in pairs(aliens) do + local collision_box = alien_collision_boxes[name] + if not collision_box then + Debug.print_position(position, 'Unable to find prototype data for ' .. name) + break + end + + local left_top = collision_box.left_top + local right_bottom = collision_box.right_bottom + local left_top_x = left_top.x * 1.6 + local left_top_y = left_top.y * 1.6 + local right_bottom_x = right_bottom.x * 1.6 + local right_bottom_y = right_bottom.y * 1.6 + + for i = #locations_to_scan, 1, -1 do + local direction = locations_to_scan[i] + local x_center = direction.x + x + local y_center = direction.y + y + + -- *_center indicates the offset center relative to the location where it should spawn + -- the area is composed of the bounding_box of the alien with a bigger size so it has space to move + local offset_area = { + left_top = { + x = floor(x_center + left_top_x), + y = floor(y_center + left_top_y), + }, + right_bottom = { + x = ceil(x_center + right_bottom_x), + y = ceil(y_center + right_bottom_y), + }, + } + + -- can't spawn properly if void is present + if count_tiles_filtered({area = offset_area, name = 'out-of-map'}) == 0 then + spawn_count = spawn_count + 1 + set_timeout_in_ticks(spawn_count, do_alien_mining, { + surface = surface, + clear_area = offset_area, + spawn_location = { + name = name, + position = {x = x_center, y = y_center}, + force = force, + amount = amount + }, + }) + break + end + end + end +end + +--[[-- + Registers all event handlers. +]] +function AlienSpawner.register(cfg) + config = cfg + local alien_minimum_distance_square = cfg.alien_minimum_distance ^ 2 + local alien_probability = cfg.alien_probability + local hail_hydra = cfg.hail_hydra + local evolution_per_void_removed = cfg.evolution_per_void_removed + + if size(hail_hydra) > 0 then + global.config.hail_hydra.enabled = true + global.config.hail_hydra.hydras = hail_hydra + end + + Event.add(Template.events.on_void_removed, function (event) + local force = game.forces.enemy + local evolution_factor = force.evolution_factor + force.evolution_factor = evolution_factor + evolution_per_void_removed + + local position = event.position + local x = position.x + local y = position.y + + if (x * x + y * y < alien_minimum_distance_square or alien_probability < random()) then + return + end + + spawn_aliens(get_aliens(create_spawner_request(2), force.evolution_factor), force, event.surface, x, y) + end) +end + +function AlienSpawner.get_extra_map_info(cfg) + local multiplier = 10000 + + return [[Alien Spawner, aliens might spawn when chopping wood! +Spawn chance: ]] .. (cfg.alien_probability * 100) .. [[% +Minimum spawn distance: ]] .. cfg.alien_minimum_distance .. [[ tiles +Evolution is increased by ]] ..(multiplier * cfg.evolution_per_void_removed * 100) .. '% per ' .. format_number(multiplier, true) .. [[ mine size +]] +end + +function AlienSpawner.on_init() + if config.evolution_over_time_factor then + game.map_settings.enemy_evolution.time_factor = config.evolution_over_time_factor + end + game.forces.enemy.evolution_factor = config.initial_evolution * 0.01 + game.map_settings.pollution.enabled = false +end + +return AlienSpawner diff --git a/map_gen/maps/black_forest/feature/antigrief.lua b/map_gen/maps/black_forest/feature/antigrief.lua new file mode 100644 index 000000000..3dd1e29bd --- /dev/null +++ b/map_gen/maps/black_forest/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.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/black_forest/feature/black_forest_flame.lua b/map_gen/maps/black_forest/feature/black_forest_flame.lua new file mode 100644 index 000000000..60024a4ce --- /dev/null +++ b/map_gen/maps/black_forest/feature/black_forest_flame.lua @@ -0,0 +1,51 @@ +--- Provides the ability to inform players that solar panels doesn't work underground +-- also handles the freezing of nighttime +-- @module NightTime +-- + + +-- dependencies +local Event = require 'utils.event' + + +-- this +local Flame = {} + +--- Event handler for on_built_entity +-- checks if player placed a solar-panel and displays a popup +-- @param event table containing the on_built_entity event specific attributes +-- +--local function on_built_entity(event) +-- local player = game.get_player(event.player_index) +-- local entity = event.created_entity +-- if (entity.name == 'flamethrower') then +-- require 'features.gui.popup'.player( +-- player, {'diggy.night_time_warning'} +-- ) +-- end +--end + +--- Event handler for on_research_finished +-- sets the force, which the research belongs to, recipe for solar-panel-equipment +-- to false, to prevent wastefully crafting. The technology is needed for further progression +-- @param event table containing the on_research_finished event specific attributes +-- +local function on_research_finished(event) + local force = event.research.force + force.recipes["flamethrower"].enabled = false + force.recipes["flamethrower-ammo"].enabled = false + force.recipes["flamethrower-turret"].enabled = false +end + +--- Setup of on_built_entity and on_research_finished events +-- assigns the two events to the corresponding local event handlers +-- @param config table containing the configurations for NightTime.lua +-- +function Flame.register() + --Event.add(defines.events.on_built_entity, on_built_entity) + Event.add(defines.events.on_research_finished, on_research_finished) +end + + + +return Flame diff --git a/map_gen/maps/black_forest/feature/black_forest_grow.lua b/map_gen/maps/black_forest/feature/black_forest_grow.lua new file mode 100644 index 000000000..96ac16cd9 --- /dev/null +++ b/map_gen/maps/black_forest/feature/black_forest_grow.lua @@ -0,0 +1,154 @@ +-- dependencies +local Event = require 'utils.event' +local random = math.random +local ceil = math.ceil +local RS = require 'map_gen.shared.redmew_surface' +--local LS = game.surface[1] +local pairs = pairs +--local surface = RS.get_surface() +local Grow = {} +local this = {} + +local Global = require 'utils.global' +Global.register(this, function (tbl) + this = tbl +end) + +local TreeKillTypes = {"items","accumulator","ammo-category","ammo-turret","arithmetic-combinator","artillery-turret","artillery-wagon","assembling-machine","beacon","boiler","car","cargo-wagon","constant-combinator","container","curved-rail","decider-combinator","electric-pole","electric-turret","fluid-turret","fluid-wagon","furnace","gate","generator","generator-equipment","heat-pipe","infinity-container","infinity-pipe","inserter","lab","lamp","land-mine","loader","locomotive","logistic-container","mining-drill","offshore-pump","pipe","pipe-to-ground","programmable-speaker","pump","radar","rail-chain-signal","rail-remnants","rail-signal","roboport","solar-panel","splitter","straight-rail","train-stop","transport-belt","underground-belt"} +--[[ +local DontTiles = { + ['out-of-map'] = true, + ['concrete'] = true, + ['hazard-concrete-left'] = true, + ['hazard-concrete-right'] = true, + ['refined-hazard-concrete-right'] = true, + ['refined-hazard-concrete-left'] = true, + ['refined-concrete'] = true, + ['stone-path'] = true, + ['water'] = true, + ['water-green'] = true, + ['water-mud'] = true, + ['water-shallow'] = true, + ['deepwater-green'] = true, + ['deepwater'] = true, +} +--]] + +--- Event handler for on_built_entity +-- checks if player placed a solar-panel and displays a popup +-- @param event table containing the on_built_entity event specific attributes +-- +local function on_tick(event) + local this = this + this.ticks = this.ticks + 1 + if (this.ticks < this.period) then + return + end + this.ticks = 0 + + local surface = RS.get_surface() + local trees = this.trees + if (this.tree_count < 1) then + if surface.count_entities_filtered{type = "tree", limit = 1} == 0 then + this.tree_count = 0 + this.period = 30 * 60 --wait 30 seconds if there weren't any trees + return + end + + trees = surface.find_entities_filtered{type = "tree"} + this.trees = trees + this.tree_count = #trees + --ten minutes = 36000 ticks, 8.33mins = 30000 + this.period = ceil(30000/this.tree_count) + end + + local tree + local tries = 1 + while (tries < 10) do + local i = random(1, this.tree_count) + tree = trees[i] + + trees[i] = trees[this.tree_count] + trees[this.tree_count] = nil + this.tree_count = this.tree_count - 1 + + if tree.valid then + break + end + if this.tree_count < 1 then + return + end + tries = tries + 1 + end + + if not tree.valid then return end + + local position = tree.position + local X = position.x + local Y = position.y + if (surface.count_entities_filtered{type = "tree", position = position} == 1) then + local get_tile = surface.get_tile + local positions_around = { + {X + 1, Y}, + {X - 1, Y}, + {X, Y + 1}, + {X, Y - 1}, + {X + 1, Y + 1}, + {X - 1, Y - 1}, + {X - 1, Y + 1}, + {X + 1, Y - 1}, + } + for _, position in pairs(positions_around) do + local tile = get_tile(position) + --if (not DontTiles[tile.name]) then + if not (tile.hidden_tile or tile.collides_with("water-tile")) then + if (surface.count_entities_filtered{type = {"tree","wall","market"}, position = position} == 0) then + for i, entityd in pairs(surface.find_entities_filtered{position = position, type = TreeKillTypes}) do + if entityd.valid then + entityd.die() + else + --game.print("entity invalid") + end + end + surface.create_entity{name = "tree-0" .. random(1, 3), position = position} + end + end + end + --if (entity.name == 'solar-panel') then + -- require 'features.gui.popup'.player( + -- player, {'diggy.night_time_warning'} + --) + end +end + +--- Event handler for on_research_finished +-- sets the force, which the research belongs to, recipe for solar-panel-equipment +-- to false, to prevent wastefully crafting. The technology is needed for further progression +-- @param event table containing the on_research_finished event specific attributes +-- +local function on_player_joined_game(event) + --surface = RS.get_surface() + +end + +--- Setup of on_built_entity and on_research_finished events +-- assigns the two events to the corresponding local event handlers +-- @param config table containing the configurations for Grow.lua +-- +function Grow.register() + Event.add(defines.events.on_tick, on_tick) + Event.add(defines.events.on_player_joined_game, on_player_joined_game) +end + +--- Sets the daytime to 0.5 and freezes the day/night circle. +-- a daytime of 0.5 is the value where every light and ambient lights are turned on. +-- +function Grow.on_init() + this.trees = {} + this.tree_count = 0 + this.period = 2*60*60 -- start trying after 2 minutes + this.ticks = 0 + --surface = RS.get_surface() +end + +return Grow diff --git a/map_gen/maps/black_forest/feature/black_forest_hole.lua b/map_gen/maps/black_forest/feature/black_forest_hole.lua new file mode 100644 index 000000000..68caaec57 --- /dev/null +++ b/map_gen/maps/black_forest/feature/black_forest_hole.lua @@ -0,0 +1,294 @@ +--[[-- 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.black_forest.template' +local ScoreTracker = require 'utils.score_tracker' +local Command = require 'utils.command' +local CreateParticles = require 'features.create_particles' +local destroy_tree = CreateParticles.destroy_tree +local Ranks = require 'resources.ranks' +local random = math.random +local tonumber = tonumber +local pairs = pairs +local is_black_forest_tree = Template.is_black_forest_tree +local raise_event = script.raise_event +local mine_size_name = 'mine-size' + +-- this +local black_forestHole = {} +local config + +-- 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 black_forest black_forest hole for a given sand-rock-big, rock-big or rock-huge. +---@param entity LuaEntity +local function black_forest_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 + count = count + 1 + out_of_map_found[count] = {x = x - 1, y = y} + end + + + + if (get_tile(x - 1, y-1).name == 'out-of-map') then + count = count + 1 + out_of_map_found[count] = {x = x - 1, y = y-1} + end + if (get_tile(x - 1, y+1).name == 'out-of-map') then + count = count + 1 + out_of_map_found[count] = {x = x - 1, y = y+1} + end + if (get_tile(x + 1, y+1).name == 'out-of-map') then + count = count + 1 + out_of_map_found[count] = {x = x + 1, y = y+1} + end + if (get_tile(x + 1, y-1).name == 'out-of-map') then + out_of_map_found[count + 1] = {x = x + 1, y = y-1} + end + + + + + + + + for i = #out_of_map_found, 1, -1 do + local void_position = out_of_map_found[i] + tiles[i] = {name = 'grass-' .. random(1, 4), position = void_position} + local predicted = random() + if predicted < 0.2 then + rocks[i] = {name = 'tree-01', position = void_position} + elseif predicted < 0.6 then + rocks[i] = {name = 'tree-02', position = void_position} + else + rocks[i] = {name = 'tree-03', position = void_position} + 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 = 'grass-' .. random(1, 4), position = tile.position} + end + end + + Template.insert(surface, new_tiles, {}) +end +Command.add('black_forest-clear-void', { + description = {'command_description.black_forest_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 = 'grass-' .. random(1, 4), 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 black_forestHole.register(cfg) + ScoreTracker.register(mine_size_name, {'black_forest.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 + robot_mining.damage = cfg.robot_initial_mining_damage + + Event.add(defines.events.on_entity_died, function (event) + local entity = event.entity + local name = entity.name + if not is_black_forest_tree(name) then + return + end + if event.loot then + event.loot.clear() + end + black_forest_hole(entity) + + end) + + Event.add(defines.events.script_raised_destroy, function (event) + local entity = event.entity + local name = entity.name + if not is_black_forest_tree(name) then + return + end + black_forest_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_black_forest_tree(name) then + return + end + destroy_tree(entity.surface.create_particle, 10, entity.position) + entity.destroy{raise_destroy = true} + end) + + Event.add(defines.events.on_robot_mined_entity, function (event) + local entity = event.entity + local name = entity.name + + if not is_black_forest_tree(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 + entity.die(force) + return + end + entity.destroy() + + local rock = create_entity({name = name, position = 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_black_forest_tree(name) then + return + end + + --event.buffer.clear() + + black_forest_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 black_forestHole.on_init() + game.forces.player.technologies['landfill'].enabled = config.allow_landfill_research + game.forces.player.technologies['atomic-bomb'].enabled = false +end + +return black_forestHole diff --git a/map_gen/maps/black_forest/feature/coin_gathering.lua b/map_gen/maps/black_forest/feature/coin_gathering.lua new file mode 100644 index 000000000..555b4813e --- /dev/null +++ b/map_gen/maps/black_forest/feature/coin_gathering.lua @@ -0,0 +1,126 @@ +--[[-- info + Provides the ability to collect coins. +]] + +-- dependencies +local Event = require 'utils.event' +local Debug = require 'map_gen.maps.black_forest.debug' +local Template = require 'map_gen.maps.black_forest.template' +local Perlin = require 'map_gen.shared.perlin_noise' +local random = math.random +local ceil = math.ceil +local pairs = pairs + +-- this +local CoinGathering = {} + +function CoinGathering.register(config) + local seed + local noise_variance = config.noise_variance + local function get_noise(surface, x, y) + seed = seed or surface.map_gen_settings.seed + surface.index + 300 + return Perlin.noise(x * noise_variance * 0.9, y * noise_variance * 1.1, seed) + end + + local distance_required = config.minimal_treasure_chest_distance * config.minimal_treasure_chest_distance + + local treasure_chest_noise_threshold = config.treasure_chest_noise_threshold + Event.add(Template.events.on_void_removed, function (event) + local position = event.position + local x = position.x + local y = position.y + + if (x * x + y * y <= distance_required) then + return + end + + local surface = event.surface + + if get_noise(surface, x, y) < treasure_chest_noise_threshold then + return + end + + -- local chest = surface.create_entity({name = 'wooden-chest', position = position, force = game.forces.player}) + local chest = surface.create_entity({name = 'wooden-chest', position = position, force = 'neutral'}) + if not chest then + return + end + + local insert = chest.insert + for name, prototype in pairs(config.treasure_chest_raffle) do + if random() <= prototype.chance then + insert({name = name, count = random(prototype.min, prototype.max)}) + end + end + end) + + local modifiers = config.alien_coin_modifiers + local alien_coin_drop_chance = config.alien_coin_drop_chance + + Event.add(defines.events.on_entity_died, function (event) + local entity = event.entity + local force = entity.force + if force.name ~= 'enemy' or random() > alien_coin_drop_chance then + return + end + + local modifier = modifiers[entity.name] or 1 + local evolution_multiplier = force.evolution_factor + local count = random( + ceil(2 * evolution_multiplier * modifier), + ceil(5 * evolution_multiplier * modifier) + ) + + local coin = entity.surface.create_entity({ + name = 'item-on-ground', + position = entity.position, + stack = {name = 'coin', count = count} + }) + + if coin and coin.valid then + coin.to_be_looted = true + end + end) + + local mining_coin_chance = config.mining_coin_chance + local mining_coin_amount_min = config.mining_coin_amount.min + local mining_coin_amount_max = config.mining_coin_amount.max + Event.add(defines.events.on_pre_player_mined_item, function (event) + local entity = event.entity + if entity.type ~= 'tree' then + return + end + + if random() > mining_coin_chance then + return + end + + local coin = entity.surface.create_entity({ + name = 'item-on-ground', + position = entity.position, + stack = {name = 'coin', count = random(mining_coin_amount_min, mining_coin_amount_max)} + }) + + if coin and coin.valid then + coin.to_be_looted = true + end + end) + + if config.display_chest_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 + local sq_x = x * x + for y = area.left_top.y, area.left_top.y + 31 do + if sq_x + y * y >= distance_required and get_noise(surface, x, y) >= treasure_chest_noise_threshold then + Debug.print_grid_value('chest', surface, {x = x, y = y}, nil, nil, true) + end + end + end + end) + end +end + +return CoinGathering diff --git a/map_gen/maps/black_forest/feature/experience.lua b/map_gen/maps/black_forest/feature/experience.lua new file mode 100644 index 000000000..ba1e08a84 --- /dev/null +++ b/map_gen/maps/black_forest/feature/experience.lua @@ -0,0 +1,632 @@ +-- dependencies +local Event = require 'utils.event' +local Game = require 'utils.game' +local Global = require 'utils.global' +local Toast = require 'features.gui.toast' +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' + +-- 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, {'black_forest.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 tree_01_xp +local tree_02_xp +local tree_03_xp + +---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 + 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 name == 'tree-01' then + exp = tree_01_xp + floor(level / 5) + elseif name == 'tree-02' then + exp = tree_02_xp + floor(level / 5) + elseif name == 'tree-03' then + exp = tree_03_xp + floor(level / 5) + end + + if exp == 0 then + return + end + + local text = {'', '[img=entity/' .. name .. '] ', {'black_forest.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] ', {'black_forest.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] ', {'black_forest.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 + 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 entity_name == 'tree-01' then + exp = floor((tree_01_xp + level * 0.2) * 0.5) + elseif entity_name == 'tree-02' then + exp = floor((tree_02_xp + level * 0.2) * 0.5) + elseif entity_name == 'tree-03' then + exp = floor((tree_03_xp + 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 .. '] ', {'black_forest.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 .. '] ', {'black_forest.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]', {'black_forest.float_xp_drain', exp}} + game.print({'black_forest.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 = {'black_forest.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 {'black_forest.gui_reward_item'} or {'black_forest.gui_reward_buff'} + Gui.clear(frame) + + local heading_table = frame.add(table_column_layout) + apply_heading_style(heading_table.add({type = 'label', caption = {'black_forest.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 = {'black_forest.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 = 'black_forest.Experience.Frame.Progress.Level', + caption = {'black_forest.gui_progress_caption'} + } + ).style + ) + local level_progressbar = flow.add({type = 'progressbar', tooltip = {'black_forest.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 = {'black_forest.gui_tabel_level', current_item_level} + end + + local level_column = + list.add( + { + type = 'label', + caption = level_caption, + tooltip = {'black_forest.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 = {'black_forest.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 = {'black_forest.gui_buff_mining', effect_value, effect_max * 100} + elseif name == 'inventory_slot' then + buff_caption = {'black_forest.gui_buff_inv', effect_value, effect_max} + elseif name == 'health_bonus' then + buff_caption = {'black_forest.gui_buff_health', effect_value, effect_max} + else + buff_caption = {'black_forest.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['black_forest.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 = 'black_forest.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 = 'black_forest.Experience.Button', caption = {'black_forest.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 = 'black_forest.Experience.Button', + type = 'sprite-button', + sprite = 'entity/market', + tooltip = {'black_forest.gui_experience_button_tip'} + } + ) +end + +Gui.allow_player_to_toggle_top_element_visibility('black_forest.Experience.Button') + +Gui.on_click('black_forest.Experience.Button', toggle) +Gui.on_custom_close( + 'black_forest.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['black_forest.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, {'black_forest.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, {'black_forest.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 + tree_01_xp = config.XP['tree-01'] + tree_02_xp = config.XP['tree-02'] + tree_03_xp = config.XP['tree-03'] +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/black_forest/feature/refresh_map.lua b/map_gen/maps/black_forest/feature/refresh_map.lua new file mode 100644 index 000000000..d91c3645b --- /dev/null +++ b/map_gen/maps/black_forest/feature/refresh_map.lua @@ -0,0 +1,88 @@ +--[[-- info + Provides the ability to refresh the map and generate darkness. +]] + +-- dependencies +local Event = require 'utils.event' + +-- this +local RefreshMap = {} + +--[[-- + Registers all event handlers. +]] +function RefreshMap.register(config) + Event.add(defines.events.on_chunk_generated, function (event) + local tiles = {} + + local left_top = event.area.left_top + local left_top_x = left_top.x + local left_top_y = left_top.y + + local count = 0 + for x = 0, 31, 1 do + for y = 0, 31, 1 do + local target_x = left_top_x + x + local target_y = left_top_y + y + local tile = 'out-of-map' + local target_x_1000 = target_x % 1000 + + if (config.river) then + if target_y > -8 and target_y < 9 then + tile = 'deepwater-green' + end + if target_y < -6 and target_y > -10 and target_x_1000 > 749 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + if target_y < -7 and target_y > -10 and target_x_1000 < 251 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + if target_y < -6 and target_y > -10 and target_x_1000 > 249 and target_x_1000 < 501 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + if target_y < -7 and target_y > -10 and target_x_1000 > 499 and target_x_1000 < 751 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + + if target_y < 10 and target_y > 7 and target_x_1000 > 699 +25 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + if target_y < 10 and target_y > 7 and target_x_1000 < 51 -25 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + if target_y < 10 and target_y > 7 and target_x_1000 > 199 +25 and target_x_1000 < 551 -25 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + if target_y < 10 and target_y > 6 and target_x_1000 > 549 -25 and target_x_1000 < 701 +25 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end + if target_y < 10 and target_y > 6 and target_x_1000 > 49 -25 and target_x_1000 < 201 +25 then + tile = 'dirt-6' + event.surface.create_entity{name='tree-01', position={target_x +0.5, target_y+0.5}} + end--[[--]] + end + if target_x > -2 and target_x < 1 and target_y > -2 and target_y < 1 then + tile = 'lab-dark-1' + end + count = count + 1 + tiles[count] = { + name = tile, + position = {x = target_x, y = target_y} + } + end + end + + event.surface.set_tiles(tiles) + + end) +end + +return RefreshMap diff --git a/map_gen/maps/black_forest/feature/scattered_resources.lua b/map_gen/maps/black_forest/feature/scattered_resources.lua new file mode 100644 index 000000000..311d46e73 --- /dev/null +++ b/map_gen/maps/black_forest/feature/scattered_resources.lua @@ -0,0 +1,262 @@ +--[[-- info + Provides the ability to spawn random ores all over the place. +]] + +-- dependencies +local Event = require 'utils.event' +local Debug = require 'map_gen.maps.black_forest.debug' +local Template = require 'map_gen.maps.black_forest.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.03)) * 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!]] +end + +return ScatteredResources diff --git a/map_gen/maps/black_forest/feature/setup_player.lua b/map_gen/maps/black_forest/feature/setup_player.lua new file mode 100644 index 000000000..33b3ce383 --- /dev/null +++ b/map_gen/maps/black_forest/feature/setup_player.lua @@ -0,0 +1,29 @@ +local Event = require 'utils.event' + +local SetupPlayer = {} +local config + +function SetupPlayer.register(cfg) + config = cfg + Event.add( + defines.events.on_player_created, + function() + local redmew_player_create = global.config.player_create + + if #cfg.starting_items > 0 then + redmew_player_create.starting_items = cfg.starting_items + end + + if not _DEBUG then + redmew_player_create.cheats = cfg.cheats + end + end + ) +end + +function SetupPlayer.on_init() + game.forces.player.manual_mining_speed_modifier = config.initial_mining_speed_bonus + game.forces.player.character_resource_reach_distance_bonus = 1 +end + +return SetupPlayer diff --git a/map_gen/maps/black_forest/feature/simple_room_generator.lua b/map_gen/maps/black_forest/feature/simple_room_generator.lua new file mode 100644 index 000000000..2d6e51338 --- /dev/null +++ b/map_gen/maps/black_forest/feature/simple_room_generator.lua @@ -0,0 +1,119 @@ +--[[-- info + Provides the ability to make a simple room with contents +]] + +-- dependencies +local Template = require 'map_gen.maps.black_forest.template' +local Event = require 'utils.event' +local Debug = require 'map_gen.maps.black_forest.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 +-- this +local SimpleRoomGenerator = {} + +local do_spawn_tile = Token.register(function(params) + template_insert(params.surface, {params.tile}, {}) +end) + +local trees_lookup = Template.black_forest_trees + +local do_mine = Token.register(function(params) + local surface = params.surface + local position = params.position + local trees = surface.find_entities_filtered({position = position, name = trees_lookup}) + + local tree_count = #trees + if tree_count == 0 then + return + end + + for i = tree_count, 1, -1 do + local tree = trees[i] + tree.destroy{raise_destroy = true} + 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 = 'water-green', position = position}}) + return + end + + if 'deepwater' == 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 = 'deepwater-green', 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 + + 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.x +0.5 , position.y +0.5}) + 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, chopping around might open clearings!' +end + +return SimpleRoomGenerator diff --git a/map_gen/maps/black_forest/feature/starting_zone.lua b/map_gen/maps/black_forest/feature/starting_zone.lua new file mode 100644 index 000000000..1924c7a53 --- /dev/null +++ b/map_gen/maps/black_forest/feature/starting_zone.lua @@ -0,0 +1,103 @@ +--[[-- 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.black_forest.template' +local Retailer = require 'features.retailer' +--local black_forestCaveCollapse = require 'map_gen.maps.black_forest.feature.black_forest_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 black_forest.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 + insert(tiles, {name = 'grass-' .. random(1, 4), position = {x = x, y = y}}) + else + insert(tiles, {name = 'stone-path', position = {x = x, y = y}}) + end + + if (distance > rock_range) then + insert(rocks, {name = 'tree-01', position = {x = x+0.5, y = y+0.5}}) + end + + -- hack to avoid starting area from collapsing + --if (distance > stress_hack) then + --black_forestCaveCollapse.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', 'Black Forest 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/black_forest/feature/weapon_balance.lua b/map_gen/maps/black_forest/feature/weapon_balance.lua new file mode 100644 index 000000000..9c171df31 --- /dev/null +++ b/map_gen/maps/black_forest/feature/weapon_balance.lua @@ -0,0 +1,87 @@ +local Event = require 'utils.event' +local floor = math.floor + +local player_ammo_starting_modifiers = { + ['artillery-shell'] = -0.75, + ['biological'] = -0.5, + ['bullet'] = -0.25, + ['cannon-shell'] = -0.15, + ['capsule'] = -0.5, + ['electric'] = -0.5, + ['flamethrower'] = -0, + ['grenade'] = -0.5, + ['landmine'] = -0.33, + ['laser'] = -0.50, + ['melee'] = 1, + ['rocket'] = -0.4, + ['shotgun-shell'] = -0.20 +} + +local player_ammo_research_modifiers = { + ['artillery-shell'] = -0.75, + ['biological'] = -0.5, + ['bullet'] = -0.20, + ['cannon-shell'] = -0.15, + ['capsule'] = -0.5, + ['electric'] = -0.6, + ['flamethrower'] = -0, + ['grenade'] = -0.5, + ['landmine'] = -0.5, + ['laser'] = -0.50, + ['melee'] = -0.5, + ['rocket'] = -0.4, + ['shotgun-shell'] = -0.20 +} + +local player_turrets_research_modifiers = { + ['gun-turret'] = -0.5, + ['laser-turret'] = -0.50, + ['flamethrower-turret'] = -0.25 +} + +local function init_weapon_damage() + local forces = game.forces + local p_force = forces.player + + for k, v in pairs(player_ammo_starting_modifiers) do + p_force.set_ammo_damage_modifier(k, v) + end +end + +local function research_finished(event) + local r = event.research + local p_force = r.force + + for _, e in ipairs(r.effects) do + local t = e.type + + if t == 'ammo-damage' then + local category = e.ammo_category + local factor = player_ammo_research_modifiers[category] + + if factor then + local current_m = p_force.get_ammo_damage_modifier(category) + local m = e.modifier + p_force.set_ammo_damage_modifier(category, floor((current_m + factor * m)*10)*0.1) + end + elseif t == 'turret-attack' then + local category = e.turret_id + local factor = player_turrets_research_modifiers[category] + + if factor then + local current_m = p_force.get_turret_attack_modifier(category) + local m = e.modifier + p_force.set_turret_attack_modifier(category, floor((current_m + factor * m)*10)*0.1) + end + end + end +end + +local weapon_balance = {} + +function weapon_balance.register() + Event.on_init(init_weapon_damage) + Event.add(defines.events.on_research_finished, research_finished) +end + +return weapon_balance diff --git a/map_gen/maps/black_forest/orepattern/clusters.lua b/map_gen/maps/black_forest/orepattern/clusters.lua new file mode 100644 index 000000000..8ecae9bc8 --- /dev/null +++ b/map_gen/maps/black_forest/orepattern/clusters.lua @@ -0,0 +1,51 @@ + +-- defines all ore patches to be generated. Add as many clusters as +-- needed. Clusters listed first have a higher placement priority over +-- the latter clusters +-- +-- TODO update and document all configuration settings +-- +-- noise types: +-- cluster: same as vanilla factorio generation +-- skip: skips this cluster +-- connected_tendril: long ribbons of ore +-- fragmented_tendril: long ribbons of ore that occur when inside another +-- region of ribbons +-- +-- noise source types and configurations +-- perlin: same as vanilla factorio generation +-- variance: increase to make patches closer together and smaller +-- note that this is the inverse of the cluster_mode variance +-- threshold: increase to shrink size of patches +-- simplex: similar to perlin +-- zero: does nothing with this source +-- one: adds the weight directly to the noise calculation +return { + { + yield=1.0, + min_distance=30, + distance_richness=7, + noise_settings = { + type = "cluster", + threshold = 0.40, + sources = { + {variance=25, weight = 1, offset = 000, type="perlin"}, + } + }, + weights = { + ['coal'] = 20, + ['copper-ore'] = 215, + ['iron-ore'] = 389, + ['stone'] = 212, + ['uranium-ore'] = 21, + ['crude-oil'] = 8, + }, + distances = { + ['coal'] = 44, + ['copper-ore'] = 18, + ['iron-ore'] = 18, + ['stone'] = 15, + ['uranium-ore'] = 86, + ['crude-oil'] = 57, + }, }, +} diff --git a/map_gen/maps/black_forest/orepattern/tendrils.lua b/map_gen/maps/black_forest/orepattern/tendrils.lua new file mode 100644 index 000000000..b9e6bf51f --- /dev/null +++ b/map_gen/maps/black_forest/orepattern/tendrils.lua @@ -0,0 +1,220 @@ + +-- defines all ore patches to be generated. Add as many clusters as +-- needed. Clusters listed first have a higher placement priority over +-- the latter clusters +-- +-- TODO update and document all configuration settings +-- +-- noise types: +-- cluster: same as vanilla factorio generation +-- skip: skips this cluster +-- connected_tendril: long ribbons of ore +-- fragmented_tendril: long ribbons of ore that occur when inside another +-- region of ribbons +-- +-- noise source types and configurations +-- perlin: same as vanilla factorio generation +-- variance: increase to make patches closer together and smaller +-- note that this is the inverse of the cluster_mode variance +-- threshold: increase to shrink size of patches +-- simplex: similar to perlin +-- zero: does nothing with this source +-- one: adds the weight directly to the noise calculation + +return { + { -- tendril default large + yield=1.5, + min_distance=40, + distance_richness=7, + color={r=255/255, g=0/255, b=255/255}, + noise_settings = { + type = "connected_tendril", + threshold = 0.05, + sources = { + {variance=350*2, weight = 1.000, offset = 000, type="simplex"}, + {variance=200*2, weight = 0.350, offset = 150, type="simplex"}, + {variance=050*2, weight = 0.050, offset = 300, type="simplex"}, + {variance=020*2, weight = 0.015, offset = 450, type="simplex"}, + } + }, + weights = { + ['coal'] = 160, + ['copper-ore'] = 280, + ['iron-ore'] = 395, + ['stone'] = 135, + ['uranium-ore'] = 6, + }, + distances = { + ['coal'] = 16, + ['copper-ore'] = 18, + ['iron-ore'] = 18, + ['stone'] = 15, + ['uranium-ore'] = 120, + }, }, + { -- tendril default small + yield=1.0, + min_distance=25, + distance_richness=7, + color={r=255/255, g=255/255, b=0/255}, + noise_settings = { + type = "connected_tendril", + threshold = 0.05, + sources = { + {variance=120, weight = 1.000, offset = 000, type="simplex"}, + {variance=060, weight = 0.300, offset = 150, type="simplex"}, + {variance=040, weight = 0.200, offset = 300, type="simplex"}, + {variance=020, weight = 0.090, offset = 450, type="simplex"}, + } + }, + weights = { + ['coal'] = 160, + ['copper-ore'] = 215, + ['iron-ore'] = 389, + ['stone'] = 100, + ['uranium-ore'] = 30, + }, + distances = { + ['coal'] = 16, + ['copper-ore'] = 18, + ['iron-ore'] = 18, + ['stone'] = 15, + ['uranium-ore'] = 120, + }, + }, + { -- tendril default fragments coal + yield=0.25, + min_distance=10, + distance_richness=7, + color={r=0/255, g=0/255, b=0/255}, + noise_settings = { + type = "fragmented_tendril", + threshold = 0.05, + discriminator_threshold = 0.4, + sources = { + {variance=050, weight = 1.000, offset = 600, type="simplex"}, + {variance=030, weight = 0.500, offset = 750, type="simplex"}, + {variance=020, weight = 0.250, offset = 900, type="simplex"}, + {variance=010, weight = 0.100, offset =1050, type="simplex"}, + }, + discriminator = { + {variance=120, weight = 1.000, offset = 000, type="simplex"}, + {variance=060, weight = 0.300, offset = 150, type="simplex"}, + {variance=040, weight = 0.200, offset = 300, type="simplex"}, + {variance=020, weight = 0.090, offset = 450, type="simplex"}, + }, + }, + weights = { + ['coal'] = 1, + }, + distances = { + ['coal'] = 16, + }, + }, + { -- tendril default fragments iron + yield=0.25, + min_distance=10, + distance_richness=7, + color={r=0/255, g=140/255, b=255/255}, + noise_settings = { + type = "fragmented_tendril", + threshold = 0.05, + discriminator_threshold = 0.4, + sources = { + {variance=050, weight = 1.000, offset = 600, type="simplex"}, + {variance=030, weight = 0.500, offset = 750, type="simplex"}, + {variance=020, weight = 0.250, offset = 900, type="simplex"}, + {variance=010, weight = 0.100, offset =1050, type="simplex"}, + }, + discriminator = { + {variance=120, weight = 1.000, offset = 000, type="simplex"}, + {variance=060, weight = 0.300, offset = 150, type="simplex"}, + {variance=040, weight = 0.200, offset = 300, type="simplex"}, + {variance=020, weight = 0.090, offset = 450, type="simplex"}, + }, + }, + weights = { + ['iron-ore'] = 389, + }, + distances = { + ['iron-ore'] = 18, + }, + }, + { -- tendril default fragments copper + yield=0.25, + min_distance=10, + distance_richness=7, + color={r=255/255, g=55/255, b=0/255}, + noise_settings = { + type = "fragmented_tendril", + threshold = 0.05, + discriminator_threshold = 0.4, + sources = { + {variance=050, weight = 1.000, offset = 600, type="simplex"}, + {variance=030, weight = 0.500, offset = 750, type="simplex"}, + {variance=020, weight = 0.250, offset = 900, type="simplex"}, + {variance=010, weight = 0.100, offset =1050, type="simplex"}, + }, + discriminator = { + {variance=120, weight = 1.000, offset = 000, type="simplex"}, + {variance=060, weight = 0.300, offset = 150, type="simplex"}, + {variance=040, weight = 0.200, offset = 300, type="simplex"}, + {variance=020, weight = 0.090, offset = 450, type="simplex"}, + }, + }, + weights = { + ['copper-ore'] = 215, + }, + distances = { + ['copper-ore'] = 18, + }, + }, + { -- tendril default fragments stone + yield=0.25, + min_distance=10, + distance_richness=7, + color={r=100/255, g=100/255, b=100/255}, + noise_settings = { + type = "fragmented_tendril", + threshold = 0.05, + discriminator_threshold = 0.4, + sources = { + {variance=050, weight = 1.000, offset = 600, type="simplex"}, + {variance=030, weight = 0.500, offset = 750, type="simplex"}, + {variance=020, weight = 0.250, offset = 900, type="simplex"}, + {variance=010, weight = 0.100, offset =1050, type="simplex"}, + }, + discriminator = { + {variance=120, weight = 1.000, offset = 000, type="simplex"}, + {variance=060, weight = 0.300, offset = 150, type="simplex"}, + {variance=040, weight = 0.200, offset = 300, type="simplex"}, + {variance=020, weight = 0.090, offset = 450, type="simplex"}, + }, + }, + weights = { + ['stone'] = 1, + }, + distances = { + ['stone'] = 15, + }, + }, + { -- crude oil + yield=1.7, + min_distance=57, + distance_richness=7, + color={r=0/255, g=255/255, b=255/255}, + noise_settings = { + type = "cluster", + threshold = 0.40, + sources = { + {variance=25, weight = 1, offset = 000, type="perlin"}, + }, + }, + weights = { + ['skip'] = 990, + ['crude-oil'] = 10, + }, + distances = { + ['crude-oil'] = 57, + }, + }, +} diff --git a/map_gen/maps/black_forest/orepattern/tendrils_impure.lua b/map_gen/maps/black_forest/orepattern/tendrils_impure.lua new file mode 100644 index 000000000..59438ae81 --- /dev/null +++ b/map_gen/maps/black_forest/orepattern/tendrils_impure.lua @@ -0,0 +1,208 @@ +-- defines all ore patches to be generated. Add as many clusters as +-- needed. Clusters listed first have a higher placement priority over +-- the latter clusters +-- +-- TODO update and document all configuration settings +-- +-- noise types: +-- cluster: same as vanilla factorio generation +-- skip: skips this cluster +-- connected_tendril: long ribbons of ore +-- fragmented_tendril: long ribbons of ore that occur when inside another +-- region of ribbons +-- +-- noise source types and configurations +-- perlin: same as vanilla factorio generation +-- variance: increase to make patches closer together and smaller +-- note that this is the inverse of the cluster_mode variance +-- threshold: increase to shrink size of patches +-- simplex: similar to perlin +-- zero: does nothing with this source +-- one: adds the weight directly to the noise calculation +-- +-- weights: recommend having resource weights for each cluster add up to 1000 +-- so that it is apparent that every 10 weight = 1%. eg. weight 860 (86%) + weight 80 (8%) + weight 60 (6%) = 100% + +return { + { -- tendril medium large impure iron + yield=1.15, + min_distance=25, + distance_richness=9, + color={r=0/255, g=140/255, b=255/255}, + noise_settings = { + type = "connected_tendril", + threshold = 0.05, + sources = { + {variance=350, weight = 1.000, offset = 000, type="simplex"}, + {variance=200, weight = 0.350, offset = 150, type="simplex"}, + {variance=050, weight = 0.050, offset = 300, type="simplex"}, + {variance=020, weight = 0.015, offset = 450, type="simplex"}, + } + }, + weights = { + ['iron-ore'] = 860, + ['coal'] = 19, + ['stone'] = 40, + }, + distances = { + ['coal'] = 40, + ['iron-ore'] = 18, + ['stone'] = 15, + }, + }, + { -- tendril medium large impure copper + yield=0.92, + min_distance=25, + distance_richness=9, + color={r=255/255, g=55/255, b=0/255}, + noise_settings = { + type = "connected_tendril", + threshold = 0.05, + sources = { + {variance=350, weight = 1.000, offset = 000, type="simplex"}, + {variance=200, weight = 0.350, offset = 150, type="simplex"}, + {variance=050, weight = 0.050, offset = 300, type="simplex"}, + {variance=020, weight = 0.015, offset = 450, type="simplex"}, + } + }, + weights = { + ['copper-ore'] = 860, + ['coal'] = 19, + ['stone'] = 50, + }, + distances = { + ['coal'] = 22, + ['copper-ore'] = 18, + ['stone'] = 15, + }, + }, + { -- tendril medium impure coal + yield=0.5, + min_distance=25, + distance_richness=9, + color={r=0/255, g=0/255, b=0/255}, + noise_settings = { + type = "connected_tendril", + threshold = 0.03, + sources = { + {variance=350, weight = 1.000, offset = 000, type="simplex"}, + {variance=200, weight = 0.350, offset = 150, type="simplex"}, + {variance=050, weight = 0.050, offset = 300, type="simplex"}, + {variance=020, weight = 0.015, offset = 450, type="simplex"}, + }, + }, + weights = { + ['coal'] = 180, + ['iron-ore'] = 160, + ['stone'] = 35, + }, + distances = { + ['coal'] = 16, + ['iron-ore'] = 18, + ['stone'] = 15, + }, + }, + { -- tendril medium impure stone + yield=0.35, + min_distance=25, + distance_richness=9, + color={r=100/255, g=100/255, b=100/255}, + noise_settings = { + type = "connected_tendril", + threshold = 0.028, + sources = { + {variance=350, weight = 1.000, offset = 000, type="simplex"}, + {variance=200, weight = 0.350, offset = 150, type="simplex"}, + {variance=050, weight = 0.050, offset = 300, type="simplex"}, + {variance=020, weight = 0.015, offset = 450, type="simplex"}, + } + }, + weights = { + ['stone'] = 500, + ['copper-ore'] = 126, + ['coal'] = 14, + }, + distances = { + ['coal'] = 16, + ['copper-ore'] = 18, + ['stone'] = 15, + }, + }, + { -- tendril small uranium + yield=0.2, + min_distance=86, + distance_richness=9, + color={r=0/255, g=0/255, b=0/255}, + noise_settings = { + type = "connected_tendril", + threshold = 0.025, + sources = { + {variance=120, weight = 1.000, offset = 000, type="simplex"}, + {variance=060, weight = 0.300, offset = 150, type="simplex"}, + {variance=040, weight = 0.200, offset = 300, type="simplex"}, + {variance=020, weight = 0.090, offset = 450, type="simplex"}, + } + }, + weights = { + ['uranium-ore'] = 1, + }, + distances = { + ['uranium-ore'] = 86, + }, + }, + { -- scattered tendril fragments + yield=0.2, + min_distance=10, + distance_richness=7, + color={r=0/255, g=0/255, b=0/255}, + noise_settings = { + type = "fragmented_tendril", + threshold = 0.06, + discriminator_threshold = 1.2, + sources = { + {variance=025, weight = 1.000, offset = 600, type="simplex"}, + {variance=015, weight = 0.500, offset = 750, type="simplex"}, + {variance=010, weight = 0.250, offset = 900, type="simplex"}, + {variance=05, weight = 0.100, offset =1050, type="simplex"}, + }, + discriminator = { + {variance=120, weight = 1.000, offset = 000, type="simplex"}, + {variance=060, weight = 0.300, offset = 150, type="simplex"}, + {variance=040, weight = 0.200, offset = 300, type="simplex"}, + {variance=020, weight = 0.090, offset = 450, type="simplex"}, + }, + }, + weights = { + ['coal'] = 10, + ['copper-ore'] = 272, + ['iron-ore'] = 454, + ['stone'] = 80, + }, + distances = { + ['coal'] = 16, + ['iron-ore'] = 18, + ['copper-ore'] = 18, + ['stone'] = 15, + }, + }, + { -- crude oil + yield=1.7, + min_distance=57, + distance_richness=9, + color={r=0/255, g=255/255, b=255/255}, + noise_settings = { + type = "cluster", + threshold = 0.40, + sources = { + {variance=25, weight = 1, offset = 000, type="perlin"}, + }, + }, + weights = { + ['skip'] = 990, + ['crude-oil'] = 10, + }, + distances = { + ['crude-oil'] = 57, + }, + }, +} diff --git a/map_gen/maps/black_forest/scenario.lua b/map_gen/maps/black_forest/scenario.lua new file mode 100644 index 000000000..7000325de --- /dev/null +++ b/map_gen/maps/black_forest/scenario.lua @@ -0,0 +1,87 @@ +-- dependencies +local Config = require 'map_gen.maps.black_forest.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.black_forest_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.black_forest_scenario_registered then + error('Cannot register the black_forest scenario multiple times.') + return + end + + -- disabled redmew features for black_forest + 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 = require ('map_gen.maps.black_forest.feature.' .. feature_name) + if ('function' ~= type(feature.register)) then + error('Feature ' .. feature_name .. ' did not define a register function.') + end + + feature.register(feature_config) + + if ('function' == type(feature.get_extra_map_info)) then + 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 = {'grass-1','grass-2','grass-3','grass-4'} + require ('map_gen.shared.change_landfill_tile')(landfill_tiles) + + ScenarioInfo.set_map_name('black_forest') + ScenarioInfo.set_map_description([[There are many trees! +Trees will grow back, be careful! +Maybe try concrete to stop them.]]) + + global.black_forest_scenario_registered = true +end + +return Scenario diff --git a/map_gen/maps/black_forest/template.lua b/map_gen/maps/black_forest/template.lua new file mode 100644 index 000000000..8077e6fc4 --- /dev/null +++ b/map_gen/maps/black_forest/template.lua @@ -0,0 +1,174 @@ +-- dependencies +local Task = require 'utils.task' +local Token = require 'utils.token' +local Event = require 'utils.event' +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 + + 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 + created_entities[created_entities_count] = create_entity(data.entities[i]) + 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.black_forest_trees = {'tree-01', 'tree-02', 'tree-03'} + +local black_forest_trees_by_name = { + ['tree-01'] = true, + ['tree-02'] = true, + ['tree-03'] = true, +} + +---Returns true if the entity name is that of a black_forest tree. +---@param entity_name string +function Template.is_black_forest_tree(entity_name) + return black_forest_trees_by_name[entity_name] +end + +return Template diff --git a/map_gen/maps/blackforest.lua b/map_gen/maps/blackforest.lua new file mode 100644 index 000000000..513ad4359 --- /dev/null +++ b/map_gen/maps/blackforest.lua @@ -0,0 +1,4 @@ +-- blackforest is based on diggy. +-- authors Diggy: Linaori, valansch +-- authors blackforest: Bienenstock +require 'map_gen.maps.black_forest.scenario'.register() diff --git a/map_selection.sample.lua b/map_selection.sample.lua index e03b52a73..340319c64 100644 --- a/map_selection.sample.lua +++ b/map_selection.sample.lua @@ -25,6 +25,7 @@ return require 'map_gen.maps.default' beach connected_dots crosses + blackforest danger_ores diagonal_ribbon double_beach