You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

590 lines
25 KiB

local SpaceshipClamp = {}
SpaceshipClamp.name_spaceship_clamp_keep = mod_prefix .. "spaceship-clamp"
SpaceshipClamp.name_spaceship_clamp_place = mod_prefix .. "spaceship-clamp-place"
SpaceshipClamp.name_spaceship_clamp_internal_power_pole = mod_prefix .. "spaceship-clamp-power-pole-internal"
SpaceshipClamp.name_spaceship_clamp_external_power_pole_east = mod_prefix .. "spaceship-clamp-power-pole-external-east"
SpaceshipClamp.name_spaceship_clamp_external_power_pole_west = mod_prefix .. "spaceship-clamp-power-pole-external-west"
SpaceshipClamp.internal_power_pole_offsets = {
[defines.direction.east] = { x = 0.35, y = 0},
[defines.direction.west] = { x = -0.35, y = 0}
}
SpaceshipClamp.external_power_pole_names = {
[defines.direction.east] = SpaceshipClamp.name_spaceship_clamp_external_power_pole_east,
[defines.direction.west] = SpaceshipClamp.name_spaceship_clamp_external_power_pole_west
}
SpaceshipClamp.external_power_pole_offsets = {
[defines.direction.east] = { x = -0.75, y = -0.5},
[defines.direction.west] = { x = 0.75, y = -0.5}
}
SpaceshipClamp.other_clamp_offsets = {
[defines.direction.east] = { x = 2, y = 0},
[defines.direction.west] = { x = -2, y = 0}
}
SpaceshipClamp.signal_for_disable = {type = "virtual", name = "signal-red"}
---@class SpaceshipClamp All data necessary to maintain the state of a spaceship clamp
--- Gets the SpaceshipClamp for this unit_number
---@param unit_number number
function SpaceshipClamp.from_unit_number (unit_number)
if not unit_number then Log.trace("SpaceshipClamp.from_unit_number: invalid unit_number: nil") return end
unit_number = tonumber(unit_number)
-- NOTE: only supports container as the entity
if global.spaceship_clamps[unit_number] then
return global.spaceship_clamps[unit_number]
else
Log.trace("SpaceshipClamp.from_unit_number: invalid unit_number: " .. unit_number)
end
end
--- Gets the SpaceshipClamp for this entity
---@param entity LuaEntity
function SpaceshipClamp.from_entity (entity)
if not(entity and entity.valid) then
Log.trace("SpaceshipClamp.from_entity: invalid entity")
return
end
-- NOTE: only suppors container as the entity
return SpaceshipClamp.from_unit_number(entity.unit_number)
end
--[[========================================================================================
Lifecycle methods for maintaining state of composite entity
]]--
function SpaceshipClamp.get_make_internal_power_pole(entity)
local offset = SpaceshipClamp.internal_power_pole_offsets[entity.direction]
if not offset then return Log.trace('could not get offset for clamp external power pole because the direction of ' .. entity.direction .. ' does not match') end
local power_pole_position = util.vectors_add(entity.position, offset)
local power_pole = entity.surface.find_entity(SpaceshipClamp.name_spaceship_clamp_internal_power_pole, power_pole_position)
if power_pole then
return power_pole
end
local power_pole_ghosts = entity.surface.find_entities_filtered{
ghost_name = SpaceshipClamp.name_spaceship_clamp_internal_power_pole,
position = power_pole_position}
-- take the first one, delete the rest
for _, power_pole_ghost in pairs(power_pole_ghosts) do
if power_pole_ghost.valid then
if not power_pole then
local collisions, power_pole = power_pole_ghosts[1].revive({})
if power_pole_ghost.valid and not power_pole then
power_pole_ghost.destroy()
end
else
power_pole_ghost.destroy()
end
end
end
if power_pole then
return power_pole
end
power_pole = entity.surface.create_entity{
name = SpaceshipClamp.name_spaceship_clamp_internal_power_pole,
position = power_pole_position,
force = entity.force
}
return power_pole
end
function SpaceshipClamp.delete_internal_power_pole(entity)
local power_poles = entity.surface.find_entities_filtered{
name = SpaceshipClamp.name_spaceship_clamp_internal_power_pole,
area = entity.bounding_box
}
for _, power_pole in pairs(power_poles) do
if power_pole.valid then
power_pole.destroy()
end
end
local power_pole_ghosts = entity.surface.find_entities_filtered{
ghost_name = SpaceshipClamp.name_spaceship_clamp_internal_power_pole,
area = entity.bounding_box
}
for _, power_pole_ghost in pairs(power_pole_ghosts) do
if power_pole_ghost.valid then
power_pole_ghost.destroy()
end
end
end
function SpaceshipClamp.get_make_external_power_pole(entity)
local offset = SpaceshipClamp.external_power_pole_offsets[entity.direction]
if not offset then return Log.trace('could not get offset for clamp external power pole because the direction of ' .. entity.direction .. ' does not match') end
local name = SpaceshipClamp.external_power_pole_names[entity.direction]
local power_pole_position = util.vectors_add(entity.position, offset)
local invalid_pole = nil
-- try to find the power pole *anywhere* inside the main entity
-- because it is not centered on the main entity and the entity can be rotated
local power_poles = entity.surface.find_entities_filtered{
name = {SpaceshipClamp.name_spaceship_clamp_external_power_pole_east, SpaceshipClamp.name_spaceship_clamp_external_power_pole_west},
area = entity.bounding_box
}
for _, power_pole in pairs(power_poles) do
if power_pole.valid and util.area_contains_position(entity.bounding_box, power_pole.position) then
-- if the name is correct for the direction, reuse as-is
if power_pole.name == name and util.position_equal(power_pole.position, power_pole_position) then
return power_pole
else
-- otherwise the circuit connections have to be recorded, the entity deleted
-- the correct direction entity created, and circuit connections restored
invalid_pole = power_pole
end
end
end
-- try to find the power pole ghost *anywhere* inside the main entity
-- because it is not centered on the main entity and the entity can be rotated
local power_pole_ghosts = entity.surface.find_entities_filtered{
ghost_name = {SpaceshipClamp.name_spaceship_clamp_external_power_pole_east, SpaceshipClamp.name_spaceship_clamp_external_power_pole_west},
area = entity.bounding_box
}
if not invalid_pole then
for _, power_pole_ghost in pairs(power_pole_ghosts) do
if power_pole_ghost.valid and util.area_contains_position(entity.bounding_box, power_pole_ghost.position) then
local collisions, power_pole = power_pole_ghost.revive({})
if power_pole then
-- if the name is correct for the direction, reuse as-is
if power_pole.name == name and util.position_equal(power_pole.position, power_pole_position) then
return power_pole
else
-- otherwise the circuit connections have to be recorded, the entity deleted
-- the correct direction entity created, and circuit connections restored
invalid_pole = power_pole
end
end
end
end
end
-- it's not anywhere inside the clamp so make a new one
local power_pole = entity.surface.create_entity{
name = name,
position = power_pole_position,
force = entity.force
}
-- if there was an invalid clamp placement, copy the circuit connections, and destroy the bad pole
if invalid_pole then
-- I couldn't figure out how to copy the circuit connections but someone else is welcome to try
invalid_pole.destroy()
end
return power_pole
end
function SpaceshipClamp.delete_external_power_pole(entity)
local power_poles = entity.surface.find_entities_filtered{
name = {SpaceshipClamp.name_spaceship_clamp_external_power_pole_east, SpaceshipClamp.name_spaceship_clamp_external_power_pole_west},
area = entity.bounding_box
}
for _, power_pole in pairs(power_poles) do
if power_pole.valid then
power_pole.destroy()
end
end
local power_pole_ghosts = entity.surface.find_entities_filtered{
ghost_name = {SpaceshipClamp.name_spaceship_clamp_external_power_pole_east, SpaceshipClamp.name_spaceship_clamp_external_power_pole_west},
area = entity.bounding_box
}
for _, power_pole_ghost in pairs(power_pole_ghosts) do
if power_pole_ghost.valid then
power_pole_ghost.destroy()
end
end
end
function SpaceshipClamp.on_entity_created(event)
local entity = util.get_entity_from_event(event)
if not entity then return end
local entity_name = entity.name
if entity_name == SpaceshipClamp.name_spaceship_clamp_place or entity_name == SpaceshipClamp.name_spaceship_clamp_keep then
if entity.position.x % 2 ~= 1 or entity.position.y % 2 ~= 1 then
-- TODO: Add locale.
SpaceshipClamp.delete_internal_power_pole(entity)
SpaceshipClamp.delete_external_power_pole(entity)
return cancel_entity_creation(entity, event.player_index, {"space-exploration.clamp_must_be_on_grid"})
end
-- find spaceship at tile
local direction
if entity_name == SpaceshipClamp.name_spaceship_clamp_place then
direction = (entity.direction == defines.direction.east or entity.direction == defines.direction.west ) and defines.direction.west or defines.direction.east
else
direction = (entity.direction == defines.direction.west or entity.direction == defines.direction.north ) and defines.direction.west or defines.direction.east
entity.direction = direction
end
local surface = entity.surface
local check_positions = {}
if direction == defines.direction.west then
table.insert(check_positions, util.vectors_add(entity.position, {x=0, y=-1})) -- top right over
table.insert(check_positions, util.vectors_add(entity.position, {x=0, y=0})) -- bottom right behind
else
table.insert(check_positions, util.vectors_add(entity.position, {x=-1, y=-1})) -- top left over
table.insert(check_positions, util.vectors_add(entity.position, {x=-1, y=0})) -- bottom left behind
end
local space_tiles = 0
for _, pos in pairs(check_positions) do
if tile_is_space(surface.get_tile(pos)) then
space_tiles = space_tiles + 1
Spaceship.flash_tile(surface, pos, {r=255,g=0,b=0, a = 0.25}, 10)
end
end
if space_tiles >= 1 then
SpaceshipClamp.delete_internal_power_pole(entity)
SpaceshipClamp.delete_external_power_pole(entity)
cancel_entity_creation(entity, event.player_index, {"space-exploration.clamp_invalid_empty_space"})
return
end
local force_name = entity.force.name
local keep
if entity_name == SpaceshipClamp.name_spaceship_clamp_place then
keep = entity.surface.create_entity{
name = SpaceshipClamp.name_spaceship_clamp_keep,
position = entity.position,
force = entity.force,
direction = direction
}
entity.destroy()
else
keep = entity
end
keep.rotatable = false
local spaceship_clamp = {
force_name = force_name,
unit_number = keep.unit_number,
main = keep,
surface_name = keep.surface.name
}
global.spaceship_clamps[spaceship_clamp.unit_number] = spaceship_clamp
global.spaceship_clamps_by_surface[spaceship_clamp.surface_name] = global.spaceship_clamps_by_surface[spaceship_clamp.surface_name] or {}
global.spaceship_clamps_by_surface[spaceship_clamp.surface_name][spaceship_clamp.unit_number] = spaceship_clamp
Log.trace("SpaceshipClamp: spaceship_clamp added")
-- spawn internal power pole
spaceship_clamp.internal_power_pole = SpaceshipClamp.get_make_internal_power_pole(keep)
spaceship_clamp.internal_power_pole.destructible = false
-- spawn external power pole
spaceship_clamp.external_power_pole = SpaceshipClamp.get_make_external_power_pole(keep)
spaceship_clamp.external_power_pole.destructible = false
-- connect the internal poles
spaceship_clamp.internal_power_pole.connect_neighbour({
wire = defines.wire_type.red,
target_entity = spaceship_clamp.external_power_pole
})
spaceship_clamp.internal_power_pole.connect_neighbour({
wire = defines.wire_type.green,
target_entity = spaceship_clamp.external_power_pole
})
spaceship_clamp.internal_power_pole.connect_neighbour(spaceship_clamp.external_power_pole)
if entity_name == SpaceshipClamp.name_spaceship_clamp_place then
local id = SpaceshipClamp.find_unique_clamp_id(direction, keep.surface, keep)
local comb = keep.get_or_create_control_behavior()
if direction == defines.direction.west then
comb.set_signal(1, {signal={type="virtual", name=mod_prefix.."anchor-using-left-clamp"}, count=id})
else
comb.set_signal(1, {signal={type="virtual", name=mod_prefix.."anchor-using-right-clamp"}, count=id})
end
end
-- attempt to connect this clamp to the one next to it
SpaceshipClamp.attempt_connect_clamp(keep)
end
end
Event.addListener(defines.events.on_entity_cloned, SpaceshipClamp.on_entity_created)
Event.addListener(defines.events.on_built_entity, SpaceshipClamp.on_entity_created)
Event.addListener(defines.events.on_robot_built_entity, SpaceshipClamp.on_entity_created)
Event.addListener(defines.events.script_raised_built, SpaceshipClamp.on_entity_created)
Event.addListener(defines.events.script_raised_revive, SpaceshipClamp.on_entity_created)
function SpaceshipClamp.destroy_sub(spaceship_clamp, key)
if spaceship_clamp[key] and spaceship_clamp[key].valid then
spaceship_clamp[key].destroy()
spaceship_clamp[key] = nil
end
end
function SpaceshipClamp.destroy(spaceship_clamp)
if not spaceship_clamp then
Log.trace("spaceship_clamp_destroy: not spaceship_clamp")
return
end
spaceship_clamp.valid = false
SpaceshipClamp.destroy_sub(spaceship_clamp, 'main')
SpaceshipClamp.destroy_sub(spaceship_clamp, 'internal_power_pole')
SpaceshipClamp.destroy_sub(spaceship_clamp, 'external_power_pole')
global.spaceship_clamps[spaceship_clamp.unit_number] = nil
global.spaceship_clamps_by_surface[spaceship_clamp.surface_name][spaceship_clamp.unit_number] = nil
end
function SpaceshipClamp.on_entity_removed(event)
local entity = event.entity
if entity and entity.valid then
if entity.name == SpaceshipClamp.name_spaceship_clamp_keep then
SpaceshipClamp.destroy(SpaceshipClamp.from_entity(entity))
elseif entity.type == "entity-ghost" and entity.ghost_name == SpaceshipClamp.name_spaceship_clamp_keep then
Log.trace("remove")
SpaceshipClamp.delete_internal_power_pole(entity)
SpaceshipClamp.delete_external_power_pole(entity)
end
end
end
Event.addListener(defines.events.on_entity_died, SpaceshipClamp.on_entity_removed)
Event.addListener(defines.events.on_robot_mined_entity, SpaceshipClamp.on_entity_removed)
Event.addListener(defines.events.on_player_mined_entity, SpaceshipClamp.on_entity_removed)
Event.addListener(defines.events.script_raised_destroy, SpaceshipClamp.on_entity_removed)
--- Reconnects the internal_power_pole to the external_power_pole
---@param spaceship_clamp any
function SpaceshipClamp.attempt_validate_internal_poles(spaceship_clamp)
if spaceship_clamp and spaceship_clamp.main and spaceship_clamp.main.valid
and spaceship_clamp.external_power_pole and spaceship_clamp.external_power_pole.valid
and spaceship_clamp.internal_power_pole and spaceship_clamp.internal_power_pole.valid then
spaceship_clamp.internal_power_pole.connect_neighbour({
wire = defines.wire_type.red,
target_entity = spaceship_clamp.external_power_pole
})
spaceship_clamp.internal_power_pole.connect_neighbour({
wire = defines.wire_type.green,
target_entity = spaceship_clamp.external_power_pole
})
spaceship_clamp.internal_power_pole.connect_neighbour(spaceship_clamp.external_power_pole)
end
end
--- Attempts to connect a clamp to the other clamp it is anchored to
--- Also reconnects each clamps internal power poles in case the player
--- managed to disconnect them by accident (for example shift clicking the external power pole)
---@param entity any
function SpaceshipClamp.attempt_connect_clamp(entity)
local spaceship_clamp = SpaceshipClamp.from_entity(entity)
SpaceshipClamp.attempt_validate_internal_poles(spaceship_clamp)
local offset = SpaceshipClamp.other_clamp_offsets[entity.direction]
if not offset then return Log.trace('could not get offset for clamp external power pole because the direction of ' .. entity.direction .. ' does not match') end
local other_clamp_position = util.vectors_add(spaceship_clamp.main.position, offset)
local other_clamp_entity = spaceship_clamp.main.surface.find_entity(SpaceshipClamp.name_spaceship_clamp_keep, other_clamp_position)
if not other_clamp_entity then return end
local other_clamp = SpaceshipClamp.from_entity(other_clamp_entity)
if other_clamp then
SpaceshipClamp.attempt_validate_internal_poles(other_clamp)
local external_power_pole = spaceship_clamp.external_power_pole
local other_external_power_pole = other_clamp.external_power_pole
if external_power_pole and external_power_pole.valid and other_external_power_pole and other_external_power_pole.valid then
external_power_pole.disconnect_neighbour(other_external_power_pole)
end
local internal_power_pole = spaceship_clamp.internal_power_pole
local other_internal_power_pole = other_clamp.internal_power_pole
if internal_power_pole and internal_power_pole.valid and other_internal_power_pole and other_internal_power_pole.valid then
spaceship_clamp.internal_power_pole.connect_neighbour({
wire = defines.wire_type.red,
target_entity = other_clamp.internal_power_pole
})
spaceship_clamp.internal_power_pole.connect_neighbour({
wire = defines.wire_type.green,
target_entity = other_clamp.internal_power_pole
})
spaceship_clamp.internal_power_pole.connect_neighbour(other_clamp.internal_power_pole)
end
end
end
function SpaceshipClamp.is_clamp_enabled(spaceship_clamp)
if not spaceship_clamp.main.valid then return false end
local red_network = spaceship_clamp.main.get_circuit_network(defines.wire_type.red)
local green_network = spaceship_clamp.main.get_circuit_network(defines.wire_type.green)
local red_signal_count = 0
if red_network then
red_signal_count = red_signal_count + red_network.get_signal(SpaceshipClamp.signal_for_disable)
end
if green_network then
red_signal_count = red_signal_count + green_network.get_signal(SpaceshipClamp.signal_for_disable)
end
return red_signal_count <= 0
end
function SpaceshipClamp.find_matches(spaceship_clamps, destination_clamps, using_id, to_id, using_direction, to_direction)
-- find a *single* clamp on the spaceship that matches the ID
local matching_spaceship_clamp
for _, spaceship_clamp in pairs(spaceship_clamps) do
if spaceship_clamp.main.valid then
if spaceship_clamp.main.direction == using_direction then
local comb = spaceship_clamp.main.get_or_create_control_behavior()
local signal = comb.get_signal(1)
if signal and signal.count == using_id then
matching_spaceship_clamp = spaceship_clamp.main
end
end
else
SpaceshipClamp.destroy(spaceship_clamp)
end
end
-- find *all* clamps at the destination that match the ID *and* are empty,
-- empty clamps being defined as not having another clamp docked next to it
local matching_destination_clamps = {}
for _, destination_clamp in pairs(destination_clamps) do
if destination_clamp.main.valid then
if destination_clamp.main.direction == to_direction then
local comb = destination_clamp.main.get_or_create_control_behavior()
local signal = comb.get_signal(1)
if signal and signal.count == to_id then
local offset = SpaceshipClamp.other_clamp_offsets[destination_clamp.main.direction]
-- check for another clamp already docked
local other_clamp_position = util.vectors_add(destination_clamp.main.position, offset)
local other_clamp_entity = destination_clamp.main.surface.find_entity(SpaceshipClamp.name_spaceship_clamp_keep, other_clamp_position)
local other_clamp
if other_clamp_entity then other_clamp = SpaceshipClamp.from_entity(other_clamp_entity) end
if not other_clamp then
-- check for signal to switch clamp off.
if SpaceshipClamp.is_clamp_enabled(destination_clamp) then
table.insert(matching_destination_clamps, destination_clamp.main)
end
end
end
end
else
SpaceshipClamp.destroy(destination_clamp)
end
end
return matching_spaceship_clamp, matching_destination_clamps
end
function SpaceshipClamp.attempt_anchor_spaceship(spaceship, red, green)
local using_left = util.signal_from_wires(red, green, Spaceship.signal_for_anchor_using_left)
local to_right = util.signal_from_wires(red, green, Spaceship.signal_for_anchor_to_right)
local using_right = util.signal_from_wires(red, green, Spaceship.signal_for_anchor_using_right)
local to_left = util.signal_from_wires(red, green, Spaceship.signal_for_anchor_to_left)
local try_anchor_using_left = using_left ~= 0 and to_right ~= 0
local try_anchor_using_right = using_right ~= 0 and to_left ~= 0
if not try_anchor_using_left and not try_anchor_using_right then return end
local spaceship_surface = spaceship.console.surface
local destination_surface = Zone.get_surface(Zone.from_zone_index(spaceship.destination.index))
if not destination_surface then return end
local spaceship_clamps = global.spaceship_clamps_by_surface[spaceship_surface.name]
local destination_clamps = global.spaceship_clamps_by_surface[destination_surface.name]
if not spaceship_clamps or not destination_clamps then return end
if try_anchor_using_left then
local matching_spaceship_clamp, matching_destination_clamps =
SpaceshipClamp.find_matches(spaceship_clamps, destination_clamps, using_left, to_right, defines.direction.west, defines.direction.east)
if matching_spaceship_clamp and #matching_destination_clamps > 0 then
-- try to land based on the relative offsets
local position = {
x = -1 * matching_spaceship_clamp.position.x + 2,
y = -1 * matching_spaceship_clamp.position.y
}
position = util.vectors_add(position, matching_destination_clamps[1].position)
Spaceship.land_at_position(spaceship, position, true)
return
end
end
if try_anchor_using_right then
local matching_spaceship_clamp, matching_destination_clamps =
SpaceshipClamp.find_matches(spaceship_clamps, destination_clamps, using_right, to_left, defines.direction.east, defines.direction.west)
if matching_spaceship_clamp and #matching_destination_clamps > 0 then
-- try to land based on the relative offsets
local position = {
x = -1 * matching_spaceship_clamp.position.x - 2,
y = -1 * matching_spaceship_clamp.position.y
}
position = util.vectors_add(position, matching_destination_clamps[1].position)
Spaceship.land_at_position(spaceship, position, true)
return
end
end
end
--- Finds an id value that is unused by any clamp on the given surface
---@param direction any direction of clamp
---@param surface LuaSurface surface on which to find the unique id
---@param exclude_clamp LuaEntity entity to exlude from search
function SpaceshipClamp.find_unique_clamp_id(direction, surface, exclude_clamp)
local spaceship_clamps = global.spaceship_clamps_by_surface[surface.name]
local used_ids = {}
for _, spaceship_clamp in pairs(spaceship_clamps) do
local entity = spaceship_clamp.main
if entity.valid then
if entity ~= exclude_clamp and entity.direction == direction then
local value = 0
local comb = entity.get_or_create_control_behavior()
local signal = comb.get_signal(1)
if signal then value = signal.count end
used_ids[value] = value
end
else
SpaceshipClamp.destroy(spaceship_clamp)
end
end
local i = 1
while used_ids[i] do
i = i + 1
end
return i
end
--- Validates the type of a clamp signal
---@param entity LuaEntity
function SpaceshipClamp.validate_clamp_signal(spaceship_clamp_entity)
-- make sure it still has the correct signal.
local value = 1
local comb = spaceship_clamp_entity.get_or_create_control_behavior()
local signal = comb.get_signal(1)
if signal then value = signal.count end
if spaceship_clamp_entity.direction == defines.direction.west then
comb.set_signal(1, {signal={type="virtual", name=mod_prefix.."anchor-using-left-clamp"}, count=value})
else
comb.set_signal(1, {signal={type="virtual", name=mod_prefix.."anchor-using-right-clamp"}, count=value})
end
end
--- GUI closed so need to validate clamp signal
---@param event any
function SpaceshipClamp.on_gui_closed(event)
if event.entity and event.entity.valid and event.entity.name == SpaceshipClamp.name_spaceship_clamp_keep then
SpaceshipClamp.validate_clamp_signal(event.entity)
end
end
Event.addListener(defines.events.on_gui_closed, SpaceshipClamp.on_gui_closed)
--- Combinator settings pasted so need to valdiate clamp signal
---@param event any
function SpaceshipClamp.on_entity_settings_pasted(event)
if event.destination and event.destination.valid and event.destination.name == SpaceshipClamp.name_spaceship_clamp_keep then
SpaceshipClamp.validate_clamp_signal(event.destination)
end
end
Event.addListener(defines.events.on_entity_settings_pasted, SpaceshipClamp.on_entity_settings_pasted)
function SpaceshipClamp.on_init(event)
global.spaceship_clamps = {}
global.spaceship_clamps_by_surface = {}
end
Event.addListener("on_init", SpaceshipClamp.on_init, true)
return SpaceshipClamp