Files
factorio-scenario-ExpCluster/exp_legacy/module/modules/control/warps.lua
2024-11-19 22:36:52 +00:00

489 lines
16 KiB
Lua

--[[-- Control Module - Warps
- Stores warps for each force.
@control Warps
@alias Warps
@usage-- Making a new spawn warp
local player = game.player
local force = player.force
local spawn_id = Warps.add_warp(force.name, player.physical_surface, player.physical_position, player.name, 'Spawn')
Warps.set_spawn_warp(spawn_id, force)
Warps.make_warp_tag(spawn_id)
@usage-- Making a new warp with a warp area
local player = game.player
local force = player.force
local warp_id = Warps.add_warp(force.name, player.physical_surface, player.physical_position, player.name)
Warps.make_warp_area(warp_id)
Warps.make_warp_tag(warp_id)
]]
local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore
local Storage = require("modules/exp_util/storage")
local config = require("modules.exp_legacy.config.gui.warps") --- @dep config.warps
--- Stores all data for the warp system
local WrapData = Datastore.connect("WrapData")
WrapData:set_serializer(function(raw_key) return raw_key.warp_id end)
local Warps = {}
--- @class ExpScenario_Warps.force_warps: { spawn: string, [number]: string }
-- Storage lookup table for force name to task ids
local force_warps = { _uid = 1 }
--- @cast force_warps table<string, ExpScenario_Warps.force_warps>
Storage.register(force_warps, function(tbl)
force_warps = tbl
end)
-- Create an array of entity names that will be added to the remove filter
local remove_warp_area_entity_names = {}
for _, entity in pairs(config.entities) do
table.insert(remove_warp_area_entity_names, entity[1])
end
-- When a warp is updated change its chat tag and restore the warp order
WrapData:on_update(function(warp_id, warp, old_warp)
if warp then
warp.updates = warp.updates + 1
-- Update the map chart tag if there is one
if warp.tag then
Warps.make_warp_tag(warp_id)
end
-- Check that the name of the warp has been changed
if not old_warp or warp.name == old_warp.name then return end
-- Get the names of all the warp points for this force
local force_name = warp.force_name
local warp_ids = force_warps[force_name]
local spawn_id = warp_ids.spawn
local warp_names = {} --- @type table<string, string>
for _, next_warp_id in pairs(warp_ids) do
local next_warp = WrapData:get(next_warp_id)
if next_warp_id ~= spawn_id then
warp_names[next_warp.name .. next_warp_id] = next_warp_id
end
end
-- Sort the warp names in alphabetical order
local new_warp_ids = table.get_values(table.key_sort(warp_names))
--- @cast new_warp_ids ExpScenario_Warps.force_warps
table.insert(new_warp_ids, 1, spawn_id)
new_warp_ids.spawn = spawn_id
force_warps[force_name] = new_warp_ids
end
end)
--- Map Integration.
-- functions used to create and alter warps with in the map
-- @section mapIntegration
--[[-- Add or update the chat tag for this warp
@tparam string warp_id the uid of the warp you want the chart tag for
@treturn boolean true if a new tag was made, false if it was updated
@usage-- Adding a chart tag for a new warp
local tag_added = Warps.make_warp_tag(warp_id)
]]
function Warps.make_warp_tag(warp_id)
local warp = WrapData:get(warp_id)
local name = warp.name
local icon = warp.icon
-- Edit the existing tag if it is present
local tag = warp.tag
if tag and tag.valid then
tag.text = "Warp: " .. name
tag.icon = icon
return false
end
-- Make a new tag if one did not exist
local force = game.forces[warp.force_name]
local surface = warp.surface
local position = warp.position
tag = force.add_chart_tag(surface, {
position = { position.x + 0.5, position.y + 0.5 },
text = "Warp: " .. name,
icon = icon,
})
-- Add the tag to this warp, store.update not needed as we dont want it to trigger
warp.tag = tag
return true
end
--[[-- Remove the chart tag for this warp
@tparam string warp_id the uid for the warp that you want to remove the chart tag from
@treturn boolean true if the tag was valid and was removed, false if the tag was invalid
@usage-- Removing the chart tag from a warp
local removed = Warps.remove_warp_tag(warp_id)
]]
function Warps.remove_warp_tag(warp_id)
local warp = WrapData:get(warp_id)
-- Check there is a tag to remove
local tag = warp.tag
if not tag or not tag.valid then
warp.tag = nil
return false
end
-- Remove the warp chart tag if it is valid
tag.destroy()
warp.tag = nil
return true
end
--[[-- Add a warp area for the warp, purely cosmetic
@tparam string warp_id the uid of the warp you want the area for
@usage-- Adding a warp area for a warp
Warps.make_warp_area(warp_id)
]]
function Warps.make_warp_area(warp_id)
local warp = WrapData:get(warp_id)
local surface = warp.surface
local position = warp.position
local posx = position.x
local posy = position.y
-- Get the tile that is being replaced, store.update not needed as we don't want it to trigger
local old_tile = surface.get_tile(position).name
warp.old_tile = old_tile
-- Add a tile pattern on top of the base
local tiles = {}
for _, tile in pairs(config.tiles) do
table.insert(tiles, { name = tile[1], position = { tile[2] + posx, tile[3] + posy } })
end
surface.set_tiles(tiles)
-- Add entities to the warp structure
for _, entity in pairs(config.entities) do
entity = surface.create_entity{
name = entity[1],
position = { entity[2] + posx, entity[3] + posy },
force = "neutral",
}
entity.destructible = false
entity.health = 0
entity.minable = false
entity.rotatable = false
-- Save reference of the last power pole
if entity.type == "electric-pole" then
warp.electric_pole = entity
end
end
end
--[[-- Remove the warp area for a warp
@tparam string warp_id the uid of the warp that you want to remove the area for
@usage-- Remove the warp area for a warp
Warps.remove_warp_area(warp_id)
]]
function Warps.remove_warp_area(warp_id)
local warp = WrapData:get(warp_id)
local position = warp.position
local surface = warp.surface
local radius = config.standard_proximity_radius
-- Check that a warp area was created previously
local old_tile = warp.old_tile
if not old_tile then return end
-- Restore the original tiles before the creation of the warp
local tiles = {}
for _, tile in pairs(config.tiles) do
table.insert(tiles, { name = old_tile, position = { tile[2] + position.x, tile[3] + position.y } })
end
surface.set_tiles(tiles)
local area = {
{ position.x - radius, position.y - radius },
{ position.x + radius, position.y + radius },
}
-- Remove warp structure entities
local entities = surface.find_entities_filtered{ force = "neutral", area = area, name = remove_warp_area_entity_names }
for _, entity in pairs(entities) do
-- Destroy them, this will leave corpses of the entities that it destroyed.
if entity and entity.valid and entity.destructible == false then
entity.destructible = true
entity.die(entity.force)
end
end
-- Rechart map area, useful if warp is not covered by a radar
game.forces[warp.force_name].chart(surface, area)
end
--[[-- Set a warp to be the spawn point for a force, force must own this warp
@tparam string warp_id the uid of the warp that you want to be the spawn for the force
@tparam LuaForce force the force that you want to set the spawn for
@usage-- Set your forces spawn to a warp
Warps.set_spawn_warp(warp_id, game.player.force)
]]
function Warps.set_spawn_warp(warp_id, force)
-- Check the force owns this warp
local warp = WrapData:get(warp_id)
if warp.force_name ~= force.name then
return
end
-- Set this warp as the spawn
local warp_ids = force_warps[warp.force_name]
if not warp_ids then
warp_ids = {}
force_warps[warp.force_name] = warp_ids
end
warp_ids.spawn = warp_id
-- Set the forces spawn to this warp
force.set_spawn_position(warp.position, warp.surface)
end
--[[-- Teleport a player to a warp point
@tparam string warp_id the uid of the warp to send the player to
@tparam LuaPlayer player the player to teleport to the warp
@usage-- Teleport yourself to a warp point
Warps.teleport_player(warp_id, game.player)
]]
function Warps.teleport_player(warp_id, player)
local warp = WrapData:get(warp_id)
local surface = warp.surface
local position = {
x = warp.position.x + 0.5,
y = warp.position.y + 0.5,
}
if player.vehicle then
-- Teleport the entity
local entity = player.vehicle
local goto_position = surface.find_non_colliding_position(entity.name, position, 32, 1)
-- Surface teleport can only be done for players and cars at the moment. (with surface as an peramitor it gives this error)
if entity.type == "car" then
entity.teleport(goto_position, surface)
elseif surface.index == entity.surface.index then
-- Try teleport the entity
if not entity.teleport(goto_position) then
player.driving = false
-- Need to calculate new goto_position because entities have different collision boxes
goto_position = surface.find_non_colliding_position("character", position, 32, 1)
player.teleport(goto_position, surface)
end
end
else
-- Teleport the player
local goto_position = surface.find_non_colliding_position("character", position, 32, 1)
if player.driving then player.driving = false end
player.teleport(goto_position, surface)
end
end
--- Setters.
-- functions used to created and alter warps
-- @section setters
--[[-- Add a new warp for a force, the warp must have a surface and a position
@tparam string force_name the name of the force to add the warp for
@tparam LuaSurface surface the surface that the warp will be on
@tparam Concepts.Position position the position that the warp will be on
@tparam[opt] string player_name the name of the player that is adding the warp
@tparam[opt] string warp_name the name of the warp that is being added, if omited default is used
@treturn string the uid of the warp which was created
@usage-- Adding a new warp for your force at your position
local player = game.player
local warp_id = Warps.add_warp(player.force.name, player.physical_surface, player.physical_position, player.name)
]]
function Warps.add_warp(force_name, surface, position, player_name, warp_name)
-- Get new warp id
local warp_id = tostring(force_warps._uid)
force_warps._uid = force_warps._uid + 1
warp_name = warp_name or "New warp"
-- Get the existing warps for this force
local warp_ids = force_warps[force_name]
if not warp_ids then
warp_ids = {}
force_warps[force_name] = warp_ids
end
-- Insert the warp id into the force warps
table.insert(warp_ids, warp_id)
-- Create the editing table
local editing = {}
if player_name then
editing[player_name] = true
end
-- Add the new warp to the store
WrapData:set(warp_id, {
warp_id = warp_id,
force_name = force_name,
name = warp_name,
icon = { type = config.default_icon.type, name = config.default_icon.name },
surface = surface,
position = {
x = math.floor(position.x),
y = math.floor(position.y),
},
last_edit_name = player_name or "<server>",
last_edit_time = game.tick,
currently_editing = editing,
updates = 0,
})
return warp_id
end
--[[-- Removes a warp and any data linked to it
@tparam string warp_id the uid of the warp that you want to remove
@usage-- Removing a warp
Warps.remove_warp(warp_id)
]]
function Warps.remove_warp(warp_id)
local warp = WrapData:get(warp_id)
local force_name = warp.force_name
Warps.remove_warp_tag(warp_id)
Warps.remove_warp_area(warp_id)
WrapData:remove(warp_id)
table.remove_element(force_warps[force_name], warp_id)
end
--[[-- Update the name and icon for a warp
@tparam string warp_id the uid of the warp that you want to update
@tparam[opt] string new_name the new name that you want the warp to have
@tparam[opt] string new_icon the new icon that you want the warp to have
@tparam[opt='server'] string player_name the name of the player that made the edit
@usage-- Changing the name and icon for a warp
Warps.update_warp(warp_id, 'My Warp', 'iron-plate', game.player.name)
]]
function Warps.update_warp(warp_id, new_name, new_icon, player_name)
WrapData:update(warp_id, function(_, warp)
-- If the icon is not valid then replace with the old icon
if new_icon and not new_icon.name or not new_icon.type then
new_icon = warp.icon
end
warp.last_edit_name = player_name or "<server>"
warp.last_edit_time = game.tick
warp.name = new_name or warp.name
warp.icon = new_icon or warp.icon
end)
end
--[[-- Set the editing state for a player, can be used as a warning or to display a text field
@tparam string warp_id the uid of the warp that you want to effect
@tparam string player_name the name of the player you want to set the state for
@tparam boolean state the new state to set editing to
@usage-- Setting your editing state to true
Warps.set_editing(warp_id, game.player.name, true)
]]
function Warps.set_editing(warp_id, player_name, state)
WrapData:update(warp_id, function(_, warp)
warp.currently_editing[player_name] = state
end)
end
--[[-- Adds an update handler for when a warp is added, removed, or updated
@tparam function handler the handler which is called when a warp is updated
@usage-- Add a game print when a warp is updated
Warps.on_update(function(warp)
game.print(warp.force_name..' now has the warp: '..warp.name)
end)
]]
function Warps.on_update(handler)
WrapData:on_update(handler)
end
--- Getters.
-- function used to get information about warps
-- @section getters
--[[-- Gets the warp information that is linked with this id
@tparam string warp_id the uid of the warp you want to get
@treturn table the warp information
@usage-- Getting warp information outside of on_update
local warp = Warps.get_warp(warp_id)
]]
function Warps.get_warp(warp_id)
return WrapData:get(warp_id)
end
--[[-- Gets all the warp ids that a force has
@tparam string force_name the name of the force that you want the warp ids for
@treturn table an array of all the warp ids
@usage-- Getting the warp ids for a force
local warp_ids = Warps.get_force_warp_ids(game.player.force.name)
]]
function Warps.get_force_warp_ids(force_name)
return force_warps[force_name] or {}
end
--[[-- Get the id of the spawn warp
@tparam string force_name the name of the force that you want to get the spawn warp for
@treturn ?string|nil the uid of the spawn warp for this force if there is one
@usage-- Getting the spawn warp id
local spawn_id = Warps.get_spawn_warp_id(game.player.force.name)
]]
function Warps.get_spawn_warp_id(force_name)
local warp_ids = force_warps[force_name] or {}
return warp_ids.spawn
end
--[[-- Gets the editing state for a player
@tparam string warp_id the uid of the warp you want to check
@tparam string player_name the name of the player that you want to check
@treturn boolean weather the player is currently editing this warp
@usage-- Check if a player is editing a warp or not
local editing = Warps.get_editing(warp_id, game.player.name)
]]
function Warps.get_editing(warp_id, player_name)
local warp = WrapData:get(warp_id)
return warp.currently_editing[player_name]
end
-- Module return
return Warps