1
0
mirror of https://github.com/Refactorio/RedMew.git synced 2025-03-17 21:08:08 +02:00

April fools maps (#1404)

* Upload pinguin scenario

* Fix relative path

* Pinguin scenario modularization

* Update enemy_turrets.lua

Added energy interface controls to limit power available to enemy laser turrets, with laser_shots_per_level constant for balancing.

* Update floor_is_lava.lua

Now spawns fire under players when level is half of max.

* Explosion Scare Module

Added explosion_scare module. Chooses players to randomly explode (non-damaging) a number of times before switching to new targets. Explosion intensity increases as module increases.

* Update pinguin.lua

Removed comment block over modules.

* Added New Module: permanent_factory

Has a very small chance to make an entity unminable and undestructible when placed.

* MeteOres

Added new module: MeteOres.
Spawns a random meteor that damages entities, creates ore, and spawns biters.

* Update meteOres.lua

Added explosion to meteor

* Added Auto Build

Added auto_build module. Selects random players, and automatically builds the item in their cursor nearby for a while, before changing targets.

* New module: Unorganized Recipes

Added a new module to hide recipe groups and subgroups for random players. This leads to "unorganized" crafting menus.

* Update auto_build.lua

Fixed typo. I must have changed base targets to 0 instead of the global level when preparing this file for commit.

* Add Biter Ores Module

Add new module. Spawns ores on death of biters, worms, and spawners, based on difficulty of biter and level.

looks for ores on the tile the biter dies on to add to, otherwise looks nearby for an ore type and uses that, otherwise decides on a new ore type to spawn.

This should allow players to set up "farms" for their ores, creating reasonable ore patches.

Contains a RANDOM_ORES constant that will make the search radius small and ensure random ores are placed instead.

* Update biter_ores.lua

Found typo. radius should be .1 not 1 for tile directly beneath biter.

* Updated Existing Modules

Got luacheck setup in my IDE so we don't have to wait for RedMew to run it. Fixed white-space and other linting errors.

* Split AF scenarios

* Add alien biomes module

* Draft april-fools scenarios

* Fix draft issues

---------

Co-authored-by: R. Nukem <Reoisasa@gmail.com>
This commit is contained in:
RedRafe 2024-03-29 00:27:27 +01:00 committed by GitHub
parent 2efc9b2604
commit acc257b8b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 10084 additions and 4 deletions

View File

@ -169,7 +169,7 @@ stds.factorio = {
fields = {
"by_pixel", "distance", "findfirstentity", "positiontostr", "formattime", "moveposition", "oppositedirection",
"ismoduleavailable", "multiplystripes", "format_number", "increment", "color", "conditional_return",
"add_shift", "merge", "premul_color", "encode", "decode", "insert_safe",
"add_shift", "merge", "premul_color", "encode", "decode", "insert_safe", "list_to_map",
table = {
fields = {
"compare", "deepcopy", "shallow_copy"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,240 @@
-- this file contains all information related to map generation and control of new features.
-- a new feature has a chance to be added or increased every time a research is completed
-- or a rocket is launched, until its max capacity
-- Setup the scenario map information because everyone gets upset if you don't
local ScenarioInfo = require 'features.gui.info'
ScenarioInfo.set_map_name('Pinguin')
ScenarioInfo.set_map_description('You are Pinguins in Antarctica!')
ScenarioInfo.set_map_extra_info('Watch out for Icebergs!')
--- Config
local config = global.config
config.currency = nil
config.market.enabled = false
config.player_rewards.enabled = false
config.redmew_qol.set_alt_on_create = false
local restart_command = require 'map_gen.maps.april_fools.scenario.restart_command'
restart_command({scenario_name = 'april-fools-2019'})
-- == MAP GEN =================================================================
local b = require 'map_gen.shared.builders'
local RS = require 'map_gen.shared.redmew_surface'
local MGSP = require 'resources.map_gen_settings'
--[[
Scale the map.
The pictures are originally quite large to preserve detail.
Will need to scale the map differently depending on which map you use.
Antarctica map at .5 scale: Antarctica is 46 chunks tall
Earth map at .5 scale: Antarctica is 4 chunks tall
]]
local map_scale = _DEBUG and 0.1 or 20
local pic = require 'map_gen.data.presets.antarctica'
-- local pic = require 'map_gen.data.presets.antarctica_earth'
local shape = b.picture(pic)
shape = b.scale(shape, map_scale, map_scale)
local map = b.change_tile(shape, false, 'deepwater')
-- Override map gen selections
RS.set_map_gen_settings({ MGSP.water_very_low })
-- == MODULES IMPORT ==========================================================
local Event = require 'utils.event'
local modules = {
require 'map_gen.maps.april_fools.modules.alternative_biters', -- Spawns a random biters on every player that has alt-mode turned on
require 'map_gen.maps.april_fools.modules.crazy_chat_colors', -- Chance to change player's color every time they send a message in chat
require 'map_gen.maps.april_fools.modules.crazy_toolbar', -- Randomly replaces quickbar slots with new items
require 'map_gen.maps.april_fools.modules.enemy_turrets', -- Chance to change turret to enemy force, and give it ammo/fuel/power
require 'map_gen.maps.april_fools.modules.floor_is_lava', -- Does minor damage to a player when afk for a few second
require 'map_gen.maps.april_fools.modules.golden_goose', -- Randomly selected players will drop coins for a time, before changing targets
require 'map_gen.maps.april_fools.modules.marathon_mode', -- Enables expensive recipes and increases technology multiplier
require 'map_gen.maps.april_fools.modules.orphan_crafting', -- Chance to give the player an additional single underground belt or pipe-to-ground
require 'map_gen.maps.april_fools.modules.random_ores', -- Chance to change an ore to a random ore when a mining drill is placed
require 'map_gen.maps.april_fools.modules.rotate_entities', -- Chance to randomly rotate an entity when rotated by a player
require 'map_gen.maps.april_fools.modules.rotate_inserters', -- Chance to randomly rotate an inserter when built
require 'map_gen.maps.april_fools.modules.rotten_egg', -- Randomly selected players will produce pollution for a time, before changing targets
}
-- if script.active_mods['redmew-data'] then
-- local PATH_MODULES_MOD = '__redmew-data__/'
-- table.insert(modules, PATH_MODULES_MOD .. 'name_of_the_module')
-- end
-- Activate module events
for _, mod in pairs(modules) do
if mod.on_init then
Event.on_init(mod.on_init)
end
if mod.on_load then
Event.on_load(mod.on_load)
end
if mod.on_configuration_changed then
Event.on_configuration_changed(mod.on_configuration_changed)
end
if mod.events then
for id_event, callback in pairs(mod.events) do
Event.add(id_event, callback)
end
end
if mod.on_nth_tick then
for nth_tick, callback in pairs(mod.on_nth_tick) do
Event.on_nth_tick(nth_tick, callback)
end
end
end
-- == CONTROLLER ==============================================================
local Toast = require 'features.gui.toast'
local ICEBERG_ENABLE_PERCENTAGE = 0.50
local TOAST_DURATION = 10
local function draw_random_effect(max_share)
local mod_index = math.random(1, #modules)
local mod = modules[mod_index]
if mod == nil then
return
end
local old_level, new_level, max_level = 0, 0, 0
if mod.level_get then
old_level = mod.level_get()
end
if mod.max_get then
max_level = mod.max_get()
end
if old_level < (max_level * max_share) then
if mod.level_increase then
mod.level_increase()
end
end
if mod.level_get then
new_level = mod.level_get()
end
if new_level == old_level then
Toast.toast_all_players(TOAST_DURATION, 'Everything seems normal... for now.')
game.print('There appears to be no change to the iceberg, lucky Pinguins.')
else
if new_level == 1 then
Toast.toast_all_players(TOAST_DURATION, 'More snow has fallen! A new layer has been added to the iceberg!')
else
Toast.toast_all_players(TOAST_DURATION, 'The iceberg shifts, but you don\'t notice anything new.')
end
game.print(mod.name .. ' level: ' .. tostring(new_level))
end
end
-- Features can be incremented up to 50% of max level with research
local function on_research_finished()
if math.random(100) <= 100 * ICEBERG_ENABLE_PERCENTAGE then
draw_random_effect(0.5)
else
Toast.toast_all_players(TOAST_DURATION, 'Everything seems normal... for now.')
game.print('There appears to be no change to the iceberg, lucky Pinguins.')
end
end
-- Features can be incremented up to 100% of max level with rocket launches
local function on_rocket_launched()
if math.random(100) <= 100 * 2 * ICEBERG_ENABLE_PERCENTAGE then
draw_random_effect(1)
else
Toast.toast_all_players(TOAST_DURATION, 'Everything seems normal... for now.')
game.print('There appears to be no change to the iceberg, lucky Pinguins.')
end
end
Event.add(defines.events.on_research_finished, on_research_finished)
Event.add(defines.events.on_rocket_launched, on_rocket_launched)
require 'map_gen.maps.april_fools.scenario.rocket_launched'
-- == COMMANDS ================================================================
local Command = require 'utils.command'
local Ranks = require 'resources.ranks'
local Color = require 'resources.color_presets'
---Use "/af-reset" to reset all features's levels (admin/server only)
Command.add(
'af-reset',
{
description = [[Reset all features]],
arguments = {},
required_rank = Ranks.admin,
allowed_by_server = true
},
function()
for _, mod in pairs(modules) do
if mod.level_reset then
mod.level_reset()
end
end
game.print('Scenario reset!', Color.success)
end
)
---Use "/af-debug" to print all feature's levels, only to admin (admin/server only)
Command.add(
'af-debug',
{
description = [[Prints all features's current levels]],
arguments = {},
required_rank = Ranks.admin,
allowed_by_server = true
},
function(_, player)
for _, mod in pairs(modules) do
local msg = ''
if mod.level_get and mod.max_get then
msg = msg .. 'Lvl. ' ..tostring(mod.level_get()) .. '/' .. tostring(mod.max_get())
end
if mod.name then
msg = msg .. ' - ' .. mod.name
end
if player and player.valid then
player.print(msg, Color.info)
else
game.print(msg, Color.info)
end
end
end
)
---Use "/af-max" to set all features to their max level (admin/server only)
Command.add(
'af-max',
{
description = [[Sets all features to according max level]],
arguments = {},
required_rank = Ranks.admin,
allowed_by_server = true
},
function()
for _, mod in pairs(modules) do
if mod.level_set and mod.max_get then
mod.level_set(mod.max_get())
end
end
game.print('Scenario maxed out!', Color.warning)
end
)
-- ============================================================================
return map

View File

@ -0,0 +1,322 @@
--[[
Scenario info: Double Trouble
2024 revamped version of "Pinguin" scenario from 2019 with several addons.
Required mods:
- Alien Biomes v0.6.8
- RedMew Data v0.2.4
]]
local ScenarioInfo = require 'features.gui.info'
ScenarioInfo.set_map_name('Double Trouble')
ScenarioInfo.set_map_description('You are Pinguins in Antarctica and Miners underground!')
ScenarioInfo.set_map_extra_info('Watch out for Icebergs!')
--- Config
local Config = global.config
Config.redmew_surface.enabled = false
Config.currency = 'coin'
Config.market.enabled = false
Config.player_rewards.enabled = true
Config.redmew_qol.set_alt_on_create = false
local restart_command = require 'map_gen.maps.april_fools.scenario.restart_command'
restart_command({scenario_name = 'april-fools-2024', mod_pack = 'april_fools_2024'})
if _DEBUG then
Config.player_create.starting_items = {
{name = 'tunnel', count = 10},
{name = 'iron-plate', count = 26 },
{name = 'steel-chest', count = 12},
{name = 'power-armor-mk2', count = 1},
{name = 'fusion-reactor-equipment', count = 4},
{name = 'personal-roboport-mk2-equipment', count = 4},
{name = 'battery-mk2-equipment', count = 4},
{name = 'construction-robot', count = 50},
{name = 'rocket-launcher', count = 1},
{name = 'explosive-rocket', count = 200},
{name = 'green-wire', count = 200},
{name = 'red-wire', count = 200},
}
else
Config.player_create.starting_items = {
{name = 'burner-mining-drill', count = 4 },
{name = 'stone-furnace', count = 2},
{name = 'iron-gear-wheel', count = 3},
{name = 'electronic-circuit', count = 5},
{name = 'pistol', count = 1},
{name = 'firearm-magazine', count = 20},
{name = 'coal', count = 34},
}
end
-- == MAP GEN =================================================================
local Event = require 'utils.event'
local ABS = require 'resources.alien_biomes.biomes_settings'
local Biomes = require 'resources.alien_biomes.biomes'
local mgs = Biomes.preset_to_mgs
local function on_init()
local spawn = {0, 0}
-- Above ground
local islands_preset = Biomes.presets.ice
islands_preset.water = ABS.water.max
islands_preset.enemy = ABS.enemy.high
local islands_mgs = mgs(islands_preset)
for _, resource in pairs({'iron-ore', 'copper-ore', 'stone', 'coal', 'uranium-ore', 'crude-oil'}) do
islands_mgs.autoplace_controls[resource] = { frequency = 1, richness = 1, size = 0 }
end
local islands = game.create_surface('islands', islands_mgs)
islands.request_to_generate_chunks(spawn, 5)
islands.force_generate_chunk_requests()
islands.ticks_per_day = 72000
-- Under ground
local mines_preset = Biomes.presets.volcano
mines_preset.water = ABS.water.none
local mines_mgs = mgs(mines_preset)
mines_mgs.seed = _DEBUG and 309111855 or nil
mines_mgs.autoplace_settings = {
tile = {}
}
for _, tile in pairs({'deepwater', 'deepwater-green', 'water', 'water-green', 'water-mud', 'water-shallow'}) do
mines_mgs.autoplace_settings.tile[tile] = { frequency = 1, size = 0, richness = 1 }
end
for _, resource in pairs({'iron-ore', 'copper-ore', 'stone', 'coal', 'uranium-ore', 'crude-oil'}) do
mines_mgs.autoplace_controls[resource] = { frequency = 12.0, size = 0.5, richness = 0.08 }
end
local mines = game.create_surface('mines', mines_mgs)
mines.request_to_generate_chunks(spawn, 2)
mines.force_generate_chunk_requests()
mines.solar_power_multiplier = 0
mines.min_brightness = 0.11
mines.ticks_per_day = 72000
mines.daytime = 0.42
mines.freeze_daytime = true
mines.show_clouds = false
mines.brightness_visual_weights = {1/0.85, 1/0.85, 1/0.85}
game.forces.player.set_spawn_position(spawn, 'islands')
game.forces.player.manual_mining_speed_modifier = _DEBUG and 20 or 1.2
game.difficulty_settings.technology_price_multiplier = game.difficulty_settings.technology_price_multiplier * 2
end
local function on_player_created(event)
local player = game.get_player(event.player_index)
if not (player and player.valid) then
return
end
player.teleport({0,0}, 'islands')
end
Event.on_init(on_init)
Event.add(defines.events.on_player_created, on_player_created)
require 'map_gen.maps.april_fools.scenario.camera'
require 'map_gen.maps.april_fools.scenario.entity-restrictions'
require 'map_gen.maps.april_fools.scenario.evolution_control'
require 'map_gen.maps.april_fools.scenario.market'
require 'map_gen.maps.april_fools.scenario.mines'
-- == MODULES IMPORT ==========================================================
local modules = {
require 'map_gen.maps.april_fools.modules.alternative_biters', -- Spawns a random biters on every player that has alt-mode turned on
require 'map_gen.maps.april_fools.modules.auto_build', -- Randomly selected players will have their cursor items automatically built nearby for a time, before changing targets
require 'map_gen.maps.april_fools.modules.biter_ores', -- Biters spawn ores on death, level determines amount
require 'map_gen.maps.april_fools.modules.crazy_chat_colors', -- Chance to change player's color every time they send a message in chat
require 'map_gen.maps.april_fools.modules.crazy_toolbar', -- Randomly replaces quickbar slots with new items
require 'map_gen.maps.april_fools.modules.enemy_turrets', -- Chance to change turret to enemy force, and give it ammo/fuel/power
require 'map_gen.maps.april_fools.modules.explosion_scare', -- Spawns random non-damaging explosions on random players as a jump-scare
require 'map_gen.maps.april_fools.modules.floor_is_lava', -- Does minor damage to a player when afk for a few second
require 'map_gen.maps.april_fools.modules.golden_goose', -- Randomly selected players will drop coins for a time, before changing targets
require 'map_gen.maps.april_fools.modules.marathon_mode', -- Enables expensive recipes and increases technology multiplier
require 'map_gen.maps.april_fools.modules.meteOres', -- Meteors fall from the sky, generating ores, and biters
require 'map_gen.maps.april_fools.modules.orphan_crafting', -- Chance to give the player an additional single underground belt or pipe-to-ground
require 'map_gen.maps.april_fools.modules.permanent_factory', -- Chance to make an entity indestructable
require 'map_gen.maps.april_fools.modules.random_ores', -- Chance to change an ore to a random ore when a mining drill is placed
require 'map_gen.maps.april_fools.modules.rotate_entities', -- Chance to randomly rotate an entity when rotated by a player
require 'map_gen.maps.april_fools.modules.rotate_inserters', -- Chance to randomly rotate an inserter when built
require 'map_gen.maps.april_fools.modules.rotten_egg', -- Randomly selected players will produce pollution for a time, before changing targets
require 'map_gen.maps.april_fools.modules.unorganized_recipes', -- Randomly selected players will have their recipe groups and subgroups disabled, unorganizing their crafting menu
}
-- if script.active_mods['redmew-data'] then
-- local PATH_MODULES_MOD = '__redmew-data__/'
-- table.insert(modules, PATH_MODULES_MOD .. 'name_of_the_module')
-- end
-- Activate module events
for _, mod in pairs(modules) do
if mod.on_init then
Event.on_init(mod.on_init)
end
if mod.on_load then
Event.on_load(mod.on_load)
end
if mod.on_configuration_changed then
Event.on_configuration_changed(mod.on_configuration_changed)
end
if mod.events then
for id_event, callback in pairs(mod.events) do
Event.add(id_event, callback)
end
end
if mod.on_nth_tick then
for nth_tick, callback in pairs(mod.on_nth_tick) do
Event.on_nth_tick(nth_tick, callback)
end
end
end
-- == CONTROLLER ==============================================================
local Toast = require 'features.gui.toast'
local ICEBERG_ENABLE_PERCENTAGE = _DEBUG and 1 or 0.50
local TOAST_DURATION = 10
local function draw_random_effect(max_share)
local mod_index = math.random(1, #modules)
local mod = modules[mod_index]
if mod == nil then
return
end
local old_level, new_level, max_level = 0, 0, 0
if mod.level_get then
old_level = mod.level_get()
end
if mod.max_get then
max_level = mod.max_get()
end
if old_level < (max_level * max_share) then
if mod.level_increase then
mod.level_increase()
end
end
if mod.level_get then
new_level = mod.level_get()
end
if new_level == old_level then
Toast.toast_all_players(TOAST_DURATION, 'Everything seems normal... for now.')
game.print('There appears to be no change to the iceberg, lucky Pinguins.')
else
if new_level == 1 then
Toast.toast_all_players(TOAST_DURATION, 'More snow has fallen! A new layer has been added to the iceberg!')
else
Toast.toast_all_players(TOAST_DURATION, 'The iceberg shifts, but you don\'t notice anything new.')
end
game.print(mod.name .. ' level: ' .. tostring(new_level))
end
end
-- Features can be incremented up to 50% of max level with research
local function on_research_finished()
if math.random(100) <= 100 * ICEBERG_ENABLE_PERCENTAGE then
draw_random_effect(0.5)
else
Toast.toast_all_players(TOAST_DURATION, 'Everything seems normal... for now.')
game.print('There appears to be no change to the iceberg, lucky Pinguins.')
end
end
-- Features can be incremented up to 100% of max level with rocket launches
local function on_rocket_launched()
if math.random(100) <= 100 * 2 * ICEBERG_ENABLE_PERCENTAGE then
draw_random_effect(1)
else
Toast.toast_all_players(TOAST_DURATION, 'Everything seems normal... for now.')
game.print('There appears to be no change to the iceberg, lucky Pinguins.')
end
end
Event.add(defines.events.on_research_finished, on_research_finished)
Event.add(defines.events.on_rocket_launched, on_rocket_launched)
require 'map_gen.maps.april_fools.scenario.rocket_launched'
-- == COMMANDS ================================================================
local Command = require 'utils.command'
local Ranks = require 'resources.ranks'
local Color = require 'resources.color_presets'
---Use "/af-reset" to reset all features's levels (admin/server only)
Command.add(
'af-reset',
{
description = [[Reset all features]],
arguments = {},
required_rank = Ranks.admin,
allowed_by_server = true
},
function()
for _, mod in pairs(modules) do
if mod.level_reset then
mod.level_reset()
end
end
game.print('Scenario reset!', Color.success)
end
)
---Use "/af-debug" to print all feature's levels, only to admin (admin/server only)
Command.add(
'af-debug',
{
description = [[Prints all features's current levels]],
arguments = {},
required_rank = Ranks.admin,
allowed_by_server = true
},
function(_, player)
for _, mod in pairs(modules) do
local msg = ''
if mod.level_get and mod.max_get then
msg = msg .. 'Lvl. ' ..tostring(mod.level_get()) .. '/' .. tostring(mod.max_get())
end
if mod.name then
msg = msg .. ' - ' .. mod.name
end
if player and player.valid then
player.print(msg, Color.info)
else
game.print(msg, Color.info)
end
end
end
)
---Use "/af-max" to set all features to their max level (admin/server only)
Command.add(
'af-max',
{
description = [[Sets all features to according max level]],
arguments = {},
required_rank = Ranks.admin,
allowed_by_server = true
},
function()
for _, mod in pairs(modules) do
if mod.level_set and mod.max_get then
mod.level_set(mod.max_get())
end
end
game.print('Scenario maxed out!', Color.warning)
end
)
-- ============================================================================
--return map

View File

@ -0,0 +1,126 @@
-- Spawns biters of a level scaling with _global.level on every player that has alt_mode toggled ON
-- Complete
local Global = require 'utils.global'
local math = require 'utils.math'
local SPAWN_INTERVAL = 60 * 60 -- 60sec
local UNIT_COUNT = 1 -- Number of units spawned per enemy listed in each ENEMY_GROUP
local _global = {
level = 0, --1 to enabled by defualt
max_level = 10,
alt_biters_players = {},
}
Global.register(_global, function(tbl) _global = tbl end)
local ENEMY_GROUPS = {
{ 'small-biter', 'small-spitter' },
{ 'small-biter', 'small-spitter', 'small-worm-turret' },
{ 'medium-biter', 'medium-spitter' },
{ 'medium-biter', 'medium-spitter', 'medium-worm-turret' },
{ 'big-biter', 'big-spitter' },
{ 'big-biter', 'big-spitter', 'big-worm-turret' },
{ 'behemoth-biter', 'behemoth-spitter' },
{ 'behemoth-biter', 'behemoth-spitter', 'behemoth-worm-turret' },
{ 'behemoth-biter', 'behemoth-spitter', 'behemoth-worm-turret', 'biter-spawner' },
{ 'behemoth-biter', 'behemoth-spitter', 'behemoth-worm-turret', 'biter-spawner', 'spitter-spawner' },
}
-- ============================================================================
local function spawn_biters_nearby_players()
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local index_from_level = math.clamp(_global.level, 1, #ENEMY_GROUPS)
local biters = ENEMY_GROUPS[index_from_level]
for _, player in pairs(game.players) do
if (player and player.valid and _global.alt_biters_players[player.name]) then
local position = player.position
for i=1, UNIT_COUNT do
local unit_index = math.random(1, #biters)
player.surface.create_entity{
name = biters[unit_index],
position = position,
force = 'enemy',
target = player.character,
move_stuck_players = true,
}
end
end
end
end
-- toggle alt-biters for the player when alt-mode is toggled
local function on_player_toggled_alt_mode(event)
local player_index = event.player_index
if player_index == nil then
return
end
local player = game.get_player(player_index)
if (player and player.valid and player.name) then
_global.alt_biters_players[player.name] = event.alt_mode or false
end
end
-- turn off alt-mode on game join, and set alt-biters to off
local function on_player_joined_game(event)
local player_index = event.player_index
if player_index == nil then
return
end
local player = game.get_player(player_index)
if (player and player.valid and player.name) then
player.game_view_settings.show_entity_info = false
_global.alt_biters_players[player.name] = false
end
end
-- ============================================================================
local Public = {}
Public.name = 'Alternative biters'
Public.events = {
[defines.events.on_player_toggled_alt_mode] = on_player_toggled_alt_mode,
[defines.events.on_player_joined_game] = on_player_joined_game,
}
Public.on_nth_tick = {
[SPAWN_INTERVAL] = spawn_biters_nearby_players,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,129 @@
-- auto-builds the item in a random players cursor if possible
-- WIP
local Global = require 'utils.global'
local BASE_TARGETS = 1 -- how many targets per level
local BUILD_INTERVAL = 60 * 5 -- 5sec
local CHANGE_TARGET_INTERVAL = _DEBUG and 60 * 1 or 60 * 100 -- 100sec
local _global = {
level = 0, -- 1 to enabled by defualt
max_level = 10,
rand_targets = {},
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function clear_targets()
for j=1, #_global.rand_targets do
_global.rand_targets[j] = nil
end
end
local function change_targets()
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local num_targets = math.min(#game.connected_players, _global.level * BASE_TARGETS)
for j=1, num_targets do
_global.rand_targets[j] = nil
end
if #game.connected_players > 0 then
for j=1, num_targets do
local player_index = math.random(1, #game.connected_players)
_global.rand_targets[j] = game.connected_players[player_index]
end
end
end
local function try_auto_build()
if not (_global and _global.rand_targets and (#_global.rand_targets > 0)) then
-- No targets
return
end
-- useful functions:
-- surface.find_non_colliding_position, surface.find_non_colliding_position_in_box
-- player.can_build_from_cursor, player.build_from_cursor
--
local cursor_item
local surface
local build_position
for _, player in pairs(_global.rand_targets) do
if not (player and player.valid) then
goto skip
end
surface = player.surface
if player.cursor_stack.valid_for_read then
cursor_item = player.cursor_stack.name
else
goto skip --cursor not valud to read, i.e. just before spawning
end
if cursor_item == nil then
goto skip -- no item in cursor
end
-- randomly pick a position near the player, check for a valid nearby location
build_position = {player.position.x + math.random(-5,5),player.position.y + math.random(-5,5)}
build_position = surface.find_non_colliding_position(cursor_item,build_position, 5, .1)
if build_position == nil then
goto skip -- no valud build position
end
-- must be extra cautious with surface & players as they may be teleported across temporary surfaces
if (surface and surface.valid and build_position) then
if player.can_build_from_cursor{position = build_position} then
player.build_from_cursor{position = build_position}
end
end
end
::skip::
end
-- ============================================================================
local Public = {}
Public.name = 'Auto Build'
Public.on_nth_tick = {
[CHANGE_TARGET_INTERVAL] = change_targets,
[BUILD_INTERVAL] = try_auto_build,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.rand_targets[_global.level] = nil
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
clear_targets()
_global.level = 0
end
Public.level_set = function(val)
clear_targets()
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,115 @@
-- Spawns ores on biter death
-- Complete
local Global = require 'utils.global'
local math = require 'utils.math'
local RANDOM_ORES = false
local BASE_ORE_AMOUNT = 5 -- ore spawned per small biter, per level
local BASIC_ORES = {'coal','stone','iron-ore','copper-ore'}
local URANIUM_CHANCE = 5 -- chance for uranium ore. All others will share the remaining chance
local ORE_SEARCH_RADIUS = RANDOM_ORES and 1 or 20 -- how far away do we look for ores before spawning a new one
local _global = {
level = 0, --1 to enabled by defualt
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
local ENEMY_ORE_MULTIPLIER = { -- roughly in order of evolution percentage required to see these biters
['small-biter'] = 1,
['small-spitter'] = 2,
['small-worm-turret'] = 5,
['medium-biter'] = 2,
['medium-spitter'] = 4,
['medium-worm-turret'] = 10,
['big-biter'] = 5,
['big-spitter'] = 10,
['big-worm-turret'] = 25,
['behemoth-biter'] = 10,
['behemoth-spitter'] = 20,
['behemoth-worm-turret'] = 50,
['biter-spawner'] = 10,
['spitter-spawner'] = 20
}
-- ============================================================================
local function spawn_ores_on_death(event)
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
-- check if entity is biter, worm, or spawner
local entity = event.entity
if entity.type ~= 'unit' and entity.type ~= 'turret' and entity.type ~= 'unit-spawner' then
return
end
local ore_amount_to_add = _global.level * ENEMY_ORE_MULTIPLIER[entity.name] * BASE_ORE_AMOUNT
local position = entity.position
local surface = entity.surface
local ore_type
--first, look for ores on the tile the biter died, and add ores to that ore if possible
local found_ores = surface.find_entities_filtered{position = position, radius = .1, type = 'resource'}
if #found_ores == 0 then
--no ore found on biter tile
found_ores = surface.find_entities_filtered{position = position, radius = ORE_SEARCH_RADIUS, type = 'resource'}
if #found_ores == 0 then
--no ore found nearby, decide on a new ore to spawn
if math.random(1,100) < URANIUM_CHANCE then
ore_type = 'uranium-ore'
else
ore_type = BASIC_ORES[math.random(1,#BASIC_ORES)]
end
else
-- found nearby ore, use that one
ore_type = found_ores[math.random(1,#found_ores)].name
end
if surface.get_tile(position).collides_with("ground-tile") then
surface.create_entity{name = ore_type, position = position, amount = ore_amount_to_add}
end
--return since we might have changed found_ores
return
else
-- ore on biters tile, add to that ore instead
found_ores[1].amount = found_ores[1].amount + ore_amount_to_add
end
end
-- ============================================================================
local Public = {}
Public.name = 'Biter Ores'
Public.events = {
[defines.events.on_entity_died] = spawn_ores_on_death
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,75 @@
-- Changes player color with every message
-- Complete
local Global = require 'utils.global'
local Colors = require 'resources.color_presets'
local COLORS = {}
for _, color in pairs(Colors) do
table.insert(COLORS, color)
end
local BASE_PERCENT = 0.1
local MAX_RAND = 100
local _global = {
level = 0,
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function on_console_chat(event)
local index = event.player_index
if index == nil then
return
end
local change_percent = _global.level * BASE_PERCENT
local rand = math.random(0, MAX_RAND)
if rand >= MAX_RAND*(1 - change_percent) then
local color = COLORS[math.random(1, #COLORS)]
local player = game.get_player(index)
player.color = color
player.chat_color = color
end
end
-- ============================================================================
local Public = {}
Public.name = 'Disco players'
Public.events = {
[defines.events.on_console_chat] = on_console_chat,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,83 @@
-- Randmly changes shortcut items in player's quickbar
-- Complete
local Global = require 'utils.global'
local Item_list = require 'resources.item_list'
local BASE_PERCENT = 0.05
local MAX_RAND = 100
local CHANGE_INTERVAL = 60 * 12 --12sec
local SEARCHED_QUICKBAR_SLOTS = 100
local _global = {
level = 0,
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function change_quickbar_item()
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local crazy_percent = _global.level * BASE_PERCENT
for _, player in pairs(game.players) do
if (player and player.valid) then
local rand = math.random(0, MAX_RAND)
if rand >= MAX_RAND*(1 - crazy_percent) then
local valid_item, rand_item = false, false
local max_attempts = 10
while ((max_attempts > 0) and (not valid_item)) do
rand_item = Item_list[math.random(1, #Item_list)]
valid_item = (game.item_prototypes[rand_item] ~= nil)
max_attempts = max_attempts - 1
end
if valid_item then
local rand_position = math.random(1, SEARCHED_QUICKBAR_SLOTS)
player.set_quick_bar_slot(rand_position, rand_item)
end
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'Fuzzy quickbar'
Public.on_nth_tick = {
[CHANGE_INTERVAL] = change_quickbar_item,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,147 @@
-- turrets have a 90% chance of being on player force when placed
-- Be sure to adjust ammo count/amount on the enemy turrets below as needed
-- Completed
local Global = require 'utils.global'
local BASE_PERCENT = 0.05
local MAX_RAND = 100
local LASER_SHOTS_PER_LEVEL = 10 -- No idea what a good number is here balance wise.
local ENERGY_PER_SHOT = 800000 -- 1 shot of the laser turret
local _global = { level = 0, max_level = 10 }
Global.register(_global, function(tbl)
_global = tbl
end)
-- ============================================================================
local TURRET_ACTIONS = {
['gun-turret'] = function(entity)
entity.insert{ name = 'firearm-magazine', count = 2 * (_global.level or 1) }
end,
['flamethrower-turret'] = function(entity)
entity.insert_fluid{ name = 'crude-oil', amount = 6 * (_global.level or 1) }
end,
['artillery-turret'] = function(entity)
entity.insert{ name = 'artillery-shell', count = _global.level or 1 }
end,
['laser-turret'] = function(entity)
-- TODO: change e-interface to accumulator with power
if entity.surface then
entity.surface.create_entity{
name = 'hidden-electric-energy-interface',
force = 'enemy',
position = entity.position,
raise_built = false,
move_stuck_players = true
}
-- find that interface we just made
local entities = entity.surface.find_entities_filtered{ name = 'hidden-electric-energy-interface', position = entity.position, radius = 2 }
-- Set energy interface
local total_power = ENERGY_PER_SHOT * LASER_SHOTS_PER_LEVEL * (_global.level or 1)
for i = 1, #entities do
if (entities[i] and entities[i].valid) then
entities[i].electric_buffer_size = total_power
entities[i].power_production = 0
entities[i].power_usage = 0
entities[i].energy = total_power
end
end
entity.surface.create_entity{
name = 'small-electric-pole',
force = 'enemy',
position = entity.position,
raise_built = false,
move_stuck_players = true
}
end
end,
}
local function on_built_turret(event)
local entity = event.created_entity
if not (entity and entity.valid and entity.name) then
-- Invalid entity
return
end
local fill_entity = TURRET_ACTIONS[entity.name]
if not fill_entity then
-- Turret not whitelisted
return
end
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local change_percent = _global.level * BASE_PERCENT
local rand = math.random(0, MAX_RAND)
if rand >= MAX_RAND * (1 - change_percent) then
fill_entity(entity)
entity.clone({ position = entity.position, force = 'enemy' })
entity.destroy()
end
end
local function remove_enemy_power_on_death(event)
local entity = event.entity
if not (entity and entity.valid) then
-- Invalid entity
return
end
if not (entity.name == 'laser-turret' and entity.force == 'enemy') then
-- Wrong entity
return
end
local entities = entity.surface.find_entities_filtered{ name = 'hidden-electric-energy-interface', position = entity.position, radius = 2 }
for i = 1, #entities do
if (entities[i] and entities[i].valid) then
entities[i].destroy()
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'Rogue turrets'
Public.events = {
[defines.events.on_robot_built_entity] = on_built_turret,
[defines.events.on_built_entity] = on_built_turret,
[defines.events.on_entity_died] = remove_enemy_power_on_death
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,120 @@
-- Spawns random non-damaging explosions on random players as a jump-scare
-- WIP
local Global = require 'utils.global'
local BASE_TARGETS = 1 -- how many targets per level
local EXPLOSION_INTERVAL = _DEBUG and 60 * 5 or 60 * 60 -- 60sec
local CHANGE_TARGET_INTERVAL = _DEBUG and 60 * 10 or 60 * 180 -- 180 seconds
local _global = {
level = 0, -- 1 to enabled by defualt
max_level = 10,
rand_target = {},
}
Global.register(_global, function(tbl) _global = tbl end)
local EXPLOSION_GROUPS = {
{ 'water-splash' },
{ 'water-splash', 'explosion' },
{ 'water-splash', 'explosion', 'land-mine-explosion' },
{ 'explosion', 'land-mine-explosion', 'grenade-explosion' },
{ 'land-mine-explosion', 'grenade-explosion', 'medium-explosion' },
{ 'grenade-explosion', 'medium-explosion' },
{ 'medium-explosion', 'big-explosion' },
{ 'big-explosion', 'massive-explosion', 'big-artillery-explosion' },
{ 'massive-explosion', 'big-artillery-explosion' },
{ 'massive-explosion', 'big-artillery-explosion', 'nuke-explosion' },
}
-- ============================================================================
local function clear_targets()
for j=1, #_global.rand_target do
_global.rand_target[j] = nil
end
end
local function change_targets()
if not (_global and (_global.level > 0)) then
-- Level not enabled
return
end
-- without taking the min of connected players, and the desired targets, it's possible for 1 player to get ALL the explosions
-- The code would then randomly choose an explosion for each target, so you might get a water splash and a normal explosion at the same time.
local num_targets = math.min(_global.level * BASE_TARGETS, #game.connected_players)
for j=1, num_targets do
_global.rand_target[j] = nil
end
if #game.connected_players > 0 then
for j=1, num_targets do
local player_index = math.random(1, #game.connected_players)
_global.rand_target[j] = game.connected_players[player_index]
end
end
end
local function explode_targets()
if not (_global and _global.rand_target and (#_global.rand_target > 0)) then
-- No targets
return
end
local index_from_level = math.clamp(_global.level, 1, #EXPLOSION_GROUPS) --luacheck: ignore 143 math.clamp does in fact exist
local explosions = EXPLOSION_GROUPS[index_from_level]
for _, player in pairs(_global.rand_target) do
if (player and player.valid) then
local surface = player.surface
local position = player.position
local explosion_index = math.random(1, #explosions)
-- must be extra cautious with surface & players as they may be teleported across temporary surfaces
if (surface and surface.valid and position) then
surface.create_entity{
name = explosions[explosion_index],
position = position,
}
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'Explosion Scare'
Public.on_nth_tick = {
[CHANGE_TARGET_INTERVAL] = change_targets,
[EXPLOSION_INTERVAL] = explode_targets,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.rand_target[_global.level] = nil
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
clear_targets()
_global.level = 0
end
Public.level_set = function(val)
clear_targets()
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,71 @@
-- Floor is lava, a player that is AFK for ALLOWED_AFK_TIME ticks will be damaged every DAMAGE_INTERVAL ticks
-- Complete
local Global = require 'utils.global'
local DAMAGE_INTERVAL = 60 * 5 -- 5sec
local ALLOWED_AFK_TIME = 60 * 7 -- 7sec
local BASE_DAMAGE = 1
local _global = {
level = 0, -- 1 to enabled by defualt
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function damage_afk_players()
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
for _, player in pairs(game.players) do
if (player and player.valid and player.character and player.character.valid) then
if player.afk_time > ALLOWED_AFK_TIME then
player.character.damage(BASE_DAMAGE * _global.level, 'enemy')
if _global.level >= _global.max_level/2 then
player.surface.create_entity({name = 'fire-flame', position = player.position})
end
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'The floor is lava'
Public.on_nth_tick = {
[DAMAGE_INTERVAL] = damage_afk_players,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,106 @@
-- golden goose
-- SPAWN_INTERVAL and CHANGE_GEESE_INTERVAL should be set to the number of ticks between the event triggering
local Global = require 'utils.global'
local BASE_GEESE = 1 -- how many geese per level
local SPAWN_INTERVAL = 60 * 5 -- 5sec
local CHANGE_GEESE_INTERVAL = 60 * 100 -- 100sec
local DROP_AMOUNT = 1
local _global = {
level = 0, -- 1 to enabled by defualt
max_level = 10,
rand_geese = {},
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function clear_geese()
for j=1, #_global.rand_geese do
_global.rand_geese[j] = nil
end
end
local function change_geese()
if not (_global and (_global.level > 0)) then
-- Level not enabled
return
end
local num_geese = _global.level * BASE_GEESE
for j=1, num_geese do
_global.rand_geese[j] = nil
end
if #game.connected_players > 0 then
for j=1, num_geese do
local player_index = math.random(1, #game.connected_players)
_global.rand_geese[j] = game.connected_players[player_index]
end
end
end
local function geese_spawn_coin()
if not (_global and _global.rand_geese and (#_global.rand_geese > 0)) then
-- No geese
return
end
for _, player in pairs(_global.rand_geese) do
if (player and player.valid) then
local surface = player.surface
local position = player.position
-- must be extra cautious with surface & players as they may be teleported across temporary surfaces
if (surface and surface.valid and position) then
surface.create_entity{
name = 'item-on-ground',
position = position,
stack = {name = 'coin', count = DROP_AMOUNT},
}
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'Golden goose'
Public.on_nth_tick = {
[CHANGE_GEESE_INTERVAL] = change_geese,
[SPAWN_INTERVAL] = geese_spawn_coin,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.rand_geese[_global.level] = nil
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
clear_geese()
_global.level = 0
end
Public.level_set = function(val)
clear_geese()
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,62 @@
-- Increases research costs and enables marathon mode
-- Complete
local Global = require 'utils.global'
local COST_STEP = 1.0
local _global = {
level = 0,
max_level = 9,
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function update_price_multiplier()
local level = math.max(0, _global.level)
-- Enable expensive recipes after lvl.3
game.difficulty_settings.recipe_difficulty = (level > 3) and 1 or 0
-- Apply expensive tech cost above lvl.0
game.difficulty_settings.technology_difficulty = (level > 0) and 1 or 0
game.difficulty_settings.technology_price_multiplier = (level + 1) * COST_STEP
end
-- ============================================================================
local Public = {}
Public.name = 'Marathon'
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
update_price_multiplier()
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
update_price_multiplier()
end
Public.level_reset = function()
_global.level = 0
update_price_multiplier()
end
Public.level_set = function(val)
_global.level = val
update_price_multiplier()
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,176 @@
-- Spawns "meteors" that spawn a boulder, and dense random ores, and biters, and maybe nests
-- WIP
local Global = require 'utils.global'
local math = require 'utils.math'
local SPAWN_INTERVAL = _DEBUG and 60 * 1 or 60 * 60 * 8 -- 8 mins
local UNIT_COUNT = 10 -- Balance Number of units spawned per enemy listed in each ENEMY_GROUP
local METEOR_COUNT = 1 -- meteors per spawn interval
local METEOR_SIZE = 7 -- radius, Balance
local METEOR_DAMAGE = 50 -- Balance
local ORE_DENSITY = 10 -- Balance, Must be at least 8?
local ORE_COMPLEXITY = 5 -- Percent chance for each of the 5 ore types to spawn, otherwise mixed ores without uranium will spawn.
local _global = {
level = 0, --1 to enabled by defualt
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
local ENEMY_GROUPS = {
{ 'small-biter'},
{ 'small-biter', 'small-spitter', 'small-worm-turret', 'biter-spawner'},
{ 'small-biter', 'small-spitter','medium-biter','small-worm-turret', 'biter-spawner', 'spitter-spawner' },
{ 'small-biter', 'small-spitter','medium-biter', 'medium-spitter','small-worm-turret', 'biter-spawner', 'spitter-spawner' },
{ 'medium-biter', 'medium-spitter', 'big-biter', 'big-spitter','small-worm-turret','medium-worm-turret', 'biter-spawner', 'spitter-spawner' },
{ 'medium-biter', 'medium-spitter', 'big-biter', 'big-spitter', 'big-worm-turret','medium-worm-turret', 'biter-spawner', 'spitter-spawner' },
{ 'big-biter', 'big-spitter','behemoth-biter', 'behemoth-spitter', 'medium-worm-turret','big-worm-turret', 'biter-spawner', 'spitter-spawner' },
{ 'big-biter', 'big-spitter','behemoth-biter', 'behemoth-spitter', 'medium-worm-turret', 'big-worm-turret', 'biter-spawner', 'spitter-spawner' },
{ 'big-biter', 'big-spitter','behemoth-biter', 'behemoth-spitter', 'big-worm-turret','behemoth-worm-turret', 'biter-spawner', 'spitter-spawner' },
{ 'behemoth-biter', 'behemoth-spitter', 'behemoth-worm-turret', 'biter-spawner', 'spitter-spawner' },
}
local BASIC_ORES = {'coal','stone','iron-ore','copper-ore'}
local ALL_ORES = {'coal','stone','iron-ore','copper-ore','uranium-ore'}
-- ============================================================================
local function drop_meteors()
--[[ Large function, lots of steps. May want to split out into several functions later
[X] Find a player to use their surface
[X] Generate a random position on the map
[X] Spawn a rock
[X] Damage Nearby Entities
[X] Spawn Ores
[X] Spawn Biters
--]]
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local player
local surface
for _ = 1, METEOR_COUNT do
-- find a random player so we can use their surface
if #game.connected_players > 0 then
player = game.connected_players[math.random(1, #game.connected_players)]
surface = player.surface
else
return -- no connected players
end
-- generate a random position in a random chunk
local chunk_position = surface.get_random_chunk()
local rand_x = math.random(0, 31)
local rand_y = math.random(0, 31)
local map_position = {x = chunk_position.x * 32 + rand_x, y = chunk_position.y * 32 + rand_y}
-- Spawn Rock
if surface.get_tile(map_position).collides_with('ground-tile') then
surface.create_entity({name = 'rock-huge', position = map_position, move_stuck_players = true,})
surface.create_entity({name = 'massive-explosion', position = map_position,})
end
-- Find nearby entities
local damaged_entities = surface.find_entities_filtered{position = map_position, radius = METEOR_SIZE}
-- Damage nearby entities
if damaged_entities == nil then
return
else
for _, entity in ipairs(damaged_entities) do
if entity.is_entity_with_health then
entity.damage(METEOR_DAMAGE,'enemy','impact')
end
end
end
-- Select ores to spawn
local ore_selector = math.random(1,100)
local ores
local ore_type
local ore_amount
if ore_selector > 100 - 5 * ORE_COMPLEXITY then
ores = 'individual'
ore_type = ALL_ORES[math.random(1, #ALL_ORES)]
else
ores = 'mixed'
end
-- Spawn ores
-- Loop over x, y, check in the circle, and spawn ores in a natural density
-- aka code adapted from wube wiki console page for spawning a resource patch
for y = -METEOR_SIZE, METEOR_SIZE do
for x = -METEOR_SIZE, METEOR_SIZE do
if (x * x + y * y < METEOR_SIZE * METEOR_SIZE) then
local a = (METEOR_SIZE + 1 - math.abs(x)) * 10
local b = (METEOR_SIZE + 1 - math.abs(y)) * 10
if a < b then
ore_amount = math.random(a * ORE_DENSITY - a * (ORE_DENSITY - 8), a * ORE_DENSITY + a * (ORE_DENSITY - 8))
end
if b < a then
ore_amount = math.random(b * ORE_DENSITY - b * (ORE_DENSITY - 8), b * ORE_DENSITY + b * (ORE_DENSITY - 8))
end
if surface.get_tile(map_position.x + x, map_position.y + y).collides_with('ground-tile') then
if ores == 'mixed' then
ore_type = BASIC_ORES[math.random(1, #BASIC_ORES)]
end
surface.create_entity({name=ore_type, amount=ore_amount, position={map_position.x + x, map_position.y + y}})
end
end
end
end
-- spawn biters
local index_from_level = math.clamp(_global.level, 1, #ENEMY_GROUPS)
local biters = ENEMY_GROUPS[index_from_level]
for i=1, UNIT_COUNT do
local unit_index = math.random(1, #biters)
local biter_position = {
map_position.x + math.random(-METEOR_SIZE, METEOR_SIZE),
map_position.y + math.random(-METEOR_SIZE, METEOR_SIZE)}
if surface.get_tile(biter_position).collides_with('ground-tile') then
surface.create_entity{
name = biters[unit_index],
position = biter_position,
force = 'enemy',
-- target = player.character, -- try without player target? Will they behave normally?
}
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'MeteOres'
Public.on_nth_tick = {
[SPAWN_INTERVAL] = drop_meteors,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,90 @@
-- crafting underground belt/pipes will no longer give an even number
-- Complete
local Global = require 'utils.global'
local BASE_PERCENT = 0.01
local MAX_RAND = 100
local _global = {
level = 0,
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
local PAIR_NAMES = {
['underground-belt'] = true,
['fast-underground-belt'] = true,
['express-underground-belt'] = true,
['pipe-to-ground'] = true,
}
-- ============================================================================
local function on_player_crafted_item(event)
local name = event.item_stack and event.item_stack.name
if not (name and PAIR_NAMES[name]) then
-- Invalid item
return
end
local index = event.player_index
if not index then
return
end
local player = game.get_player(index)
if not (player and player.valid) then
-- Invalid player
return
end
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local extra_percent = _global.level * BASE_PERCENT
local rand = math.random(0, MAX_RAND)
if rand >= MAX_RAND*(1 - extra_percent) then
player.insert({ name = event.item_stack.name, count = 1 })
end
end
-- ============================================================================
local Public = {}
Public.name = 'Orphan crafting'
Public.events = {
[defines.events.on_player_crafted_item] = on_player_crafted_item,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,77 @@
-- Any placed entity has a chance to become permanent
-- WIP
local Global = require 'utils.global'
local BASE_PERCENT = 0.01
local MAX_RAND = 100
local _global = {
level = 0,
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function on_built_entity(event)
local entity = event.created_entity
if not (entity and entity.valid) then
-- Invalid entity
return
end
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local permanent_percent = _global.level * BASE_PERCENT
local rand = math.random(0, MAX_RAND)
if rand <= MAX_RAND*(1 - permanent_percent) then
-- Normal construction
return
else
entity.destructible = false
entity.minable = false
end
end
-- ============================================================================
local Public = {}
Public.name = 'Permanent Structures'
Public.events = {
[defines.events.on_robot_built_entity] = on_built_entity,
[defines.events.on_built_entity] = on_built_entity,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,118 @@
-- Each ore tile has 1% chance to mutate into another ore (every patch becomes a mixed ore patch)
-- Complete
local Global = require 'utils.global'
local table = require 'utils.table'
local BASE_PERCENT = 0.01
local MAX_RAND = 100
local _global = {
level = 0, --1 to enabled by defualt
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
local ORES = {
{'iron-ore', 10},
{'copper-ore', 7},
{'stone', 5},
{'coal', 3},
{'uranium-ore', 1},
}
local ALLOWED_DRILLS = {
['burner-mining-drill'] = true,
['electric-mining-drill'] = true,
}
-- ============================================================================
local function on_built_miner(event)
local entity = event.created_entity
if not (entity and entity.valid and entity.name and ALLOWED_DRILLS[entity.name]) then
-- Invalid entity
return
end
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local surface = entity.surface
local position = entity.position
local radius = entity.prototype.mining_drill_radius
if not (surface and surface.valid) then
-- Invalid surface
return
end
local ore_tiles = surface.find_entities_filtered{
position = position,
radius = radius or 1.5,
type = 'resource',
}
for _, ore in pairs(ore_tiles) do
if (ore and ore.valid) then
local extra_percent = _global.level * BASE_PERCENT
local rand = math.random(0, MAX_RAND)
if rand >= MAX_RAND*(1 - extra_percent) then
local rand_ore = table.get_random_weighted(ORES)
if (rand_ore ~= ore.name) and surface.get_tile(ore.position.x, ore.position.y).collides_with('ground-tile') then
local amount = ore.amount
local ore_position = ore.position
ore.destroy()
surface.create_entity{
name = rand_ore,
amount = amount,
position = ore_position,
}
end
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'Magic drills'
Public.events = {
[defines.events.on_robot_built_entity] = on_built_miner,
[defines.events.on_built_entity] = on_built_miner,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,83 @@
-- when the player rotates an object it is sometimes rotated to a random direction instead.
-- Complete
local Global = require 'utils.global'
local ROTATE_BASE_PERCENT = 0.05
local MAX_RAND = 100 * 3
local _global = {
level = 0,
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
local function on_player_rotated_entity(event)
local entity = event.entity
if not (entity and entity.valid) then
-- Invalid entity
return
end
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local rotate_percent = _global.level * ROTATE_BASE_PERCENT
local rand = math.random(0, MAX_RAND)
if rand <= MAX_RAND*(1 - rotate_percent) then
-- No Rotation
return
elseif rand <= MAX_RAND*(1 - rotate_percent * 2/3) then
-- Single Rotation
entity.rotate()
return
elseif rand <= MAX_RAND*(1 - rotate_percent * 1/3) then
-- Double Rotation
entity.rotate()
entity.rotate()
return
elseif rand <= MAX_RAND then
-- Reverse Rotation
entity.rotate({reverse = true})
return
end
end
-- ============================================================================
local Public = {}
Public.name = 'Spinning world'
Public.events = {
[defines.events.on_player_rotated_entity] = on_player_rotated_entity,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,92 @@
-- Inserters are sometimes randomly rotated when placed by player or bots
-- Complete
local Global = require 'utils.global'
local BASE_PERCENT = 0.05
local MAX_RAND = 100 * 3
local _global = {
level = 0,
max_level = 10,
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function on_built_inserter(event)
local entity = event.created_entity
if not (entity and entity.valid) then
-- Invalid entity
return
end
if not (entity.prototype and entity.prototype.type == 'inserter') then
-- Wrong entity type
return
end
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local rotate_percent = _global.level * BASE_PERCENT
local rand = math.random(0, MAX_RAND)
if rand <= MAX_RAND*(1 - rotate_percent) then
-- No Rotation
return
elseif rand <= MAX_RAND*(1 - rotate_percent * 2/3) then
-- Single Rotation
entity.rotate()
return
elseif rand <= MAX_RAND*(1 - rotate_percent * 1/3) then
-- Double Rotation
entity.rotate()
entity.rotate()
return
elseif rand <= MAX_RAND then
-- Reverse Rotation
entity.rotate({reverse = true})
return
end
end
-- ============================================================================
local Public = {}
Public.name = 'Dancing inserters'
Public.events = {
[defines.events.on_robot_built_entity] = on_built_inserter,
[defines.events.on_built_entity] = on_built_inserter,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
_global.level = 0
end
Public.level_set = function(val)
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,104 @@
-- rotten egg, produces pollution. Similar to golden goose
-- SPAWN_INTERVAL and CHANGE_EGGS_INTERVAL should be set to the number of ticks between the event triggering
local Global = require 'utils.global'
local BASE_EGGS = 1 -- how many eggs per level
local SPAWN_INTERVAL = 60 * 5 -- 5sec
local CHANGE_EGGS_INTERVAL = 60 * 101 -- 100sec
local DROP_AMOUNT = 1 -- 60/m ~ 6x mining drills
local _global = {
level = 0, -- 1 to enabled by defualt
max_level = 10,
rand_eggs = {},
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function clear_eggs()
for j=1, #_global.rand_eggs do
_global.rand_eggs[j] = nil
end
end
local function change_eggs()
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local num_eggs = _global.level * BASE_EGGS
for j=1, num_eggs do
_global.rand_eggs[j] = nil
end
if #game.connected_players > 0 then
for j=1, num_eggs do
local player_index = math.random(1, #game.connected_players)
_global.rand_eggs[j] = game.connected_players[player_index]
end
end
end
local function eggs_spawn_pollution()
if not (_global and _global.rand_eggs and (#_global.rand_eggs > 0)) then
-- No eggs
return
end
for _, player in pairs(_global.rand_eggs) do
if (player and player.valid) then
local surface = player.surface
local position = player.position
local amount = (_global.level or 1) * DROP_AMOUNT
-- must be extra cautious with surface & players as they may be teleported across temporary surfaces
if (surface and surface.valid and position) then
surface.pollute(position, amount)
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'Rotten egg'
Public.on_nth_tick = {
[CHANGE_EGGS_INTERVAL] = change_eggs,
[SPAWN_INTERVAL] = eggs_spawn_pollution,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.rand_eggs[_global.level] = nil
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
clear_eggs()
_global.level = 0
end
Public.level_set = function(val)
clear_eggs()
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,95 @@
-- hides recipe groups from random players. If a player is targeted again, it will revert back to normal
-- only works with vanilla item-groups.
-- in future possible change to on_init to populate item_groups with all detected item groups?
-- WIP
local Global = require 'utils.global'
local BASE_TARGETS = 1 -- how many targets per level
local CHANGE_TARGET_INTERVAL = _DEBUG and 60 * 10 or 60 * 180 -- 180sec
local _global = {
level = 0, -- 1 to enabled by defualt
max_level = 10,
rand_targets = {},
}
Global.register(_global, function(tbl) _global = tbl end)
-- ============================================================================
local function clear_targets()
for j=1, #_global.rand_targets do
_global.rand_targets[j] = nil
end
end
local function change_targets()
if not (_global and _global.level > 0) then
-- Level not enabled
return
end
local num_targets = math.min(#game.connected_players, _global.level * BASE_TARGETS)
if #game.connected_players > 0 then
for j=1, num_targets do
local player_index = math.random(1, #game.connected_players)
local duplicate = false
--check that randomly selected player is not in old list
for k=1, #_global.rand_targets do
if game.connected_players[player_index].name == _global.rand_targets[k].name then
duplicate = true -- target was previously targeted, so enable them and take them off the list
_global.rand_targets[k].enable_recipe_groups()
_global.rand_targets[k].enable_recipe_subgroups()
_global.rand_targets[k] = nil
end
end
if duplicate == false then -- this is a new target
_global.rand_targets[j] = game.connected_players[player_index]
_global.rand_targets[j].disable_recipe_groups()
_global.rand_targets[j].disable_recipe_subgroups()
end
end
end
end
-- ============================================================================
local Public = {}
Public.name = 'Unorganized Recipes'
Public.on_nth_tick = {
[CHANGE_TARGET_INTERVAL] = change_targets,
}
Public.level_increase = function()
_global.level = math.min(_global.level + 1, _global.max_level)
end
Public.level_decrease = function()
_global.rand_targets[_global.level] = nil
_global.level = math.max(_global.level - 1, 0)
end
Public.level_reset = function()
clear_targets()
_global.level = 0
end
Public.level_set = function(val)
clear_targets()
_global.level = val
end
Public.level_get = function()
return _global.level
end
Public.max_get = function()
return _global.max_level
end
return Public

View File

@ -0,0 +1,215 @@
-- from features/gui/camera
-- edited to support "opposite surface"
local Event = require 'utils.event'
local mod_gui = require 'mod-gui'
local Gui = require 'utils.gui'
local Global = require 'utils.global'
local main_button_name = Gui.uid_name()
local camera_users = {}
Global.register({ camera_users = camera_users }, function(tbl)
camera_users = tbl.camera_users
end)
local camera_prototype = 'camera'
local zoomlevels = { 1.00, 0.75, 0.50, 0.40, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05 }
local zoomlevellabels = { '100%', '75%', '50%', '40%', '30%', '25%', '20%', '15%', '10%', '5%' }
local sizelevels = { 0, 100, 200, 250, 300, 350, 400, 450, 500 }
local sizelevellabels = { 'hide', '100x100', '200x200', '250x250', '300x300', '350x350', '400x400', '450x450', '500x500' }
---@param surface SurfaceIdentification union LuaSurface|string
local function get_opposite_surface(surface)
local src = game.get_surface(type(surface) == 'table' and surface.name or surface)
local mines = game.get_surface('mines')
local islands = game.get_surface('islands')
if not islands or not mines or not src then
return
end
if src.name == mines.name then
return islands
end
if src.name == islands.name then
return mines
end
return
end
--- Takes args and a LuaPlayer and creates a camera GUI element
local function create_camera(args, player)
local player_index = player.index
local mainframeflow = mod_gui.get_frame_flow(player)
local mainframeid = 'mainframe_' .. player_index
local mainframe = mainframeflow[mainframeid]
local target = game.get_player(args.target)
if not target then
player.print('Not a valid target')
return
end
if not mainframe then
mainframe = mainframeflow.add { type = 'frame', name = mainframeid, direction = 'vertical', style = 'captionless_frame' }
mainframe.visible = true
player.set_shortcut_toggled(camera_prototype, true)
end
local headerframe = mainframe.headerframe
if not headerframe then
mainframe.add { type = 'frame', name = 'headerframe', direction = 'horizontal', style = 'captionless_frame' }
end
local cameraframe = mainframe.cameraframe
if not cameraframe then
mainframe.add { type = 'frame', name = 'cameraframe', style = 'captionless_frame' }
end
mainframe.add { type = 'label', caption = 'Following: ' .. target.name .. '\'s steps on other surface' }
Gui.make_close_button(mainframe, main_button_name)
local target_index = target.index
camera_users[player_index] = target_index
end
--- Takes table with a LuaPlayer under key player and destroys the camera of the associated player
local function destroy_camera(data)
local player = data.player
if not player then
return
end
local player_index = player.index
local mainframeflow = mod_gui.get_frame_flow(player)
local mainframeid = 'mainframe_' .. player_index
local mainframe = mainframeflow[mainframeid]
if mainframe then
mainframe.destroy()
player.set_shortcut_toggled(camera_prototype, false)
return true
end
end
--- Destroys existing camera and, if applicable, creates a new one for the new target.
local function camera_command(args, player)
destroy_camera({ player = player })
-- Once the old camera is destroyed, check to see if we need to make a new one
if global.config.camera_disabled then
player.print('The watch/camera function has been disabled for performance reasons.')
return
end
if args and args.target and player then
create_camera(args, player)
end
end
local function update_camera_render(target, targetframe, zoom, size, visible)
local position = { x = target.position.x, y = target.position.y - 0.5 }
local surface_index = target.surface.index
local preview_size = size
local camera = targetframe.camera
if not camera then
camera = targetframe.add { type = 'camera', name = 'camera', position = position, surface_index = surface_index, zoom = zoom }
end
camera.position = position
camera.surface_index = get_opposite_surface(surface_index).index
camera.zoom = zoom
camera.visible = visible
camera.style.minimal_width = preview_size
camera.style.minimal_height = preview_size
camera.style.maximal_width = preview_size
camera.style.maximal_height = preview_size
end
local function update_camera_zoom(targetframe)
local zoomselection = targetframe.zoomselection
local zoomlabels = {}
for i = 1, #zoomlevellabels do
zoomlabels[i] = { '', '' .. zoomlevellabels[i] }
end
if zoomselection then
zoomselection.items = zoomlabels
else
zoomselection = targetframe.add { type = 'drop-down', name = 'zoomselection', items = zoomlabels, selected_index = 3 }
end
local zoom = zoomlevels[zoomselection.selected_index]
return zoom
end
local function update_camera_size(targetframe)
local sizeselection = targetframe.sizeselection
local sizelabels = {}
for i = 1, #sizelevellabels do
sizelabels[i] = { '', '' .. sizelevellabels[i] }
end
if sizeselection then
sizeselection.items = sizelabels
else
sizeselection = targetframe.add { type = 'drop-down', name = 'sizeselection', items = sizelabels, selected_index = 4 }
end
local size = sizelevels[sizeselection.selected_index]
local visible = (size ~= 0)
return size, visible
end
local function on_tick()
if global.config.camera_disabled then
return
end
for table_key, camera_table in pairs(camera_users) do
local player = game.get_player(table_key)
local target = game.get_player(camera_table)
if not target.connected then
destroy_camera({ player = player })
player.print('Target is offline, camera closed')
camera_users[player.index] = nil
return
end
local mainframeflow = mod_gui.get_frame_flow(player)
local mainframeid = 'mainframe_' .. table_key
local mainframe = mainframeflow[mainframeid]
if mainframe then
local headerframe = mainframe.headerframe
local cameraframe = mainframe.cameraframe
local zoom = update_camera_zoom(headerframe)
local size, visible = update_camera_size(headerframe)
update_camera_render(target, cameraframe, zoom, size, visible)
player.set_shortcut_toggled(camera_prototype, visible)
end
end
end
local function on_lua_shortcut(event)
local player = game.get_player(event.player_index)
if not (player and player.valid) then
return
end
local shortcut = event.prototype_name or event.input_name
if shortcut ~= camera_prototype then
return
end
local toggled = player.is_shortcut_toggled(camera_prototype)
player.set_shortcut_toggled(camera_prototype, not toggled)
if toggled then
destroy_camera({ player = player })
else
camera_command({ target = player.name }, player)
end
end
Event.on_nth_tick(61, on_tick)
Gui.on_click(main_button_name, destroy_camera)
Event.add(defines.events.on_lua_shortcut, on_lua_shortcut)
-- luacheck: ignore script
script.on_event(camera_prototype, on_lua_shortcut)

View File

@ -0,0 +1,51 @@
local Event = require 'utils.event'
local alert_message = '[color=yellow]Cannot build here![/color]'
local banned_per_surface = {
islands = {
['furnace'] = true,
},
mines = {
['assembling-machine'] = true,
['solar-panel'] = true,
['rocket-silo'] = true,
}
}
local function on_built(event)
local entity = event.created_entity
if not (entity and entity.valid) then
return
end
local surface = entity.surface.name
local entity_type = entity.prototype.type
if not (banned_per_surface[surface] and banned_per_surface[surface][entity_type]) then
return
end
local ghost = false
if entity.name == 'entity-ghost' then
ghost = true
end
entity.destroy()
local stack = event.stack
local player = game.get_player(event.player_index or 'none')
local robot = event.robot
if player and player.valid and not ghost and stack.valid then
if player.can_insert(stack) then
player.insert(stack)
player.print(alert_message)
end
elseif robot and robot.valid and not ghost and stack.valid then
-- FIXME: currenlty not refunding anything when using robots...
if robot.can_insert(stack) then
robot.insert(stack)
end
end
end
Event.add(defines.events.on_built_entity, on_built)
Event.add(defines.events.on_robot_built_entity, on_built)

View File

@ -0,0 +1,22 @@
local Event = require 'utils.event'
local relations = {
['logistic-science-pack'] = 10,
['military-science-pack'] = 20,
['chemical-science-pack'] = 40,
['production-science-pack'] = 50,
['utility-science-pack'] = 90,
}
Event.on_nth_tick(103, function()
local max = 0
local tech = game.forces.player.technologies
for name, evo in pairs(relations) do
if tech[name] and tech[name].researched then
max = math.max(max, evo)
end
end
if game.forces.enemy.evolution_factor > 10 and max > 2 then
game.forces.enemy.evolution_factor = math.min(game.forces.enemy.evolution_factor, max - 1)
end
end)

View File

@ -0,0 +1,312 @@
-- dependencies
local Event = require 'utils.event'
local Token = require 'utils.token'
local Task = require 'utils.task'
local Global = require 'utils.global'
local Retailer = require 'features.retailer'
local market_items = require 'map_gen.maps.april_fools.scenario.market_items'
local fish_market_bonus_message = require 'resources.fish_messages'
local ScoreTracker = require 'utils.score_tracker'
local Color = require 'resources.color_presets'
local change_for_player = ScoreTracker.change_for_player
local get_for_player = ScoreTracker.get_for_player
local coins_earned_name = 'coins-earned'
-- localized functions
local pairs = pairs
local round = math.round
local random = math.random
local format = string.format
local market_config = global.config.market
local currency = market_config.currency
local entity_drop_amount = market_config.entity_drop_amount
local max_coins_earned = 5
-- local vars
local nth_tick_token
local running_speed_boost_messages = {
'%s found the lost Dragon Scroll and got a lv.1 speed boost!',
'Guided by Master Oogway, %s got a lv.2 speed boost!',
'Kung Fu Master %s defended the village and was awarded a lv.3 speed boost!',
'Travelled at the speed of light. %s saw a black hole. Oops.',
}
local mining_speed_boost_messages = {
'%s is going on a tree harvest!',
'In search of a sharper axe, %s got a lv.2 mining boost!',
'Wood fiend, %s, has picked up a massive chain saw and is awarded a lv.3 mining boost!',
'Better learn to control that saw, %s, chopped off their legs. Oops.',
}
-- Global registered local vars
local primitives = { event_registered = nil }
local markets = {}
local speed_records = {}
local mining_records = {}
Global.register({ markets = markets, speed_records = speed_records, mining_records = mining_records, primitives = primitives }, function(tbl)
markets = tbl.markets
speed_records = tbl.speed_records
mining_records = tbl.mining_records
primitives = tbl.primitives
end)
-- local functions
local function register_event()
if not primitives.event_registered then
Event.add_removable_nth_tick(907, nth_tick_token)
primitives.event_registered = true
end
end
local function unregister_event()
if primitives.event_registered then
Event.remove_removable_nth_tick(907, nth_tick_token)
primitives.event_registered = nil
end
end
local function spawn_market(args, player)
if args and args.removeall == 'removeall' then
local count = 0
for _, market in pairs(markets) do
if market.valid then
count = count + 1
market.destroy()
end
end
player.print(count .. ' markets removed')
return
end
local surface = game.get_surface('mines')
local force = game.forces.player
local maket_spawn_pos = market_config.standard_market_location
if args and args.surface then
surface = game.get_surface(type(args.surface) == 'table' and args.surface.name or args.surface)
end
if player then -- If we have a player, this is coming from a player running the command
surface = player.surface
force = player.force
maket_spawn_pos = player.position
maket_spawn_pos.y = round(maket_spawn_pos.y - 4)
maket_spawn_pos.x = round(maket_spawn_pos.x)
player.print('Market added. To remove it, highlight it with your cursor and use the /destroy command, or use /market removeall to remove all markets placed.')
end
local market = surface.create_entity({ name = 'market', position = maket_spawn_pos, force = 'neutral' })
markets[#markets + 1] = market
market.destructible = false
Retailer.add_market('fish_market', market)
if table.size(Retailer.get_items('fish_market')) == 0 then
for _, prototype in pairs(market_items) do
Retailer.set_item('fish_market', prototype)
end
end
force.add_chart_tag(surface, { icon = { type = 'item', name = currency }, position = maket_spawn_pos, text = 'Market' })
end
local function fish_earned(event, amount)
amount = math.random(math.min(amount, max_coins_earned))
local player_index = event.player_index
local player = game.get_player(player_index)
local stack = { name = currency, count = amount }
local inserted = player.insert(stack)
if amount > 0 then
player.surface.create_entity {
name = 'flying-text',
position = { player.position.x - 1, player.position.y },
text = '+' .. amount .. ' [img=item.coin]',
color = Color.gold,
render_player_index = player.index,
}
end
local diff = amount - inserted
if diff > 0 then
stack.count = diff
player.surface.spill_item_stack(player.position, stack, true)
end
change_for_player(player_index, coins_earned_name, amount)
if get_for_player(player_index, coins_earned_name) % 70 == 0 and player and player.valid then
local message = fish_market_bonus_message[random(#fish_market_bonus_message)]
player.print(message)
end
end
local function pre_player_mined_item(event)
local type = event.entity.type
if type == 'simple-entity' then -- Cheap check for rock, may have other side effects
fish_earned(event, 10)
return
end
if type == 'tree' and random(1, 4) == 1 then
fish_earned(event, 4)
end
end
local spill_items = Token.register(function(data)
data.surface.spill_item_stack(data.position, { name = currency, count = data.count }, true)
end)
-- Determines how many coins to drop when enemy entity dies based upon the entity_drop_amount table in config.lua
local function fish_drop_entity_died(event)
local entity = event.entity
if not entity or not entity.valid then
return
end
local bounds = entity_drop_amount[entity.name]
if not bounds then
return
end
local chance = bounds.chance
if chance == 0 then
return
end
if chance == 1 or random() <= chance then
local count = random(bounds.low, bounds.high)
if count > 0 then
Task.set_timeout_in_ticks(1, spill_items, { count = count, surface = entity.surface, position = entity.position })
end
end
end
local function reset_player_running_speed(player)
local index = player.index
player.character_running_speed_modifier = speed_records[index].pre_boost_modifier
speed_records[index] = nil
end
local function reset_player_mining_speed(player)
local index = player.index
player.character_mining_speed_modifier = mining_records[index].pre_mining_boost_modifier
mining_records[index] = nil
end
local function boost_player_running_speed(player)
local index = player.index
local p_name = player.name
if not speed_records[index] then
speed_records[index] = { start_tick = game.tick, pre_boost_modifier = player.character_running_speed_modifier, boost_lvl = 0 }
end
speed_records[index].boost_lvl = 1 + speed_records[index].boost_lvl
player.character_running_speed_modifier = 1 + player.character_running_speed_modifier
if speed_records[index].boost_lvl >= 4 then
game.print(format(running_speed_boost_messages[speed_records[index].boost_lvl], p_name))
reset_player_running_speed(player)
player.character.die(player.force, player.character)
return
end
player.print(format(running_speed_boost_messages[speed_records[index].boost_lvl], p_name))
register_event()
end
local function boost_player_mining_speed(player)
local index = player.index
local p_name = player.name
if not mining_records[index] then
mining_records[index] = { start_tick = game.tick, pre_mining_boost_modifier = player.character_mining_speed_modifier, boost_lvl = 0 }
end
mining_records[index].boost_lvl = 1 + mining_records[index].boost_lvl
player.character_mining_speed_modifier = 1 + player.character_mining_speed_modifier
if mining_records[index].boost_lvl >= 4 then
game.print(format(mining_speed_boost_messages[mining_records[index].boost_lvl], p_name))
reset_player_mining_speed(player)
player.character.die(player.force, player.character)
return
end
player.print(format(mining_speed_boost_messages[mining_records[index].boost_lvl], p_name))
register_event()
end
local function market_item_purchased(event)
local item_name = event.item.name
if item_name == 'temporary-running-speed-bonus' then
boost_player_running_speed(event.player)
return
end
if item_name == 'temporary-mining-speed-bonus' then
boost_player_mining_speed(event.player)
return
end
end
nth_tick_token = Token.register(function()
local tick = game.tick
for k, v in pairs(speed_records) do
if tick - v.start_tick > 3000 then
local player = game.get_player(k)
if player and player.valid and player.connected and player.character then
reset_player_running_speed(player)
end
end
end
for k, v in pairs(mining_records) do
if tick - v.start_tick > 6000 then
local player = game.get_player(k)
if player and player.valid and player.connected and player.character then
reset_player_mining_speed(player)
end
end
end
if not next(speed_records) and not next(mining_records) then
unregister_event()
end
end)
local function fish_player_crafted_item(event)
if random(1, 50) == 1 then
fish_earned(event, 1)
end
end
local function player_created(event)
local player = game.get_player(event.player_index)
if not player or not player.valid then
return
end
local count = global.config.player_rewards.info_player_reward and 1 or 10
player.insert { name = currency, count = count }
end
Event.add(defines.events.on_pre_player_mined_item, pre_player_mined_item)
Event.add(defines.events.on_entity_died, fish_drop_entity_died)
Event.add(Retailer.events.on_market_purchase, market_item_purchased)
Event.add(defines.events.on_player_crafted_item, fish_player_crafted_item)
Event.add(defines.events.on_player_created, player_created)
if market_config.create_standard_market then
local delay = market_config.delay or 30
if delay then
local spawn_market_token = Token.register(spawn_market)
Event.on_init(function()
Task.set_timeout_in_ticks(delay, spawn_market_token)
end)
else
Event.on_init(spawn_market)
end
end

View File

@ -0,0 +1,66 @@
return {
{
name = 'temporary-running-speed-bonus',
name_label = {'market_items.running_speed_bonus_name_label'},
type = 'temporary-buff',
description = {'market_items.running_speed_bonus_description'},
sprite = 'technology/exoskeleton-equipment',
stack_limit = 1,
price = 10,
},
{
name = 'temporary-mining-speed-bonus',
name_label = {'market_items.mining_speed_bonus_name_label'},
type = 'temporary-buff',
description = {'market_items.mining_speed_bonus_description'},
sprite = 'technology/mining-productivity-1',
stack_limit = 1,
price = 10,
},
{price = 1, name = 'firearm-magazine'},
{price = 1, name = 'land-mine'},
{price = 1, name = 'wood'},
{price = 2, name = 'rocket'},
{price = 2, name = 'shotgun-shell'},
{price = 3, name = 'cannon-shell'},
{price = 4, name = 'defender-capsule'},
{price = 4, name = 'piercing-rounds-magazine'},
{price = 5, name = 'raw-fish'},
{price = 6, name = 'piercing-shotgun-shell'},
{price = 7, name = 'explosive-cannon-shell'},
{price = 7, name = 'explosive-rocket'},
{price = 8, name = 'grenade'},
{price = 10, name = 'artillery-shell'},
{price = 10, name = 'light-armor'},
{price = 25, name = 'artillery-targeting-remote'},
{price = 25, name = 'cliff-explosives'},
{price = 25, name = 'flamethrower-ammo'},
{price = 30, name = 'submachine-gun'},
{price = 32, name = 'cluster-grenade'},
{price = 35, name = 'construction-robot'},
{price = 40, name = 'poison-capsule'},
{price = 50, name = 'gun-turret'},
{price = 50, name = 'solar-panel-equipment'},
{price = 80, name = 'car'},
{price = 100, name = 'battery-equipment'},
{price = 125, name = 'heavy-armor'},
{price = 125, name = 'night-vision-equipment'},
{price = 125, name = 'rocket-launcher'},
{price = 175, name = 'exoskeleton-equipment'},
{price = 200, name = 'belt-immunity-equipment'},
{price = 200, name = 'energy-shield-equipment'},
{price = 250, name = 'combat-shotgun'},
{price = 250, name = 'personal-roboport-equipment'},
{price = 300, name = 'laser-turret'},
{price = 350, name = 'modular-armor'},
{price = 450, name = 'artillery-turret'},
{price = 450, name = 'flamethrower'},
{price = 625, name = 'battery-mk2-equipment'},
{price = 850, name = 'personal-laser-defense-equipment'},
{price = 1000, name = 'tunnel'},
{price = 1200, name = 'tank'},
{price = 1500, name = 'power-armor'},
{price = 2250, name = 'fusion-reactor-equipment'},
{price = 7500, name = 'atomic-bomb'},
{price = 12000, name = 'power-armor-mk2'},
}

View File

@ -0,0 +1,159 @@
local Event = require 'utils.event'
local Perlin = require 'map_gen.shared.perlin_noise'
local AlienEvolutionProgress = require 'utils.alien_evolution_progress'
local rock_list = {
'rock-huge-volcanic',
'rock-big-volcanic',
'rock-huge-black',
'rock-big-black',
'rock-huge-grey',
'rock-big-grey',
'rock-huge-red',
'rock-big-red',
'rock-huge-white',
'rock-big-white',
'rock-huge-brown',
'rock-big-brown',
'rock-huge-dustyrose',
'rock-big-dustyrose',
}
local rocks = #rock_list
local rock_map = util.list_to_map(rock_list)
-- == MAP GEN =================================================================
local starting_radius = 64
local biter_radius = 144
local PRECISION = 10e8
local function inside_radius(x, y, radius)
return x*x + y*y < radius*radius + 3600 * Perlin.noise(x, y)
end
local function worm_by_distance(x, y)
local evo = game.forces.enemy.evolution_factor or 0
local radius = math.sqrt(x*x + y*y)
local weighted_distance = radius * (evo + 0.45)
if weighted_distance < 1000 then
return 'small-worm-turret'
elseif weighted_distance < 1800 then
return 'medium-worm-turret'
elseif weighted_distance < 2600 then
return 'big-worm-turret'
else
return 'behemoth-worm-turret'
end
end
Event.add(defines.events.on_chunk_generated, function(event)
local surface = event.surface
if not (surface and surface.valid and surface.name == 'mines') then
return
end
local area = event.area
-- remove water
local tiles = surface.find_tiles_filtered { area = area, name = { 'deepwater', 'deepwater-green', 'water', 'water-green', 'water-mud', 'water-shallow' } }
local new_tiles = {}
for _, tile in pairs(tiles) do
table.insert(new_tiles, { name = 'volcanic-orange-heat-4', position = tile.position })
end
surface.set_tiles(new_tiles)
-- place rocks
local tx, ty = area.left_top.x, area.left_top.y
local bx, by = area.right_bottom.x, area.right_bottom.y
for x = tx, bx do
for y = ty, by do
local c = math.random(PRECISION)
if not inside_radius(x, y, starting_radius) and c > (0.55 * PRECISION) then
surface.create_entity { name = rock_list[math.random(rocks)], position = { x, y }, raise_built = false, move_stuck_players = true, force = 'neutral'}
else
if not inside_radius(x, y, biter_radius) and c < (0.000125 * PRECISION) then
surface.create_entity { name = worm_by_distance(x, y), position = {x, y}, move_stuck_players = true }
end
end
end
end
end)
-- == SPAWNERS ================================================================
Event.add(defines.events.on_player_mined_entity, function(event)
local player = game.get_player(event.player_index)
if not (player and player.valid) then
return
end
local entity = event.entity
if not (entity and entity.valid) then
return
end
if not rock_map[entity.name] then
return
end
local pos = entity.position
local c = math.random(PRECISION)
if c < (0.005 * PRECISION) and not inside_radius(pos.x, pos.y, biter_radius) then
entity.surface.create_entity {
name = math.random() > 0.40 and 'biter-spawner' or 'spitter-spawner',
position = entity.position,
force = 'enemy',
target = player.character,
move_stuck_players = true,
}
end
end)
local function give_command(group, target)
if target and target.valid then
local command = { type = defines.command.attack, target = target, distraction = defines.distraction.by_damage }
group.set_command(command)
group.start_moving()
else
local command = { type = defines.command.attack_area, destination = {0, 0}, radius = 32, distraction = defines.distraction.by_damage }
local members = group.members
for i = 1, #members do
local entitiy = members[i]
entitiy.set_command(command)
end
end
end
Event.add(defines.events.on_entity_died, function(event)
local entity = event.entity
if not entity or not (entity.name == 'biter-spawner' or entity.name == 'spitter-spawner') then
return
end
local surface = entity.surface
local position = entity.position
local spawn = entity.surface.create_entity
local evo = game.forces.enemy.evolution_factor
local spawner = AlienEvolutionProgress.create_spawner_request(math.ceil(evo * 100 / 4))
local aliens = AlienEvolutionProgress.get_aliens(spawner, evo)
local group = surface.create_unit_group { position = position }
local add_member = group.add_member
for name, count in pairs(aliens) do
for i = 1, count do
local ent = spawn{ name = name, position = position, force = 'enemy', move_stuck_players = true, }
if ent then
add_member(ent)
end
end
end
give_command(group, event.cause)
end)
-- ============================================================================

View File

@ -0,0 +1,194 @@
local Discord = require 'resources.discord'
local Server = require 'features.server'
local Core = require 'utils.core'
local Restart = require 'features.restart_command'
local ShareGlobals = require 'map_gen.maps.april_fools.scenario.shared_globals'
local ScoreTracker = require 'utils.score_tracker'
local PlayerStats = require 'features.player_stats'
local format_number = require 'util'.format_number
return function(config)
local map_promotion_channel = Discord.channel_names.map_promotion
local events_channel = Discord.channel_names.events
local map_update_role_mention = Discord.role_mentions.map_update
-- Use these settings for testing
--local map_promotion_channel = Discord.channel_names.bot_playground
--local events_channel = Discord.channel_names.bot_playground
--local map_update_role_mention = Discord.role_mentions.test
Restart.set_start_game_data({type = Restart.game_types.scenario, name = config.scenario_name or 'April Fools', mod_pack = config.mod_pack})
local function can_restart(player)
if player.admin then
return true
end
if not ShareGlobals.data.map_won then
player.print({'command_description.danger_ore_restart_condition_not_met'})
return false
end
return true
end
local function restart_callback()
local start_game_data = Restart.get_start_game_data()
local new_map_name = start_game_data.name
local time_string = Core.format_time(game.ticks_played)
local end_epoch = Server.get_current_time()
if end_epoch == nil then
end_epoch = -1 -- end_epoch is nil if the restart command is used locally rather than on the server
end
local player_data = {}
for _, p in pairs(game.players) do
player_data[p.index] = {
name = p.name,
total_kills = ScoreTracker.get_for_player(p.index, PlayerStats.player_total_kills_name),
spawners_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_spawners_killed_name),
worms_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_worms_killed_name),
units_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_units_killed_name),
turrets_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_turrets_killed_name),
distance_walked = ScoreTracker.get_for_player(p.index, PlayerStats.player_distance_walked_name),
player_deaths = ScoreTracker.get_for_player(p.index, PlayerStats.player_deaths_name),
entities_built = ScoreTracker.get_for_player(p.index, PlayerStats.player_entities_built_name),
entities_crafted = ScoreTracker.get_for_player(p.index, PlayerStats.player_items_crafted_name),
resources_hand_mined = ScoreTracker.get_for_player(p.index, PlayerStats.player_resources_hand_mined_name),
time_played = p.online_time
}
end
local statistics = {
scenario = config.scenario_name or 'April Fools',
start_epoch = Server.get_start_time(),
end_epoch = end_epoch, -- stored as key already, useful to have it as part of same structure
game_ticks = game.ticks_played,
biters_killed = ScoreTracker.get_for_global(PlayerStats.aliens_killed_name),
total_players = #game.players,
entities_built = ScoreTracker.get_for_global(PlayerStats.built_by_players_name),
resources_exhausted = ScoreTracker.get_for_global(PlayerStats.resources_exhausted_name),
resources_hand_mined = ScoreTracker.get_for_global(PlayerStats.resources_hand_mined_name),
player_data = player_data
}
local awards = {
['total_kills'] = {value = 0, player = ""},
['units_killed'] = {value = 0, player = ""},
['spawners_killed'] = {value = 0, player = ""},
['worms_killed'] = {value = 0, player = ""},
['player_deaths'] = {value = 0, player = ""},
['time_played'] = {value = 0, player = ""},
['entities_built'] = {value = 0, player = ""},
['entities_crafted'] = {value = 0, player = ""},
['distance_walked'] = {value = 0, player = ""},
['resources_hand_mined'] = {value = 0, player = ""}
}
for _, v in pairs(statistics.player_data) do
if v.total_kills > awards.total_kills.value then
awards.total_kills.value = v.total_kills
awards.total_kills.player = v.name
end
if v.units_killed > awards.units_killed.value then
awards.units_killed.value = v.units_killed
awards.units_killed.player = v.name
end
if v.spawners_killed > awards.spawners_killed.value then
awards.spawners_killed.value = v.spawners_killed
awards.spawners_killed.player = v.name
end
if v.worms_killed > awards.worms_killed.value then
awards.worms_killed.value = v.worms_killed
awards.worms_killed.player = v.name
end
if v.player_deaths > awards.player_deaths.value then
awards.player_deaths.value = v.player_deaths
awards.player_deaths.player = v.name
end
if v.time_played > awards.time_played.value then
awards.time_played.value = v.time_played
awards.time_played.player = v.name
end
if v.entities_built > awards.entities_built.value then
awards.entities_built.value = v.entities_built
awards.entities_built.player = v.name
end
if v.entities_crafted > awards.entities_crafted.value then
awards.entities_crafted.value = v.entities_crafted
awards.entities_crafted.player = v.name
end
if v.distance_walked > awards.distance_walked.value then
awards.distance_walked.value = v.distance_walked
awards.distance_walked.player = v.name
end
if v.resources_hand_mined > awards.resources_hand_mined.value then
awards.resources_hand_mined.value = v.resources_hand_mined
awards.resources_hand_mined.player = v.name
end
end
local resource_prototypes = game.get_filtered_entity_prototypes({{filter = "type", type = "resource"}})
local ore_products = {}
for _, ore_prototype in pairs(resource_prototypes) do
local mineable_properties = ore_prototype.mineable_properties
if mineable_properties.minable and ore_prototype.resource_category == 'basic-solid' then
for _, product in pairs(mineable_properties.products) do
ore_products[product.name] = true
end
end
end
local total_ore = 0
local ore_totals_message = '('
for ore_name in pairs(ore_products) do
local count = game.forces["player"].item_production_statistics.get_input_count(ore_name)
total_ore = total_ore + count
ore_totals_message = ore_totals_message..ore_name:gsub( "-ore", "")..": "..format_number(count, true)..", "
end
ore_totals_message = ore_totals_message:sub(1, -3)..')' -- remove the last ", " and add a bracket
ore_totals_message = "Total ore mined: "..format_number(total_ore, true).. "\\n"..ore_totals_message
local statistics_message = statistics.scenario..' completed!\\n\\n'..
'Statistics:\\n'..
'Map time: '..time_string..'\\n'..
'Total entities built: '..statistics.entities_built..'\\n'..
'Total ore mined:'..ore_totals_message..'\\n'..
'Total ore resources exhausted: '..statistics.resources_exhausted..'\\n'..
'Total ore hand mined: '..statistics.resources_hand_mined..'\\n'..
'Players: '..statistics.total_players..'\\n'..
'Enemies killed: '..statistics.biters_killed..'\\n\\n'..
'Awards:\\n'..
'Most ore hand mined:'..awards.resources_hand_mined.player..' ('..awards.resources_hand_mined.value..')\\n'..
'Most items crafted: '..awards.entities_crafted.player..' ('..awards.entities_crafted.value..')\\n'..
'Most entities built: '..awards.entities_built.player..' ('..awards.entities_built.value..')\\n'..
'Most time played: '..awards.time_played.player..' ('..Core.format_time(awards.time_played.value)..')\\n'..
'Furthest walked: '..awards.distance_walked.player..' ('..math.floor(awards.distance_walked.value)..')\\n'..
'Most deaths: '..awards.player_deaths.player..' ('..awards.player_deaths.value..')\\n'..
'Most kills overall: '..awards.total_kills.player..' ('..awards.total_kills.value..')\\n'..
'Most biters/spitters killed: '..awards.units_killed.player..' ('..awards.units_killed.value..')\\n'..
'Most spawners killed: '..awards.spawners_killed.player..' ('..awards.spawners_killed.value..')\\n'..
'Most worms killed: '..awards.worms_killed.player..' ('..awards.worms_killed.value..')\\n'
Server.to_discord_named_embed(map_promotion_channel, statistics_message)
Server.to_discord_named_embed(events_channel, statistics_message)
Server.set_data('danger_ores_data', tostring(end_epoch), statistics)
local message = {
map_update_role_mention,
' **April Fools has just restarted! Previous map lasted: ',
time_string,
'!\\n',
'Next map: ',
new_map_name,
'**'
}
message = table.concat(message)
Server.to_discord_named_raw(map_promotion_channel, message)
end
Restart.register(can_restart, restart_callback, nil)
end

View File

@ -0,0 +1,80 @@
local Event = require 'utils.event'
local Server = require 'features.server'
local ShareGlobals = require 'map_gen.maps.april_fools.scenario.shared_globals'
ShareGlobals.data.map_won = false
local win_satellite_count = 1000
_G.rocket_launched_win_data = {
extra_rockets = win_satellite_count
}
local function disable_biters()
game.forces.enemy.kill_all_units()
for _, surface in pairs(game.surfaces) do
for _, enemy_entity in pairs(surface.find_entities_filtered({ force = 'enemy' })) do
enemy_entity.destroy()
end
end
local message = 'Launching the last satellite has killed all the biters!'
game.print(message)
Server.to_discord_bold(message)
end
local function win()
if ShareGlobals.data.map_won then
return
end
ShareGlobals.data.map_won = true
local message = 'Congratulations! The map has been won. Restart the map with /restart'
game.print(message)
Server.to_discord_bold(message)
end
local function print_satellite_message(count)
local remaining_count = win_satellite_count - count
if remaining_count <= 0 then
return
end
local message = table.concat { 'Launch another ', remaining_count, ' satellites to win the map.' }
game.print(message)
Server.to_discord_bold(message)
end
local function rocket_launched(event)
if ShareGlobals.data.map_won then
return
end
local entity = event.rocket
if not entity or not entity.valid or not entity.force == 'player' then
return
end
local inventory = entity.get_inventory(defines.inventory.rocket)
if not inventory or not inventory.valid then
return
end
local satellite_count = game.forces.player.get_item_launched('satellite')
if satellite_count == 0 then
return
end
if satellite_count == win_satellite_count then
disable_biters()
win()
return
end
if (satellite_count % 50) == 0 then
print_satellite_message(satellite_count)
end
end
Event.add(defines.events.on_rocket_launched, rocket_launched)

View File

@ -0,0 +1,228 @@
local Event = require 'utils.event'
local Generate = require 'map_gen.shared.generate'
local Global = require 'utils.global'
local Queue = require 'utils.queue'
local AlienEvolutionProgress = require 'utils.alien_evolution_progress'
local Task = require 'utils.task'
local Token = require 'utils.token'
local Server = require 'features.server'
local ShareGlobals = require 'map_gen.maps.april_fools.scenario.shared_globals'
ShareGlobals.data.map_won = false
local config = {
recent_chunks_max = 20,
ticks_between_waves = 60 * 30,
enemy_factor = 5,
max_enemies_per_wave_per_chunk = 60,
extra_rockets = 33
}
local recent_chunks = Queue.new() -- Keeps track of recently revealed chunks
local recent_chunks_max = config.recent_chunks_max or 10 -- Maximum number of chunks to track
local ticks_between_waves = config.ticks_between_waves or (60 * 30)
local enemy_factor = config.enemy_factor or 5
local max_enemies_per_wave_per_chunk = config.max_enemies_per_wave_per_chunk or 60
local win_data = { evolution_rocket_maxed = -1, extra_rockets = config.extra_rockets or 100 }
_G.rocket_launched_win_data = win_data
Global.register({ recent_chunks = recent_chunks, win_data = win_data }, function(tbl)
recent_chunks = tbl.recent_chunks
win_data = tbl.win_data
end)
local function give_command(group, data)
local target = data.target
if target and target.valid then
local command = { type = defines.command.attack, target = target, distraction = defines.distraction.by_damage }
group.set_command(command)
group.start_moving()
else
local command = { type = defines.command.attack_area, destination = data.position, radius = 32, distraction = defines.distraction.by_damage }
local members = group.members
for i = 1, #members do
local entitiy = members[i]
entitiy.set_command(command)
end
end
end
local do_waves
local do_wave
do_waves = Token.register(function(data)
Task.queue_task(do_wave, data, 10)
end)
do_wave = Token.register(function(data)
local wave = data.wave
local last_wave = data.last_wave
-- game.print('wave: ' .. wave .. '/' .. last_wave)
local chunk_index = data.chunk_index
local chunk = data.chunks[chunk_index]
if not chunk then
data.wave = wave + 1
data.chunk_index = 1
Task.set_timeout_in_ticks(ticks_between_waves, do_waves, data)
return false
end
local spawner = data.spawner
local aliens = AlienEvolutionProgress.get_aliens(spawner, game.forces.enemy.evolution_factor)
local left_top = chunk.area.left_top
local center = { left_top.x + 16, left_top.y + 16 }
local surface = chunk.surface
local find_non_colliding_position = surface.find_non_colliding_position
local create_entity = surface.create_entity
local group = surface.create_unit_group { position = center }
local add_member = group.add_member
for name, count in pairs(aliens) do
for i = 1, count do
local pos = find_non_colliding_position(name, center, 32, 1)
if pos then
local e = { name = name, position = pos, force = 'enemy', center = center, radius = 16, 1 }
local ent = create_entity(e)
add_member(ent)
end
end
end
give_command(group, data)
if chunk_index < recent_chunks_max then
data.chunk_index = chunk_index + 1
return true
end
if wave < last_wave then
data.wave = wave + 1
data.chunk_index = 1
Task.set_timeout_in_ticks(ticks_between_waves, do_waves, data)
end
return false
end)
local function start_waves(event)
local num_enemies = enemy_factor * game.forces.player.get_item_launched('satellite')
local number_of_waves = math.ceil(num_enemies / max_enemies_per_wave_per_chunk)
local num_enemies_per_wave_per_chunk = math.ceil(num_enemies / number_of_waves)
local target = event.rocket_silo
local position
if target and target.valid then
position = target.position
else
position = { 0, 0 }
end
local data = {
spawner = AlienEvolutionProgress.create_spawner_request(num_enemies_per_wave_per_chunk),
wave = 1,
last_wave = number_of_waves,
chunk_index = 1,
chunks = Queue.to_array(recent_chunks),
target = target,
position = position,
}
Task.set_timeout_in_ticks(1, do_waves, data)
game.print('Warning incomming biter attack! Number of waves: ' .. number_of_waves)
end
local function rocket_launched(event)
local entity = event.rocket
if not entity or not entity.valid or not entity.force == 'player' then
return
end
local inventory = entity.get_inventory(defines.inventory.rocket)
if not inventory or not inventory.valid then
return
end
local satellite_count = game.forces.player.get_item_launched('satellite')
if satellite_count == 0 then
return
end
-- Increase enemy_evolution
local current_evolution = game.forces.enemy.evolution_factor
if (satellite_count % 5) == 0 and win_data.evolution_rocket_maxed == -1 then
local message = 'Continued launching of satellites has angered the local biter population, evolution increasing...'
game.print(message)
Server.to_discord_bold(message)
current_evolution = current_evolution + 0.05
game.forces.enemy.evolution_factor = current_evolution
end
if current_evolution < 1 then
start_waves(event)
return
end
if win_data.evolution_rocket_maxed == -1 then
win_data.evolution_rocket_maxed = satellite_count
end
local remaining_satellites = win_data.evolution_rocket_maxed + win_data.extra_rockets - satellite_count
if remaining_satellites > 0 then
local message = 'Biters at maximum evolution! Protect the base for an additional ' .. remaining_satellites .. ' rockets to wipe them out forever.'
game.print(message)
Server.to_discord_bold(message)
start_waves(event)
return
end
local win_message = 'Congratulations! Biters have been wiped from the map!'
game.print(win_message)
Server.to_discord_bold(win_message)
ShareGlobals.data.map_won = true
game.forces.enemy.kill_all_units()
for _, surface in pairs(game.surfaces) do
for _, enemy_entity in pairs(surface.find_entities_filtered({ force = 'enemy' })) do
enemy_entity.destroy()
end
end
end
local bad_tiles = { 'deepwater-green', 'deepwater', 'out-of-map', 'water-green', 'water' }
local function chunk_unlocked(chunk)
if chunk.surface and chunk.surface.name == 'islands' then
-- Dont need to rule out chunks with water if biters can walk on water (Walkable Water mod)
if not script.active_mods['walkable-water'] then
local count = chunk.surface.count_tiles_filtered({ area = chunk.area, name = bad_tiles, limit = 1 })
if count > 0 then
return
end
end
Queue.push(recent_chunks, chunk)
while Queue.size(recent_chunks) > recent_chunks_max do
Queue.pop(recent_chunks)
end
end
end
Event.add(defines.events.on_rocket_launched, rocket_launched)
Event.add(Generate.events.on_chunk_generated, chunk_unlocked)

View File

@ -0,0 +1,17 @@
local Global = require 'utils.global'
local Public = {
data = {}
}
_G.april_fools_shared_globals = Public.data
Global.register(
Public.data,
function(tbl)
Public.data = tbl
_G.april_fools_shared_globals = tbl
end
)
return Public

View File

@ -0,0 +1,217 @@
--[[
Map gen settings generator supporting Alien Biome's noise levels & autoplace settings
=== Preset Library ===
A MGS preset is a dictionary of autoplace controls for:
- aux (Alien Biomes)
- moisture (Alien Biomes)
- temperature (Alien Biomes)
- enemy
- trees
- water
full updated list available at: resources/alien_biomes/biomes.lua
** AB.set_preset(data), AB.remove_preset(name), AB.clear_presets()
=== Param Customization ===
** AB.override_vanilla_mgs(MapGenSettings)
Adds aux and moisture parameters to neg generated maps.
@usage
local AB = require 'map_gen.shared.alien_biomes'
AB.override_vanilla_mgs(game.surfaces.redmew.map_gen_settings)
=== Map Gen Settings ===
** AB.new_from_existent(config)
1. Generate a new random MGS based off default preset
@usage
local AB = require 'map_gen.shared.alien_biomes'
local new_mgs = AB.new_from_existent()
2. Generate a new random MGS based on current surface
@usage
local AB = require 'map_gen.shared.alien_biomes'
local new_mgs = AB.new_from_existent({map_gen_settings = game.surfaces.redmew})
** AB.new_from_preset(config)
1. Generate a new random MGS based off a random preset from the AB library
@usage
local AB = require 'map_gen.shared.alien_biomes'
local new_mgs = AB.new_from_preset()
2. Generate a new random MGS based on a specific preset from the AB library
@usage
local AB = require 'map_gen.shared.alien_biomes'
local new_mgs = AB.new_from_preset({preset_name = 'volcano'})
]]
require 'util'
require 'utils.table'
local Global = require 'utils.global'
local Biomes = require 'resources.alien_biomes.biomes'
local Public = {}
local _this = {
presets = Biomes.presets
}
Global.register(_this, function(tbl) _this = tbl end)
-- === PRESET LIBRARY MANIPULATION ============================================
--- Adds a new preset to the global table
---@param data table<{ name: tostringing, preset: table }>
---@return bool
function Public.set_preset(data)
if not (data and data.name and data.preset) then
return false
end
_this.presets[data.name] = data.preset
return _this.presets[data.name] ~= nil
end
--- Remove target preset from the global presets list
---@param name tostringing
---@return bool
function Public.remove_preset(name)
if not (name and type(name) == 'tostringing') then
return false
end
_this.presets[name] = nil
return _this.presets[name] ~= nil
end
--- Clears the global table from all presets
---@return bool
function Public.clear_presets()
for key, _ in pairs(_this.presets) do
_this.presets[key] = nil
end
return table_size(_this.presets) == 0
end
-- === PARAM CUSTOMIZATION ====================================================
local function apply_temperature(mgs)
local hf, hs = 1, 1
local cf, cs = 1, 1
if _LIFECYCLE == _STAGE.init or _LIFECYCLE == _STAGE.runtime then
hf = hf + 0.5*(math.random()-0.5)
hs = hs + 0.2*(math.random()-0.5)
cf = cf + 0.5*(math.random()-0.5)
cs = cs + 0.2*(math.random()-0.5)
end
mgs.autoplace_controls = mgs.autoplace_controls or {}
mgs.autoplace_controls.hot = { frequency = hf, size = hs }
mgs.autoplace_controls.cold = { frequency = cf, size = cs }
end
--- Adds +-25% freq and +-10% bias to Aux autoplace
---@param mgs MapGenSettings
local function apply_aux(mgs)
local freq, bias = 1, 0
if _LIFECYCLE == _STAGE.init or _LIFECYCLE == _STAGE.runtime then
freq = freq + 0.5*(math.random()-0.5)
bias = bias + 0.2*(math.random()-0.5)
end
mgs.property_expression_names = mgs.property_expression_names or {}
mgs.property_expression_names['control-setting:aux:bias'] = tostring(bias)
mgs.property_expression_names['control-setting:aux:frequency'] = tostring(freq)
end
--- Adds +-25% freq and +-10% bias to Moisture autoplace
---@param mgs MapGenSettings
local function apply_moisture(mgs)
local freq, bias = 1, 0
if _LIFECYCLE == _STAGE.init or _LIFECYCLE == _STAGE.runtime then
freq = freq + 0.5*(math.random()-0.5)
bias = bias + 0.2*(math.random()-0.5)
end
mgs.property_expression_names = mgs.property_expression_names or {}
mgs.property_expression_names['control-setting:moisture:bias'] = tostring(bias)
mgs.property_expression_names['control-setting:moisture:frequency'] = tostring(freq)
end
--- Adds random aux and moisture to default vanilla MapGenSettings
--- Is safe to call even for vanilla scenarios
---@param mgs MapGenSettings
function Public.override_vanilla_mgs(mgs)
if not script.active_mods['alien-biomes'] then
return
end
apply_aux(mgs)
apply_moisture(mgs)
apply_temperature(mgs)
end
-- === MAP GEN SETTINGS =======================================================
--- Generates a new random map_gen_setting from a given preset. If none is passed, the default one is used instead
--- Is safe to call even for vanilla scenarios
---@param config table
---@field seed? number
---@field map_gen_settings? MapGenSettings
---@return MapGenSettings
function Public.new_from_existent(config)
config = config or {}
local mgs = game.default_map_gen_settings
if config.map_gen_settings then
mgs = config.map_gen_settings
end
if _LIFECYCLE == _STAGE.init or _LIFECYCLE == _STAGE.runtime then
mgs.seed = config.seed or math.random(4294967295)
end
Public.override_vanilla(mgs)
return mgs
end
--- Generates a random map_gen_setting from the available presets
---- Is safe to call even for vanilla scenarios
--@param config table
---@field seed? number
---@field preset_name? tostringing
---@field map_gen_settings? MapGenSetting
---@return MapGenSettings
function Public.new_from_preset(config)
config = config or {}
local mgs = game.default_map_gen_settings
mgs.seed = config.seed or mgs.seed or 4294967295
local n_presets = table_size(_this.presets)
local index = mgs.seed % n_presets + 1
if config.map_gen_settings then
mgs = util.merge{mgs, config.map_gen_settings}
end
if _LIFECYCLE == _STAGE.init or _LIFECYCLE == _STAGE.runtime then
mgs.seed = config.seed or math.random(4294967295)
index = math.random(n_presets)
end
if script.active_mods['alien-biomes'] and n_presets > 0 then
local preset_name = config.preset_name or table.keys(_this.presets)[index]
local preset = Biomes.preset_to_mgs(_this.presets[preset_name])
mgs = util.merge{mgs, preset}
end
return mgs
end
-- ============================================================================
return Public

View File

@ -0,0 +1,133 @@
local b = require 'resources.alien_biomes.biomes_settings'
local str = tostring
local Public = {}
-- Converts a table of settings from resources/alien_biomes/biome_settings into a valid MapGenSettings
function Public.preset_to_mgs(preset)
local mgs = {
autoplace_controls = {},
property_expression_names = {},
cliff_settings = {},
}
if preset.aux then
mgs.property_expression_names['control-setting:aux:bias'] = str(preset.aux.aux.bias or 0)
mgs.property_expression_names['control-setting:aux:frequency'] = str(preset.aux.aux.frequency or 1)
end
if preset.moisture then
mgs.property_expression_names['control-setting:moisture:bias'] = str(preset.moisture.moisture.bias or 0)
mgs.property_expression_names['control-setting:moisture:frequency'] = str(preset.moisture.moisture.frequency or 1)
end
if preset.enemy then
mgs.autoplace_controls['enemy-base'] = preset.enemy['enemy-base']
end
if preset.temperature then
local t = preset.temperature
if t.cold then
mgs.autoplace_controls.cold = t.cold
end
if t.hot then
mgs.autoplace_controls.hot = t.hot
end
end
if preset.water then
mgs.water = preset.water.water.size
if preset.water.water.frequency and preset.water.water.frequency > 0 then
mgs.terrain_segmentation = 1 / preset.water.water.frequency
end
end
if preset.trees then
mgs.autoplace_controls.trees = preset.trees.trees
end
for _, k in pairs({'autoplace_controls', 'property_expression_names', 'cliff_settings'}) do
if table_size(mgs[k]) == 0 then
mgs[k] = nil
end
end
return mgs
end
Public.presets = {
default = {
aux = b.aux.med,
enemy = b.enemy.high,
moisture = b.moisture.med,
temperature = b.temperature.balanced,
trees = b.trees.high,
water = b.water.med,
},
cloud = {
aux = b.aux.very_low,
enemy = b.enemy.none,
moisture = b.moisture.high,
temperature = b.temperature.cool,
trees = b.trees.med,
water = b.water.high,
},
ice = {
aux = b.aux.med,
enemy = b.enemy.none,
moisture = b.moisture.med,
temperature = b.temperature.frozen,
trees = b.trees.none,
water = b.water.low,
},
volcano = {
aux = b.aux.very_low,
enemy = b.enemy.none,
moisture = b.moisture.none,
temperature = b.temperature.volcanic,
trees = b.trees.none,
water = b.water.none,
},
mountain = {
aux = b.aux.low,
enemy = b.enemy.low,
moisture = b.moisture.low,
temperature = b.temperature.wild,
trees = b.trees.none,
water = b.water.low,
},
neptune = {
aux = b.aux.very_high,
enemy = b.enemy.none,
moisture = b.moisture.high,
temperature = b.temperature.bland,
trees = b.trees.med,
water = b.water.max,
},
jungle = {
aux = b.aux.very_low,
enemy = b.enemy.med,
moisture = b.moisture.high,
temperature = b.temperature.bland,
trees = b.trees.high,
water = b.water.med,
},
canyon = {
aux = b.aux.low,
enemy = b.enemy.med,
moisture = b.moisture.high,
temperature = b.temperature.hot,
trees = b.trees.low,
water = b.water.none,
},
desert = {
aux = b.aux.low,
enemy = b.enemy.med,
moisture = b.moisture.low,
temperature = b.temperature.warm,
trees = b.trees.low,
water = b.water.low,
},
}
return Public

View File

@ -0,0 +1,71 @@
return {
-- AUX
-- range: [0, 1]
-- determines whether low-moisture tiles become sand or red desert
aux = {
very_low = { aux = { frequency = 1, bias = -0.5 } },
low = { aux = { frequency = 1, bias = -0.3 } },
med = { aux = { frequency = 1, bias = -0.1 } },
high = { aux = { frequency = 1, bias = 0.2 } },
very_high = { aux = { frequency = 1, bias = 0.5 } },
},
-- MOISTURE
-- range: [0, 1]
-- determines whether a tile becomes sandy (low moisture) or grassy (high moisture).
moisture = {
none = { moisture = { frequency = 2, bias = -1 } },
low = { moisture = { frequency = 1, bias = -0.15 } },
med = { moisture = { frequency = 1, bias = 0 } },
high = { moisture = { frequency = 1, bias = 0.15 } },
max = { moisture = { frequency = 2, bias = 0.5 } },
},
temperature = {
-- mixed
bland = { hot = { frequency = 0.5, size = 0 }, cold = { frequency = 0.5, size = 0 } },
temperate = { hot = { frequency = 1, size = 0.25 }, cold = { frequency = 1, size = 0.25 } },
midrange = { hot = { frequency = 1, size = 0.65 }, cold = { frequency = 1, size = 0.65 } },
balanced = { hot = { frequency = 1, size = 1 }, cold = { frequency = 1, size = 1 } },
wild = { hot = { frequency = 1, size = 3 }, cold = { frequency = 1, size = 3 } },
extreme = { hot = { frequency = 1, size = 6 }, cold = { frequency = 1, size = 6 } },
-- cold
cool = { hot = { frequency = 0.75, size = 0 }, cold = { frequency = 0.75, size = 0.5 } },
cold = { hot = { frequency = 0.75, size = 0 }, cold = { frequency = 0.75, size = 1 } },
very_cold = { hot = { frequency = 0.75, size = 0 }, cold = { frequency = 0.75, size = 2.2 } },
frozen = { hot = { frequency = 0.75, size = 0 }, cold = { frequency = 0.75, size = 6 } },
-- hot
warm = { hot = { frequency = 0.75, size = 0.5 }, cold = { frequency = 0.75, size = 0 } },
hot = { hot = { frequency = 0.75, size = 1 }, cold = { frequency = 0.75, size = 0 } },
very_hot = { hot = { frequency = 0.75, size = 2.2 }, cold = { frequency = 0.75, size = 0 } },
volcanic = { hot = { frequency = 0.75, size = 6 }, cold = { frequency = 0.75, size = 0 } },
},
trees = {
none = { trees = { frequency = 0.25, size = 0, richness = 0 } },
low = { trees = { frequency = 0.6, size = 0.35, richness = 0.8 } },
med = { trees = { frequency = 0.8, size = 0.66, richness = 1 } },
high = { trees = { frequency = 1, size = 1, richness = 1 } },
max = { trees = { frequency = 3, size = 1, richness = 1 } },
},
cliff = {
none = { cliff = { frequency = 0.01, richness = 0 } },
low = { cliff = { frequency = 0.3, richness = 0.3 } },
med = { cliff = { frequency = 1, richness = 1 } },
high = { cliff = { frequency = 2, richness = 2 } },
max = { cliff = { frequency = 6, richness = 2 } },
},
water = {
none = { water = { frequancy = 1, size = 0 } },
low = { water = { frequency = 0.5, size = 0.3 } },
med = { water = { frequency = 1, size = 1 } },
high = { water = { frequency = 1, size = 4 } },
max = { water = { frequency = 0.5, size = 10 } },
},
enemy = {
none = { ['enemy-base'] = { frequency = 1e-6, size = -1, richness = -1 } },
very_low = { ['enemy-base'] = { frequency = 0.1, size = 0.1, richness = 0.1 } },
low = { ['enemy-base'] = { frequency = 0.2, size = 0.2, richness = 0.2 } },
med = { ['enemy-base'] = { frequency = 0.5, size = 0.5, richness = 0.5 } },
high = { ['enemy-base'] = { frequency = 1, size = 1, richness = 1 } },
very_high = { ['enemy-base'] = { frequency = 1.5, size = 2, richness = 1.5 } },
max = { ['enemy-base'] = { frequency = 2, size = 6, richness = 2 } },
},
}

View File

@ -0,0 +1,49 @@
local function make_variations(name, min, max)
local tiles = {}
for i=min, max do
table.insert(tiles, name .. '-' .. tostring(i))
end
return tiles
end
return {
['frozen-snow'] = make_variations('frozen-snow', 0, 9),
['mineral-aubergine-dirt'] = make_variations('mineral-aubergine-dirt', 1, 6),
['mineral-aubergine-sand'] = make_variations('mineral-aubergine-sand', 1, 3),
['mineral-beige-dirt'] = make_variations('mineral-beige-dirt', 1, 6),
['mineral-beige-sand'] = make_variations('mineral-beige-sand', 1, 3),
['mineral-black-dirt'] = make_variations('mineral-black-dirt', 1, 6),
['mineral-black-sand'] = make_variations('mineral-black-sand', 1, 3),
['mineral-brown-dirt'] = make_variations('mineral-brown-dirt', 1, 6),
['mineral-brown-sand'] = make_variations('mineral-brown-sand', 1, 3),
['mineral-cream-dirt'] = make_variations('mineral-cream-dirt', 1, 6),
['mineral-cream-sand'] = make_variations('mineral-cream-sand', 1, 3),
['mineral-dustyrose-dirt'] = make_variations('mineral-dustyrose-dirt', 1, 6),
['mineral-dustyrose-sand'] = make_variations('mineral-dustyrose-sand', 1, 3),
['mineral-grey-dirt'] = make_variations('mineral-grey-dirt', 1, 6),
['mineral-grey-sand'] = make_variations('mineral-grey-sand', 1, 3),
['mineral-purple-dirt'] = make_variations('mineral-purple-dirt', 1, 6),
['mineral-purple-sand'] = make_variations('mineral-purple-sand', 1, 3),
['mineral-red-dirt'] = make_variations('mineral-red-dirt', 1, 6),
['mineral-red-sand'] = make_variations('mineral-red-sand', 1, 3),
['mineral-tan-dirt'] = make_variations('mineral-tan-dirt', 1, 6),
['mineral-tan-sand'] = make_variations('mineral-tan-sand', 1, 3),
['mineral-violet-dirt'] = make_variations('mineral-violet-dirt', 1, 6),
['mineral-violet-sand'] = make_variations('mineral-violet-sand', 1, 3),
['mineral-white-dirt'] = make_variations('mineral-white-dirt', 1, 6),
['mineral-white-sand'] = make_variations('mineral-white-sand', 1, 3),
['vegetation-blue-grass'] = make_variations('vegetation-blue-grass', 1, 2),
['vegetation-green-grass'] = make_variations('vegetation-green-grass', 1, 4),
['vegetation-mauve-grass'] = make_variations('vegetation-mauve-grass', 1, 2),
['vegetation-olive-grass'] = make_variations('vegetation-olive-grass', 1, 2),
['vegetation-orange-grass'] = make_variations('vegetation-orange-grass', 1, 2),
['vegetation-purple-grass'] = make_variations('vegetation-purple-grass', 1, 2),
['vegetation-red-grass'] = make_variations('vegetation-red-grass', 1, 2),
['vegetation-turquoise-grass'] = make_variations('vegetation-turquoise-grass', 1, 2),
['vegetation-violet-grass'] = make_variations('vegetation-violet-grass', 1, 2),
['vegetation-yellow-grass'] = make_variations('vegetation-yellow-grass', 1, 2),
['volcanic-blue-heat'] = make_variations('volcanic-blue-heat', 1, 4),
['volcanic-green-heat'] = make_variations('volcanic-green-heat', 1, 4),
['volcanic-orange-heat'] = make_variations('volcanic-orange-heat', 1, 4),
['volcanic-purple-heat'] = make_variations('volcanic-purple-heat', 1, 4),
}

View File

@ -0,0 +1,262 @@
--[[
Each preset will be similar to:
grass = autoplace_settings = {
tile = {
treat_missing_as_default = false,
settings = {
['grass-1'] = {frequency = 1, size = 1, richness = 1},
['grass-2'] = {frequency = 1, size = 1, richness = 1},
['grass-3'] = {frequency = 1, size = 1, richness = 1},
['grass-4'] = {frequency = 1, size = 1, richness = 1},
}
}
}
- no water, you will need to add it if wanted
- tiles not listed tiles will not be placed
]]
local Table = require 'utils.table'
local ab_tiles = require 'resources.alien_biomes.tile_names'
local function make_default_autoplace_settings(tile_table)
local autoplace_settings = {
tile = {
treat_missing_as_default = false,
settings = {}
}
}
for _, tile_name in pairs(Table.concat_tables(tile_table)) do
autoplace_settings.tile.settings[tile_name] = {frequency = 1, size = 1, richness = 1}
end
return autoplace_settings
end
return {
-- Color subsets
green = make_default_autoplace_settings{
ab_tiles['vegetation-green-grass'],
ab_tiles['vegetation-olive-grass'],
ab_tiles['vegetation-turquioise-grass']
},
white = make_default_autoplace_settings{
ab_tiles['mineral-white-dirt'],
ab_tiles['mineral-white-sand'],
},
grey = make_default_autoplace_settings{
ab_tiles['mineral-grey-dirt'],
ab_tiles['mineral-grey-sand'],
},
black = make_default_autoplace_settings{
ab_tiles['mineral-black-dirt'],
ab_tiles['mineral-black-sand'],
},
aubergine = make_default_autoplace_settings{
ab_tiles['mineral-aubergine-dirt'],
ab_tiles['mineral-aubergine-sand'],
},
purple = make_default_autoplace_settings{
ab_tiles['mineral-purple-dirt'],
ab_tiles['mineral-purple-sand'],
},
red = make_default_autoplace_settings{
ab_tiles['mineral-red-dirt'],
ab_tiles['mineral-red-sand'],
},
beige = make_default_autoplace_settings{
ab_tiles['mineral-beige-dirt'],
ab_tiles['mineral-beige-sand'],
},
brown = make_default_autoplace_settings{
ab_tiles['mineral-brown-dirt'],
ab_tiles['mineral-brown-sand'],
},
cream = make_default_autoplace_settings{
ab_tiles['mineral-cream-dirt'],
ab_tiles['mineral-cream-sand'],
},
dustyrose = make_default_autoplace_settings{
ab_tiles['mineral-dustyrose-dirt'],
ab_tiles['mineral-dustyrose-sand'],
},
tan = make_default_autoplace_settings{
ab_tiles['mineral-tan-dirt'],
ab_tiles['mineral-tan-sand'],
},
violet = make_default_autoplace_settings{
ab_tiles['mineral-violet-dirt'],
ab_tiles['mineral-violet-sand'],
},
-- Texture subsets
grass = make_default_autoplace_settings{
ab_tiles['vegetation-green-grass'],
ab_tiles['vegetation-olive-grass'],
ab_tiles['vegetation-turquioise-grass'],
},
snow = make_default_autoplace_settings{
ab_tiles['frozen-snow'],
},
light_sand = make_default_autoplace_settings{
ab_tiles['mineral-tan-sand'],
ab_tiles['mineral-beige-sand'],
ab_tiles['mineral-cream-sand'],
},
dark_sand = make_default_autoplace_settings{
ab_tiles['mineral-black-sand'],
ab_tiles['mineral-grey-sand'],
},
colorful_sand = make_default_autoplace_settings{
ab_tiles['mineral-brown-sand'],
ab_tiles['mineral-aubergine-sand'],
ab_tiles['mineral-purple-sand'],
ab_tiles['mineral-dustyrose-sand'],
ab_tiles['mineral-red-sand'],
ab_tiles['mineral-violet-sand'],
},
sand_only = make_default_autoplace_settings{
-- light sand
ab_tiles['mineral-tan-sand'],
ab_tiles['mineral-beige-sand'],
ab_tiles['mineral-cream-sand'],
-- dark sand
ab_tiles['mineral-black-sand'],
ab_tiles['mineral-grey-sand'],
-- purple/red sand
ab_tiles['mineral-brown-sand'],
ab_tiles['mineral-aubergine-sand'],
ab_tiles['mineral-purple-sand'],
ab_tiles['mineral-dustyrose-sand'],
ab_tiles['mineral-red-sand'],
ab_tiles['mineral-violet-sand'],
},
brown_light_dirt = make_default_autoplace_settings{
ab_tiles['mineral-beige-dirt'],
ab_tiles['mineral-cream-dirt'],
ab_tiles['mineral-tan-dirt'],
},
brown_dark_dirt = make_default_autoplace_settings{
ab_tiles['mineral-brown-dirt'],
ab_tiles['mineral-dustyrose-dirt'],
ab_tiles['mineral-tan-dirt'],
},
brown_dirt = make_default_autoplace_settings{
ab_tiles['mineral-beige-dirt'],
ab_tiles['mineral-cream-dirt'],
ab_tiles['mineral-tan-dirt'],
ab_tiles['mineral-brown-dirt'],
ab_tiles['mineral-dustyrose-dirt'],
},
light_grey_dirt = make_default_autoplace_settings{
ab_tiles['mineral-grey-dirt'],
ab_tiles['mineral-white-dirt'],
},
colorful_dirt = {
ab_tiles['mineral-brown-dirt'],
ab_tiles['mineral-aubergine-dirt'],
ab_tiles['mineral-purple-dirt'],
ab_tiles['mineral-dustyrose-dirt'],
ab_tiles['mineral-red-dirt'],
ab_tiles['mineral-violet-dirt'],
},
dirt_only = make_default_autoplace_settings{
-- light dirt
ab_tiles['mineral-tan-dirt'],
ab_tiles['mineral-beige-dirt'],
ab_tiles['mineral-cream-dirt'],
-- dark dirt
ab_tiles['mineral-black-dirt'],
ab_tiles['mineral-grey-dirt'],
-- purple/red dirt
ab_tiles['mineral-brown-dirt'],
ab_tiles['mineral-aubergine-dirt'],
ab_tiles['mineral-purple-dirt'],
ab_tiles['mineral-dustyrose-dirt'],
ab_tiles['mineral-red-dirt'],
ab_tiles['mineral-violet-dirt'],
},
heat_blue = make_default_autoplace_settings{
ab_tiles['volcaninc-blue-heat'],
},
heat_green = make_default_autoplace_settings{
ab_tiles['volcaninc-green-heat'],
},
heat_orange = make_default_autoplace_settings{
ab_tiles['volcaninc-orange-heat'],
},
heat_purple = make_default_autoplace_settings{
ab_tiles['volcaninc-purple-heat'],
},
-- Full biomes
cold = make_default_autoplace_settings{
ab_tiles['frozen-snow'],
ab_tiles['mineral-white-dirt'],
ab_tiles['mineral-white-sand'],
ab_tiles['volcaninc-blue-heat'],
},
hot = make_default_autoplace_settings{
ab_tiles['mineral-red-dirt'],
ab_tiles['mineral-red-sand'],
ab_tiles['vegetation-red-grass'],
ab_tiles['volcaninc-orange-heat'],
},
pale = make_default_autoplace_settings{
ab_tiles['mineral-beige-dirt'],
ab_tiles['mineral-beige-sand'],
ab_tiles['mineral-grey-dirt'],
ab_tiles['mineral-grey-sand'],
ab_tiles['mineral-white-dirt'],
ab_tiles['mineral-white-sand'],
},
temperate = make_default_autoplace_settings{
ab_tiles['mineral-tan-dirt'],
ab_tiles['mineral-tan-sand'],
ab_tiles['mineral-brown-dirt'],
ab_tiles['mineral-brown-sand'],
ab_tiles['mineral-cream-dirt'],
ab_tiles['mineral-cream-sand'],
ab_tiles['mineral-dustyrose-dirt'],
ab_tiles['mineral-dustyrose-sand'],
ab_tiles['vegetation-green-grass'],
ab_tiles['vegetation-olive-grass'],
ab_tiles['vegetation-orange-grass'],
ab_tiles['vegetation-turquoise-grass'],
ab_tiles['vegetation-yellow-grass'],
},
vegetation = make_default_autoplace_settings{
ab_tiles['mineral-cream-dirt'],
ab_tiles['mineral-cream-sand'],
ab_tiles['mineral-tan-dirt'],
ab_tiles['mineral-tan-sand'],
ab_tiles['vegetation-green-grass'],
ab_tiles['vegetation-olive-grass'],
ab_tiles['vegetation-orange-grass'],
ab_tiles['vegetation-turquoise-grass'],
ab_tiles['vegetation-yellow-grass'],
ab_tiles['volcanic-green-heat'],
},
volcano = make_default_autoplace_settings{
ab_tiles['mineral-black-dirt'],
ab_tiles['mineral-black-sand'],
ab_tiles['mineral-red-dirt'],
ab_tiles['mineral-red-sand'],
ab_tiles['vegetation-red-grass'],
ab_tiles['volcaninc-orange-heat'],
},
mystic_purple = make_default_autoplace_settings{
ab_tiles['mineral-aubergine-dirt'],
ab_tiles['mineral-aubergine-sand'],
ab_tiles['mineral-purple-dirt'],
ab_tiles['mineral-purple-sand'],
ab_tiles['mineral-violet-dirt'],
ab_tiles['mineral-violet-sand'],
ab_tiles['vegetation-blue-grass'],
ab_tiles['vegetation-mauve-grass'],
ab_tiles['vegetation-purple-grass'],
ab_tiles['vegetation-violet-grass'],
ab_tiles['volcanic-blue-heat'],
ab_tiles['volcanic-purple-heat'],
},
}

View File

@ -8,7 +8,8 @@ return {
moderation_log = 'moderation-log',
helpdesk = 'helpdesk',
danger_ores = 'danger-ores',
crash_site = 'crash-site'
crash_site = 'crash-site',
events = 'events',
},
--- The strings that mention the discord role.
-- Has to be used with features.server.to_discord_raw variants else the mention is sanitized server side.
@ -17,6 +18,7 @@ return {
crash_site = '<@&762441731194748958>',
danger_ore = '<@&793231011144007730>',
moderator = '<@&454192594633883658>',
diggy = '<@&921476458076061718>'
diggy = '<@&921476458076061718>',
map_update = '<@486532533220147203>',
}
}

146
resources/item_list.lua Normal file
View File

@ -0,0 +1,146 @@
return {
'accumulator',
'advanced-circuit',
'arithmetic-combinator',
'artillery-turret',
'assembling-machine-1',
'assembling-machine-2',
'assembling-machine-3',
'battery',
'battery-equipment',
'battery-mk2-equipment',
'beacon',
'belt-immunity-equipment',
'big-electric-pole',
'boiler',
'burner-inserter',
'burner-mining-drill',
'centrifuge',
'chemical-plant',
'coal',
'coin',
'concrete',
'constant-combinator',
'construction-robot',
'copper-cable',
'copper-ore',
'copper-plate',
'crude-oil-barrel',
'decider-combinator',
'discharge-defense-equipment',
'electric-engine-unit',
'electric-furnace',
'electric-mining-drill',
'electronic-circuit',
'empty-barrel',
'energy-shield-equipment',
'energy-shield-mk2-equipment',
'engine-unit',
'exoskeleton-equipment',
'explosives',
'express-loader',
'express-splitter',
'express-transport-belt',
'express-underground-belt',
'fast-inserter',
'fast-loader',
'fast-splitter',
'fast-transport-belt',
'fast-underground-belt',
'filter-inserter',
'flamethrower-turret',
'flying-robot-frame',
'fusion-reactor-equipment',
'gate',
'green-wire',
'gun-turret',
'hazard-concrete',
'heat-exchanger',
'heat-interface',
'heat-pipe',
'heavy-oil-barrel',
'inserter',
'iron-chest',
'iron-gear-wheel',
'iron-ore',
'iron-plate',
'iron-stick',
'lab',
'land-mine',
'landfill',
'laser-turret',
'light-oil-barrel',
'loader',
'logistic-chest-active-provider',
'logistic-chest-buffer',
'logistic-chest-passive-provider',
'logistic-chest-requester',
'logistic-chest-storage',
'logistic-robot',
'long-handed-inserter',
'low-density-structure',
'lubricant-barrel',
'medium-electric-pole',
'night-vision-equipment',
'nuclear-fuel',
'nuclear-reactor',
'offshore-pump',
'oil-refinery',
'personal-laser-defense-equipment',
'personal-roboport-equipment',
'personal-roboport-mk2-equipment',
'petroleum-gas-barrel',
'pipe',
'pipe-to-ground',
'plastic-bar',
'player-port',
'power-switch',
'processing-unit',
'programmable-speaker',
'pump',
'pumpjack',
'radar',
'rail-chain-signal',
'rail-signal',
'red-wire',
'refined-concrete',
'refined-hazard-concrete',
'roboport',
'rocket-control-unit',
'rocket-fuel',
'rocket-part',
'rocket-silo',
'satellite',
'small-electric-pole',
'small-lamp',
'solar-panel',
'solar-panel-equipment',
'solid-fuel',
'splitter',
'stack-filter-inserter',
'stack-inserter',
'steam-engine',
'steam-turbine',
'steel-chest',
'steel-furnace',
'steel-plate',
'stone',
'stone-brick',
'stone-furnace',
'stone-wall',
'storage-tank',
'substation',
'sulfur',
'sulfuric-acid-barrel',
'train-stop',
'transport-belt',
'underground-belt',
'uranium-235',
'uranium-238',
'uranium-fuel-cell',
'uranium-ore',
'used-up-uranium-fuel-cell',
'water-barrel',
'wood',
'wooden-chest',
}

View File

@ -0,0 +1 @@
return require 'map_gen.maps.april_fools.2019'

View File

@ -0,0 +1 @@
return require 'map_gen.maps.april_fools.2024'

View File

@ -10,7 +10,7 @@ Declare.module({'utils', 'Gui'}, function()
end
for _, name in pairs(Gui._top_elements) do
Declare.test(Gui.names[name] or name, function(context)
Declare.test(Gui.names and Gui.names[name] or name, function(context)
local player = context.player
local element = player.gui.top[name]