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.

2635 lines
112 KiB

local Spaceship = {}
Spaceship.names_tech_integrity = {
{name = mod_prefix.."spaceship-integrity", bonus_per_level = 100, infinite = false},
{name = mod_prefix.."factory-spaceship", bonus_per_level = 500, infinite = true}
}
Spaceship.integrity_base = 300
Spaceship.name_spaceship_console = mod_prefix .. "spaceship-console"
Spaceship.name_spaceship_console_output = mod_prefix .. "spaceship-console-output"
Spaceship.console_output_offset = {x = 1.5, y = -1}
Spaceship.engine_efficiency_blocked = 0.60
Spaceship.engine_efficiency_unblocked = 1
Spaceship.engine_efficiency_unblocked_taper = 20
Spaceship.engine_efficiency_side = 0.01
Spaceship.engines = {
[mod_prefix .. "spaceship-rocket-engine"] = { name = mod_prefix .. "spaceship-rocket-engine", thrust = 100 / 5, max_energy = 1837, smoke_trigger = mod_prefix .. "spaceship-engine-smoke" },
[mod_prefix .. "spaceship-ion-engine"] = { name = mod_prefix .. "spaceship-ion-engine", thrust = 250 / 5, max_energy = 183700, smoke_trigger = mod_prefix .. "spaceship-engine-smoke" },
[mod_prefix .. "spaceship-antimatter-engine"]= { name = mod_prefix .. "spaceship-antimatter-engine", thrust = 500 / 5, max_energy = 18370, smoke_trigger = mod_prefix .. "spaceship-engine-smoke" }
}
Spaceship.names_engines = {}
Spaceship.names_smoke_trigger = {}
Spaceship.names_booster_tanks = {
mod_prefix .. "spaceship-rocket-booster-tank",
mod_prefix .. "spaceship-ion-booster-tank",
mod_prefix .. "spaceship-antimatter-booster-tank"
}
Spaceship.ion_stream_energy = 4000000 -- 2x rocket fuel
Spaceship.names_spaceship_floors = {mod_prefix .. "spaceship-floor"}
Spaceship.names_spaceship_walls = {mod_prefix .. "spaceship-wall"}
Spaceship.names_spaceship_gates = {mod_prefix .. "spaceship-gate"}
Spaceship.names_spaceship_bulkheads = {
mod_prefix .. "spaceship-wall",
mod_prefix .. "spaceship-gate",
SpaceshipClamp.name_spaceship_clamp_keep,
}
for _, engine in pairs(Spaceship.engines) do
table.insert(Spaceship.names_engines, engine.name)
table.insert(Spaceship.names_spaceship_bulkheads, engine.name)
if engine.smoke_trigger then
table.insert(Spaceship.names_smoke_trigger, engine.smoke_trigger)
end
end
Spaceship.integrity_affecting_types = {
{type = "linked-container", integrity_stress_container = 1000},
}
Spaceship.integrity_affecting_names = {
{name = mod_prefix.."nexus", integrity_stress_container = 2000},
{mod = "Krastorio2", name = "kr-antimatter-reactor", integrity_stress_structure = 100, integrity_stress_container = 100, max_speed_multiplier = 0.5},
}
Spaceship.signal_for_own_spaceship_id = {type = "item", name = Spaceship.name_spaceship_console}
Spaceship.signal_for_destination_spaceship = {type = "virtual", name = mod_prefix.."spaceship"}
Spaceship.signal_for_speed = {type = "virtual", name = "signal-speed"}
Spaceship.signal_for_distance = {type = "virtual", name = "signal-distance"}
Spaceship.signal_for_launch = {type = "virtual", name = mod_prefix.."spaceship-launch"}
Spaceship.signal_for_anchor_using_left = {type = "virtual", name = mod_prefix.."anchor-using-left-clamp"}
Spaceship.signal_for_anchor_using_right = {type = "virtual", name = mod_prefix.."anchor-using-right-clamp"}
Spaceship.signal_for_anchor_to_left = {type = "virtual", name = mod_prefix.."anchor-to-left-clamp"}
Spaceship.signal_for_anchor_to_right = {type = "virtual", name = mod_prefix.."anchor-to-right-clamp"}
Spaceship.energy_per_launch_integrity_delta_v = 135 * 1000
Spaceship.tick_interval_density = 60 -- must coincide with %60
Spaceship.tick_interval_move = 20 -- must coincide with %60
Spaceship.tick_interval_anchor = 5 -- must coincide with %60
Spaceship.tick_interval_gui = 5 -- must coincide with %60
Spaceship.tick_max_await = 60 * 10 -- 10 seconds
Spaceship.types_to_restore = {-- after surface change/area clone
"inserter",
"pump",
--"transport-belt" -- entity.active does not work on belts
}
-- Note: production machines should NOT be included as some are supposed to be disabled on specific surfaces.
Spaceship.time_to_restore = 1
---@class Spaceship All data necessary to maintain the state of a spaceship
Spaceship.particle_speed_power = 0.75 -- 0.5 would be sqrt, 0 is static, 1 is linear with speed.
Spaceship.space_drag = 0.00135
Spaceship.minimum_impulse = 1/100
Spaceship.minimum_mass = 100
Spaceship.speed_taper = 250
Spaceship.travel_speed_multiplier = 1/200
Spaceship.integrity_pulse_interval = 60 * 60 * 10
Spaceship.tile_status = {}
--[[
change to:
{
outer = 0 or 1. outer skin of tiles including diagonals
floor = 0 or 1, has floor otherwise exterior
exposed = 0 for contained, or higher for any tile exposed to space
wall = 0 or 1, walls only
bulkhead = 0 or 1, any bulkhead
connection nil or distance to console.
}
]]--
Spaceship.tile_status.exterior = 1 -- any tile not with flooring (without bulkhead)
Spaceship.tile_status.wall_exterior = 2 -- bulkeahd outside of flooring
Spaceship.tile_status.bulkhead_exterior = 3 -- bulkeahd outside of flooring
Spaceship.tile_status.floor = 4 -- unknown floor
Spaceship.tile_status.wall = 5 -- unknown bulkhead
Spaceship.tile_status.bulkhead = 6 -- unknown bulkhead
Spaceship.tile_status.floor_exterior = 7 -- outside floor
Spaceship.tile_status.floor_interior = 8 -- contained floor
Spaceship.tile_status.floor_console_disconnected = 9 -- disconnected floor
Spaceship.tile_status.wall_console_disconnected = 10 -- disconnected bulkhead
Spaceship.tile_status.bulkhead_console_disconnected = 11 -- disconnected bulkhead
Spaceship.tile_status.floor_console_connected = 12 -- connected floor
Spaceship.tile_status.wall_console_connected = 13 -- connected bulkhead
Spaceship.tile_status.bulkhead_console_connected = 14 -- connected bulkhead
--[[ tile statuses
1 = exterior
2 = floor_pending (on the edge of checking, used for next tick)
3 = unknown floor (exists but unknown containment statis)
4 = exterior floor
5 = bulkhead (floor with wall or gate)
6 = interior (contained) floor
]]--
--[[
console sends out a pule over all connected spaceship tiles (with a max based on tech)
then consider all tiles with wall or gate.
divide tiles into groups, ones that touch the outside are not part of the ship.
]]--
Spaceship.names = {
"Abaddon", "Ackbar", "Aegis", "Albatross", "Alchemist", "Albion", "Alexander",
"Angler", "Apparition", "ArchAngel", "Assassin", "Avenger", "Axe",
"Bade", "Bardiche", "Battleth", "Blackbird", "Bounty Hunter", "Breaker",
"Brigandine","Bullfinch", "Buzzard",
"Cartographer", "Catface", "Calamari", "Canary", "Caravel", "Carrak", "Citadel", "Clockwerk",
"Chimera", "Coot", "Cormorant", "Crane", "Crossbill", "Crow", "Cuckoo",
"Darkstar", "Dauntless", "Desby", "Dragon", "Drake", "Dream", "Doombringer",
"Dolphin", "Devourer", "Dunn",
"Eagle", "Earthshaker", "Earl Grey", "Egret", "Eider", "Ember", "Enigma", "Eris", "Excalibur",
"Falcon", "Falx", "Feral Pigeon", "Firecrest", "Firefly", "Flying Duckman",
"Fountain", "Fulmar",
"Gadwall", "Gannet", "Garganey", "Gigantosaurus", "Ghast", "Ghoul", "Ghost",
"Glaive", "Goldcrest", "Goldeneye", "Goldfinch", "Goosander", "Goose",
"Goshawk", "Grasshopper", "Greenfinch", "Griffon", "Grouse", "Guillemot",
"Halberd", "Hammer", "Hammerhead", "Harrier", "Hawk", "Harking", "Heron", "Hippogryph", "Honeybadger", "Honeybear",
"Iron Cordon", "Ingot", "Intrepid", "Invoker", "Isabella",
"Jack Snipe", "Jackdaw", "Jay",
"Kamsta", "Katherine", "Kestrel", "Kingfisher", "Kite", "Knight", "Kraken",
"Lapwing", "Lance", "Lancer", "Lick", "Linnet", "Lucas",
"Magi", "Magpie", "Mallard", "Mangonel", "Medusa", "Memento", "Merlin",
"Mistress", "Mocking Jay", "Monstrosity", "Moorhen", "Musk",
"Naga", "Narwhal", "Nebulon", "Nemesis", "Newton", "Nexela", "Nial", "Nicholas",
"Nightjar", "Nissa", "Nightingale", "Night Stalker",
"Oracle", "Orca", "Ostricth", "Outrider", "Owl",
"Partridge", "Pangolin", "Penguin", "Peregrine", "Petrel", "Phantom",
"Pheasant", "Phoenix", "Piccard", "Pintail", "Pioneer",
"Pipit", "Plover", "Prophet", "Prowler", "Pochard", "Puffin",
"Quail",
"Radiance", "Raptor", "Raven", "Razor", "Razorbill", "Red Kite", "Redshank",
"Redstart", "Redwing", "Requiem",
"Riccardo", "Robin", "Roc", "Rook", "Rossi", "Rogue", "Ruff",
"Sanderling", "Sawfish", "Scythe", "Seraph", "Serenity", "Sickle", "Shadow",
"Shag", "Sharknado", "Shelduck", "Sherrif", "Shoveler", "Sin Eater", "Siren",
"Siskin", "Skylark", "Skyshark", "Skywalker", "Skywrath", "Smew", "Snek",
"Snipe", "Sparrowhawk", "Spear", "Spectre", "Spinosaur", "Spynx",
"Starchaser", "Starling", "Stonechat", "Swallow", "Swan", "Swift", "Swordfish",
"Tachyon", "Tali", "Tantive", "Teal", "Templar", "Terrorblade", "Tesla", "Thanatos",
"Throne", "Thrush", "Tigress", "Tin Can", "Titan", "Trebuchet",
"Trimaran", "Turnstone", "Turing", "Tusk", "Twite",
"Ursa", "Undertaker", "Undying Dodo", "Underlord",
"Vengeance", "Viper", "Virtue", "Visage", "Void Hunter", "Volt", "Vulture",
"Wagtail", "Warbird", "Warbler", "Warcry", "Warden", "Warlock", "Warlord", "Warrunner",
"Waxwing", "Weaver", "Wheatear", "Whimbrel", "Whinchat", "Whitestar",
"Wigeon", "Windranger", "Woodcock", "Wraith", "Wrath", "Wren", "Wyvern", "Wyrm",
"Xena", "Xenon", "Xylem",
"Yacht", "Yellowhammer", "Yettie",
"Zenith", "Zilla", "Zombie", "Zweihander"
}
--[[========================================================================================
Helper functions for identifying if a tile or entity is a certain type of spaceship thing.
]]--
function Spaceship.is_floor(tile_name)
return Util.table_contains(Spaceship.names_spaceship_floors, tile_name)
end
function Spaceship.is_wall(entity_name)
return Util.table_contains(Spaceship.names_spaceship_walls, entity_name)
end
function Spaceship.is_gate(entity_name)
return Util.table_contains(Spaceship.names_spaceship_gates, entity_name)
end
function Spaceship.is_wall_or_gate(entity_name)
return Spaceship.is_wall(entity_name) or Spaceship.is_gate(entity_name)
end
--[[========================================================================================
Helper functions for getting spaceship references.
]]--
function Spaceship.from_index(spaceship_index)
if global.spaceships then return global.spaceships[tonumber(spaceship_index)] end
end
function Spaceship.from_entity(entity)
if not entity then return end
for _, spaceship in pairs(global.spaceships) do
if spaceship.console and spaceship.console.valid and spaceship.console.unit_number == entity.unit_number then
return spaceship
end
end
end
function Spaceship.from_name(name)
for _, spaceship in pairs(global.spaceships) do
if spaceship.name == name then
return spaceship
end
end
end
function Spaceship.from_own_surface_index(surface_index) -- can't be a zone
if global.spaceships then
for _, spaceship in pairs(global.spaceships) do
if spaceship.own_surface_index == surface_index then
return spaceship
end
end
end
end
function Spaceship.from_surface_position(surface, position)
local x = math.floor(position.x or position[1])
local y = math.floor(position.y or position[2])
-- TODO allow multiple spaceships per surface
for _, spaceship in pairs(global.spaceships) do
if spaceship.own_surface_index then
if spaceship.own_surface_index == surface.index then
return spaceship
end
elseif spaceship.console and spaceship.console.valid and spaceship.console.surface == surface then
-- check tiles
if spaceship.known_tiles and spaceship.known_tiles[x] and spaceship.known_tiles[x][y] and
(spaceship.known_tiles[x][y] == Spaceship.tile_status.floor_console_connected
or spaceship.known_tiles[x][y] == Spaceship.tile_status.bulkhead_console_connected) then
return spaceship
end
end
end
end
--[[========================================================================================
Helper functions for spaceship states
]]
function Spaceship.is_on_own_surface(spaceship)
return spaceship.own_surface_index and not spaceship.awaiting_requests
end
--[[========================================================================================
Helper functions for getting the surfaces a spaceship cares about.
]]
function Spaceship.get_own_surface(spaceship)
return game.surfaces["spaceship-"..spaceship.index]
end
function Spaceship.get_current_surface(spaceship)
if spaceship.zone_index then
local zone = Zone.from_zone_index(spaceship.zone_index)
if zone then
return Zone.get_make_surface(zone)
end
end
return Spaceship.get_own_surface(spaceship)
end
--[[
Computes the spaceship integrity limit for a force.
]]
function Spaceship.get_integrity_limit(force)
local integrity = Spaceship.integrity_base
for _, tech_branch in pairs(Spaceship.names_tech_integrity) do
local i = 1
while i > 0 do
local tech = force.technologies[tech_branch.name.."-"..i]
if (not tech) and i == 1 then
tech = force.technologies[tech_branch.name]
end
if not tech then
i = -1
else
local levels = tech.level - tech.prototype.level + (tech.researched and 1 or 0)
integrity = integrity + levels * tech_branch.bonus_per_level
i = i + 1
end
end
end
return integrity
end
--[[
Computes the cost (in fuel) of launching a spaceship from its current surface.
]]
function Spaceship.get_launch_energy_cost(spaceship)
if spaceship.zone_index and spaceship.integrity_stress then
local zone = Zone.from_zone_index(spaceship.zone_index)
if zone then
if Zone.is_space(zone) then
return 250 * spaceship.integrity_stress * Spaceship.energy_per_launch_integrity_delta_v
end
local delta_v = Zone.get_launch_delta_v(zone)
local energy_cost = delta_v * spaceship.integrity_stress * Spaceship.energy_per_launch_integrity_delta_v
return energy_cost
end
end
end
--- Computes and returns the launch energy needed for a spaceship. Also stores the value in spaceship.launch_energy
--- Returns reference to all the tanks used to make the calculation
---@param spaceship Spaceship spaceship data
function Spaceship.get_compute_launch_energy(spaceship)
spaceship.launch_energy = nil
local tanks = nil
local zone
if spaceship.zone_index then
zone = Zone.from_zone_index(spaceship.zone_index)
end
if spaceship.zone_index and spaceship.console and spaceship.console.valid and spaceship.known_tiles then
spaceship.launch_energy = 0
local surface = spaceship.console.surface
tanks = surface.find_entities_filtered{name = Spaceship.names_booster_tanks, area = spaceship.known_bounds}
for _, tank in pairs(tanks) do
local tank_x = math.floor(tank.position.x)
local tank_y = math.floor(tank.position.y)
if spaceship.known_tiles[tank_x] and spaceship.known_tiles[tank_x][tank_y]
and spaceship.known_tiles[tank_x][tank_y] == Spaceship.tile_status.floor_console_connected
and #tank.fluidbox > 0 then
local fluidbox = tank.fluidbox[1] or {amount = 0}
if fluidbox.amount > 0 then
if zone and Zone.is_space(zone) and fluidbox.name == "se-ion-stream" then
spaceship.launch_energy = spaceship.launch_energy + fluidbox.amount * Spaceship.ion_stream_energy
else
spaceship.launch_energy = spaceship.launch_energy + fluidbox.amount * game.fluid_prototypes[fluidbox.name].fuel_value
end
end
end
end
end
return spaceship.launch_energy, tanks
end
function Spaceship.get_console_or_middle_position(spaceship)
if spaceship.console and spaceship.console.valid then
return spaceship.console.position
end
if spaceship.known_tiles_average_x and spaceship.known_tiles_average_y then
return {x = spaceship.known_tiles_average_x, y = spaceship.known_tiles_average_y}
end
end
function Spaceship.get_boarding_position(spaceship)
if spaceship.known_tiles_average_x and spaceship.known_bounds then
return {
x = spaceship.known_bounds.left_top.x + math.random() * (spaceship.known_bounds.right_bottom.x - spaceship.known_bounds.left_top.x),
y = spaceship.known_bounds.right_bottom.y + 32
}
end
if spaceship.console and spaceship.console.valid then
return Util.vectors_add(spaceship.console.position, {x = 64 * ( math.random() - 0.5), y = 64})
end
end
function Spaceship.destroy(spaceship)
if spaceship.zone_index or not Spaceship.is_on_own_surface(spaceship) then -- don't remove the whole surface if in space
global.spaceships[spaceship.index] = nil
spaceship.valid = false
-- if a player has this gui open then close it
local gui_name = SpaceshipGUI.name_spaceship_gui_root
for _, player in pairs(game.connected_players) do
local root = player.gui.left[gui_name]
if root and root.tags and root.tags.index == spaceship.index then
root.destroy()
end
end
if spaceship.own_surface_index then
game.delete_surface(spaceship.own_surface_index)
spaceship.own_surface_index = nil
end
-- make sure that history references to this spaceship are cleaned up
for _, player in pairs(game.players) do
RemoteView.make_history_valid(player)
end
end
end
--[[========================================================================================
Helper functions for getting information about a spaceship's target destination
]]
function Spaceship.get_destination_zone(spaceship)
if spaceship.destination then
if spaceship.destination.type == "spaceship" then
return Spaceship.from_index(spaceship.destination.index)
else
return Zone.from_zone_index(spaceship.destination.index)
end
end
end
function Spaceship.is_near_destination(spaceship)
if spaceship.near then
if not spaceship.destination then
return true
elseif spaceship.near.type == spaceship.destination.type
and spaceship.near.index == spaceship.destination.index then
return true
end
end
return false
end
function Spaceship.is_at_destination(spaceship)
if spaceship.destination and spaceship.destination.type ~= "spaceship" and spaceship.zone_index and spaceship.zone_index == spaceship.destination.index then
return true
end
return false
end
--- Gets or makes the spaceship's own surface
---@param spaceship Spaceship spaceship data
function Spaceship.get_make_spaceship_surface(spaceship)
local current_surface = spaceship.console.surface
local ship_surface
if spaceship.own_surface_index then
ship_surface = game.surfaces["spaceship-" .. spaceship.index]
end
if not ship_surface then
local map_gen_settings = {
autoplace_controls = {
["planet-size"] = { frequency = 1/1000, size = 1 }
}
}
map_gen_settings.autoplace_settings={
["decorative"]={
treat_missing_as_default=false,
settings={
}
},
["entity"]={
treat_missing_as_default=false,
settings={
}
},
["tile"]={
treat_missing_as_default=false,
settings={
["se-space"]={}
}
},
}
ship_surface = game.create_surface("spaceship-"..spaceship.index, map_gen_settings)
ship_surface.freeze_daytime = true
ship_surface.daytime = 0.4 -- dark but not midnight
spaceship.own_surface_index = ship_surface.index
end
if not ship_surface then
game.print("Error creating ship surface")
elseif current_surface == ship_surface then
Log.trace("Same surface")
end
return ship_surface
end
--- Launch a spaceship
---@param spaceship Spaceship the spaceship data
function Spaceship.launch(spaceship)
if not spaceship.is_launching then Log.trace("Abort launch not is_launching") return end
local automated_launch = spaceship.is_launching_automatically
spaceship.is_launching = false
spaceship.is_launching_automatically = false
if not spaceship.zone_index then Log.trace("Abort launch no zone_index") return end
if not spaceship.integrity_valid then Log.trace("Abort launch not integrity_valid") return end
if not spaceship.known_tiles then Log.trace("Abort launch not known_tiles") return end
if not spaceship.console and spaceship.console.valid then Log.trace("Abort launch not known_tiles") return end
local current_surface = spaceship.console.surface
local current_zone = Zone.from_surface(current_surface)
local required_energy = Spaceship.get_launch_energy_cost(spaceship)
local _, tanks = Spaceship.get_compute_launch_energy(spaceship)
if not (required_energy and spaceship.launch_energy and spaceship.launch_energy >= required_energy) then return end
-- point of no return
log("spaceship launch start")
local ship_surface = Spaceship.get_make_spaceship_surface(spaceship)
local linked_containers = current_surface.find_entities_filtered{
area=spaceship.known_bounds,
type="linked-container"
}
for _, linked_container in pairs(linked_containers) do
linked_container.link_id = 0
end
local valid_tanks = {}
local valid_tank_fuel = {}
local total_energy = 0
local zone = Zone.from_zone_index(spaceship.zone_index)
for _, tank in pairs(tanks) do
local tank_x = math.floor(tank.position.x)
local tank_y = math.floor(tank.position.y)
if spaceship.known_tiles[tank_x] and spaceship.known_tiles[tank_x][tank_y]
and spaceship.known_tiles[tank_x][tank_y] == Spaceship.tile_status.floor_console_connected
and #tank.fluidbox > 0 then
local fluidbox = tank.fluidbox[1] or {amount = 0}
if fluidbox.amount > 0 and required_energy > 0 then
local amount = fluidbox.amount
local energy_per_fuel = (Zone.is_space(zone) and fluidbox.name == "se-ion-stream") and Spaceship.ion_stream_energy or game.fluid_prototypes[fluidbox.name].fuel_value
table.insert(valid_tanks, tank)
table.insert(valid_tank_fuel, fluidbox.amount)
total_energy = total_energy + amount * energy_per_fuel
end
end
end
for i, tank in pairs(valid_tanks) do
local percent_required = required_energy / total_energy -- Remove x% from all booster tanks
local consume = math.min(valid_tank_fuel[i], math.ceil(percent_required * valid_tank_fuel[i]))
local fluidbox = tank.fluidbox[1] or {amount = 0}
fluidbox.amount = fluidbox.amount - consume
if fluidbox.amount > 0 then
tank.fluidbox[1] = fluidbox
else
tank.fluidbox[1] = nil
end
end
-- set the ship's location to the new statuses
spaceship.near = {type="zone", index= spaceship.zone_index}
spaceship.stopped = true
spaceship.zone_index = nil
spaceship.near_star = Zone.get_star_from_child(current_zone) or Zone.get_star_from_position(spaceship)
Spaceship.set_light(spaceship, ship_surface)
-- start generating chunks at the destination
spaceship.requests_made = SpaceshipClone.enqueue_generate_clone_to_area(spaceship, current_surface, ship_surface, {dx=0,dy=0})
if automated_launch then
-- await chunk generation before completing the launch
spaceship.await_start_tick = game.tick
spaceship.awaiting_requests = true
spaceship.clone_params = {
clone_from = current_surface,
clone_to = ship_surface,
clone_delta = {dx=0,dy=0}
}
else
-- immediate launch
SpaceshipClone.clone(spaceship, current_surface, ship_surface, {dx=0,dy=0})
end
log("spaceship launch end")
end
--- Land a spaceship
---@param spaceship Spaceship spaceship data
---@param position any the position at which to land
---@param ignore_average any if we are cloning based on the corner of the ship or its center
function Spaceship.land_at_position(spaceship, position, ignore_average)
if not (Spaceship.is_on_own_surface(spaceship) and spaceship.near and spaceship.near.type == "zone" and spaceship.known_tiles) then return end
local destination_zone = Zone.from_zone_index(spaceship.near.index)
if not destination_zone then return end
local ship_surface = game.surfaces["spaceship-" .. spaceship.index]
local target_surface = Zone.get_make_surface(destination_zone)
local offset_x = util.to_rail_grid(position.x - spaceship.known_tiles_average_x)
local offset_y = util.to_rail_grid(position.y - spaceship.known_tiles_average_y)
if ignore_average then
offset_x = util.to_rail_grid(position.x)
offset_y = util.to_rail_grid(position.y)
end
local destination_area = {
left_top = {
x = spaceship.known_bounds.left_top.x + offset_x,
y = spaceship.known_bounds.left_top.y + offset_y
},
right_bottom = {
x = spaceship.known_bounds.right_bottom.x + offset_x,
y = spaceship.known_bounds.right_bottom.y + offset_y
},
}
local dont_land_on = table.deepcopy(Ancient.vault_entrance_structures)
table.insert(dont_land_on, Ancient.name_gate_blocker)
table.insert(dont_land_on, Ancient.name_gate_blocker_void)
for name, stuff in pairs(Ancient.gate_fragments) do
table.insert(dont_land_on, name)
end
if target_surface.count_entities_filtered{name = dont_land_on, area = destination_area} > 0 then
ship_surface.print({"cant-build-reason.entity-in-the-way", {"space-exploration.ruin"}})
return
end
local landing_area_entities = {}
for x = spaceship.known_bounds.left_top.x, spaceship.known_bounds.right_bottom.x do
for y = spaceship.known_bounds.left_top.y, spaceship.known_bounds.right_bottom.y do
if spaceship.known_tiles[x] and spaceship.known_tiles[x][y] and
(spaceship.known_tiles[x][y] == Spaceship.tile_status.floor_console_connected
or spaceship.known_tiles[x][y] == Spaceship.tile_status.bulkhead_console_connected) then
local area = {
left_top={
x=x + offset_x,
y=y + offset_y},
right_bottom={
x=x+31/32 + offset_x,
y=y+31/32 + offset_y}}
local entities = target_surface.find_entities_filtered{ area = area }
for _, entity in pairs(entities) do
if entity.force.name ~= "neutral" and entity.type ~= "entity-ghost" and entity.type ~= "tile-ghost"
and entity.type ~= "logistic-robot" and entity.type ~= "construction-robot" and entity.type ~= "deconstructible-tile-proxy" then
ship_surface.print({"cant-build-reason.entity-in-the-way", {"entity-name."..entity.name}})
return
end
table.insert(landing_area_entities, entity)
end
end
end
end
-- point of no return
log('spaceship land start')
Spaceship.deactivate_engines(spaceship)
local linked_containers = ship_surface.find_entities_filtered{
area=spaceship.known_bounds,
type="linked-container"
}
for _, linked_container in pairs(linked_containers) do
linked_container.link_id = 0
end
Spaceship.destroy_all_smoke_triggers(ship_surface)
SpaceshipObstacles.destroy(spaceship, ship_surface) -- destroy all the obstacles on the ship's surface
Zone.apply_markers(destination_zone) -- in case the surface exists
-- clear the target area
for _, entity in pairs(landing_area_entities) do
if entity.valid then
if entity.type == "character" then
entity.health = entity.health * 0.1
else
util.safe_destroy(entity)-- maybe use die?
end
end
end
-- request chunk generation at destination and then immediately land there
local clone_delta = {dx=offset_x,dy=offset_y}
SpaceshipClone.enqueue_generate_clone_to_area(spaceship, ship_surface, target_surface, clone_delta)
SpaceshipClone.clone(spaceship, ship_surface, target_surface, clone_delta, Spaceship.post_land_at_position)
log('spaceship land end')
end
--- Finishes landing the spaceship after the cloning procedure finishes
---@param spaceship any
---@param clone_from any
---@param clone_to any
---@param clone_delta any
function Spaceship.post_land_at_position(spaceship, clone_from, clone_to, clone_delta)
-- move non-character players before deleting the surface the ship was on
for _, player in pairs(game.connected_players) do
if player.surface == clone_from then
player.teleport({
x = player.position.x + clone_delta.dx,
y = player.position.y + clone_delta.dy
}, clone_to)
RemoteView.gui_update(player)
end
end
game.delete_surface(clone_from)
-- set the ship's location to the new statuses
spaceship.own_surface_index = nil
spaceship.particles = {}
spaceship.mineables = {}
spaceship.zone_index = spaceship.near.index
spaceship.near = nil
spaceship.stopped = true
spaceship.is_moving = false
spaceship.speed = 0
end
--- Decrements the number of requests being waited upon for a spaceship surface transfer whenever a chunk is generated
---@param event any
function Spaceship.on_chunk_generated(event)
if event.surface and string.find(event.surface.name, "spaceship-") then
for _, spaceship in pairs(global.spaceships) do
if spaceship.clone_params and spaceship.requests_made and spaceship.clone_params.clone_to == event.surface then
spaceship.requests_made = spaceship.requests_made - 1
end
end
end
end
Event.addListener(defines.events.on_chunk_generated, Spaceship.on_chunk_generated)
--- Determines the rectangles necessary to somewhat quickly draw an image of the spaceship
--- that this player is anchor scouting. Necessary because drawing each individual tile
--- as a separate rectangle lags the game.
---@param player any
---@param spaceship Spaceship spaceship data
function Spaceship.get_make_anchor_scouting_cache(player, spaceship)
local playerdata = get_make_playerdata(player)
if not playerdata.anchor_scouting_cache then
playerdata.anchor_scouting_cache = {}
if spaceship.known_tiles and spaceship.known_bounds then
local aabb
for x = spaceship.known_bounds.left_top.x, spaceship.known_bounds.right_bottom.x do
for y = spaceship.known_bounds.left_top.y, spaceship.known_bounds.right_bottom.y do
local value = spaceship.known_tiles[x] and spaceship.known_tiles[x][y]
if value == Spaceship.tile_status.floor_console_connected
or value == Spaceship.tile_status.bulkhead_console_connected then
if not aabb then
aabb = {min_x=x,max_x=x+1,min_y=y,max_y=y+1}
else
aabb.max_y = aabb.max_y + 1
end
else
if aabb then
table.insert(playerdata.anchor_scouting_cache, aabb)
aabb = nil
end
end
end
if aabb then
table.insert(playerdata.anchor_scouting_cache, aabb)
aabb = nil
end
end
end
end
return playerdata.anchor_scouting_cache
end
--- While anchoring scouting we need to draw rectangles at the position the ship would land
--- if the player were to confirm the landing location
---@param player any
---@param spaceship Spaceship spaceship data
function Spaceship.anchor_scouting_tick(player, spaceship)
local anchor_scouting_cache = Spaceship.get_make_anchor_scouting_cache(player, spaceship)
if anchor_scouting_cache then
local offset_x = util.to_rail_grid(player.position.x - spaceship.known_tiles_average_x)
local offset_y = util.to_rail_grid(player.position.y - spaceship.known_tiles_average_y)
for _, aabb in pairs(anchor_scouting_cache) do
rendering.draw_rectangle{
color = {r = 0.125, g = 0.125, b = 0, a = 0.01},
filled = true,
left_top = {x=aabb.min_x+offset_x,y=aabb.min_y+offset_y},
right_bottom = {x=aabb.max_x+offset_x,y=aabb.max_y+offset_y},
surface = player.surface,
time_to_live = Spaceship.tick_interval_anchor + 1, -- tll must be 1 greater than the interval at which we draw to not flicker
}
end
end
end
--- Starts anchor scouting at the location the spaceship is currently at
---@param spaceship Spaceship spaceship data
---@param player any
function Spaceship.start_anchor_scouting(spaceship, player)
if not spaceship.near and spaceship.near.type == "zone" then return end
local zone = Zone.from_zone_index(spaceship.near.index)
if not zone then return end
local playerdata = get_make_playerdata(player)
-- enter remote view
playerdata.anchor_scouting_for_spaceship_index = spaceship.index
local character = player.character
if character then
playerdata.character = character
end
player.set_controller{type = defines.controllers.ghost}
--player.set_controller{type = defines.controllers.spectator}
if character then
-- stop the character from continuing input action (running to doom)
character.walking_state = {walking = false, direction = defines.direction.south}
character.riding_state = {acceleration = defines.riding.acceleration.braking, direction = defines.riding.direction.straight}
character.shooting_state = {state = defines.shooting.not_shooting, position=character.position}
end
local surface = Zone.get_make_surface(zone)
local position = {x=0,y=0}
if playerdata.surface_positions and playerdata.surface_positions[surface.index] then
position = playerdata.surface_positions[surface.index]
end
player.teleport(position, surface)
end
--- Stops any in progress anchor scouting for the given player
---@param player any
function Spaceship.stop_anchor_scouting(player)
local playerdata = get_make_playerdata(player)
if playerdata.anchor_scouting_for_spaceship_index then
playerdata.anchor_scouting_for_spaceship_index = nil
if playerdata.remote_view_active then
local surface = player.surface
local position = player.position
RemoteView.stop(player)
RemoteView.start(player)
player.teleport(position, surface)
else
if playerdata.character and playerdata.character.valid then
player.teleport(playerdata.character.position, playerdata.character.surface)
player.set_controller{type = defines.controllers.character, character = playerdata.character}
elseif not player.character then
Respawn.die(player)
end
end
end
playerdata.anchor_scouting_cache = nil
end
--- When an equipment grid is changed, we have to recalculate integrity costs
---@param event any
function Spaceship.on_equipment_grid_changed(event)
local player = game.players[event.player_index]
local spaceship = Spaceship.from_own_surface_index(player.surface.index)
if spaceship then
Spaceship.start_integrity_check(spaceship)
end
end
Event.addListener(defines.events.on_player_placed_equipment, Spaceship.on_equipment_grid_changed)
Event.addListener(defines.events.on_player_removed_equipment, Spaceship.on_equipment_grid_changed)
function Spaceship.on_entity_created(event)
local entity
if event.entity and event.entity.valid then
entity = event.entity
end
if event.created_entity and event.created_entity.valid then
entity = event.created_entity
end
if not entity then return end
if entity.type == "entity-ghost" or entity.type == "tile-ghost" then return end
local surface = entity.surface
local spaceship = Spaceship.from_own_surface_index(entity.surface.index)
if spaceship and not spaceship.is_cloning then
Spaceship.start_integrity_check(spaceship)
end
for _, name in pairs(Spaceship.names_engines) do
if entity.name == name then
if spaceship and spaceship.is_moving then
-- sets smoke
Spaceship.find_own_surface_engines(spaceship)
else
entity.active = false
end
end
end
if entity.name == Spaceship.name_spaceship_console then
global.spaceships = global.spaceships or {}
local console = entity
if spaceship then
if not (spaceship.console and spaceship.console.valid) then
spaceship.console = entity
spaceship.console_output = nil
if event.player_index and game.players[event.player_index] then
SpaceshipGUI.gui_open(game.players[event.player_index], spaceship)
end
Spaceship.start_integrity_check(spaceship, 0.1)
end
else
local zone = Zone.from_surface(entity.surface)
if not zone then
entity.surface.create_entity{
name = "item-on-ground",
position = entity.position,
["item-entity"] = {name = Spaceship.name_spaceship_console, count = 1}
}
entity.destroy()
game.print({"space-exploration.construction-denied-se-surface"})
return
end
local fn = entity.force.name
local spaceship_index = global.next_spaceship_index or 1
global.next_spaceship_index = spaceship_index + 1
local available_names = {}
for _, name in pairs(Spaceship.names) do
local found = false
for _, spaceship in pairs(global.spaceships) do
if name == spaceship.name then
found = true
break
end
end
if not found then
table.insert(available_names, name)
end
end
local name = "Spaceship " .. spaceship_index
if #available_names > 0 then
name = available_names[math.random(#available_names)]
end
local spaceship = {
type = "spaceship",
index = spaceship_index,
valid = true,
force_name = fn,
unit_number = entity.unit_number,
console = entity,
name = name,
zone_index = zone.index, -- this is dynamic and can be nil
speed = 1,
destination_zone_index = zone.index,
space_distortion = Zone.get_space_distortion(zone),
stellar_position = Zone.get_stellar_position(zone),
star_gravity_well = Zone.get_star_gravity_well(zone),
planet_gravity_well = Zone.get_planet_gravity_well(zone),
near_stellar_object = Zone.get_stellar_object_from_child(zone),
}
global.spaceships[spaceship_index] = spaceship
Spaceship.start_integrity_check(spaceship, 0.1)
if event.player_index and game.players[event.player_index] then
SpaceshipGUI.gui_open(game.players[event.player_index], spaceship)
end
end
end
if spaceship then
Spaceship.check_integrity_stress(spaceship)
end
end
Event.addListener(defines.events.on_built_entity, Spaceship.on_entity_created)
Event.addListener(defines.events.on_robot_built_entity, Spaceship.on_entity_created)
Event.addListener(defines.events.script_raised_built, Spaceship.on_entity_created)
Event.addListener(defines.events.script_raised_revive, Spaceship.on_entity_created)
function Spaceship.on_built_tile(event)
local surface = game.surfaces[event.surface_index]
if surface and string.find(surface.name, "spaceship-") then
local spaceship = Spaceship.from_own_surface_index(surface.index)
if not spaceship.is_cloning then
Spaceship.check_integrity_stress(spaceship)
Spaceship.start_integrity_check(spaceship)
end
end
end
Event.addListener(defines.events.on_player_built_tile, Spaceship.on_built_tile)
Event.addListener(defines.events.on_robot_built_tile, Spaceship.on_built_tile)
function Spaceship.on_removed_tile(event)
local surface
if event.player_index and game.players[event.player_index] and game.players[event.player_index].connected then
surface = game.players[event.player_index].surface
end
if event.robot and event.robot.valid then
surface = event.robot.surface
end
if surface and string.find(surface.name, "spaceship-") then
local spaceship = Spaceship.from_own_surface_index(surface.index)
if not spaceship.is_cloning then
Spaceship.check_integrity_stress(spaceship)
Spaceship.start_integrity_check(spaceship)
end
end
end
Event.addListener(defines.events.on_robot_mined_tile, Spaceship.on_removed_tile)
Event.addListener(defines.events.on_player_mined_tile, Spaceship.on_removed_tile)
function Spaceship.on_removed_entity(event)
if event.entity and event.entity.valid then
if event.entity.name == Spaceship.name_spaceship_console then
local outputs = event.entity.surface.find_entities_filtered{name = Spaceship.name_spaceship_console_output, area = util.position_to_area(event.entity.position, 2)}
for _, output in pairs(outputs) do
output.destroy()
end
elseif event.entity.surface and Util.table_contains(Spaceship.names_spaceship_bulkheads, event.entity.name) then
-- this check is *not* appropriate if we can have multiple spaceships on a spaceship surface
-- when implementing multiple ships per spaceship surface, the way of handling not responding to events raised by cloning must be changed to work with that
if string.find(event.entity.surface.name, "spaceship-") then
local spaceship = Spaceship.from_own_surface_index(event.entity.surface.index)
if not spaceship.is_cloning then
spaceship.speed = spaceship.speed * 0.9
Spaceship.check_integrity_stress(spaceship)
Spaceship.start_integrity_check(spaceship)
end
end
end
end
end
Event.addListener(defines.events.on_entity_died, Spaceship.on_removed_entity)
Event.addListener(defines.events.on_robot_mined_entity, Spaceship.on_removed_entity)
Event.addListener(defines.events.on_player_mined_entity, Spaceship.on_removed_entity)
Event.addListener(defines.events.script_raised_destroy, Spaceship.on_removed_entity)
function Spaceship.get_distance_to_destination(spaceship)
if (not spaceship.destination) or Spaceship.is_near_destination(spaceship) then
return 0
end
local target_zone = Spaceship.get_destination_zone(spaceship)
if target_zone then
local destination_space_distorion = Zone.get_space_distortion(target_zone)
local destination_stellar_position = Zone.get_stellar_position(target_zone)
local destination_star_gravity_well = Zone.get_star_gravity_well(target_zone)
local destination_planet_gravity_well = Zone.get_planet_gravity_well(target_zone)
local distortion_distance = 0
local interstellar_distance = 0
local star_gravity_distance = 0
local planet_gravity_distance = 0
distortion_distance = math.abs(spaceship.space_distortion - destination_space_distorion)
interstellar_distance = Util.vectors_delta_length(spaceship.stellar_position, destination_stellar_position)
--if distortion_distance == 1 then
if distortion_distance >= 1 or (spaceship.space_distortion == 1 and destination_space_distorion == 1) then
interstellar_distance = 0
end
if interstellar_distance == 0 then
-- same solar system
star_gravity_distance = math.abs(spaceship.star_gravity_well - destination_star_gravity_well)
else
star_gravity_distance = spaceship.star_gravity_well + destination_star_gravity_well
end
if star_gravity_distance == 0 then
-- same solar system
planet_gravity_distance = math.abs(spaceship.planet_gravity_well - destination_planet_gravity_well)
else
planet_gravity_distance = spaceship.planet_gravity_well + destination_planet_gravity_well
end
if target_zone.type == "anomaly" and star_gravity_distance == 0 and planet_gravity_distance == 0 and distortion_distance > 0 then
return math.random(Zone.travel_cost_space_distortion - 1000) * 4 + 1000
-- actual distance calculation: return distortion_distance * Zone.travel_cost_space_distortion
else
return distortion_distance * Zone.travel_cost_space_distortion
+ interstellar_distance * Zone.travel_cost_interstellar
+ star_gravity_distance * Zone.travel_cost_star_gravity
+ planet_gravity_distance * Zone.travel_cost_planet_gravity
end
end
end
--- Finds the engines for this spaceship
---@param spaceship Spaceship
---@param start_position {x=int, y=int}
function Spaceship.get_space_behind(spaceship, start_position)
-- spaceship must be on own surface
local space_behind = math.huge
if spaceship.known_tiles and spaceship.known_tiles[start_position.x] then
-- there's no way this is efficient but it needs to be profiled to see if this actually
-- meaningfully contributes to the UPS cost of ships (probably contributes more to integrity
-- checks than anything else)
for y = start_position.y, spaceship.known_bounds.right_bottom.y, 1 do
if spaceship.known_tiles[start_position.x][y]
and (spaceship.known_tiles[start_position.x][y] == Spaceship.tile_status.floor_console_connected
or spaceship.known_tiles[start_position.x][y] == Spaceship.tile_status.bulkhead_console_connected) then
space_behind = y - start_position.y
break
end
end
end
return space_behind
end
---@param spaceship Spaceship the spaceship data
---@param engine Spaceship engine
function Spaceship.update_smoke(spaceship, engine)
if engine.entity.active then
if (not engine.smoke_trigger) and Spaceship.engines[engine.entity.name].smoke_trigger then
engine.smoke_trigger = engine.entity.surface.create_entity{
name = Spaceship.engines[engine.entity.name].smoke_trigger,
position = {x = engine.entity.position.x, y = engine.entity.bounding_box.right_bottom.y}
}
end
elseif engine.smoke_trigger and engine.smoke_trigger.valid then
engine.smoke_trigger.destroy()
engine.smoke_trigger = nil
end
end
function Spaceship.destroy_all_smoke_triggers(surface)
local smoke_triggers = surface.find_entities_filtered{
type = "smoke-with-trigger",
name = Spaceship.names_smoke_trigger
-- do not restrict area,
}
for _, smoke_trigger in pairs(smoke_triggers) do
smoke_trigger.destroy()
end
end
--- Finds the engines for this spaceship
---@param spaceship Spaceship the spaceship data
function Spaceship.find_own_surface_engines(spaceship)
spaceship.engines = nil
local surface = Spaceship.get_own_surface(spaceship)
if surface then
Spaceship.destroy_all_smoke_triggers(surface)
end
if surface and spaceship.known_tiles and spaceship.known_bounds then
spaceship.engines = {}
local engines = surface.find_entities_filtered{
name = Spaceship.names_engines,
area = spaceship.known_bounds
}
local y_engines = {} -- thrust harmonics
for _, entity in pairs(engines) do
local efficiency = Spaceship.engine_efficiency_blocked
local box = entity.bounding_box
local engine_y_behind = math.floor(box.right_bottom.y) + 1
local engine_x = math.floor((box.left_top.x + box.right_bottom.x)/2)
local space_behind
if entity.position.x % 1 < 0.25 or entity.position.x % 1 > 0.75 then
-- 2-wide trail
space_behind = math.min(
Spaceship.get_space_behind(spaceship, {x = engine_x - 1, y = engine_y_behind}),
Spaceship.get_space_behind(spaceship, {x = engine_x, y = engine_y_behind})
)
else
-- 1-wide trail
space_behind = Spaceship.get_space_behind(spaceship, {x = engine_x, y = engine_y_behind})
end
if space_behind < 0 then
efficiency = Spaceship.engine_efficiency_unblocked
else
efficiency = 1-(1-Spaceship.engine_efficiency_blocked) / (space_behind + Spaceship.engine_efficiency_unblocked_taper) * Spaceship.engine_efficiency_unblocked_taper
end
efficiency = efficiency - Spaceship.engine_efficiency_side
local engine = {entity = entity, efficiency = efficiency}
if not y_engines[engine_y_behind] then
y_engines[engine_y_behind] = {left = engine, right = engine}
else
if entity.position.x < y_engines[engine_y_behind].left.entity.position.x then
y_engines[engine_y_behind].left = engine
end
if entity.position.x > y_engines[engine_y_behind].right.entity.position.x then
y_engines[engine_y_behind].right = engine
end
end
table.insert(spaceship.engines, engine)
end
-- thrust harmonics
-- the left-most and right-most engines get a bonus
-- there is a 1% incentive to have engines on different Y values.
-- You waste integrity building this way, but it means more interesting designs are penalised less by the forced grid.
for y, left_right in pairs(y_engines) do
left_right.left.efficiency = left_right.left.efficiency + Spaceship.engine_efficiency_side
if left_right.left ~= left_right.right then
left_right.right.efficiency = left_right.right.efficiency + Spaceship.engine_efficiency_side
end
end
-- Show the result
for _, engine in pairs(spaceship.engines) do
engine.entity.surface.create_entity{
name = "flying-text",
position = util.vectors_add({x=0, y=1}, engine.entity.position),
text = math.ceil(engine.efficiency*100).."%"
}
Spaceship.update_smoke(spaceship, engine)
end
end
end
--- Activates the engines on this spaceship
---@param spaceship Spaceship the spaceship data
function Spaceship.activate_engines(spaceship)
if not spaceship.engines then
Spaceship.find_own_surface_engines(spaceship)
end
if spaceship.engines then
for _, engine in pairs(spaceship.engines) do
if engine.entity and engine.entity.valid then
engine.entity.active = true
Spaceship.update_smoke(spaceship, engine)
else
spaceship.engines = nil
Spaceship.activate_engines(spaceship)
return
end
end
end
end
--- Deactivates the engines on this spaceship
---@param spaceship Spaceship the spaceship data
function Spaceship.deactivate_engines(spaceship)
if not spaceship.engines then
Spaceship.find_own_surface_engines(spaceship)
end
if spaceship.engines then
for _, engine in pairs(spaceship.engines) do
if engine.smoke_trigger and engine.smoke_trigger.valid then
engine.smoke_trigger.destroy()
engine.smoke_trigger = nil
end
if engine.entity and engine.entity.valid then
engine.entity.active = false
Spaceship.update_smoke(spaceship, engine)
else
spaceship.engines = nil
Spaceship.deactivate_engines(spaceship)
return
end
end
end
end
function Spaceship.surface_tick(spaceship, time_passed)
-- actions that apply to maintaining a spaceship surface
spaceship.speed = spaceship.speed or 0
local surface = Spaceship.get_own_surface(spaceship)
if spaceship.speed > 1 then
surface.wind_orientation = 0.5
end
local speed_factor = SpaceshipObstacles.particle_speed_factor(spaceship.speed)
surface.wind_speed = 0.01 + 0.005 * speed_factor
-- floating characters
if remote.interfaces["jetpack"] and remote.interfaces["jetpack"]["get_jetpacks"] then
local jetpacks = remote.call("jetpack", "get_jetpacks", {surface_index = spaceship.own_surface_index})
for _, jetpack in pairs(jetpacks) do
jetpack.velocity.y = jetpack.velocity.y +
0.000005 * time_passed * math.pow(spaceship.speed / Spaceship.speed_taper, Spaceship.particle_speed_power) * Spaceship.speed_taper
if remote.interfaces["jetpack"]["set_velocity"] then
remote.call("jetpack", "set_velocity", {unit_number = jetpack.unit_number, velocity = jetpack.velocity})
end
end
end
-- obstacles
SpaceshipObstacles.tick_obstacles(spaceship, surface, time_passed)
end
--- Sets the lighting of a spaceship surface. This dictates both how bright the player's screen is and solar power
---@param spaceship Spaceship spaceship data
---@param surface LuaSurface the spaceship's surface
function Spaceship.set_light(spaceship, surface)
-- expect 15 is the max, 10 + 5 planets but reduced start position
local light_percent = Zone.get_solar(spaceship)
surface.freeze_daytime = true
if light_percent >= 0.5 then
surface.daytime = 0.35 -- half light
surface.solar_power_multiplier = Zone.solar_multiplier * light_percent * 2 -- x2 compensate for half light
else
surface.daytime = 0.45 - 0.2 * light_percent
surface.solar_power_multiplier = Zone.solar_multiplier -- x2 compensate for half light max
-- light_percent of 1 would be 0.35 (half-light),
-- light_percent of 0 would be 0.45 (dark)
end
end
function Spaceship.apply_engine_thust(spaceship, time_passed)
-- Apply engines to speed.
if spaceship.engines then
for _, engine_table in pairs(spaceship.engines) do
local engine = engine_table.entity
if engine and engine.valid then
if engine.active and engine.is_crafting() then
for _, engine_proto in pairs(Spaceship.engines) do
if engine.name == engine_proto.name then
spaceship.speed = spaceship.speed
+ (spaceship.speed_multiplier or 1) * engine_table.efficiency * engine_proto.thrust
* (engine.energy / engine_proto.max_energy) / (Spaceship.minimum_mass + spaceship.integrity_stress)
* (Spaceship.speed_taper / (Spaceship.speed_taper + spaceship.speed))
* time_passed
end
end
end
else
spaceship.engines[_] = nil
end
end
end
end
function Spaceship.apply_drag(spaceship, time_passed)
-- space_drag from imperfect vacuum
-- streamline 0 = 110.45
-- streamline 1 = 181.25
local drag = Spaceship.space_drag * (2 - (spaceship.streamline or 0)) * time_passed
spaceship.speed = spaceship.speed * (1 - drag) + Spaceship.minimum_impulse
-- brake
if spaceship.target_speed and spaceship.speed > spaceship.target_speed then
spaceship.speed = math.min(spaceship.speed, math.max(spaceship.target_speed + 1, spaceship.speed - 0.001 * time_passed))
spaceship.speed = math.min(spaceship.speed, math.max(spaceship.target_speed + 0.5, spaceship.speed - (spaceship.speed + 0.5 - spaceship.target_speed) * (1 - math.pow(0.999, time_passed))))
end
spaceship.max_speed = math.max(spaceship.speed, spaceship.max_speed or 0)
end
--- Move away from the current planet
---@param spaceship Spaceship the spaceship
---@param travel_speed number speed at which to move
function Spaceship.move_from_planet(spaceship, travel_speed)
spaceship.planet_gravity_well = math.max(0, spaceship.planet_gravity_well - travel_speed / Zone.travel_cost_planet_gravity)
spaceship.travel_message = {"space-exploration.spaceship-travel-message-exiting-planet-gravity"}
end
--- Move away from the current star
---@param spaceship Spaceship the spaceship
---@param travel_speed number speed at which to move
function Spaceship.move_from_star(spaceship, travel_speed)
spaceship.star_gravity_well = math.max(0, spaceship.star_gravity_well - travel_speed / Zone.travel_cost_star_gravity)
spaceship.travel_message = {"space-exploration.spaceship-travel-message-exiting-star-gravity"}
end
--- Move towards a particular position in intestellar space
---@param spaceship Spaceship the spaceship
---@param travel_speed number speed at which to move
---@param destination_stellar_position Position the target location
function Spaceship.move_towards_interstellar_position(spaceship, travel_speed, destination_stellar_position)
spaceship.near_stellar_object = nil
spaceship.stellar_position = Util.move_to(spaceship.stellar_position, destination_stellar_position,
travel_speed / Zone.travel_cost_interstellar)
spaceship.travel_message = {"space-exploration.spaceship-travel-message-navigating-interstellar"}
end
--- Move towards a particular position in intestellar space instantly
---@param spaceship Spaceship the spaceship
---@param travel_speed number speed at which to move
---@param destination_stellar_position Position the target location
function Spaceship.move_instant_interstellar_position(spaceship, travel_speed, destination_stellar_position)
spaceship.stellar_position = table.deepcopy(destination_stellar_position)
spaceship.space_distortion = 1 - travel_speed / Zone.travel_cost_space_distortion
spaceship.travel_message = {"space-exploration.spaceship-travel-message-spatial-distortions"}
end
--- Move towards the anomaly
---@param spaceship Spaceship the spaceship
---@param travel_speed number speed at which to move
---@param destination_space_distorion number the distortion of the target (does not have to be 1 / at the anomaly)
function Spaceship.move_to_anomaly(spaceship, travel_speed, destination_space_distorion)
spaceship.near_stellar_object = nil
local delta_space_distortion = destination_space_distorion - spaceship.space_distortion
if delta_space_distortion == 0 then
spaceship.near = table.deepcopy(spaceship.destination)
spaceship.stopped = true
else
local space_distortion_travel = travel_speed / Zone.travel_cost_space_distortion
-- step towards destination
spaceship.space_distortion = spaceship.space_distortion
+ math.min(math.max(delta_space_distortion, -space_distortion_travel), space_distortion_travel)
spaceship.travel_message = {"space-exploration.spaceship-travel-message-spatial-distortions"}
end
end
--- Move away from the anomaly
---@param spaceship Spaceship the spaceship
---@param travel_speed number speed at which to move
function Spaceship.move_from_anomaly(spaceship, travel_speed)
spaceship.space_distortion = spaceship.space_distortion - travel_speed / Zone.travel_cost_space_distortion
spaceship.travel_message = {"space-exploration.spaceship-travel-message-spatial-distortions"}
end
--- Move the spaceship through space conventionally (i.e. no teleportation/spatial distortion)
---@param spaceship Spaceship the spaceship
---@param travel_speed number speed at which to move
---@param target_zone any
---@param destination_stellar_position any
---@param destination_star_gravity_well any
---@param destination_planet_gravity_well any
function Spaceship.move_conventional(spaceship, travel_speed, target_zone, destination_stellar_position, destination_star_gravity_well, destination_planet_gravity_well)
local interstellar_distance = Util.vectors_delta_length(spaceship.stellar_position, destination_stellar_position)
if interstellar_distance == 0 then -- same system
if spaceship.type == "spaceship" then -- not needed on spaceship-lookahead
spaceship.near_star = Zone.get_star_from_child(target_zone) or Zone.get_star_from_position(target_zone)
spaceship.near_stellar_object = Zone.get_stellar_object_from_child(target_zone) or Zone.get_stellar_object_from_position(target_zone)
end
if spaceship.star_gravity_well == destination_star_gravity_well then -- same planet system
if spaceship.planet_gravity_well == destination_planet_gravity_well then -- we're here
spaceship.near = table.deepcopy(spaceship.destination)
spaceship.stopped = true
else
local delta_planet_gravity = destination_planet_gravity_well - spaceship.planet_gravity_well
local planet_gravity_travel = travel_speed / Zone.travel_cost_planet_gravity
spaceship.planet_gravity_well = spaceship.planet_gravity_well
+ math.min(math.max(delta_planet_gravity, -planet_gravity_travel), planet_gravity_travel)
spaceship.travel_message = {"space-exploration.spaceship-travel-message-navigating-planet-gravity"}
end
else
if spaceship.planet_gravity_well > 0 then
spaceship.planet_gravity_well = math.max(0, spaceship.planet_gravity_well - travel_speed / Zone.travel_cost_planet_gravity)
spaceship.travel_message = {"space-exploration.spaceship-travel-message-exiting-planet-gravity"}
else
local delta_star_gravity = destination_star_gravity_well - spaceship.star_gravity_well
local star_gravity_travel = travel_speed / Zone.travel_cost_star_gravity
spaceship.star_gravity_well = spaceship.star_gravity_well
+ math.min(math.max(delta_star_gravity, -star_gravity_travel), star_gravity_travel)
spaceship.travel_message = {"space-exploration.spaceship-travel-message-navigating-star-gravity"}
end
end
else -- different systems
if spaceship.planet_gravity_well > 0 then -- leave the planet gravity well
Spaceship.move_from_planet(spaceship, travel_speed)
elseif spaceship.star_gravity_well > 0 then -- leave the star gravity well
Spaceship.move_from_star(spaceship, travel_speed)
else -- match interstellar position
Spaceship.move_towards_interstellar_position(spaceship, travel_speed, destination_stellar_position)
end
end
end
--- Moves the spaceship from its current position towards its destination
---@param spaceship Spaceship the spaceship
---@param time_passed number amount of time passed since the position of the spaceship was last updated
function Spaceship.move_to_destination(spaceship, time_passed)
if not spaceship.destination then return end
local target_zone = Spaceship.get_destination_zone(spaceship)
if not target_zone then
spaceship.destination = nil
spaceship.travel_message = "No destination."
Log.trace("Spaceship destination invalid")
return
end
--Log.trace(game.tick .. " moving to destination.")
-- move away from current zone
if spaceship.near and not Spaceship.is_near_destination(spaceship) then
--Log.trace(game.tick .. "Leaving zone.")
spaceship.near = nil
-- close any scouting views
for _, player in pairs(game.connected_players) do
local playerdata = get_make_playerdata(player)
if playerdata.anchor_scouting_for_spaceship_index == spaceship.index then
Spaceship.stop_anchor_scouting(player)
player.print("Cannot anchor, spaceship has departed for a different destination.")
end
end
end
Spaceship.move_to_destination_basic(spaceship, target_zone, time_passed)
local ship_surface = Spaceship.get_own_surface(spaceship)
Spaceship.set_light(spaceship, ship_surface)
end
--- Moves a spaceship or spaceship
---@param spaceship Spaceship the spaceship or spaceship-lookahead
---@param target_zone Zone or Spaceship
---@param time_passed number amount of time passed since the position of the spaceship was last updated
function Spaceship.move_to_destination_basic(spaceship, target_zone, time_passed)
-- step towards destination
local travel_speed = spaceship.speed * Spaceship.travel_speed_multiplier * time_passed
local destination_space_distorion = Zone.get_space_distortion(target_zone)
local destination_stellar_position = Zone.get_stellar_position(target_zone)
local destination_star_gravity_well = Zone.get_star_gravity_well(target_zone)
local destination_planet_gravity_well = Zone.get_planet_gravity_well(target_zone)
if destination_space_distorion == 1 then -- target is anomaly (or spaceship at anomaly)
if spaceship.planet_gravity_well > 0 then -- leave the planet gravity well
Spaceship.move_from_planet(spaceship, travel_speed)
elseif spaceship.star_gravity_well > 0 then -- leave the star gravity well
Spaceship.move_from_star(spaceship, travel_speed)
else -- move towards the anomaly
Spaceship.move_to_anomaly(spaceship, travel_speed, destination_space_distorion)
end
elseif spaceship.space_distortion == 1 then -- at the anomaly so the stellar position can be anywhere instantly
Spaceship.move_instant_interstellar_position(spaceship, travel_speed, destination_stellar_position)
elseif destination_space_distorion > 0 then -- target is spaceship on way to/from anomaly
local interstellar_distance = Util.vectors_delta_length(spaceship.stellar_position, destination_stellar_position)
if spaceship.planet_gravity_well > 0 then -- leave the planet gravity well
Spaceship.move_from_planet(spaceship, travel_speed)
elseif spaceship.star_gravity_well > 0 then -- leave the star gravity well
Spaceship.move_from_star(spaceship, travel_speed)
elseif interstellar_distance > 0 then -- match interstellar position
Spaceship.move_towards_interstellar_position(spaceship, travel_speed, destination_stellar_position)
else -- move towards the anomaly
Spaceship.move_to_anomaly(spaceship, travel_speed, destination_space_distorion)
end
elseif spaceship.space_distortion > 0 then -- leaving the anomaly
Spaceship.move_from_anomaly(spaceship, travel_speed)
else -- conventional travel to planet/star/spaceship
Spaceship.move_conventional(spaceship, travel_speed, target_zone, destination_stellar_position, destination_star_gravity_well, destination_planet_gravity_well)
end
end
function Spaceship.spaceship_tick(spaceship)
if not (spaceship.console and spaceship.console.valid) then
spaceship.check_message= {"space-exploration.spaceship-check-message-no-console"}
spaceship.integrity_valid = false
end
-- Pause inserters, workaround for https://forums.factorio.com/viewtopic.php?f=58&t=89035
-- Note: production machines should NOT be included as some are supposed to be disabled on specific surfaces.
if spaceship.entities_to_restore and spaceship.entities_to_restore_tick < game.tick then
for _, storedState in pairs(spaceship.entities_to_restore) do
if storedState.entity and storedState.entity.valid then
storedState.entity.active = storedState.active
end
end
spaceship.entities_to_restore = nil
end
if Spaceship.is_on_own_surface(spaceship) or (spaceship.console and spaceship.console.valid) then
-- integrity check
if spaceship.is_doing_check then
if Spaceship.is_on_own_surface(spaceship) and spaceship.known_floor_tiles and not spaceship.is_doing_check_slowly then
-- need to tick faster on bigger ships
for i = 0, math.ceil(spaceship.known_floor_tiles / 1000) do
-- tick once for each 1000 tiles
if spaceship.is_doing_check then
Spaceship.integrity_check_tick(spaceship)
end
end
else
Spaceship.integrity_check_tick(spaceship)
end
elseif spaceship.console and spaceship.console.valid and (game.tick + spaceship.console.unit_number) % (Spaceship.integrity_pulse_interval) == 0 and spaceship.console.energy > 0 then
Spaceship.start_slow_integrity_check(spaceship)
end
if spaceship.console and spaceship.console.valid and (game.tick + spaceship.console.unit_number) % 60 == 0 then
-- read signals
local red = spaceship.console.get_circuit_network(defines.wire_type.red)
local green = spaceship.console.get_circuit_network(defines.wire_type.green)
-- set speed
if spaceship.target_speed_source ~= "manual-override" then
local signal_target_speed = util.signal_from_wires(red, green, Spaceship.signal_for_speed)
if signal_target_speed > 0 then
spaceship.target_speed = signal_target_speed + 0.5
spaceship.target_speed_source = "circuit"
elseif signal_target_speed < 0 then
spaceship.stopped = true
spaceship.target_speed = nil
spaceship.target_speed_source = "circuit"
elseif not spaceship.stopped then
-- 0 means use set targets
local last_target_speed = spaceship.target_speed
local asteroid_density = spaceship.asteroid_density or SpaceshipObstacles.default_asteroid_density
if spaceship.future_asteroid_density and spaceship.future_asteroid_density > asteroid_density then
asteroid_density = spaceship.future_asteroid_density
end
if asteroid_density == SpaceshipObstacles.default_asteroid_density then
spaceship.target_speed = spaceship.target_speed_normal
spaceship.target_speed_source = "normal"
elseif asteroid_density == SpaceshipObstacles.asteroid_density_by_zone_type['asteroid-belt'] then
spaceship.target_speed = spaceship.target_speed_belt or spaceship.target_speed_normal
spaceship.target_speed_source = "asteroid-belt"
elseif asteroid_density == SpaceshipObstacles.asteroid_density_by_zone_type['asteroid-field'] then
spaceship.target_speed = spaceship.target_speed_field or spaceship.target_speed_belt or spaceship.target_speed_normal
spaceship.target_speed_source = "asteroid-field"
end
-- target speed was set to nil which means the fields are empty and target speed should be unlimited
if not spaceship.target_speed then
if last_target_speed and spaceship.is_moving then Spaceship.activate_engines(spaceship) end -- if spaceship was being speed throttled then need to fire all engines now that it's unlimited (but only once)
spaceship.target_speed_source = nil
end
end
end
-- set destination
for signal_name, type in pairs(Zone.signal_to_zone_type) do
local value = util.signal_from_wires(red, green, {type = "virtual", name = signal_name})
if value > 0 then
local zone = Zone.from_zone_index(value)
if zone and zone.type == type then
if Zone.is_visible_to_force(zone, spaceship.force_name) or global.debug_view_all_zones then
if (not spaceship.destination) or (spaceship.destination.type == "zone" and spaceship.destination.index ~= value) then
spaceship.destination = { type = "zone", index = value }
Spaceship.update_output_combinator(spaceship)
end
break
end
end
end
end
-- TODO: allow spacwhip as destination
-- launch
if not spaceship.own_surface_index and not spaceship.entities_to_restore then
if (red and red.get_signal(Spaceship.signal_for_launch) > 0)
or (green and green.get_signal(Spaceship.signal_for_launch) > 0) then
spaceship.is_launching = true
spaceship.is_launching_automatically = true
spaceship.is_landing = false
Spaceship.start_integrity_check(spaceship)
end
end
-- land
if Spaceship.is_on_own_surface(spaceship)
and not spaceship.entities_to_restore
and spaceship.destination
and spaceship.destination.type == "zone"
and Spaceship.is_near_destination(spaceship) then
SpaceshipClamp.attempt_anchor_spaceship(spaceship, red, green)
end
end
-- delayed launch (either until the number of waiting chunk requests is finished or a maximum delay is reached)
if spaceship.awaiting_requests and (spaceship.requests_made <= 0 or (game.tick - spaceship.await_start_tick) >= Spaceship.tick_max_await) then
local params = spaceship.clone_params
SpaceshipClone.clone(spaceship, params.clone_from, params.clone_to, params.clone_delta)
spaceship.awaiting_requests = false
spaceship.clone_params = nil
end
-- don't upkeep the ship as if it is in-transit until it has actually cloned to the own_surface_index surface
-- maybe use Spaceship.is_on_own_surface(spaceship) instead
if spaceship.own_surface_index and not spaceship.awaiting_requests then
-- space upkeep
if game.tick % Spaceship.tick_interval_move == 0 then
Spaceship.surface_tick(spaceship, Spaceship.tick_interval_move)
end
if spaceship.target_speed and spaceship.is_moving == false and spaceship.stopped then
spaceship.stopped = false
end
-- this has to be done every tick for seamless movement
local surface = Spaceship.get_own_surface(spaceship)
SpaceshipObstacles.tick_entity_obstacles(spaceship, surface)
-- navigation
if spaceship.integrity_valid
and spaceship.destination
and not spaceship.stopped
and not Spaceship.is_near_destination(spaceship) then
-- wants to move and can move
if not spaceship.is_moving then
spaceship.is_moving = true
Spaceship.activate_engines(spaceship)
Spaceship.start_integrity_check(spaceship)
end
if (game.tick % 6) == 0 then
if spaceship.target_speed and spaceship.is_moving and spaceship.engines then
if spaceship.speed > spaceship.target_speed then
for _, engine in pairs(spaceship.engines) do
if engine.entity.valid and math.random() < 1/table_size(spaceship.engines) then
engine.entity.active = false
Spaceship.update_smoke(spaceship, engine)
end
end
else
for _, engine in pairs(spaceship.engines) do
if engine.entity.valid and math.random() < 1/table_size(spaceship.engines) then
engine.entity.active = true
Spaceship.update_smoke(spaceship, engine)
end
end
end
end
end
if game.tick % Spaceship.tick_interval_move == 0 then
Spaceship.apply_engine_thust(spaceship, Spaceship.tick_interval_move)
Spaceship.apply_drag(spaceship, Spaceship.tick_interval_move)
Spaceship.move_to_destination(spaceship, Spaceship.tick_interval_move)
end
else
-- can't move
if spaceship.is_moving then
spaceship.is_moving = false
Spaceship.deactivate_engines(spaceship)
end
if Spaceship.is_near_destination(spaceship) then
spaceship.speed = 0
spaceship.travel_message = {"space-exploration.spaceship-travel-message-at-destination"}
end
if spaceship.speed > 0 then
local drag = Spaceship.space_drag * (2 - (spaceship.streamline or 0))
spaceship.speed = math.max(0, spaceship.speed * (1 - Spaceship.space_drag) - (spaceship.stopped and 0.1 or 0.02))
end
end
end
else
Spaceship.destroy(spaceship)
return
end
if game.tick % 60 == 0 then
Spaceship.update_output_combinator(spaceship)
end
end
function Spaceship.update_output_combinator(spaceship)
if not (spaceship.console and spaceship.console.valid) then return end
if not (spaceship.console_output and spaceship.console_output.valid) then
-- spawn the console output
local console = spaceship.console
local output_position = util.vectors_add(console.position, Spaceship.console_output_offset)
local output = util.find_entity_or_revive_ghost(console.surface, Spaceship.name_spaceship_console_output, output_position)
if not output then
output = console.surface.create_entity{
name = Spaceship.name_spaceship_console_output,
position = util.vectors_add(console.position, Spaceship.console_output_offset),
force = console.force
}
end
spaceship.console_output = output
end
if spaceship.console_output and spaceship.console_output.valid then
local ctrl = spaceship.console_output.get_or_create_control_behavior()
local slot = 1
-- Spaceship ID
ctrl.set_signal(slot, {signal=Spaceship.signal_for_own_spaceship_id, count=spaceship.index})
slot = slot + 1
-- Speed
if spaceship.is_moving and spaceship.speed > 0 then
ctrl.set_signal(slot, {signal=Spaceship.signal_for_speed, count=math.max(1, spaceship.speed)})
elseif Spaceship.is_on_own_surface(spaceship) then
ctrl.set_signal(slot, {signal=Spaceship.signal_for_speed, count=-1}) -- stopped
else
ctrl.set_signal(slot, {signal=Spaceship.signal_for_speed, count=-2}) -- anchored
end
slot = slot + 1
-- Distance
if (not spaceship.distance_to_destination_tick) or spaceship.distance_to_destination_tick + 60 < game.tick then
spaceship.distance_to_destination = Spaceship.get_distance_to_destination(spaceship)
spaceship.distance_to_destination_tick = game.tick
end
if spaceship.destination and Spaceship.is_near_destination(spaceship) then
ctrl.set_signal(slot, {signal=Spaceship.signal_for_distance, count=-1})
elseif spaceship.destination and Spaceship.is_at_destination(spaceship) then
ctrl.set_signal(slot, {signal=Spaceship.signal_for_distance, count=-2})
elseif spaceship.distance_to_destination and spaceship.distance_to_destination > 0 then
ctrl.set_signal(slot, {signal=Spaceship.signal_for_distance, count=math.max(1, spaceship.distance_to_destination)})
else --no destination
ctrl.set_signal(slot, {signal=Spaceship.signal_for_distance, count=-3})
end
slot = slot + 1
-- Destination
if spaceship.destination then
if spaceship.destination.type == "spaceship" then
ctrl.set_signal(slot, {signal=Spaceship.signal_for_destination_spaceship, count=spaceship.destination.index})
elseif spaceship.destination.type == "zone" then
local zone = Spaceship.get_destination_zone(spaceship)
local signal_name = Zone.get_signal_name(zone)
ctrl.set_signal(slot, {signal={type="virtual", name=signal_name}, count=zone.index})
else
ctrl.set_signal(slot, {signal=Spaceship.signal_for_destination_spaceship, count=0})
end
else
ctrl.set_signal(slot, {signal=Spaceship.signal_for_destination_spaceship, count=0})
end
slot = slot + 1
-- Asteroid Density
if spaceship.asteroid_density then
ctrl.set_signal(slot, {signal={type = "virtual", name = "signal-D"}, count=spaceship.asteroid_density * 100})
else
ctrl.set_signal(slot, {signal={type = "virtual", name = "signal-D"}, count=SpaceshipObstacles.default_asteroid_density * 100})
end
slot = slot + 1
-- Anchor (only change if the ship is not in the middle of launching)
if not spaceship.awaiting_requests then
if spaceship.zone_index then
ctrl.set_signal(slot, {signal={type = "virtual", name = "signal-A"}, count=spaceship.zone_index})
else
ctrl.set_signal(slot, {signal={type = "virtual", name = "signal-A"}, count=0})
end
slot = slot + 1
end
end
end
--- Updates all spaceships, potentially launching/landing them or spawning/destroying/changing speed of obstacles
--- or updating their guis or updating anchor scoutin
---@param event any
function Spaceship.on_tick(event)
-- update asteroid density
for _, spaceship in pairs(global.spaceships) do
if spaceship.console and spaceship.console.valid and Spaceship.is_on_own_surface(spaceship) then
if (game.tick + spaceship.console.unit_number) % Spaceship.tick_interval_density == 0 then
spaceship.asteroid_density = 0
if spaceship.speed > 5 then
local target_zone = Spaceship.get_destination_zone(spaceship)
if target_zone then
local spaceship_lookahead = {
type = "spaceship-lookahead",
speed = spaceship.speed,
stellar_position = spaceship.stellar_position,
space_distortion = spaceship.space_distortion,
star_gravity_well = spaceship.star_gravity_well,
planet_gravity_well = spaceship.planet_gravity_well,
}
Spaceship.move_to_destination_basic(spaceship_lookahead, target_zone, 5 * 60) -- 5 seconds
spaceship.future_asteroid_density = SpaceshipObstacles.get_asteroid_density(spaceship_lookahead)
end
end
spaceship.asteroid_density = SpaceshipObstacles.get_asteroid_density(spaceship)
end
end
end
-- update spaceships
if global.spaceships then
for _, spaceship in pairs(global.spaceships) do
Spaceship.spaceship_tick(spaceship)
end
end
-- update guis
if game.tick % Spaceship.tick_interval_gui == 0 then
for _, player in pairs(game.connected_players) do
SpaceshipGUI.gui_update(player)
end
end
-- update obstacles
SpaceshipObstacles.tick_projectile_speeds()
-- update anchoring
if game.tick % Spaceship.tick_interval_anchor == 0 then
for _, player in pairs(game.connected_players) do
local playerdata = get_make_playerdata(player)
if playerdata and playerdata.anchor_scouting_for_spaceship_index then
Spaceship.anchor_scouting_tick(player, Spaceship.from_index(playerdata.anchor_scouting_for_spaceship_index))
end
end
end
end
Event.addListener(defines.events.on_tick, Spaceship.on_tick)
--[[
function Spaceship.on_chunk_generated(event)
local area = event.area
local surface = event.surface
local spaceship = Spaceship.from_own_surface_index(surface.index)
if spaceship then
area.right_bottom.x = area.right_bottom.x + 0.99
area.right_bottom.y = area.right_bottom.y + 0.99
entities = surface.find_entities_filtered{
area = area
}
for _, entity in pairs(entities) do -- rocks
entity.destroy()
end
local bad_tiles = surface.find_tiles_filtered{name={name_asteroid_tile, "out-of-map"}}
local set_tiles = {}
for _, tile in pairs(bad_tiles) do
table.insert(set_tiles, {position = tile.position, name=name_space_tile})
surface.set_hidden_tile(tile.position, name_space_tile)
end
surface.set_tiles(set_tiles)
end
end
Event.addListener(defines.events.on_chunk_generated, Spaceship.on_chunk_generated)
]]--
function Spaceship.flash_tile(surface, position, color, time)
local a = (color.a or 1)
rendering.draw_rectangle{
color = {r = color.r * a, g = color.g * a, b = color.b * a, a = a},
filled = true,
left_top = position,
right_bottom = {(position.x or position[1])+1, (position.y or position[2])+1},
surface = surface,
time_to_live = time
}
end
function Spaceship.calculate_integrity_stress(spaceship, area)
spaceship.integrity_stress_structure = 0
spaceship.integrity_stress_container = 0
-- use all tiles for the cost even if they are not connected
-- get walls for an integrity discount
local surface = Spaceship.get_current_surface(spaceship)
if not surface then surface = spaceship.console.surface end
if not area then --- whole surface
spaceship.tile_count = surface.count_tiles_filtered{name = Spaceship.names_spaceship_floors}
spaceship.wall_count = surface.count_entities_filtered{name = Spaceship.names_spaceship_walls}
else
spaceship.tile_count = spaceship.known_floor_tiles + spaceship.known_bulkhead_tiles
spaceship.wall_count = 0
local walls = surface.find_entities_filtered{name = Spaceship.names_spaceship_walls, area = area}
for _, wall in pairs(walls) do
local tile_pos = Util.position_to_tile(wall.position)
if spaceship.known_tiles[tile_pos.x] and spaceship.known_tiles[tile_pos.x][tile_pos.y] == Spaceship.tile_status.bulkhead_console_connected then
spaceship.wall_count = spaceship.wall_count + 1
end
end
end
-- Find weakpoints in the design
-- If very narrow sections are used in the middle those are not strong,
-- so we need to increase the cost not decrease it if it gets too narrow.
-- Scan from front and back so decorative pointy bits are not a problem
-- don't increase width in jump, follow the edge more loosely.
-- phantom tiles start at the 50% reduced with mark.
-- They should not have full effect immediatly.
local widths = {}
for x, x_tiles in pairs(spaceship.known_tiles) do
for y, state in pairs(x_tiles) do
if state == Spaceship.tile_status.floor_console_connected or state == Spaceship.tile_status.bulkhead_console_connected then
widths[y] = (widths[y] or 0) + 1
end
end
end
local front_y = spaceship.known_bounds.left_top.y
local back_y = spaceship.known_bounds.right_bottom.y
local front_max_width = 0
local back_max_width = 0
local phantom_tiles = 0
local widths_total = 0
for _, width in pairs(widths) do
widths_total = widths_total + width
end
local width_average = widths_total / table_size(widths)
while front_y <= back_y do
if front_max_width <= back_max_width then
if widths[front_y] then
local width = widths[front_y]
if front_max_width < width then
front_max_width = math.min(width, front_max_width + 2)
end
if width < front_max_width / 2 then
-- tiles in more extreme hollows count more
local hollow = (front_max_width / 2 - width)
phantom_tiles = phantom_tiles + hollow * hollow / (front_max_width / 2)
end
end
front_y = front_y + 1
else
if widths[back_y] then
local width = widths[back_y]
if back_max_width < width then
back_max_width = math.min(width, back_max_width + 2)
end
if width < back_max_width / 2 then
local hollow = (back_max_width / 2 - width)
phantom_tiles = phantom_tiles + hollow * hollow / (front_max_width / 2)
end
end
back_y = back_y - 1
end
end
if phantom_tiles > 0 then
Log.trace("phantom_tiles " .. phantom_tiles)
end
spaceship.container_slot_count = 0
local containers = surface.find_entities_filtered{ type = {"container", "logistic-container", "car", "spider-vehicle", "locomotive", "cargo-wagon"}, area = area}
for _, container in pairs(containers) do
local tile_pos = Util.position_to_tile(container.position)
if area == nil or (spaceship.known_tiles[tile_pos.x] and spaceship.known_tiles[tile_pos.x][tile_pos.y] == Spaceship.tile_status.floor_console_connected) then
local container_size = container.prototype.get_inventory_size(defines.inventory.car_trunk) or (container.prototype.get_inventory_size(defines.inventory.chest) or 0)
if container.type == "car" or container.type == "spider-vehicle" then
spaceship.container_slot_count = spaceship.container_slot_count + container_size
elseif container.type == "locomotive" or container.type == "cargo-wagon" then
spaceship.container_slot_count = spaceship.container_slot_count + container_size / 10
else
spaceship.container_slot_count = spaceship.container_slot_count + container_size
end
if container.type == "car" or container.type == "spider-vehicle" or container.type == "locomotive" or container.type == "cargo-wagon" then
if container.grid then
local grid_usage = 0
for _, equipment in pairs(container.grid.equipment) do
if equipment and equipment.shape and equipment.shape.width and equipment.shape.height then
grid_usage = grid_usage + equipment.shape.width * equipment.shape.height
end
end
spaceship.integrity_stress_structure = spaceship.integrity_stress_structure + grid_usage
spaceship.integrity_stress_container = spaceship.integrity_stress_container + grid_usage
end
end
end
end
spaceship.container_fluid_capacity = 0
local containers = surface.find_entities_filtered{type = {"storage-tank", "fluid-wagon"}, area = area}
for _, container in pairs(containers) do
if container.name ~= BigTurbine.name_big_turbine_tank and container.name ~= CondenserTurbine.name_condenser_turbine_tank and (not string.starts(container.name, "se-space-pipe")) then
local mult = 2
if container.type == "fluid-wagon" then
mult = 0.1
else
if string.find(container.name, "booster", 1, true) then mult = 0.5 end
if container.name == "storage-tank" then mult = 1 end
end
local tile_pos = Util.position_to_tile(container.position)
if area == nil or (spaceship.known_tiles[tile_pos.x] and spaceship.known_tiles[tile_pos.x][tile_pos.y] == Spaceship.tile_status.floor_console_connected) then
if container.fluidbox and #container.fluidbox > 0 then
local i = 1
for i = 1, #container.fluidbox do
if container.fluidbox[1] and container.fluidbox[1].name == "steam" then
spaceship.container_fluid_capacity = spaceship.container_fluid_capacity + 2 * mult * container.fluidbox.get_capacity(i)
else
spaceship.container_fluid_capacity = spaceship.container_fluid_capacity + mult * container.fluidbox.get_capacity(i)
end
end
end
end
end
end
spaceship.speed_multiplier = 1
-- name-based entity modifiers
local names = {}
local name_effects = {}
for _, ia_name in pairs(Spaceship.integrity_affecting_names) do
if ia_name.mod == nil or game.active_mods[ia_name.mod] then
table.insert(names, ia_name.name)
name_effects[ia_name.name] = ia_name
end
end
local entities = surface.find_entities_filtered{name = names, area = area}
for _, entity in pairs(entities) do
local tile_pos = Util.position_to_tile(entity.position)
if area == nil or (spaceship.known_tiles[tile_pos.x] and spaceship.known_tiles[tile_pos.x][tile_pos.y] == Spaceship.tile_status.floor_console_connected) then
local name_effect_set = name_effects[entity.name]
if name_effect_set.integrity_stress_container then
spaceship.integrity_stress_container = spaceship.integrity_stress_container + name_effect_set.integrity_stress_container
end
if name_effect_set.integrity_stress_structure then
spaceship.integrity_stress_structure = spaceship.integrity_stress_structure + name_effect_set.integrity_stress_structure
end
if name_effect_set.max_speed_multiplier then
spaceship.speed_multiplier = math.min(spaceship.speed_multiplier, name_effect_set.max_speed_multiplier)
end
end
end
-- type-based entity modifiers
local types = {}
local type_effects = {}
for _, ia_type in pairs(Spaceship.integrity_affecting_types) do
if ia_type.mod == nil or game.active_mods[ia_type.mod] then
table.insert(types, ia_type.type)
type_effects[ia_type.type] = ia_type
end
end
local entities = surface.find_entities_filtered{type = types, area = area}
for _, entity in pairs(entities) do
local tile_pos = Util.position_to_tile(entity.position)
if area == nil or (spaceship.known_tiles[tile_pos.x] and spaceship.known_tiles[tile_pos.x][tile_pos.y] == Spaceship.tile_status.floor_console_connected) then
local type_effect_set = type_effects[entity.type]
if type_effect_set.integrity_stress_container then
spaceship.integrity_stress_container = spaceship.integrity_stress_container + type_effect_set.integrity_stress_container
end
if type_effect_set.integrity_stress_structure then
spaceship.integrity_stress_structure = spaceship.integrity_stress_structure + type_effect_set.integrity_stress_structure
end
if type_effect_set.max_speed_multiplier then
spaceship.speed_multiplier = math.min(spaceship.speed_multiplier, type_effect_set.max_speed_multiplier)
end
end
end
-- container slot is 0.5 or 24 for a normal container 4800 ish items. Cost is 24
-- container can caryy 48 * 10 barrels = 24k fluid
-- storage tank is 5, 25k fluids = 250 effective items. Cost is 12.5 (50% discount)
-- booster tanks cost50% less
--Log.trace((spaceship.count_empty_tiles or 0) .." / "..(spaceship.tile_count - spaceship.wall_count))
local empty_tiles = spaceship.count_empty_tiles or 0
spaceship.integrity_stress_structure =
spaceship.integrity_stress_structure
+ spaceship.tile_count
+ phantom_tiles * 0.25
- spaceship.wall_count * 0.75
spaceship.integrity_stress_container =
spaceship.integrity_stress_container
+ spaceship.container_slot_count/2
+ spaceship.container_fluid_capacity / 2000
-- if the ship is very long and thin start taking integrity penalties.
local length = spaceship.known_bounds.right_bottom.y - spaceship.known_bounds.left_top.y
-- over 4:1 length gets a penalty of 5% per additional length
local integrity_multiplier = math.max(1, 1 + 0.05 * (length / width_average - 4))
spaceship.integrity_stress_structure = spaceship.integrity_stress_structure * integrity_multiplier
spaceship.integrity_stress_container = spaceship.integrity_stress_container
-- corridor allowance
spaceship.integrity_stress_structure_max = spaceship.integrity_stress_structure
spaceship.integrity_stress_structure = spaceship.integrity_stress_structure_max
-- this encourages keeping 10% of the tital size empty.
- math.min(spaceship.tile_count * 0.1, empty_tiles)
-- this encourages keeping 20% of the internal space empty.
-- math.min((spaceship.tile_count - spaceship.wall_count) * 0.2, empty_tiles)
-- this has the first empty tile discounted by 1, the last by 0, if 20% of the ship is empty they are discouted by 80%
-- empty_tiles * math.max(0, 1 - empty_tiles / (spaceship.tile_count - spaceship.wall_count))
spaceship.integrity_stress = math.max(spaceship.integrity_stress_structure, spaceship.integrity_stress_container)
if spaceship.integrity_stress > spaceship.integrity_limit then
spaceship.integrity_valid = false
spaceship.check_message = {"space-exploration.spaceship-check-message-failed-stress"}
end
end
function Spaceship.check_integrity_stress(spaceship)
spaceship.integrity_limit = Spaceship.get_integrity_limit(game.forces[spaceship.force_name])
if Spaceship.is_on_own_surface(spaceship) then
-- use all tiles
Spaceship.calculate_integrity_stress(spaceship, nil) -- whole area
elseif not (spaceship.console and spaceship.console.valid) then
spaceship.integrity_valid = false
spaceship.check_message= {"space-exploration.spaceship-check-message-no-console"}
elseif not spaceship.integrity_valid then
-- already invalid
elseif not spaceship.known_bounds then
spaceship.integrity_valid = false
spaceship.check_message= {"space-exploration.spaceship-check-message-failed-unknown-bounds"}
elseif spaceship.integrity_valid and spaceship.known_bounds and spaceship.known_tiles then
Spaceship.calculate_integrity_stress(spaceship, spaceship.known_bounds) -- limited area
--[[ TODO: use improved know tiles approach
and spaceship.known_tile_count and spaceship.known_wall_count then
spaceship.integrity_stress = spaceship.known_tile_count - spaceship.known_wall_count / 2
if spaceship.integrity_stress > spaceship.integrity_limit then
spaceship.integrity_valid = false
spaceship.check_message = "Fail: Structural integrity stress exceeds technology limit."
end
]]--
end
end
function Spaceship.start_slow_integrity_check(spaceship, alpha)
spaceship.is_doing_check_slowly = true
Spaceship.start_integrity_check(spaceship, alpha)
end
function Spaceship.start_integrity_check(spaceship, alpha)
if alpha then
spaceship.check_flash_alpha = alpha
end
spaceship.is_doing_check = true
end
function Spaceship.stop_integrity_check(spaceship)
spaceship.check_flash_alpha = nil
spaceship.is_doing_check = nil
spaceship.is_doing_check_slowly = nil
spaceship.check_stage = nil
spaceship.pending_tiles = nil
spaceship.streamline = nil
if spaceship.integrity_valid and spaceship.check_tiles then
-- success
spaceship.count_empty_tiles = spaceship.check_count_empty_tiles
spaceship.check_count_empty_tiles = nil
spaceship.known_tiles = table.deepcopy(spaceship.check_tiles)
spaceship.check_tiles = nil
-- get the average for surface transfer
local min_x = nil
local max_x = nil
local min_y = nil
local max_y = nil
local floor_tiles = 0
local bulkhead_tiles = 0
local front_tiles = {} -- x:y
for x, x_tiles in pairs(spaceship.known_tiles) do
if min_x == nil or x < min_x then min_x = x end
if max_x == nil or x > max_x then max_x = x end
for y, status in pairs(x_tiles) do
if status == Spaceship.tile_status.floor_console_connected
or status == Spaceship.tile_status.bulkhead_console_connected then
if min_y == nil or y < min_y then min_y = y end
if max_y == nil or y > max_y then max_y = y end
if status == Spaceship.tile_status.floor_console_connected then
floor_tiles = floor_tiles + 1
else
bulkhead_tiles = bulkhead_tiles + 1
end
if (not front_tiles[x]) or y < front_tiles[x] then
front_tiles[x] = y
end
end
end
end
max_x = max_x + 1 -- whole tile
max_y = max_y + 1 -- whole tile
spaceship.known_floor_tiles = floor_tiles
spaceship.known_bulkhead_tiles = bulkhead_tiles
spaceship.known_bounds = {left_top = {x = min_x, y = min_y}, right_bottom={x = max_x, y = max_y}}
spaceship.known_tiles_average_x = math.floor((min_x + max_x)/2)
spaceship.known_tiles_average_y = math.floor((min_y + max_y)/2)
local front_tiles_by_y = {} -- y:count
local front_tiles_by_y_left = {} -- y:count
local front_tiles_by_y_right = {} -- y:count
local max_flat = 0
for x, y in pairs(front_tiles) do
front_tiles_by_y[y] = (front_tiles_by_y[y] or 0) + 1
if front_tiles_by_y[y] > max_flat then
max_flat = front_tiles_by_y[y]
end
if x < spaceship.known_tiles_average_x then
front_tiles_by_y_left[y] = (front_tiles_by_y_left[y] or 0) + 1
else
front_tiles_by_y_right[y] = (front_tiles_by_y_right[y] or 0) + 1
end
end
local width = max_x - min_x
-- max_flat == width = 0
-- max_flat == width / 3 = 1
local streamline_flatness = math.min(1, (1 - (max_flat-2) / (width-2)) * 1.5)
local streamline_left = math.min(1, 2 * (table_size(front_tiles_by_y_left)-1) / (math.max(0.5, (width-1) / 2.5)))
local streamline_right = math.min(1, 2 * (table_size(front_tiles_by_y_right)-1) / (math.max(0.5, (width-1) / 2.5)))
spaceship.streamline = (streamline_flatness + streamline_left + streamline_right
+ 3 * math.min(streamline_flatness, math.min(streamline_left, streamline_right))) / 6
--spaceship.streamline = math.min(1, 3.5 * (table_size(front_tiles_by_y)-1) / width)
-- if it is symetrical then 1/2 would be max (excluding the -1)
-- use 1/3.5 as max so there can be a few flat areas
Log.trace("streamline flat "..streamline_flatness.." left "..streamline_left.." right "..streamline_right)
--Log.trace("streamline "..spaceship.streamline )
if Spaceship.is_on_own_surface(spaceship) then
Spaceship.find_own_surface_engines(spaceship)
end
else
spaceship.integrity_valid = false
spaceship.check_tiles = nil
spaceship.check_message = {"space-exploration.spaceship-check-message-failed-empty"}
end
--spaceship.check_message = nil
if spaceship.console and spaceship.console.valid then
--spaceship.console.force.print("Spaceship integrity check complete.")
end
Spaceship.check_integrity_stress(spaceship)
if spaceship.is_launching then
Spaceship.launch(spaceship)
end
end
function Spaceship.integrity_check_tick(spaceship)
if not(spaceship.console and spaceship.console.valid) then
spaceship.check_message= {"space-exploration.spaceship-check-message-no-console"}
spaceship.integrity_valid = false
Spaceship.stop_integrity_check(spaceship)
return
end
local surface = spaceship.console.surface
-- check if the player is around
local player_is_here = not SpaceshipObstacles.surface_has_no_players(surface)
if not (spaceship.check_stage and spaceship.check_tiles and spaceship.pending_tiles) then
local start_tile = surface.get_tile(spaceship.console.position)
if Spaceship.is_floor(start_tile.name) then
Spaceship.check_message = nil
Spaceship.is_doing_check = true
-- floor tiles is a 2d array x then y
spaceship.check_count_empty_tiles = 0
spaceship.check_tiles = {}
spaceship.check_tiles[start_tile.position.x] = {}
spaceship.check_tiles[start_tile.position.x][start_tile.position.y] = Spaceship.tile_status.floor
spaceship.pending_tiles = {}
spaceship.pending_tiles[start_tile.position.x] = {}
spaceship.pending_tiles[start_tile.position.x][start_tile.position.y] = true
spaceship.check_stage = "floor-connectivity"
spaceship.check_message= {"space-exploration.spaceship-check-message-checking-console-floor"}
else
spaceship.check_message= {"space-exploration.spaceship-check-message-failed-console-floor"}
spaceship.integrity_valid = false
Spaceship.stop_integrity_check(spaceship)
return
end
end
if not (spaceship.check_tiles and spaceship.pending_tiles) then return end
local alpha = spaceship.check_flash_alpha or 0.05
-- do a round of checking
-- check_tiles. List of tiles to check this tick.
-- pending_tiles should always exists in check_tiles
-- it basically justs keeps a lst of which ones to search
local next_pending_tiles = {}
local changed = false
if spaceship.check_stage == "floor-connectivity" then
local position_table = {}
for x, x_tiles in pairs(spaceship.pending_tiles) do
for y, yes in pairs(x_tiles) do
local value = spaceship.check_tiles[x][y]
if value == Spaceship.tile_status.floor or value == Spaceship.tile_status.bulkhead then
for d = 1, 4 do -- 4 way direction
local cx = x + (d == 2 and 1 or (d == 4 and -1 or 0))
local cy = y + (d == 1 and -1 or (d == 3 and 1 or 0))
if not (spaceship.check_tiles[cx] and spaceship.check_tiles[cx][cy]) then -- unknown tile
changed = true
local tile = surface.get_tile({cx, cy})
position_table.x = cx + 0.5
position_table.y = cy + 0.5
local wall_count = surface.count_entities_filtered{
position = position_table,
name = Spaceship.names_spaceship_bulkheads
}
if tile.valid and Spaceship.is_floor(tile.name) then
spaceship.check_tiles[cx] = spaceship.check_tiles[cx] or {}
if wall_count > 0 then
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.bulkhead
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {cx, cy}, {r = 0, g = 0, b = 1, a = alpha}, 5)
end
else
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.floor
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {cx, cy}, {r = 0, g = 1, b = 0, a = alpha}, 5)
end
end
next_pending_tiles[cx] = next_pending_tiles[cx] or {}
next_pending_tiles[cx][cy] = true
else
spaceship.check_tiles[cx] = spaceship.check_tiles[cx] or {}
if wall_count > 0 then
position_table.x = cx + 0.5
position_table.y = cy + 0.5
local clamps = surface.count_entities_filtered{
position = position_table,
name = SpaceshipClamp.name_spaceship_clamp_keep
}
if clamps > 0 then
-- if it is a clamp sticking out of the craft treat it as exterior,
-- otherwise it is treated as unstaeble bulkhead and takes damage
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.exterior
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {cx, cy}, {r = 1, g = 0, b = 1, a = alpha}, 5)
end
else
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.bulkhead_exterior
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {cx, cy}, {r = 1, g = 0, b = 0, a = alpha}, 5)
end
end
else
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.exterior
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {cx, cy}, {r = 1, g = 0, b = 1, a = alpha}, 5)
end
end
end
end
end
end
end
end
if changed == false then
-- all connected tiles have been found
-- if in space and if moving detach disconnected tiles
if spaceship.is_moving then
local surface = Spaceship.get_own_surface(spaceship)
if surface then
local set_tiles = {}
local all_tiles = surface.find_tiles_filtered{name=Spaceship.names_spaceship_floors}
for _, tile in pairs(all_tiles) do
if not (spaceship.check_tiles[tile.position.x] and spaceship.check_tiles[tile.position.x][tile.position.y]) then
local stack = tile.prototype.items_to_place_this[1]
table.insert(set_tiles, {name = name_space_tile, position = tile.position, ghost_name = tile.name, stack = stack})
if player_is_here then
Spaceship.flash_tile(surface, tile.position, {r = 1, g = 0, b = 0, a = alpha}, 120)
end
end
end
if #set_tiles > 0 then
surface.set_tiles(set_tiles)
for _, tile in pairs(set_tiles) do
if Util.table_contains(Spaceship.names_spaceship_floors, tile.ghost_name) then
surface.create_entity{name = "tile-ghost", inner_name = tile.ghost_name, force = spaceship.force_name, position=tile.position}
end
end
end
end
end
changed = true
spaceship.check_stage = "containment"
spaceship.check_message = {"space-exploration.spaceship-check-message-checking-containment"}
for x, x_tiles in pairs(spaceship.check_tiles) do
for y, status in pairs(x_tiles) do
if status == Spaceship.tile_status.exterior
or status == Spaceship.tile_status.bulkhead_exterior then
next_pending_tiles[x] = next_pending_tiles[x] or {}
next_pending_tiles[x][y] = true
end
end
end
end
elseif spaceship.check_stage == "containment" then
for x, x_tiles in pairs(spaceship.pending_tiles) do
for y, yes in pairs(x_tiles) do
local value = spaceship.check_tiles[x][y]
if value == Spaceship.tile_status.exterior
or value == Spaceship.tile_status.bulkhead_exterior
or value == Spaceship.tile_status.floor_exterior then
for cx = x-1, x+1 do
for cy = y-1, y+1 do
if spaceship.check_tiles[cx] and spaceship.check_tiles[cx][cy]
and spaceship.check_tiles[cx][cy] == Spaceship.tile_status.floor then
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.floor_exterior
changed = true
next_pending_tiles[cx] = next_pending_tiles[cx] or {}
next_pending_tiles[cx][cy] = true
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {cx, cy}, {r = 1, g = 1, b = 0, a = alpha}, 30)
end
end
end
end
end
end
end
if changed == false then
changed = true
-- convert non-exterior floor to interior
for x, x_tiles in pairs(spaceship.check_tiles) do
for y, status in pairs(x_tiles) do
if status == Spaceship.tile_status.floor then
spaceship.check_tiles[x][y] = Spaceship.tile_status.floor_interior
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {x, y}, {r = 0, g = 0, b = 1, a = alpha}, 40)
end
end
end
end
local console_tile_x = math.floor(spaceship.console.position.x)
local console_tile_y = math.floor(spaceship.console.position.y)
if spaceship.check_tiles and spaceship.check_tiles[console_tile_x] and spaceship.check_tiles[console_tile_x][console_tile_y] == Spaceship.tile_status.floor_interior then
spaceship.check_tiles[console_tile_x][console_tile_y] = Spaceship.tile_status.floor_console_connected
next_pending_tiles[console_tile_x] = {}
next_pending_tiles[console_tile_x][console_tile_y] = true
spaceship.check_stage = "console-connectivity"
spaceship.check_message = {"space-exploration.spaceship-check-message-checking-connectivity"}
else
for x, x_tiles in pairs(spaceship.check_tiles) do
for y, status in pairs(x_tiles) do
if status == Spaceship.tile_status.exterior
or status == Spaceship.tile_status.bulkhead_exterior then
if player_is_here then
Spaceship.flash_tile(surface, {x, y}, {r = 1, g = 0, b = 0, a = alpha}, 120)
end
end
end
end
spaceship.integrity_valid = false
spaceship.check_message = {"space-exploration.spaceship-check-message-failed-containment"}
return Spaceship.stop_integrity_check(spaceship)
end
end
elseif spaceship.check_stage == "console-connectivity" then
for x, x_tiles in pairs(spaceship.pending_tiles) do
for y, yes in pairs(x_tiles) do
local value = spaceship.check_tiles[x][y]
if value == Spaceship.tile_status.floor_console_connected then
local blockers = surface.count_entities_filtered{
position = {x + 0.5, y + 0.5},
collision_mask = {"object-layer"}
}
if blockers == 0 then -- this tile is clear, add to corridor allowance.
spaceship.check_count_empty_tiles = (spaceship.check_count_empty_tiles or 0) + 1
end
end
if value == Spaceship.tile_status.floor_console_connected
or value == Spaceship.tile_status.bulkhead_console_connected then
for d = 1, 4 do -- 4 way direction
local cx = x + (d == 2 and 1 or (d == 4 and -1 or 0))
local cy = y + (d == 1 and -1 or (d == 3 and 1 or 0))
if spaceship.check_tiles[cx] and spaceship.check_tiles[cx][cy] and
(spaceship.check_tiles[cx][cy] == Spaceship.tile_status.floor_interior
or spaceship.check_tiles[cx][cy] == Spaceship.tile_status.bulkhead) then
if spaceship.check_tiles[cx][cy] == Spaceship.tile_status.floor_interior then
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.floor_console_connected
else
spaceship.check_tiles[cx][cy] = Spaceship.tile_status.bulkhead_console_connected
end
if player_is_here and not spaceship.is_doing_check_slowly then
Spaceship.flash_tile(surface, {cx, cy}, {r = 0, g = 1, b = 1, a = alpha}, 5)
end
changed = true
next_pending_tiles[cx] = next_pending_tiles[cx] or {}
next_pending_tiles[cx][cy] = true
end
end
end
end
end
if changed == false then
-- completed the check
spaceship.check_message = {"space-exploration.spaceship-check-message-passed"}
spaceship.integrity_valid = true
local set_tiles = {}
local reset = false
for x, x_tiles in pairs(spaceship.check_tiles) do
for y, status in pairs(x_tiles) do
if not (status == Spaceship.tile_status.floor_console_connected
or status == Spaceship.tile_status.bulkhead_console_connected
or status == Spaceship.tile_status.exterior) then
spaceship.check_message = {"space-exploration.spaceship-check-message-unstable"}
if player_is_here then
Spaceship.flash_tile(surface, {x, y}, {r = 1, g = 0, b = 0, a = alpha}, 120)
end
-- detatch
if Spaceship.is_on_own_surface(spaceship) and spaceship.is_moving then
local support = 1 -- it will count self
for cx = x-1, x+1 do
for cy = y-1, y+1 do
if spaceship.check_tiles[cx] and spaceship.check_tiles[cx][cy] then
if spaceship.check_tiles[cx][cy] ~= Spaceship.tile_status.exterior then
support = support + 1
if spaceship.check_tiles[cx][cy] == Spaceship.bulkhead_console_connected then
support = support + 2
end
end
end
end
end
if support <= 6 then -- has a chance to be removed
reset = true
if support - math.random(2) <= 4 then
local entities = surface.find_entities({{x,y}, {x+1,y+1}})
local remove = true
for _, entity in pairs(entities) do
if entity and entity.valid and entity.type ~= "character" and entity.health then
entity.damage(150, "neutral", "explosion")
remove = false
end
end
if remove then
local tile = surface.get_tile(x,y)
table.insert(set_tiles, {name = name_space_tile, ghost_name=tile.name, position = {x,y}})
end
end
end
end
end
end
end
if #set_tiles > 0 then
spaceship.check_message = {"space-exploration.spaceship-check-message-valid-but-disconnecting"}
surface.print({"space-exploration.spaceship-warning-sections-disconnecting"})
surface.set_tiles(set_tiles)
for _, tile in pairs(set_tiles) do
if Spaceship.is_floor(tile.ghost_name) then
surface.create_entity{name = "tile-ghost", inner_name = tile.ghost_name, force = spaceship.force_name, position=tile.position}
end
end
end
Spaceship.stop_integrity_check(spaceship)
if reset then
Spaceship.start_integrity_check(spaceship)
return
else
Spaceship.get_compute_launch_energy(spaceship)
return
end
end
end
if changed then
spaceship.pending_tiles = next_pending_tiles
else
spaceship.integrity_valid = false
spaceship.check_message = {"space-exploration.spaceship-check-message-did-not-complete"}
return Spaceship.stop_integrity_check(spaceship)
end
end
function Spaceship.on_init(event)
global.spaceships = {}
end
Event.addListener("on_init", Spaceship.on_init, true)
return Spaceship