mirror of
https://github.com/Oarcinae/FactorioScenarioMultiplayerSpawn.git
synced 2024-12-12 10:13:58 +02:00
4cd397f815
Have a workaround for demolishers that tracks them and destroys them if they are too close to the spawn. Can definitely be abused, but good enough for now. Fixed issue with joiners being teleported unexpectedly when rerolling a secondary spawn. Removed some old commented out code in utils. Tweak default danger radius to be smaller based on feedback. Fix fuild resources and sharing entities not moving when scaling spawn size. Added locale for welcome home ground text.
458 lines
16 KiB
Lua
458 lines
16 KiB
Lua
-- Sep 2024
|
|
-- ____ _____ _____
|
|
-- / __ \ /\ | __ \ / ____|
|
|
-- | | | | / \ | |__) | |
|
|
-- | | | |/ /\ \ | _ /| |
|
|
-- | |__| / ____ \| | \ \| |____
|
|
-- \____/_/ \_\_| \_\\_____|
|
|
|
|
-- Oarc's Separated Spawn MOD V2
|
|
-- I decided to rewrite my old scenario due to the coming changes in Factorio V2.0 and its new Space Age Expansion.
|
|
|
|
-- Change Overview:
|
|
-- Support the scenario "as a mod" ONLY. Scenario merely provides a way to overwrite settings on_init.
|
|
-- Removed a lot of unnecessary feature bloat.
|
|
-- Move text to locale files where possible.
|
|
|
|
-- Major Features:
|
|
-- Core feature allows for a safe, separate spawn area for each player.
|
|
-- Players can choose to spawn with friends (buddy spawn) or join other bases.
|
|
-- Offline protection from enemy attacks.
|
|
-- Chunk cleanup to keep save file size down.
|
|
-- Sharing of electricity and items between players.
|
|
|
|
require("lib/oarc_utils")
|
|
require("lib/config")
|
|
require("lib/config_parser")
|
|
require("lib/regrowth_map")
|
|
require("lib/holding_pen")
|
|
require("lib/separate_spawns")
|
|
require("lib/separate_spawns_guis")
|
|
require("lib/oarc_gui_tabs")
|
|
require("lib/offline_protection")
|
|
require("lib/scaled_enemies")
|
|
require("lib/sharing")
|
|
|
|
-- TODO: Possibly remove this later?
|
|
require("lib/oarc_tests")
|
|
|
|
require("lib/oarc_commands")
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- On Init - Only runs once the first time the game starts
|
|
--------------------------------------------------------------------------------
|
|
script.on_init(function(event)
|
|
|
|
-- If init has already been run, we immediately hard error.
|
|
-- This is NOT supported. Checking for this force is just an easy dumb way to do it.
|
|
if (game.forces[ABANDONED_FORCE_NAME] ~= nil) then
|
|
error("It appears as though this mod is trying to re-run on_init. If you removed the mod and re-added it again, this is NOT supported. You have to rollback to a previous save where the mod was enabled.")
|
|
return
|
|
end
|
|
|
|
ValidateAndLoadConfig()
|
|
RegrowthInit()
|
|
|
|
InitSpawnGlobalsAndForces()
|
|
CreateHoldingPenSurface() -- Must be after init spawn globals?
|
|
|
|
-- Useful for debugging and if players choose not to use the provided empty scenario.
|
|
if remote.interfaces["freeplay"] then
|
|
log("Freeplay interface detected. Disabling various freeplay features now!")
|
|
remote.call("freeplay", "set_skip_intro", true)
|
|
remote.call("freeplay", "set_disable_crashsite", true)
|
|
remote.call("freeplay", "set_created_items", {})
|
|
remote.call("freeplay", "set_respawn_items", {})
|
|
end
|
|
|
|
-- If there are any players that already exist, init them now.
|
|
for _,player in pairs(game.players) do
|
|
SeparateSpawnsInitPlayer(player.index)
|
|
end
|
|
|
|
game.technology_notifications_enabled = false
|
|
end)
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- On Load - Only for setting up some table stuff that shouldn't change during gameplay!
|
|
--------------------------------------------------------------------------------
|
|
script.on_load(function()
|
|
SetupOCFGModKeys()
|
|
end)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- On Configuration Changed - Only runs when the mod configuration changes
|
|
--------------------------------------------------------------------------------
|
|
script.on_configuration_changed(function(data)
|
|
for _,player in pairs(game.players) do
|
|
RecreateOarcGui(player) -- Reset the players GUI
|
|
end
|
|
end)
|
|
|
|
script.on_event(defines.events.on_runtime_mod_setting_changed, function(event)
|
|
if (not StringStartsWith(event.setting, "oarc-mod")) then return end
|
|
RuntimeModSettingChanged(event)
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- Player Events
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_player_created, function(event)
|
|
SeparateSpawnsInitPlayer(event.player_index)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_player_respawned, function(event)
|
|
SeparateSpawnsPlayerRespawned(event)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_player_left_game, function(event)
|
|
SeparateSpawnsPlayerLeft(event)
|
|
end)
|
|
|
|
-- This does NOT do what I expected, it triggers anytime the VIEW changes, not the character moving.
|
|
-- script.on_event(defines.events.on_player_changed_surface, function(event)
|
|
-- SeparateSpawnsPlayerChangedSurface(event)
|
|
-- end)
|
|
|
|
script.on_event(defines.events.on_rocket_launched, function(event)
|
|
log("Rocket launched!")
|
|
log(serpent.block(event))
|
|
end)
|
|
|
|
script.on_event(defines.events.on_player_driving_changed_state, function (event)
|
|
local entity = event.entity
|
|
|
|
--If a player gets in or out of a vehicle, mark the area as safe so we don't delete the vehicle by accident.
|
|
--Only world eater will clean up these chunks over time if it is enabled.
|
|
if storage.ocfg.regrowth.enable_regrowth and (entity ~= nil) then
|
|
RegrowthMarkAreaSafeGivenTilePos(entity.surface.name, entity.position, 1, false)
|
|
end
|
|
|
|
log("Player driving changed state")
|
|
log(serpent.block(event))
|
|
|
|
-- Track the surfaces whenever driving state changes for a cargo-pod ONLY.
|
|
-- This triggers events for surface changes when using the standard space travel method.
|
|
if (entity ~= nil) and (entity.name == "cargo-pod") then
|
|
local player = game.players[event.player_index]
|
|
|
|
-- Check if driving flag is set
|
|
if (player.driving) then
|
|
log("Player is driving a cargo-pod")
|
|
end
|
|
|
|
SeparateSpawnsUpdatePlayerSurface(player, entity.surface.name)
|
|
end
|
|
end)
|
|
|
|
script.on_event(defines.events.on_research_finished, function(event)
|
|
local research = event.research
|
|
-- TODO: Add a non-mod setting to disable this.
|
|
SendBroadcastMsg({"oarc-research-finished", research.force.name, research.localised_name})
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- CUSTOM OARC Events (shown here for demo and logging purposes)
|
|
----------------------------------------
|
|
|
|
---@class OarcCustomEventBase
|
|
---@field mod_name string
|
|
---@field name integer --For custom events, this is the event ID
|
|
---@field tick integer
|
|
|
|
---@class OarcModOnSpawnCreatedEvent: OarcCustomEventBase
|
|
---@field spawn_data OarcUniqueSpawn
|
|
script.on_event("oarc-mod-on-spawn-created", function(event)
|
|
log("EVENT - oarc-mod-on-spawn-created:" .. serpent.block(event --[[@as OarcModOnSpawnCreatedEvent]]))
|
|
end)
|
|
|
|
---@class OarcModOnSpawnRemoveRequestEvent: OarcCustomEventBase
|
|
---@field spawn_data OarcUniqueSpawn
|
|
script.on_event("oarc-mod-on-spawn-remove-request", function(event)
|
|
log("EVENT - oarc-mod-on-spawn-remove-request:" .. serpent.block(event --[[@as OarcModOnSpawnRemoveRequestEvent]]))
|
|
end)
|
|
|
|
---@class OarcModOnPlayerResetEvent: OarcCustomEventBase
|
|
---@field player_index integer
|
|
script.on_event("oarc-mod-on-player-reset", function(event)
|
|
log("EVENT - oarc-mod-on-player-reset:" .. serpent.block(event --[[@as OarcModOnPlayerResetEvent]]))
|
|
end)
|
|
|
|
---@class OarcModOnPlayerSpawnedEvent: OarcCustomEventBase
|
|
---@field player_index integer
|
|
script.on_event("oarc-mod-on-player-spawned", function(event)
|
|
log("EVENT - oarc-mod-on-player-spawned:" .. serpent.block(event --[[@as OarcModOnPlayerSpawnedEvent]]))
|
|
end)
|
|
|
|
---@class OarcModCharacterSurfaceChangedEvent: OarcCustomEventBase
|
|
---@field player_index integer
|
|
---@field old_surface_name string
|
|
---@field new_surface_name string
|
|
script.on_event("oarc-mod-character-surface-changed", function(event)
|
|
log("EVENT - oarc-mod-character-surface-changed:" .. serpent.block(event --[[@as OarcModCharacterSurfaceChangedEvent]]))
|
|
|
|
--This is just here so I don't get lua warnings about unused variables.
|
|
---@type OarcModCharacterSurfaceChangedEvent
|
|
local custom_event = event --[[@as OarcModCharacterSurfaceChangedEvent]]
|
|
|
|
local player = game.players[custom_event.player_index]
|
|
SeparateSpawnsPlayerChangedSurface(player, custom_event.old_surface_name, custom_event.new_surface_name)
|
|
end)
|
|
|
|
-- I raise this event whenever teleporting the player!
|
|
script.on_event(defines.events.script_raised_teleported, function(event)
|
|
log("script_raised_teleported")
|
|
log(serpent.block(event))
|
|
|
|
local entity = event.entity
|
|
if entity.type == "character" and entity.player then
|
|
SeparateSpawnsUpdatePlayerSurface(entity.player, entity.surface.name)
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- Shared chat, so you don't have to type /s
|
|
-- But you do lose your player colors across forces.
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_console_chat, function(event)
|
|
if (storage.ocfg.gameplay.enable_shared_team_chat) then
|
|
if (event.player_index ~= nil) then
|
|
ShareChatBetweenForces(game.players[event.player_index], event.message)
|
|
end
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- On tick events. Stuff that needs to happen at regular intervals.
|
|
-- Delayed events, delayed spawns, ...
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_tick, function(event)
|
|
DelayedSpawnOnTick()
|
|
FadeoutRenderOnTick()
|
|
OnTickNilCharacterTeleportQueue()
|
|
|
|
if storage.ocfg.regrowth.enable_regrowth then
|
|
RegrowthOnTick()
|
|
end
|
|
RegrowthForceRemovalOnTick() -- Allows for abandoned base cleanup without regrowth enabled.
|
|
|
|
if storage.ocfg.gameplay.modified_enemy_spawning then
|
|
RemoveDemolishersInWarningZone(event)
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- Chunk Generation
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_chunk_generated, function(event)
|
|
if storage.ocfg.regrowth.enable_regrowth then
|
|
RegrowthChunkGenerate(event)
|
|
end
|
|
|
|
CreateHoldingPenChunks(event)
|
|
|
|
if storage.ocfg.gameplay.modified_enemy_spawning then
|
|
DowngradeWormsDistanceBasedOnChunkGenerate(event)
|
|
DowngradeAndReduceEnemiesOnChunkGenerate(event)
|
|
end
|
|
|
|
SeparateSpawnsGenerateChunk(event)
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- Radar Scanning
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_sector_scanned, function (event)
|
|
if storage.ocfg.regrowth.enable_regrowth then
|
|
RegrowthSectorScan(event)
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- Surface Generation
|
|
----------------------------------------
|
|
-- This is not called when the default surface "nauvis" is created as it will always exist!
|
|
script.on_event(defines.events.on_surface_created, function(event)
|
|
local surface = game.surfaces[event.surface_index]
|
|
|
|
log("Surface created: " .. surface.name)
|
|
if IsSurfaceBlacklisted(surface.name) then return end
|
|
|
|
SeparateSpawnsSurfaceCreated(event)
|
|
RegrowthSurfaceCreated(event)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_pre_surface_deleted, function(event)
|
|
local surface = game.surfaces[event.surface_index]
|
|
|
|
log("Surface deleted: " .. surface.name)
|
|
if IsSurfaceBlacklisted(surface.name) then return end
|
|
|
|
SeparateSpawnsSurfaceDeleted(event)
|
|
RegrowthSurfaceDeleted(event)
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- Various on "built" events
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_built_entity, function(event)
|
|
if storage.ocfg.regrowth.enable_regrowth then
|
|
RegrowthMarkAreaSafeGivenTilePos(event.entity.surface.name, event.entity.position, 2, false)
|
|
end
|
|
|
|
-- For tracking spidertrons...
|
|
RegrowthOnBuiltEntity(event)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_robot_built_entity, function (event)
|
|
if storage.ocfg.regrowth.enable_regrowth then
|
|
RegrowthMarkAreaSafeGivenTilePos(event.entity.surface.name, event.entity.position, 2, false)
|
|
end
|
|
end)
|
|
|
|
script.on_event(defines.events.on_player_built_tile, function (event)
|
|
if storage.ocfg.regrowth.enable_regrowth then
|
|
for _,v in pairs(event.tiles) do
|
|
RegrowthMarkAreaSafeGivenTilePos(game.surfaces[event.surface_index].name, v.position, 2, false)
|
|
end
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- On script_raised_built. This should help catch mods that
|
|
-- place items that don't count as player_built and robot_built.
|
|
-- Specifically FARL.
|
|
----------------------------------------
|
|
script.on_event(defines.events.script_raised_built, function(event)
|
|
if storage.ocfg.regrowth.enable_regrowth then
|
|
RegrowthMarkAreaSafeGivenTilePos(event.entity.surface.name, event.entity.position, 2, false)
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- On Entity Spawned and On Biter Base Built
|
|
-- This is where I modify biter spawning based on location and other factors.
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_entity_spawned, function(event)
|
|
if (storage.ocfg.gameplay.modified_enemy_spawning) then
|
|
ModifyEnemySpawnsNearPlayerStartingAreas(event)
|
|
end
|
|
end)
|
|
|
|
script.on_event(defines.events.on_biter_base_built, function(event)
|
|
if (storage.ocfg.gameplay.modified_enemy_spawning) then
|
|
ModifyEnemySpawnsNearPlayerStartingAreas(event)
|
|
end
|
|
end)
|
|
|
|
script.on_event(defines.events.on_segment_entity_created, function(event)
|
|
if storage.ocfg.gameplay.modified_enemy_spawning then
|
|
TrackDemolishers(event)
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- On unit group finished gathering
|
|
-- This is where I remove biter waves on offline players
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_unit_group_finished_gathering, function(event)
|
|
if (storage.ocfg.gameplay.enable_offline_protection) then
|
|
OarcModifyEnemyGroup(event)
|
|
end
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- On enemies killed
|
|
-- For coin generation and stuff
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_post_entity_died, function(event)
|
|
if storage.ocfg.coin_generation.enabled then
|
|
CoinsFromEnemiesOnPostEntityDied(event)
|
|
end
|
|
end,
|
|
{{filter="type", type = "unit"}, {filter="type", type = "unit-spawner"}, {filter="type", type = "turret"}})
|
|
|
|
script.on_event(defines.events.on_entity_damaged, function(event)
|
|
if storage.ocfg.gameplay.scale_spawner_damage then
|
|
ApplySpawnerDamageScaling(event)
|
|
end
|
|
end,
|
|
{{filter="type", type = "unit-spawner"}})
|
|
|
|
----------------------------------------
|
|
-- Gui Events
|
|
----------------------------------------
|
|
script.on_event(defines.events.on_gui_click, function(event)
|
|
if not event.element.valid then return end
|
|
|
|
SeparateSpawnsGuiClick(event)
|
|
OarcGuiTabsClick(event)
|
|
end)
|
|
|
|
--- Called when LuaGuiElement checked state is changed (related to checkboxes and radio buttons).
|
|
script.on_event(defines.events.on_gui_checked_state_changed, function (event)
|
|
if not event.element.valid then return end
|
|
|
|
SeparateSpawnsGuiCheckedStateChanged(event)
|
|
OarcGuiTabsCheckedStateChanged(event)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_gui_selected_tab_changed, function (event)
|
|
if not event.element.valid then return end
|
|
|
|
OarcGuiTabsSelectedTabChanged(event)
|
|
end)
|
|
|
|
-- For capturing player escaping custom GUI so we can close it using ESC key.
|
|
script.on_event(defines.events.on_gui_closed, function(event)
|
|
OarcGuiClosed(event)
|
|
end)
|
|
|
|
|
|
--- For sliders and other value changing elements.
|
|
script.on_event(defines.events.on_gui_value_changed, function(event)
|
|
if not event.element.valid then return end
|
|
|
|
SeparateSpawnsGuiValueChanged(event)
|
|
OarcGuiTabsValueChanged(event)
|
|
end)
|
|
|
|
--- For dropdowns and listboxes.
|
|
script.on_event(defines.events.on_gui_selection_state_changed, function(event)
|
|
if not event.element.valid then return end
|
|
|
|
SeparateSpawnsGuiSelectionStateChanged(event)
|
|
OarcGuiTabsSelectionStateChanged(event)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_gui_text_changed, function(event)
|
|
if not event.element.valid then return end
|
|
|
|
OarcGuiTabsTextChanged(event)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_gui_confirmed, function(event)
|
|
if not event.element.valid then return end
|
|
|
|
OarcGuiTabsConfirmed(event)
|
|
end)
|
|
|
|
script.on_event(defines.events.on_gui_elem_changed, function(event)
|
|
if not event.element.valid then return end
|
|
|
|
OarcGuiTabsElemChanged(event)
|
|
end)
|
|
|
|
----------------------------------------
|
|
-- Remote Interface
|
|
----------------------------------------
|
|
local oarc_mod_interface =
|
|
{
|
|
get_mod_settings = function()
|
|
return storage.ocfg
|
|
end
|
|
}
|
|
|
|
remote.add_interface("oarc_mod", oarc_mod_interface) |