2019-01-17 01:55:19 +02:00
local Command = require ' utils.command '
2020-09-23 12:14:36 +02:00
local Rank = require ' features.rank_system '
2019-01-17 01:55:19 +02:00
local Task = require ' utils.task '
local Token = require ' utils.token '
local Server = require ' features.server '
local Popup = require ' features.gui.popup '
local Global = require ' utils.global '
2020-11-17 23:35:12 +02:00
local Event = require ' utils.event '
local Retailer = require ' features.retailer '
2019-01-30 05:52:43 +02:00
local Ranks = require ' resources.ranks '
2020-10-23 18:10:04 +02:00
local Core = require ' utils.core '
2020-11-05 20:28:34 +02:00
local Color = require ' resources.color_presets '
2020-11-19 23:19:26 +02:00
local Toast = require ' features.gui.toast '
2020-11-25 16:57:05 +02:00
local Utils = require ' utils.core '
2020-12-23 18:08:20 +02:00
local DiscordChannelNames = require ' resources.discord_channel_names '
2020-11-17 22:56:53 +02:00
local set_timeout_in_ticks = Task.set_timeout_in_ticks
2019-01-17 01:55:19 +02:00
2020-10-01 22:08:09 +02:00
local Public = { }
function Public . control ( config )
2019-01-17 01:55:19 +02:00
local server_player = { name = ' <server> ' , print = print }
local global_data = { restarting = nil }
2020-11-19 23:19:26 +02:00
local airstrike_data = { radius_level = 1 , count_level = 1 }
2019-01-17 01:55:19 +02:00
2020-11-17 23:35:12 +02:00
Global.register ( {
global_data = global_data ,
airstrike_data = airstrike_data
} ,
2019-01-17 01:55:19 +02:00
function ( tbl )
2020-11-17 23:35:12 +02:00
global_data = tbl.global_data
airstrike_data = tbl.airstrike_data
2019-01-17 01:55:19 +02:00
end
)
local function double_print ( str )
game.print ( str )
print ( str )
end
local callback
callback =
Token.register (
function ( data )
if not global_data.restarting then
return
end
local state = data.state
if state == 0 then
Server.start_scenario ( data.scenario_name )
double_print ( ' restarting ' )
2020-04-16 00:03:51 +02:00
global_data.restarting = nil
2019-01-17 01:55:19 +02:00
return
elseif state == 1 then
2020-10-23 18:10:04 +02:00
local time_string = Core.format_time ( game.ticks_played )
local discord_crashsite_role = ' <@&762441731194748958> ' -- @crash_site
--local discord_crashsite_role = '<@&593534612051984431>' -- @test
2020-12-23 18:08:20 +02:00
Server.to_discord_named_raw ( DiscordChannelNames.map_promotion , discord_crashsite_role .. ' **Crash Site has just restarted! Previous map lasted: ' .. time_string .. ' !** ' )
2020-10-29 18:38:14 +02:00
2020-10-29 18:26:27 +02:00
local unix_time = Server.get_current_time ( )
local game_time = game.ticks_played
Server.set_data ( ' crash_site_data ' , tostring ( unix_time ) , game_time ) -- Store the server unix time as key and total game ticks in the Scenario Data
2020-10-29 18:38:14 +02:00
2019-01-17 01:55:19 +02:00
Popup.all ( ' \n Server restarting! \n Initiated by ' .. data.name .. ' \n ' )
end
double_print ( state )
data.state = state - 1
Task.set_timeout_in_ticks ( 60 , callback , data )
end
)
2020-10-10 11:41:25 +02:00
local static_entities_to_check = {
2020-10-01 22:08:09 +02:00
' spitter-spawner ' , ' biter-spawner ' ,
2020-10-01 22:52:47 +02:00
' small-worm-turret ' , ' medium-worm-turret ' , ' big-worm-turret ' , ' behemoth-worm-turret ' ,
' gun-turret ' , ' laser-turret ' , ' artillery-turret ' , ' flamethrower-turret '
2020-10-01 22:08:09 +02:00
}
2020-10-10 14:07:22 +02:00
2020-10-10 11:41:25 +02:00
local biter_entities_to_check = {
' small-spitter ' , ' medium-spitter ' , ' big-spitter ' , ' behemoth-spitter ' ,
' small-biter ' , ' medium-biter ' , ' big-biter ' , ' behemoth-biter '
}
2020-10-01 22:08:09 +02:00
2020-10-10 11:41:25 +02:00
local function map_cleared ( player )
player = player or server_player
2020-10-01 22:08:09 +02:00
local get_entity_count = game.forces [ " enemy " ] . get_entity_count
2020-10-10 11:41:25 +02:00
-- 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 ]
2020-10-01 22:08:09 +02:00
if get_entity_count ( name ) > 0 then
2020-10-10 14:07:22 +02:00
player.print ( ' All enemy spawners, worms, buildings, biters and spitters must be cleared before crashsite can be restarted. ' )
2020-10-01 22:08:09 +02:00
return false
end
end
2020-10-10 11:41:25 +02:00
-- Count all the remaining biters and spitters
local biter_total = 0 ;
for i = 1 , # biter_entities_to_check do
2020-10-10 14:07:22 +02:00
local name = biter_entities_to_check [ i ]
2020-10-10 11:41:25 +02:00
biter_total = biter_total + get_entity_count ( name )
end
-- 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
2020-10-10 14:07:22 +02:00
player.print ( ' All enemy spawners, worms, buildings are dead. Crashsite can be restarted when all biters and spitters are killed. ' )
2020-10-10 11:41:25 +02:00
return false
end
2020-10-01 22:08:09 +02:00
return true
end
2019-03-06 23:35:38 +02:00
local function restart ( args , player )
player = player or server_player
2020-10-01 22:08:09 +02:00
local sanitised_scenario = args.scenario_name
2019-03-06 23:35:38 +02:00
if global_data.restarting then
player.print ( ' Restart already in progress ' )
return
end
2020-10-01 22:52:47 +02:00
if player ~= server_player and Rank.less_than ( player.name , Ranks.admin ) then
-- Check enemy count
2020-10-10 11:41:25 +02:00
if not map_cleared ( player ) then
2020-09-23 12:14:36 +02:00
return
2020-10-01 22:52:47 +02:00
end
2020-10-01 22:08:09 +02:00
-- Limit the ability of non-admins to call the restart function with arguments to change the scenario
-- If not an admin, restart the same scenario always
sanitised_scenario = config.scenario_name
2020-09-23 12:14:36 +02:00
end
2020-10-01 22:52:47 +02:00
2019-03-06 23:35:38 +02:00
global_data.restarting = true
double_print ( ' #################-Attention-################# ' )
double_print ( ' Server restart initiated by ' .. player.name )
double_print ( ' ########################################### ' )
2020-10-03 16:32:57 +02:00
for _ , p in pairs ( game.players ) do
if p.admin then
p.print ( ' Abort restart with /abort ' )
2019-03-06 23:35:38 +02:00
end
end
print ( ' Abort restart with /abort ' )
2020-10-01 22:08:09 +02:00
Task.set_timeout_in_ticks ( 60 , callback , { name = player.name , scenario_name = sanitised_scenario , state = 10 } )
2019-03-06 23:35:38 +02:00
end
local function abort ( _ , player )
player = player or server_player
if global_data.restarting then
global_data.restarting = nil
double_print ( ' Restart aborted by ' .. player.name )
else
player.print ( ' Cannot abort a restart that is not in progress. ' )
end
end
2020-11-19 23:25:56 +02:00
local chart_area_callback = Token.register ( function ( data )
2020-11-19 23:19:26 +02:00
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 )
2020-11-04 18:19:52 +02:00
local function spy ( args , player )
local player_name = player.name
2020-11-10 22:11:18 +02:00
local inv = player.get_inventory ( defines.inventory . character_main )
2020-11-05 20:28:34 +02:00
local coin_count = inv.get_item_count ( " coin " )
2020-11-04 18:19:52 +02:00
2020-11-04 18:24:14 +02:00
-- Parse the values from the location string
2020-11-04 18:19:52 +02:00
-- {location = "[gps=-110,-17,redmew]"}
local location_string = args.location
local coords = { }
2020-11-06 20:56:42 +02:00
2020-11-04 18:24:14 +02:00
for m in string.gmatch ( location_string , " %-?%d+ " ) do
2020-11-04 18:19:52 +02:00
table.insert ( coords , tonumber ( m ) )
end
-- Do some checks then reveal the pinged map and remove 1000 coins
2020-11-10 22:11:18 +02:00
if # coords < 2 then
player.print ( { ' command_description.crash_site_spy_invalid ' } , Color.fail )
2020-11-04 18:19:52 +02:00
return
elseif coin_count < 1000 then
2020-11-10 22:11:18 +02:00
player.print ( { ' command_description.crash_site_spy_funds ' } , Color.fail )
2020-11-04 18:19:52 +02:00
return
else
local xpos = coords [ 1 ]
local ypos = coords [ 2 ]
2020-11-08 23:59:54 +02:00
-- reveal 3x3 chunks centred on chunk containing pinged location
2020-11-19 23:19:26 +02:00
-- 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
2020-11-06 20:52:33 +02:00
game.print ( { ' command_description.crash_site_spy_success ' , player_name , xpos , ypos } , Color.success )
2020-11-05 20:28:34 +02:00
inv.remove ( { name = " coin " , count = 1000 } )
2020-11-04 18:19:52 +02:00
end
end
2020-11-19 23:25:56 +02:00
local spawn_poison_callback = Token.register ( function ( data )
2020-11-17 22:56:53 +02:00
local r = data.r
2020-12-09 20:33:33 +02:00
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 }
2020-11-17 22:56:53 +02:00
end )
local function strike ( args , player )
local s = player.surface
local location_string = args.location
local coords = { }
2020-11-19 23:25:56 +02:00
2020-11-19 23:19:26 +02:00
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
2020-11-19 23:25:56 +02:00
if count_level == 1 then
2020-11-25 16:57:05 +02:00
player.print ( { ' command_description.crash_site_airstrike_not_researched ' } , Color.fail )
2020-11-19 23:19:26 +02:00
return
end
2020-11-17 22:56:53 +02:00
2020-11-19 23:19:26 +02:00
local radius = 5 + ( radius_level * 3 )
2020-12-13 17:40:32 +02:00
local count = ( count_level - 2 ) * 5 + 3
2020-11-25 16:57:05 +02:00
local strikeCost = count * 4 -- the number of poison-capsules required in the chest as payment
2020-11-19 23:25:56 +02:00
2020-11-19 23:19:26 +02:00
-- parse GPS coordinates from map ping
2020-11-17 22:56:53 +02:00
for m in string.gmatch ( location_string , " %-?%d+ " ) do
table.insert ( coords , tonumber ( m ) )
end
2020-11-19 23:19:26 +02:00
2020-11-25 16:57:05 +02:00
-- Do some checks on the coordinates passed in the argument
2020-11-17 22:56:53 +02:00
if # coords < 2 then
2020-11-25 16:57:05 +02:00
player.print ( { ' command_description.crash_site_airstrike_invalid ' } , Color.fail )
2020-11-17 22:56:53 +02:00
return
end
2020-11-19 23:19:26 +02:00
local xpos = coords [ 1 ]
local ypos = coords [ 2 ]
2020-11-17 22:56:53 +02:00
2020-11-29 20:39:32 +02:00
-- Check that the chest is where it should be.
2020-11-25 23:19:35 +02:00
local entities = s.find_entities_filtered { position = { - 0.5 , - 3.5 } , type = ' container ' , limit = 1 }
local dropbox = entities [ 1 ]
2020-11-29 20:39:32 +02:00
if dropbox == nil then
player.print ( " Chest not found. Replace it here: [gps=-0.5,-3.5,redmew] " )
return
end
2020-11-19 23:19:26 +02:00
-- Check the contents of the chest by spawn for enough poison capsules to use as payment
2020-11-17 22:56:53 +02:00
local inv = dropbox.get_inventory ( defines.inventory . chest )
2020-11-19 23:25:56 +02:00
local capCount = inv.get_item_count ( " poison-capsule " )
2020-11-20 02:29:45 +02:00
2020-11-19 23:25:56 +02:00
if capCount < strikeCost then
2020-11-25 16:57:05 +02:00
player.print ( { ' command_description.crash_site_airstrike_insufficient_currency_error ' , strikeCost - capCount } , Color.fail )
2020-11-17 22:56:53 +02:00
return
end
2020-11-19 23:19:26 +02:00
-- Do a simple check to make sure the player isn't trying to grief the base
--local enemyEntities = s.find_entities_filtered {position = {xpos, ypos}, radius = radius, force = "enemy"}
2020-11-25 23:35:16 +02:00
local enemyEntities = player.surface . count_entities_filtered { position = { xpos , ypos } , radius = radius + 30 , force = " enemy " , limit = 1 }
2020-11-25 23:33:04 +02:00
if enemyEntities < 1 then
2020-11-25 16:57:05 +02:00
player.print ( { ' command_description.crash_site_airstrike_friendly_fire_error ' } , Color.fail )
2020-11-25 17:32:52 +02:00
Utils.print_admins ( player.name .. " tried to airstrike the base here: [gps= " .. xpos .. " , " .. ypos .. " ,redmew] " , nil )
2020-11-19 23:19:26 +02:00
return
end
inv.remove ( { name = " poison-capsule " , count = strikeCost } )
2020-11-25 16:57:05 +02:00
game.print ( { ' command_description.crash_site_airstrike_success ' , player.name , xpos , ypos } )
2020-11-19 23:19:26 +02:00
for j = 1 , count do
set_timeout_in_ticks (
2020-11-17 22:56:53 +02:00
30 * j ,
spawn_poison_callback ,
2020-11-19 23:19:26 +02:00
{ 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 }
2020-11-17 22:56:53 +02:00
)
end
end
2020-11-17 23:35:12 +02:00
Event.add ( Retailer.events . on_market_purchase , function ( event )
2020-11-20 02:29:45 +02:00
2020-11-17 23:35:12 +02:00
local market_id = event.group_name
local group_label = Retailer.get_market_group_label ( market_id )
if group_label ~= ' Spawn ' then
return
end
2020-11-17 22:56:53 +02:00
2020-11-17 23:35:12 +02:00
local item = event.item
if item.type ~= ' airstrike ' then
return
end
2020-11-19 23:19:26 +02:00
-- airstrike stuff
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
2020-11-19 23:25:56 +02:00
local strikeCost = count * 4
2020-11-19 23:19:26 +02:00
2020-11-17 23:35:12 +02:00
local name = item.name
2020-11-19 23:19:26 +02:00
if name == ' airstrike_damage ' then
airstrike_data.count_level = airstrike_data.count_level + 1
2020-11-17 23:35:12 +02:00
2020-11-25 16:57:05 +02:00
Toast.toast_all_players ( 15 , { ' command_description.crash_site_airstrike_damage_upgrade_success ' , count_level } )
item.name_label = { ' command_description.crash_site_airstrike_count_name_label ' , ( count_level + 1 ) }
2020-11-19 23:19:26 +02:00
item.price = math.floor ( math.exp ( airstrike_data.count_level ^ 0.8 ) / 2 ) * 1000
2020-11-25 16:57:05 +02:00
item.description = { ' command_description.crash_site_airstrike_count ' , ( count_level + 1 ) , count_level , count , tostring ( strikeCost ) .. ' poison capsules ' }
2020-11-17 23:35:12 +02:00
Retailer.set_item ( market_id , item ) -- this updates the retailer with the new item values.
2020-11-19 23:19:26 +02:00
elseif name == ' airstrike_radius ' then
airstrike_data.radius_level = airstrike_data.radius_level + 1
2020-11-25 16:57:05 +02:00
Toast.toast_all_players ( 15 , { ' command_description.crash_site_airstrike_radius_upgrade_success ' , radius_level } )
item.name_label = { ' command_description.crash_site_airstrike_radius_name_label ' , ( radius_level + 1 ) }
2020-11-19 23:19:26 +02:00
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
2020-11-17 23:35:12 +02:00
Retailer.set_item ( market_id , item ) -- this updates the retailer with the new item values.
end
end )
2020-11-17 22:56:53 +02:00
2019-03-06 23:35:38 +02:00
Command.add (
' crash-site-restart-abort ' ,
{
description = { ' command_description.crash_site_restart_abort ' } ,
required_rank = Ranks.admin ,
allowed_by_server = true
} ,
abort
)
2019-01-17 01:55:19 +02:00
Command.add (
2019-03-06 23:35:38 +02:00
' abort ' ,
2019-01-17 01:55:19 +02:00
{
2019-03-04 00:13:56 +02:00
description = { ' command_description.crash_site_restart_abort ' } ,
2019-01-30 05:52:43 +02:00
required_rank = Ranks.admin ,
2019-01-17 01:55:19 +02:00
allowed_by_server = true
} ,
2019-03-06 23:35:38 +02:00
abort
2019-01-17 01:55:19 +02:00
)
2019-07-28 12:44:13 +02:00
2020-11-04 18:19:52 +02:00
local default_name = config.scenario_name or ' crashsite '
Command.add (
' crash-site-restart ' ,
{
description = { ' command_description.crash_site_restart ' } ,
arguments = { ' scenario_name ' } ,
default_values = { scenario_name = default_name } ,
required_rank = Ranks.admin ,
allowed_by_server = true
} ,
restart
)
Command.add (
' restart ' ,
{
description = { ' command_description.crash_site_restart ' } ,
arguments = { ' scenario_name ' } ,
default_values = { scenario_name = default_name } ,
required_rank = Ranks.auto_trusted ,
allowed_by_server = true
} ,
restart
)
Command.add (
' spy ' ,
{
description = { ' command_description.crash_site_spy ' } ,
arguments = { ' location ' } ,
2020-11-05 20:28:34 +02:00
capture_excess_arguments = true ,
required_rank = Ranks.guest ,
2020-11-04 18:19:52 +02:00
allowed_by_server = false
} ,
spy
)
2020-11-17 22:56:53 +02:00
Command.add (
' strike ' ,
{
description = { ' command_description.strike ' } ,
arguments = { ' location ' } ,
capture_excess_arguments = true ,
required_rank = Ranks.guest ,
allowed_by_server = false
} ,
strike
)
2019-07-28 12:44:13 +02:00
end
return Public