local SpaceshipObstacles = {} SpaceshipObstacles.particle_enemy_proxy = mod_prefix .. "spaceship-enemy-proxy" SpaceshipObstacles.particle_travel_anchor = mod_prefix .. "spaceship-travel-anchor" SpaceshipObstacles.particle_hp_multiplier = 300 SpaceshipObstacles.bounding_box_buffer = 2 -- This dictates how much sideways velocity the asteroids get -- (0.00 is purely vertical) --SpaceshipObstacles.particle_orientation_variance = 0.00 SpaceshipObstacles.particle_orientation_variance = 0.03 -- sprite_prefix: append variation index, used by rendering api -- vehicle_name: the actual vehicle being used to move the asteroid -- Speed values are not true speed, asteroid areas have a multiplier. SpaceshipObstacles.particles = { ["speck"] = { multiplier = 1/1000, speed = 1/100, min_speed = 0, max_effective_speed = 50, sprite_name = mod_prefix.."spaceship-speck-graphic" }, ["small"] = { multiplier = 1/8000, speed = 1/400, min_speed = 5, variations = 16, scale = 0.25, max_effective_speed = 100, sprite_prefix = mod_prefix .. "spaceship-obstacle-small-sprite-", vehicle_name = mod_prefix .. "spaceship-obstacle-small-vehicle", }, ["medium"] = { multiplier = 1/60000, speed = 1/450, min_speed = 25, destroys_floor = true, variations = 16, scale = 0.5, max_effective_speed = 200, sprite_prefix = mod_prefix .. "spaceship-obstacle-medium-sprite-", vehicle_name = mod_prefix .. "spaceship-obstacle-medium-vehicle", }, ["large"] = { multiplier = 1/3000000, speed = 1/500, min_speed = 100, min_size = 1000, destroys_floor = true, variations = 16, scale = 1, sprite_prefix = mod_prefix .. "spaceship-obstacle-large-sprite-", vehicle_name = mod_prefix .. "spaceship-obstacle-large-vehicle", }, } SpaceshipObstacles.entity_particle_templates = { ["ice"] = {speed = 1/300}, ["small"] = { speed = 1/300, projectile_name = mod_prefix.."spaceship-obstacle-entity-small-projectile", targetable_name = mod_prefix.."spaceship-obstacle-entity-small-targetable", }, ["large"] = { speed = 1/600, projectile_name = mod_prefix.."spaceship-obstacle-entity-large-projectile", targetable_name = mod_prefix.."spaceship-obstacle-entity-large-targetable", } } -- names of the entities used to make the asteroids mineable when a ship is stopped SpaceshipObstacles.mineable_particles = {} for x=1,16 do table.insert(SpaceshipObstacles.mineable_particles, mod_prefix .. "spaceship-obstacle-small-static-"..x) table.insert(SpaceshipObstacles.mineable_particles, mod_prefix .. "spaceship-obstacle-medium-static-"..x) table.insert(SpaceshipObstacles.mineable_particles, mod_prefix .. "spaceship-obstacle-large-static-"..x) end SpaceshipObstacles.collision_slowdown_factor = 0.98 -- slow down the ship when an obstacle collides with it SpaceshipObstacles.particle_spawn_range = 32 -- same as laser turret prepare range SpaceshipObstacles.all_targetables = {} for _, particle in pairs(SpaceshipObstacles.particles) do if particle.vehicle_name then table.insert(SpaceshipObstacles.all_targetables, particle.vehicle_name) end end SpaceshipObstacles.obstacle_damage_multipliers = { ["electric-turret"] = 120, ["ammo-turret"] = 12 } SpaceshipObstacles.default_asteroid_density = 1 SpaceshipObstacles.asteroid_density_by_zone_type = { ["asteroid-belt"] = 2, ["asteroid-field"] = 3 } SpaceshipObstacles.near_enough_by_zone_type = { ["asteroid-belt"] = 300, ["asteroid-field"] = 4500 } function SpaceshipObstacles.particle_speed_factor(spaceship_speed) -- particle speed from spaceship speed return spaceship_speed > 0 and math.pow(spaceship_speed / Spaceship.speed_taper, Spaceship.particle_speed_power) * Spaceship.speed_taper or 0 end --- Gets the asteroid density caption for a spaceship ---@param spaceship Spaceship the spaceship data function SpaceshipObstacles.get_asteroid_density_caption(spaceship) if not spaceship.asteroid_density then return {"space-exploration.spaceship-asteroid-density-default"} end if spaceship.asteroid_density < SpaceshipObstacles.asteroid_density_by_zone_type["asteroid-belt"] then return {"space-exploration.spaceship-asteroid-density-default"} end if spaceship.asteroid_density < SpaceshipObstacles.asteroid_density_by_zone_type["asteroid-field"] then return {"space-exploration.spaceship-asteroid-density-belt"} end return {"space-exploration.spaceship-asteroid-density-field"} end --- Gets the asteroid density for a spaceship ---@param spaceship Spaceship the spaceship data function SpaceshipObstacles.get_asteroid_density(spaceship) local density = SpaceshipObstacles.default_asteroid_density local zone = Zone.find_nearest_zone( spaceship.space_distortion, spaceship.stellar_position, spaceship.star_gravity_well, spaceship.planet_gravity_well ) local delta_v = Zone.get_travel_delta_v_sub(spaceship, zone) if zone and zone.type and SpaceshipObstacles.asteroid_density_by_zone_type[zone.type] and delta_v < SpaceshipObstacles.near_enough_by_zone_type[zone.type] then density = SpaceshipObstacles.asteroid_density_by_zone_type[zone.type] end return density end --[[ Destroys all particles on a surface ]] function SpaceshipObstacles.destroy(spaceship, surface) if spaceship.particle_object_ids then for _, ids in pairs(spaceship.particle_object_ids) do for _, id in pairs(ids) do rendering.destroy(id) end end spaceship.particle_object_ids = nil end local destroy_names = {} table.insert(destroy_names, SpaceshipObstacles.particle_travel_anchor) for _, particle in pairs(SpaceshipObstacles.particles) do if particle.vehicle_name then table.insert(destroy_names, particle.vehicle_name) end end for _, mineable_particle in pairs(SpaceshipObstacles.mineable_particles) do table.insert(destroy_names, mineable_particle) end local destroy_entities = surface.find_entities_filtered{name = destroy_names} for _, entity in pairs(destroy_entities) do -- this does not raise any event! -- it will silently destroy the entity -- if any mod was tracking the space rocks, it will get borked by this -- why do potentially other-mod breaking thing? -- we do this for performance reasons because raising events is expensive entity.destroy() end for _, proxy in pairs(surface.find_entities_filtered{name = SpaceshipObstacles.particle_enemy_proxy}) do util.safe_destroy(proxy) end end --[[ Travel anchor particles are used for visual only particles that move exactly downward. One particle may have many sprites attached to it. As such, travel anchor particles are reused based on speed and position along the y-axis. This will get the travel anchor particle for a speed and position, or create it if it does not exist. ]] function SpaceshipObstacles.get_make_travel_anchor_particle(spaceship, surface, y, particle_template) local speed = particle_template.speed if not (spaceship.travel_anchor_particles and spaceship.travel_anchor_particles[speed] and spaceship.travel_anchor_particles[speed].valid and y + 10 > spaceship.travel_anchor_particles[speed].position.y) then spaceship.travel_anchor_particles = spaceship.travel_anchor_particles or {} spaceship.travel_anchor_particles[speed] = surface.create_entity({ name = SpaceshipObstacles.particle_travel_anchor, position = { x = spaceship.known_bounds.left_top.x - SpaceshipObstacles.particle_spawn_range, y = spaceship.known_bounds.left_top.y - SpaceshipObstacles.particle_spawn_range - 10 - 10 }, direction = defines.direction.south }) table.insert(spaceship.particles, { valid = true, vehicle = spaceship.travel_anchor_particles[speed], speed = speed, }) end return spaceship.travel_anchor_particles[speed] end --[[ Computes the number of asteroids to spawn for a certain template. The number of asteroids to spawn is based on the spaceship's speed and the time passed. ]] function SpaceshipObstacles.get_num_particles_to_spawn(spaceship, time_passed, particle_template) local density = spaceship.asteroid_density or SpaceshipObstacles.default_asteroid_density local effective_speed = spaceship.speed * density if particle_template.max_effective_speed then effective_speed = math.min(effective_speed, particle_template.max_effective_speed) end local spawn_particles_quota = (Spaceship.minimum_mass + spaceship.integrity_stress) * effective_speed / 60 * particle_template.multiplier * time_passed local spawn_particles_whole = math.floor(spawn_particles_quota) -- we can only ever spawn a whole number of particles so use math.random() to average out the decimal part of spawn_particles_quota local spawn_particles_chance = spawn_particles_quota - spawn_particles_whole if math.random() <= spawn_particles_chance then spawn_particles_whole = spawn_particles_whole + 1 end return spawn_particles_whole end --[[ Determines if a surface has no players on it. Useful for optimizing away visual particles if no one can see them. ]] function SpaceshipObstacles.surface_has_no_players(surface) local no_players = true for _, player in pairs(game.players) do if player.valid and player.surface.index == surface.index then no_players = false end end return no_players end --[[ Spawns a visual only particle. These particles are always just sprites attached to a travel anchor particle by LuaRendering.draw_sprite ]] function SpaceshipObstacles.spawn_particles_visual_only(spaceship, surface, time_passed, particle_template) local spawn_particles_whole = SpaceshipObstacles.get_num_particles_to_spawn(spaceship, time_passed, particle_template) if spawn_particles_whole > 0 then local speed_factor = SpaceshipObstacles.particle_speed_factor(spaceship.speed) local x_min = spaceship.known_bounds.left_top.x - SpaceshipObstacles.particle_spawn_range local x_max = spaceship.known_bounds.right_bottom.x + SpaceshipObstacles.particle_spawn_range local spawn_y = spaceship.known_bounds.left_top.y - SpaceshipObstacles.particle_spawn_range - 20 local travel_anchor_particle = SpaceshipObstacles.get_make_travel_anchor_particle(spaceship, surface, spawn_y, particle_template) spaceship.particle_object_ids = spaceship.particle_object_ids or {} spaceship.particle_object_ids[travel_anchor_particle.unit_number] = spaceship.particle_object_ids[travel_anchor_particle.unit_number] or {} travel_anchor_particle.speed = particle_template.speed * spaceship.speed local x_delta = x_max - x_min local y_factor = time_passed * speed_factor * particle_template.speed for i = 1, spawn_particles_whole do local spawn_x = x_min + math.random() * x_delta local this_spawn_y = spawn_y - math.random() * y_factor local object_id = rendering.draw_sprite({ surface = surface, sprite = particle_template.sprite_name, target = travel_anchor_particle, target_offset = { x = spawn_x - travel_anchor_particle.position.x, y = this_spawn_y - travel_anchor_particle.position.y } }) table.insert(spaceship.particle_object_ids[travel_anchor_particle.unit_number], object_id) end end end --[[ Spawns a non-visual only particle. Asteroids are invisible vehicles, with an invisible enemy character riding them that have a graphic attached with LuaRendering.draw_sprite ]] function SpaceshipObstacles.spawn_particles_non_visual(spaceship, surface, time_passed, particle_template, no_players) local spawn_particles_whole = SpaceshipObstacles.get_num_particles_to_spawn(spaceship, time_passed, particle_template) if spawn_particles_whole > 0 then local speed_factor = SpaceshipObstacles.particle_speed_factor(spaceship.speed) local x_min = spaceship.known_bounds.left_top.x - SpaceshipObstacles.particle_spawn_range local x_max = spaceship.known_bounds.right_bottom.x + SpaceshipObstacles.particle_spawn_range local spawn_y = spaceship.known_bounds.left_top.y - SpaceshipObstacles.particle_spawn_range - 20 -- aabb cannot be exactly the ship bounds since asteroids are not points (they have a rectangular bounding box) local aabb = { min_x = spaceship.known_bounds.left_top.x - SpaceshipObstacles.bounding_box_buffer, max_x = spaceship.known_bounds.right_bottom.x + SpaceshipObstacles.bounding_box_buffer, min_y = spaceship.known_bounds.left_top.y - SpaceshipObstacles.bounding_box_buffer, max_y = spaceship.known_bounds.right_bottom.y + SpaceshipObstacles.bounding_box_buffer } local speed = particle_template.speed * spaceship.speed local x_delta = x_max - x_min local y_factor = time_passed * speed_factor * particle_template.speed for i = 1, spawn_particles_whole do local spawn_x = x_min + math.random() * x_delta local this_spawn_y = spawn_y - math.random() * y_factor local spawn_orientation = 0.5 - SpaceshipObstacles.particle_orientation_variance * (math.random() - math.random()) local intersects = util.intersects_ray_aabb({x=spawn_x,y=spawn_y}, spawn_orientation, aabb) if not no_players or intersects then local particle = table.deepcopy(particle_template) particle.valid = true particle.variation = math.random(particle.variations) particle.vehicle = surface.create_entity{ name = particle_template.vehicle_name, position = {x = spawn_x, y = this_spawn_y }, create_build_effect_smoke = false, } particle.vehicle.orientation = spawn_orientation -- smart turrets only attack asteroids that will hit the ship if intersects then particle.enemy_proxy = surface.create_entity{ name = SpaceshipObstacles.particle_enemy_proxy, position = {x = spawn_x, y = this_spawn_y }, } particle.vehicle.set_driver(particle.enemy_proxy) end rendering.draw_sprite{ sprite = particle.sprite_prefix..particle.variation, surface = surface, target = particle.vehicle, scale = particle.scale } particle.vehicle.speed = speed table.insert(spaceship.particles, particle) end end end end --[[ Spawns particles from a particle_template. ]] function SpaceshipObstacles.spawn_particles_speed_size(spaceship, surface, time_passed, particle_template) -- only spawn the asteroids if the conditions on their template are met if spaceship.speed > (particle_template.min_speed or 0) and spaceship.integrity_stress > (particle_template.min_size or 0) then local no_players = SpaceshipObstacles.surface_has_no_players(surface) if particle_template.sprite_name then -- visual particles are only spawned if players are on that surface if not no_players then SpaceshipObstacles.spawn_particles_visual_only(spaceship, surface, time_passed, particle_template) end else SpaceshipObstacles.spawn_particles_non_visual(spaceship, surface, time_passed, particle_template, no_players) end end end --[[ Resolves a collision between a particle and an obstacle it collided with. Either the particle or the obstacle will survive, not both. If the particle has enough effective health to kill the obstacle, the obstacle dies and the particle loses hp Otherwise, the obstacle has enough effective health to kill the particle, so the particle dies and the obstacle loses hp ]] function SpaceshipObstacles.smash_particle_to_entity(particle_entity, other_entity) local particle_effective_health = particle_entity.health / SpaceshipObstacles.particle_hp_multiplier if particle_effective_health > other_entity.health then -- the particle "wins" the collision local other_effective_health = other_entity.health * SpaceshipObstacles.particle_hp_multiplier particle_entity.health = particle_entity.health - other_effective_health other_entity.die() else -- the obstacle "wins" the collision other_entity.health = other_entity.health - particle_effective_health if particle_entity.type == "car" and particle_entity.get_driver() then particle_entity.get_driver().destroy() end particle_entity.die() end end --[[ Listen to the event when an entity is damaged. Necessary in order to modify the damage done to/from particles ]] function SpaceshipObstacles.on_entity_damaged(event) --[[ In a vehicle collision: the struck object takes damage first with the vehicle as the cause. Then the striking vehile takes damage with no cause. (may be the crash trigger damage?) ]] if event.entity and event.entity.valid then if Util.table_contains(SpaceshipObstacles.all_targetables, event.entity.name) then if event.cause and event.cause.valid then if SpaceshipObstacles.obstacle_damage_multipliers[event.cause.type] then -- take more damage from certain sources. event.entity.health = event.entity.health - event.final_damage_amount * (SpaceshipObstacles.obstacle_damage_multipliers[event.cause.type] - 1) end end event.entity.surface.create_trivial_smoke{name = "smoke", position = event.entity.position } elseif event.cause and event.cause.valid and Util.table_contains(SpaceshipObstacles.all_targetables, event.cause.name) then if event.damage_type.name == "impact" then if event.entity.name ~= "shield-projector-barrier" then local spaceship = Spaceship.from_own_surface_index(event.entity.surface.index) if spaceship then spaceship.speed = spaceship.speed * SpaceshipObstacles.collision_slowdown_factor end end SpaceshipObstacles.smash_particle_to_entity(event.cause, event.entity) if event.cause.valid then global.spaceship_projectile_speeds = global.spaceship_projectile_speeds or {} table.insert(global.spaceship_projectile_speeds, {entity = event.cause, speed = event.cause.speed}) end end end end end Event.addListener(defines.events.on_entity_damaged, SpaceshipObstacles.on_entity_damaged) function SpaceshipObstacles.tick_projectile_speeds() if global.spaceship_projectile_speeds then for _, ps in pairs(global.spaceship_projectile_speeds) do if ps.entity and ps.entity.valid and ps.entity.type == "car" then ps.entity.speed = ps.speed end end global.spaceship_projectile_speeds = nil end end --[[ Listen for when vehicles are destroyed so we can destroy their dummy drivers. ]] function SpaceshipObstacles.on_removed_entity(event) if event.entity and event.entity.valid then if event.entity.type == "car" and Util.table_contains(SpaceshipObstacles.all_targetables, event.entity.name) then local driver = event.entity.get_driver() if driver then driver.destroy() end end end end Event.addListener(defines.events.on_entity_died, SpaceshipObstacles.on_removed_entity) Event.addListener(defines.events.on_robot_mined_entity, SpaceshipObstacles.on_removed_entity) Event.addListener(defines.events.on_player_mined_entity, SpaceshipObstacles.on_removed_entity) Event.addListener(defines.events.script_raised_destroy, SpaceshipObstacles.on_removed_entity) --- Adds an entity to the pool of moveable entities. --- Moveable entities act like obstacles in that: --- 1. they move downwards --- 2. they are targetted by turrets --- 3. they get destroyed by shields --- They are dissimilar in that: --- 1. they don't damage anything on the ship --- 2. they suck for UPS ---@param spaceship any ---@param entity any function SpaceshipObstacles.add_moveable_entity(spaceship, entity, name) if spaceship and entity then spaceship.entity_particles = spaceship.entity_particles or {} local entity_particle = table.deepcopy(SpaceshipObstacles.entity_particle_templates[name]) entity_particle.graphic = entity entity_particle.valid = true table.insert(spaceship.entity_particles, entity_particle) end end --- Debris needs to be moved. This trigger listens for when that is created on a spaceship surface, --- and if so, puts it in the list of things that need to move. ---@param event any function SpaceshipObstacles.on_trigger_created_entity(event) if event.entity and event.entity.valid and event.entity.name == mod_prefix.."trigger-movable-debris" then local surface = event.entity.surface local spaceship = Spaceship.from_own_surface_index(surface.index) if spaceship then local entities = surface.find_entities_filtered{type = "simple-entity", area = Util.position_to_area(event.entity.position, 0.5)} for _, entity in pairs(entities) do if string.find(entity.name, "meteor") then SpaceshipObstacles.add_moveable_entity(spaceship, entity, "large") else SpaceshipObstacles.add_moveable_entity(spaceship, entity, "small") end end end end end Event.addListener(defines.events.on_trigger_created_entity, SpaceshipObstacles.on_trigger_created_entity) --- Moves the moveable entities. Handles destroying them safely if one of the component entities is destroyed or if they get offscreen. ---@param spaceship Spaceship spaceship data ---@param surface LuaSurface spaceship surface function SpaceshipObstacles.tick_entity_obstacles(spaceship, surface) spaceship.entity_particles = spaceship.entity_particles or {} local speed_factor = SpaceshipObstacles.particle_speed_factor(spaceship.speed) local upper_bound = spaceship.known_bounds.right_bottom.y + SpaceshipObstacles.particle_spawn_range + 32 for _, entity_particle in pairs(spaceship.entity_particles) do if not (entity_particle.graphic and entity_particle.graphic.valid) then -- killed or mined entity_particle.valid = false elseif entity_particle.graphic.position.y > upper_bound then -- out of range entity_particle.valid = false elseif entity_particle.projectile and entity_particle.projectile.valid == false then -- projectile was there but detonated entity_particle.valid = false elseif entity_particle.targetable and entity_particle.targetable.valid == false then -- projectile detonated entity_particle.valid = false end if not entity_particle.valid then -- destroy invalid entity particles if entity_particle.graphic and entity_particle.graphic.valid then entity_particle.graphic.destroy() entity_particle.graphic = nil end if entity_particle.projectile and entity_particle.projectile.valid then entity_particle.projectile.destroy() entity_particle.projectile = nil end if entity_particle.targetable and entity_particle.targetable.valid then entity_particle.targetable.destroy() entity_particle.targetable = nil end spaceship.entity_particles[_] = nil else -- update the speed and move valid entity particles local position = entity_particle.graphic.position position.y = position.y + entity_particle.speed * speed_factor if spaceship.speed > 0.001 then entity_particle.graphic.teleport(position) if entity_particle.projectile then entity_particle.projectile.speed = entity_particle.speed * speed_factor elseif entity_particle.projectile_name then entity_particle.projectile = surface.create_entity{ name = entity_particle.projectile_name, -- particle would overtake the rest if we didn't subtract the current vector position = util.vectors_add(position, {x = 0, y = -entity_particle.speed * speed_factor}), speed = entity_particle.speed * speed_factor, target = {x = position.x, y = position.y + 100}, force="enemy" } end if entity_particle.targetable then entity_particle.targetable.teleport(position) elseif entity_particle.targetable_name then entity_particle.targetable = surface.create_entity{ name = entity_particle.targetable_name, position = position, target = {x = position.x, y = position.y + 100}, force="enemy" } end else if entity_particle.projectile and entity_particle.projectile.valid then entity_particle.projectile.destroy() entity_particle.projectile = nil end if entity_particle.targetable and entity_particle.targetable.valid then entity_particle.targetable.destroy() entity_particle.targetable = nil end end end end end function SpaceshipObstacles.tick_obstacles(spaceship, surface, time_passed) spaceship.particles = spaceship.particles or {} spaceship.mineables = spaceship.mineables or {} -- spawn particles of each template for _, particle_template in pairs(SpaceshipObstacles.particles) do SpaceshipObstacles.spawn_particles_speed_size(spaceship, surface, time_passed, particle_template) end -- update particles if spaceship.particles then local speed_factor = SpaceshipObstacles.particle_speed_factor(spaceship.speed) local upper_bound = spaceship.known_bounds.right_bottom.y + SpaceshipObstacles.particle_spawn_range + 32 for _, particle in pairs(spaceship.particles) do if not (particle.vehicle and particle.vehicle.valid and particle.vehicle.position.y < upper_bound) then -- the particle is in bounds particle.valid = false end if not particle.valid then if particle.vehicle and particle.vehicle.valid then particle.vehicle.destroy() particle.vehicle = nil end if particle.enemy_proxy and particle.enemy_proxy.valid then particle.enemy_proxy.destroy() particle.enemy_proxy = nil end spaceship.particles[_] = nil end if particle and particle.valid then -- particle is still valid particle.vehicle.speed = particle.speed * speed_factor local targetable = particle.vehicle local position = targetable.position if targetable and particle.destroys_floor then -- destroy floor local x = math.floor(position.x) local y = math.floor(position.y) if spaceship.known_tiles[x] and spaceship.known_tiles[x][y] then spaceship.known_tiles[x][y] = nil local tile = surface.get_tile(x, y) local tile_name = tile.name if tile_name ~= name_space_tile then targetable.damage(50, "neutral", "explosion") -- damage the particle local entities = surface.find_entities_filtered{ -- destroy entities on the floor area = util.tile_to_area({x=x,y=y}), collision_mask = { global.named_collision_masks.space_collision_layer, "object-layer", "resource-layer" } } for _, entity in pairs(entities) do if entity and entity.valid then entity.die() end end surface.set_tiles( {{name=name_space_tile, position = {x,y}}}, false, -- corect tiles true, -- remove_colliding_entities true, -- remove_colliding_decoratives true -- raise_event ) if Util.table_contains(Spaceship.names_spaceship_floors, tile_name) then -- make blueprint for the tile. surface.create_entity{name = "tile-ghost", inner_name = tile_name, force = spaceship.force_name, position = {x=x,y=y}} end Spaceship.start_integrity_check(spaceship) end end end end end end -- swap vehicles with mineables and vice-versa when the spaceship changes between stopped and moving SpaceshipObstacles.maybe_swap_vehicles_mineables(spaceship, surface) end --[[ Converts name + variation from vehicle asteroids to mineable asteroids ]] function SpaceshipObstacles.static_name_for_vehicle_name_and_variation(vehicle_name, variation) if vehicle_name == mod_prefix .. "spaceship-obstacle-small-vehicle" then return mod_prefix .. "spaceship-obstacle-small-static-"..variation elseif vehicle_name == mod_prefix .. "spaceship-obstacle-medium-vehicle" then return mod_prefix .. "spaceship-obstacle-medium-static-"..variation elseif vehicle_name == mod_prefix .. "spaceship-obstacle-large-vehicle" then return mod_prefix .. "spaceship-obstacle-large-static-"..variation else return nil end end --[[ Converts name + variation from mineable asteroids to vehicle asteroids ]] function SpaceshipObstacles.particle_template_for_static_name(static_name, variation) if string.find(static_name,"spaceship-obstacle-small-static-", 1, true) then return SpaceshipObstacles.particles["small"], variation elseif string.find(static_name,"spaceship-obstacle-medium-static-", 1, true) then return SpaceshipObstacles.particles["medium"], variation elseif string.find(static_name,"spaceship-obstacle-large-static-", 1, true) then return SpaceshipObstacles.particles["large"], variation else return nil, 1 end end --[[ Tries to swap vehicles asteroids with mineable asteroids when the spaceship's speed goes from moving to stopped Tries to swap mineable asteroids with vehicle asteroids when the spaceship's speed goes from stopped to moving ]] function SpaceshipObstacles.maybe_swap_vehicles_mineables(spaceship, surface) if spaceship.speed and spaceship.last_speed then if spaceship.speed == 0 and spaceship.speed ~= spaceship.last_speed then -- spaceship came to a stop, replace vehicles with mineables for _, particle in pairs(spaceship.particles) do if particle.valid and particle.vehicle.valid then -- make the mineable local mineable_name = SpaceshipObstacles.static_name_for_vehicle_name_and_variation(particle.vehicle.name, particle.variation) if mineable_name then local mineable = { mineable = surface.create_entity{ name=mineable_name, position=particle.vehicle.position, force="enemy" }, variation=particle.variation } table.insert(spaceship.mineables, mineable) end -- destroy the vehicle if particle.vehicle and particle.vehicle.valid then particle.vehicle.destroy() particle.vehicle = nil end if particle.enemy_proxy and particle.enemy_proxy.valid then particle.enemy_proxy.destroy() particle.enemy_proxy = nil end spaceship.particles[_] = nil end end elseif spaceship.last_speed == 0 and spaceship.speed ~= spaceship.last_speed then -- spaceship is starting moving, replaces mineables with vehicles local aabb = { min_x = spaceship.known_bounds.left_top.x, max_x = spaceship.known_bounds.right_bottom.x, min_y = spaceship.known_bounds.left_top.y, max_y = spaceship.known_bounds.right_bottom.y } local no_players = true for _, player in pairs(game.players) do if player.valid and player.surface.index == surface.index then no_players = false end end for _, mineable in pairs(spaceship.mineables) do if mineable.mineable and mineable.mineable.valid then -- make the vehicle local particle_template, variation = SpaceshipObstacles.particle_template_for_static_name(mineable.mineable.name, mineable.variation) if particle_template then local speed = particle_template.speed * spaceship.speed local spawn_orientation = 0.5 - SpaceshipObstacles.particle_orientation_variance * (math.random() - math.random()) local intersects = util.intersects_ray_aabb({x=mineable.mineable.position.x,y=mineable.mineable.position.y}, spawn_orientation, aabb) if not no_players or intersects then local particle = table.deepcopy(particle_template) particle.valid = true particle.variation = variation particle.vehicle = surface.create_entity{ name = particle_template.vehicle_name, position = {x = mineable.mineable.position.x, y = mineable.mineable.position.y }, create_build_effect_smoke = false, } particle.vehicle.orientation = spawn_orientation -- smart turrets only attack asteroids that will hit the ship if intersects then particle.enemy_proxy = surface.create_entity{ name = SpaceshipObstacles.particle_enemy_proxy, position = {x = mineable.mineable.position.x, y = mineable.mineable.position.y }, } particle.vehicle.set_driver(particle.enemy_proxy) end rendering.draw_sprite{ sprite = particle.sprite_prefix..particle.variation, surface = surface, target = particle.vehicle, scale = particle.scale } particle.vehicle.speed = speed table.insert(spaceship.particles, particle) end end -- destroy the mineable if mineable.mineable and mineable.mineable.valid then mineable.mineable.destroy() end spaceship.mineables[_] = nil end end end end spaceship.last_speed = spaceship.speed end return SpaceshipObstacles