local Command = require 'utils.command' local Task = require 'utils.task' local Token = require 'utils.token' local Server = require 'features.server' local Global = require 'utils.global' local Event = require 'utils.event' local Retailer = require 'features.retailer' local Ranks = require 'resources.ranks' local Core = require 'utils.core' local Color = require 'resources.color_presets' local Toast = require 'features.gui.toast' local Discord = require 'resources.discord' local ScoreTracker = require 'utils.score_tracker' local PlayerStats = require 'features.player_stats' local Restart = require 'features.restart_command' local set_timeout_in_ticks = Task.set_timeout_in_ticks local format_number = require 'util'.format_number -- Use these settings for live local map_promotion_channel = Discord.channel_names.map_promotion --local crash_site_channel = Discord.channel_names.crash_site local crash_site_role_mention = Discord.role_mentions.crash_site -- Use these settings for testing -- local map_promotion_channel = Discord.channel_names.bot_playground -- local crash_site_channel = Discord.channel_names.bot_playground -- local crash_site_role_mention = Discord.role_mentions.test local Public = {} function Public.control(config) Restart.set_start_game_data({type = Restart.game_types.scenario, name = config.scenario_name or 'crashsite'}) local airstrike_data = {radius_level = 1, count_level = 1} local barrage_data = {radius_level = 1, count_level = 1} local spy_message_cooldown = {false} Global.register({airstrike_data = airstrike_data, barrage_data = barrage_data, spy_message_cooldown = spy_message_cooldown}, function(tbl) airstrike_data = tbl.airstrike_data barrage_data = tbl.barrage_data spy_message_cooldown = tbl.spy_message_cooldown end) local static_entities_to_check = { 'spitter-spawner', 'biter-spawner', 'small-worm-turret', 'medium-worm-turret', 'big-worm-turret', 'behemoth-worm-turret', 'gun-turret', 'laser-turret', 'artillery-turret', 'flamethrower-turret' } local biter_entities_to_check = { 'small-spitter', 'medium-spitter', 'big-spitter', 'behemoth-spitter', 'small-biter', 'medium-biter', 'big-biter', 'behemoth-biter' } local function count_enemy_entities() local get_entity_count = game.forces["enemy"].get_entity_count local entity_count = 0; for i = 1, #static_entities_to_check do local name = static_entities_to_check[i] entity_count = entity_count + get_entity_count(name) end for i = 1, #biter_entities_to_check do local name = biter_entities_to_check[i] entity_count = entity_count + get_entity_count(name) end return entity_count end -- Scenario display name for printing new scenario to discord local scenario_display_name = { ['crashsite'] = 'Crash Site', ['crashsite-world'] = 'Crash Site World Map', ['crashsite-desert'] = 'Crash Site Desert', ['crashsite-arrakis'] = 'Crash Site Arrakis', ['crashsite-venice'] = 'Crash Site Venice', ['crashsite-manhattan'] = 'Crash Site Manhattan', ['crashsite-UK'] = 'Crash Site United Kingdom' } local function can_restart(player) if player.admin then return true end local get_entity_count = game.forces["enemy"].get_entity_count -- Check how many of each turrets, worms and spawners are left and return false if there are any of each left. for i = 1, #static_entities_to_check do local name = static_entities_to_check[i] if get_entity_count(name) > 0 then player.print( 'All enemy spawners, worms, buildings, biters and spitters must be cleared before crashsite can be restarted.') return false end end -- Count all the remaining enemies local biter_total = count_enemy_entities() -- Return false if more than 20 left. Players have had problems finding the last few biters so set to a reasonable value. if biter_total > 20 then player.print( 'All enemy spawners, worms, buildings are dead. Crashsite can be restarted when all biters and spitters are killed.') return false end return true end local places = {'1st','2nd','3rd','4th','5th'} -- builds a message for top 3 or 4 etc players for a given stat to use in the embedded awards local function get_top_players(players_as_array, statistic_name) local return_message = "" table.sort(players_as_array, function (a, b) return a[statistic_name] > b[statistic_name] end ) for i = 1, math.min(3, #players_as_array) do local data = players_as_array[i] if statistic_name == "time_played" then return_message = return_message..places[i].." - "..data.name.." ("..Core.format_time(data[statistic_name]).."), " elseif statistic_name == "fish_eaten" then return_message = return_message..places[i].." - "..data.name.." ("..format_number(data[statistic_name],true).." fish eaten), " else return_message = return_message..places[i].." - "..data.name.." ("..format_number(data[statistic_name],true).."), " end end return_message = return_message:sub(1, -3) -- remove the last ", " return return_message end local function restart_callback() local end_epoch = Server.get_current_time() if end_epoch == nil then end_epoch = -1 -- end_epoch is nil if the restart command is used locally rather than on the server end local player_data = {} for _, p in pairs(game.players) do player_data[p.index] = { name = p.name, total_kills = ScoreTracker.get_for_player(p.index, PlayerStats.player_total_kills_name), spawners_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_spawners_killed_name), worms_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_worms_killed_name), units_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_units_killed_name), turrets_killed = ScoreTracker.get_for_player(p.index, PlayerStats.player_turrets_killed_name), distance_walked = ScoreTracker.get_for_player(p.index, PlayerStats.player_distance_walked_name), player_deaths = ScoreTracker.get_for_player(p.index, PlayerStats.player_deaths_name), coins_earned = ScoreTracker.get_for_player(p.index, PlayerStats.coins_earned_name), entities_built = ScoreTracker.get_for_player(p.index, PlayerStats.player_entities_built_name), entities_crafted = ScoreTracker.get_for_player(p.index, PlayerStats.player_items_crafted_name), fish_eaten = ScoreTracker.get_for_player(p.index, PlayerStats.player_fish_eaten_name), time_played = p.online_time } end local statistics = { scenario = config.scenario_name, start_epoch = Server.get_start_time(), end_epoch = end_epoch, -- stored as key already, useful to have it as part of same structure game_ticks = game.ticks_played, enemy_entities = count_enemy_entities(), biters_killed = ScoreTracker.get_for_global(PlayerStats.aliens_killed_name), total_players = #game.players, entities_built = ScoreTracker.get_for_global(PlayerStats.built_by_players_name), player_data = player_data } local players_as_array = {} for _, data in pairs(player_data) do players_as_array[#players_as_array+1] = data end local statistics_message local time_string = Core.format_time(game.ticks_played) if statistics.enemy_entities < 1000 then statistics_message = 'Crash Site map won!\\n\\n' .. 'Statistics:\\n' .. 'Map time: '..time_string..'\\n' .. 'Total kills: '..statistics.biters_killed..'\\n' .. 'Biters remaining on map: '..statistics.enemy_entities..'\\n' .. 'Players: '..statistics.total_players..'\\n' .. 'Total entities built: '..statistics.entities_built..'\\n\\n' .. 'Awards:\\n' -- do the individual awards .. 'Most kills overall: '..get_top_players(players_as_array, "total_kills")..'\\n' .. 'Most biters/spitters killed: '..get_top_players(players_as_array, "units_killed")..'\\n' .. 'Most spawners killed: '..get_top_players(players_as_array, "spawners_killed")..')\\n' .. 'Most worms killed: '..get_top_players(players_as_array, "worms_killed")..')\\n' .. 'Most deaths: '..get_top_players(players_as_array, "player_deaths")..')\\n' .. 'Most items crafted: '..get_top_players(players_as_array, "entities_crafted")..')\\n' .. 'Most entities built: '..get_top_players(players_as_array, "entities_built")..')\\n' .. 'Most time played: '..get_top_players(players_as_array, "time_played")..')\\n' .. 'Furthest walked: '..get_top_players(players_as_array, "distance_walked")..')\\n' .. 'Most coins earned: '..get_top_players(players_as_array, "coins_earned")..')\\n' .. 'Seafood lover: '..get_top_players(players_as_array, "fish_eaten")..'\\n' else statistics_message = 'Crash Site map failed!\\n\\n' .. 'Statistics:\\n' .. 'Map time: '..time_string..'\\n' .. 'Total kills: '..statistics.biters_killed..'\\n' .. 'Biters remaining on map: '..statistics.enemy_entities..'\\n' .. 'Players: '..statistics.total_players..'\\n' end Server.to_discord_named_embed(map_promotion_channel, statistics_message) -- Uncomment to enable posting stats to crashsite channel. -- Server.to_discord_named_embed(crash_site_channel, statistics_message) local start_game_data = Restart.get_start_game_data() local new_map_name = start_game_data.name Server.to_discord_named_raw(map_promotion_channel, crash_site_role_mention .. ' **' .. (scenario_display_name[config.scenario_name] or config.scenario_name) .. ' has just restarted!!\\n' .. 'Next map: ' .. (scenario_display_name[new_map_name] or new_map_name) .. '**') Server.set_data('crash_site_data', tostring(end_epoch), statistics) -- Store the table, with end_epoch as the key end Restart.register(can_restart, restart_callback) local chart_area_callback = Token.register(function(data) local xpos = data.xpos local ypos = data.ypos local player = data.player local s = player.surface player.force.chart(s, {{xpos - 32, ypos - 32}, {xpos + 32, ypos + 32}}) end) local map_chart_tag_clear_callback = Token.register(function(tag) if not tag or not tag.valid then return -- in case a player deleted the tag manually end tag.destroy() end) -- This is needed for if players use /spy, /barrage or /strike on an uncharted area. A map tag cannot be placed on void. local map_chart_tag_place_callback = Token.register(function(data) local xpos = data.xpos local ypos = data.ypos local player = data.player local tag = player.force.add_chart_tag(player.surface, { icon = {type = 'item', name = data.item}, position = {xpos, ypos}, text = player.name }) set_timeout_in_ticks(60 * 30, map_chart_tag_clear_callback, tag) -- To clear the tag after 30 seconds end) -- Prevents message spam to chat when someone uses /spy a lot -- Agreed over removing message as the message helps new players know they can use this command local spy_message_cooldown_callback = Token.register(function() spy_message_cooldown[1] = false end) local function spy(args, player) local player_name = player.name local inv = player.get_inventory(defines.inventory.character_main) -- Parse the values from the location string -- {location = "[gps=-110,-17,redmew]"} local location_string = args.location local coords = {} local spy_cost = 100 for m in string.gmatch(location_string, "(%-?%d*%.?%d+)") do -- Assuming the surface name isn't a valid number. table.insert(coords, tonumber(m)) end -- Do some checks on the coordinates passed in the argument if #coords < 2 then player.print({'command_description.crash_site_spy_invalid'}, Color.fail) return end -- process each set of coordinates local i = 1 local xpos = coords[i] local ypos = coords[i + 1] while xpos ~= nil and ypos ~= nil do local coin_count = inv.get_item_count("coin") -- Make sure player has enough coin to cover spying cost if coin_count < spy_cost then player.print({'command_description.crash_site_spy_funds'}, Color.fail) return else -- show a fish on the map so players can easily find where the new spy locations are from the map view set_timeout_in_ticks(120, map_chart_tag_place_callback, {player = player, xpos = xpos, ypos = ypos, item = 'raw-fish'}) -- reveal 3x3 chunks centred on chunk containing pinged location. Use a callback to make sure it lasts 15 seconds for j = 1, 15 do set_timeout_in_ticks(60 * j, chart_area_callback, {player = player, xpos = xpos, ypos = ypos}) end if spy_message_cooldown[1] == false then game.print({'command_description.crash_site_spy_success', player_name, spy_cost, xpos, ypos},Color.success) spy_message_cooldown[1] = true set_timeout_in_ticks(60*30, spy_message_cooldown_callback) end inv.remove({name = "coin", count = spy_cost}) end -- move to the next set of coordinates i = i + 2 xpos = coords[i] ypos = coords[i + 1] end end local spawn_poison_callback = Token.register(function(data) local r = data.r data.s.create_entity { name = "poison-capsule", position = {0, 0}, target = {data.xpos + math.random(-r, r), data.ypos + math.random(-r, r)}, speed = 10, max_range = 100000 } end) local function render_crosshair(data) local red = {r = 0.5, g = 0, b = 0, a = 1} local timeout = 20 * 60 local line_width = 5 local line_length = 1.8 local s = data.player.surface local f = data.player.force rendering.draw_circle { color = red, radius = 1.2, width = line_width, filled = false, target = data.position, surface = s, time_to_live = timeout, forces = {f} } rendering.draw_line { color = red, width = line_width, from = {data.position.x - line_length, data.position.y}, to = {data.position.x + line_length, data.position.y}, surface = s, time_to_live = timeout, forces = {f} } rendering.draw_line { color = red, width = line_width, from = {data.position.x, data.position.y - line_length}, to = {data.position.x, data.position.y + line_length}, surface = s, time_to_live = timeout, forces = {f} } s.create_entity { name = "flying-text", position = {data.position.x + 3, data.position.y}, text = "[item=" .. data.item .. "] " .. data.player.name, speed = 1/180, time_to_live = 60*5, color = {r = 1, g = 1, b = 1, a = 1} } end local function render_radius(data) local timeout = 20 * 60 rendering.draw_circle { color = data.color, radius = data.radius, filled = true, target = data.position, surface = data.player.surface, time_to_live = timeout, players = {data.player} } end local function strike(args, player) local s = player.surface local location_string = args.location local coords = {} local radius_level = airstrike_data.radius_level -- max radius of the strike area local count_level = airstrike_data.count_level -- the number of poison capsules launched at the enemy if count_level == 1 then player.print({'command_description.crash_site_airstrike_not_researched'}, Color.fail) return end local radius = 5 + (radius_level * 3) local count = (count_level - 2) * 5 + 3 local strikeCost = count * 4 -- the number of poison-capsules required in the chest as payment -- parse GPS coordinates from map ping for m in string.gmatch(location_string, "(%-?%d*%.?%d+)") do -- Assuming the surface name isn't a valid number. table.insert(coords, tonumber(m)) end -- Do some checks on the coordinates passed in the argument if #coords < 2 then player.print({'command_description.crash_site_airstrike_invalid'}, Color.fail) return end -- Check that the chest is where it should be. local entities = s.find_entities_filtered {position = {3.5, -7.5}, type = 'logistic-container', limit = 1} local dropbox = entities[1] if dropbox == nil then player.print("Chest not found. Replace it here: [gps=3.5,-7.5,redmew]") return end -- process each set of coordinates from the arguments with a 20 coordinate, 10 strike limit. local i = 1 local xpos = coords[i] local ypos = coords[i + 1] while xpos ~= nil and ypos ~= nil and i < 20 do -- Process multiple GPS coordinates in the /strike command arguments to strike more than one place at once. -- Check the contents of the chest by spawn for enough poison capsules to use as payment local inv = dropbox.get_inventory(defines.inventory.chest) local capCount = inv.get_item_count("poison-capsule") if capCount < strikeCost then player.print({ 'command_description.crash_site_airstrike_insufficient_currency_error', strikeCost - capCount }, Color.fail) return end inv.remove({name = "poison-capsule", count = strikeCost}) player.force.chart(s, {{xpos - 32, ypos - 32}, {xpos + 32, ypos + 32}}) -- aim of anti-grief is to stop players accidentally using it on themselves. -- we don't mind if there's no enemies there, we'll still take the poison capsules and do the charting so it can still be used to reveal parts of the map local enemies = s.count_entities_filtered{position = {xpos, ypos}, radius=radius+10, force="enemy", limit=1} if enemies ~= 0 then for j = 1, count do set_timeout_in_ticks(30 * j, spawn_poison_callback, {s = s, xpos = xpos, ypos = ypos, count = count, r = radius}) set_timeout_in_ticks(60 * j, chart_area_callback, {player = player, xpos = xpos, ypos = ypos}) end else player.print({'command_description.crash_site_airstrike_no_enemies', xpos, ypos, s.name},Color.fail) end -- render some items regardless as good visual feedback where their strike was. render_crosshair({position = {x = xpos, y = ypos}, player = player, item = "poison-capsule"}) render_radius({position = {x = xpos, y = ypos}, player = player, radius = radius+10, color = {r = 0, g = 0, b = 0.1, a = 0.1}}) set_timeout_in_ticks(60, map_chart_tag_place_callback, {player = player, xpos = xpos, ypos = ypos, item = 'poison-capsule'}) -- move to the next set of coordinates i = i + 2 xpos = coords[i] ypos = coords[i + 1] end end local spawn_rocket_callback = Token.register(function(data) data.s.create_entity { name = "explosive-rocket", position = {0, 0}, target = {data.xpos, data.ypos}, speed = 10, max_range = 100000 } end) local function barrage(args, player) local s = player.surface local location_string = args.location local coords = {} local radius_level = barrage_data.radius_level -- max radius of the barrage area local count_level = barrage_data.count_level -- the number of rockets launched at the enemy if count_level == 1 then player.print({'command_description.crash_site_barrage_not_researched'}, Color.fail) return end local radius = 25 + (radius_level * 5) local count = (count_level-1) * 6 local strikeCost = count * 4 -- parse GPS coordinates from map ping for m in string.gmatch(location_string, "(%-?%d*%.?%d+)") do -- Assuming the surface name isn't a valid number. table.insert(coords, tonumber(m)) end -- Do some checks on the coordinates passed in the argument if #coords < 2 then player.print({'command_description.crash_site_airstrike_invalid'}, Color.fail) return end -- Check that the chest is where it should be. local entities = s.find_entities_filtered {position = {-4.5, -7.5}, type = 'logistic-container', limit = 1} local dropbox = entities[1] if dropbox == nil then player.print("Chest not found. Replace it here: [gps=-4.5,-7.5,redmew]") return end -- process each set of coordinates from the arguments with a 20 coordinate, 10 barrage limit local i = 1 local xpos = coords[i] local ypos = coords[i + 1] while xpos ~= nil and ypos ~= nil and i < 20 do -- Process multiple GPS coordinates in the /barrage command arguments to strike more than one place at once. -- Check the contents of the chest by spawn for enough rockets to use as payment local inv = dropbox.get_inventory(defines.inventory.chest) local capCount = inv.get_item_count("explosive-rocket") if capCount < count then player.print({ 'command_description.crash_site_barrage_insufficient_currency_error', strikeCost - capCount }, Color.fail) return end local nests = player.surface.find_entities_filtered { position = {xpos, ypos}, radius = radius, force = "enemy", type = "unit-spawner" } local nest_count = #nests inv.remove({name = "explosive-rocket", count = strikeCost}) if nest_count == 0 then player.print({'command_description.crash_site_barrage_no_nests',xpos, ypos,s.name}, Color.fail) else player.force.chart(s, {{xpos - 32, ypos - 32}, {xpos + 32, ypos + 32}}) -- draw radius set_timeout_in_ticks(60, map_chart_tag_place_callback, {player = player, xpos = xpos, ypos = ypos, item = 'explosive-rocket'}) render_radius({position = {x = xpos, y = ypos}, player = player, radius = radius, color = {r = 0.1, g = 0, b = 0, a = 0.1}}) for _, nest in pairs(nests) do render_crosshair({position = {x = nest.position.x, y = nest.position.y}, player = player, item = "explosive-rocket"}) end for j = 1, count do set_timeout_in_ticks(60 * j + math.random(0, 30), spawn_rocket_callback, {s = s, xpos = nests[(j%nest_count)+1].position.x, ypos = nests[(j%nest_count)+1].position.y}) set_timeout_in_ticks(60 * j, chart_area_callback, {player = player, xpos = xpos, ypos = ypos}) end end -- move to the next set of coordinates i = i + 2 xpos = coords[i] ypos = coords[i + 1] end end Public.call_strike = strike Public.call_barrage= barrage Event.add(Retailer.events.on_market_purchase, function(event) local market_id = event.group_name local group_label = Retailer.get_market_group_label(market_id) if group_label ~= 'Spawn' then return end local item = event.item if item.type ~= 'airstrike' and item.type~= 'barrage' and item.type~= 'spidertron' then return end if item.type == 'airstrike' then local radius_level = airstrike_data.radius_level -- max radius of the strike area local count_level = airstrike_data.count_level -- the number of poison capsules launched at the enemy local radius = 5 + (radius_level * 3) local count = (count_level - 1) * 5 + 3 local strikeCost = count * 4 local name = item.name local player_name = event.player.name if name == 'airstrike_damage' then airstrike_data.count_level = airstrike_data.count_level + 1 Toast.toast_all_players(15, { 'command_description.crash_site_airstrike_damage_upgrade_success', player_name, count_level }) Server.to_discord_bold('*** ' .. player_name .. ' has upgraded Airstrike Damage to level ' .. count_level .. ' ***') item.name_label = {'command_description.crash_site_airstrike_count_name_label', (count_level + 1)} item.price = math.floor(math.exp(airstrike_data.count_level ^ 0.8) / 2) * 1000 item.description = { 'command_description.crash_site_airstrike_count', (count_level + 1), count_level, count, tostring(strikeCost) .. ' poison capsules' } Retailer.set_item(market_id, item) -- this updates the retailer with the new item values. elseif name == 'airstrike_radius' then airstrike_data.radius_level = airstrike_data.radius_level + 1 Toast.toast_all_players(15, { 'command_description.crash_site_airstrike_radius_upgrade_success', player_name, radius_level }) Server.to_discord_bold('*** ' .. player_name .. ' has upgraded Airstrike Radius to level ' .. radius_level .. ' ***') item.name_label = {'command_description.crash_site_airstrike_radius_name_label', (radius_level + 1)} item.description = { 'command_description.crash_site_airstrike_radius', (radius_level + 1), radius_level, radius } item.price = math.floor(math.exp(airstrike_data.radius_level ^ 0.8) / 2) * 1000 Retailer.set_item(market_id, item) -- this updates the retailer with the new item values. elseif name == 'airstrike_planner' then local player = event.player player.clear_cursor() local cursor_stack = player.cursor_stack cursor_stack.set_stack({name = 'deconstruction-planner'}) cursor_stack.label = 'Poison strike targetting remote' cursor_stack.blueprint_icons = {{index = 1, signal = {type = 'item', name = 'poison-capsule'}}} cursor_stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.never cursor_stack.entity_filters = {'sand-rock-big'} end end if item.type == 'barrage' then local radius_level = barrage_data.radius_level -- max radius of the strike area local count_level = barrage_data.count_level -- the number of poison capsules launched at the enemy local radius = 25 + (radius_level * 5) local count = count_level * 6 local strikeCost = count * 4 local name = item.name local player_name = event.player.name if name == 'barrage_damage' then barrage_data.count_level = barrage_data.count_level + 1 Toast.toast_all_players(15, { 'command_description.crash_site_barrage_damage_upgrade_success', player_name, count_level }) Server.to_discord_bold('*** ' .. player_name .. ' has upgraded Rocket Barrage Damage to level ' .. count_level .. ' ***') item.name_label = {'command_description.crash_site_barrage_count_name_label', (count_level + 1)} item.price = math.floor(math.exp(barrage_data.count_level ^ 0.8) / 2) * 1000 item.description = { 'command_description.crash_site_barrage_count', (count_level + 1), count_level, count, tostring(strikeCost) .. ' explosive rockets' } Retailer.set_item(market_id, item) -- this updates the retailer with the new item values. elseif name == 'barrage_radius' then barrage_data.radius_level = barrage_data.radius_level + 1 Toast.toast_all_players(15, { 'command_description.crash_site_barrage_radius_upgrade_success', player_name, radius_level }) Server.to_discord_bold('*** ' .. player_name .. ' has upgraded Rocket Barrage Radius to level ' .. radius_level .. ' ***') item.name_label = {'command_description.crash_site_barrage_radius_name_label', (radius_level + 1)} item.description = { 'command_description.crash_site_barrage_radius', (radius_level + 1), radius_level, radius } item.price = math.floor(math.exp(barrage_data.radius_level ^ 0.8) / 2) * 1000 Retailer.set_item(market_id, item) -- this updates the retailer with the new item values. elseif name == 'barrage_planner' then local player = event.player player.clear_cursor() local cursor_stack = player.cursor_stack cursor_stack.set_stack({name = 'deconstruction-planner'}) cursor_stack.label = 'Barrage targetting remote' cursor_stack.blueprint_icons = {{index = 1, signal = {type = 'item', name = 'explosive-rocket'}}} cursor_stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.never cursor_stack.entity_filters = {'sand-rock-big'} end end if item.type == 'spidertron' and item.name=='spidertron_planner' then local player = event.player player.clear_cursor() local cursor_stack = player.cursor_stack cursor_stack.set_stack({name = 'deconstruction-planner'}) cursor_stack.label = "Select a group of spidertrons that belong to you! 0 selected." cursor_stack.blueprint_icons = {{index = 1, signal = {type = 'item', name = 'spidertron'}}} cursor_stack.tile_selection_mode = defines.deconstruction_item.tile_selection_mode.never cursor_stack.entity_filters = {'sand-rock-big'} end end) Command.add('spy', { description = {'command_description.crash_site_spy'}, arguments = {'location'}, capture_excess_arguments = true, required_rank = Ranks.guest, allowed_by_server = false }, spy) Command.add('strike', { description = {'command_description.crash_site_airstrike'}, arguments = {'location'}, capture_excess_arguments = true, required_rank = Ranks.guest, allowed_by_server = false }, strike) Command.add('barrage', { description = {'command_description.crash_site_barrage'}, arguments = {'location'}, capture_excess_arguments = true, required_rank = Ranks.guest, allowed_by_server = false }, barrage) end return Public