1
0
mirror of https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git synced 2025-01-05 22:53:48 +02:00

Merge pull request #200 from Oarcinae/dev_2.0.4_pre_sa_release

2.0.4 Pre Space Age Release
This commit is contained in:
Oarcinae 2024-10-19 13:25:27 -04:00 committed by GitHub
commit 6e5788b29a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 255 additions and 302 deletions

1
.gitignore vendored
View File

@ -1,2 +1 @@
releases
scenarios

View File

@ -1,4 +1,20 @@
---------------------------------------------------------------------------------------------------
Version: 2.0.4
Date: 2024-10-19
Bugfixes:
- Reverting the enemy scaling that introduced new enemy forces due to being a suspected source of desyncs.
- Fixed an issue where unexpected chunks were being removed when abandoned bases were being cleaned up, even though regrowth was disabled.
- Fixed a bug where if you use the custom scenario, it would ignore mod settings even when not overriding them.
- Removing accidentally included scenario from the mod package. (Gitignore doesn't stop it from being included in the mod package when using FMTK.)
Minor Features:
- Added a section in the custom GUI for settings that don't appear in the mod settings, but might still be good to have easy access to. Currently, this just adds a couple of settings related to coins generated by killing enemies. I may expand this in the future.
- Added settings for enabling primary and secondary spawning per surface in the in game GUI settings. Previously you couldn't choose which surfaces allow primary and which allow secondary spawning. Now you can pick which surfaces players are allowed to start on, and which surfaces will generate custom secondary spawns when a player first travels to them. This will be useful for space age support.
Changes:
- Re-added the code that turns on the research queue by default. This is the default vanilla behavior.
- Changed resource placement to use degrees instead of radians. Only applies to circle/octagon bases. Should be more intuitive. Watch out for this change if you modified the resource placement mod settings.
Info:
- Due to the nature of the changes, I would recommend restarting fresh to avoid potential migration issues. I did add migration scripts, but can't guarantee they will work perfectly in all cases.
---------------------------------------------------------------------------------------------------
Version: 2.0.3
Date: 2024-10-13
Major Features:

View File

@ -134,10 +134,6 @@ script.on_event(defines.events.on_tick, function(event)
RegrowthOnTick()
end
RegrowthForceRemovalOnTick() -- Allows for abandoned base cleanup without regrowth enabled.
if global.ocfg.gameplay.modified_enemy_spawning then
RestrictEnemyEvolutionOnTick()
end
end)
----------------------------------------
@ -192,10 +188,6 @@ script.on_event(defines.events.on_built_entity, function(event)
-- For tracking spidertrons...
RegrowthOnBuiltEntity(event)
-- if global.ocfg.enable_anti_grief then
-- SetItemBlueprintTimeToLive(event)
-- end
end)
script.on_event(defines.events.on_robot_built_entity, function (event)
@ -236,15 +228,14 @@ end)
-- This is where I modify biter spawning based on location and other factors.
----------------------------------------
script.on_event(defines.events.on_entity_spawned, function(event)
-- if (global.ocfg.gameplay.modified_enemy_spawning) then
-- ModifyEnemySpawnsNearPlayerStartingAreas(event)
-- end
if (global.ocfg.gameplay.modified_enemy_spawning) then
ModifyEnemySpawnsNearPlayerStartingAreas(event)
end
end)
script.on_event(defines.events.on_biter_base_built, function(event)
if (global.ocfg.gameplay.modified_enemy_spawning) then
-- ModifyEnemySpawnsNearPlayerStartingAreas(event)
ChangeEnemySpawnersToOtherForceOnBuilt(event)
ModifyEnemySpawnsNearPlayerStartingAreas(event)
end
end)
@ -339,7 +330,7 @@ end)
local oarc_mod_interface =
{
get_mod_settings = function()
return OCFG
return global.ocfg
end
}

View File

@ -1,6 +1,6 @@
{
"name": "oarc-mod",
"version": "2.0.3",
"version": "2.0.4",
"factorio_version": "1.1",
"title": "Oarc Multiplayer Spawn",
"author": "Oarcinae",

View File

@ -302,7 +302,7 @@ OCFG = {
-- Default setting for enabling spawning on other surfaces other than the default_surface.
-- This is a STARTUP setting, so it can't be changed in game!!
-- This is a STARTUP setting, so it can't be changed in game!!
default_allow_spawning_on_other_surfaces = true,
default_allow_spawning_on_other_surfaces = false,
-- The name of the main force.
-- This is a STARTUP setting, so it can't be changed in game!!
@ -375,11 +375,11 @@ OCFG = {
-- If you're trying out the vanilla spawning, you might want to disable this.
modified_enemy_spawning = true,
-- Enemy evolution factor for the easy force (inside warning area).
modified_enemy_easy_evo = 0.0,
-- -- Enemy evolution factor for the easy force (inside warning area).
-- modified_enemy_easy_evo = 0.0,
-- Enemy evolution factor for the medium force (inside danger area).
modified_enemy_medium_evo = 0.3,
-- -- Enemy evolution factor for the medium force (inside danger area).
-- modified_enemy_medium_evo = 0.3,
-- Require playes to be online for at least X minutes
-- Else their character is removed and their spawn point is freed up for use
@ -456,14 +456,14 @@ OCFG = {
-- Distance in tiles from the edge of spawn that resources are placed. Only applicable for circular spawns.
distance_to_edge = 20,
-- At what angle (in radians) do resources start.
-- At what angle (in degrees) do resources start.
-- 0 means starts directly east.
-- Resources are placed clockwise from there.
angle_offset = 2.09, -- Approx SSW.
angle_offset = 120, -- Approx SSW.
-- At what andle do we place the last resource.
-- angle_offset and angle_final determine spacing and placement.
angle_final = 4.18, -- Approx NNW.
angle_final = 240, -- Approx NNW.
-- Vertical offset in tiles for the deposit resource placement. Starting from top-left corner.
-- Only applicable for square spawns.
@ -590,8 +590,8 @@ OCFG = {
---@field enable_secondary_spawns boolean Enable secondary spawns for players. This automatically creates a new spawn point when they first move to a separate spawns enabled surface.
---@field scale_resources_around_spawns boolean Scales resources so that even if you spawn "far away" from the center of the map, resources near to your spawn point scale so you aren't surrounded by 100M patches or something. This is useful depending on what map gen settings you pick.
---@field modified_enemy_spawning boolean Adjust enemy spawning based on distance to spawns. All it does it make things more balanced based on your distance and makes the game a little easier. No behemoth worms everywhere just because you spawned far away.
---@field modified_enemy_easy_evo number Enemy evolution factor for the easy force (inside warning area).
---@field modified_enemy_medium_evo number Enemy evolution factor for the medium force (inside danger area).
----@field modified_enemy_easy_evo number Enemy evolution factor for the easy force (inside warning area).
----@field modified_enemy_medium_evo number Enemy evolution factor for the medium force (inside danger area).
---@field minimum_online_time number Require playes to be online for at least X minutes Else their character is removed and their spawn point is freed up for use
---@field respawn_cooldown_min number Respawn cooldown in minutes.
---@field enable_shared_power boolean Enable shared power between bases. Creates a special power pole for cross surface connections.
@ -648,8 +648,8 @@ OCFG = {
---@class OarcConfigSpawnResourcePlacementSettings
---@field enabled boolean Autoplace resources. This will ignore the fixed x_offset/y_offset values in solid_resources. Only works for solid_resources at the moment, not oil patches/water.
---@field distance_to_edge number Distance in tiles from the edge of spawn that resources are placed. Only applicable for circular spawns.
---@field angle_offset number At what angle (in radians) do resources start. 0 means starts directly east. Resources are placed clockwise from there. Only applicable for circular spawns.
---@field angle_final number At what andle do we place the last resource. angle_offset and angle_final determine spacing and placement. Only applicable for circular spawns.
---@field angle_offset integer At what angle (in degrees) do resources start. 0 means starts directly east. Resources are placed clockwise from there. Only applicable for circular spawns.
---@field angle_final integer At what andle do we place the last resource. angle_offset and angle_final determine spacing and placement. Only applicable for circular spawns.
---@field vertical_offset number Vertical offset in tiles for the deposit resource placement. Only applicable for square spawns.
---@field horizontal_offset number Horizontal offset in tiles for the deposit resource placement. Only applicable for square spawns.
---@field linear_spacing number Spacing between resource deposits in tiles. Only applicable for square spawns.

View File

@ -2,7 +2,7 @@
-- DON'T JUDGE ME! I wanted to try and make a nice in game setting GUI since the native mod settings GUI is so limited.
---Provides a way to look up the config settings key from the mod settings key.
---@alias OarcSettingsLookup { mod_key: string, ocfg_keys: table<integer, string>, type: string, text: LocalisedString? }
---@alias OarcSettingsLookup { mod_key: string, ocfg_keys: table<integer, string>, type: string, text: LocalisedString?, caption: LocalisedString?, tooltip: LocalisedString? }
---@type table<string, OarcSettingsLookup>
OCFG_KEYS =
@ -33,8 +33,8 @@ OCFG_KEYS =
["gameplay.enable_offline_protection"] = {mod_key = "oarc-mod-enable-offline-protection" , ocfg_keys = {"gameplay", "enable_offline_protection"}, type = "boolean"},
["gameplay.scale_resources_around_spawns"] = {mod_key = "oarc-mod-scale-resources-around-spawns" , ocfg_keys = {"gameplay", "scale_resources_around_spawns"}, type = "boolean"},
["gameplay.modified_enemy_spawning"] = {mod_key = "oarc-mod-modified-enemy-spawning" , ocfg_keys = {"gameplay", "modified_enemy_spawning"}, type = "boolean"},
["gameplay.modified_enemy_easy_evo"] = {mod_key = "oarc-mod-modified-enemy-easy-evo" , ocfg_keys = {"gameplay", "modified_enemy_easy_evo"}, type = "double"},
["gameplay.modified_enemy_medium_evo"] = {mod_key = "oarc-mod-modified-enemy-medium-evo" , ocfg_keys = {"gameplay", "modified_enemy_medium_evo"}, type = "double"},
-- ["gameplay.modified_enemy_easy_evo"] = {mod_key = "oarc-mod-modified-enemy-easy-evo" , ocfg_keys = {"gameplay", "modified_enemy_easy_evo"}, type = "double"},
-- ["gameplay.modified_enemy_medium_evo"] = {mod_key = "oarc-mod-modified-enemy-medium-evo" , ocfg_keys = {"gameplay", "modified_enemy_medium_evo"}, type = "double"},
["gameplay_misc_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-gameplay-misc"}},
["gameplay.enable_friendly_fire"] = {mod_key = "oarc-mod-enable-friendly-fire" , ocfg_keys = {"gameplay", "enable_friendly_fire"}, type = "boolean"},
@ -48,7 +48,6 @@ OCFG_KEYS =
["gameplay.enable_shared_chest"] = {mod_key = "oarc-mod-enable-shared-chest" , ocfg_keys = {"gameplay", "enable_shared_chest"}, type = "boolean"},
["gameplay.enable_coin_shop"] = {mod_key = "oarc-mod-enable-coin-shop" , ocfg_keys = {"gameplay", "enable_coin_shop"}, type = "boolean"},
["regrowth_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = {"oarc-settings-section-header-regrowth"}},
["regrowth_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-regrowth-warning"}},
["regrowth.enable_regrowth"] = {mod_key = "oarc-mod-enable-regrowth" , ocfg_keys = {"regrowth", "enable_regrowth"}, type = "boolean"},
@ -71,13 +70,19 @@ OCFG_KEYS =
["resource_placement_circle_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-resource-placement-circular"}},
["resource_placement.distance_to_edge"] = {mod_key = "oarc-mod-resource-placement-distance-to-edge" , ocfg_keys = {"resource_placement", "distance_to_edge"}, type = "integer"},
["resource_placement.angle_offset"] = {mod_key = "oarc-mod-resource-placement-angle-offset" , ocfg_keys = {"resource_placement", "angle_offset"}, type = "double"},
["resource_placement.angle_final"] = {mod_key = "oarc-mod-resource-placement-angle-final" , ocfg_keys = {"resource_placement", "angle_final"}, type = "double"},
["resource_placement.angle_offset"] = {mod_key = "oarc-mod-resource-placement-degrees-offset" , ocfg_keys = {"resource_placement", "angle_offset"}, type = "integer"},
["resource_placement.angle_final"] = {mod_key = "oarc-mod-resource-placement-degrees-final" , ocfg_keys = {"resource_placement", "angle_final"}, type = "integer"},
["resource_placement_square_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = {"oarc-settings-section-subheader-resource-placement-square"}},
["resource_placement.vertical_offset"] = {mod_key = "oarc-mod-resource-placement-vertical-offset" , ocfg_keys = {"resource_placement", "vertical_offset"}, type = "integer"},
["resource_placement.horizontal_offset"] = {mod_key = "oarc-mod-resource-placement-horizontal-offset" , ocfg_keys = {"resource_placement", "horizontal_offset"}, type = "integer"},
["resource_placement.linear_spacing"] = {mod_key = "oarc-mod-resource-placement-linear-spacing" , ocfg_keys = {"resource_placement", "linear_spacing"}, type = "integer"},
-- These are settings that aren't included in the games mod settings but are still nice to have easy access to.
["non_mod_settings_HEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "header", text = "Additional Settings (Not available in the mod settings menu.)"},
["coin_generation_SUBHEADER"] = {mod_key = "" , ocfg_keys = {""}, type = "subheader", text = "Coin Generation"},
["coin_generation.enabled"] = {mod_key = "" , ocfg_keys = {"coin_generation", "enabled"}, type = "boolean", caption = "Coin Generation", tooltip = "Enemies drop coins when killed."},
["coin_generation.auto_decon_coins"] = {mod_key = "" , ocfg_keys = {"coin_generation", "auto_decon_coins"}, type = "boolean", caption = "Auto Decon Coins", tooltip = "Automatically marks coins dropped by enemies for deconstruction so robots will pick them up."},
}
---Easy reverse lookup for mod settings keys.
@ -118,7 +123,7 @@ function ValidateAndLoadConfig()
-- Check that each entry in OCFG matches the default value of the mod setting. This is just for my own sanity.
-- Helps make sure mod default settings and my internal config are in sync.
for _,entry in pairs(OCFG_KEYS) do
if (entry.type ~= "header") and (entry.type ~= "subheader") then
if (entry.mod_key ~= "") then
local mod_key = entry.mod_key
local oarc_key = entry.ocfg_keys
local mod_value = game.mod_setting_prototypes[mod_key].default_value
@ -249,7 +254,7 @@ function CacheModSettings()
-- Copy the global settings from the mod settings.
-- Find the matching OARC setting and update it.
for _,entry in pairs(OCFG_KEYS) do
if (entry.type ~= "header") and (entry.type ~= "subheader") then
if (entry.mod_key ~= "") then
SetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys, settings.global[entry.mod_key].value)
end
end
@ -281,7 +286,7 @@ function SyncModSettingsToOCFG()
-- Override the mod settings with the the global.ocfg settings.
for _,entry in pairs(OCFG_KEYS) do
if (entry.type ~= "header") and (entry.type ~= "subheader") then
if (entry.mod_key ~= "") then
local mod_key = entry.mod_key
local oarc_key = entry.ocfg_keys
local scenario_value = GetGlobalOarcConfigUsingKeyTable(oarc_key)
@ -384,7 +389,7 @@ function ApplyRuntimeChanges(oarc_setting_index)
---Handle changing enable_shared_team_vision
if (oarc_setting_index == "gameplay.enable_shared_team_vision") then
for _,force in pairs(game.forces) do
if (force.name ~= "neutral") and (force.name ~= "enemy") and (force.name ~= "enemy-easy") then
if (not TableContains(ENEMY_FORCES_NAMES_INCL_NEUTRAL, force.name)) then
force.share_chart = global.ocfg.gameplay.enable_shared_team_vision
end
end
@ -392,7 +397,7 @@ function ApplyRuntimeChanges(oarc_setting_index)
---Handle changing enable_friendly_fire
elseif (oarc_setting_index == "gameplay.enable_friendly_fire") then
for _,force in pairs(game.forces) do
if (force.name ~= "neutral") and (force.name ~= "enemy") and (force.name ~= "enemy-easy") then
if (not TableContains(ENEMY_FORCES_NAMES_INCL_NEUTRAL, force.name)) then
force.friendly_fire = global.ocfg.gameplay.enable_friendly_fire
end
end

View File

@ -36,7 +36,7 @@ function CreateSettingsControlsTab(tab_container, player)
scroll_pane_right.style.maximal_height = GENERIC_GUI_MAX_HEIGHT
scroll_pane_right.style.padding = 5
scroll_pane_right.style.left_margin = 2
CreateSurfaceSettingsSection(scroll_pane_right, player)
AddSpacerLine(scroll_pane_right)
@ -83,20 +83,24 @@ function CreateSurfaceSettingsSection(container, player)
local surface_table = container.add {
type = "table",
name = "surface_table",
column_count = 3,
column_count = 4,
style = "bordered_table",
}
--- Add the header row
AddLabel(surface_table, nil, {"oarc-settings-tab-surface-column-header"}, "caption_label")
AddLabel(surface_table, nil, {"oarc-settings-tab-surface-spawning-enabled"}, "caption_label")
AddLabel(surface_table, nil, {"oarc-settings-tab-surface-secondary-enabled"}, "caption_label")
AddLabel(surface_table, nil, {"oarc-settings-tab-surface-regrowth-enabled"}, "caption_label")
--- Add the rows
for name, allowed in pairs(global.oarc_surfaces --[[@as table<string, boolean>]]) do
for name, allowed in pairs(global.oarc_surfaces) do
AddLabel(surface_table, nil, name, my_label_style)
AddSurfaceCheckboxSetting(surface_table, name, "spawn_enabled", allowed, player.admin,
AddSurfaceCheckboxSetting(surface_table, name, "spawn_enabled", allowed.primary, player.admin,
{ "oarc-settings-tab-surface-checkbox-tooltip" })
AddSurfaceCheckboxSetting(surface_table, name, "secondary_enabled", allowed.secondary, player.admin,
{ "oarc-settings-tab-surface-secondary-checkbox-tooltip" })
local regrowth_enabled = TableContains(global.rg.active_surfaces, name)
AddSurfaceCheckboxSetting(surface_table, name, "regrowth_enabled", regrowth_enabled, player.admin,
{"oarc-settings-tab-surface-regrowth-checkbox-tooltip"})
@ -163,7 +167,11 @@ function SettingsControlsTabGuiClick(event)
local entry = OCFG_KEYS[index]
if (entry.type == "boolean") then
settings.global[entry.mod_key] = { value = gui_elem.state }
if (entry.mod_key ~= "") then
settings.global[entry.mod_key] = { value = gui_elem.state }
else
SetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys, gui_elem.state)
end
end
end
@ -307,16 +315,33 @@ end
---@param enabled boolean
---@return nil
function AddCheckboxSetting(tab_container, index, entry, enabled)
local caption, tooltip = GetCaptionAndTooltip(entry)
tab_container.add{
type = "checkbox",
caption = { "mod-setting-name."..entry.mod_key },
caption = caption,
state = GetGlobalOarcConfigUsingKeyTable(entry.ocfg_keys),
enabled = enabled,
tooltip = { "mod-setting-description."..entry.mod_key },
tooltip = tooltip,
tags = { action = "oarc_settings_tab_left_pane", setting = index },
}
end
---Gets the caption and tooltip for a setting entry whether it is a mod setting or not.
---@param entry OarcSettingsLookup
---@return LocalisedString, LocalisedString
function GetCaptionAndTooltip(entry)
local caption
local tooltip
if (entry.mod_key == "") then
caption = entry.caption
tooltip = entry.tooltip
else
caption = { "mod-setting-name."..entry.mod_key }
tooltip = { "mod-setting-description."..entry.mod_key }
end
return caption, tooltip
end
---Creates a textfield setting
---@param tab_container LuaGuiElement
---@param index string
@ -519,14 +544,19 @@ function SettingsSurfaceControlsTabGuiClick(event)
if (setting_name == "spawn_enabled") then
local surface_name = gui_elem.tags.surface --[[@as string]]
global.oarc_surfaces[surface_name] = gui_elem.state
global.oarc_surfaces[surface_name].primary = gui_elem.state
if (#GetAllowedSurfaces() == 0) then
log("Warning - GetAllowedSurfaces() - No surfaces found! Forcing default surface!")
global.oarc_surfaces[global.ocfg.gameplay.default_surface] = true
global.oarc_surfaces[global.ocfg.gameplay.default_surface].primary = true
event.element.parent[global.ocfg.gameplay.default_surface.."_spawn_enabled"].state = true
end
elseif (setting_name == "secondary_enabled") then
local surface_name = gui_elem.tags.surface --[[@as string]]
global.oarc_surfaces[surface_name].secondary = gui_elem.state
elseif (setting_name == "regrowth_enabled") then
local surface_name = gui_elem.tags.surface --[[@as string]]

View File

@ -553,7 +553,7 @@ function ConfigurePlayerForceRelationships(cease_fire, friends)
local player_forces = {}
for name, force in pairs(game.forces) do
if name ~= "neutral" and name ~= ABANDONED_FORCE_NAME and not TableContains(ENEMY_FORCES_NAMES, name) then
if name ~= ABANDONED_FORCE_NAME and not TableContains(ENEMY_FORCES_NAMES_INCL_NEUTRAL, name) then
table.insert(player_forces, force)
end
end

View File

@ -18,33 +18,25 @@ function OarcModifyEnemyGroup(event)
local group = event.group
-- Check validity
if ((group == nil) or (group.command == nil) or not TableContains(ENEMY_FORCES_NAMES, group.force.name)) then
if ((group.command == nil) or (group.force.name ~= "enemy")) then
log("WARN - OarcModifyEnemyGroup ignoring INVALID group/command " .. serpent.block(group))
return
end
-- Make sure the attack is of a TYPE that we care about.
if ((group.command.type == defines.command.attack_area) or
(group.command.type == defines.command.build_base)) then
-- log("OarcModifyEnemyGroup MODIFYING command TYPE=" .. group.command.type)
else
if ((group.command.type ~= defines.command.attack_area) and
(group.command.type ~= defines.command.build_base)) then
-- log("OarcModifyEnemyGroup ignoring command TYPE=" .. group.command.type)
return
end
-- (group.command.type == defines.command.attack) or
-- defines.command.attack --> target --> target.position
-- if (group.command.type == defines.command.attack) then
-- log("OarcModifyEnemyGroup defines.command.attack NOT IMPLEMENTED YET!")
-- return
-- end
-- defines.command.attack_area --> destination --> closest enemy (within 3 chunk radius?)
-- defines.command.build_base --> destination --> closest enemy (expansion chunk distance?)
-- For these 2 commands, we look around to find the nearest player.
-- defines.command.attack_area --> destination --> closest player (within 3 chunk radius?)
-- defines.command.build_base --> destination --> closest player (expansion chunk distance?)
local destination = group.command.destination
local search_radius = CHUNK_SIZE*3
local search_radius = CHUNK_SIZE * 3 -- Just a reasonable default search size I think?
if (group.command.type == defines.command.build_base) then
search_radius = CHUNK_SIZE * (game.map_settings.enemy_expansion.max_expansion_distance)
end
@ -73,10 +65,7 @@ function OarcModifyEnemyGroup(event)
if (group.command.type == defines.command.attack_area) then
-- SendBroadcastMsg("OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!? " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, group.command.destination))
log("ERROR - OarcModifyEnemyGroup find_nearest_enemy attack_area FAILED!?!?" .. serpent.block(group))
-- for _,member in pairs(group.members) do
-- member.destroy()
-- end
-- This is fine, as the enemy group is just expanding / building bases
else
-- log("OarcModifyEnemyGroup find_nearest_enemy did not find anything!")
@ -87,14 +76,8 @@ function OarcModifyEnemyGroup(event)
-- Most common target will be a built entity with a "last_user"
local target_player = target_entity.last_user
-- -- Target could also be a player character (more rare)
-- if (target_player == nil) and (target_entity.type == "character") then
-- target_player = target_entity.player
-- end
-- I don't think this should happen ever...
if ((target_player == nil) or (not target_player.valid)) then
-- SendBroadcastMsg("ERROR?? target_player nil/invalid " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position))
log("ERROR - OarcModifyEnemyGroup target_player nil/invalid?" .. serpent.block(group))
-- for _,member in pairs(group.members) do
-- member.destroy()
@ -104,7 +87,6 @@ function OarcModifyEnemyGroup(event)
-- Is the target player online? Then the attack can go through.
if (target_player.connected) then
-- SendBroadcastMsg("Enemy group released (player): " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position) .. " " .. target_player.name)
-- log("OarcModifyEnemyGroup RELEASING enemy group since player is ONLINE " .. target_player.name)
return
end
@ -115,7 +97,6 @@ function OarcModifyEnemyGroup(event)
-- Is someone in the group online?
if (#online_players > 0) then
-- SendBroadcastMsg("Enemy group released (shared): " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position) .. " " .. target_player.name)
-- log("OarcModifyEnemyGroup RELEASING enemy group since someone in the group is ONLINE " .. target_player.name)
return
end
@ -124,7 +105,5 @@ function OarcModifyEnemyGroup(event)
for _,member in pairs(group.members) do
member.destroy()
end
-- SendBroadcastMsg("Enemy group deleted: " .. GetGPStext(group.surface.name, group.position) .. " Target: " .. GetGPStext(group.surface.name, target_entity.position) .. " " .. target_player.name)
-- log("OarcModifyEnemyGroup REMOVED enemy group since nobody was online? " .. target_player.name)
end

View File

@ -137,6 +137,9 @@ function IsRegrowthEnabledOnSurface(surface_name)
return global.rg[surface_name].active
end
---Enables a surface by initializing it.
---@param surface_name string - The surface name to act on
---@return nil
function RegrowthEnableSurface(surface_name)
InitSurface(surface_name)
end
@ -497,8 +500,12 @@ function OarcRegrowthRemoveAllChunks()
elseif (global.rg[surface_name].map[c_pos.x][c_pos.y] == REGROWTH_FLAG_REMOVAL) then
-- If regrowth is disabled, remove the chunnk from the map without deleting it.
if (not global.ocfg.regrowth.enable_regrowth or not global.rg[surface_name].active) then
global.rg[surface_name].map[c_pos.x][c_pos.y] = nil
-- If it is a normal timeout removal, don't do it if there is pollution in the chunk.
if (game.surfaces[surface_name].get_pollution({ c_pos.x * 32, c_pos.y * 32 }) > 0) then
elseif (game.surfaces[surface_name].get_pollution({ c_pos.x * 32, c_pos.y * 32 }) > 0) then
global.rg[surface_name].map[c_pos.x][c_pos.y] = game.tick
-- Else delete the chunk

View File

@ -5,94 +5,11 @@
-- TODO: Plan for new enemies in space DLC.
-- TODO: Plan for new enemies in space DLC.
ENEMY_FORCE_EASY = "enemy-easy"
ENEMY_FORCE_MEDIUM = "enemy-medium"
ENEMY_FORCES_NAMES = {"enemy", ENEMY_FORCE_EASY, ENEMY_FORCE_MEDIUM}
ENEMY_FORCES_NAMES_INCL_NEUTRAL = {"enemy", ENEMY_FORCE_EASY, ENEMY_FORCE_MEDIUM, "neutral"}
ENEMY_FORCES_NAMES = { "enemy" }
ENEMY_FORCES_NAMES_INCL_NEUTRAL = { "enemy", "neutral" }
ENEMY_BUILT_TYPES = { "biter-spawner", "spitter-spawner", "small-worm-turret", "medium-worm-turret", "big-worm-turret", "behemoth-worm-turret" }
---Create a few extra enemy forces with fixed evolution factors for scaling down near player bases.
---@return nil
function CreateEnemyForces()
-- Create the enemy forces if they don't exist
for _,force_name in pairs(ENEMY_FORCES_NAMES) do
if (game.forces[force_name] == nil) then
game.create_force(force_name)
end
local enemy_force = game.forces[force_name]
enemy_force.ai_controllable = true
end
ConfigureEnemyForceRelationships()
end
---Configures the friend and cease fire relationships between all forces and enemy forces.
---@return nil
function ConfigureEnemyForceRelationships()
for _,force in pairs(game.forces) do
-- If this is an enemy force
if (TableContains(ENEMY_FORCES_NAMES, force.name)) then
-- Make sure it IS friends with all other enemy forces.
for _,enemy_force_name in pairs(ENEMY_FORCES_NAMES) do
if (force.name ~= enemy_force_name) then -- Exclude self
local enemy_force = game.forces[enemy_force_name]
force.set_friend(enemy_force, true)
force.set_cease_fire(enemy_force, true)
enemy_force.set_friend(force, true)
enemy_force.set_cease_fire(force, true)
end
end
-- If this is a non-enemy force
else
-- Make sure this force is NOT friends with any enemy forces.
for _,enemy_force_name in pairs(ENEMY_FORCES_NAMES) do
local enemy_force = game.forces[enemy_force_name]
force.set_friend(enemy_force, false)
force.set_cease_fire(enemy_force, false)
enemy_force.set_friend(force, false)
enemy_force.set_cease_fire(force, false)
end
end
end
end
---Configures the friend and cease fire relationships between all enemy forces and a new player force.
---@param new_player_force LuaForce
---@return nil
function ConfigureEnemyForceRelationshipsForNewPlayerForce(new_player_force)
for _,enemy_force_name in pairs(ENEMY_FORCES_NAMES) do
local enemy_force = game.forces[enemy_force_name]
new_player_force.set_friend(enemy_force, false)
new_player_force.set_cease_fire(enemy_force, false)
enemy_force.set_friend(new_player_force, false)
enemy_force.set_cease_fire(new_player_force, false)
end
end
--- Keep the enemy evolution factor in check.
---@return nil
function RestrictEnemyEvolutionOnTick()
local base_evo_factor = game.forces["enemy"].evolution_factor
-- Restrict the evolution factor of the enemy forces
game.forces[ENEMY_FORCE_EASY].evolution_factor = math.min(base_evo_factor, global.ocfg.gameplay.modified_enemy_easy_evo)
game.forces[ENEMY_FORCE_MEDIUM].evolution_factor = math.min(base_evo_factor, global.ocfg.gameplay.modified_enemy_medium_evo)
end
---Downgrades worms based on distance from origin and near/far spawn distances.
---This helps make sure worms aren't too overwhelming even at these further spawn distances.
@ -104,13 +21,13 @@ function DowngradeWormsDistanceBasedOnChunkGenerate(event)
local gameplay = global.ocfg.gameplay
if (util.distance({ x = 0, y = 0 }, event.area.left_top) < (gameplay.near_spawn_distance * CHUNK_SIZE)) then
DowngradeWormsInArea(event.surface, event.area, 100, 100, 100)
DowngradeWormsInArea(event.surface, event.area, 50, 100, 100) -- 50% small, 50% medium
elseif (util.distance({ x = 0, y = 0 }, event.area.left_top) < (gameplay.far_spawn_distance * CHUNK_SIZE)) then
DowngradeWormsInArea(event.surface, event.area, 50, 90, 100)
elseif (util.distance({ x = 0, y = 0 }, event.area.left_top) < (gameplay.far_spawn_distance * CHUNK_SIZE * 2)) then
DowngradeWormsInArea(event.surface, event.area, 20, 80, 97)
DowngradeWormsInArea(event.surface, event.area, 25, 50, 95) -- 25% small, 25% medium, 45% big, 5% behemoth
elseif (util.distance({ x = 0, y = 0 }, event.area.left_top) < (gameplay.far_spawn_distance * CHUNK_SIZE * 1.5)) then
DowngradeWormsInArea(event.surface, event.area, 0, 40, 85) -- 40% medium, 45% big, 15% behemoth
else
DowngradeWormsInArea(event.surface, event.area, 0, 20, 90)
DowngradeWormsInArea(event.surface, event.area, 0, 20, 50) -- 20% medium, 30% big, 50% behemoth
end
end
@ -141,7 +58,6 @@ function DowngradeAndReduceEnemiesOnChunkGenerate(event)
-- TODO: Refactor this to reduce calls to find_entities_filtered!
ReduceEnemiesInArea(surface, chunk_area, spawn_config.safe_area.warn_reduction)
RemoveWormsInArea(surface, chunk_area, false, true, true, true) -- remove all non-small worms.
ConvertEnemiesToOtherForceInArea(surface, chunk_area, ENEMY_FORCE_EASY)
-- Create a third area with moderately reduced enemies
elseif (util.distance(closest_spawn.position, chunkAreaCenter) < spawn_config.safe_area.danger_radius * CHUNK_SIZE) then
@ -149,7 +65,6 @@ function DowngradeAndReduceEnemiesOnChunkGenerate(event)
-- TODO: Refactor this to reduce calls to find_entities_filtered!
ReduceEnemiesInArea(surface, chunk_area, spawn_config.safe_area.danger_reduction)
RemoveWormsInArea(surface, chunk_area, false, false, true, true) -- remove all huge/behemoth worms.
ConvertEnemiesToOtherForceInArea(surface, chunk_area, ENEMY_FORCE_MEDIUM)
end
end
@ -180,9 +95,9 @@ end
---Downgrades worms in an area based on chance. 100% small would mean all worms are changed to small.
---@param surface LuaSurface
---@param area BoundingBox
---@param small_percent integer ---Chance to change to small worm
---@param medium_percent integer
---@param big_percent integer
---@param small_percent integer ---Chance to force to small worm
---@param medium_percent integer ---Chance to force to medium worm
---@param big_percent integer ---Chance to force to big worm
---@return nil
function DowngradeWormsInArea(surface, area, small_percent, medium_percent, big_percent)
-- Leave out "small-worm-turret" as it's the lowest.
@ -193,26 +108,25 @@ function DowngradeWormsInArea(surface, area, small_percent, medium_percent, big_
local rand_percent = math.random(0, 100)
local worm_pos = entity.position
local worm_name = entity.name
local force = entity.force
-- If number is less than small percent, change to small
if (rand_percent <= small_percent) then
if (not (worm_name == "small-worm-turret")) then
entity.destroy()
surface.create_entity { name = "small-worm-turret", position = worm_pos, force = game.forces.enemy }
end
entity.destroy()
surface.create_entity { name = "small-worm-turret", position = worm_pos, force = force }
-- ELSE If number is less than medium percent, change to small
-- ELSE If number is less than medium percent, change to medium
elseif (rand_percent <= medium_percent) then
if (not (worm_name == "medium-worm-turret")) then
entity.destroy()
surface.create_entity { name = "medium-worm-turret", position = worm_pos, force = game.forces.enemy }
surface.create_entity { name = "medium-worm-turret", position = worm_pos, force = force }
end
-- ELSE If number is less than big percent, change to small
-- ELSE If number is less than big percent, change to big
elseif (rand_percent <= big_percent) then
if (not (worm_name == "big-worm-turret")) then
entity.destroy()
surface.create_entity { name = "big-worm-turret", position = worm_pos, force = game.forces.enemy }
surface.create_entity { name = "big-worm-turret", position = worm_pos, force = force }
end
-- ELSE ignore it.
@ -254,57 +168,6 @@ function RemoveWormsInArea(surface, area, small, medium, big, behemoth)
end
end
---Converts all enemies in the base enemy force in an area to easy force.
---@param surface LuaSurface
---@param area BoundingBox
---@param force_name string
---@return nil
function ConvertEnemiesToOtherForceInArea(surface, area, force_name)
for _, entity in pairs(surface.find_entities_filtered { area = area, force = "enemy" }) do
entity.force = game.forces[force_name]
end
end
---Converts new enemy bases to different enemy forces if they are near to player bases.
---@param event EventData.on_biter_base_built
---@return nil
function ChangeEnemySpawnersToOtherForceOnBuilt(event)
if (not event.entity or not event.entity.position or not TableContains(ENEMY_FORCES_NAMES, event.entity.force.name)) then
log("ChangeEnemySpawnersToOtherForceOnBuilt Unexpected entity or force?")
return
end
local enemy_pos = event.entity.position
local surface = event.entity.surface
local enemy_name = event.entity.name
if (not TableContains(ENEMY_BUILT_TYPES, enemy_name)) then
log("ChangeEnemySpawnersToOtherForceOnBuilt Unexpected entity name? " .. enemy_name)
return
end
local closest_spawn = GetClosestUniqueSpawn(surface.name, enemy_pos)
if (closest_spawn == nil) then return end
-- No enemies inside safe radius!
if (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.safe_radius * CHUNK_SIZE) then
event.entity.destroy()
-- Warn distance should be EASY
elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius * CHUNK_SIZE) then
event.entity.force = game.forces[ENEMY_FORCE_EASY]
-- Danger distance should be MEDIUM
elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.danger_radius * CHUNK_SIZE) then
event.entity.force = game.forces[ENEMY_FORCE_MEDIUM]
-- Otherwise make sure they are on the base enemy force (stops easy enemies from spreading out too far).
else
event.entity.force = game.forces["enemy"]
end
end
-- I wrote this to ensure everyone gets safer spawns regardless of evolution level.
-- This is intended to downgrade any biters/spitters spawning near player bases.
-- I'm not sure the performance impact of this but I'm hoping it's not bad.
@ -334,19 +197,19 @@ function ModifyEnemySpawnsNearPlayerStartingAreas(event)
-- Warn distance is all SMALL only.
elseif (util.distance(enemy_pos, closest_spawn.position) < global.ocfg.surfaces_config[surface.name].spawn_config.safe_area.warn_radius * CHUNK_SIZE) then
if ((enemy_name == "biter-spawner") or (enemy_name == "spitter-spawner")) then
event.entity.force = game.forces["enemy-easy"]
-- Do nothing.
elseif ((enemy_name == "big-biter") or (enemy_name == "behemoth-biter") or (enemy_name == "medium-biter")) then
event.entity.destroy()
surface.create_entity { name = "small-biter", position = enemy_pos, force = game.forces["enemy-easy"] }
surface.create_entity { name = "small-biter", position = enemy_pos, force = game.forces.enemy }
-- log("Downgraded biter close to spawn.")
elseif ((enemy_name == "big-spitter") or (enemy_name == "behemoth-spitter") or (enemy_name == "medium-spitter")) then
event.entity.destroy()
surface.create_entity { name = "small-spitter", position = enemy_pos, force = game.forces["enemy-easy"] }
surface.create_entity { name = "small-spitter", position = enemy_pos, force = game.forces.enemy }
-- log("Downgraded spitter close to spawn.")
elseif ((enemy_name == "big-worm-turret") or (enemy_name == "behemoth-worm-turret") or (enemy_name == "medium-worm-turret")) then
event.entity.destroy()
surface.create_entity { name = "small-worm-turret", position = enemy_pos, force = game.forces["enemy-easy"] }
surface.create_entity { name = "small-worm-turret", position = enemy_pos, force = game.forces.enemy }
-- log("Downgraded worm close to spawn.")
end

View File

@ -20,7 +20,7 @@ ABANDONED_FORCE_NAME = "_ABANDONED_"
function InitSpawnGlobalsAndForces()
-- Contains a table of entries for each surface. This tracks which surfaces allow spawning?
--[[@type table<string, boolean>]]
--[[@type table<string, OarcSurfaceSpawnSetting>]]
global.oarc_surfaces = {}
for _, surface in pairs(game.surfaces) do
SeparateSpawnsInitSurface(surface.name)
@ -69,9 +69,6 @@ function InitSpawnGlobalsAndForces()
game.create_force(ABANDONED_FORCE_NAME)
-- game.create_force(DESTROYED_FORCE_NAME)
-- Special enemy forces for scaling down enemies near player bases.
CreateEnemyForces()
-- Name a new force to be the default force.
-- This is what any new player is assigned to when they join, even before they spawn.
local main_force = CreatePlayerForce(global.ocfg.gameplay.main_force_name)
@ -134,11 +131,14 @@ function SeparateSpawnsInitSurface(surface_name)
-- Add the surface to the list of surfaces that allow spawns with value from config.
if global.ocfg.gameplay.default_allow_spawning_on_other_surfaces then
global.oarc_surfaces[surface_name] = true
global.oarc_surfaces[surface_name] = { primary = true, secondary = true }
-- Otherwise only allow the default surface (by default)
else
global.oarc_surfaces[surface_name] = (surface_name == global.ocfg.gameplay.default_surface)
global.oarc_surfaces[surface_name] = {
primary = (surface_name == global.ocfg.gameplay.default_surface),
secondary = false
}
end
-- Make sure it has a surface configuration entry!
@ -213,7 +213,7 @@ function SeparateSpawnsPlayerRespawned(event)
if (player.surface.name == HOLDING_PEN_SURFACE_NAME) then return end
-- If the mod isn't active on this surface, then ignore it.
if (not global.oarc_surfaces[surface_name]) then return end
if (global.oarc_surfaces[surface_name] == nil) then return end
SendPlayerToSpawn(surface_name, player)
GivePlayerRespawnItems(player)
@ -239,16 +239,22 @@ function SeparateSpawnsPlayerChangedSurface(event)
if (not global.ocfg.gameplay.enable_secondary_spawns) then return end
local player = game.players[event.player_index]
local surface_name = player.surface.name
-- Check if player has been init'd yet. If not, then ignore it.
if (global.player_respawns[player.name] == nil) then return end
-- If the mod isn't active on this surface, then ignore it.
if (not global.oarc_surfaces[player.surface.name]) then return end
if (global.oarc_surfaces[surface_name] == nil) then return end
-- If this is their first time on the planet, create a secondary spawn point for them.
-- TODO: Check for buddy and shared spawn hosts?
if (global.unique_spawns[player.surface.name] == nil) or (global.unique_spawns[player.surface.name][player.name] == nil) then
-- If the mod is configured to not allow secondary spawns here, then ignore it.
if (not global.oarc_surfaces[surface_name].secondary) then return end
-- Check if there is already a spawn for them on this surface
-- Could be a buddy spawn, or a shared spawn, or a unique spawn.
local player_spawn = FindPlayerSpawnOnSurface(player.name, surface_name)
if (player_spawn == nil) then
log("WARNING - THIS IS NOT FULLY IMPLEMENTED YET!!")
SecondarySpawn(player, player.surface)
end
@ -352,15 +358,16 @@ function PlaceResourcesInSemiCircle(surface, position, size_mod, amount_mod)
local shuffled_list = FYShuffle(r_list)
-- This places resources in a semi-circle
local angle_offset = global.ocfg.resource_placement.angle_offset
local angle_offset_radians = math.rad(global.ocfg.resource_placement.angle_offset)
local angle_final_radians = math.rad(global.ocfg.resource_placement.angle_final)
local num_resources = table_size(global.ocfg.surfaces_config[surface.name].spawn_config.solid_resources)
local theta = ((global.ocfg.resource_placement.angle_final - global.ocfg.resource_placement.angle_offset) / (num_resources-1));
local theta = ((angle_final_radians - angle_offset_radians) / (num_resources-1));
local count = 0
local radius = global.ocfg.spawn_general.spawn_radius_tiles - global.ocfg.resource_placement.distance_to_edge
for _, r_name in pairs(shuffled_list) do
local angle = (theta * count) + angle_offset;
local angle = (theta * count) + angle_offset_radians;
local tx = (radius * math.cos(angle)) + position.x
local ty = (radius * math.sin(angle)) + position.y
@ -792,6 +799,19 @@ function FindPlayerHomeSpawn(player_name)
end
end
---Find the spawn of a player, if one exists, on a specific surface.
---@param player_name string
---@param surface_name string
---@return OarcUniqueSpawn?
function FindPlayerSpawnOnSurface(player_name, surface_name)
if (global.unique_spawns[surface_name] == nil) then return nil end
for _,spawn in pairs(global.unique_spawns[surface_name]) do
if ((spawn.host_name == player_name) or TableContains(spawn.joiners, player_name)) then
return spawn
end
end
end
---Searches all unique spawns for a list of secondary ones for a player.
---@param player_name string
---@return table<string, OarcUniqueSpawn> -- Indexed by surface name!
@ -864,12 +884,10 @@ function CleanupPlayerGlobals(player_name)
for index, joiner in pairs(spawn.joiners) do
if (player_name == joiner) then
global.unique_spawns[surface_index][player_index].joiners[index] = nil
goto LOOP_BREAK -- Nest loop break. Assumes only one entry per player is possible.
end
end
end
end
::LOOP_BREAK::
-- Clear their personal spawn point info
if (global.player_respawns[player_name] ~= nil) then
@ -1273,11 +1291,32 @@ function SecondarySpawn(player, surface)
player.print({ "oarc-no-ungenerated-land-error" })
return
end
-- Add new spawn point for the new surface
SetPlayerRespawn(player.name, surface.name, spawn_position, false) -- Do not reset cooldown
QueuePlayerForDelayedSpawn(player.name, surface.name, spawn_position, spawn_choices.moat, false, nil)
-- TODO: Temporary for space age, buddies get a single spawn point for secondary spawns.
if (spawn_choices.buddy) then
SetPlayerRespawn(spawn_choices.buddy, surface.name, spawn_position, false)
-- Add buddy to the shared spawn?
global.unique_spawns[surface.name][player.name].joiners = { spawn_choices.buddy }
log("Buddy secondary spawn NOT IMPLEMENTED YET!")
end
-- -- If it is a buddy spawn, we need to setup both areas TOGETHER
-- if spawn_choices.buddy then
-- -- The x_offset must be big enough to ensure the spawns DO NOT overlap!
-- local x_offset = (global.ocfg.spawn_general.spawn_radius_tiles * 2)
-- if (spawn_choices.moat) then
-- x_offset = x_offset + 10
-- end
-- local buddy_spawn_position = { x = spawn_position.x + x_offset, y = spawn_position.y }
-- SetPlayerRespawn(spawn_choices.buddy, spawn_choices.surface, buddy_spawn_position, true)
-- end
-- Send them to the holding pen
SafeTeleport(player, game.surfaces[HOLDING_PEN_SURFACE_NAME], {x=0,y=0})
@ -1349,7 +1388,7 @@ end
---@param player_name string
---@return boolean
function PlayerHasDelayedSpawn(player_name)
for _,delayedSpawn in pairs(global.delayed_spawns --[[@as OarcDelayedSpawnsTable]]) do
for _,delayedSpawn in pairs(global.delayed_spawns) do
if (delayedSpawn.playerName == player_name) then
return true
end
@ -1357,13 +1396,13 @@ function PlayerHasDelayedSpawn(player_name)
return false
end
---Get the list of surfaces that are allowed for spawning.
---Get the list of surfaces that are allowed for primary spawning.
---@return string[]
function GetAllowedSurfaces()
---@type string[]
local surfaceList = {}
for surfaceName,allowed in pairs(global.oarc_surfaces --[[@as table<string, boolean>]]) do
if allowed then
for surfaceName, allowed in pairs(global.oarc_surfaces) do
if allowed.primary then
table.insert(surfaceList, surfaceName)
end
end
@ -1395,10 +1434,11 @@ function CreatePlayerForce(force_name)
new_force = game.create_force(force_name)
new_force.share_chart = global.ocfg.gameplay.enable_shared_team_vision
new_force.friendly_fire = global.ocfg.gameplay.enable_friendly_fire
new_force.research_queue_enabled = true
-- SetCeaseFireBetweenAllPlayerForces()
-- SetFriendlyBetweenAllPlayerForces()
ConfigurePlayerForceRelationships(true, true)
ConfigureEnemyForceRelationshipsForNewPlayerForce(new_force)
-- ConfigureEnemyForceRelationshipsForNewPlayerForce(new_force)
else
log("TOO MANY FORCES!!! - CreatePlayerForce()")
return game.forces[global.ocfg.gameplay.main_force_name]
@ -1473,7 +1513,18 @@ SPAWN_TEAM_CHOICE = {
---@alias OarcDelayedSpawnsTable table<string, OarcDelayedSpawn>
---This contains the spawn choices for a player in the spawn menu.
---@alias OarcSpawnChoices { surface: string, team: SpawnTeamChoice, moat: boolean, buddy: string?, distance: integer, host: string?, buddy_team: boolean }
---Class representing the spawn choices for a player in the spawn menu.
---@class OarcSpawnChoices
---@field surface string The surface on which the player wants to spawn.
---@field team SpawnTeamChoice The team choice for the player. Main team or own team.
---@field moat boolean Whether the player wants a moat around their spawn.
---@field buddy string? The buddy player name if the player wants to spawn with a buddy.
---@field distance integer The distance from the center of the map where the player wants to spawn.
---@field host string? The host player name if the player wants to join a shared spawn.
---@field buddy_team boolean Whether the player wants to join a buddy's team. This means both players will be on the same team.
---Table of [OarcSpawnChoices](lua://OarcSpawnChoices) indexed by player name.
---@alias OarcSpawnChoicesTable table<string, OarcSpawnChoices>
---Primary means a player can spawn for the first time on this surface, secondary they can land here and also receive a custom spawn area.
---@alias OarcSurfaceSpawnSetting { primary: boolean, secondary: boolean}

View File

@ -1122,7 +1122,10 @@ function AcceptBuddyRequest(player, requesting_buddy_name)
local requesting_buddy = game.players[requesting_buddy_name]
local surface = game.surfaces[spawn_choices.surface]
global.spawn_choices[player.name].host = nil -- N/A for buddy spawns so clear it.
-- Copy the buddy's spawn choices to the accepting player
spawn_choices.host = nil -- N/A for buddy spawns so clear it.
global.spawn_choices[player.name] = table.deepcopy(spawn_choices)
global.spawn_choices[player.name].buddy = requesting_buddy_name
-- Find coordinates of a good place to spawn
local spawn_position = FindUngeneratedCoordinates(surface, spawn_choices.distance, 3)
@ -1159,12 +1162,12 @@ function AcceptBuddyRequest(player, requesting_buddy_name)
x_offset = x_offset + 10
end
buddySpawn = { x = spawn_position.x + x_offset, y = spawn_position.y }
SetPlayerRespawn(player.name, spawn_choices.surface, spawn_position, true)
SetPlayerRespawn(requesting_buddy_name, spawn_choices.surface, buddySpawn, true)
SetPlayerRespawn(player.name, spawn_choices.surface, buddySpawn, true)
SetPlayerRespawn(requesting_buddy_name, spawn_choices.surface, spawn_position, true)
-- Send the player there
QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, spawn_position, spawn_choices.moat, true, requesting_buddy_name)
QueuePlayerForDelayedSpawn(requesting_buddy_name, spawn_choices.surface, buddySpawn, spawn_choices.moat, true, player.name)
QueuePlayerForDelayedSpawn(player.name, spawn_choices.surface, buddySpawn, spawn_choices.moat, true, requesting_buddy_name)
QueuePlayerForDelayedSpawn(requesting_buddy_name, spawn_choices.surface, spawn_position, spawn_choices.moat, true, player.name)
SendBroadcastMsg({"", {"oarc-buddies-are-joining", requesting_buddy_name, player.name, spawn_choices.surface}, " ", GetGPStext(spawn_choices.surface, spawn_position)})
-- Unlock spawn control gui tab

View File

@ -32,8 +32,8 @@ oarc-mod-enable-secondary-spawns=Enable secondary spawns
oarc-mod-scale-resources-around-spawns=Scale resources around spawns
oarc-mod-modified-enemy-spawning=Scale enemies around spawns
oarc-mod-modified-enemy-easy-evo=Easy enemy evolution
oarc-mod-modified-enemy-medium-evo=Medium enemy evolution
# oarc-mod-modified-enemy-easy-evo=Easy enemy evolution
# oarc-mod-modified-enemy-medium-evo=Medium enemy evolution
oarc-mod-minimum-online-time=Minimum online time
oarc-mod-respawn-cooldown-min=Reset respawn point cooldown
oarc-mod-enable-shared-power=Shared power
@ -54,8 +54,8 @@ oarc-mod-spawn-general-shape=Spawn area shape
oarc-mod-resource-placement-enabled=Starting resource auto placement
oarc-mod-resource-placement-distance-to-edge=Starting resource distance to edge
oarc-mod-resource-placement-angle-offset=Starting resource angle offset
oarc-mod-resource-placement-angle-final=Starting resource angle final
oarc-mod-resource-placement-degrees-offset=Starting resource angle offset
oarc-mod-resource-placement-degrees-final=Starting resource angle final
oarc-mod-resource-placement-vertical-offset=Starting resource vertical offset
oarc-mod-resource-placement-horizontal-offset=Starting resource horizontal offset
oarc-mod-resource-placement-linear-spacing=Starting resource linear spacing
@ -93,8 +93,8 @@ oarc-mod-enable-secondary-spawns=Enabling this will provide players with a secon
oarc-mod-scale-resources-around-spawns=This scales resources around every spawn area so far away spawns aren't immediately next to very rich deposits.
oarc-mod-modified-enemy-spawning=This scales the enemy spawning globally based on the allowed spawn distances to avoid every spawn being surrounded by behemoth worms.
oarc-mod-modified-enemy-easy-evo=This is the maximum evolution that the enemies in the warning zone (closest) to a spawn will be FIXED at to ensure all players have a safe start! The warning zone area can be configured via the custom scenario.
oarc-mod-modified-enemy-medium-evo=This is the maximum evolution that the enemies in the danger zone (next closest) to a spawn will be allowed to reach. Helps ensure players have a safe start! The danger zone area can be configured via the custom scenario.
# oarc-mod-modified-enemy-easy-evo=This is the maximum evolution that the enemies in the warning zone (closest) to a spawn will be FIXED at to ensure all players have a safe start! The warning zone area can be configured via the custom scenario.
# oarc-mod-modified-enemy-medium-evo=This is the maximum evolution that the enemies in the danger zone (next closest) to a spawn will be allowed to reach. Helps ensure players have a safe start! The danger zone area can be configured via the custom scenario.
oarc-mod-minimum-online-time=The minimum time a player must be online before they leave, otherwise their spawn area will be cleaned up.
oarc-mod-respawn-cooldown-min=The minimum time a player must wait before they can change their spawn point to a new location.
oarc-mod-enable-shared-power=This allows players to share their power network with other players. It will create a special power pole at their spawn point that connects to the shared power network wirelessly.
@ -115,8 +115,8 @@ oarc-mod-spawn-general-shape=This is the shape of the spawn area.
oarc-mod-resource-placement-enabled=You should leave this enabled unless you are manually specifying resource placements in the custom scenario!
oarc-mod-resource-placement-distance-to-edge=This is the distance from the edge of the spawn area that resources will be placed. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-angle-offset=This is the starting angle offset (in radians) for the resource placement. At what angle (in radians) do resources start. 0 = east. 3.14 = west. Resources are placed clockwise starting at this angle. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-angle-final=This is the final angle offset (in radians) for the resource placement. At what angle (in radians) do resources end. 0 = east. 3.14 = west. Resources are placed clockwise ending at this angle. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-degrees-offset=This is the starting angle offset (in degrees) for the resource placement. At what angle (in degrees) do resources start. 0 = east. 90 = south. Resources are placed clockwise starting at this angle. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-degrees-final=This is the final angle offset (in degrees) for the resource placement. At what angle (in degrees) do resources end. 0 = east. 90 = south. Resources are placed clockwise ending at this angle. Only applicable for circle/octagon shaped spawns.
oarc-mod-resource-placement-vertical-offset=This is the vertical offset (in tiles) for the resource placement from the top-left of the spawn. Only applicable for square shaped spawns.
oarc-mod-resource-placement-horizontal-offset=This is the horizontal offset (in tiles) for the resource placement from the top-left of the spawn. Only applicable for square shaped spawns.
oarc-mod-resource-placement-linear-spacing=This is the linear spacing (in tiles) between resources. Only applicable for square shaped spawns.

View File

@ -221,9 +221,11 @@ oarc-settings-tab-description=This tab contains the same mod settings in the mod
oarc-settings-tab-text-field-enter-tooltip=[color=red]You must press ENTER after typing in a text field to save the value![/color]
oarc-settings-tab-title-surface=Surface Settings
oarc-settings-tab-surface-checkbox-tooltip=Enabling this will allow custom spawn areas (the main feature of this mod) on this surface. You need at least one of these enabled for the mod to work.
oarc-settings-tab-surface-secondary-checkbox-tooltip=Enabling this will give players custom secondary spawns when they first travel to this surface. [color=red]This feature is currently a work in progress.[/color]
oarc-settings-tab-surface-regrowth-checkbox-tooltip=Enabling this will allow the regrowth and world eater features to work on this surface, if those are enabled.
oarc-settings-tab-surface-column-header=Surface
oarc-settings-tab-surface-spawning-enabled=Custom Spawns
oarc-settings-tab-surface-spawning-enabled=Home
oarc-settings-tab-surface-secondary-enabled=Secondary
oarc-settings-tab-surface-regrowth-enabled=Regrowth
oarc-settings-tab-title-export=Import/Export Settings
oarc-settings-tab-export-button=Export

View File

@ -0,0 +1,7 @@
-- If "enemy-easy" and "enemy-medium" forces still exist, merge them into "enemy" force.
if game.forces["enemy-easy"] then
game.merge_forces("enemy-easy", "enemy")
end
if game.forces["enemy-medium"] then
game.merge_forces("enemy-medium", "enemy")
end

View File

@ -185,24 +185,24 @@ data:extend({
default_value = true,
order = "f2"
},
{
type = "double-setting",
name = "oarc-mod-modified-enemy-easy-evo",
setting_type = "runtime-global",
default_value = 0,
minimum_value = 0,
maximum_value = 1,
order = "f21"
},
{
type = "double-setting",
name = "oarc-mod-modified-enemy-medium-evo",
setting_type = "runtime-global",
default_value = 0.3,
minimum_value = 0,
maximum_value = 1,
order = "f22"
},
-- {
-- type = "double-setting",
-- name = "oarc-mod-modified-enemy-easy-evo",
-- setting_type = "runtime-global",
-- default_value = 0,
-- minimum_value = 0,
-- maximum_value = 1,
-- order = "f21"
-- },
-- {
-- type = "double-setting",
-- name = "oarc-mod-modified-enemy-medium-evo",
-- setting_type = "runtime-global",
-- default_value = 0.3,
-- minimum_value = 0,
-- maximum_value = 1,
-- order = "f22"
-- },
{
type = "int-setting",
@ -345,21 +345,21 @@ data:extend({
order = "i2"
},
{
type = "double-setting",
name = "oarc-mod-resource-placement-angle-offset",
type = "int-setting",
name = "oarc-mod-resource-placement-degrees-offset",
setting_type = "runtime-global",
default_value = 2.09,
default_value = 120,
minimum_value = 0,
maximum_value = 6.28,
maximum_value = 359,
order = "i3"
},
{
type = "double-setting",
name = "oarc-mod-resource-placement-angle-final",
type = "int-setting",
name = "oarc-mod-resource-placement-degrees-final",
setting_type = "runtime-global",
default_value = 4.18,
default_value = 240,
minimum_value = 0,
maximum_value = 6.28,
maximum_value = 359,
order = "i4"
},
{