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

Update Frontier to V2 (#1420)

* Frontier V2

* Shortcuts default disabled
This commit is contained in:
RedRafe 2024-08-10 15:36:11 +02:00 committed by GitHub
parent 8ccf00fc6d
commit a1f1b81ca4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1781 additions and 187 deletions

View File

@ -385,6 +385,8 @@ stds.factorio_control = {
"reload_script",
"remove_offline_players",
"remove_path",
"reset_game_state",
"reset_time_played",
"save_atlas",
"server_save",
"set_game_state",

View File

@ -461,6 +461,14 @@ global.config = {
max_lifetime = 20 * 60, -- 20s
min_length = 40, -- messages shorter than this value will still be displayed for the min_lifetime
max_length = 92, -- messages longer than this value will be trimmed
},
player_shortcuts = {
enabled = false,
shortcuts = {
auto_stash = true,
clear_corpses = true,
battery_charge = true,
}
}
}

View File

@ -168,6 +168,9 @@ end
if config.radio.enabled or _DEBUG then
require 'features.gui.radio'
end
if config.player_shortcuts.enabled then
require 'features.gui.shortcuts'
end
if config.score.enabled then
require 'features.gui.score'
end

647
features/auto_stash.lua Normal file
View File

@ -0,0 +1,647 @@
local Color = require 'resources.color_presets'
local Global = require 'utils.global'
local Event = require 'utils.event'
local floor = math.floor
local this = {
floating_text_y_offsets = {},
whitelist = {},
insert_to_neutral_chests = false,
insert_into_furnace = false,
insert_into_wagon = false,
bottom_button = false,
small_radius = 2,
limit_containers = 50,
init_whitelist = false,
}
local Public = {}
Global.register(this, function(tbl) this = tbl end)
local bps_blacklist = { ['blueprint-book'] = true, ['blueprint'] = true }
local function create_floaty_text(surface, position, name, count)
if this.floating_text_y_offsets[position.x .. '_' .. position.y] then
this.floating_text_y_offsets[position.x .. '_' .. position.y] =
this.floating_text_y_offsets[position.x .. '_' .. position.y] - 0.5
else
this.floating_text_y_offsets[position.x .. '_' .. position.y] = 0
end
surface.create_entity({
name = 'flying-text',
position = { position.x, position.y + this.floating_text_y_offsets[position.x .. '_' .. position.y] },
text = { '', '-', count, ' ', game.item_prototypes[name].localised_name },
color = { r = 255, g = 255, b = 255 },
})
end
local function prepare_floaty_text(list, surface, position, name, count)
local str = surface.index .. ',' .. position.x .. ',' .. position.y
if not list[str] then
list[str] = {}
end
if not list[str][name] then
list[str][name] = { surface = surface, position = position, count = 0 }
end
list[str][name].count = list[str][name].count + count
end
local function chest_is_valid(chest)
for _, e in pairs(chest.surface.find_entities_filtered({
type = { 'inserter', 'loader' },
area = { { chest.position.x - 1, chest.position.y - 1 }, { chest.position.x + 1, chest.position.y + 1 } },
})) do
if e.name ~= 'long-handed-inserter' then
if e.position.x == chest.position.x then
if e.direction == 0 or e.direction == 4 then
return false
end
end
if e.position.y == chest.position.y then
if e.direction == 2 or e.direction == 6 then
return false
end
end
end
end
local i1 = chest.surface.find_entity('long-handed-inserter', { chest.position.x - 2, chest.position.y })
if i1 then
if i1.direction == 2 or i1.direction == 6 then
return false
end
end
local i2 = chest.surface.find_entity('long-handed-inserter', { chest.position.x + 2, chest.position.y })
if i2 then
if i2.direction == 2 or i2.direction == 6 then
return false
end
end
local i3 = chest.surface.find_entity('long-handed-inserter', { chest.position.x, chest.position.y - 2 })
if i3 then
if i3.direction == 0 or i3.direction == 4 then
return false
end
end
local i4 = chest.surface.find_entity('long-handed-inserter', { chest.position.x, chest.position.y + 2 })
if i4 then
if i4.direction == 0 or i4.direction == 4 then
return false
end
end
return true
end
local function sort_entities_by_distance(position, entities)
local t = {}
local distance
local index
local size_of_entities = #entities
if size_of_entities < 2 then
return entities
end
for _, entity in pairs(entities) do
distance = (entity.position.x - position.x) ^ 2 + (entity.position.y - position.y) ^ 2
index = floor(distance) + 1
if not t[index] then
t[index] = {}
end
table.insert(t[index], entity)
end
local i = 0
local containers = {}
for _, range in pairs(t) do
for _, entity in pairs(range) do
i = i + 1
if i >= (this.limit_containers or 50) then
return containers
end
containers[i] = entity
end
end
return containers
end
local function get_nearby_chests(player, a, furnace, wagon)
local r = player.force.character_reach_distance_bonus + 10
local r_square = r * r
local chests, inventories = {}, {}
local size_of_chests = 0
local area = { { player.position.x - r, player.position.y - r }, { player.position.x + r, player.position.y + r } }
area = a or area
local container_type = { 'container', 'logistic-container', 'linked-container' }
local inventory_type = defines.inventory.chest
local containers = {}
local i = 0
if furnace then
container_type = { 'furnace' }
inventory_type = defines.inventory.furnace_source
end
if wagon then
container_type = { 'cargo-wagon', 'logistic-container' }
inventory_type = defines.inventory.cargo_wagon
end
local forces = player.force
if this.insert_to_neutral_chests then
forces = { player.force, 'neutral' }
end
for _, e in pairs(player.surface.find_entities_filtered({ type = container_type, area = area, force = forces })) do
if ((player.position.x - e.position.x) ^ 2 + (player.position.y - e.position.y) ^ 2) <= r_square then
i = i + 1
containers[i] = e
end
end
containers = sort_entities_by_distance(player.position, containers)
for _, entity in pairs(containers) do
size_of_chests = size_of_chests + 1
chests[size_of_chests] = entity
inventories[size_of_chests] = entity.get_inventory(inventory_type)
end
return { chest = chests, inventory = inventories }
end
local function insert_to_furnace(player_inventory, chests, name, count, floaty_text_list)
local try = 0
local to_insert = floor(count / #chests.chest)
if to_insert <= 0 then
if count > 0 then
to_insert = count
else
return
end
end
local variate = count % #chests.chest
local chests_available = #chests.chest
local tries = #chests.chest
::retry::
-- Attempt to store into furnaces.
for chestnr, chest in pairs(chests.chest) do
local chest_inventory = chests.inventory[chestnr]
local amount = to_insert
if variate > 0 then
amount = amount + 1
variate = variate - 1
end
if amount <= 0 then
return
end
if chest_inventory then
if (chest.type == 'furnace' or chest.type == 'assembling-machine') then
if name == 'stone' then
local valid_to_insert = (amount % 2 == 0)
if valid_to_insert then
if chest_inventory.can_insert({ name = name, count = amount }) then
local inserted_count = chest_inventory.insert({ name = name, count = amount })
if inserted_count < 0 then
return
end
player_inventory.remove({ name = name, count = inserted_count })
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
count = count - inserted_count
if count <= 0 then
return
end
end
else
try = try + 1
if try <= tries then
chests_available = chests_available - 1
to_insert = floor(count / chests_available)
variate = count % chests_available
goto retry
end
end
else
if chest_inventory.can_insert({ name = name, count = amount }) then
local inserted_count = chest_inventory.insert({ name = name, count = amount })
if inserted_count < 0 then
return
end
player_inventory.remove({ name = name, count = inserted_count })
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
count = count - inserted_count
if count <= 0 then
return
end
end
end
end
end
end
to_insert = floor(count / #chests.chest)
variate = count % #chests.chest
for _, chest in pairs(chests.chest) do -- fuel
if chest.type == 'furnace' or chest.type == 'assembling-machine' then
local amount = to_insert
if variate > 0 then
amount = amount + 1
variate = variate - 1
end
if amount <= 0 then
return
end
local chest_inventory = chest.get_inventory(defines.inventory.chest)
if chest_inventory and chest_inventory.can_insert({ name = name, count = amount }) then
local inserted_count = chest_inventory.insert({ name = name, count = amount })
if inserted_count < 0 then
return
end
player_inventory.remove({ name = name, count = inserted_count })
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
count = count - inserted_count
if count <= 0 then
return
end
end
end
end
end
local function insert_into_wagon(stack, chests, name, floaty_text_list)
-- Attempt to load filtered cargo wagon
for chestnr, chest in pairs(chests.chest) do
if chest.type == 'cargo-wagon' then
local chest_inventory = chests.inventory[chestnr]
if chest_inventory.can_insert(stack) then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
end
end
end
local function insert_into_wagon_filtered(stack, chests, name, floaty_text_list)
-- Attempt to load filtered cargo wagon
for chestnr, chest in pairs(chests.chest) do
if chest.type == 'cargo-wagon' then
local chest_inventory = chests.inventory[chestnr]
for index = 1, 40 do
if chest_inventory.can_insert(stack) then
if chest_inventory.get_filter(index) ~= nil then
local n = chest_inventory.get_filter(index)
if n == name then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
end
end
end
end
end
-- Attempt to load filtered slots
for chestnr, chest in pairs(chests.chest) do
if chest.type == 'logistic-container' then
local chest_inventory = chests.inventory[chestnr]
for index = 1, chest.request_slot_count do
if chest_inventory.can_insert(stack) then
if chest.get_request_slot(index) ~= nil then
local n = chest.get_request_slot(index)
if n and n.name == name then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
end
end
end
end
end
end
local function insert_item_into_chest(stack, chests, filtered_chests, name, floaty_text_list, previous_insert)
local container = { ['container'] = true, ['logistic-container'] = true, ['linked-container'] = true }
-- Attemp to store in chest that stored last same item
if previous_insert.name == name and previous_insert.full ~= nil then
local chest_inventory = chests.inventory[previous_insert.full]
if chest_inventory and chest_inventory.can_insert(stack) then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chests.chest[previous_insert.full].surface,
chests.chest[previous_insert.full].position, name, inserted_count)
if stack.count <= 0 then
return previous_insert.full
end
end
end
--- Attempt to store in req slots that are filtered
for chestnr, chest in pairs(chests.chest) do
if chest.type == 'logistic-container' then
local chest_inventory = chests.inventory[chestnr]
for index = 1, chest.request_slot_count do
if chest_inventory.can_insert(stack) then
if chest.get_request_slot(index) ~= nil then
local n = chest.get_request_slot(index)
if n and n.name == name then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
end
end
end
end
end
-- Attempt to store in chests that already have the same item.
for chestnr, chest in pairs(chests.chest) do
if container[chest.type] then
if chest.request_slot_count and chest.request_slot_count > 0 then
goto continue
end
local chest_inventory = chests.inventory[chestnr]
if chest_inventory and chest_inventory.find_item_stack(stack.name) then
if chest_inventory.can_insert(stack) then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
end
::continue::
end
end
-- Attempt to store in empty chests.
for chestnr, chest in pairs(filtered_chests.chest) do
if container[chest.type] then
if chest.request_slot_count and chest.request_slot_count > 0 then
goto continue
end
local chest_inventory = filtered_chests.inventory[chestnr]
if not chest_inventory then
break
end
local count = chest_inventory.get_item_count() == 0
if count then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
::continue::
end
end
local item_prototypes = game.item_prototypes
-- Attempt to store in chests with same item subgroup.
local item_subgroup = game.item_prototypes[name].subgroup.name
if item_subgroup then
for chestnr, chest in pairs(filtered_chests.chest) do
if chest.request_slot_count and chest.request_slot_count > 0 then
goto continue
end
if container[chest.type] then
local chest_inventory = filtered_chests.inventory[chestnr]
if not chest_inventory then
break
end
local content = chest_inventory.get_contents()
if chest_inventory.can_insert(stack) then
for equal_name, _ in pairs(content) do
local t = item_prototypes[equal_name]
if t and t.subgroup.name == item_subgroup then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
end
end
end
::continue::
end
end
-- Attempt to store in mixed chests.
for chestnr, chest in pairs(filtered_chests.chest) do
if container[chest.type] then
if chest.request_slot_count and chest.request_slot_count > 0 then
goto continue
end
local chest_inventory = filtered_chests.inventory[chestnr]
if not chest_inventory then
break
end
if chest_inventory.can_insert(stack) then
local inserted_count = chest_inventory.insert(stack)
stack.count = stack.count - inserted_count
prepare_floaty_text(floaty_text_list, chest.surface, chest.position, name, inserted_count)
if stack.count <= 0 then
return chestnr
end
end
::continue::
end
end
end
local function whitelist()
local resources = game.entity_prototypes
local items = game.item_prototypes
this.whitelist = {}
for k, _ in pairs(resources) do
if resources[k] and resources[k].type == 'resource' and resources[k].mineable_properties then
if resources[k].mineable_properties.products and resources[k].mineable_properties.products[1] then
local r = resources[k].mineable_properties.products[1].name
this.whitelist[r] = true
elseif resources[k].mineable_properties.products and resources[k].mineable_properties.products[2] then
local r = resources[k].mineable_properties.products[2].name
this.whitelist[r] = true
end
end
end
for k, _ in pairs(items) do
if items[k] and items[k].group.name == 'resource-refining' then
local r = items[k].name
this.whitelist[r] = true
end
end
this.init_whitelist = true
end
function Public.auto_stash(player, event)
if not this.init_whitelist then
whitelist()
end
local button = event.button
local ctrl = event.control
local shift = event.shift
if not (player and player.valid) then
return
end
if not (player.character and player.character.valid) then
player.print({'auto_stash.err_no_character'}, Color.warning)
return
end
local inventory = player.get_main_inventory()
if inventory.is_empty() then
player.print({'auto_stash.err_no_inventory'}, Color.warning)
return
end
local floaty_text_list = {}
local chests = { chest = {}, inventory = {} }
local r = this.small_radius
local area = { { player.position.x - r, player.position.y - r }, { player.position.x + r, player.position.y + r } }
if ctrl then
if button == defines.mouse_button_type.right and this.insert_into_furnace then
chests = get_nearby_chests(player, nil, true, false)
end
elseif shift then
if button == defines.mouse_button_type.right and this.insert_into_wagon or button == defines.mouse_button_type.left and
this.insert_into_wagon then
chests = get_nearby_chests(player, area, false, true)
end
else
chests = get_nearby_chests(player)
end
if not chests.chest or not chests.chest[1] then
player.print({'auto_stash.err_no_container'}, Color.warning)
return
end
local filtered_chests = { chest = {}, inventory = {} }
for index, e in pairs(chests.chest) do
if chest_is_valid(e) then
filtered_chests.chest[index] = e
filtered_chests.inventory[index] = chests.inventory[index]
end
end
this.floating_text_y_offsets = {}
local hotbar_items = {}
for i = 1, 100, 1 do
local prototype = player.get_quick_bar_slot(i)
if prototype then
hotbar_items[prototype.name] = true
end
end
local furnaceList = { ['coal'] = 0, ['iron-ore'] = 0, ['copper-ore'] = 0, ['stone'] = 0 }
local full_insert = { full = nil, name = nil }
for i = #inventory, 1, -1 do
if not inventory[i].valid_for_read then
goto continue
end
local name = inventory[i].name
local is_resource = this.whitelist[name]
if not hotbar_items[name] and not bps_blacklist[name] then
if ctrl and this.insert_into_furnace then
if button == defines.mouse_button_type.right then
if is_resource then
furnaceList[name] = (furnaceList[name] or 0) + inventory[i].count
end
end
elseif shift and this.insert_into_wagon then -- insert into wagon
if button == defines.mouse_button_type.right then -- insert all ores into wagon
if is_resource then
full_insert = { full = insert_into_wagon(inventory[i], chests, name, floaty_text_list), name = name }
end
end
if button == defines.mouse_button_type.left then -- insert all filtered into wagon
full_insert = { full = insert_into_wagon_filtered(inventory[i], chests, name, floaty_text_list), name = name }
end
elseif button == defines.mouse_button_type.right then -- only ores to nearby chests
if is_resource then
full_insert = {
full = insert_item_into_chest(inventory[i], chests, filtered_chests, name, floaty_text_list, full_insert),
name = name,
}
end
elseif button == defines.mouse_button_type.left then -- all items to nearby chests
full_insert = {
full = insert_item_into_chest(inventory[i], chests, filtered_chests, name, floaty_text_list, full_insert),
name = name,
}
end
if not full_insert.success then
hotbar_items[#hotbar_items + 1] = name
end
end
::continue::
end
for furnaceName, furnaceCount in pairs(furnaceList) do
insert_to_furnace(inventory, chests, furnaceName, furnaceCount, floaty_text_list)
end
for _, texts in pairs(floaty_text_list) do
for name, text in pairs(texts) do
create_floaty_text(text.surface, text.position, name, text.count)
end
end
local c = this.floating_text_y_offsets
for k, _ in pairs(c) do
this.floating_text_y_offsets[k] = nil
end
end
function Public.insert_into_furnace(value)
this.insert_into_furnace = value or false
end
function Public.insert_into_wagon(value)
this.insert_into_wagon = value or false
end
function Public.limit_containers(value)
this.limit_containers = value or 50
end
function Public.insert_to_neutral_chests(value)
this.insert_to_neutral_chests = value or false
end
Event.on_configuration_changed(whitelist)
Event.on_init(whitelist)
return Public

View File

@ -0,0 +1,93 @@
local Color = require 'resources.color_presets'
local Global = require 'utils.global'
local this = {
radius = 13
}
Global.register(this, function(tbl) this = tbl end)
local Public = {}
local function discharge_accumulators(surface, position, force, power_needs)
local accumulators = surface.find_entities_filtered {
name = 'accumulator',
force = force,
position = position,
radius = this.radius,
}
local power_drained = 0
power_needs = power_needs * 1
for _, accu in pairs(accumulators) do
if accu.valid then
if accu.energy > 3000000 and power_needs > 0 then
if power_needs >= 2000000 then
power_drained = power_drained + 2000000
accu.energy = accu.energy - 2000000
power_needs = power_needs - 2000000
else
power_drained = power_drained + power_needs
accu.energy = accu.energy - power_needs
end
elseif power_needs <= 0 then
break
end
end
end
return power_drained / 1
end
function Public.recharge(player)
if not player.character then
player.print({'battery_charge.err_no_character'}, Color.warning)
return
end
local armor_inventory = player.get_inventory(defines.inventory.character_armor)
if not armor_inventory.valid then
player.print({'battery_charge.err_no_armor'}, Color.warning)
return
end
local armor = armor_inventory[1]
if not armor.valid_for_read then
player.print({'battery_charge.err_no_armor'}, Color.warning)
return
end
local grid = armor.grid
if not grid or not grid.valid then
player.print({'battery_charge.err_no_armor'}, Color.warning)
return
end
local entities = player.surface.find_entities_filtered {
name = 'accumulator',
force = player.force,
position = player.position,
radius = this.radius,
}
if not entities or not next(entities) then
player.print({'battery_charge.err_no_accumulators'}, Color.warning)
return
end
local equip = grid.equipment
for _, piece in pairs(equip) do
if piece.valid and piece.generator_power == 0 then
local energy_needs = piece.max_energy - piece.energy
if energy_needs > 0 then
local energy = discharge_accumulators(player.surface, player.position, player.force, energy_needs)
if energy > 0 then
if piece.energy + energy >= piece.max_energy then
piece.energy = piece.max_energy
else
piece.energy = piece.energy + energy
end
end
end
end
end
end
function Public.radius(value)
this.radius = value or 13
end
return Public

View File

@ -0,0 +1,50 @@
local Global = require 'utils.global'
local this = {
radius = 128
}
Global.register(this, function(tbl) this = tbl end)
local Public = {}
function Public.clear_corpses(player, args)
if not player or not player.valid then
return
end
local surface = player.surface
if not surface or not surface.valid then
return
end
local whole_surface = false
if args and args.surface then
local surface_arg = args.surface:lower()
whole_surface = player.admin and (surface_arg == 's' or surface_arg == 'surface')
end
local corpses
if whole_surface then
corpses = surface.find_entities_filtered({ type = 'corpse' })
else
local pos = player.position
local area = { { pos.x - this.radius, pos.y - this.radius }, { pos.x + this.radius, pos.y + this.radius } }
corpses = surface.find_entities_filtered({ type = 'corpse', area = area })
end
for i = 1, #corpses do
corpses[i].destroy()
end
if #corpses > 0 then
player.print({ 'clear_corpses.count', #corpses })
else
player.print({ 'clear_corpses.clear' })
end
end
function Public.radius(value)
this.radius = value or 128
end
return Public

111
features/enemy_turret.lua Normal file
View File

@ -0,0 +1,111 @@
local Event = require 'utils.event'
local Global = require 'utils.global'
local register_on_entity_destroyed = script.register_on_entity_destroyed
local Public = {}
local turrets_map = {}
local register_map = {}
local primitives = { index = nil }
Global.register(
{
turrets_map = turrets_map,
register_map = register_map,
primitives = primitives,
},
function(tbl)
turrets_map = tbl.turrets_map
register_map = tbl.register_map
primitives = tbl.primitives
end)
function Public.register(entity, refill_name)
if not (entity and entity.valid) then
return
end
if not refill_name then
return
end
local is_item = game.item_prototypes[refill_name] and true or false
local is_fluid = game.fluid_prototypes[refill_name] and true or false
if not (is_item or is_fluid )then
return
end
local destroy_id = register_on_entity_destroyed(entity)
local unit_id = entity.unit_number
local data = {
entity = entity,
refill = refill_name,
is_fluid = is_fluid,
destroy_id = destroy_id
}
if data.is_fluid then
data.capacity = data.entity.fluidbox.get_capacity(1)
else
data.item_stack = {
name = data.refill,
count = game.item_prototypes[data.refill].stack_size
}
end
register_map[destroy_id] = unit_id
turrets_map[unit_id] = data
end
function Public.remove(entity)
local unit_id = entity.unit_number
local destroy_id = turrets_map[unit_id].destroy_id
register_map[destroy_id] = nil
turrets_map[unit_id] = nil
end
function Public.reset()
for k, _ in pairs(turrets_map) do
turrets_map[k] = nil
end
for k, _ in pairs(register_map) do
register_map[k] = nil
end
end
local function on_entity_destroyed(event)
local destroy_id = event.registration_number
local unit_id = event.unit_number
register_map[destroy_id] = nil
turrets_map[unit_id] = nil
end
Event.add(defines.events.on_entity_destroyed, on_entity_destroyed)
local function on_tick()
if primitives.index ~= nil and turrets_map[primitives.index] == nil then
primitives.index = nil
return
end
local idx, data = next(turrets_map, primitives.index)
if not (data and data.entity and data.entity.valid) then
primitives.index = nil
return
end
if data.is_fluid then
local fb = data.entity.fluidbox[1]
fb.name = data.refill
fb.amount = data.capacity
else
data.entity.insert(data.item_stack)
end
primitives.index = idx
end
Event.add(defines.events.on_tick, on_tick)
return Public

283
features/gui/shortcuts.lua Normal file
View File

@ -0,0 +1,283 @@
local Gui = require 'utils.gui'
local Event = require 'utils.event'
local Global = require 'utils.global'
local Config = require 'config'.player_shortcuts
local AutoStash = require 'features.auto_stash'
local ClearCorpses = require 'features.clear_corpses'
local BatteryCharge = require 'features.battery_charge'
local player_preferences = {}
Global.register(player_preferences, function(tbl) player_preferences = tbl end)
local main_button_name = Gui.uid_name()
local main_frame_name = Gui.uid_name()
local settings_button_name = Gui.uid_name()
local checkbox_action_name = Gui.uid_name()
local shortcut_action_name = Gui.uid_name()
local shortcut_buttons = {
auto_stash = {
name = 'auto_stash',
caption = {'player_shortcuts.auto_stash_caption'},
sprite = 'item/wooden-chest',
tooltip = {'player_shortcuts.auto_stash_tooltip'},
action = AutoStash.auto_stash,
},
battery_charge = {
name = 'battery_charge',
caption = {'player_shortcuts.battery_charge_caption'},
sprite = 'item/battery-mk2-equipment',
tooltip = {'player_shortcuts.battery_charge_tooltip'},
action = BatteryCharge.recharge,
},
clear_corpses = {
name = 'clear_corpses',
caption = {'player_shortcuts.clear_corpses_caption'},
sprite = 'entity/big-biter',
tooltip = {'player_shortcuts.clear_corpses_tooltip'},
action = ClearCorpses.clear_corpses,
},
}
local Public = {}
Public.main_button_name = main_button_name
Public.main_frame_name = main_frame_name
local function enabled_shortcuts()
local shortcuts = {}
for k, v in pairs(shortcut_buttons) do
if Config.shortcuts[k] then
shortcuts[k] = v
end
end
return shortcuts
end
local function get_player_preferences(player)
local player_data = player_preferences[player.name]
if not player_data then
player_data = {}
player_preferences[player.name] = player_data
end
return player_data
end
local function add_shortcut_selection_row(player, parent, child)
local player_data = get_player_preferences(player)
if player_data[child.name] == nil then
player_data[child.name] = true
end
local row = parent.add { type = 'frame', style = 'shortcut_selection_row' }
Gui.set_style(row, { horizontally_stretchable = true, vertically_stretchable = false })
local icon = row.add {
type = 'sprite-button',
style = 'transparent_slot',
sprite = child.sprite,
tooltip = child.tooltip,
}
Gui.set_style(icon, { width = 20, height = 20 })
local checkbox = row.add {
type = 'checkbox',
caption = child.caption,
state = player_data[child.name],
tags = { action = checkbox_action_name, name = child.name },
}
Gui.set_style(checkbox, { minimal_width = 160, horizontally_stretchable = true })
end
function Public.on_player_created(player)
if not Config.enabled then
return
end
local b = Gui.add_top_element(player, {
type = 'sprite-button',
name = main_button_name,
sprite = 'utility/hand_black',
tooltip = {'player_shortcuts.info_tooltip'},
})
b.style.padding = 2
end
function Public.toggle_main_button(player)
local main_frame = player.gui.screen[main_frame_name]
if main_frame then
main_frame.destroy()
else
Public.get_main_frame(player)
end
end
function Public.toggle_shortcuts_settings(player)
local frame = Public.get_main_frame(player)
frame.children[1].qbip.qbsp.visible = not frame.children[1].qbip.qbsp.visible
end
function Public.get_main_frame(player)
local main_frame = player.gui.screen[main_frame_name]
if main_frame and main_frame.valid then
return main_frame
end
main_frame = player.gui.screen.add {
type = 'frame',
name = main_frame_name,
direction = 'horizontal',
style = 'quick_bar_window_frame',
}
main_frame.auto_center = true
do -- shortcuts
local left_flow = main_frame.add { type = 'flow', direction = 'vertical' }
Gui.set_style(left_flow, { horizontally_stretchable = true })
local settings_scroll_pane = left_flow
.add {
type = 'frame',
name = 'qbip',
style = 'quick_bar_inner_panel'
}.
add {
type = 'scroll-pane',
name = 'qbsp',
style = 'shortcut_bar_selection_scroll_pane',
}
Gui.set_style(settings_scroll_pane, { horizontally_squashable = false, minimal_width = 40 * table_size(enabled_shortcuts()) })
for _, s in pairs(enabled_shortcuts()) do
add_shortcut_selection_row(player, settings_scroll_pane, s)
end
settings_scroll_pane.visible = false
local table_frame = left_flow.add {
type = 'frame',
name = 'table_frame',
direction = 'horizontal',
style = 'quick_bar_inner_panel',
}
Gui.set_style(table_frame, { horizontally_stretchable = true, margin = 0 })
local table = table_frame.add {
type = 'table',
name = 'table',
column_count = table_size(enabled_shortcuts()),
style = 'filter_slot_table',
}
Gui.set_style(table, { horizontally_stretchable = true })
local button
local player_data = get_player_preferences(player)
for button_name, s in pairs(enabled_shortcuts()) do
button = table.add {
type = 'sprite-button',
style = 'quick_bar_slot_button',
sprite = s.sprite,
hovered_sprite = s.hovered_sprite,
tooltip = s.tooltip,
tags = { action = shortcut_action_name, name = button_name },
}
Gui.set_style(button, { font_color = { 165, 165, 165 } })
if player_data[button_name] == nil then
player_data[button_name] = true
end
button.visible = player_data[button_name]
end
end
do -- settings
local right_flow = main_frame.add {
type = 'flow',
direction = 'vertical',
}
Gui.set_style(right_flow, { horizontal_align = 'center', padding = 0 })
right_flow.drag_target = main_frame
right_flow.add {
type = 'sprite-button',
name = settings_button_name,
style = 'shortcut_bar_expand_button',
sprite = 'utility/expand_dots_white',
hovered_sprite = 'utility/expand_dots',
clicked_sprite = 'utility/expand_dots',
tooltip = {'player_shortcuts.settings_tooltip'},
mouse_button_filter = { 'left' },
auto_toggle = true,
}
local widget = right_flow.add { type = 'empty-widget', style = 'draggable_space', ignored_by_interaction = true }
Gui.set_style(widget, { vertically_stretchable = true, width = 8, margin = 0 })
end
return main_frame
end
Gui.allow_player_to_toggle_top_element_visibility(main_button_name)
Gui.on_click(main_button_name, function(event)
Public.toggle_main_button(event.player)
end)
Gui.on_click(settings_button_name, function(event)
Public.toggle_shortcuts_settings(event.player)
end)
Event.add(defines.events.on_gui_checked_state_changed, function(event)
local element = event.element
if not (element and element.valid) then
return
end
local player = game.get_player(event.player_index)
if not (player and player.valid) then
return
end
local action_name = element.tags and element.tags.action
if action_name and action_name == checkbox_action_name then
local name = element.tags.name
local frame = Public.get_main_frame(player)
for _, button in pairs(frame.children[1].table_frame.table.children) do
if button.tags.name == name then
local player_data = get_player_preferences(player)
player_data[name] = element.state
button.visible = element.state
end
end
end
end)
Event.add(defines.events.on_gui_click, function(event)
local element = event.element
if not (element and element.valid) then
return
end
local player = game.get_player(event.player_index)
if not (player and player.valid) then
return
end
local action_name = element.tags and element.tags.action
if action_name and action_name == shortcut_action_name then
local name = element.tags.name
local shortcut = shortcut_buttons[name]
if shortcut and shortcut.action then
shortcut.action(player, event)
end
end
end)
Event.add(defines.events.on_player_created, function(event)
local player = game.get_player(event.player_index)
if not (player and player.valid) then
return
end
Public.on_player_created(player)
end)
return Public

View File

@ -16,6 +16,7 @@ local tonumber = tonumber
local pairs = pairs
local floor = math.floor
local Ranks = require 'resources.ranks'
local ClearCorpses = require 'features.clear_corpses'
--- Informs the actor that there is no target. Acts as a central place where this message can be changed.
local function print_no_target(target_name)
@ -215,32 +216,7 @@ local function print_player_info(args, player)
end
local function clear_corpses(args, player)
if not player or not player.valid then
return
end
local surface = player.surface
if not surface or not surface.valid then
return
end
local surface_arg = args.surface:lower()
local whole_surface = player.admin and (surface_arg == 's' or surface_arg == 'surface')
local corpses
if whole_surface then
corpses = surface.find_entities_filtered({ type = 'corpse' })
else
local pos = player.position
local area = { { pos.x - 128, pos.y - 128 }, { pos.x + 128, pos.y + 128 } }
corpses = surface.find_entities_filtered({ type = 'corpse', area = area })
end
for i = 1, #corpses do
corpses[i].destroy()
end
player.print({ 'redmew_commands.clear_corpses_count', #corpses })
ClearCorpses.clear_corpses(player, args)
end
-- Command registrations

View File

@ -42,7 +42,6 @@ find_player_fail_no_character=Sorry, __1__ doesn't have a character to find.
show_rail_block_success=show_rail_block_visualisation set to __1__
server_time_fail=Server time is not available, is this game running on a Redmew server?
print_version_from_source=This map was created from source code, only releases (zips with names) and server saves have versions
clear_corpses_count=__1__ corpses were removed.
[donator_commands]
add_message_fail_not_string=Must enter a value to set as message.
@ -203,4 +202,18 @@ missing_research=[technology=landfill] required before landfill can be removed w
[spidertron_group_control]
none_found=No [img=item.spidertron] found
none_selected=No [img=item.spidertron] selected. Drag the planner over [img=item.spidertron] you own.
spidertrons_selected=+__1__ [img=item.spidertron]
spidertrons_selected=+__1__ [img=item.spidertron]
[auto_stash]
err_no_character=[color=blue][Auto stash][/color] It seems that you are not in the realm of living.
err_no_inventory=[color=blue][Auto stash][/color] Inventory is empty.
err_no_container=[color=blue][Auto stash][/color] No valid nearby containers found.
[battery_charge]
err_no_character=[color=blue][Battery recharge][/color] It seems that you are not in the realm of living.
err_no_armor=[color=blue][Battery recharge][/color] No valid armor to charge was found.
err_no_accumulators=[color=blue][Battery recharge][/color] No accumulators nearby.
[clear_corpses]
count=[color=blue][Cleaner][/color] __1__ __plural_for_parameter_1_{1=__1__ corpse|rest=__1__ corpses}__ removed.
clear=[color=blue][Cleaner][/color] already clear.

View File

@ -123,3 +123,13 @@ notify_tooltip=Receive a message when tasks or announcements are created or edit
[popup]
default_title=NOTICE!
confirm_btn=OK
[player_shortcuts]
info_tooltip=[font=default-bold]Player shortcuts[/font] - Toggle your shortcuts bar
settings_tooltip=[font=default-bold]Settings[/font] - Customize your shortcut bar
clear_corpses_caption=Clear corpses
clear_corpses_tooltip=[font=default-bold]Clear corpses[/font] - Clear biter corpses around you
battery_charge_caption=Battery recharge
battery_charge_tooltip=[font=default-bold]Battery recharge[/font] - Recharge battery equipments in your armor from nearby accumulators.\nThey must have at least 3MJ of energy stored.
auto_stash_caption=Auto stash
auto_stash_tooltip=[font=default-bold]Auto stash[/font] - Sort your inventory into nearby chests.\n[color=yellow][font=default-bold]LMB[/font][/color]: Everything, excluding quickbar items.\n[color=yellow][font=default-bold]RMB[/font][/color]: Only ores to nearby chests, excluding quickbar items.\n[color=yellow][font=default-bold]CTRL + RMB[/font][/color]: Fill nearby furnaces.\n[color=yellow][font=default-bold]SHIFT + LMB[/font][/color]: Everything onto filtered slots to wagon/chests.\n[color=yellow][font=default-bold]SHIFT + RMB[/font][/color]: Only ores to wagon.

View File

@ -192,4 +192,6 @@ kraken_eat=[color=purple][Kraken][/color] ate __1__ and was delicious!
rocket_launched=[color=blue][Mapkeeper][/color] __1__ __plural_for_parameter_1_{1=rocket|rest=rockets}__ launched, __2__ __plural_for_parameter_2_{1=rocket|rest=rockets}__ to go!
add_rocket=Adding __1__ extra __plural_for_parameter_1_{1=launch|rest=launches}__ thanks to the death of __2__. __3__ __plural_for_parameter_3_{1=rocket|rest=rockets}__ to go!
loot_chest=[achievement=golem] You find an hidden [color=orange]treasure[/color] beneath the enemy forces
empty_rocket=[color=purple][Kraken][/color] The God of the Sea accepts your rocket offer and rewards you with magic fishes
empty_rocket=[color=purple][Kraken][/color] The God of the Sea accepts your rocket offer and rewards you with magic fishes
rockets_to_launch=Remaining rockets to launch
restart=[color=blue][Mapkeeper][/color] Map is restarting in __1__ __plural_for_parameter_1_{1=second|rest=seconds}__

View File

@ -1,6 +1,8 @@
local b = require 'map_gen.shared.builders'
local Color = require 'resources.color_presets'
local Command = require 'utils.command'
local Event = require 'utils.event'
local EnemyTurret = require 'features.enemy_turret'
local Global = require 'utils.global'
local math = require 'utils.math'
local MGSP = require 'resources.map_gen_settings'
@ -8,6 +10,7 @@ local Noise = require 'map_gen.shared.simplex_noise'
local PriceRaffle = require 'features.price_raffle'
local RS = require 'map_gen.shared.redmew_surface'
local ScenarioInfo = require 'features.gui.info'
local ScoreTracker = require 'utils.score_tracker'
local Sounds = require 'utils.sounds'
local Toast = require 'features.gui.toast'
local Token = require 'utils.token'
@ -20,7 +23,10 @@ local math_abs = math.abs
local math_ceil = math.ceil
local math_floor = math.floor
local math_clamp = math.clamp
local math_sqrt = math.sqrt
local simplex = Noise.d2
local SECOND = 60
local MINUTE = SECOND * 60
--[[
Scenario info: Frontier
@ -52,6 +58,12 @@ Your mission, should you choose to accept it, is to journey through this ribbon
In [font=default-bold]Frontier[/font], your wits will be tested as you evolve from a mere survivor to an engineering genius capable of taming the land and launching your final escape. Build a thriving factory, and prepare to conquer both nature and the relentless horde in a race against time. But remember, the frontier waits for no one. Will you make your mark on this alien world or become another lost tale in the void of space?
]])
ScenarioInfo.set_new_info([[
2024-08-10:
- Added enemy turrets
- Added soft reset
- Added shortcuts gui
- Added score tracker for rockets to win
- Deaths no longer contribute to rocket to win, instead, a rng value is rolled at the beginning of the game
2024-07-31:
- Added Frontier
]])
@ -61,6 +73,7 @@ local Config = global.config
Config.redmew_surface.enabled = true
Config.market.enabled = false
Config.player_rewards.enabled = false
Config.player_shortcuts.enabled = true
Config.player_create.starting_items = {
{ name = 'burner-mining-drill', count = 1 },
{ name = 'stone-furnace', count = 1 },
@ -69,14 +82,16 @@ Config.player_create.starting_items = {
{ name = 'wood', count = 1 },
}
local _g = {
local this = {
rounds = 0,
-- Map gen
silo_starting_x = 1700,
height = 36, -- in chunks, height of the ribbon world
left_boundary = 8, -- in chunks, distance to water body
right_boundary = 11, -- in chunks, distance to wall/biter presence
wall_width = 5,
wall_width = 5, -- in tiles
rock_richness = 1, -- how many rocks/chunk
ore_base_quantity = 61, -- base ore quantity, everything is scaled up from this
ore_chunk_scale = 32, -- sets how fast the ore will increase from spawn, lower = faster
@ -89,7 +104,7 @@ local _g = {
-- Satellites to win
rockets_to_win = 1,
rockets_launched = 0,
rockets_per_death = 1, -- how many extra launch needed for each death
rockets_per_death = 0, -- how many extra launch needed for each death
scenario_finished = false,
-- Loot chests
@ -110,12 +125,7 @@ local _g = {
invincible = {}
}
if _DEBUG then
_g.silo_starting_x = 30
_g.rockets_to_win = 3
end
Global.register(_g, function(tbl) _g = tbl end)
Global.register(this, function(tbl) this = tbl end)
local noise_weights = {
{ modifier = 0.0042, weight = 1.000 },
@ -124,6 +134,96 @@ local noise_weights = {
}
local mixed_ores = { 'iron-ore', 'copper-ore', 'iron-ore', 'stone', 'copper-ore', 'iron-ore', 'copper-ore', 'iron-ore', 'coal', 'iron-ore', 'copper-ore', 'iron-ore', 'stone', 'copper-ore', 'coal'}
local Main = {
events = {
on_game_started = Event.generate_event_name('on_game_started'),
on_game_finished = Event.generate_event_name('on_game_finished'),
}
}
local rocket_launches_name = 'rockets-launches-frontier'
local global_to_show = Config.score.global_to_show
global_to_show[#global_to_show + 1] = rocket_launches_name
ScoreTracker.register(rocket_launches_name, {'frontier.rockets_to_launch'}, '[img=item.rocket-silo]')
local escape_player = false
-- == LOBBY ===================================================================
local Lobby = {}
Lobby.enabled = false
Lobby.name = 'nauvis'
Lobby.mgs = {
water = 0,
default_enable_all_autoplace_controls = false,
width = 64,
height = 64,
peaceful_mode = true,
}
function Lobby.get_surface()
local surface = game.get_surface(Lobby.name)
if not surface then
surface = game.create_surface(Lobby.name, Lobby.mgs)
end
return surface
end
function Lobby.teleport_to(player)
for k = 1, player.get_max_inventory_index() do
local inv = player.get_inventory(k)
if inv and inv.valid then
inv.clear()
end
end
local surface = Lobby.get_surface()
local position = surface.find_non_colliding_position('character', {0, 0}, 0, 0.2)
player.teleport(position, surface, true)
end
function Lobby.teleport_from(player, destination)
for _, stack in pairs(Config.player_create.starting_items) do
if game.item_prototypes[stack.name] then
player.insert(stack)
end
end
local surface = RS.get_surface()
local position = surface.find_non_colliding_position('character', destination or {0, 0}, 0, 0.2)
player.teleport(position, surface, true)
end
function Lobby.teleport_all_to()
for _, player in pairs(game.players) do
Lobby.teleport_to(player)
end
end
function Lobby.teleport_all_from(destination)
for _, player in pairs(game.players) do
Lobby.teleport_from(player, destination)
end
end
function Lobby.on_chunk_generated(event)
local area = event.area
local surface = event.surface
surface.build_checkerboard(area)
for _, e in pairs(surface.find_entities_filtered{ area = area }) do
if e.type ~= 'character' then
e.destroy()
end
end
end
function Lobby.on_init()
local surface = Lobby.get_surface()
surface.map_gen_settings = Lobby.mgs
Lobby.on_chunk_generated({ area = {left_top = {-64, -64}, right_bottom = {64, 64}}, surface = surface })
end
-- == MAP GEN =================================================================
local map, water, green_water
@ -131,17 +231,17 @@ local map, water, green_water
RS.set_map_gen_settings({
{
autoplace_controls = {
coal = { frequency = 3, richness = 1, size = 0.75 },
['copper-ore'] = { frequency = 3, richness = 1, size = 0.75 },
['crude-oil'] = { frequency = 1, richness = 1, size = 0.75 },
['enemy-base'] = { frequency = 6, richness = 1, size = 4 },
['iron-ore'] = { frequency = 3, richness = 1, size = 0.75 },
stone = { frequency = 3, richness = 1, size = 0.75 },
trees = { frequency = 1, richness = 1, size = 1 },
['uranium-ore'] = { frequency = 0.5, richness = 1, size = 0.5 },
['coal'] = { frequency = 3, richness = 1, size = 0.75 },
['copper-ore'] = { frequency = 3, richness = 1, size = 0.75 },
['crude-oil'] = { frequency = 1, richness = 1, size = 0.75 },
['enemy-base'] = { frequency = 6, richness = 1, size = 4 },
['iron-ore'] = { frequency = 3, richness = 1, size = 0.75 },
['stone'] = { frequency = 3, richness = 1, size = 0.75 },
['trees'] = { frequency = 1, richness = 1, size = 1 },
['uranium-ore'] = { frequency = 0.5, richness = 1, size = 0.5 },
},
cliff_settings = { name = 'cliff', cliff_elevation_0 = 20, cliff_elevation_interval = 40, richness = 1 / 3 },
height = _g.height * 32,
height = this.height * 32,
property_expression_names = {
['control-setting:aux:frequency:multiplier'] = '1.333333',
['control-setting:moisture:bias'] = '-0.250000',
@ -154,7 +254,7 @@ RS.set_map_gen_settings({
})
local bounds = function(x, y)
return x > (-_g.left_boundary * 32 - 320) and not ((y < -_g.height * 16) or (y > _g.height * 16))
return x > (-this.left_boundary * 32 - 320) and not ((y < -this.height * 16) or (y > this.height * 16))
end
water = b.change_tile(bounds, true, 'water')
@ -162,12 +262,14 @@ water = b.fish(water, 0.075)
green_water = b.change_tile(bounds, true, 'deepwater-green')
map = b.choose(function(x) return x < -_g.left_boundary * 32 end, water, bounds)
map = b.choose(function(x) return math_floor(x) == -(_g.kraken_distance + _g.left_boundary * 32 + 1) end, green_water, map)
map = b.choose(function(x) return x < -this.left_boundary * 32 end, water, bounds)
map = b.choose(function(x) return math_floor(x) == -(this.kraken_distance + this.left_boundary * 32 + 1) end, green_water, map)
-- == EVENTS ==================================================================
-- == TERRAIN ==================================================================
local function noise_pattern(position, seed)
local Terrain = {}
function Terrain.noise_pattern(position, seed)
local noise, d = 0, 0
for i = 1, #noise_weights do
local nw = noise_weights[i]
@ -179,8 +281,8 @@ local function noise_pattern(position, seed)
return noise
end
local function mixed_resources(surface, area)
local left_top = { x = math_max(area.left_top.x, _g.right_boundary * 32), y = area.left_top.y }
function Terrain.mixed_resources(surface, area)
local left_top = { x = math_max(area.left_top.x, this.right_boundary * 32), y = area.left_top.y }
local right_bottom = area.right_bottom
if left_top.x >= right_bottom.x then
return
@ -195,19 +297,22 @@ local function mixed_resources(surface, area)
for _, resource in pairs(find_entities_filtered{
position = position,
type = 'resource'
}) do resource.destroy() end
}) do
if resource.name ~= 'uranium-ore' and resource.name ~= 'crude-oil' then
resource.destroy() end
end
end
local chunks = math_clamp(math_abs((left_top.x - _g.right_boundary * 32) / _g.ore_chunk_scale), 1, 100)
local chunks = math_clamp(math_abs((left_top.x - this.right_boundary * 32) / this.ore_chunk_scale), 1, 100)
chunks = math_random(chunks, chunks + 4)
for x = 0, 31 do
for y = 0, 31 do
local position = { x = left_top.x + x, y = left_top.y + y }
if can_place_entity({ name = 'iron-ore', position = position }) then
local noise = noise_pattern(position, seed)
local noise = Terrain.noise_pattern(position, seed)
if math_abs(noise) > 0.67 then
local idx = math_floor(noise * 25 + math_abs(position.x) * 0.05) % #mixed_ores + 1
local amount = _g.ore_base_quantity * chunks * 3
local amount = this.ore_base_quantity * chunks * 3
clear_ore(position)
create_entity({ name = mixed_ores[idx], position = position, amount = amount })
end
@ -216,29 +321,61 @@ local function mixed_resources(surface, area)
end
end
local function clear_enemies_inside_wall(surface, area)
if area.right_bottom.x < (_g.right_boundary * 32 + 96) then
function Terrain.clear_enemies_inside_wall(surface, area)
if area.right_bottom.x < (this.right_boundary * 32 + 96) then
for _, entity in pairs(surface.find_entities_filtered { area = area, force = 'enemy' }) do
entity.destroy()
end
end
end
local function scale_resource_richness(surface, area)
function Terrain.scale_resource_richness(surface, area)
for _, resource in pairs(surface.find_entities_filtered { area = area, type = 'resource' }) do
if resource.position.x > _g.right_boundary * 32 then
local chunks = math.clamp(math_abs((resource.position.x - _g.right_boundary * 32) / _g.ore_chunk_scale), 1, 100)
if resource.position.x > this.right_boundary * 32 then
local chunks = math.clamp(math_abs((resource.position.x - this.right_boundary * 32) / this.ore_chunk_scale), 1, 100)
chunks = math_random(chunks, chunks + 4)
if resource.prototype.resource_category == 'basic-fluid' then
resource.amount = 3000 * 3 * chunks
elseif resource.prototype.resource_category == 'basic-solid' then
resource.amount = _g.ore_base_quantity * chunks
resource.amount = this.ore_base_quantity * chunks
end
end
end
end
local function set_silo_tiles(entity)
function Terrain.rich_rocks(surface, area)
local left_top = { x = math_max(area.left_top.x, this.right_boundary * 32), y = area.left_top.y }
local right_bottom = area.right_bottom
if left_top.x >= right_bottom.x then
return
end
local function place_rock(rock_name)
local search = surface.find_non_colliding_position
local place = surface.create_entity
for _ = 1, 10 do
local x, y = math_random(1, 31) + math_random(), math_random(1, 31) + math_random()
local rock_pos = search(rock_name, {left_top.x + x, left_top.y + y}, 4, 0.4)
if rock_pos then
local rock = place{
name = rock_name,
position = rock_pos,
direction = math_random(1, 4)
}
rock.graphics_variation = math_random(16)
return
end
end
end
for _ = 1, this.rock_richness do
local rock_name = math_random() < 0.4 and 'rock-huge' or 'rock-big'
place_rock(rock_name)
end
end
function Terrain.set_silo_tiles(entity)
local pos = entity.position
local surface = entity.surface
surface.request_to_generate_chunks(pos, 1)
@ -258,7 +395,29 @@ local function set_silo_tiles(entity)
entity.surface.set_tiles(tiles, true)
end
local function nuclear_explosion(entity)
function Terrain.create_wall(x, w)
local surface = RS.get_surface()
local area = { { x, -this.height * 16 }, { x + w, this.height * 16 } }
for _, entity in pairs(surface.find_entities_filtered { area = area, collision_mask = 'player-layer' }) do
entity.destroy()
end
for y = -this.height * 16, this.height * 16 do
for j = 0, w do
local e = surface.create_entity {
name = 'stone-wall',
position = { x + j, y },
force = 'player',
move_stuck_players = true,
}
e.destructible = false
end
end
end
-- == MAIN ====================================================================
function Main.nuclear_explosion(entity)
local surface = entity.surface
local center_position = entity.position
local force = entity.force
@ -273,81 +432,126 @@ local function nuclear_explosion(entity)
}
end
local function spawn_enemy_wave(position)
function Main.spawn_enemy_wave(position)
local surface = RS.get_surface()
local find_position = surface.find_non_colliding_position
local spawn = surface.create_entity
local current_tick = game.tick
local max_time = math_max(MINUTE, MINUTE * math_ceil(0.5 * (this.rockets_launched ^ 0.5)))
local radius = 20
for _ = 1, 24 do
local name = math_random() > 0.15 and 'behemoth-worm-turret' or 'big-worm-turret'
local about = find_position(name, { x = position.x + math_random(), y = position.y + math_random() }, radius, 0.2)
if about then
local worm = spawn { name = name, position = about, force = 'enemy', move_stuck_players = true }
_g.invincible[worm.unit_number] = {
time_to_live = current_tick + math_random(60 * 2, 60 * (4 + _g.rockets_launched))
this.invincible[worm.unit_number] = {
time_to_live = current_tick + math_random(MINUTE, max_time)
}
end
end
radius = 32
for _ = 1, 64 do
for _ = 1, 20 do
local name = math_random() > 0.3 and 'behemoth-biter' or 'behemoth-spitter'
local about = find_position(name, { x = position.x + math_random(), y = position.y + math_random() }, radius, 0.2)
local about = find_position(name, { x = position.x + math_random(), y = position.y + math_random() }, radius, 0.6)
if about then
local unit = spawn { name = name, position = about, force = 'enemy', move_stuck_players = true }
_g.invincible[unit.unit_number] = {
time_to_live = current_tick + math_random(60 * 2, 60 * (4 + _g.rockets_launched))
this.invincible[unit.unit_number] = {
time_to_live = current_tick + math_random(MINUTE, max_time)
}
end
end
end
local spawn_enemy_wave_token = Token.register(spawn_enemy_wave)
Main.spawn_enemy_wave_token = Token.register(Main.spawn_enemy_wave)
local function init_wall(x, w)
local surface = RS.get_surface()
local area = { { x, -_g.height * 16 }, { x + w, _g.height * 16 } }
for _, entity in pairs(surface.find_entities_filtered { area = area, collision_mask = 'player-layer' }) do
entity.destroy()
end
for y = -_g.height * 16, _g.height * 16 do
for j = 0, w do
local e = surface.create_entity {
name = 'stone-wall',
position = { x + j, y },
force = 'player',
move_stuck_players = true,
}
e.destructible = false
end
end
end
local function win()
_g.scenario_finished = true
game.set_game_state { game_finished = true, player_won = true, can_continue = true, victorious_force = 'player' }
end
local function on_spawner_died(event)
local entity = event.entity
local chance = math_random()
if chance > _g.loot_chance then
function Main.spawn_turret_outpost(position)
if position.x < this.right_boundary + this.wall_width then
return
end
local budget = _g.loot_budget + entity.position.x * 2.75
local max_chance = math_clamp(0.02 * math_sqrt(position.x), 0.01, 0.04)
if math_random() > max_chance then
return
end
local surface = RS.get_surface()
if escape_player then
for _, player in pairs(surface.find_entities_filtered{type = 'character'}) do
local pos = surface.find_non_colliding_position('character', { position.x -10, position.y }, 5, 0.5)
if pos then
player.teleport(pos, surface)
end
end
end
local evolution = game.forces.enemy.evolution_factor
local ammo = 'firearm-magazine'
if math_random() < evolution then
ammo = 'piercing-rounds-magazine'
end
if math_random() < evolution then
ammo = 'uranium-rounds-magazine'
end
for _, v in pairs({
{ x = -5, y = 0 },
{ x = 5, y = 0 },
{ x = 0, y = 5 },
{ x = 0, y = -5 },
}) do
local pos = surface.find_non_colliding_position('gun-turret', { position.x + v.x, position.y + v.y }, 2, 0.5)
if pos then
local turret = surface.create_entity {
name = 'gun-turret',
position = pos,
force = 'enemy',
move_stuck_players = true,
create_build_effect_smoke = true,
}
if turret and turret.valid then
EnemyTurret.register(turret, ammo)
end
end
end
end
function Main.win()
this.scenario_finished = true
game.set_game_state { game_finished = true, player_won = true, can_continue = true, victorious_force = 'player' }
Task.set_timeout( 1, Main.restart_message_token, 90)
Task.set_timeout(31, Main.restart_message_token, 60)
Task.set_timeout(61, Main.restart_message_token, 30)
Task.set_timeout(81, Main.restart_message_token, 10)
Task.set_timeout(86, Main.restart_message_token, 5)
Task.set_timeout(91, Main.end_game_token)
Task.set_timeout(92, Main.restart_game_token)
end
function Main.on_spawner_died(event)
local entity = event.entity
local chance = math_random()
if chance > this.loot_chance then
return
end
local budget = this.loot_budget + entity.position.x * 2.75
budget = budget * math_random(25, 175) * 0.01
local player = event.cause and event.cause.player
local player = false
if event.cause and event.cause.type == 'character' then
player = event.cause.player
end
if player and player.valid then
budget = budget + (_g.death_contributions[player.name] or 0) * 80
budget = budget + (this.death_contributions[player.name] or 0) * 80
end
if math_random(1, 128) == 1 then budget = budget * 4 end
if math_random(1, 256) == 1 then budget = budget * 4 end
budget = budget * _g.loot_richness
budget = budget * this.loot_richness
local chest = entity.surface.create_entity { name = 'steel-chest', position = entity.position, force = 'player', move_stuck_players = true }
chest.destructible = false
@ -362,14 +566,15 @@ local function on_spawner_died(event)
end
end
local function on_enemy_died(entity)
function Main.on_enemy_died(entity)
local uid = entity.unit_number
local data = _g.invincible[uid]
local data = this.invincible[uid]
if not data then
return
end
if data.time_to_live > game.tick then
if game.tick > data.time_to_live then
this.invincible[uid] = nil
return
end
@ -379,27 +584,31 @@ local function on_enemy_died(entity)
force = entity.force,
}
_g.invincible[new_entity.unit_number] = {
this.invincible[new_entity.unit_number] = {
time_to_live = data.time_to_live,
}
_g.invincible[uid] = nil
this.invincible[uid] = nil
if new_entity.type == 'unit' then
new_entity.set_command(entity.command)
end
end
local play_sound_token = Token.register(Sounds.notify_all)
Main.play_sound_token = Token.register(Sounds.notify_all)
local function move_silo(position)
Main.restart_message_token = Token.register(function(seconds)
game.print({'frontier.restart', seconds}, Color.success)
end)
function Main.move_silo(position)
local surface = RS.get_surface()
local old_silo = _g.rocket_silo
local old_silo = this.rocket_silo
local old_position = old_silo and old_silo.position or { x = 0, y = 0 }
local new_silo
local new_position = position or { x = _g.x, y = _g.y }
local new_position = position or { x = this.x, y = this.y }
if old_silo and math_abs(new_position.x - old_position.x) < _g.min_step then
_g.move_buffer = _g.move_buffer + new_position.x - old_position.x
if old_silo and math_abs(new_position.x - old_position.x) < this.min_step then
this.move_buffer = this.move_buffer + new_position.x - old_position.x
return
end
@ -436,8 +645,12 @@ local function move_silo(position)
end
end
game.print({'frontier.empty_rocket'})
nuclear_explosion(chest)
Task.set_timeout(5, spawn_enemy_wave_token, old_position)
Main.nuclear_explosion(chest)
Task.set_timeout(5, Main.spawn_enemy_wave_token, old_position)
game.forces.enemy.reset_evolution()
local enemy_evolution = game.map_settings.enemy_evolution
enemy_evolution.time_factor = enemy_evolution.time_factor * 1.01
else
new_silo = surface.create_entity { name = 'rocket-silo', position = new_position, force = 'player', move_stuck_players = true }
end
@ -447,11 +660,11 @@ local function move_silo(position)
new_silo.minable = false
new_silo.active = true
new_silo.get_output_inventory().clear()
_g.rocket_silo = new_silo
_g.x = new_silo.position.x
_g.y = new_silo.position.y
_g.move_buffer = 0
set_silo_tiles(new_silo)
this.rocket_silo = new_silo
this.x = new_silo.position.x
this.y = new_silo.position.y
this.move_buffer = 0
Terrain.set_silo_tiles(new_silo)
local x_diff = math.round(new_position.x - old_position.x)
if x_diff > 0 then
@ -461,86 +674,179 @@ local function move_silo(position)
end
end
end
local move_silo_token = Token.register(move_silo)
Main.move_silo_token = Token.register(Main.move_silo)
local function compute_silo_coordinates(step)
_g.move_buffer = _g.move_buffer + (step or 0)
function Main.compute_silo_coordinates(step)
this.move_buffer = this.move_buffer + (step or 0)
if _g.x + _g.move_buffer > _g.max_distance then
if this.x + this.move_buffer > this.max_distance then
-- Exceeding max right direction, move to max (if not already) and add rockets to win
local remainder = _g.x + _g.move_buffer - _g.max_distance
local add_rockets = math_floor(remainder / _g.rocket_step)
local remainder = this.x + this.move_buffer - this.max_distance
local add_rockets = math_floor(remainder / this.rocket_step)
if add_rockets > 0 then
_g.rockets_to_win = _g.rockets_to_win + add_rockets
game.print({'frontier.warning_max_distance', _g.rocket_step})
this.rockets_to_win = this.rockets_to_win + add_rockets
game.print({'frontier.warning_max_distance', this.rocket_step})
end
_g.x = math_min(_g.max_distance, _g.x + _g.move_buffer)
_g.move_buffer = remainder % _g.rocket_step
elseif _g.x + _g.move_buffer < -(_g.left_boundary * 32) + 12 then
this.x = math_min(this.max_distance, this.x + this.move_buffer)
this.move_buffer = remainder % this.rocket_step
elseif this.x + this.move_buffer < -(this.left_boundary * 32) + 12 then
-- Exceeding min left direction, move to min (if not already) and remove rockets to win
local min_distance = -(_g.left_boundary * 32) + 12
local remainder = _g.x + _g.move_buffer - min_distance -- this is negative
local remove_rockets = math_floor(-remainder / _g.rocket_step)
local min_distance = -(this.left_boundary * 32) + 12
local remainder = this.x + this.move_buffer - min_distance -- this is negative
local remove_rockets = math_floor(-remainder / this.rocket_step)
if remove_rockets > 0 then
_g.rockets_to_win = _g.rockets_to_win - remove_rockets
if _g.rockets_to_win < 1 then _g.rockets_to_win = 1 end
if _g.rockets_launched >= _g.rockets_to_win then
win()
this.rockets_to_win = this.rockets_to_win - remove_rockets
if this.rockets_to_win < 1 then this.rockets_to_win = 1 end
if this.rockets_launched >= this.rockets_to_win then
Main.win()
return
else
game.print({'frontier.warning_min_distance', _g.rocket_step})
game.print({'frontier.warning_min_distance', this.rocket_step})
end
end
_g.x = math_max(min_distance, _g.x + _g.move_buffer)
_g.move_buffer = remainder % _g.rocket_step
this.x = math_max(min_distance, this.x + this.move_buffer)
this.move_buffer = remainder % this.rocket_step
else
_g.x = _g.x + _g.move_buffer
_g.move_buffer = 0
this.x = this.x + this.move_buffer
this.move_buffer = 0
end
local max_height = (_g.height * 16) - 16
_g.y = math_random(-max_height, max_height)
local max_height = (this.height * 16) - 16
this.y = math_random(-max_height, max_height)
end
Event.on_init(function()
local ms = game.map_settings
ms.enemy_expansion.friendly_base_influence_radius = 0
ms.enemy_expansion.min_expansion_cooldown = 60 * 30 -- 30 seconds
ms.enemy_expansion.max_expansion_cooldown = 60 * 60 * 4 -- 4 minutes
ms.enemy_expansion.max_expansion_distance = 5
ms.enemy_evolution.destroy_factor = 0.0001
function Main.reveal_spawn_area()
local surface = RS.get_surface()
local far_left, far_right = _g.kraken_distance + _g.left_boundary * 32 + 1, _g.right_boundary * 32 + _g.wall_width
surface.request_to_generate_chunks({ x = 0, y = 0 }, math.ceil(math_max(far_left, far_right, _g.height * 32) / 32))
local far_left, far_right = this.kraken_distance + this.left_boundary * 32 + 1, this.right_boundary * 32 + this.wall_width
surface.request_to_generate_chunks({ x = 0, y = 0 }, math.ceil(math_max(far_left, far_right, this.height * 32) / 32))
surface.force_generate_chunk_requests()
compute_silo_coordinates(_g.silo_starting_x + math_random(100))
move_silo()
init_wall(_g.right_boundary * 32, _g.wall_width)
Main.compute_silo_coordinates(this.silo_starting_x + math_random(100))
Main.move_silo()
Terrain.create_wall(this.right_boundary * 32, this.wall_width)
game.forces.player.chart(surface, { { -far_left - 32, -_g.height * 16 }, { far_right + 32, _g.height * 16 } })
game.forces.player.chart(surface, { { -far_left - 32, -this.height * 16 }, { far_right + 32, this.height * 16 } })
end
function Main.on_game_started()
local ms = game.map_settings
ms.enemy_expansion.friendly_base_influence_radius = 0
ms.enemy_expansion.min_expansion_cooldown = SECOND * 30
ms.enemy_expansion.max_expansion_cooldown = MINUTE * 4
ms.enemy_expansion.max_expansion_distance = 5
ms.enemy_evolution.destroy_factor = 0.0001
ms.enemy_evolution.time_factor = 0.000004
this.rounds = this.rounds + 1
this.kraken_contributors = {}
this.death_contributions = {}
this.rockets_to_win = 3 + math_random(12 + this.rounds)
this.rockets_launched = 0
this.scenario_finished = false
this.x = 0
this.y = 0
this.rocket_silo = nil
this.move_buffer = 0
this.invincible = {}
if _DEBUG then
this.silo_starting_x = 30
this.rockets_to_win = 1
end
for _, force in pairs(game.forces) do
force.reset()
force.reset_evolution()
end
game.speed = 1
game.reset_game_state()
game.reset_time_played()
ScoreTracker.reset()
end
Main.restart_game_token = Token.register(function()
script.raise_event(Main.events.on_game_started, {})
end)
function Main.on_game_finished()
Lobby.enabled = true
Lobby.teleport_all_to()
local surface = RS.get_surface()
surface.clear(true)
surface.map_gen_settings.seed = surface.map_gen_settings.seed + 1
end
Main.end_game_token = Token.register(function()
script.raise_event(Main.events.on_game_finished, {})
end)
-- == EVENTS ==================================================================
local function on_init()
Lobby.on_init()
Main.on_game_started()
Main.reveal_spawn_area()
Lobby.enabled = false
Lobby.teleport_all_from()
end
Event.on_init(on_init)
local function on_game_started()
Main.on_game_started()
Main.reveal_spawn_area()
Lobby.enabled = false
Lobby.teleport_all_from()
end
Event.add(Main.events.on_game_started, on_game_started)
local function on_game_finished()
Main.on_game_finished()
end
Event.add(Main.events.on_game_finished, on_game_finished)
local function on_player_created(event)
local player = game.get_player(event.player_index)
if not (player and player.valid) then
return
end
if Lobby.enabled then
Lobby.teleport_to(player)
end
end
Event.add(defines.events.on_player_created, on_player_created)
local function on_chunk_generated(event)
local area = event.area
local surface = event.surface
if surface.name == Lobby.name then
Lobby.on_chunk_generated(event)
end
if surface.name ~= RS.get_surface_name() then
return
end
-- kill off biters inside the wall
clear_enemies_inside_wall(surface, area)
Terrain.clear_enemies_inside_wall(surface, area)
-- scale freshly generated ore by a scale factor
scale_resource_richness(surface, area)
Terrain.scale_resource_richness(surface, area)
-- add mixed patches
mixed_resources(surface, area)
Terrain.mixed_resources(surface, area)
-- add extra rocks
Terrain.rich_rocks(surface, area)
end
Event.add(defines.events.on_chunk_generated, on_chunk_generated)
local function on_entity_died(event)
local entity = event.entity
if not (entity and entity.valid) then
@ -549,10 +855,10 @@ local function on_entity_died(event)
local entity_type = entity.type
if entity_type == 'unit-spawner' then
on_spawner_died(entity)
Main.on_spawner_died(event)
elseif entity_type == 'unit' or entity.type == 'turret' then
if entity.force.name == 'enemy' then
on_enemy_died(entity)
Main.on_enemy_died(entity)
end
end
end
@ -576,22 +882,20 @@ local function on_player_died(event)
return
end
if _g.rockets_per_death <= 0 then
if this.rockets_per_death <= 0 then
return
end
local player_name = 'a player'
if player then
player_name = player.name
_g.death_contributions[player_name] = (_g.death_contributions[player_name] or 0) + 1
this.death_contributions[player_name] = (this.death_contributions[player_name] or 0) + 1
end
_g.rockets_to_win = _g.rockets_to_win + _g.rockets_per_death
if _g.rockets_to_win < 1 then
_g.rockets_to_win = 1
end
this.rockets_to_win = this.rockets_to_win + this.rockets_per_death
ScoreTracker.set_for_global(rocket_launches_name, this.rockets_to_win - this.rocket_launched)
game.print({'frontier.add_rocket', _g.rockets_per_death, player_name, (_g.rockets_to_win - _g.rockets_launched)})
game.print({'frontier.add_rocket', this.rockets_per_death, player_name, (this.rockets_to_win - this.rockets_launched)})
end
Event.add(defines.events.on_player_died, on_player_died)
@ -601,7 +905,7 @@ local function on_player_changed_position(event)
return
end
if player.position.x < (-_g.left_boundary * 32 - _g.kraken_distance) then
if player.position.x < (-this.left_boundary * 32 - this.kraken_distance) then
local player_name = 'a player'
if player.character ~= nil then
player_name = player.name
@ -609,7 +913,7 @@ local function on_player_changed_position(event)
game.print({'frontier.kraken_eat', player_name}, { sound_path = 'utility/game_lost' })
if player.character ~= nil then
player.character.die()
_g.kraken_contributors[player_name] = true
this.kraken_contributors[player_name] = true
end
end
end
@ -621,32 +925,46 @@ local function on_rocket_launched(event)
return
end
if _g.scenario_finished then
if this.scenario_finished then
return
end
_g.rockets_launched = _g.rockets_launched + 1
if _g.rockets_launched >= _g.rockets_to_win then
win()
this.rockets_launched = this.rockets_launched + 1
if this.rockets_launched >= this.rockets_to_win then
Main.win()
return
end
game.print({'frontier.rocket_launched', _g.rockets_launched, (_g.rockets_to_win - _g.rockets_launched) })
compute_silo_coordinates(500)
game.print({'frontier.rocket_launched', this.rockets_launched, (this.rockets_to_win - this.rockets_launched) })
Main.compute_silo_coordinates(500)
local ticks = 60
for _, delay in pairs{60, 40, 20} do
for i = 1, 30 do
ticks = ticks + math_random(math_ceil(delay/5), delay)
Task.set_timeout_in_ticks(ticks, play_sound_token, 'utility/alert_destroyed')
Task.set_timeout_in_ticks(ticks, Main.play_sound_token, 'utility/alert_destroyed')
end
end
Task.set_timeout_in_ticks(ticks + 30, move_silo_token)
Task.set_timeout_in_ticks(ticks + 30, Main.move_silo_token)
local silo = event.rocket_silo
if silo then silo.active = false end
end
Event.add(defines.events.on_rocket_launched, on_rocket_launched)
local function on_entity_mined(event)
local entity = event.entity
if not (entity and entity.valid) then
return
end
if entity.type == 'simple-entity' then
Main.spawn_turret_outpost(entity.position)
end
end
Event.add(defines.events.on_robot_mined_entity, on_entity_mined)
Event.add(defines.events.on_player_mined_entity, on_entity_mined)
-- == COMMANDS ================================================================
Command.add('ping-silo',

View File

@ -117,6 +117,84 @@ function Public.change_for_global(score_name, value)
})
end
---Sets a setting to a specific value for a player.
---
---@param score_name string
---@param value number to subtract or add
function Public.set_for_global(score_name, value)
if not value then
value = 0
end
local setting = score_metadata[score_name]
if not setting then
if _DEBUG then
error(format('Trying to set score "%s" while it has was never registered.', score_name), 2)
end
return
end
local previous_value = Public.get_for_global(score_name)
if value == previous_value then
return
end
memory_global[score_name] = value
raise_event(on_global_score_changed, {
score_name = score_name
})
end
---Sets a setting to a specific value for a player.
---
---@param player_index number
---@param score_name string
---@param value number to subtract or add
function Public.set_for_player(player_index, score_name, value)
if not value then
value = 0
end
local setting = score_metadata[score_name]
if not setting then
if _DEBUG then
error(format('Trying to change score "%s" while it has was never registered.', score_name), 2)
end
return
end
local player_score = memory_players[player_index]
if not player_score then
player_score = {}
memory_players[player_index] = player_score
end
local previous_value = Public.get_for_player(player_index, score_name)
if value == previous_value then
return
end
player_score[score_name] = (player_score[score_name] or 0) + value
raise_event(on_player_score_changed, {
score_name = score_name,
player_index = player_index
})
end
--- Resets all scores
function Public.reset()
for score_name, _ in pairs(memory_global) do
Public.set_for_global(score_name, 0)
end
for player_index, player_memory in pairs(memory_players) do
for score_name, _ in pairs(player_memory) do
Public.set_for_player(player_index, score_name, 0)
end
end
end
---Returns the value for this player of a specific score.
---
---@param player_index number