From cf95b8e91a93514e60bc93a0656333a0d0024ef1 Mon Sep 17 00:00:00 2001 From: Gerkiz Date: Tue, 11 Jun 2024 22:45:12 +0200 Subject: [PATCH] Mtn v3 - small fixes Removes broken spells from objective Fixes broken teleport when final battle Lowered the length of final battle to max 15m --- control.lua | 2 +- maps/mountain_fortress_v3/functions.lua | 9 + maps/mountain_fortress_v3/stateful/gui.lua | 2766 ++++++------- maps/mountain_fortress_v3/stateful/table.lua | 3884 +++++++++--------- 4 files changed, 3327 insertions(+), 3334 deletions(-) diff --git a/control.lua b/control.lua index 89a7bec1a..5c6b0e1ac 100644 --- a/control.lua +++ b/control.lua @@ -83,7 +83,7 @@ require 'utils.remote_chunks' --require 'maps.biter_battles.biter_battles' --![[Guide a Train through rough terrain, while defending it from the biters]]-- ---require 'maps.mountain_fortress_v3.main' +require 'maps.mountain_fortress_v3.main' --require 'maps.mountain_fortress_v2.main' --require 'maps.mountain_fortress' diff --git a/maps/mountain_fortress_v3/functions.lua b/maps/mountain_fortress_v3/functions.lua index b11a5474c..51f5a9d93 100644 --- a/maps/mountain_fortress_v3/functions.lua +++ b/maps/mountain_fortress_v3/functions.lua @@ -987,6 +987,15 @@ function Public.find_void_tiles_and_replace() right_bottom = { x = (zone_settings.zone_width / 2) - 10, y = rp.y } } + local adjusted_zones = Public.get('adjusted_zones') + + if adjusted_zones.reversed then + area = { + left_top = { x = ((zone_settings.zone_width / 2) + 10) * -1, y = rp.y }, + right_bottom = { x = math.abs((-zone_settings.zone_width / 2) - 10), y = cp.y }, + } + end + local tiles = surface.find_tiles_filtered({ area = area, name = { 'out-of-map', 'water', 'deepwater', 'water-green', 'deepwater-green' } }) if tiles and #tiles > 0 then Public.set('tiles_to_replace', tiles) diff --git a/maps/mountain_fortress_v3/stateful/gui.lua b/maps/mountain_fortress_v3/stateful/gui.lua index 62894470e..a213311fe 100644 --- a/maps/mountain_fortress_v3/stateful/gui.lua +++ b/maps/mountain_fortress_v3/stateful/gui.lua @@ -1,1383 +1,1383 @@ -local Event = require 'utils.event' -local SpamProtection = require 'utils.spam_protection' -local Public = require 'maps.mountain_fortress_v3.table' -local Alert = require 'utils.alert' -local Stateful = require 'maps.mountain_fortress_v3.stateful.table' -local Gui = require 'utils.gui' -local WD = require 'modules.wave_defense.table' -local Collapse = require 'modules.collapse' -local Task = require 'utils.task_token' -local Core = require 'utils.core' -local Server = require 'utils.server' -local LinkedChests = require 'maps.mountain_fortress_v3.icw.linked_chests' -local Discord = require 'utils.discord' -local format_number = require 'util'.format_number -local Explosives = require 'modules.explosives' - -local zone_settings = Public.zone_settings -local send_ping_to_channel = Discord.channel_names.mtn_channel -local main_button_name = Gui.uid_name() -local main_frame_name = Gui.uid_name() -local boss_frame_name = Gui.uid_name() -local close_button = Gui.uid_name() -local close_buffs_window_name = Gui.uid_name() -local buffs_window_name = Gui.uid_name() -local on_click_buff_name = Gui.uid_name() -local random = math.random -local floor = math.floor -local scenario_name = Public.scenario_name -local main_frame - -local function create_particles(surface, name, position, amount, cause_position) - local d1 = (-100 + random(0, 200)) * 0.0004 - local d2 = (-100 + random(0, 200)) * 0.0004 - - name = name or 'leaf-particle' - - if cause_position then - d1 = (cause_position.x - position.x) * 0.025 - d2 = (cause_position.y - position.y) * 0.025 - end - - for _ = 1, amount, 1 do - local m = random(4, 10) - local m2 = m * 0.005 - - surface.create_particle( - { - name = name, - position = position, - frame_speed = 1, - vertical_speed = 0.130, - height = 0, - movement = { - (m2 - (random(0, m) * 0.01)) + d1, - (m2 - (random(0, m) * 0.01)) + d2 - } - } - ) - end -end - -local spread_particles_token = - Task.register( - function (event) - local player_index = event.player_index - local player = game.get_player(player_index) - if not player or not player.valid then - return - end - local particle = event.particle - - create_particles(player.surface, particle, player.position, 128) - end - ) - -local function pretty_format(input) - local action = string.gsub(input, '-', ' ') - local result = string.upper(string.sub(action, 1, 1)) .. string.sub(action, 2) - return result -end - -local function notify_won_to_discord(buff) - if not buff then - return error('Buff is required when sending message to discord.', 2) - end - local server_name_matches = Server.check_server_name(scenario_name) - - local stateful = Public.get_stateful() - - local wave = WD.get_wave() - local date = Server.get_start_time() - game.server_save('Complete_Mtn_v3_' .. tostring(date) .. '_wave' .. tostring(wave)) - - local time_played = Core.format_time(game.ticks_played) - local total_players = #game.players - local total_connected_players = #game.connected_players - local pickaxe_upgrades = Public.pickaxe_upgrades - local upgrades = Public.get('upgrades') - local pick_tier = pickaxe_upgrades[upgrades.pickaxe_tier] - - local text = { - title = 'Game won!', - description = 'Game statistics from the game is below', - color = 'success', - field1 = { - text1 = 'Time played:', - text2 = time_played, - inline = 'false' - }, - field2 = { - text1 = 'Rounds survived:', - text2 = stateful.rounds_survived, - inline = 'false' - }, - field3 = { - text1 = 'Wave:', - text2 = format_number(wave, true), - inline = 'false' - }, - field4 = { - text1 = 'Total connected players:', - text2 = total_players, - inline = 'false' - }, - field5 = { - text1 = 'Pickaxe Upgrade:', - text2 = pick_tier .. ' (' .. upgrades.pickaxe_tier .. ')', - inline = 'false' - }, - field6 = { - text1 = 'Connected players:', - text2 = total_connected_players, - inline = 'false' - }, - field7 = { - text1 = 'Buff granted:', - text2 = buff.discord, - inline = 'false' - } - } - if server_name_matches then - Server.to_discord_named_parsed_embed(send_ping_to_channel, text) - else - Server.to_discord_embed_parsed(text) - end -end - -local function clear_all_frames() - Core.iter_players( - function (player) - local b_frame = player.gui.screen[boss_frame_name] - if b_frame then - Gui.remove_data_recursively(b_frame) - b_frame.destroy() - end - - local frame = player.gui.screen[main_frame_name] - if frame then - Gui.remove_data_recursively(frame) - frame.destroy() - end - end - ) -end - -local function refresh_frames() - Core.iter_connected_players( - function (player) - local frame = player.gui.screen[main_frame_name] - if frame and frame.valid then - Gui.remove_data_recursively(frame) - frame.destroy() - main_frame(player) - else - main_frame(player) - end - end - ) -end - -local warn_player_sound_token = - Task.register( - function (event) - local player_index = event.player_index - local player = game.get_player(player_index) - if not player or not player.valid then - return - end - local particle = event.particle - - player.play_sound { path = 'utility/new_objective', volume_modifier = 0.75 } - - create_particles(player.surface, particle, player.position, 128) - end - ) - -local function create_button(player) - if Gui.get_mod_gui_top_frame() then - local b = - Gui.add_mod_button( - player, - { - type = 'sprite-button', - name = main_button_name, - sprite = 'utility/custom_tag_icon', - tooltip = 'Has information about all objectives that needs to be completed', - style = Gui.button_style - } - ) - if b then - b.style.font_color = { 165, 165, 165 } - b.style.font = 'heading-3' - b.style.minimal_height = 36 - b.style.maximal_height = 36 - b.style.minimal_width = 40 - b.style.padding = -2 - end - else - local b = - player.gui.top.add( - { - type = 'sprite-button', - name = main_button_name, - sprite = 'utility/custom_tag_icon', - tooltip = 'Has information about all objectives that needs to be completed', - style = Gui.button_style - } - ) - b.style.minimal_height = 38 - b.style.maximal_height = 38 - end -end - -local function create_input_element(frame, type, value, items, index, tooltip, custom_space) - if type == 'slider' then - return frame.add({ type = 'slider', value = value, minimum_value = 0, maximum_value = 1 }) - end - if type == 'boolean' then - return frame.add({ type = 'checkbox', state = value }) - end - if type == 'label' then - local label = frame.add({ type = 'label', caption = value }) - label.style.font = 'default-listbox' - label.tooltip = tooltip or '' - if custom_space then - label.style.minimal_height = custom_space - end - return label - end - if type == 'dropdown' then - return frame.add({ type = 'drop-down', items = items, selected_index = index }) - end - return frame.add({ type = 'text-box', text = value }) -end - -local function play_game_won() - Explosives.disable(false) - Core.iter_connected_players( - function (player) - Explosives.detonate_entity(player) - player.play_sound { path = 'utility/game_won', volume_modifier = 0.75 } - Task.set_timeout_in_ticks(10, spread_particles_token, { player_index = player.index, particle = 'iron-ore-particle' }) - Task.set_timeout_in_ticks(15, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(20, spread_particles_token, { player_index = player.index, particle = 'copper-ore-particle' }) - Task.set_timeout_in_ticks(25, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(30, spread_particles_token, { player_index = player.index, particle = 'stone-particle' }) - Task.set_timeout_in_ticks(35, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(40, spread_particles_token, { player_index = player.index, particle = 'coal-particle' }) - Task.set_timeout_in_ticks(45, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - end - ) -end - -local function play_achievement_unlocked() - Core.iter_connected_players( - function (player) - player.play_sound { path = 'utility/achievement_unlocked', volume_modifier = 0.75 } - Task.set_timeout_in_ticks(10, spread_particles_token, { player_index = player.index, particle = 'iron-ore-particle' }) - Task.set_timeout_in_ticks(15, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(20, spread_particles_token, { player_index = player.index, particle = 'copper-ore-particle' }) - Task.set_timeout_in_ticks(25, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(30, spread_particles_token, { player_index = player.index, particle = 'stone-particle' }) - Task.set_timeout_in_ticks(35, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(40, spread_particles_token, { player_index = player.index, particle = 'coal-particle' }) - Task.set_timeout_in_ticks(45, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) - end - ) -end - -local function alert_players_sound() - Core.iter_connected_players( - function (player) - Task.set_timeout_in_ticks(10, warn_player_sound_token, { player_index = player.index, particle = 'iron-ore-particle' }) - Task.set_timeout_in_ticks(20, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(30, warn_player_sound_token, { player_index = player.index, particle = 'copper-ore-particle' }) - Task.set_timeout_in_ticks(40, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(50, warn_player_sound_token, { player_index = player.index, particle = 'stone-particle' }) - Task.set_timeout_in_ticks(60, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) - Task.set_timeout_in_ticks(70, warn_player_sound_token, { player_index = player.index, particle = 'coal-particle' }) - Task.set_timeout_in_ticks(80, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) - end - ) -end - -local function spacer(frame) - local flow = frame.add({ type = 'flow' }) - flow.style.minimal_height = 2 -end - -local function objective_frames(stateful, player_frame, objective, data) - local objective_name = objective.name - if objective_name == 'supplies' or objective_name == 'single_item' then - local supplies = stateful.objectives.supplies - local tbl = player_frame.add { type = 'table', column_count = 2 } - tbl.style.horizontally_stretchable = true - local left_flow = tbl.add({ type = 'flow' }) - left_flow.style.horizontal_align = 'left' - left_flow.style.horizontally_stretchable = true - - if objective_name == 'single_item' then - left_flow.add({ type = 'label', caption = { 'stateful.production_single' }, tooltip = { 'stateful.production_tooltip' } }) - else - left_flow.add({ type = 'label', caption = { 'stateful.production' }, tooltip = { 'stateful.production_tooltip' } }) - end - player_frame.add({ type = 'line', direction = 'vertical' }) - local right_flow = tbl.add({ type = 'flow' }) - right_flow.style.horizontal_align = 'right' - right_flow.style.horizontally_stretchable = true - - if objective_name == 'single_item' then - if stateful.objectives_completed.single_item then - data.single_item_complete = right_flow.add({ type = 'label', caption = ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) - else - data.single_item_complete = right_flow.add({ type = 'label', caption = ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) - end - else - if stateful.objectives_completed.supplies then - data.supply_completed = right_flow.add({ type = 'label', caption = ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) - else - data.supply_completed = right_flow.add({ type = 'label', caption = ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) - end - end - - if not data.supply then - data.supply = {} - end - - local flow = player_frame.add({ type = 'flow' }) - local item_table = flow.add({ type = 'table', name = 'item_table', column_count = 3 }) - if objective_name ~= 'single_item' then - data.supply[#data.supply + 1] = item_table.add({ type = 'sprite-button', name = supplies[1].name, sprite = 'item/' .. supplies[1].name, enabled = false, number = supplies[1].count }) - data.supply[#data.supply + 1] = item_table.add({ type = 'sprite-button', name = supplies[2].name, sprite = 'item/' .. supplies[2].name, enabled = false, number = supplies[2].count }) - data.supply[#data.supply + 1] = item_table.add({ type = 'sprite-button', name = supplies[3].name, sprite = 'item/' .. supplies[3].name, enabled = false, number = supplies[3].count }) - else - local single_item = stateful.objectives.single_item - data.single_item = item_table.add({ type = 'sprite-button', name = single_item.name, sprite = 'item/' .. single_item.name, enabled = false, number = single_item.count }) - end - - return - end - - local callback = Task.get(objective.token) - - local _, objective_locale_left, objective_locale_right, tooltip_left, tooltip_right = callback() - - local tbl = player_frame.add { type = 'table', column_count = 2 } - tbl.style.horizontally_stretchable = true - local left_flow = tbl.add({ type = 'flow' }) - left_flow.style.horizontal_align = 'left' - left_flow.style.horizontally_stretchable = true - - left_flow.add({ type = 'label', caption = objective_locale_left, tooltip = tooltip_left }) - local right_flow = tbl.add({ type = 'flow' }) - right_flow.style.horizontal_align = 'right' - right_flow.style.horizontally_stretchable = true - - local objective_locale_right_label = right_flow.add({ type = 'label', caption = objective_locale_right, tooltip = tooltip_right }) - data.random_objectives[#data.random_objectives + 1] = { name = objective_name, frame = objective_locale_right_label } - return -end - -local function buff_window(player) - local buff_frame_name, inside_table = Gui.add_main_frame_with_toolbar(player, 'center', buffs_window_name, nil, close_buffs_window_name, 'Buffs gathered') - if not buff_frame_name then - return - end - if not inside_table then - return - end - - local stateful = Public.get_stateful() - - local inside_table_style = inside_table.style - inside_table_style.width = 530 - - local info_text = inside_table.add({ type = 'label', caption = 'All the buffs that have been gathered throughout the runs!' }) - local info_text_style = info_text.style - info_text_style.font = 'heading-2' - info_text_style.padding = 0 - info_text_style.left_padding = 10 - info_text_style.horizontal_align = 'left' - info_text_style.vertical_align = 'bottom' - info_text_style.font_color = { 0.55, 0.55, 0.99 } - - local buff_pane = inside_table.add({ type = 'scroll-pane' }) - local ns = buff_pane.style - ns.vertically_squashable = true - ns.bottom_padding = 5 - ns.left_padding = 5 - ns.right_padding = 5 - ns.top_padding = 5 - - buff_pane.add({ type = 'line' }) - - local starting_items_label = buff_pane.add({ type = 'label', caption = 'Starting items' }) - local starting_items_label_style = starting_items_label.style - starting_items_label_style.font = 'heading-3' - starting_items_label_style.padding = 0 - starting_items_label_style.horizontal_align = 'left' - starting_items_label_style.font_color = { 0.55, 0.55, 0.99 } - - local starting_grid = buff_pane.add({ type = 'table', column_count = 8 }) - - buff_pane.add({ type = 'line' }) - - local force_label = buff_pane.add({ type = 'label', caption = 'Force Buffs' }) - local force_label_style = force_label.style - force_label_style.font = 'heading-3' - force_label_style.padding = 0 - force_label_style.horizontal_align = 'left' - force_label_style.font_color = { 0.55, 0.55, 0.99 } - local force_grid = buff_pane.add({ type = 'table', column_count = 2 }) - - buff_pane.add({ type = 'line' }) - - local custom_label = buff_pane.add({ type = 'label', caption = 'Custom Buffs' }) - local custom_label_style = custom_label.style - custom_label_style.font = 'heading-3' - custom_label_style.padding = 0 - custom_label_style.horizontal_align = 'left' - custom_label_style.font_color = { 0.55, 0.55, 0.99 } - local custom_grid = buff_pane.add({ type = 'table', column_count = 2 }) - - if stateful.buffs and next(stateful.buffs) then - if stateful.buffs_collected and next(stateful.buffs_collected) then - if stateful.buffs_collected.starting_items then - for item_name, item_data in pairs(stateful.buffs_collected.starting_items) do - -- local text = pretty_format(item_name) .. ': [font=font-bold]' .. item_data.count - local text = '[font=default-large] [item=' .. item_name .. '][/font]' .. ': [font=default-bold]' .. item_data.count .. '[/font]' - create_input_element(starting_grid, 'label', text, nil, nil, item_data.discord, 30) - end - end - - for name, buff_data in pairs(stateful.buffs_collected) do - if type(buff_data.amount) ~= 'table' and buff_data.force then - local c = buff_data.count - local text - if name == 'xp_level' or name == 'xp_bonus' or name == 'character_health_bonus' then - text = '[font=default-bold]' .. Stateful.buff_to_string[name] .. ': ' .. c .. '[/font]' - else - text = '[font=default-bold]' .. Stateful.buff_to_string[name] .. ': ' .. (c * 100) .. '%[/font]' - end - - create_input_element(force_grid, 'label', text, nil, nil, buff_data.discord) - end - - if name ~= 'starting_items' and not buff_data.force then - if buff_data.name then - local text_to_place = buff_data.count or 'Unlocked' - local text = '[font=default-bold]' .. buff_data.name .. ': ' .. text_to_place .. ' [/font]' - create_input_element(custom_grid, 'label', text, nil, nil, buff_data.discord) - else - for _, buff in pairs(buff_data) do - local text_to_place = buff.count or 'Unlocked' - local text = '[font=default-bold]' .. pretty_format(buff.name) .. ': ' .. text_to_place .. ' [/font]' - create_input_element(custom_grid, 'label', text, nil, nil, buff.discord) - end - end - end - end - end - end - - player.opened = buff_frame_name -end - -local function boss_frame(player, alert) - local main_winning_frame = player.gui.screen[main_frame_name] - if main_winning_frame then - Gui.remove_data_recursively(main_winning_frame) - main_winning_frame.destroy() - end - local main_player_boss_frame = player.gui.screen[boss_frame_name] - if main_player_boss_frame then - Gui.remove_data_recursively(main_player_boss_frame) - main_player_boss_frame.destroy() - end - - local data = {} - - local stateful = Public.get_stateful() - local collection = stateful.collection - - local frame = player.gui.screen.add { type = 'frame', name = boss_frame_name, caption = { 'stateful.win_conditions' }, direction = 'vertical' } - if not alert then - frame.location = { x = 1, y = 45 } - else - frame.location = { x = 1, y = 123 } - end - frame.style.maximal_height = 500 - frame.style.minimal_width = 200 - frame.style.maximal_width = 400 - local season_tbl = frame.add { type = 'table', column_count = 2 } - season_tbl.style.horizontally_stretchable = true - - local season_left_flow = season_tbl.add({ type = 'flow' }) - season_left_flow.style.horizontal_align = 'left' - season_left_flow.style.horizontally_stretchable = true - - season_left_flow.add({ type = 'label', caption = { 'stateful.season' }, tooltip = { 'stateful.season_tooltip', stateful.time_to_reset } }) - frame.add({ type = 'line', direction = 'vertical' }) - local season_right_flow = season_tbl.add({ type = 'flow' }) - season_right_flow.style.horizontal_align = 'right' - season_right_flow.style.horizontally_stretchable = true - - data.season_label = season_right_flow.add({ type = 'label', caption = stateful.season }) - - spacer(frame) - - local rounds_survived_tbl = frame.add { type = 'table', column_count = 2 } - rounds_survived_tbl.style.horizontally_stretchable = true - - local rounds_survived_left_flow = rounds_survived_tbl.add({ type = 'flow' }) - rounds_survived_left_flow.style.horizontal_align = 'left' - rounds_survived_left_flow.style.horizontally_stretchable = true - - rounds_survived_left_flow.add({ type = 'label', caption = { 'stateful.rounds_survived' }, tooltip = { 'stateful.rounds_survived_tooltip' } }) - frame.add({ type = 'line', direction = 'vertical' }) - local rounds_survived_right_flow = rounds_survived_tbl.add({ type = 'flow' }) - rounds_survived_right_flow.style.horizontal_align = 'right' - rounds_survived_right_flow.style.horizontally_stretchable = true - - data.rounds_survived_label = rounds_survived_right_flow.add({ type = 'label', caption = stateful.rounds_survived }) - spacer(frame) - - frame.add({ type = 'line' }) - - spacer(frame) - - if not collection.game_won then - local objective_tbl = frame.add { type = 'table', column_count = 2 } - objective_tbl.style.horizontally_stretchable = true - - if collection.gather_time <= 0 then - local survive_for_left_flow = objective_tbl.add({ type = 'flow' }) - survive_for_left_flow.style.horizontal_align = 'left' - survive_for_left_flow.style.horizontally_stretchable = true - - survive_for_left_flow.add({ type = 'label', caption = { 'stateful.survive_for' } }) - frame.add({ type = 'line', direction = 'vertical' }) - local survive_for_right_flow = objective_tbl.add({ type = 'flow' }) - survive_for_right_flow.style.horizontal_align = 'right' - survive_for_right_flow.style.horizontally_stretchable = true - - local survive_for_timer = floor(collection.survive_for / 60 / 60) .. 'm' - - if collection.survive_for / 60 / 60 <= 1 then - survive_for_timer = floor(collection.survive_for / 60) .. 's' - end - - if collection.survive_for <= 0 then - data.survive_for = survive_for_right_flow.add({ type = 'label', caption = { 'stateful.won' } }) - else - data.survive_for = survive_for_right_flow.add({ type = 'label', caption = survive_for_timer }) - end - end - -- new frame - local biter_sprites_tbl = objective_tbl.add({ type = 'flow' }) - biter_sprites_tbl.style.horizontal_align = 'left' - biter_sprites_tbl.style.horizontally_stretchable = true - - biter_sprites_tbl.add({ type = 'label', caption = { 'stateful.biter_sprites' } }) - else - local objective_tbl = frame.add { type = 'table', column_count = 2 } - objective_tbl.style.horizontally_stretchable = true - - local game_won_left_flow = objective_tbl.add({ type = 'flow' }) - game_won_left_flow.style.horizontal_align = 'left' - game_won_left_flow.style.horizontally_stretchable = true - - game_won_left_flow.add({ type = 'label', caption = { 'stateful.game_won' } }) - end - - local close = frame.add({ type = 'button', name = close_button, caption = 'Close' }) - close.style.horizontally_stretchable = true - Gui.set_data(frame, data) -end - -local function refresh_boss_frame() - Core.iter_connected_players( - function (player) - boss_frame(player) - end - ) -end - -main_frame = function (player) - local main_player_frame = player.gui.screen[main_frame_name] - if main_player_frame then - Gui.remove_data_recursively(main_player_frame) - main_player_frame.destroy() - end - - local data = {} - - local stateful = Public.get_stateful() - local breached_wall = Public.get('breached_wall') - breached_wall = breached_wall - 1 - local wave_number = WD.get('wave_number') - - local frame = player.gui.screen.add { type = 'frame', name = main_frame_name, caption = { 'stateful.win_conditions' }, direction = 'vertical', tooltip = { 'stateful.win_conditions_tooltip' } } - if Gui.get_mod_gui_top_frame() then - frame.location = { x = 0, y = 67 } - else - frame.location = { x = 1, y = 45 } - end - frame.style.maximal_height = 700 - frame.style.minimal_width = 200 - frame.style.maximal_width = 400 - local season_tbl = frame.add { type = 'table', column_count = 2 } - season_tbl.style.horizontally_stretchable = true - - local season_left_flow = season_tbl.add({ type = 'flow' }) - season_left_flow.style.horizontal_align = 'left' - season_left_flow.style.horizontally_stretchable = true - - season_left_flow.add({ type = 'label', caption = { 'stateful.season' }, tooltip = { 'stateful.season_tooltip', stateful.time_to_reset } }) - frame.add({ type = 'line', direction = 'vertical' }) - local season_right_flow = season_tbl.add({ type = 'flow' }) - season_right_flow.style.horizontal_align = 'right' - season_right_flow.style.horizontally_stretchable = true - - data.season_label = season_right_flow.add({ type = 'label', caption = stateful.season }) - - spacer(frame) - - local rounds_survived_tbl = frame.add { type = 'table', column_count = 2 } - rounds_survived_tbl.style.horizontally_stretchable = true - - local rounds_survived_left_flow = rounds_survived_tbl.add({ type = 'flow' }) - rounds_survived_left_flow.style.horizontal_align = 'left' - rounds_survived_left_flow.style.horizontally_stretchable = true - - rounds_survived_left_flow.add({ type = 'label', caption = { 'stateful.rounds_survived' }, tooltip = { 'stateful.rounds_survived_tooltip' } }) - frame.add({ type = 'line', direction = 'vertical' }) - local rounds_survived_right_flow = rounds_survived_tbl.add({ type = 'flow' }) - rounds_survived_right_flow.style.horizontal_align = 'right' - rounds_survived_right_flow.style.horizontally_stretchable = true - - data.rounds_survived_label = rounds_survived_right_flow.add({ type = 'label', caption = stateful.rounds_survived }) - spacer(frame) - - frame.add({ type = 'line' }) - - spacer(frame) - - if stateful.buffs and next(stateful.buffs) then - local buff_tbl = frame.add { type = 'table', column_count = 2 } - buff_tbl.style.horizontally_stretchable = true - - local buff_left_flow = buff_tbl.add({ type = 'flow' }) - buff_left_flow.style.horizontal_align = 'left' - buff_left_flow.style.horizontally_stretchable = true - - local buff_right_flow = buff_tbl.add({ type = 'flow' }) - buff_right_flow.style.horizontal_align = 'right' - buff_right_flow.style.horizontally_stretchable = true - - buff_right_flow.add({ name = on_click_buff_name, type = 'label', caption = '[img=utility/center]', tooltip = { 'stateful.buff_tooltip_click' } }) - - local buff_label = buff_left_flow.add({ type = 'label', caption = { 'stateful.buffs' }, tooltip = { 'stateful.buff_tooltip' } }) - buff_label.style.single_line = false - frame.add({ type = 'line', direction = 'vertical' }) - - spacer(frame) - - frame.add({ type = 'line' }) - end - - spacer(frame) - - if stateful.objectives_completed.boss_time then - local gather_objective_tbl = frame.add { type = 'table', column_count = 2 } - gather_objective_tbl.style.horizontally_stretchable = true - - local gather_warning_flow = gather_objective_tbl.add({ type = 'flow' }) - gather_warning_flow.style.horizontal_align = 'left' - gather_warning_flow.style.horizontally_stretchable = true - - gather_warning_flow.add({ type = 'label', caption = { 'stateful.gather' } }) - frame.add({ type = 'line', direction = 'vertical' }) - - local objective_tbl = frame.add { type = 'table', column_count = 2 } - objective_tbl.style.horizontally_stretchable = true - - local warn_timer_flow_left = objective_tbl.add({ type = 'flow' }) - warn_timer_flow_left.style.horizontal_align = 'left' - warn_timer_flow_left.style.horizontally_stretchable = true - - warn_timer_flow_left.add({ type = 'label', caption = { 'stateful.warp' }, tooltip = { 'stateful.warp_tooltip' } }) - frame.add({ type = 'line', direction = 'vertical' }) - - local warn_timer_flow_right = objective_tbl.add({ type = 'flow' }) - warn_timer_flow_right.style.horizontal_align = 'right' - warn_timer_flow_right.style.horizontally_stretchable = true - - local time_left = floor(stateful.collection.gather_time / 60 / 60) .. 'm' - - if stateful.collection.gather_time / 60 / 60 <= 1 then - time_left = floor(stateful.collection.gather_time / 60) .. 's' - end - - data.gather_time_label = warn_timer_flow_right.add({ type = 'label', caption = time_left }) - else - local objective_tbl = frame.add { type = 'table', column_count = 2 } - objective_tbl.style.horizontally_stretchable = true - - local zone_left_flow = objective_tbl.add({ type = 'flow' }) - zone_left_flow.style.horizontal_align = 'left' - zone_left_flow.style.horizontally_stretchable = true - - zone_left_flow.add({ type = 'label', caption = { 'stateful.zone' }, tooltip = { 'stateful.zone_tooltip' } }) - frame.add({ type = 'line', direction = 'vertical' }) - local zone_right_flow = objective_tbl.add({ type = 'flow' }) - zone_right_flow.style.horizontal_align = 'right' - zone_right_flow.style.horizontally_stretchable = true - - if breached_wall >= stateful.objectives.randomized_zone then - data.randomized_zone_label = zone_right_flow.add({ type = 'label', caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) - else - data.randomized_zone_label = zone_right_flow.add({ type = 'label', caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) - end - - -- new frame - local wave_left_flow = objective_tbl.add({ type = 'flow' }) - wave_left_flow.style.horizontal_align = 'left' - wave_left_flow.style.horizontally_stretchable = true - - wave_left_flow.add({ type = 'label', caption = { 'stateful.wave' }, tooltip = { 'stateful.wave_tooltip' } }) - frame.add({ type = 'line', direction = 'vertical' }) - local wave_right_flow = objective_tbl.add({ type = 'flow' }) - wave_right_flow.style.horizontal_align = 'right' - wave_right_flow.style.horizontally_stretchable = true - - if wave_number >= stateful.objectives.randomized_wave then - data.randomized_wave_label = wave_right_flow.add({ type = 'label', caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) - else - data.randomized_wave_label = wave_right_flow.add({ type = 'label', caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) - end - - --dynamic conditions - data.random_objectives = {} - - for index = 1, #stateful.selected_objectives do - local objective = stateful.selected_objectives[index] - objective_frames(stateful, frame, objective, data) - end - end - - -- warn players - spacer(frame) - frame.add({ type = 'line' }) - spacer(frame) - -- if not stateful.collection.final_arena_disabled then - -- local final_label = frame.add({type = 'label', caption = {'stateful.tooltip_final'}}) - -- final_label.style.single_line = false - -- else - -- local final_label_disabled = frame.add({type = 'label', caption = {'stateful.tooltip_final_disabled'}}) - -- final_label_disabled.style.single_line = false - -- local reason_label = frame.add({type = 'label', caption = {'stateful.tooltip_completing'}}) - -- reason_label.style.single_line = false - -- end - -- spacer(frame) - -- frame.add({type = 'line'}) - spacer(frame) - - local close = frame.add({ type = 'button', name = close_button, caption = 'Close' }) - close.style.horizontally_stretchable = true - Gui.set_data(frame, data) -end - -local function update_data() - local players = game.connected_players - local stateful = Public.get_stateful() - local breached_wall = Public.get('breached_wall') - if not breached_wall then - return - end - - breached_wall = breached_wall - 1 - local wave_number = WD.get('wave_number') - local collection = stateful.collection - - for i = 1, #players do - local player = players[i] - local f = player.gui.screen[main_frame_name] - local b = player.gui.screen[boss_frame_name] - local data = Gui.get_data(f) - local data_boss = Gui.get_data(b) - - if data then - if data.season_label and data.season_label.valid then - data.season_label.caption = stateful.season - end - if data.rounds_survived_label and data.rounds_survived_label.valid then - data.rounds_survived_label.caption = stateful.rounds_survived - end - if data.randomized_zone_label and data.randomized_zone_label.valid and stateful.objectives.randomized_zone then - if breached_wall >= stateful.objectives.randomized_zone then - data.randomized_zone_label.caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/check_mark_green]' - data.randomized_zone_label.tooltip = { 'stateful.tooltip_completed' } - else - data.randomized_zone_label.caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/not_available]' - end - end - - if data.randomized_wave_label and data.randomized_wave_label.valid and stateful.objectives.randomized_wave then - if wave_number >= stateful.objectives.randomized_wave then - data.randomized_wave_label.caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/check_mark_green]' - data.randomized_wave_label.tooltip = { 'stateful.tooltip_completed' } - else - data.randomized_wave_label.caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/not_available]' - end - end - - if data.supply and next(data.supply) then - local items_done = 0 - local supplies = stateful.objectives.supplies - if supplies then - for index = 1, #data.supply do - local frame = data.supply[index] - if frame and frame.valid then - local supplies_data = supplies[index] - local count = Stateful.get_item_produced_count(supplies_data.name) - if count then - if not supplies_data.total then - supplies_data.total = supplies_data.count - end - supplies_data.count = supplies_data.total - count - if supplies_data.count <= 0 then - supplies_data.count = 0 - items_done = items_done + 1 - frame.number = nil - frame.sprite = 'utility/check_mark_green' - else - frame.number = supplies_data.count - frame.tooltip = 'Crafted: ' .. count .. '\nNeeded: ' .. supplies_data.total - end - if items_done == 3 then - if data.supply_completed and data.supply_completed.valid then - data.supply_completed.caption = ' [img=utility/check_mark_green]' - end - end - else - frame.number = supplies_data.count - frame.tooltip = 'Crafted: 0\nNeeded: ' .. supplies_data.total - end - end - end - end - end - - if data.single_item and data.single_item.valid then - local single_item = stateful.objectives.single_item - if single_item then - local frame = data.single_item - local count = Stateful.get_item_produced_count(single_item.name) - if count then - if not single_item.total then - single_item.total = single_item.count - end - single_item.count = single_item.total - count - if single_item.count <= 0 then - single_item.count = 0 - frame.number = nil - frame.sprite = 'utility/check_mark_green' - if data.single_item_complete and data.single_item_complete.valid then - data.single_item_complete.caption = ' [img=utility/check_mark_green]' - end - else - frame.number = single_item.count - frame.tooltip = count .. ' / ' .. single_item.total - frame.tooltip = 'Crafted: ' .. count .. '\nNeeded: ' .. single_item.total - end - else - frame.number = single_item.count - frame.tooltip = 'Crafted: 0\nNeeded: ' .. single_item.total - end - end - end - - if stateful.collection.gather_time and data.gather_time_label and data.gather_time_label.valid then - local time_left = floor(stateful.collection.gather_time / 60 / 60) .. 'm' - - if stateful.collection.gather_time / 60 / 60 <= 1 then - time_left = floor(stateful.collection.gather_time / 60) .. 's' - end - data.gather_time_label.caption = time_left - end - - if data.random_objectives and next(data.random_objectives) then - for index = 1, #data.random_objectives do - local frame_data = data.random_objectives[index] - local name = frame_data.name - local frame = frame_data.frame - for objective_index = 1, #stateful.selected_objectives do - local objective = stateful.selected_objectives[objective_index] - local objective_name = objective.name - local callback = Task.get(objective.token) - local _, _, objective_locale_right, _, objective_tooltip_right = callback() - if name == objective_name and frame and frame.valid then - frame.caption = objective_locale_right - frame.tooltip = objective_tooltip_right - end - end - end - end - end - if data_boss then - if data_boss.season_label and data_boss.season_label.valid then - data_boss.season_label.caption = stateful.season - end - if data_boss.rounds_survived_label and data_boss.rounds_survived_label.valid then - data_boss.rounds_survived_label.caption = stateful.rounds_survived - end - - if collection.survive_for and data_boss.survive_for and data_boss.survive_for.valid then - if not stateful.objectives_completed.warn_players then - stateful.objectives_completed.warn_players = true - alert_players_sound() - end - - local survive_for_timer = floor(collection.survive_for / 60 / 60) .. 'm' - - if collection.survive_for / 60 / 60 <= 1 then - survive_for_timer = floor(collection.survive_for / 60) .. 's' - end - - if collection.survive_for <= 0 then - data_boss.survive_for.caption = { 'stateful.won' } - else - data_boss.survive_for.caption = survive_for_timer - end - end - end - end -end - -local function update_raw() - local game_lost = Public.get('game_lost') - - if game_lost then - clear_all_frames() - return - end - - local stateful = Public.get_stateful() - if not stateful or not stateful.objectives then - return - end - local breached_wall = Public.get('breached_wall') - if not breached_wall then - return - end - local wave_number = WD.get('wave_number') - local collection = stateful.collection - local tick = game.tick - - breached_wall = breached_wall - 1 - if stateful.objectives.randomized_zone then - if breached_wall >= stateful.objectives.randomized_zone then - if not stateful.objectives_completed.randomized_zone then - stateful.objectives_completed.randomized_zone = true - stateful.objectives_time_spent.randomized_zone = tick - play_achievement_unlocked() - Alert.alert_all_players(100, 'Objective: **breach zone** has been complete!') - Server.to_discord_embed('Objective: **breach zone** has been complete!') - stateful.objectives_completed_count = stateful.objectives_completed_count + 1 - end - end - end - - if stateful.objectives.randomized_wave then - if wave_number >= stateful.objectives.randomized_wave then - if not stateful.objectives_completed.randomized_wave then - stateful.objectives_completed.randomized_wave = true - stateful.objectives_time_spent.randomized_wave = tick - - play_achievement_unlocked() - Alert.alert_all_players(100, 'Objective: **survive until wave** has been complete!') - Server.to_discord_embed('Objective: **survive until wave** has been complete!') - stateful.objectives_completed_count = stateful.objectives_completed_count + 1 - end - end - end - - if stateful.objectives.supplies and next(stateful.objectives.supplies) then - local items_done = 0 - for index = 1, #stateful.objectives.supplies do - local supplies_data = stateful.objectives.supplies[index] - local count = Stateful.get_item_produced_count(supplies_data.name) - if count then - if not supplies_data.total then - supplies_data.total = supplies_data.count - end - supplies_data.count = supplies_data.total - count - if supplies_data.count <= 0 then - supplies_data.count = 0 - items_done = items_done + 1 - end - if items_done == 3 then - if not stateful.objectives_completed.supplies then - stateful.objectives_completed.supplies = true - stateful.objectives_time_spent.supplies = tick - Alert.alert_all_players(100, 'Objective: **produce 3 items multiple times** has been complete!') - Server.to_discord_embed('Objective: **produce 3 items multiple times** has been complete!') - play_achievement_unlocked() - stateful.objectives_completed_count = stateful.objectives_completed_count + 1 - end - end - else - if not supplies_data.total then - supplies_data.total = supplies_data.count - end - supplies_data.count = supplies_data.total - end - end - end - - if stateful.objectives.single_item then - local count = Stateful.get_item_produced_count(stateful.objectives.single_item.name) - if count then - if not stateful.objectives.single_item.total then - stateful.objectives.single_item.total = stateful.objectives.single_item.count - end - stateful.objectives.single_item.count = stateful.objectives.single_item.total - count - if stateful.objectives.single_item.count <= 0 then - stateful.objectives.single_item.count = 0 - if not stateful.objectives_completed.single_item then - stateful.objectives_completed.single_item = true - stateful.objectives_time_spent.single_item = tick - play_achievement_unlocked() - Alert.alert_all_players(100, 'Objective: **produce an item multiple times** has been completed!') - Server.to_discord_embed('Objective: **produce an item multiple times** has been completed!') - stateful.objectives_completed_count = stateful.objectives_completed_count + 1 - end - end - else - if not stateful.objectives.single_item.total then - stateful.objectives.single_item.total = stateful.objectives.single_item.count - end - - stateful.objectives.single_item.count = stateful.objectives.single_item.total - end - end - - if collection.gather_time and not collection.final_arena_disabled then - collection.gather_time = collection.gather_time_timer - tick - if collection.gather_time > 0 then - collection.gather_time = collection.gather_time - elseif collection.gather_time and collection.gather_time <= 0 then - collection.gather_time = 0 - if not collection.gather_time_done then - collection.gather_time_done = true - if not collection.clear_rocks then - Public.find_rocks_and_slowly_remove() - collection.clear_rocks = true - end - LinkedChests.clear_linked_frames() - stateful.final_battle = true - Public.set('final_battle', true) - WD.set('final_battle', true) - - collection.survive_for = game.tick + Stateful.scale(10 * 3600, 35 * 3600) - collection.survive_for_timer = collection.survive_for - WD.disable_spawning_biters(false) - Public.allocate() - Public.set_final_battle() - Server.to_discord_embed('Final battle starts now!') - refresh_boss_frame() - end - end - end - - if collection.survive_for and collection.survive_for_timer then - collection.survive_for = collection.survive_for_timer - tick - if not collection.survive_for_alerted and collection.gather_time <= 0 then - collection.survive_for_alerted = true - refresh_boss_frame() - end - - if collection.survive_for and collection.survive_for < 0 then - collection.survive_for = 0 - if collection.game_won and not collection.game_won_notified then - game.print('[color=yellow][Mtn v3][/color] Game won!') - collection.game_won = true - stateful.collection.gather_time = 0 - stateful.collection.gather_time_timer = 0 - collection.survive_for = 0 - collection.survive_for_timer = 0 - refresh_frames() - - local reversed = Public.get_stateful_settings('reversed') - if reversed then - Public.set_stateful_settings('reversed', false) - else - Public.set_stateful_settings('reversed', true) - end - - collection.game_won_notified = true - refresh_boss_frame() - play_game_won() - WD.disable_spawning_biters(true) - Collapse.start_now(false) - WD.nuke_wave_gui() - Server.to_discord_embed('Game won!') - stateful.rounds_survived = stateful.rounds_survived + 1 - stateful.selected_objectives = nil - local buff = Stateful.save_settings() - notify_won_to_discord(buff) - local locomotive = Public.get('locomotive') - if locomotive and locomotive.valid then - locomotive.surface.spill_item_stack(locomotive.position, { name = 'coin', count = 512 }, false) - end - Public.set('game_reset_tick', 5400) - return - end - end - end - - if stateful.selected_objectives and next(stateful.selected_objectives) then - for objective_index = 1, #stateful.selected_objectives do - local objective = stateful.selected_objectives[objective_index] - local objective_name = objective.name - local callback = Task.get(objective.token) - local completed, _, _ = callback() - if completed and completed == true and not stateful.objectives_completed[objective_name] then - stateful.objectives_completed[objective_name] = true - stateful.objectives_time_spent[objective_name] = tick - Alert.alert_all_players(100, 'Objective: **' .. objective_name .. '** has been completed!') - Server.to_discord_embed('Objective: **' .. objective_name .. '** has been completed!') - play_achievement_unlocked() - stateful.objectives_completed_count = stateful.objectives_completed_count + 1 - end - end - end - - if stateful.objectives_completed_count == stateful.tasks_required_to_win and not stateful.objectives_completed.boss_time then - stateful.objectives_completed.boss_time = true - - Server.to_discord_embed('All objectives has been completed! Take your time to prepare for the final push!') - Alert.alert_all_players(300, 'All objectives has been completed!') - Alert.alert_all_players(300, 'Take your time to prepare for the final push!') - - if stateful.collection.final_arena_disabled then - game.print('[color=yellow][Mtn v3][/color] Game won!') - game.print('[color=yellow][Mtn v3][/color] Final battle arena is currently being tweaked.') - collection.game_won = true - stateful.collection.gather_time = 0 - stateful.collection.gather_time_timer = 0 - collection.survive_for = 0 - collection.survive_for_timer = 0 - refresh_frames() - - local reversed = Public.get_stateful_settings('reversed') - if reversed then - Public.set_stateful_settings('reversed', false) - else - Public.set_stateful_settings('reversed', true) - end - - collection.game_won_notified = true - refresh_boss_frame() - play_game_won() - WD.disable_spawning_biters(true) - Collapse.start_now(false) - WD.nuke_wave_gui() - Server.to_discord_embed('Game won!') - stateful.rounds_survived = stateful.rounds_survived + 1 - stateful.selected_objectives = nil - local buff = Stateful.save_settings() - notify_won_to_discord(buff) - local locomotive = Public.get('locomotive') - if locomotive and locomotive.valid then - locomotive.surface.spill_item_stack(locomotive.position, { name = 'coin', count = 512 }, false) - end - Public.set('game_reset_tick', 5400) - return - end - - stateful.collection.gather_time = tick + (10 * 3600) - stateful.collection.gather_time_timer = tick + (10 * 3600) - game.forces.enemy.evolution_factor = 1 - play_achievement_unlocked() - local reverse_position = zone_settings.zone_depth * (breached_wall + 1) - local reversed = Public.get_stateful_settings('reversed') - if not reversed then - reverse_position = reverse_position * -1 - end - Explosives.disable(true) - WD.disable_spawning_biters(true) - WD.set_track_bosses_only(false) - Collapse.set_reverse_position({ 0, reverse_position }) - Collapse.set_reverse_direction() - Collapse.reverse_start_now(true) - Alert.alert_all_players(200, 'Reverse collapse has been initiated!') - Server.to_discord_embed('Reverse collapse has been initiated!') - -- Public.stateful_blueprints.blueprint() - WD.nuke_wave_gui() - WD.set('final_battle', true) - Public.set('pre_final_battle', true) - - refresh_frames() - end -end - -local function on_player_joined_game(event) - local player = game.players[event.player_index] - if not player then - return - end - - if not player.gui.top[main_button_name] then - create_button(player) - end -end - -Gui.on_click( - main_button_name, - function (event) - local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Mtn v3 open stateful Button') - if is_spamming then - return - end - - local game_lost = Public.get('game_lost') - if game_lost then - clear_all_frames() - return - end - - local player = event.player - if not player or not player.valid then - return - end - - local final_battle = Public.get_stateful('final_battle') - - if final_battle then - local frame = player.gui.screen[boss_frame_name] - if frame then - Gui.remove_data_recursively(frame) - frame.destroy() - else - Gui.clear_all_active_frames(player) - boss_frame(player) - end - else - local frame = player.gui.screen[main_frame_name] - if frame then - Gui.remove_data_recursively(frame) - frame.destroy() - else - Gui.clear_all_active_frames(player) - main_frame(player) - end - end - end -) - -Gui.on_click( - close_button, - function (event) - local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Mtn v3 close stateful Button') - if is_spamming then - return - end - - local player = event.player - if not player or not player.valid then - return - end - - local frame = player.gui.screen[main_frame_name] - - if frame then - Gui.remove_data_recursively(frame) - frame.destroy() - end - - local frame_boss = player.gui.screen[boss_frame_name] - - if frame_boss then - Gui.remove_data_recursively(frame_boss) - frame_boss.destroy() - end - end -) - -Gui.on_click( - close_buffs_window_name, - function (event) - local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Buff Close Button') - if is_spamming then - return - end - local player = event.player - local center = player.gui.center - if not player or not player.valid or not player.character then - return - end - - local frame_buff = center[buffs_window_name] - if frame_buff and frame_buff.valid then - Gui.remove_data_recursively(frame_buff) - frame_buff.destroy() - end - end -) - -Gui.on_custom_close( - buffs_window_name, - function (event) - local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Buff Custom Close') - if is_spamming then - return - end - local player = event.player - local center = player.gui.center - if not player or not player.valid or not player.character then - return - end - - local frame_buff = center[buffs_window_name] - if frame_buff and frame_buff.valid then - Gui.remove_data_recursively(frame_buff) - frame_buff.destroy() - end - end -) - -Gui.on_click( - on_click_buff_name, - function (event) - local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Buff Open Button') - if is_spamming then - return - end - local player = event.player - local center = player.gui.center - if not player or not player.valid or not player.character then - return - end - - local frame_buff = center[buffs_window_name] - if frame_buff and frame_buff.valid then - Gui.remove_data_recursively(frame_buff) - frame_buff.destroy() - else - buff_window(player) - end - end -) - -Event.add(defines.events.on_player_joined_game, on_player_joined_game) -Event.on_nth_tick(30, update_data) -Event.on_nth_tick(30, update_raw) - -Public.boss_frame = boss_frame -Public.clear_all_frames = clear_all_frames -Stateful.refresh_frames = refresh_frames - -return Public +local Event = require 'utils.event' +local SpamProtection = require 'utils.spam_protection' +local Public = require 'maps.mountain_fortress_v3.table' +local Alert = require 'utils.alert' +local Stateful = require 'maps.mountain_fortress_v3.stateful.table' +local Gui = require 'utils.gui' +local WD = require 'modules.wave_defense.table' +local Collapse = require 'modules.collapse' +local Task = require 'utils.task_token' +local Core = require 'utils.core' +local Server = require 'utils.server' +local LinkedChests = require 'maps.mountain_fortress_v3.icw.linked_chests' +local Discord = require 'utils.discord' +local format_number = require 'util'.format_number +local Explosives = require 'modules.explosives' + +local zone_settings = Public.zone_settings +local send_ping_to_channel = Discord.channel_names.mtn_channel +local main_button_name = Gui.uid_name() +local main_frame_name = Gui.uid_name() +local boss_frame_name = Gui.uid_name() +local close_button = Gui.uid_name() +local close_buffs_window_name = Gui.uid_name() +local buffs_window_name = Gui.uid_name() +local on_click_buff_name = Gui.uid_name() +local random = math.random +local floor = math.floor +local scenario_name = Public.scenario_name +local main_frame + +local function create_particles(surface, name, position, amount, cause_position) + local d1 = (-100 + random(0, 200)) * 0.0004 + local d2 = (-100 + random(0, 200)) * 0.0004 + + name = name or 'leaf-particle' + + if cause_position then + d1 = (cause_position.x - position.x) * 0.025 + d2 = (cause_position.y - position.y) * 0.025 + end + + for _ = 1, amount, 1 do + local m = random(4, 10) + local m2 = m * 0.005 + + surface.create_particle( + { + name = name, + position = position, + frame_speed = 1, + vertical_speed = 0.130, + height = 0, + movement = { + (m2 - (random(0, m) * 0.01)) + d1, + (m2 - (random(0, m) * 0.01)) + d2 + } + } + ) + end +end + +local spread_particles_token = + Task.register( + function (event) + local player_index = event.player_index + local player = game.get_player(player_index) + if not player or not player.valid then + return + end + local particle = event.particle + + create_particles(player.surface, particle, player.position, 128) + end + ) + +local function pretty_format(input) + local action = string.gsub(input, '-', ' ') + local result = string.upper(string.sub(action, 1, 1)) .. string.sub(action, 2) + return result +end + +local function notify_won_to_discord(buff) + if not buff then + return error('Buff is required when sending message to discord.', 2) + end + local server_name_matches = Server.check_server_name(scenario_name) + + local stateful = Public.get_stateful() + + local wave = WD.get_wave() + local date = Server.get_start_time() + game.server_save('Complete_Mtn_v3_' .. tostring(date) .. '_wave' .. tostring(wave)) + + local time_played = Core.format_time(game.ticks_played) + local total_players = #game.players + local total_connected_players = #game.connected_players + local pickaxe_upgrades = Public.pickaxe_upgrades + local upgrades = Public.get('upgrades') + local pick_tier = pickaxe_upgrades[upgrades.pickaxe_tier] + + local text = { + title = 'Game won!', + description = 'Game statistics from the game is below', + color = 'success', + field1 = { + text1 = 'Time played:', + text2 = time_played, + inline = 'false' + }, + field2 = { + text1 = 'Rounds survived:', + text2 = stateful.rounds_survived, + inline = 'false' + }, + field3 = { + text1 = 'Wave:', + text2 = format_number(wave, true), + inline = 'false' + }, + field4 = { + text1 = 'Total connected players:', + text2 = total_players, + inline = 'false' + }, + field5 = { + text1 = 'Pickaxe Upgrade:', + text2 = pick_tier .. ' (' .. upgrades.pickaxe_tier .. ')', + inline = 'false' + }, + field6 = { + text1 = 'Connected players:', + text2 = total_connected_players, + inline = 'false' + }, + field7 = { + text1 = 'Buff granted:', + text2 = buff.discord, + inline = 'false' + } + } + if server_name_matches then + Server.to_discord_named_parsed_embed(send_ping_to_channel, text) + else + Server.to_discord_embed_parsed(text) + end +end + +local function clear_all_frames() + Core.iter_players( + function (player) + local b_frame = player.gui.screen[boss_frame_name] + if b_frame then + Gui.remove_data_recursively(b_frame) + b_frame.destroy() + end + + local frame = player.gui.screen[main_frame_name] + if frame then + Gui.remove_data_recursively(frame) + frame.destroy() + end + end + ) +end + +local function refresh_frames() + Core.iter_connected_players( + function (player) + local frame = player.gui.screen[main_frame_name] + if frame and frame.valid then + Gui.remove_data_recursively(frame) + frame.destroy() + main_frame(player) + else + main_frame(player) + end + end + ) +end + +local warn_player_sound_token = + Task.register( + function (event) + local player_index = event.player_index + local player = game.get_player(player_index) + if not player or not player.valid then + return + end + local particle = event.particle + + player.play_sound { path = 'utility/new_objective', volume_modifier = 0.75 } + + create_particles(player.surface, particle, player.position, 128) + end + ) + +local function create_button(player) + if Gui.get_mod_gui_top_frame() then + local b = + Gui.add_mod_button( + player, + { + type = 'sprite-button', + name = main_button_name, + sprite = 'utility/custom_tag_icon', + tooltip = 'Has information about all objectives that needs to be completed', + style = Gui.button_style + } + ) + if b then + b.style.font_color = { 165, 165, 165 } + b.style.font = 'heading-3' + b.style.minimal_height = 36 + b.style.maximal_height = 36 + b.style.minimal_width = 40 + b.style.padding = -2 + end + else + local b = + player.gui.top.add( + { + type = 'sprite-button', + name = main_button_name, + sprite = 'utility/custom_tag_icon', + tooltip = 'Has information about all objectives that needs to be completed', + style = Gui.button_style + } + ) + b.style.minimal_height = 38 + b.style.maximal_height = 38 + end +end + +local function create_input_element(frame, type, value, items, index, tooltip, custom_space) + if type == 'slider' then + return frame.add({ type = 'slider', value = value, minimum_value = 0, maximum_value = 1 }) + end + if type == 'boolean' then + return frame.add({ type = 'checkbox', state = value }) + end + if type == 'label' then + local label = frame.add({ type = 'label', caption = value }) + label.style.font = 'default-listbox' + label.tooltip = tooltip or '' + if custom_space then + label.style.minimal_height = custom_space + end + return label + end + if type == 'dropdown' then + return frame.add({ type = 'drop-down', items = items, selected_index = index }) + end + return frame.add({ type = 'text-box', text = value }) +end + +local function play_game_won() + Explosives.disable(false) + Core.iter_connected_players( + function (player) + Explosives.detonate_entity(player) + player.play_sound { path = 'utility/game_won', volume_modifier = 0.75 } + Task.set_timeout_in_ticks(10, spread_particles_token, { player_index = player.index, particle = 'iron-ore-particle' }) + Task.set_timeout_in_ticks(15, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(20, spread_particles_token, { player_index = player.index, particle = 'copper-ore-particle' }) + Task.set_timeout_in_ticks(25, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(30, spread_particles_token, { player_index = player.index, particle = 'stone-particle' }) + Task.set_timeout_in_ticks(35, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(40, spread_particles_token, { player_index = player.index, particle = 'coal-particle' }) + Task.set_timeout_in_ticks(45, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + end + ) +end + +local function play_achievement_unlocked() + Core.iter_connected_players( + function (player) + player.play_sound { path = 'utility/achievement_unlocked', volume_modifier = 0.75 } + Task.set_timeout_in_ticks(10, spread_particles_token, { player_index = player.index, particle = 'iron-ore-particle' }) + Task.set_timeout_in_ticks(15, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(20, spread_particles_token, { player_index = player.index, particle = 'copper-ore-particle' }) + Task.set_timeout_in_ticks(25, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(30, spread_particles_token, { player_index = player.index, particle = 'stone-particle' }) + Task.set_timeout_in_ticks(35, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(40, spread_particles_token, { player_index = player.index, particle = 'coal-particle' }) + Task.set_timeout_in_ticks(45, spread_particles_token, { player_index = player.index, particle = 'branch-particle' }) + end + ) +end + +local function alert_players_sound() + Core.iter_connected_players( + function (player) + Task.set_timeout_in_ticks(10, warn_player_sound_token, { player_index = player.index, particle = 'iron-ore-particle' }) + Task.set_timeout_in_ticks(20, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(30, warn_player_sound_token, { player_index = player.index, particle = 'copper-ore-particle' }) + Task.set_timeout_in_ticks(40, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(50, warn_player_sound_token, { player_index = player.index, particle = 'stone-particle' }) + Task.set_timeout_in_ticks(60, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) + Task.set_timeout_in_ticks(70, warn_player_sound_token, { player_index = player.index, particle = 'coal-particle' }) + Task.set_timeout_in_ticks(80, warn_player_sound_token, { player_index = player.index, particle = 'branch-particle' }) + end + ) +end + +local function spacer(frame) + local flow = frame.add({ type = 'flow' }) + flow.style.minimal_height = 2 +end + +local function objective_frames(stateful, player_frame, objective, data) + local objective_name = objective.name + if objective_name == 'supplies' or objective_name == 'single_item' then + local supplies = stateful.objectives.supplies + local tbl = player_frame.add { type = 'table', column_count = 2 } + tbl.style.horizontally_stretchable = true + local left_flow = tbl.add({ type = 'flow' }) + left_flow.style.horizontal_align = 'left' + left_flow.style.horizontally_stretchable = true + + if objective_name == 'single_item' then + left_flow.add({ type = 'label', caption = { 'stateful.production_single' }, tooltip = { 'stateful.production_tooltip' } }) + else + left_flow.add({ type = 'label', caption = { 'stateful.production' }, tooltip = { 'stateful.production_tooltip' } }) + end + player_frame.add({ type = 'line', direction = 'vertical' }) + local right_flow = tbl.add({ type = 'flow' }) + right_flow.style.horizontal_align = 'right' + right_flow.style.horizontally_stretchable = true + + if objective_name == 'single_item' then + if stateful.objectives_completed.single_item then + data.single_item_complete = right_flow.add({ type = 'label', caption = ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) + else + data.single_item_complete = right_flow.add({ type = 'label', caption = ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) + end + else + if stateful.objectives_completed.supplies then + data.supply_completed = right_flow.add({ type = 'label', caption = ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) + else + data.supply_completed = right_flow.add({ type = 'label', caption = ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) + end + end + + if not data.supply then + data.supply = {} + end + + local flow = player_frame.add({ type = 'flow' }) + local item_table = flow.add({ type = 'table', name = 'item_table', column_count = 3 }) + if objective_name ~= 'single_item' then + data.supply[#data.supply + 1] = item_table.add({ type = 'sprite-button', name = supplies[1].name, sprite = 'item/' .. supplies[1].name, enabled = false, number = supplies[1].count }) + data.supply[#data.supply + 1] = item_table.add({ type = 'sprite-button', name = supplies[2].name, sprite = 'item/' .. supplies[2].name, enabled = false, number = supplies[2].count }) + data.supply[#data.supply + 1] = item_table.add({ type = 'sprite-button', name = supplies[3].name, sprite = 'item/' .. supplies[3].name, enabled = false, number = supplies[3].count }) + else + local single_item = stateful.objectives.single_item + data.single_item = item_table.add({ type = 'sprite-button', name = single_item.name, sprite = 'item/' .. single_item.name, enabled = false, number = single_item.count }) + end + + return + end + + local callback = Task.get(objective.token) + + local _, objective_locale_left, objective_locale_right, tooltip_left, tooltip_right = callback() + + local tbl = player_frame.add { type = 'table', column_count = 2 } + tbl.style.horizontally_stretchable = true + local left_flow = tbl.add({ type = 'flow' }) + left_flow.style.horizontal_align = 'left' + left_flow.style.horizontally_stretchable = true + + left_flow.add({ type = 'label', caption = objective_locale_left, tooltip = tooltip_left }) + local right_flow = tbl.add({ type = 'flow' }) + right_flow.style.horizontal_align = 'right' + right_flow.style.horizontally_stretchable = true + + local objective_locale_right_label = right_flow.add({ type = 'label', caption = objective_locale_right, tooltip = tooltip_right }) + data.random_objectives[#data.random_objectives + 1] = { name = objective_name, frame = objective_locale_right_label } + return +end + +local function buff_window(player) + local buff_frame_name, inside_table = Gui.add_main_frame_with_toolbar(player, 'center', buffs_window_name, nil, close_buffs_window_name, 'Buffs gathered') + if not buff_frame_name then + return + end + if not inside_table then + return + end + + local stateful = Public.get_stateful() + + local inside_table_style = inside_table.style + inside_table_style.width = 530 + + local info_text = inside_table.add({ type = 'label', caption = 'All the buffs that have been gathered throughout the runs!' }) + local info_text_style = info_text.style + info_text_style.font = 'heading-2' + info_text_style.padding = 0 + info_text_style.left_padding = 10 + info_text_style.horizontal_align = 'left' + info_text_style.vertical_align = 'bottom' + info_text_style.font_color = { 0.55, 0.55, 0.99 } + + local buff_pane = inside_table.add({ type = 'scroll-pane' }) + local ns = buff_pane.style + ns.vertically_squashable = true + ns.bottom_padding = 5 + ns.left_padding = 5 + ns.right_padding = 5 + ns.top_padding = 5 + + buff_pane.add({ type = 'line' }) + + local starting_items_label = buff_pane.add({ type = 'label', caption = 'Starting items' }) + local starting_items_label_style = starting_items_label.style + starting_items_label_style.font = 'heading-3' + starting_items_label_style.padding = 0 + starting_items_label_style.horizontal_align = 'left' + starting_items_label_style.font_color = { 0.55, 0.55, 0.99 } + + local starting_grid = buff_pane.add({ type = 'table', column_count = 8 }) + + buff_pane.add({ type = 'line' }) + + local force_label = buff_pane.add({ type = 'label', caption = 'Force Buffs' }) + local force_label_style = force_label.style + force_label_style.font = 'heading-3' + force_label_style.padding = 0 + force_label_style.horizontal_align = 'left' + force_label_style.font_color = { 0.55, 0.55, 0.99 } + local force_grid = buff_pane.add({ type = 'table', column_count = 2 }) + + buff_pane.add({ type = 'line' }) + + local custom_label = buff_pane.add({ type = 'label', caption = 'Custom Buffs' }) + local custom_label_style = custom_label.style + custom_label_style.font = 'heading-3' + custom_label_style.padding = 0 + custom_label_style.horizontal_align = 'left' + custom_label_style.font_color = { 0.55, 0.55, 0.99 } + local custom_grid = buff_pane.add({ type = 'table', column_count = 2 }) + + if stateful.buffs and next(stateful.buffs) then + if stateful.buffs_collected and next(stateful.buffs_collected) then + if stateful.buffs_collected.starting_items then + for item_name, item_data in pairs(stateful.buffs_collected.starting_items) do + -- local text = pretty_format(item_name) .. ': [font=font-bold]' .. item_data.count + local text = '[font=default-large] [item=' .. item_name .. '][/font]' .. ': [font=default-bold]' .. item_data.count .. '[/font]' + create_input_element(starting_grid, 'label', text, nil, nil, item_data.discord, 30) + end + end + + for name, buff_data in pairs(stateful.buffs_collected) do + if type(buff_data.amount) ~= 'table' and buff_data.force then + local c = buff_data.count + local text + if name == 'xp_level' or name == 'xp_bonus' or name == 'character_health_bonus' then + text = '[font=default-bold]' .. Stateful.buff_to_string[name] .. ': ' .. c .. '[/font]' + else + text = '[font=default-bold]' .. Stateful.buff_to_string[name] .. ': ' .. (c * 100) .. '%[/font]' + end + + create_input_element(force_grid, 'label', text, nil, nil, buff_data.discord) + end + + if name ~= 'starting_items' and not buff_data.force then + if buff_data.name then + local text_to_place = buff_data.count or 'Unlocked' + local text = '[font=default-bold]' .. buff_data.name .. ': ' .. text_to_place .. ' [/font]' + create_input_element(custom_grid, 'label', text, nil, nil, buff_data.discord) + else + for _, buff in pairs(buff_data) do + local text_to_place = buff.count or 'Unlocked' + local text = '[font=default-bold]' .. pretty_format(buff.name) .. ': ' .. text_to_place .. ' [/font]' + create_input_element(custom_grid, 'label', text, nil, nil, buff.discord) + end + end + end + end + end + end + + player.opened = buff_frame_name +end + +local function boss_frame(player, alert) + local main_winning_frame = player.gui.screen[main_frame_name] + if main_winning_frame then + Gui.remove_data_recursively(main_winning_frame) + main_winning_frame.destroy() + end + local main_player_boss_frame = player.gui.screen[boss_frame_name] + if main_player_boss_frame then + Gui.remove_data_recursively(main_player_boss_frame) + main_player_boss_frame.destroy() + end + + local data = {} + + local stateful = Public.get_stateful() + local collection = stateful.collection + + local frame = player.gui.screen.add { type = 'frame', name = boss_frame_name, caption = { 'stateful.win_conditions' }, direction = 'vertical' } + if not alert then + frame.location = { x = 1, y = 45 } + else + frame.location = { x = 1, y = 123 } + end + frame.style.maximal_height = 500 + frame.style.minimal_width = 200 + frame.style.maximal_width = 400 + local season_tbl = frame.add { type = 'table', column_count = 2 } + season_tbl.style.horizontally_stretchable = true + + local season_left_flow = season_tbl.add({ type = 'flow' }) + season_left_flow.style.horizontal_align = 'left' + season_left_flow.style.horizontally_stretchable = true + + season_left_flow.add({ type = 'label', caption = { 'stateful.season' }, tooltip = { 'stateful.season_tooltip', stateful.time_to_reset } }) + frame.add({ type = 'line', direction = 'vertical' }) + local season_right_flow = season_tbl.add({ type = 'flow' }) + season_right_flow.style.horizontal_align = 'right' + season_right_flow.style.horizontally_stretchable = true + + data.season_label = season_right_flow.add({ type = 'label', caption = stateful.season }) + + spacer(frame) + + local rounds_survived_tbl = frame.add { type = 'table', column_count = 2 } + rounds_survived_tbl.style.horizontally_stretchable = true + + local rounds_survived_left_flow = rounds_survived_tbl.add({ type = 'flow' }) + rounds_survived_left_flow.style.horizontal_align = 'left' + rounds_survived_left_flow.style.horizontally_stretchable = true + + rounds_survived_left_flow.add({ type = 'label', caption = { 'stateful.rounds_survived' }, tooltip = { 'stateful.rounds_survived_tooltip' } }) + frame.add({ type = 'line', direction = 'vertical' }) + local rounds_survived_right_flow = rounds_survived_tbl.add({ type = 'flow' }) + rounds_survived_right_flow.style.horizontal_align = 'right' + rounds_survived_right_flow.style.horizontally_stretchable = true + + data.rounds_survived_label = rounds_survived_right_flow.add({ type = 'label', caption = stateful.rounds_survived }) + spacer(frame) + + frame.add({ type = 'line' }) + + spacer(frame) + + if not collection.game_won then + local objective_tbl = frame.add { type = 'table', column_count = 2 } + objective_tbl.style.horizontally_stretchable = true + + if collection.gather_time <= 0 then + local survive_for_left_flow = objective_tbl.add({ type = 'flow' }) + survive_for_left_flow.style.horizontal_align = 'left' + survive_for_left_flow.style.horizontally_stretchable = true + + survive_for_left_flow.add({ type = 'label', caption = { 'stateful.survive_for' } }) + frame.add({ type = 'line', direction = 'vertical' }) + local survive_for_right_flow = objective_tbl.add({ type = 'flow' }) + survive_for_right_flow.style.horizontal_align = 'right' + survive_for_right_flow.style.horizontally_stretchable = true + + local survive_for_timer = floor(collection.survive_for / 60 / 60) .. 'm' + + if collection.survive_for / 60 / 60 <= 1 then + survive_for_timer = floor(collection.survive_for / 60) .. 's' + end + + if collection.survive_for <= 0 then + data.survive_for = survive_for_right_flow.add({ type = 'label', caption = { 'stateful.won' } }) + else + data.survive_for = survive_for_right_flow.add({ type = 'label', caption = survive_for_timer }) + end + end + -- new frame + local biter_sprites_tbl = objective_tbl.add({ type = 'flow' }) + biter_sprites_tbl.style.horizontal_align = 'left' + biter_sprites_tbl.style.horizontally_stretchable = true + + biter_sprites_tbl.add({ type = 'label', caption = { 'stateful.biter_sprites' } }) + else + local objective_tbl = frame.add { type = 'table', column_count = 2 } + objective_tbl.style.horizontally_stretchable = true + + local game_won_left_flow = objective_tbl.add({ type = 'flow' }) + game_won_left_flow.style.horizontal_align = 'left' + game_won_left_flow.style.horizontally_stretchable = true + + game_won_left_flow.add({ type = 'label', caption = { 'stateful.game_won' } }) + end + + local close = frame.add({ type = 'button', name = close_button, caption = 'Close' }) + close.style.horizontally_stretchable = true + Gui.set_data(frame, data) +end + +local function refresh_boss_frame() + Core.iter_connected_players( + function (player) + boss_frame(player) + end + ) +end + +main_frame = function (player) + local main_player_frame = player.gui.screen[main_frame_name] + if main_player_frame then + Gui.remove_data_recursively(main_player_frame) + main_player_frame.destroy() + end + + local data = {} + + local stateful = Public.get_stateful() + local breached_wall = Public.get('breached_wall') + breached_wall = breached_wall - 1 + local wave_number = WD.get('wave_number') + + local frame = player.gui.screen.add { type = 'frame', name = main_frame_name, caption = { 'stateful.win_conditions' }, direction = 'vertical', tooltip = { 'stateful.win_conditions_tooltip' } } + if Gui.get_mod_gui_top_frame() then + frame.location = { x = 0, y = 67 } + else + frame.location = { x = 1, y = 45 } + end + frame.style.maximal_height = 700 + frame.style.minimal_width = 200 + frame.style.maximal_width = 400 + local season_tbl = frame.add { type = 'table', column_count = 2 } + season_tbl.style.horizontally_stretchable = true + + local season_left_flow = season_tbl.add({ type = 'flow' }) + season_left_flow.style.horizontal_align = 'left' + season_left_flow.style.horizontally_stretchable = true + + season_left_flow.add({ type = 'label', caption = { 'stateful.season' }, tooltip = { 'stateful.season_tooltip', stateful.time_to_reset } }) + frame.add({ type = 'line', direction = 'vertical' }) + local season_right_flow = season_tbl.add({ type = 'flow' }) + season_right_flow.style.horizontal_align = 'right' + season_right_flow.style.horizontally_stretchable = true + + data.season_label = season_right_flow.add({ type = 'label', caption = stateful.season }) + + spacer(frame) + + local rounds_survived_tbl = frame.add { type = 'table', column_count = 2 } + rounds_survived_tbl.style.horizontally_stretchable = true + + local rounds_survived_left_flow = rounds_survived_tbl.add({ type = 'flow' }) + rounds_survived_left_flow.style.horizontal_align = 'left' + rounds_survived_left_flow.style.horizontally_stretchable = true + + rounds_survived_left_flow.add({ type = 'label', caption = { 'stateful.rounds_survived' }, tooltip = { 'stateful.rounds_survived_tooltip' } }) + frame.add({ type = 'line', direction = 'vertical' }) + local rounds_survived_right_flow = rounds_survived_tbl.add({ type = 'flow' }) + rounds_survived_right_flow.style.horizontal_align = 'right' + rounds_survived_right_flow.style.horizontally_stretchable = true + + data.rounds_survived_label = rounds_survived_right_flow.add({ type = 'label', caption = stateful.rounds_survived }) + spacer(frame) + + frame.add({ type = 'line' }) + + spacer(frame) + + if stateful.buffs and next(stateful.buffs) then + local buff_tbl = frame.add { type = 'table', column_count = 2 } + buff_tbl.style.horizontally_stretchable = true + + local buff_left_flow = buff_tbl.add({ type = 'flow' }) + buff_left_flow.style.horizontal_align = 'left' + buff_left_flow.style.horizontally_stretchable = true + + local buff_right_flow = buff_tbl.add({ type = 'flow' }) + buff_right_flow.style.horizontal_align = 'right' + buff_right_flow.style.horizontally_stretchable = true + + buff_right_flow.add({ name = on_click_buff_name, type = 'label', caption = '[img=utility/center]', tooltip = { 'stateful.buff_tooltip_click' } }) + + local buff_label = buff_left_flow.add({ type = 'label', caption = { 'stateful.buffs' }, tooltip = { 'stateful.buff_tooltip' } }) + buff_label.style.single_line = false + frame.add({ type = 'line', direction = 'vertical' }) + + spacer(frame) + + frame.add({ type = 'line' }) + end + + spacer(frame) + + if stateful.objectives_completed.boss_time then + local gather_objective_tbl = frame.add { type = 'table', column_count = 2 } + gather_objective_tbl.style.horizontally_stretchable = true + + local gather_warning_flow = gather_objective_tbl.add({ type = 'flow' }) + gather_warning_flow.style.horizontal_align = 'left' + gather_warning_flow.style.horizontally_stretchable = true + + gather_warning_flow.add({ type = 'label', caption = { 'stateful.gather' } }) + frame.add({ type = 'line', direction = 'vertical' }) + + local objective_tbl = frame.add { type = 'table', column_count = 2 } + objective_tbl.style.horizontally_stretchable = true + + local warn_timer_flow_left = objective_tbl.add({ type = 'flow' }) + warn_timer_flow_left.style.horizontal_align = 'left' + warn_timer_flow_left.style.horizontally_stretchable = true + + warn_timer_flow_left.add({ type = 'label', caption = { 'stateful.warp' }, tooltip = { 'stateful.warp_tooltip' } }) + frame.add({ type = 'line', direction = 'vertical' }) + + local warn_timer_flow_right = objective_tbl.add({ type = 'flow' }) + warn_timer_flow_right.style.horizontal_align = 'right' + warn_timer_flow_right.style.horizontally_stretchable = true + + local time_left = floor(stateful.collection.gather_time / 60 / 60) .. 'm' + + if stateful.collection.gather_time / 60 / 60 <= 1 then + time_left = floor(stateful.collection.gather_time / 60) .. 's' + end + + data.gather_time_label = warn_timer_flow_right.add({ type = 'label', caption = time_left }) + else + local objective_tbl = frame.add { type = 'table', column_count = 2 } + objective_tbl.style.horizontally_stretchable = true + + local zone_left_flow = objective_tbl.add({ type = 'flow' }) + zone_left_flow.style.horizontal_align = 'left' + zone_left_flow.style.horizontally_stretchable = true + + zone_left_flow.add({ type = 'label', caption = { 'stateful.zone' }, tooltip = { 'stateful.zone_tooltip' } }) + frame.add({ type = 'line', direction = 'vertical' }) + local zone_right_flow = objective_tbl.add({ type = 'flow' }) + zone_right_flow.style.horizontal_align = 'right' + zone_right_flow.style.horizontally_stretchable = true + + if breached_wall >= stateful.objectives.randomized_zone then + data.randomized_zone_label = zone_right_flow.add({ type = 'label', caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) + else + data.randomized_zone_label = zone_right_flow.add({ type = 'label', caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) + end + + -- new frame + local wave_left_flow = objective_tbl.add({ type = 'flow' }) + wave_left_flow.style.horizontal_align = 'left' + wave_left_flow.style.horizontally_stretchable = true + + wave_left_flow.add({ type = 'label', caption = { 'stateful.wave' }, tooltip = { 'stateful.wave_tooltip' } }) + frame.add({ type = 'line', direction = 'vertical' }) + local wave_right_flow = objective_tbl.add({ type = 'flow' }) + wave_right_flow.style.horizontal_align = 'right' + wave_right_flow.style.horizontally_stretchable = true + + if wave_number >= stateful.objectives.randomized_wave then + data.randomized_wave_label = wave_right_flow.add({ type = 'label', caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/check_mark_green]', tooltip = { 'stateful.tooltip_completed' } }) + else + data.randomized_wave_label = wave_right_flow.add({ type = 'label', caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/not_available]', tooltip = { 'stateful.tooltip_not_completed' } }) + end + + --dynamic conditions + data.random_objectives = {} + + for index = 1, #stateful.selected_objectives do + local objective = stateful.selected_objectives[index] + objective_frames(stateful, frame, objective, data) + end + end + + -- warn players + spacer(frame) + frame.add({ type = 'line' }) + spacer(frame) + -- if not stateful.collection.final_arena_disabled then + -- local final_label = frame.add({type = 'label', caption = {'stateful.tooltip_final'}}) + -- final_label.style.single_line = false + -- else + -- local final_label_disabled = frame.add({type = 'label', caption = {'stateful.tooltip_final_disabled'}}) + -- final_label_disabled.style.single_line = false + -- local reason_label = frame.add({type = 'label', caption = {'stateful.tooltip_completing'}}) + -- reason_label.style.single_line = false + -- end + -- spacer(frame) + -- frame.add({type = 'line'}) + spacer(frame) + + local close = frame.add({ type = 'button', name = close_button, caption = 'Close' }) + close.style.horizontally_stretchable = true + Gui.set_data(frame, data) +end + +local function update_data() + local players = game.connected_players + local stateful = Public.get_stateful() + local breached_wall = Public.get('breached_wall') + if not breached_wall then + return + end + + breached_wall = breached_wall - 1 + local wave_number = WD.get('wave_number') + local collection = stateful.collection + + for i = 1, #players do + local player = players[i] + local f = player.gui.screen[main_frame_name] + local b = player.gui.screen[boss_frame_name] + local data = Gui.get_data(f) + local data_boss = Gui.get_data(b) + + if data then + if data.season_label and data.season_label.valid then + data.season_label.caption = stateful.season + end + if data.rounds_survived_label and data.rounds_survived_label.valid then + data.rounds_survived_label.caption = stateful.rounds_survived + end + if data.randomized_zone_label and data.randomized_zone_label.valid and stateful.objectives.randomized_zone then + if breached_wall >= stateful.objectives.randomized_zone then + data.randomized_zone_label.caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/check_mark_green]' + data.randomized_zone_label.tooltip = { 'stateful.tooltip_completed' } + else + data.randomized_zone_label.caption = breached_wall .. '/' .. stateful.objectives.randomized_zone .. ' [img=utility/not_available]' + end + end + + if data.randomized_wave_label and data.randomized_wave_label.valid and stateful.objectives.randomized_wave then + if wave_number >= stateful.objectives.randomized_wave then + data.randomized_wave_label.caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/check_mark_green]' + data.randomized_wave_label.tooltip = { 'stateful.tooltip_completed' } + else + data.randomized_wave_label.caption = wave_number .. '/' .. stateful.objectives.randomized_wave .. ' [img=utility/not_available]' + end + end + + if data.supply and next(data.supply) then + local items_done = 0 + local supplies = stateful.objectives.supplies + if supplies then + for index = 1, #data.supply do + local frame = data.supply[index] + if frame and frame.valid then + local supplies_data = supplies[index] + local count = Stateful.get_item_produced_count(supplies_data.name) + if count then + if not supplies_data.total then + supplies_data.total = supplies_data.count + end + supplies_data.count = supplies_data.total - count + if supplies_data.count <= 0 then + supplies_data.count = 0 + items_done = items_done + 1 + frame.number = nil + frame.sprite = 'utility/check_mark_green' + else + frame.number = supplies_data.count + frame.tooltip = 'Crafted: ' .. count .. '\nNeeded: ' .. (supplies_data.total or 0) + end + if items_done == 3 then + if data.supply_completed and data.supply_completed.valid then + data.supply_completed.caption = ' [img=utility/check_mark_green]' + end + end + else + frame.number = supplies_data.count + frame.tooltip = 'Crafted: 0\nNeeded: ' .. (supplies_data.total or 0) + end + end + end + end + end + + if data.single_item and data.single_item.valid then + local single_item = stateful.objectives.single_item + if single_item then + local frame = data.single_item + local count = Stateful.get_item_produced_count(single_item.name) + if count then + if not single_item.total then + single_item.total = single_item.count + end + single_item.count = single_item.total - count + if single_item.count <= 0 then + single_item.count = 0 + frame.number = nil + frame.sprite = 'utility/check_mark_green' + if data.single_item_complete and data.single_item_complete.valid then + data.single_item_complete.caption = ' [img=utility/check_mark_green]' + end + else + frame.number = single_item.count + frame.tooltip = count .. ' / ' .. single_item.total + frame.tooltip = 'Crafted: ' .. count .. '\nNeeded: ' .. (single_item.total or 0) + end + else + frame.number = single_item.count + frame.tooltip = 'Crafted: 0\nNeeded: ' .. (single_item.total or 0) + end + end + end + + if stateful.collection.gather_time and data.gather_time_label and data.gather_time_label.valid then + local time_left = floor(stateful.collection.gather_time / 60 / 60) .. 'm' + + if stateful.collection.gather_time / 60 / 60 <= 1 then + time_left = floor(stateful.collection.gather_time / 60) .. 's' + end + data.gather_time_label.caption = time_left + end + + if data.random_objectives and next(data.random_objectives) then + for index = 1, #data.random_objectives do + local frame_data = data.random_objectives[index] + local name = frame_data.name + local frame = frame_data.frame + for objective_index = 1, #stateful.selected_objectives do + local objective = stateful.selected_objectives[objective_index] + local objective_name = objective.name + local callback = Task.get(objective.token) + local _, _, objective_locale_right, _, objective_tooltip_right = callback() + if name == objective_name and frame and frame.valid then + frame.caption = objective_locale_right + frame.tooltip = objective_tooltip_right + end + end + end + end + end + if data_boss then + if data_boss.season_label and data_boss.season_label.valid then + data_boss.season_label.caption = stateful.season + end + if data_boss.rounds_survived_label and data_boss.rounds_survived_label.valid then + data_boss.rounds_survived_label.caption = stateful.rounds_survived + end + + if collection.survive_for and data_boss.survive_for and data_boss.survive_for.valid then + if not stateful.objectives_completed.warn_players then + stateful.objectives_completed.warn_players = true + alert_players_sound() + end + + local survive_for_timer = floor(collection.survive_for / 60 / 60) .. 'm' + + if collection.survive_for / 60 / 60 <= 1 then + survive_for_timer = floor(collection.survive_for / 60) .. 's' + end + + if collection.survive_for <= 0 then + data_boss.survive_for.caption = { 'stateful.won' } + else + data_boss.survive_for.caption = survive_for_timer + end + end + end + end +end + +local function update_raw() + local game_lost = Public.get('game_lost') + + if game_lost then + clear_all_frames() + return + end + + local stateful = Public.get_stateful() + if not stateful or not stateful.objectives then + return + end + local breached_wall = Public.get('breached_wall') + if not breached_wall then + return + end + local wave_number = WD.get('wave_number') + local collection = stateful.collection + local tick = game.tick + + breached_wall = breached_wall - 1 + if stateful.objectives.randomized_zone then + if breached_wall >= stateful.objectives.randomized_zone then + if not stateful.objectives_completed.randomized_zone then + stateful.objectives_completed.randomized_zone = true + stateful.objectives_time_spent.randomized_zone = tick + play_achievement_unlocked() + Alert.alert_all_players(100, 'Objective: **breach zone** has been complete!') + Server.to_discord_embed('Objective: **breach zone** has been complete!') + stateful.objectives_completed_count = stateful.objectives_completed_count + 1 + end + end + end + + if stateful.objectives.randomized_wave then + if wave_number >= stateful.objectives.randomized_wave then + if not stateful.objectives_completed.randomized_wave then + stateful.objectives_completed.randomized_wave = true + stateful.objectives_time_spent.randomized_wave = tick + + play_achievement_unlocked() + Alert.alert_all_players(100, 'Objective: **survive until wave** has been complete!') + Server.to_discord_embed('Objective: **survive until wave** has been complete!') + stateful.objectives_completed_count = stateful.objectives_completed_count + 1 + end + end + end + + if stateful.objectives.supplies and next(stateful.objectives.supplies) then + local items_done = 0 + for index = 1, #stateful.objectives.supplies do + local supplies_data = stateful.objectives.supplies[index] + local count = Stateful.get_item_produced_count(supplies_data.name) + if count then + if not supplies_data.total then + supplies_data.total = supplies_data.count + end + supplies_data.count = supplies_data.total - count + if supplies_data.count <= 0 then + supplies_data.count = 0 + items_done = items_done + 1 + end + if items_done == 3 then + if not stateful.objectives_completed.supplies then + stateful.objectives_completed.supplies = true + stateful.objectives_time_spent.supplies = tick + Alert.alert_all_players(100, 'Objective: **produce 3 items multiple times** has been complete!') + Server.to_discord_embed('Objective: **produce 3 items multiple times** has been complete!') + play_achievement_unlocked() + stateful.objectives_completed_count = stateful.objectives_completed_count + 1 + end + end + else + if not supplies_data.total then + supplies_data.total = supplies_data.count + end + supplies_data.count = supplies_data.total + end + end + end + + if stateful.objectives.single_item then + local count = Stateful.get_item_produced_count(stateful.objectives.single_item.name) + if count then + if not stateful.objectives.single_item.total then + stateful.objectives.single_item.total = stateful.objectives.single_item.count + end + stateful.objectives.single_item.count = stateful.objectives.single_item.total - count + if stateful.objectives.single_item.count <= 0 then + stateful.objectives.single_item.count = 0 + if not stateful.objectives_completed.single_item then + stateful.objectives_completed.single_item = true + stateful.objectives_time_spent.single_item = tick + play_achievement_unlocked() + Alert.alert_all_players(100, 'Objective: **produce an item multiple times** has been completed!') + Server.to_discord_embed('Objective: **produce an item multiple times** has been completed!') + stateful.objectives_completed_count = stateful.objectives_completed_count + 1 + end + end + else + if not stateful.objectives.single_item.total then + stateful.objectives.single_item.total = stateful.objectives.single_item.count + end + + stateful.objectives.single_item.count = stateful.objectives.single_item.total + end + end + + if collection.gather_time and not collection.final_arena_disabled then + collection.gather_time = collection.gather_time_timer - tick + if collection.gather_time > 0 then + collection.gather_time = collection.gather_time + elseif collection.gather_time and collection.gather_time <= 0 then + collection.gather_time = 0 + if not collection.gather_time_done then + collection.gather_time_done = true + if not collection.clear_rocks then + Public.find_rocks_and_slowly_remove() + collection.clear_rocks = true + end + LinkedChests.clear_linked_frames() + stateful.final_battle = true + Public.set('final_battle', true) + WD.set('final_battle', true) + + collection.survive_for = game.tick + Stateful.scale((5 * 3600), (15 * 3600)) + collection.survive_for_timer = collection.survive_for + WD.disable_spawning_biters(false) + Public.allocate() + Public.set_final_battle() + Server.to_discord_embed('Final battle starts now!') + refresh_boss_frame() + end + end + end + + if collection.survive_for and collection.survive_for_timer then + collection.survive_for = collection.survive_for_timer - tick + if not collection.survive_for_alerted and collection.gather_time <= 0 then + collection.survive_for_alerted = true + refresh_boss_frame() + end + + if collection.survive_for and collection.survive_for < 0 then + collection.survive_for = 0 + if collection.game_won and not collection.game_won_notified then + game.print('[color=yellow][Mtn v3][/color] Game won!') + collection.game_won = true + stateful.collection.gather_time = 0 + stateful.collection.gather_time_timer = 0 + collection.survive_for = 0 + collection.survive_for_timer = 0 + refresh_frames() + + local reversed = Public.get_stateful_settings('reversed') + if reversed then + Public.set_stateful_settings('reversed', false) + else + Public.set_stateful_settings('reversed', true) + end + + collection.game_won_notified = true + refresh_boss_frame() + play_game_won() + WD.disable_spawning_biters(true) + Collapse.start_now(false) + WD.nuke_wave_gui() + Server.to_discord_embed('Game won!') + stateful.rounds_survived = stateful.rounds_survived + 1 + stateful.selected_objectives = nil + local buff = Stateful.save_settings() + notify_won_to_discord(buff) + local locomotive = Public.get('locomotive') + if locomotive and locomotive.valid then + locomotive.surface.spill_item_stack(locomotive.position, { name = 'coin', count = 512 }, false) + end + Public.set('game_reset_tick', 5400) + return + end + end + end + + if stateful.selected_objectives and next(stateful.selected_objectives) then + for objective_index = 1, #stateful.selected_objectives do + local objective = stateful.selected_objectives[objective_index] + local objective_name = objective.name + local callback = Task.get(objective.token) + local completed, _, _ = callback() + if completed and completed == true and not stateful.objectives_completed[objective_name] then + stateful.objectives_completed[objective_name] = true + stateful.objectives_time_spent[objective_name] = tick + Alert.alert_all_players(100, 'Objective: **' .. objective_name .. '** has been completed!') + Server.to_discord_embed('Objective: **' .. objective_name .. '** has been completed!') + play_achievement_unlocked() + stateful.objectives_completed_count = stateful.objectives_completed_count + 1 + end + end + end + + if stateful.objectives_completed_count == stateful.tasks_required_to_win and not stateful.objectives_completed.boss_time then + stateful.objectives_completed.boss_time = true + + Server.to_discord_embed('All objectives has been completed! Take your time to prepare for the final push!') + Alert.alert_all_players(300, 'All objectives has been completed!') + Alert.alert_all_players(300, 'Take your time to prepare for the final push!') + + if stateful.collection.final_arena_disabled then + game.print('[color=yellow][Mtn v3][/color] Game won!') + game.print('[color=yellow][Mtn v3][/color] Final battle arena is currently being tweaked.') + collection.game_won = true + stateful.collection.gather_time = 0 + stateful.collection.gather_time_timer = 0 + collection.survive_for = 0 + collection.survive_for_timer = 0 + refresh_frames() + + local reversed = Public.get_stateful_settings('reversed') + if reversed then + Public.set_stateful_settings('reversed', false) + else + Public.set_stateful_settings('reversed', true) + end + + collection.game_won_notified = true + refresh_boss_frame() + play_game_won() + WD.disable_spawning_biters(true) + Collapse.start_now(false) + WD.nuke_wave_gui() + Server.to_discord_embed('Game won!') + stateful.rounds_survived = stateful.rounds_survived + 1 + stateful.selected_objectives = nil + local buff = Stateful.save_settings() + notify_won_to_discord(buff) + local locomotive = Public.get('locomotive') + if locomotive and locomotive.valid then + locomotive.surface.spill_item_stack(locomotive.position, { name = 'coin', count = 512 }, false) + end + Public.set('game_reset_tick', 5400) + return + end + + stateful.collection.gather_time = tick + (10 * 3600) + stateful.collection.gather_time_timer = tick + (10 * 3600) + game.forces.enemy.evolution_factor = 1 + play_achievement_unlocked() + local reverse_position = zone_settings.zone_depth * (breached_wall + 1) + local reversed = Public.get_stateful_settings('reversed') + if not reversed then + reverse_position = reverse_position * -1 + end + Explosives.disable(true) + WD.disable_spawning_biters(true) + WD.set_track_bosses_only(false) + Collapse.set_reverse_position({ 0, reverse_position }) + Collapse.set_reverse_direction() + Collapse.reverse_start_now(true) + Alert.alert_all_players(200, 'Reverse collapse has been initiated!') + Server.to_discord_embed('Reverse collapse has been initiated!') + -- Public.stateful_blueprints.blueprint() + WD.nuke_wave_gui() + WD.set('final_battle', true) + Public.set('pre_final_battle', true) + + refresh_frames() + end +end + +local function on_player_joined_game(event) + local player = game.players[event.player_index] + if not player then + return + end + + if not player.gui.top[main_button_name] then + create_button(player) + end +end + +Gui.on_click( + main_button_name, + function (event) + local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Mtn v3 open stateful Button') + if is_spamming then + return + end + + local game_lost = Public.get('game_lost') + if game_lost then + clear_all_frames() + return + end + + local player = event.player + if not player or not player.valid then + return + end + + local final_battle = Public.get_stateful('final_battle') + + if final_battle then + local frame = player.gui.screen[boss_frame_name] + if frame then + Gui.remove_data_recursively(frame) + frame.destroy() + else + Gui.clear_all_active_frames(player) + boss_frame(player) + end + else + local frame = player.gui.screen[main_frame_name] + if frame then + Gui.remove_data_recursively(frame) + frame.destroy() + else + Gui.clear_all_active_frames(player) + main_frame(player) + end + end + end +) + +Gui.on_click( + close_button, + function (event) + local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Mtn v3 close stateful Button') + if is_spamming then + return + end + + local player = event.player + if not player or not player.valid then + return + end + + local frame = player.gui.screen[main_frame_name] + + if frame then + Gui.remove_data_recursively(frame) + frame.destroy() + end + + local frame_boss = player.gui.screen[boss_frame_name] + + if frame_boss then + Gui.remove_data_recursively(frame_boss) + frame_boss.destroy() + end + end +) + +Gui.on_click( + close_buffs_window_name, + function (event) + local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Buff Close Button') + if is_spamming then + return + end + local player = event.player + local center = player.gui.center + if not player or not player.valid or not player.character then + return + end + + local frame_buff = center[buffs_window_name] + if frame_buff and frame_buff.valid then + Gui.remove_data_recursively(frame_buff) + frame_buff.destroy() + end + end +) + +Gui.on_custom_close( + buffs_window_name, + function (event) + local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Buff Custom Close') + if is_spamming then + return + end + local player = event.player + local center = player.gui.center + if not player or not player.valid or not player.character then + return + end + + local frame_buff = center[buffs_window_name] + if frame_buff and frame_buff.valid then + Gui.remove_data_recursively(frame_buff) + frame_buff.destroy() + end + end +) + +Gui.on_click( + on_click_buff_name, + function (event) + local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Buff Open Button') + if is_spamming then + return + end + local player = event.player + local center = player.gui.center + if not player or not player.valid or not player.character then + return + end + + local frame_buff = center[buffs_window_name] + if frame_buff and frame_buff.valid then + Gui.remove_data_recursively(frame_buff) + frame_buff.destroy() + else + buff_window(player) + end + end +) + +Event.add(defines.events.on_player_joined_game, on_player_joined_game) +Event.on_nth_tick(30, update_data) +Event.on_nth_tick(30, update_raw) + +Public.boss_frame = boss_frame +Public.clear_all_frames = clear_all_frames +Stateful.refresh_frames = refresh_frames + +return Public diff --git a/maps/mountain_fortress_v3/stateful/table.lua b/maps/mountain_fortress_v3/stateful/table.lua index 2733f281a..2cca7aa3c 100644 --- a/maps/mountain_fortress_v3/stateful/table.lua +++ b/maps/mountain_fortress_v3/stateful/table.lua @@ -1,1950 +1,1934 @@ -local Global = require 'utils.global' -local Event = require 'utils.event' -local Utils = require 'utils.utils' -local Server = require 'utils.server' -local Gui = require 'utils.gui' -local Task = require 'utils.task_token' -local shuffle = table.shuffle_table -local WD = require 'modules.wave_defense.table' -local format_number = require 'util'.format_number -local ICWF = require 'maps.mountain_fortress_v3.icw.functions' -local ICWT = require 'maps.mountain_fortress_v3.icw.table' -local Core = require 'utils.core' -local Public = require 'maps.mountain_fortress_v3.table' -local Alert = require 'utils.alert' -local RPG = require 'modules.rpg.table' -local Beam = require 'modules.render_beam' -local Discord = require 'utils.discord' -local Difficulty = require 'modules.difficulty_vote_by_amount' -local scenario_name = Public.scenario_name - -local this = { - enabled = false, - rounds_survived = 0, - season = 1, - buffs = {}, - reset_after = 60, - time_to_reset = 60 -} - -local random = math.random -local round = math.round -local floor = math.floor -local dataset = 'scenario_settings' -local dataset_key = 'mtn_v3' -local dataset_key_dev = 'mtn_v3_dev' -local dataset_key_previous = 'mtn_v3_previous' -local send_ping_to_channel = Discord.channel_names.mtn_channel - -Global.register( - this, - function (tbl) - this = tbl - end -) - -local damage_types = { 'physical', 'electric', 'poison', 'laser' } - -local buff_to_string = { - ['starting_items'] = 'Starting items', - ['character_running_speed_modifier'] = 'Movement', - ['manual_mining_speed_modifier'] = 'Mining', - ['character_resource_reach_distance_bonus'] = 'Resource reach', - ['character_item_pickup_distance_bonus'] = 'Item pickup', - ['character_loot_pickup_distance_bonus'] = 'Loot pickup', - ['laboratory_speed_modifier'] = 'Laboratory speed', - ['laboratory_productivity_bonus'] = 'Laboratory productivity', - ['worker_robots_storage_bonus'] = 'Robot storage', - ['worker_robots_battery_modifier'] = 'Robot battery', - ['worker_robots_speed_modifier'] = 'Robot speed', - ['mining_drill_productivity_bonus'] = 'Mining drill speed', - ['character_health_bonus'] = 'Character health', - ['character_reach_distance_bonus'] = 'Reach', - ['distance'] = 'All distance modifiers', - ['manual_crafting_speed_modifier'] = 'Crafting', - ['xp_bonus'] = 'XP Points', - ['xp_level'] = 'XP Level' -} - -local function notify_season_over_to_discord() - local server_name_matches = Server.check_server_name(scenario_name) - - local stateful = Public.get_stateful() - - local buffs = '' - if stateful.buffs and next(stateful.buffs) then - if stateful.buffs_collected and next(stateful.buffs_collected) then - if stateful.buffs_collected.starting_items then - buffs = buffs .. 'Starting items:\n' - for item_name, item_data in pairs(stateful.buffs_collected.starting_items) do - buffs = buffs .. item_name .. ': ' .. item_data.count - buffs = buffs .. '\n' - end - buffs = buffs .. '\n' - end - - buffs = buffs .. 'Force buffs:\n' - for name, buff_data in pairs(stateful.buffs_collected) do - if type(buff_data.amount) ~= 'table' and name ~= 'starting_items' then - if name == 'xp_level' or name == 'xp_bonus' or name == 'character_health_bonus' then - buffs = buffs .. buff_to_string[name] .. ': ' .. buff_data.count - else - buffs = buffs .. buff_to_string[name] .. ': ' .. (buff_data.count * 100) .. '%' - end - buffs = buffs .. '\n' - end - end - end - end - - local text = { - title = 'Season: ' .. stateful.season .. ' is over!', - description = 'Game statistics from the season is below', - color = 'success', - field1 = { - text1 = 'Rounds survived:', - text2 = stateful.rounds_survived, - inline = 'false' - }, - field2 = { - text1 = 'Buffs granted:', - text2 = buffs, - inline = 'false' - } - } - if server_name_matches then - Server.to_discord_named_parsed_embed(send_ping_to_channel, text) - else - Server.to_discord_embed_parsed(text) - end -end - -local function get_random_buff(fetch_all, only_force) - local buffs = { - { - name = 'character_running_speed_modifier', - discord = 'Running speed modifier - run faster!', - modifier = 'force', - per_force = true, - state = 0.05 - }, - { - name = 'manual_mining_speed_modifier', - discord = 'Mining speed modifier - mine faster!', - modifier = 'force', - per_force = true, - state = 0.15 - }, - { - name = 'laboratory_speed_modifier', - discord = 'Laboratory speed modifier - labs work faster!', - modifier = 'force', - per_force = true, - state = 0.15 - }, - { - name = 'laboratory_productivity_bonus', - discord = 'Laboratory productivity bonus - labs dupe things!', - modifier = 'force', - per_force = true, - state = 0.15 - }, - { - name = 'worker_robots_storage_bonus', - discord = 'Robot storage bonus - robots carry more!', - modifier = 'force', - per_force = true, - state = 1 - }, - { - name = 'worker_robots_battery_modifier', - discord = 'Robot battery bonus - robots work longer!', - modifier = 'force', - per_force = true, - state = 1 - }, - { - name = 'worker_robots_speed_modifier', - discord = 'Robot speed modifier - robots move faster!', - modifier = 'force', - per_force = true, - state = 0.5 - }, - { - name = 'mining_drill_productivity_bonus', - discord = 'Drill productivity bonus - drills work faster!', - modifier = 'force', - per_force = true, - state = 0.5 - }, - { - name = 'character_health_bonus', - discord = 'Character health bonus - more health!', - modifier = 'force', - per_force = true, - state = 250 - }, - { - name = 'distance', - discord = 'RPG reach distance bonus - reach further!', - modifier = 'rpg_distance', - per_force = true, - modifiers = { 'character_resource_reach_distance_bonus', 'character_item_pickup_distance_bonus', 'character_loot_pickup_distance_bonus', 'character_reach_distance_bonus' }, - state = 0.05 - }, - { - name = 'manual_crafting_speed_modifier', - discord = 'Crafting speed modifier - craft faster!', - modifier = 'force', - per_force = true, - state = 0.12 - }, - { - name = 'xp_bonus', - discord = 'RPG XP point bonus - more XP points from kills etc.', - modifier = 'rpg', - per_force = true, - state = 0.12 - }, - { - name = 'xp_level', - discord = 'RPG XP level bonus - start with more XP levels', - modifier = 'rpg', - per_force = true, - state = 20 - }, - { - name = 'chemicals_s', - discord = 'Starting items supplies - start with some sulfur', - modifier = 'starting_items', - limit = 200, - add_per_buff = 50, - items = { - { name = 'sulfur', count = 50 } - } - }, - { - name = 'chemicals_p', - discord = 'Starting items supplies - start with some plastic bar', - modifier = 'starting_items', - limit = 200, - add_per_buff = 50, - items = { - { name = 'plastic-bar', count = 100 } - } - }, - { - name = 'supplies', - discord = 'Starting items supplies - start with some copper and iron plates', - modifier = 'starting_items', - limit = 1000, - add_per_buff = 100, - items = { - { name = 'iron-plate', count = 100 }, - { name = 'copper-plate', count = 100 } - } - }, - { - name = 'supplies_1', - discord = 'Starting items supplies - start with more copper and iron plates', - modifier = 'starting_items', - limit = 1000, - add_per_buff = 200, - items = { - { name = 'iron-plate', count = 200 }, - { name = 'copper-plate', count = 200 } - } - }, - { - name = 'supplies_2', - discord = 'Starting items supplies - start with even more copper and iron plates', - modifier = 'starting_items', - limit = 1000, - add_per_buff = 400, - items = { - { name = 'iron-plate', count = 400 }, - { name = 'copper-plate', count = 400 } - } - }, - { - name = 'defense_3', - discord = 'Defense starting supplies - start with more turrets and ammo', - modifier = 'starting_items', - limit = 1, - add_per_buff = 1, - items = { - { name = 'rocket-launcher', count = 1 }, - { name = 'rocket', count = 100 } - } - }, - { - name = 'armor', - discord = 'Armor starting supplies - start with some armor and solar panels', - modifier = 'starting_items', - limit = 1, - add_per_buff = 1, - items = { - { name = 'modular-armor', count = 1 }, - { name = 'solar-panel-equipment', count = 2 } - } - }, - { - name = 'production', - discord = 'Production starting supplies - start with some furnaces and coal', - modifier = 'starting_items', - limit = 2, - add_per_buff = 1, - items = { - { name = 'stone-furnace', count = 4 }, - { name = 'coal', count = 100 } - } - }, - { - name = 'production_1', - discord = 'Production starting supplies - start with some steel furnaces and solid fuel', - modifier = 'starting_items', - limit = 2, - add_per_buff = 1, - items = { - { name = 'steel-furnace', count = 4 }, - { name = 'solid-fuel', count = 100 } - } - }, - { - name = 'fast_startup_1', - discord = 'Assembling starting supplies - start with some assembling machines T2', - modifier = 'starting_items', - limit = 25, - add_per_buff = 2, - items = { - { name = 'assembling-machine-2', count = 2 } - } - }, - { - name = 'fast_startup_2', - discord = 'Assembling starting supplies - start with some assembling machines T3', - modifier = 'starting_items', - limit = 25, - add_per_buff = 2, - items = { - { name = 'assembling-machine-3', count = 2 } - } - }, - { - name = 'heal-thy-buildings', - discord = 'Repair starting supplies - start with some repair packs', - modifier = 'starting_items', - limit = 20, - add_per_buff = 2, - items = { - { name = 'repair-pack', count = 5 } - } - }, - { - name = 'extra_wagons', - discord = 'Extra wagon at start', - modifier = 'locomotive', - state = 1 - }, - { - name = 'american_oil', - discord = 'Oil tech - start with some crude oil barrels', - modifier = 'starting_items', - limit = 40, - add_per_buff = 20, - items = { - { name = 'crude-oil-barrel', count = 20 } - } - }, - { - name = 'steel_plates', - discord = 'Steel tech - start with some steel plates', - modifier = 'starting_items', - limit = 200, - add_per_buff = 100, - items = { - { name = 'steel-plate', count = 100 } - } - }, - { - name = 'red_science', - discord = 'Science tech - start with some red science packs', - modifier = 'starting_items', - limit = 200, - add_per_buff = 10, - items = { - { name = 'automation-science-pack', count = 10 } - } - }, - { - name = 'roboport_equipement', - discord = 'Equipement tech - start with a personal roboport', - modifier = 'starting_items', - limit = 4, - add_per_buff = 1, - items = { - { name = 'personal-roboport-equipment', count = 1 } - } - }, - { - name = 'mk1_tech_unlocked', - discord = 'Equipement tech - start with power armor tech unlocked.', - modifier = 'tech', - limit = 1, - add_per_buff = 1, - techs = { - { name = 'power-armor', count = 1 } - } - }, - { - name = 'steel_axe_unlocked', - discord = 'Equipement tech - start with steel axe tech unlocked.', - modifier = 'tech', - limit = 1, - add_per_buff = 1, - techs = { - { name = 'steel-axe', count = 1 } - } - }, - { - name = 'military_2_unlocked', - discord = 'Equipement tech - start with military 2 tech unlocked.', - modifier = 'tech', - limit = 1, - add_per_buff = 1, - techs = { - { name = 'military-2', count = 1 } - } - }, - { - name = 'all_the_fish', - discord = 'Wagon is full of fish!', - modifier = 'fish', - limit = 1, - add_per_buff = 1 - } - } - - if only_force then - local force_buffs = {} - for _, buff in pairs(buffs) do - if buff.per_force then - force_buffs[#force_buffs + 1] = buff - end - end - - shuffle(force_buffs) - shuffle(force_buffs) - shuffle(force_buffs) - shuffle(force_buffs) - shuffle(force_buffs) - shuffle(force_buffs) - - return force_buffs[1] - end - - if fetch_all then - return buffs - end - - shuffle(buffs) - shuffle(buffs) - shuffle(buffs) - shuffle(buffs) - shuffle(buffs) - shuffle(buffs) - - return buffs[1] -end - -local function get_item_produced_count(item_name) - local force = game.forces.player - - local production = force.item_production_statistics.input_counts[item_name] - if not production then - return false - end - - return production -end - -local function get_entity_mined_count(item_name) - local force = game.forces.player - - local count = 0 - for name, entity_count in pairs(force.entity_build_count_statistics.output_counts) do - if name:find(item_name) then - count = count + entity_count - end - end - - return count -end - -local function get_killed_enemies_count(primary, secondary) - local force = game.forces.player - - local count = 0 - for name, entity_count in pairs(force.kill_count_statistics.input_counts) do - if name:find(primary) or name:find(secondary) then - count = count + entity_count - end - end - - return count -end - -local move_all_players_token = - Task.register( - function () - Public.move_all_players() - end - ) - -local search_corpse_token = - Task.register( - function (event) - local player_index = event.player_index - local player = game.get_player(player_index) - - if not player or not player.valid then - return - end - - local pos = player.position - local entities = - player.surface.find_entities_filtered { - area = { { pos.x - 0.5, pos.y - 0.5 }, { pos.x + 0.5, pos.y + 0.5 } }, - name = 'character-corpse' - } - - local entity - for _, e in ipairs(entities) do - if e.character_corpse_tick_of_death then - entity = e - break - end - end - - if not entity or not entity.valid then - return - end - - entity.destroy() - - local text = player.name .. "'s corpse was consumed by the biters." - - game.print(text) - end - ) - -local function on_pre_player_died(event) - local player_index = event.player_index - local player = game.get_player(player_index) - - if not player or not player.valid then - return - end - - local surface = player.surface - - local map_name = 'mtn_v3' - - local corpse_removal_disabled = Public.get('corpse_removal_disabled') - if corpse_removal_disabled then - return - end - - if string.sub(surface.name, 0, #map_name) ~= map_name then - return - end - - -- player.ticks_to_respawn = 1800 * (this.rounds_survived + 1) - - Task.set_timeout_in_ticks(5, search_corpse_token, { player_index = player.index }) -end - -local function on_market_item_purchased(event) - if not event.cost then - return - end - - local coins = this.objectives.locomotive_market_coins_spent - if not coins then - return - end - - coins.spent = coins.spent + event.cost -end - -local empty_token = - Task.register( - function () - return false - end - ) - -local killed_enemies_token = - Task.register( - function () - local actual = Public.get_killed_enemies_count('biter', 'spitter') - local expected = this.objectives.killed_enemies - if actual >= expected then - return true, { 'stateful.enemies_killed' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - - return false, { 'stateful.enemies_killed' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } - end - ) - -local killed_enemies_type_token = - Task.register( - function () - local actual = this.objectives.killed_enemies_type.actual - local expected = this.objectives.killed_enemies_type.expected - if actual >= expected then - return true, { 'stateful.enemies_killed_type', this.objectives.killed_enemies_type.damage_type }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - - return false, { 'stateful.enemies_killed_type', this.objectives.killed_enemies_type.damage_type }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { - 'stateful.tooltip_not_completed' - } - end - ) - -local handcrafted_items_token = - Task.register( - function () - local actual = this.objectives.handcrafted_items.actual - local expected = this.objectives.handcrafted_items.expected - if actual >= expected then - return true, { 'stateful.crafted_items', this.objectives.handcrafted_items.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - - return false, { 'stateful.crafted_items', this.objectives.handcrafted_items.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { - 'stateful.tooltip_not_completed' - } - end - ) - -local handcrafted_items_any_token = - Task.register( - function () - local actual = this.objectives.handcrafted_items_any.actual - local expected = this.objectives.handcrafted_items_any.expected - if actual >= expected then - return true, { 'stateful.crafted_items', this.objectives.handcrafted_items_any.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - - return false, { 'stateful.crafted_items', this.objectives.handcrafted_items_any.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { - 'stateful.tooltip_not_completed' - } - end - ) - -local launch_item_token = - Task.register( - function () - local actual = this.objectives.launch_item.actual - local expected = this.objectives.launch_item.expected - if actual >= expected then - return true, { 'stateful.launch_item', this.objectives.launch_item.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - - return false, { 'stateful.launch_item', this.objectives.launch_item.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { - 'stateful.tooltip_not_completed' - } - end - ) - -local cast_spell_token = - Task.register( - function () - local actual = this.objectives.cast_spell.actual - local expected = this.objectives.cast_spell.expected - if actual >= expected then - return true, { 'stateful.cast_spell', this.objectives.cast_spell.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - - return false, { 'stateful.cast_spell', this.objectives.cast_spell.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { - 'stateful.tooltip_not_completed' - } - end - ) - -local cast_spell_any_token = - Task.register( - function () - local actual = this.objectives.cast_spell_any.actual - local expected = this.objectives.cast_spell_any.expected - if actual >= expected then - return true, { 'stateful.cast_spell', this.objectives.cast_spell_any.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - - return false, { 'stateful.cast_spell', this.objectives.cast_spell_any.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { - 'stateful.tooltip_not_completed' - } - end - ) - -local research_level_selection_token = - Task.register( - function () - local actual = this.objectives.research_level_selection.research_count - local expected = this.objectives.research_level_selection.count - if actual >= expected then - return true, { 'stateful.research', this.objectives.research_level_selection.name }, { 'stateful.done', expected, expected }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - return false, { 'stateful.research', this.objectives.research_level_selection.name }, { 'stateful.not_done', actual, expected }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } - end - ) - -local locomotive_market_coins_spent_token = - Task.register( - function () - local coins = this.objectives.locomotive_market_coins_spent - local actual = coins.spent - local expected = coins.required - if actual >= expected then - return true, { 'stateful.market_spent' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - return false, { 'stateful.market_spent' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } - end - ) - -local minerals_farmed_token = - Task.register( - function () - local actual = get_entity_mined_count('rock') + get_entity_mined_count('tree') - local expected = this.objectives.minerals_farmed - if actual >= expected then - return true, { 'stateful.minerals_mined' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - return false, { 'stateful.minerals_mined' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } - end - ) - -local rockets_launched_token = - Task.register( - function () - local actual = game.forces.player.rockets_launched - local expected = this.objectives.rockets_launched - if actual >= expected then - return true, { 'stateful.launch_rockets' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } - end - return false, { 'stateful.launch_rockets' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } - end - ) - -local function scale(setting, limit, factor) - factor = factor or 1.05 - local scale_value = floor(setting * (factor ^ this.rounds_survived)) - if limit and scale_value >= limit then - return limit - end - return floor(scale_value) -end - -local function scale_lin(setting, limit, factor) - factor = factor or 1.05 - local scale_value = math.floor(setting + (factor * this.rounds_survived)) - if limit and scale_value >= limit then - return limit - end - return floor(scale_value) -end - -local function get_random_items() - local items = { - { 'advanced-circuit', scale(225000, 9000000) }, - { 'copper-cable', scale(3000000, 120000000) }, - { 'copper-plate', scale(1500000, 60000000) }, - { 'electric-engine-unit', scale(10000, 400000) }, - { 'electronic-circuit', scale(1000000, 40000000) }, - { 'engine-unit', scale(20000, 800000) }, - { 'explosives', scale(150000, 6000000) }, - { 'iron-gear-wheel', scale(150000, 6000000) }, - { 'iron-plate', scale(2000000, 80000000) }, - { 'iron-stick', scale(75000, 3000000) }, - { 'processing-unit', scale(40000, 1600000) }, - { 'rocket-control-unit', scale(8000, 320000) }, - { 'steel-plate', scale(200000, 8000000) }, - { 'rocket', scale(25000, 1000000) }, - { 'explosive-rocket', scale(25000, 1000000) }, - { 'slowdown-capsule', scale(10000, 400000) }, - { 'laser-turret', scale(3000, 120000) }, - { 'stone-wall', scale(20000, 800000) }, - { 'accumulator', scale(5000, 200000) }, - { 'refined-concrete', scale(15000, 600000) }, - { 'uranium-rounds-magazine', scale(4000, 160000) }, - { 'explosive-uranium-cannon-shell', scale(3000, 120000) }, - { 'distractor-capsule', scale(1500, 60000) }, - { 'cluster-grenade', scale(4000, 160000) }, - { 'small-lamp', scale(5000, 200000) }, - { 'uranium-fuel-cell', scale(2500, 100000) } - } - - shuffle(items) - shuffle(items) - - local container = { - [1] = { name = items[1][1], count = items[1][2] }, - [2] = { name = items[2][1], count = items[2][2] }, - [3] = { name = items[3][1], count = items[3][2] } - } - - if this.test_mode then - container = { - [1] = { name = items[1].products[1].name, count = 1 }, - [2] = { name = items[2].products[1].name, count = 1 }, - [3] = { name = items[3].products[1].name, count = 1 } - } - end - - return container -end - -local function get_random_item() - local items = { - { 'effectivity-module', scale(1000, 400000) }, - { 'productivity-module', scale(10000, 400000) }, - { 'speed-module', scale(10000, 400000) }, - { 'effectivity-module-2', scale(200, 100000) }, - { 'productivity-module-2', scale(1000, 100000) }, - { 'speed-module-2', scale(1000, 100000) }, - { 'effectivity-module-3', scale(50, 30000) }, - { 'productivity-module-3', scale(500, 30000) }, - { 'speed-module-3', scale(500, 30000) } - } - - shuffle(items) - shuffle(items) - shuffle(items) - shuffle(items) - - return { name = items[1][1], count = items[1][2] } -end - -local function get_random_handcrafted_item() - local items = { - { 'advanced-circuit', scale(2000, 500000) }, - { 'copper-cable', scale(10000, 500000) }, - { 'electronic-circuit', scale(5000, 1000000) }, - { 'engine-unit', scale(3500, 500000) }, - { 'iron-gear-wheel', scale(50000, 1000000) }, - { 'iron-stick', scale(75000, 3000000) }, - { 'rocket-control-unit', scale(1000, 50000) }, - { 'rocket', scale(5000, 1000000) }, - { 'explosive-rocket', scale(5000, 1000000) }, - { 'slowdown-capsule', scale(2500, 400000) }, - { 'laser-turret', scale(1500, 20000) }, - { 'stone-wall', scale(5000, 800000) }, - { 'accumulator', scale(1000, 200000) }, - { 'uranium-rounds-magazine', scale(1000, 60000) }, - { 'explosive-uranium-cannon-shell', scale(1000, 10000) }, - { 'distractor-capsule', scale(1500, 60000) }, - { 'grenade', scale(5000, 200000) }, - { 'cluster-grenade', scale(1000, 100000) }, - { 'small-lamp', scale(2500, 200000) }, - { 'rail', scale(5000, 100000) }, - { 'small-electric-pole', scale(5000, 100000) }, - { 'medium-electric-pole', scale(3500, 80000) }, - { 'big-electric-pole', scale(2000, 50000) }, - { 'transport-belt', scale(10000, 100000) }, - { 'fast-transport-belt', scale(3000, 50000) }, - { 'repair-pack', scale(10000, 100000) }, - { 'splitter', scale(10000, 100000) }, - { 'fast-splitter', scale(3000, 50000) }, - { 'inserter', scale(3000, 50000) }, - { 'firearm-magazine', scale(10000, 200000) }, - { 'piercing-rounds-magazine', scale(5000, 100000) }, - { 'pipe', scale(10000, 100000) }, - { 'pipe-to-ground', scale(3000, 50000) }, - { 'effectivity-module', scale(100, 50000) }, - { 'productivity-module', scale(100, 50000) }, - { 'speed-module', scale(100, 50000) } - } - - shuffle(items) - shuffle(items) - shuffle(items) - shuffle(items) - - return { name = items[1][1], count = items[1][2] } -end - -local function get_random_spell() - local items = { - { 'stone-wall', scale(1000, 250000) }, - { 'wooden-chest', scale(1000, 250000) }, - { 'iron-chest', scale(1000, 200000) }, - { 'steel-chest', scale(1000, 150000) }, - { 'transport-belt', scale(1000, 250000) }, - { 'fast-transport-belt', scale(1000, 200000) }, - { 'express-transport-belt', scale(1000, 150000) }, - { 'underground-belt', scale(1000, 250000) }, - { 'fast-underground-belt', scale(1000, 200000) }, - { 'express-underground-belt', scale(1000, 150000) }, - { 'pipe', scale(1000, 20000) }, - { 'pipe-to-ground', scale(1000, 250000) }, - { 'tree-05', scale(1000, 200000) }, - { 'sand-rock-big', scale(1000, 60000) }, - { 'small-biter', scale(1000, 10000) }, - { 'small-spitter', scale(1000, 60000) }, - { 'medium-biter', scale(1000, 200000) }, - { 'medium-spitter', scale(1000, 100000) }, - { 'biter-spawner', scale(1000, 200000) }, - { 'spitter-spawner', scale(1000, 100000) }, - { 'shotgun-shell', scale(1000, 100000) }, - { 'grenade', scale(1000, 80000) }, - { 'cluster-grenade', scale(1000, 50000) }, - { 'cannon-shell', scale(1000, 100000) }, - { 'explosive-cannon-shell', scale(1000, 50000) }, - { 'uranium-cannon-shell', scale(1000, 100000) }, - { 'rocket', scale(1000, 100000) }, - { 'repair_aoe', scale(1000, 50000) }, - { 'acid-stream-spitter-big', scale(1000, 200000) }, - { 'raw-fish', scale(3500, 500000) }, - { 'explosives', scale(5000, 100000) }, - { 'distractor-capsule', scale(5000, 100000) }, - { 'defender-capsule', scale(5000, 100000) }, - { 'destroyer-capsule', scale(5000, 100000) }, - { 'warp-gate', scale(5000, 500000) }, - { 'haste', scale(5000, 500000) } - } - - shuffle(items) - shuffle(items) - shuffle(items) - shuffle(items) - - return { name = items[1][1], count = items[1][2] } -end - -local function get_random_research_recipe() - -- scale(10, 20) - local research_level_list = { - 'energy-weapons-damage-7', - 'stronger-explosives-7', - 'mining-productivity-4', - 'worker-robots-speed-6', - 'follower-robot-count-7' - } - - shuffle(research_level_list) - - if this.test_mode then - return { name = research_level_list[1], count = 1, research_count = 0 } - end - - return { name = research_level_list[1], count = scale(2, 11, 1.03), research_count = 0 } -end - -local function get_random_objectives() - local items = { - { - name = 'single_item', - token = empty_token - }, - { - name = 'killed_enemies', - token = killed_enemies_token - }, - { - name = 'killed_enemies_type', - token = killed_enemies_type_token - }, - { - name = 'handcrafted_items', - token = handcrafted_items_token - }, - { - name = 'handcrafted_items_any', - token = handcrafted_items_any_token - }, - { - name = 'cast_spell', - token = cast_spell_token - }, - { - name = 'launch_item', - token = launch_item_token - }, - { - name = 'cast_spell_any', - token = cast_spell_any_token - }, - { - name = 'research_level_selection', - token = research_level_selection_token - }, - { - name = 'locomotive_market_coins_spent', - token = locomotive_market_coins_spent_token - }, - { - name = 'minerals_farmed', - token = minerals_farmed_token - }, - { - name = 'rockets_launched', - token = rockets_launched_token - } - } - - shuffle(items) - shuffle(items) - shuffle(items) - shuffle(items) - - if _DEBUG then - items[#items + 1] = { - name = 'supplies', - token = empty_token - } - return items - end - - return { - { - name = 'supplies', - token = empty_token - }, - items[2], - items[3], - items[4] - } -end - -local function clear_all_stats() - this.buffs_collected = {} - this.extra_wagons = 0 - local rpg_extra = RPG.get('rpg_extra') - rpg_extra.difficulty = 0 - rpg_extra.grant_xp_level = 0 -end - -local function migrate_buffs() - local state_buffs = get_random_buff(true) - - for _, data in pairs(state_buffs) do - for index, buff in pairs(this.buffs) do - if data.name == buff.name then - if data.add_per_buff then - buff.add_per_buff = data.add_per_buff - end - if buff.replaces then - buff.replaces = nil - end - - if buff.modifier == 'starting_items_1' then - buff.modifier = 'starting_items' - end - - if data.items and type(data.items) == 'table' then - buff.items = data.items - end - - if data.limit and not buff.limit then - buff.limit = data.limit - buff.name = data.name - end - - if buff.items == 0 then - this.buffs[index] = nil - end - end - end - end -end - -local function apply_buffs() - local starting_items = Public.get_func('starting_items') - local techs = Public.get_func('techs') - local limit_types = Public.get_func('limit_types') - - if this.buffs and next(this.buffs) then - local total_buffs = 0 - if not this.buffs_collected then - this.buffs_collected = {} - end - - migrate_buffs() - - local force = game.forces.player - for _, buff in pairs(this.buffs) do - if buff then - total_buffs = total_buffs + 1 - if buff.modifier == 'rpg_distance' then - for _, buff_name in pairs(buff.modifiers) do - if buff_name == 'character_reach_distance_bonus' then - buff.state = 1 - end - - force[buff_name] = force[buff_name] + buff.state - - if not this.buffs_collected[buff_name] then - this.buffs_collected[buff_name] = { - name = 'Extra Reach', - count = buff.state, - discord = buff.discord, - force = true - } - else - this.buffs_collected[buff_name].count = this.buffs_collected[buff_name].count + buff.state - end - end - end - if buff.modifier == 'force' then - force[buff.name] = force[buff.name] + buff.state - - if not this.buffs_collected[buff.name] then - this.buffs_collected[buff.name] = { - count = buff.state, - discord = buff.discord, - force = true - } - else - this.buffs_collected[buff.name].count = this.buffs_collected[buff.name].count + buff.state - end - end - if buff.modifier == 'locomotive' then - local extra_wagons = Public.get('extra_wagons') - if not extra_wagons then - this.extra_wagons = buff.state - else - this.extra_wagons = this.extra_wagons + buff.state - end - - if not this.buffs_collected['locomotive'] then - this.buffs_collected['locomotive'] = { - name = 'Extra Wagons', - count = buff.state, - discord = buff.discord - } - else - if this.extra_wagons > 4 then - this.buffs_collected['locomotive'].count = this.extra_wagons - else - this.buffs_collected['locomotive'].count = this.extra_wagons + buff.state - end - end - - if this.extra_wagons > 4 then - this.extra_wagons = 4 - end - end - if buff.modifier == 'fish' then - limit_types[buff.name] = true - Public.set('all_the_fish', true) - if not this.buffs_collected['fish'] then - this.buffs_collected['fish'] = { - name = 'A thousand fishes', - discord = buff.discord - } - end - end - if buff.modifier == 'tech' then - if not this.buffs_collected['techs'] then - this.buffs_collected['techs'] = {} - end - if type(buff.techs) ~= 'table' then - goto cont - end - - for _, tech in pairs(buff.techs) do - if tech then - if techs[tech.name] then - goto cont - end - - if not techs[tech.name] then - techs[tech.name] = { - name = buff.name - } - end - - if not this.buffs_collected['techs'][tech.name] then - this.buffs_collected['techs'][tech.name] = { - name = tech.name, - buff_type = buff.name, - discord = buff.discord - } - end - force.technologies[tech.name].researched = true - end - end - end - if buff.modifier == 'rpg' then - local rpg_extra = RPG.get('rpg_extra') - if buff.name == 'xp_bonus' then - if not rpg_extra.difficulty then - rpg_extra.difficulty = buff.state - else - rpg_extra.difficulty = rpg_extra.difficulty + buff.state - end - if not this.buffs_collected['xp_bonus'] then - this.buffs_collected['xp_bonus'] = { - name = 'XP Bonus', - count = buff.state, - discord = buff.discord - } - else - this.buffs_collected['xp_bonus'].count = this.buffs_collected['xp_bonus'].count + buff.state - end - end - if buff.name == 'xp_level' then - if not rpg_extra.grant_xp_level then - rpg_extra.grant_xp_level = buff.state - else - rpg_extra.grant_xp_level = rpg_extra.grant_xp_level + buff.state - end - if not this.buffs_collected['xp_level'] then - this.buffs_collected['xp_level'] = { - name = 'XP Level Bonus', - count = buff.state, - discord = buff.discord - } - else - this.buffs_collected['xp_level'].count = this.buffs_collected['xp_level'].count + buff.state - end - end - end - if buff.modifier == 'starting_items' then - if not this.buffs_collected['starting_items'] then - this.buffs_collected['starting_items'] = {} - end - if type(buff.items) ~= 'table' then - goto cont - end - - for _, item in pairs(buff.items) do - if item then - if starting_items[item.name] and buff.limit and starting_items[item.name].item_limit and starting_items[item.name].item_limit >= buff.limit then - starting_items[item.name].limit_reached = true - goto cont -- break if there is a limit set - end - - if starting_items[item.name] then - starting_items[item.name].count = starting_items[item.name].count + item.count - starting_items[item.name].item_limit = starting_items[item.name].item_limit and starting_items[item.name].item_limit + buff.add_per_buff or buff.add_per_buff - starting_items[item.name].buff_type = buff.name - else - starting_items[item.name] = { - buff_type = buff.name, - count = item.count, - item_limit = buff.add_per_buff - } - end - if this.buffs_collected['starting_items'][item.name] then - this.buffs_collected['starting_items'][item.name].count = this.buffs_collected['starting_items'][item.name].count + item.count - this.buffs_collected['starting_items'][item.name].buff_type = buff.name - else - this.buffs_collected['starting_items'][item.name] = { - buff_type = buff.name, - count = item.count, - discord = buff.discord - } - end - end - end - end - end - ::cont:: - end - this.total_buffs = total_buffs - end - Public.equip_players(starting_items) -end - -local function apply_startup_settings(settings) - local current_date = Server.get_current_date(false, true) - if not current_date then - return - end - - local current_time = Server.get_current_time() - if not current_time then - return - end - - current_date = round(Utils.convert_date(current_date.year, current_date.month, current_date.day)) - - local server_name_matches = Server.check_server_name(scenario_name) - - settings = settings or {} - local stored_date = this.current_date - if not stored_date then - return - end - local stored_date_raw = Server.get_current_date(false, true, stored_date) - local converted_stored_date = round(Utils.convert_date(stored_date_raw.year, stored_date_raw.month, stored_date_raw.day)) - - local time_to_reset = (current_date - converted_stored_date) - this.time_to_reset = this.reset_after - time_to_reset - - if time_to_reset and time_to_reset >= this.reset_after then - Public.save_settings_before_reset() - Public.set_season_scores() - - local s = this.season or 1 - game.server_save('Season_' .. s .. '_Mtn_v3_' .. tostring(current_time)) - notify_season_over_to_discord() - settings.current_date = current_time - settings.test_mode = false - settings.rounds_survived = 0 - settings.buffs = {} - this.buffs = {} - this.buffs_collected = {} - this.rounds_survived = 0 - this.season = this.season + 1 - this.current_date = current_time - settings.season = this.season - this.time_to_reset = this.reset_after - local message = ({ 'stateful.reset' }) - local message_discord = ({ 'stateful.reset_discord' }) - game.print(message) - Server.to_discord_embed(message_discord, true) - - game.print(({ 'entity.notify_shutdown' }), { r = 0.22, g = 0.88, b = 0.22 }) - local notify_shutdown = ({ 'entity.shutdown_game' }) - Server.to_discord_bold(notify_shutdown, true) - - Server.stop_scenario() - - if server_name_matches then - Server.set_data(dataset, dataset_key, settings) - else - Server.set_data(dataset, dataset_key_dev, settings) - end - end -end - -local apply_settings_token = - Task.register( - function (data) - local server_name_matches = Server.check_server_name(scenario_name) - local settings = data and data.value or nil - local current_time = Server.get_current_time() - if not current_time then - return - end - - if not settings then - settings = { - rounds_survived = 0, - current_date = tonumber(current_time), - season = 1 - } - if server_name_matches then - Server.set_data(dataset, dataset_key, settings) - else - Server.set_data(dataset, dataset_key_dev, settings) - end - return - end - - if not settings.current_date then - settings.current_date = tonumber(current_time) - end - - if not settings.season then - settings.season = 1 - end - - this.current_date = settings.current_date - this.buffs = settings.buffs - - apply_startup_settings(settings) - - this.rounds_survived = settings.rounds_survived - this.season = settings.season - - local current_season = Public.get('current_season') - if current_season then - ---@diagnostic disable-next-line: param-type-mismatch - rendering.set_text(current_season, 'Season: ' .. this.season) - end - - this.objectives = {} - - Public.reset_stateful() - Public.increase_enemy_damage_and_health() - Public.init_mtn() - end - ) - -local function grant_non_limit_reached_buff() - local all_buffs = get_random_buff(true) - local starting_items = Public.get_func('starting_items') - local techs = Public.get_func('techs') - local limit_types = Public.get_func('limit_types') - - for index, data in pairs(all_buffs) do - for _, item_data in pairs(starting_items) do - if item_data.buff_type == data.name and item_data.item_limit and data.limit and item_data.item_limit >= data.limit then - all_buffs[index] = nil - end - end - - for _, tech_data in pairs(techs) do - if tech_data.name == data.name then - all_buffs[index] = nil - end - end - - for limit_name, _ in pairs(limit_types) do - if limit_name == data.name then - all_buffs[index] = nil - end - end - end - - shuffle(all_buffs) - shuffle(all_buffs) - shuffle(all_buffs) - shuffle(all_buffs) - shuffle(all_buffs) - shuffle(all_buffs) - - if not all_buffs[1] then - return get_random_buff(nil, true) - end - - return all_buffs[1] -end - -function Public.save_settings() - local granted_buff = grant_non_limit_reached_buff() - this.buffs[#this.buffs + 1] = granted_buff - - local settings = { - objectives_time_spent = this.objectives_time_spent, - rounds_survived = this.rounds_survived, - season = this.season, - test_mode = this.test_mode, - buffs = this.buffs, - current_date = this.current_date - } - - local server_name_matches = Server.check_server_name(scenario_name) - if server_name_matches then - Server.set_data(dataset, dataset_key, settings) - else - Server.set_data(dataset, dataset_key_dev, settings) - end - - return granted_buff -end - -function Public.save_settings_before_reset() - local settings = { - rounds_survived = this.rounds_survived, - season = this.season, - test_mode = this.test_mode, - buffs = this.buffs, - current_date = this.current_date - } - - local server_name_matches = Server.check_server_name(scenario_name) - if server_name_matches then - Server.set_data(dataset, dataset_key_previous, settings) - else - Server.set_data(dataset, dataset_key_previous, settings) - end -end - -function Public.reset_stateful(refresh_gui, clear_buffs) - this.test_mode = false - - this.final_battle = false - this.extra_wagons = 0 - if clear_buffs then - this.buffs_collected = {} - end - this.enemies_boosted = false - this.tasks_required_to_win = 6 - - if not this.previous_objectives_time_spent then - this.previous_objectives_time_spent = {} - end - - if this.test_mode then - this.objectives = { - randomized_zone = 2, - randomized_wave = 2, - supplies = get_random_items(), - single_item = get_random_item(), - killed_enemies = 10, - killed_enemies_type = { - actual = 0, - expected = 10, - damage_type = damage_types[random(1, #damage_types)] - }, - handcrafted_items = { - actual = 0, - expected = 10, - name = 'rail' - }, - handcrafted_items_any = { - actual = 0, - expected = 10, - name = 'Any' - }, - cast_spell = { - actual = 0, - expected = 10, - name = 'pipe' - }, - cast_spell_any = { - actual = 0, - expected = 10, - name = 'Any' - }, - launch_item = { - actual = 0, - expected = 10, - name = 'raw-fish' - }, - research_level_selection = get_random_research_recipe(), - locomotive_market_coins_spent = 0, - locomotive_market_coins_spent_required = 1, - trees_farmed = 10, - minerals_farmed = 10, - rockets_launched = 1 - } - else - if not this.objectives then - this.objectives = {} - end - - if not this.selected_objectives then - this.selected_objectives = get_random_objectives() - end - - if not this.objectives.randomized_zone or (this.objectives_completed ~= nil and this.objectives_completed.randomized_zone) then - this.objectives.randomized_zone = scale(4, 15, 1.013) - end - if not this.objectives.randomized_wave or (this.objectives_completed ~= nil and this.objectives_completed.randomized_wave) then - this.objectives.randomized_wave = scale(200, 1000) - end - if not this.objectives.supplies or (this.objectives_completed ~= nil and this.objectives_completed.supplies) then - this.objectives.supplies = get_random_items() - end - if not this.objectives.single_item or (this.objectives_completed ~= nil and this.objectives_completed.single_item) then - this.objectives.single_item = get_random_item() - end - if not this.objectives.killed_enemies or (this.objectives_completed ~= nil and this.objectives_completed.killed_enemies) then - this.objectives.killed_enemies = scale(25000, 400000, 1.035) - end - if not this.objectives.killed_enemies_type or (this.objectives_completed ~= nil and this.objectives_completed.killed_enemies_type) then - this.objectives.killed_enemies_type = { - actual = 0, - expected = scale(10000, 400000, 1.035), - damage_type = damage_types[random(1, #damage_types)] - } - end - if not this.objectives.handcrafted_items or (this.objectives_completed ~= nil and this.objectives_completed.handcrafted_items) then - local item = get_random_handcrafted_item() - this.objectives.handcrafted_items = { - actual = 0, - expected = item.count, - name = item.name - } - end - if not this.objectives.handcrafted_items_any or (this.objectives_completed ~= nil and this.objectives_completed.handcrafted_items_any) then - this.objectives.handcrafted_items_any = { - actual = 0, - expected = scale(50000, 4000000, 1.035), - name = 'Any' - } - end - if not this.objectives.cast_spell or (this.objectives_completed ~= nil and this.objectives_completed.cast_spell) then - local item = get_random_spell() - this.objectives.cast_spell = { - actual = 0, - expected = item.count, - name = item.name - } - end - if not this.objectives.cast_spell_any or (this.objectives_completed ~= nil and this.objectives_completed.cast_spell_any) then - this.objectives.cast_spell_any = { - actual = 0, - expected = scale(1000, 4000000, 1.035), - name = 'Any' - } - end - if not this.objectives.launch_item or (this.objectives_completed ~= nil and this.objectives_completed.launch_item) then - local item = get_random_handcrafted_item() - this.objectives.launch_item = { - actual = 0, - expected = scale(10, 700), - name = item.name - } - end - if not this.objectives.research_level_selection or (this.objectives_completed ~= nil and this.objectives_completed.research_level_selection) then - this.objectives.research_level_selection = get_random_research_recipe() - end - if not this.objectives.locomotive_market_coins_spent or (this.objectives_completed ~= nil and this.objectives_completed.locomotive_market_coins_spent) then - this.objectives.locomotive_market_coins_spent = { - spent = 0, - required = scale(50000) - } - end - if not this.objectives.minerals_farmed or (this.objectives_completed ~= nil and this.objectives_completed.minerals_farmed) then - this.objectives.minerals_farmed = scale(25000, 250000) - end - if not this.objectives.rockets_launched or (this.objectives_completed ~= nil and this.objectives_completed.rockets_launched) then - this.objectives.rockets_launched = scale(10, 700) - end - end - - local supplies = this.objectives.supplies - for _, supply in pairs(supplies) do - if supply and supply.total then - supply.count = supply.total - end - end - - if supplies.single_item and supplies.single_item.total then - supplies.single_item.count = supplies.single_item.total - end - - WD.set_es_unit_limit(scale_lin(100, 1000, 5.819)) - - this.objectives.handcrafted_items.actual = 0 - this.objectives.handcrafted_items_any.actual = 0 - this.objectives.cast_spell.actual = 0 - this.objectives.cast_spell_any.actual = 0 - this.objectives.killed_enemies_type.actual = 0 - this.objectives.launch_item.actual = 0 - this.objectives.research_level_selection.research_count = 0 - this.objectives.locomotive_market_coins_spent.spent = 0 - - this.objectives_completed = {} - if this.objectives_time_spent and next(this.objectives_time_spent) then - this.previous_objectives_time_spent[#this.previous_objectives_time_spent + 1] = this.objectives_time_spent - end - - this.stateful_spawn_points = { - { { x = -205, y = -37 }, { x = 195, y = 37 } }, - { { x = -205, y = -112 }, { x = 195, y = 112 } }, - { { x = -205, y = -146 }, { x = 195, y = 146 } }, - { { x = -205, y = -112 }, { x = 195, y = 112 } }, - { { x = -205, y = -72 }, { x = 195, y = 72 } }, - { { x = -205, y = -146 }, { x = 195, y = 146 } }, - { { x = -205, y = -37 }, { x = 195, y = 37 } }, - { { x = -205, y = -5 }, { x = 195, y = 5 } }, - { { x = -205, y = -23 }, { x = 195, y = 23 } }, - { { x = -205, y = -5 }, { x = 195, y = 5 } }, - { { x = -205, y = -72 }, { x = 195, y = 72 } }, - { { x = -205, y = -23 }, { x = 195, y = 23 } }, - { { x = -205, y = -54 }, { x = 195, y = 54 } }, - { { x = -205, y = -80 }, { x = 195, y = 80 } }, - { { x = -205, y = -54 }, { x = 195, y = 54 } }, - { { x = -205, y = -80 }, { x = 195, y = 80 } }, - { { x = -205, y = -103 }, { x = 195, y = 103 } }, - { { x = -205, y = -150 }, { x = 195, y = 150 } }, - { { x = -205, y = -103 }, { x = 195, y = 103 } }, - { { x = -205, y = -150 }, { x = 195, y = 150 } } - } - - this.objectives_time_spent = {} - this.objectives_completed_count = 0 - - this.collection = { - clear_rocks = nil, - survive_for = nil, - survive_for_timer = nil, - final_arena_disabled = false - } - this.stateful_locomotive_migrated = false - this.force_chunk = true - - local Diff = Difficulty.get() - Diff.index = scale(1, 3, 1.009) - - if Diff.index == 3 then - local message = ({ 'stateful.difficulty_step' }) - local delay = 25 - Alert.set_timeout_in_ticks_alert(delay, { text = message }) - end - - Public.set('coin_amount', Diff.index) - - local t = { - ['randomized_zone'] = this.objectives.randomized_zone, - ['randomized_wave'] = this.objectives.randomized_wave - } - for index = 1, #this.selected_objectives do - local objective = this.selected_objectives[index] - if not t[objective.name] then - t[objective.name] = this.objectives[objective.name] - end - end - - this.objectives = t - - Public.reset_main_table() - - clear_all_stats() - - apply_buffs() - if refresh_gui then - Public.refresh_frames() - end -end - -function Public.move_all_players() - local active_surface_index = Public.get('active_surface_index') - local surface = game.surfaces[active_surface_index] - if not (surface and surface.valid) then - return - end - - local locomotive = Public.get('locomotive') - if not locomotive or not locomotive.valid then - return - end - - ICWF.disable_auto_minimap() - - local message = ({ 'stateful.final_boss_message_start' }) - Alert.alert_all_players(50, message, nil, nil, 1) - Core.iter_connected_players( - function (player) - local pos = surface.find_non_colliding_position('character', locomotive.position, 32, 1) - - Public.stateful_gui.boss_frame(player, true) - - if pos then - player.teleport(pos) - else - player.teleport(locomotive.position) - Public.unstuck_player(player.index) - end - end - ) - - if _DEBUG then - Core.iter_fake_connected_players( - global.characters, - function (player) - local pos = surface.find_non_colliding_position('character', locomotive.position, 32, 1) - - if pos then - player.teleport(pos) - else - player.teleport(locomotive.position) - Public.unstuck_player(player.index) - end - end - ) - end -end - -function Public.set_final_battle() - if this.final_battle then - return - end - - local es_settings = WD.get_es('settings') - WD.set_es('final_battle', true) - es_settings.final_battle = true - Public.set('final_battle', true) -end - -function Public.allocate() - local moved_all_players = Public.get_stateful('moved_all_players') - if not moved_all_players then - Task.set_timeout_in_ticks(10, move_all_players_token, {}) - - Beam.new_valid_targets({ 'wall', 'turret', 'furnace', 'gate' }) - - Public.set_stateful('moved_all_players', true) - - ICWT.set('speed', 0.3) - ICWT.set('final_battle', true) - - local collection = Public.get_stateful('collection') - if not collection then - return - end - - Server.to_discord_embed('Final boss wave is occuring soon!') - - WD.set('final_battle', true) - - if Gui.get_mod_gui_top_frame() then - Core.iter_players( - function (player) - local g = Gui.get_button_flow(player)['wave_defense'] - if g and g.valid then - g.destroy() - end - end - ) - else - Core.iter_connected_players( - function (player) - local wd = player.gui.top['wave_defense'] - if wd and wd.valid then - wd.destroy() - end - end - ) - end - end -end - -function Public.increase_enemy_damage_and_health() - if this.enemies_boosted then - return - end - - this.enemies_boosted = true - - if this.rounds_survived == 1 then - Event.raise(WD.events.on_biters_evolved, { force = game.forces.enemy, health_increase = true }) - Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors }) - Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors_frenzy }) - else - for _ = 1, this.rounds_survived do - Event.raise(WD.events.on_biters_evolved, { force = game.forces.enemy, health_increase = true }) - Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors }) - Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors_frenzy }) - end - end -end - -function Public.get_stateful(key) - if key then - return this[key] - else - return this - end -end - -function Public.set_stateful(key, value) - if key and (value or value == false) then - this[key] = value - return this[key] - elseif key then - return this[key] - else - return this - end -end - -function Public.remove_stateful(key, sub_key) - if key and sub_key then - if this[key] and this[key][sub_key] then - this[key][sub_key] = nil - end - elseif key then - if this[key] then - this[key] = nil - end - end -end - -function Public.enable(state) - this.enabled = state or false -end - -function Public.stateful_on_server_started() - if this.settings_applied then - return - end - - local server_name_matches = Server.check_server_name(scenario_name) - - this.settings_applied = true - - if server_name_matches then - Server.try_get_data(dataset, dataset_key, apply_settings_token) - else - Server.try_get_data(dataset, dataset_key_dev, apply_settings_token) - this.test_mode = true - end -end - -Event.add( - Server.events.on_server_started, - function () - if this.settings_applied then - return - end - - local server_name_matches = Server.check_server_name(scenario_name) - - this.settings_applied = true - - if server_name_matches then - Server.try_get_data(dataset, dataset_key, apply_settings_token) - else - Server.try_get_data(dataset, dataset_key_dev, apply_settings_token) - this.test_mode = true - end - end -) - -Server.on_data_set_changed( - dataset_key, - function (data) - if data.value then - local settings = data.value - if settings.rounds_survived ~= nil then - this.rounds_survived = settings.rounds_survived - end - if settings.season ~= nil then - this.season = settings.season - end - if settings.test_mode ~= nil then - this.test_mode = settings.test_mode - end - if settings.buffs ~= nil then - this.buffs = settings.buffs - end - if settings.current_date ~= nil then - this.current_date = settings.current_date - end - end - end -) - -Server.on_data_set_changed( - dataset_key_dev, - function (data) - if data.value then - local settings = data.value - if settings.rounds_survived ~= nil then - this.rounds_survived = settings.rounds_survived - end - if settings.season ~= nil then - this.season = settings.season - end - if settings.test_mode ~= nil then - this.test_mode = settings.test_mode - end - if settings.buffs ~= nil then - this.buffs = settings.buffs - end - if settings.current_date ~= nil then - this.current_date = settings.current_date - end - end - end -) - -Public.buff_to_string = buff_to_string -Public.get_random_buff = get_random_buff -Public.get_item_produced_count = get_item_produced_count -Public.get_entity_mined_count = get_entity_mined_count -Public.get_killed_enemies_count = get_killed_enemies_count -Public.apply_startup_settings = apply_startup_settings -Public.scale = scale -Public.on_pre_player_died = on_pre_player_died -Public.on_market_item_purchased = on_market_item_purchased - -return Public +local Global = require 'utils.global' +local Event = require 'utils.event' +local Utils = require 'utils.utils' +local Server = require 'utils.server' +local Gui = require 'utils.gui' +local Task = require 'utils.task_token' +local shuffle = table.shuffle_table +local WD = require 'modules.wave_defense.table' +local format_number = require 'util'.format_number +local ICWF = require 'maps.mountain_fortress_v3.icw.functions' +local ICWT = require 'maps.mountain_fortress_v3.icw.table' +local Core = require 'utils.core' +local Public = require 'maps.mountain_fortress_v3.table' +local Alert = require 'utils.alert' +local RPG = require 'modules.rpg.table' +local Beam = require 'modules.render_beam' +local Discord = require 'utils.discord' +local Difficulty = require 'modules.difficulty_vote_by_amount' +local scenario_name = Public.scenario_name + +local this = { + enabled = false, + rounds_survived = 0, + season = 1, + buffs = {}, + reset_after = 60, + time_to_reset = 60 +} + +local random = math.random +local round = math.round +local floor = math.floor +local dataset = 'scenario_settings' +local dataset_key = 'mtn_v3' +local dataset_key_dev = 'mtn_v3_dev' +local dataset_key_previous = 'mtn_v3_previous' +local send_ping_to_channel = Discord.channel_names.mtn_channel + +Global.register( + this, + function (tbl) + this = tbl + end +) + +local damage_types = { 'physical', 'electric', 'poison', 'laser' } + +local buff_to_string = { + ['starting_items'] = 'Starting items', + ['character_running_speed_modifier'] = 'Movement', + ['manual_mining_speed_modifier'] = 'Mining', + ['character_resource_reach_distance_bonus'] = 'Resource reach', + ['character_item_pickup_distance_bonus'] = 'Item pickup', + ['character_loot_pickup_distance_bonus'] = 'Loot pickup', + ['laboratory_speed_modifier'] = 'Laboratory speed', + ['laboratory_productivity_bonus'] = 'Laboratory productivity', + ['worker_robots_storage_bonus'] = 'Robot storage', + ['worker_robots_battery_modifier'] = 'Robot battery', + ['worker_robots_speed_modifier'] = 'Robot speed', + ['mining_drill_productivity_bonus'] = 'Mining drill speed', + ['character_health_bonus'] = 'Character health', + ['character_reach_distance_bonus'] = 'Reach', + ['distance'] = 'All distance modifiers', + ['manual_crafting_speed_modifier'] = 'Crafting', + ['xp_bonus'] = 'XP Points', + ['xp_level'] = 'XP Level' +} + +local function notify_season_over_to_discord() + local server_name_matches = Server.check_server_name(scenario_name) + + local stateful = Public.get_stateful() + + local buffs = '' + if stateful.buffs and next(stateful.buffs) then + if stateful.buffs_collected and next(stateful.buffs_collected) then + if stateful.buffs_collected.starting_items then + buffs = buffs .. 'Starting items:\n' + for item_name, item_data in pairs(stateful.buffs_collected.starting_items) do + buffs = buffs .. item_name .. ': ' .. item_data.count + buffs = buffs .. '\n' + end + buffs = buffs .. '\n' + end + + buffs = buffs .. 'Force buffs:\n' + for name, buff_data in pairs(stateful.buffs_collected) do + if type(buff_data.amount) ~= 'table' and name ~= 'starting_items' then + if name == 'xp_level' or name == 'xp_bonus' or name == 'character_health_bonus' then + buffs = buffs .. buff_to_string[name] .. ': ' .. buff_data.count + else + buffs = buffs .. buff_to_string[name] .. ': ' .. (buff_data.count * 100) .. '%' + end + buffs = buffs .. '\n' + end + end + end + end + + local text = { + title = 'Season: ' .. stateful.season .. ' is over!', + description = 'Game statistics from the season is below', + color = 'success', + field1 = { + text1 = 'Rounds survived:', + text2 = stateful.rounds_survived, + inline = 'false' + }, + field2 = { + text1 = 'Buffs granted:', + text2 = buffs, + inline = 'false' + } + } + if server_name_matches then + Server.to_discord_named_parsed_embed(send_ping_to_channel, text) + else + Server.to_discord_embed_parsed(text) + end +end + +local function get_random_buff(fetch_all, only_force) + local buffs = { + { + name = 'character_running_speed_modifier', + discord = 'Running speed modifier - run faster!', + modifier = 'force', + per_force = true, + state = 0.05 + }, + { + name = 'manual_mining_speed_modifier', + discord = 'Mining speed modifier - mine faster!', + modifier = 'force', + per_force = true, + state = 0.15 + }, + { + name = 'laboratory_speed_modifier', + discord = 'Laboratory speed modifier - labs work faster!', + modifier = 'force', + per_force = true, + state = 0.15 + }, + { + name = 'laboratory_productivity_bonus', + discord = 'Laboratory productivity bonus - labs dupe things!', + modifier = 'force', + per_force = true, + state = 0.15 + }, + { + name = 'worker_robots_storage_bonus', + discord = 'Robot storage bonus - robots carry more!', + modifier = 'force', + per_force = true, + state = 1 + }, + { + name = 'worker_robots_battery_modifier', + discord = 'Robot battery bonus - robots work longer!', + modifier = 'force', + per_force = true, + state = 1 + }, + { + name = 'worker_robots_speed_modifier', + discord = 'Robot speed modifier - robots move faster!', + modifier = 'force', + per_force = true, + state = 0.5 + }, + { + name = 'mining_drill_productivity_bonus', + discord = 'Drill productivity bonus - drills work faster!', + modifier = 'force', + per_force = true, + state = 0.5 + }, + { + name = 'character_health_bonus', + discord = 'Character health bonus - more health!', + modifier = 'force', + per_force = true, + state = 250 + }, + { + name = 'distance', + discord = 'RPG reach distance bonus - reach further!', + modifier = 'rpg_distance', + per_force = true, + modifiers = { 'character_resource_reach_distance_bonus', 'character_item_pickup_distance_bonus', 'character_loot_pickup_distance_bonus', 'character_reach_distance_bonus' }, + state = 0.05 + }, + { + name = 'manual_crafting_speed_modifier', + discord = 'Crafting speed modifier - craft faster!', + modifier = 'force', + per_force = true, + state = 0.12 + }, + { + name = 'xp_bonus', + discord = 'RPG XP point bonus - more XP points from kills etc.', + modifier = 'rpg', + per_force = true, + state = 0.12 + }, + { + name = 'xp_level', + discord = 'RPG XP level bonus - start with more XP levels', + modifier = 'rpg', + per_force = true, + state = 20 + }, + { + name = 'chemicals_s', + discord = 'Starting items supplies - start with some sulfur', + modifier = 'starting_items', + limit = 200, + add_per_buff = 50, + items = { + { name = 'sulfur', count = 50 } + } + }, + { + name = 'chemicals_p', + discord = 'Starting items supplies - start with some plastic bar', + modifier = 'starting_items', + limit = 200, + add_per_buff = 50, + items = { + { name = 'plastic-bar', count = 100 } + } + }, + { + name = 'supplies', + discord = 'Starting items supplies - start with some copper and iron plates', + modifier = 'starting_items', + limit = 1000, + add_per_buff = 100, + items = { + { name = 'iron-plate', count = 100 }, + { name = 'copper-plate', count = 100 } + } + }, + { + name = 'supplies_1', + discord = 'Starting items supplies - start with more copper and iron plates', + modifier = 'starting_items', + limit = 1000, + add_per_buff = 200, + items = { + { name = 'iron-plate', count = 200 }, + { name = 'copper-plate', count = 200 } + } + }, + { + name = 'supplies_2', + discord = 'Starting items supplies - start with even more copper and iron plates', + modifier = 'starting_items', + limit = 1000, + add_per_buff = 400, + items = { + { name = 'iron-plate', count = 400 }, + { name = 'copper-plate', count = 400 } + } + }, + { + name = 'defense_3', + discord = 'Defense starting supplies - start with more turrets and ammo', + modifier = 'starting_items', + limit = 1, + add_per_buff = 1, + items = { + { name = 'rocket-launcher', count = 1 }, + { name = 'rocket', count = 100 } + } + }, + { + name = 'armor', + discord = 'Armor starting supplies - start with some armor and solar panels', + modifier = 'starting_items', + limit = 1, + add_per_buff = 1, + items = { + { name = 'modular-armor', count = 1 }, + { name = 'solar-panel-equipment', count = 2 } + } + }, + { + name = 'production', + discord = 'Production starting supplies - start with some furnaces and coal', + modifier = 'starting_items', + limit = 2, + add_per_buff = 1, + items = { + { name = 'stone-furnace', count = 4 }, + { name = 'coal', count = 100 } + } + }, + { + name = 'production_1', + discord = 'Production starting supplies - start with some steel furnaces and solid fuel', + modifier = 'starting_items', + limit = 2, + add_per_buff = 1, + items = { + { name = 'steel-furnace', count = 4 }, + { name = 'solid-fuel', count = 100 } + } + }, + { + name = 'fast_startup_1', + discord = 'Assembling starting supplies - start with some assembling machines T2', + modifier = 'starting_items', + limit = 25, + add_per_buff = 2, + items = { + { name = 'assembling-machine-2', count = 2 } + } + }, + { + name = 'fast_startup_2', + discord = 'Assembling starting supplies - start with some assembling machines T3', + modifier = 'starting_items', + limit = 25, + add_per_buff = 2, + items = { + { name = 'assembling-machine-3', count = 2 } + } + }, + { + name = 'heal-thy-buildings', + discord = 'Repair starting supplies - start with some repair packs', + modifier = 'starting_items', + limit = 20, + add_per_buff = 2, + items = { + { name = 'repair-pack', count = 5 } + } + }, + { + name = 'extra_wagons', + discord = 'Extra wagon at start', + modifier = 'locomotive', + state = 1 + }, + { + name = 'american_oil', + discord = 'Oil tech - start with some crude oil barrels', + modifier = 'starting_items', + limit = 40, + add_per_buff = 20, + items = { + { name = 'crude-oil-barrel', count = 20 } + } + }, + { + name = 'steel_plates', + discord = 'Steel tech - start with some steel plates', + modifier = 'starting_items', + limit = 200, + add_per_buff = 100, + items = { + { name = 'steel-plate', count = 100 } + } + }, + { + name = 'red_science', + discord = 'Science tech - start with some red science packs', + modifier = 'starting_items', + limit = 200, + add_per_buff = 10, + items = { + { name = 'automation-science-pack', count = 10 } + } + }, + { + name = 'roboport_equipement', + discord = 'Equipement tech - start with a personal roboport', + modifier = 'starting_items', + limit = 4, + add_per_buff = 1, + items = { + { name = 'personal-roboport-equipment', count = 1 } + } + }, + { + name = 'mk1_tech_unlocked', + discord = 'Equipement tech - start with power armor tech unlocked.', + modifier = 'tech', + limit = 1, + add_per_buff = 1, + techs = { + { name = 'power-armor', count = 1 } + } + }, + { + name = 'steel_axe_unlocked', + discord = 'Equipement tech - start with steel axe tech unlocked.', + modifier = 'tech', + limit = 1, + add_per_buff = 1, + techs = { + { name = 'steel-axe', count = 1 } + } + }, + { + name = 'military_2_unlocked', + discord = 'Equipement tech - start with military 2 tech unlocked.', + modifier = 'tech', + limit = 1, + add_per_buff = 1, + techs = { + { name = 'military-2', count = 1 } + } + }, + { + name = 'all_the_fish', + discord = 'Wagon is full of fish!', + modifier = 'fish', + limit = 1, + add_per_buff = 1 + } + } + + if only_force then + local force_buffs = {} + for _, buff in pairs(buffs) do + if buff.per_force then + force_buffs[#force_buffs + 1] = buff + end + end + + shuffle(force_buffs) + shuffle(force_buffs) + shuffle(force_buffs) + shuffle(force_buffs) + shuffle(force_buffs) + shuffle(force_buffs) + + return force_buffs[1] + end + + if fetch_all then + return buffs + end + + shuffle(buffs) + shuffle(buffs) + shuffle(buffs) + shuffle(buffs) + shuffle(buffs) + shuffle(buffs) + + return buffs[1] +end + +local function get_item_produced_count(item_name) + local force = game.forces.player + + local production = force.item_production_statistics.input_counts[item_name] + if not production then + return false + end + + return production +end + +local function get_entity_mined_count(item_name) + local force = game.forces.player + + local count = 0 + for name, entity_count in pairs(force.entity_build_count_statistics.output_counts) do + if name:find(item_name) then + count = count + entity_count + end + end + + return count +end + +local function get_killed_enemies_count(primary, secondary) + local force = game.forces.player + + local count = 0 + for name, entity_count in pairs(force.kill_count_statistics.input_counts) do + if name:find(primary) or name:find(secondary) then + count = count + entity_count + end + end + + return count +end + +local move_all_players_token = + Task.register( + function () + Public.move_all_players() + end + ) + +local search_corpse_token = + Task.register( + function (event) + local player_index = event.player_index + local player = game.get_player(player_index) + + if not player or not player.valid then + return + end + + local pos = player.position + local entities = + player.surface.find_entities_filtered { + area = { { pos.x - 0.5, pos.y - 0.5 }, { pos.x + 0.5, pos.y + 0.5 } }, + name = 'character-corpse' + } + + local entity + for _, e in ipairs(entities) do + if e.character_corpse_tick_of_death then + entity = e + break + end + end + + if not entity or not entity.valid then + return + end + + entity.destroy() + + local text = player.name .. "'s corpse was consumed by the biters." + + game.print(text) + end + ) + +local function on_pre_player_died(event) + local player_index = event.player_index + local player = game.get_player(player_index) + + if not player or not player.valid then + return + end + + local surface = player.surface + + local map_name = 'mtn_v3' + + local corpse_removal_disabled = Public.get('corpse_removal_disabled') + if corpse_removal_disabled then + return + end + + if string.sub(surface.name, 0, #map_name) ~= map_name then + return + end + + -- player.ticks_to_respawn = 1800 * (this.rounds_survived + 1) + + Task.set_timeout_in_ticks(5, search_corpse_token, { player_index = player.index }) +end + +local function on_market_item_purchased(event) + if not event.cost then + return + end + + local coins = this.objectives.locomotive_market_coins_spent + if not coins then + return + end + + coins.spent = coins.spent + event.cost +end + +local empty_token = + Task.register( + function () + return false + end + ) + +local killed_enemies_token = + Task.register( + function () + local actual = Public.get_killed_enemies_count('biter', 'spitter') + local expected = this.objectives.killed_enemies + if actual >= expected then + return true, { 'stateful.enemies_killed' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + + return false, { 'stateful.enemies_killed' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } + end + ) + +local killed_enemies_type_token = + Task.register( + function () + local actual = this.objectives.killed_enemies_type.actual + local expected = this.objectives.killed_enemies_type.expected + if actual >= expected then + return true, { 'stateful.enemies_killed_type', this.objectives.killed_enemies_type.damage_type }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + + return false, { 'stateful.enemies_killed_type', this.objectives.killed_enemies_type.damage_type }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { + 'stateful.tooltip_not_completed' + } + end + ) + +local handcrafted_items_token = + Task.register( + function () + local actual = this.objectives.handcrafted_items.actual + local expected = this.objectives.handcrafted_items.expected + if actual >= expected then + return true, { 'stateful.crafted_items', this.objectives.handcrafted_items.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + + return false, { 'stateful.crafted_items', this.objectives.handcrafted_items.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { + 'stateful.tooltip_not_completed' + } + end + ) + +local handcrafted_items_any_token = + Task.register( + function () + local actual = this.objectives.handcrafted_items_any.actual + local expected = this.objectives.handcrafted_items_any.expected + if actual >= expected then + return true, { 'stateful.crafted_items', this.objectives.handcrafted_items_any.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + + return false, { 'stateful.crafted_items', this.objectives.handcrafted_items_any.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { + 'stateful.tooltip_not_completed' + } + end + ) + +local launch_item_token = + Task.register( + function () + local actual = this.objectives.launch_item.actual + local expected = this.objectives.launch_item.expected + if actual >= expected then + return true, { 'stateful.launch_item', this.objectives.launch_item.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + + return false, { 'stateful.launch_item', this.objectives.launch_item.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { + 'stateful.tooltip_not_completed' + } + end + ) + +local cast_spell_token = + Task.register( + function () + local actual = this.objectives.cast_spell.actual + local expected = this.objectives.cast_spell.expected + if actual >= expected then + return true, { 'stateful.cast_spell', this.objectives.cast_spell.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + + return false, { 'stateful.cast_spell', this.objectives.cast_spell.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { + 'stateful.tooltip_not_completed' + } + end + ) + +local cast_spell_any_token = + Task.register( + function () + local actual = this.objectives.cast_spell_any.actual + local expected = this.objectives.cast_spell_any.expected + if actual >= expected then + return true, { 'stateful.cast_spell', this.objectives.cast_spell_any.name }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + + return false, { 'stateful.cast_spell', this.objectives.cast_spell_any.name }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { + 'stateful.tooltip_not_completed' + } + end + ) + +local research_level_selection_token = + Task.register( + function () + local actual = this.objectives.research_level_selection.research_count + local expected = this.objectives.research_level_selection.count + if actual >= expected then + return true, { 'stateful.research', this.objectives.research_level_selection.name }, { 'stateful.done', expected, expected }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + return false, { 'stateful.research', this.objectives.research_level_selection.name }, { 'stateful.not_done', actual, expected }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } + end + ) + +local locomotive_market_coins_spent_token = + Task.register( + function () + local coins = this.objectives.locomotive_market_coins_spent + local actual = coins.spent + local expected = coins.required + if actual >= expected then + return true, { 'stateful.market_spent' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + return false, { 'stateful.market_spent' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } + end + ) + +local minerals_farmed_token = + Task.register( + function () + local actual = get_entity_mined_count('rock') + get_entity_mined_count('tree') + local expected = this.objectives.minerals_farmed + if actual >= expected then + return true, { 'stateful.minerals_mined' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + return false, { 'stateful.minerals_mined' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } + end + ) + +local rockets_launched_token = + Task.register( + function () + local actual = game.forces.player.rockets_launched + local expected = this.objectives.rockets_launched + if actual >= expected then + return true, { 'stateful.launch_rockets' }, { 'stateful.done', format_number(expected, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_completed' } + end + return false, { 'stateful.launch_rockets' }, { 'stateful.not_done', format_number(actual, true), format_number(expected, true) }, { 'stateful.generic_tooltip' }, { 'stateful.tooltip_not_completed' } + end + ) + +local function scale(setting, limit, factor) + factor = factor or 1.05 + local scale_value = floor(setting * (factor ^ this.rounds_survived)) + if limit and scale_value >= limit then + return limit + end + return floor(scale_value) +end + +local function scale_lin(setting, limit, factor) + factor = factor or 1.05 + local scale_value = math.floor(setting + (factor * this.rounds_survived)) + if limit and scale_value >= limit then + return limit + end + return floor(scale_value) +end + +local function get_random_items() + local items = { + { 'advanced-circuit', scale(225000, 9000000) }, + { 'copper-cable', scale(3000000, 120000000) }, + { 'copper-plate', scale(1500000, 60000000) }, + { 'electric-engine-unit', scale(10000, 400000) }, + { 'electronic-circuit', scale(1000000, 40000000) }, + { 'engine-unit', scale(20000, 800000) }, + { 'explosives', scale(150000, 6000000) }, + { 'iron-gear-wheel', scale(150000, 6000000) }, + { 'iron-plate', scale(2000000, 80000000) }, + { 'iron-stick', scale(75000, 3000000) }, + { 'processing-unit', scale(40000, 1600000) }, + { 'rocket-control-unit', scale(8000, 320000) }, + { 'steel-plate', scale(200000, 8000000) }, + { 'rocket', scale(25000, 1000000) }, + { 'explosive-rocket', scale(25000, 1000000) }, + { 'slowdown-capsule', scale(10000, 400000) }, + { 'laser-turret', scale(3000, 120000) }, + { 'stone-wall', scale(20000, 800000) }, + { 'accumulator', scale(5000, 200000) }, + { 'refined-concrete', scale(15000, 600000) }, + { 'uranium-rounds-magazine', scale(4000, 160000) }, + { 'explosive-uranium-cannon-shell', scale(3000, 120000) }, + { 'distractor-capsule', scale(1500, 60000) }, + { 'cluster-grenade', scale(4000, 160000) }, + { 'small-lamp', scale(5000, 200000) }, + { 'uranium-fuel-cell', scale(2500, 100000) } + } + + shuffle(items) + shuffle(items) + + local container = { + [1] = { name = items[1][1], count = items[1][2] }, + [2] = { name = items[2][1], count = items[2][2] }, + [3] = { name = items[3][1], count = items[3][2] } + } + + if this.test_mode then + container = { + [1] = { name = items[1].products[1].name, count = 1 }, + [2] = { name = items[2].products[1].name, count = 1 }, + [3] = { name = items[3].products[1].name, count = 1 } + } + end + + return container +end + +local function get_random_item() + local items = { + { 'effectivity-module', scale(1000, 400000) }, + { 'productivity-module', scale(10000, 400000) }, + { 'speed-module', scale(10000, 400000) }, + { 'effectivity-module-2', scale(200, 100000) }, + { 'productivity-module-2', scale(1000, 100000) }, + { 'speed-module-2', scale(1000, 100000) }, + { 'effectivity-module-3', scale(50, 30000) }, + { 'productivity-module-3', scale(500, 30000) }, + { 'speed-module-3', scale(500, 30000) } + } + + shuffle(items) + shuffle(items) + shuffle(items) + shuffle(items) + + return { name = items[1][1], count = items[1][2] } +end + +local function get_random_handcrafted_item() + local items = { + { 'advanced-circuit', scale(2000, 500000) }, + { 'copper-cable', scale(10000, 500000) }, + { 'electronic-circuit', scale(5000, 1000000) }, + { 'iron-gear-wheel', scale(50000, 1000000) }, + { 'iron-stick', scale(75000, 3000000) }, + { 'rocket-control-unit', scale(1000, 50000) }, + { 'rocket', scale(5000, 1000000) }, + { 'explosive-rocket', scale(5000, 1000000) }, + { 'slowdown-capsule', scale(2500, 400000) }, + { 'laser-turret', scale(1500, 20000) }, + { 'stone-wall', scale(5000, 800000) }, + { 'accumulator', scale(1000, 200000) }, + { 'uranium-rounds-magazine', scale(1000, 60000) }, + { 'explosive-uranium-cannon-shell', scale(1000, 10000) }, + { 'distractor-capsule', scale(1500, 60000) }, + { 'grenade', scale(5000, 200000) }, + { 'cluster-grenade', scale(1000, 100000) }, + { 'small-lamp', scale(2500, 200000) }, + { 'rail', scale(5000, 100000) }, + { 'small-electric-pole', scale(5000, 100000) }, + { 'medium-electric-pole', scale(3500, 80000) }, + { 'big-electric-pole', scale(2000, 50000) }, + { 'transport-belt', scale(10000, 100000) }, + { 'fast-transport-belt', scale(3000, 50000) }, + { 'repair-pack', scale(10000, 100000) }, + { 'splitter', scale(10000, 100000) }, + { 'fast-splitter', scale(3000, 50000) }, + { 'inserter', scale(3000, 50000) }, + { 'firearm-magazine', scale(10000, 200000) }, + { 'piercing-rounds-magazine', scale(5000, 100000) }, + { 'pipe', scale(10000, 100000) }, + { 'pipe-to-ground', scale(3000, 50000) }, + { 'effectivity-module', scale(100, 50000) }, + { 'productivity-module', scale(100, 50000) }, + { 'speed-module', scale(100, 50000) } + } + + shuffle(items) + shuffle(items) + shuffle(items) + shuffle(items) + + return { name = items[1][1], count = items[1][2] } +end + +local function get_random_spell() + local items = { + { 'small-biter', scale(1000, 10000) }, + { 'small-spitter', scale(1000, 60000) }, + { 'medium-biter', scale(1000, 200000) }, + { 'medium-spitter', scale(1000, 100000) }, + { 'biter-spawner', scale(1000, 200000) }, + { 'spitter-spawner', scale(1000, 100000) }, + { 'shotgun-shell', scale(1000, 100000) }, + { 'grenade', scale(1000, 80000) }, + { 'cluster-grenade', scale(1000, 50000) }, + { 'cannon-shell', scale(1000, 100000) }, + { 'explosive-cannon-shell', scale(1000, 50000) }, + { 'uranium-cannon-shell', scale(1000, 100000) }, + { 'rocket', scale(1000, 100000) }, + { 'acid-stream-spitter-big', scale(1000, 200000) }, + { 'explosives', scale(5000, 100000) }, + { 'distractor-capsule', scale(5000, 100000) }, + { 'defender-capsule', scale(5000, 100000) }, + { 'destroyer-capsule', scale(5000, 100000) }, + { 'warp-gate', scale(5000, 500000) }, + { 'haste', scale(5000, 500000) } + } + + shuffle(items) + shuffle(items) + shuffle(items) + shuffle(items) + + return { name = items[1][1], count = items[1][2] } +end + +local function get_random_research_recipe() + -- scale(10, 20) + local research_level_list = { + 'energy-weapons-damage-7', + 'stronger-explosives-7', + 'mining-productivity-4', + 'worker-robots-speed-6', + 'follower-robot-count-7' + } + + shuffle(research_level_list) + + if this.test_mode then + return { name = research_level_list[1], count = 1, research_count = 0 } + end + + return { name = research_level_list[1], count = scale(2, 11, 1.03), research_count = 0 } +end + +local function get_random_objectives() + local items = { + { + name = 'single_item', + token = empty_token + }, + { + name = 'killed_enemies', + token = killed_enemies_token + }, + { + name = 'killed_enemies_type', + token = killed_enemies_type_token + }, + { + name = 'handcrafted_items', + token = handcrafted_items_token + }, + { + name = 'handcrafted_items_any', + token = handcrafted_items_any_token + }, + { + name = 'cast_spell', + token = cast_spell_token + }, + { + name = 'launch_item', + token = launch_item_token + }, + { + name = 'cast_spell_any', + token = cast_spell_any_token + }, + { + name = 'research_level_selection', + token = research_level_selection_token + }, + { + name = 'locomotive_market_coins_spent', + token = locomotive_market_coins_spent_token + }, + { + name = 'minerals_farmed', + token = minerals_farmed_token + }, + { + name = 'rockets_launched', + token = rockets_launched_token + } + } + + shuffle(items) + shuffle(items) + shuffle(items) + shuffle(items) + + if _DEBUG then + items[#items + 1] = { + name = 'supplies', + token = empty_token + } + return items + end + + return { + { + name = 'supplies', + token = empty_token + }, + items[2], + items[3], + items[4] + } +end + +local function clear_all_stats() + this.buffs_collected = {} + this.extra_wagons = 0 + local rpg_extra = RPG.get('rpg_extra') + rpg_extra.difficulty = 0 + rpg_extra.grant_xp_level = 0 +end + +local function migrate_buffs() + local state_buffs = get_random_buff(true) + + for _, data in pairs(state_buffs) do + for index, buff in pairs(this.buffs) do + if data.name == buff.name then + if data.add_per_buff then + buff.add_per_buff = data.add_per_buff + end + if buff.replaces then + buff.replaces = nil + end + + if buff.modifier == 'starting_items_1' then + buff.modifier = 'starting_items' + end + + if data.items and type(data.items) == 'table' then + buff.items = data.items + end + + if data.limit and not buff.limit then + buff.limit = data.limit + buff.name = data.name + end + + if buff.items == 0 then + this.buffs[index] = nil + end + end + end + end +end + +local function apply_buffs() + local starting_items = Public.get_func('starting_items') + local techs = Public.get_func('techs') + local limit_types = Public.get_func('limit_types') + + if this.buffs and next(this.buffs) then + local total_buffs = 0 + if not this.buffs_collected then + this.buffs_collected = {} + end + + migrate_buffs() + + local force = game.forces.player + for _, buff in pairs(this.buffs) do + if buff then + total_buffs = total_buffs + 1 + if buff.modifier == 'rpg_distance' then + for _, buff_name in pairs(buff.modifiers) do + if buff_name == 'character_reach_distance_bonus' then + buff.state = 1 + end + + force[buff_name] = force[buff_name] + buff.state + + if not this.buffs_collected[buff_name] then + this.buffs_collected[buff_name] = { + name = 'Extra Reach', + count = buff.state, + discord = buff.discord, + force = true + } + else + this.buffs_collected[buff_name].count = this.buffs_collected[buff_name].count + buff.state + end + end + end + if buff.modifier == 'force' then + force[buff.name] = force[buff.name] + buff.state + + if not this.buffs_collected[buff.name] then + this.buffs_collected[buff.name] = { + count = buff.state, + discord = buff.discord, + force = true + } + else + this.buffs_collected[buff.name].count = this.buffs_collected[buff.name].count + buff.state + end + end + if buff.modifier == 'locomotive' then + local extra_wagons = Public.get('extra_wagons') + if not extra_wagons then + this.extra_wagons = buff.state + else + this.extra_wagons = this.extra_wagons + buff.state + end + + if not this.buffs_collected['locomotive'] then + this.buffs_collected['locomotive'] = { + name = 'Extra Wagons', + count = buff.state, + discord = buff.discord + } + else + if this.extra_wagons > 4 then + this.buffs_collected['locomotive'].count = this.extra_wagons + else + this.buffs_collected['locomotive'].count = this.extra_wagons + buff.state + end + end + + if this.extra_wagons > 4 then + this.extra_wagons = 4 + end + end + if buff.modifier == 'fish' then + limit_types[buff.name] = true + Public.set('all_the_fish', true) + if not this.buffs_collected['fish'] then + this.buffs_collected['fish'] = { + name = 'A thousand fishes', + discord = buff.discord + } + end + end + if buff.modifier == 'tech' then + if not this.buffs_collected['techs'] then + this.buffs_collected['techs'] = {} + end + if type(buff.techs) ~= 'table' then + goto cont + end + + for _, tech in pairs(buff.techs) do + if tech then + if techs[tech.name] then + goto cont + end + + if not techs[tech.name] then + techs[tech.name] = { + name = buff.name + } + end + + if not this.buffs_collected['techs'][tech.name] then + this.buffs_collected['techs'][tech.name] = { + name = tech.name, + buff_type = buff.name, + discord = buff.discord + } + end + force.technologies[tech.name].researched = true + end + end + end + if buff.modifier == 'rpg' then + local rpg_extra = RPG.get('rpg_extra') + if buff.name == 'xp_bonus' then + if not rpg_extra.difficulty then + rpg_extra.difficulty = buff.state + else + rpg_extra.difficulty = rpg_extra.difficulty + buff.state + end + if not this.buffs_collected['xp_bonus'] then + this.buffs_collected['xp_bonus'] = { + name = 'XP Bonus', + count = buff.state, + discord = buff.discord + } + else + this.buffs_collected['xp_bonus'].count = this.buffs_collected['xp_bonus'].count + buff.state + end + end + if buff.name == 'xp_level' then + if not rpg_extra.grant_xp_level then + rpg_extra.grant_xp_level = buff.state + else + rpg_extra.grant_xp_level = rpg_extra.grant_xp_level + buff.state + end + if not this.buffs_collected['xp_level'] then + this.buffs_collected['xp_level'] = { + name = 'XP Level Bonus', + count = buff.state, + discord = buff.discord + } + else + this.buffs_collected['xp_level'].count = this.buffs_collected['xp_level'].count + buff.state + end + end + end + if buff.modifier == 'starting_items' then + if not this.buffs_collected['starting_items'] then + this.buffs_collected['starting_items'] = {} + end + if type(buff.items) ~= 'table' then + goto cont + end + + for _, item in pairs(buff.items) do + if item then + if starting_items[item.name] and buff.limit and starting_items[item.name].item_limit and starting_items[item.name].item_limit >= buff.limit then + starting_items[item.name].limit_reached = true + goto cont -- break if there is a limit set + end + + if starting_items[item.name] then + starting_items[item.name].count = starting_items[item.name].count + item.count + starting_items[item.name].item_limit = starting_items[item.name].item_limit and starting_items[item.name].item_limit + buff.add_per_buff or buff.add_per_buff + starting_items[item.name].buff_type = buff.name + else + starting_items[item.name] = { + buff_type = buff.name, + count = item.count, + item_limit = buff.add_per_buff + } + end + if this.buffs_collected['starting_items'][item.name] then + this.buffs_collected['starting_items'][item.name].count = this.buffs_collected['starting_items'][item.name].count + item.count + this.buffs_collected['starting_items'][item.name].buff_type = buff.name + else + this.buffs_collected['starting_items'][item.name] = { + buff_type = buff.name, + count = item.count, + discord = buff.discord + } + end + end + end + end + end + ::cont:: + end + this.total_buffs = total_buffs + end + Public.equip_players(starting_items) +end + +local function apply_startup_settings(settings) + local current_date = Server.get_current_date(false, true) + if not current_date then + return + end + + local current_time = Server.get_current_time() + if not current_time then + return + end + + current_date = round(Utils.convert_date(current_date.year, current_date.month, current_date.day)) + + local server_name_matches = Server.check_server_name(scenario_name) + + settings = settings or {} + local stored_date = this.current_date + if not stored_date then + return + end + local stored_date_raw = Server.get_current_date(false, true, stored_date) + local converted_stored_date = round(Utils.convert_date(stored_date_raw.year, stored_date_raw.month, stored_date_raw.day)) + + local time_to_reset = (current_date - converted_stored_date) + this.time_to_reset = this.reset_after - time_to_reset + + if time_to_reset and time_to_reset >= this.reset_after then + Public.save_settings_before_reset() + Public.set_season_scores() + + local s = this.season or 1 + game.server_save('Season_' .. s .. '_Mtn_v3_' .. tostring(current_time)) + notify_season_over_to_discord() + settings.current_date = current_time + settings.test_mode = false + settings.rounds_survived = 0 + settings.buffs = {} + this.buffs = {} + this.buffs_collected = {} + this.rounds_survived = 0 + this.season = this.season + 1 + this.current_date = current_time + settings.season = this.season + this.time_to_reset = this.reset_after + local message = ({ 'stateful.reset' }) + local message_discord = ({ 'stateful.reset_discord' }) + game.print(message) + Server.to_discord_embed(message_discord, true) + + game.print(({ 'entity.notify_shutdown' }), { r = 0.22, g = 0.88, b = 0.22 }) + local notify_shutdown = ({ 'entity.shutdown_game' }) + Server.to_discord_bold(notify_shutdown, true) + + Server.stop_scenario() + + if server_name_matches then + Server.set_data(dataset, dataset_key, settings) + else + Server.set_data(dataset, dataset_key_dev, settings) + end + end +end + +local apply_settings_token = + Task.register( + function (data) + local server_name_matches = Server.check_server_name(scenario_name) + local settings = data and data.value or nil + local current_time = Server.get_current_time() + if not current_time then + return + end + + if not settings then + settings = { + rounds_survived = 0, + current_date = tonumber(current_time), + season = 1 + } + if server_name_matches then + Server.set_data(dataset, dataset_key, settings) + else + Server.set_data(dataset, dataset_key_dev, settings) + end + return + end + + if not settings.current_date then + settings.current_date = tonumber(current_time) + end + + if not settings.season then + settings.season = 1 + end + + this.current_date = settings.current_date + this.buffs = settings.buffs + + apply_startup_settings(settings) + + this.rounds_survived = settings.rounds_survived + this.season = settings.season + + local current_season = Public.get('current_season') + if current_season then + ---@diagnostic disable-next-line: param-type-mismatch + rendering.set_text(current_season, 'Season: ' .. this.season) + end + + this.objectives = {} + + Public.reset_stateful() + Public.increase_enemy_damage_and_health() + Public.init_mtn() + end + ) + +local function grant_non_limit_reached_buff() + local all_buffs = get_random_buff(true) + local starting_items = Public.get_func('starting_items') + local techs = Public.get_func('techs') + local limit_types = Public.get_func('limit_types') + + for index, data in pairs(all_buffs) do + for _, item_data in pairs(starting_items) do + if item_data.buff_type == data.name and item_data.item_limit and data.limit and item_data.item_limit >= data.limit then + all_buffs[index] = nil + end + end + + for _, tech_data in pairs(techs) do + if tech_data.name == data.name then + all_buffs[index] = nil + end + end + + for limit_name, _ in pairs(limit_types) do + if limit_name == data.name then + all_buffs[index] = nil + end + end + end + + shuffle(all_buffs) + shuffle(all_buffs) + shuffle(all_buffs) + shuffle(all_buffs) + shuffle(all_buffs) + shuffle(all_buffs) + + if not all_buffs[1] then + return get_random_buff(nil, true) + end + + return all_buffs[1] +end + +function Public.save_settings() + local granted_buff = grant_non_limit_reached_buff() + this.buffs[#this.buffs + 1] = granted_buff + + local settings = { + objectives_time_spent = this.objectives_time_spent, + rounds_survived = this.rounds_survived, + season = this.season, + test_mode = this.test_mode, + buffs = this.buffs, + current_date = this.current_date + } + + local server_name_matches = Server.check_server_name(scenario_name) + if server_name_matches then + Server.set_data(dataset, dataset_key, settings) + else + Server.set_data(dataset, dataset_key_dev, settings) + end + + return granted_buff +end + +function Public.save_settings_before_reset() + local settings = { + rounds_survived = this.rounds_survived, + season = this.season, + test_mode = this.test_mode, + buffs = this.buffs, + current_date = this.current_date + } + + local server_name_matches = Server.check_server_name(scenario_name) + if server_name_matches then + Server.set_data(dataset, dataset_key_previous, settings) + else + Server.set_data(dataset, dataset_key_previous, settings) + end +end + +function Public.reset_stateful(refresh_gui, clear_buffs) + this.test_mode = false + + this.final_battle = false + this.extra_wagons = 0 + if clear_buffs then + this.buffs_collected = {} + end + this.enemies_boosted = false + this.tasks_required_to_win = 6 + + if not this.previous_objectives_time_spent then + this.previous_objectives_time_spent = {} + end + + if this.test_mode then + this.objectives = { + randomized_zone = 2, + randomized_wave = 2, + supplies = get_random_items(), + single_item = get_random_item(), + killed_enemies = 10, + killed_enemies_type = { + actual = 0, + expected = 10, + damage_type = damage_types[random(1, #damage_types)] + }, + handcrafted_items = { + actual = 0, + expected = 10, + name = 'rail' + }, + handcrafted_items_any = { + actual = 0, + expected = 10, + name = 'Any' + }, + cast_spell = { + actual = 0, + expected = 10, + name = 'pipe' + }, + cast_spell_any = { + actual = 0, + expected = 10, + name = 'Any' + }, + launch_item = { + actual = 0, + expected = 10, + name = 'raw-fish' + }, + research_level_selection = get_random_research_recipe(), + locomotive_market_coins_spent = 0, + locomotive_market_coins_spent_required = 1, + trees_farmed = 10, + minerals_farmed = 10, + rockets_launched = 1 + } + else + if not this.objectives then + this.objectives = {} + end + + if not this.selected_objectives then + this.selected_objectives = get_random_objectives() + end + + if not this.objectives.randomized_zone or (this.objectives_completed ~= nil and this.objectives_completed.randomized_zone) then + this.objectives.randomized_zone = scale(4, 15, 1.013) + end + if not this.objectives.randomized_wave or (this.objectives_completed ~= nil and this.objectives_completed.randomized_wave) then + this.objectives.randomized_wave = scale(200, 1000) + end + if not this.objectives.supplies or (this.objectives_completed ~= nil and this.objectives_completed.supplies) then + this.objectives.supplies = get_random_items() + end + if not this.objectives.single_item or (this.objectives_completed ~= nil and this.objectives_completed.single_item) then + this.objectives.single_item = get_random_item() + end + if not this.objectives.killed_enemies or (this.objectives_completed ~= nil and this.objectives_completed.killed_enemies) then + this.objectives.killed_enemies = scale(25000, 400000, 1.035) + end + if not this.objectives.killed_enemies_type or (this.objectives_completed ~= nil and this.objectives_completed.killed_enemies_type) then + this.objectives.killed_enemies_type = { + actual = 0, + expected = scale(10000, 400000, 1.035), + damage_type = damage_types[random(1, #damage_types)] + } + end + if not this.objectives.handcrafted_items or (this.objectives_completed ~= nil and this.objectives_completed.handcrafted_items) then + local item = get_random_handcrafted_item() + this.objectives.handcrafted_items = { + actual = 0, + expected = item.count, + name = item.name + } + end + if not this.objectives.handcrafted_items_any or (this.objectives_completed ~= nil and this.objectives_completed.handcrafted_items_any) then + this.objectives.handcrafted_items_any = { + actual = 0, + expected = scale(50000, 4000000, 1.035), + name = 'Any' + } + end + if not this.objectives.cast_spell or (this.objectives_completed ~= nil and this.objectives_completed.cast_spell) then + local item = get_random_spell() + this.objectives.cast_spell = { + actual = 0, + expected = item.count, + name = item.name + } + end + if not this.objectives.cast_spell_any or (this.objectives_completed ~= nil and this.objectives_completed.cast_spell_any) then + this.objectives.cast_spell_any = { + actual = 0, + expected = scale(1000, 4000000, 1.035), + name = 'Any' + } + end + if not this.objectives.launch_item or (this.objectives_completed ~= nil and this.objectives_completed.launch_item) then + local item = get_random_handcrafted_item() + this.objectives.launch_item = { + actual = 0, + expected = scale(10, 700), + name = item.name + } + end + if not this.objectives.research_level_selection or (this.objectives_completed ~= nil and this.objectives_completed.research_level_selection) then + this.objectives.research_level_selection = get_random_research_recipe() + end + if not this.objectives.locomotive_market_coins_spent or (this.objectives_completed ~= nil and this.objectives_completed.locomotive_market_coins_spent) then + this.objectives.locomotive_market_coins_spent = { + spent = 0, + required = scale(50000) + } + end + if not this.objectives.minerals_farmed or (this.objectives_completed ~= nil and this.objectives_completed.minerals_farmed) then + this.objectives.minerals_farmed = scale(25000, 250000) + end + if not this.objectives.rockets_launched or (this.objectives_completed ~= nil and this.objectives_completed.rockets_launched) then + this.objectives.rockets_launched = scale(10, 700) + end + end + + local supplies = this.objectives.supplies + for _, supply in pairs(supplies) do + if supply and supply.total then + supply.count = supply.total + end + end + + if supplies.single_item and supplies.single_item.total then + supplies.single_item.count = supplies.single_item.total + end + + WD.set_es_unit_limit(scale_lin(100, 1000, 5.819)) + + this.objectives.handcrafted_items.actual = 0 + this.objectives.handcrafted_items_any.actual = 0 + this.objectives.cast_spell.actual = 0 + this.objectives.cast_spell_any.actual = 0 + this.objectives.killed_enemies_type.actual = 0 + this.objectives.launch_item.actual = 0 + this.objectives.research_level_selection.research_count = 0 + this.objectives.locomotive_market_coins_spent.spent = 0 + + this.objectives_completed = {} + if this.objectives_time_spent and next(this.objectives_time_spent) then + this.previous_objectives_time_spent[#this.previous_objectives_time_spent + 1] = this.objectives_time_spent + end + + this.stateful_spawn_points = { + { { x = -205, y = -37 }, { x = 195, y = 37 } }, + { { x = -205, y = -112 }, { x = 195, y = 112 } }, + { { x = -205, y = -146 }, { x = 195, y = 146 } }, + { { x = -205, y = -112 }, { x = 195, y = 112 } }, + { { x = -205, y = -72 }, { x = 195, y = 72 } }, + { { x = -205, y = -146 }, { x = 195, y = 146 } }, + { { x = -205, y = -37 }, { x = 195, y = 37 } }, + { { x = -205, y = -5 }, { x = 195, y = 5 } }, + { { x = -205, y = -23 }, { x = 195, y = 23 } }, + { { x = -205, y = -5 }, { x = 195, y = 5 } }, + { { x = -205, y = -72 }, { x = 195, y = 72 } }, + { { x = -205, y = -23 }, { x = 195, y = 23 } }, + { { x = -205, y = -54 }, { x = 195, y = 54 } }, + { { x = -205, y = -80 }, { x = 195, y = 80 } }, + { { x = -205, y = -54 }, { x = 195, y = 54 } }, + { { x = -205, y = -80 }, { x = 195, y = 80 } }, + { { x = -205, y = -103 }, { x = 195, y = 103 } }, + { { x = -205, y = -150 }, { x = 195, y = 150 } }, + { { x = -205, y = -103 }, { x = 195, y = 103 } }, + { { x = -205, y = -150 }, { x = 195, y = 150 } } + } + + this.objectives_time_spent = {} + this.objectives_completed_count = 0 + + this.collection = { + clear_rocks = nil, + survive_for = nil, + survive_for_timer = nil, + final_arena_disabled = false + } + this.stateful_locomotive_migrated = false + this.force_chunk = true + + local Diff = Difficulty.get() + Diff.index = scale(1, 3, 1.009) + + if Diff.index == 3 then + local message = ({ 'stateful.difficulty_step' }) + local delay = 25 + Alert.set_timeout_in_ticks_alert(delay, { text = message }) + end + + Public.set('coin_amount', Diff.index) + + local t = { + ['randomized_zone'] = this.objectives.randomized_zone, + ['randomized_wave'] = this.objectives.randomized_wave + } + for index = 1, #this.selected_objectives do + local objective = this.selected_objectives[index] + if not t[objective.name] then + t[objective.name] = this.objectives[objective.name] + end + end + + this.objectives = t + + Public.reset_main_table() + + clear_all_stats() + + apply_buffs() + if refresh_gui then + Public.refresh_frames() + end +end + +function Public.move_all_players() + local active_surface_index = Public.get('active_surface_index') + local surface = game.surfaces[active_surface_index] + if not (surface and surface.valid) then + return + end + + local locomotive = Public.get('locomotive') + if not locomotive or not locomotive.valid then + return + end + + ICWF.disable_auto_minimap() + + local message = ({ 'stateful.final_boss_message_start' }) + Alert.alert_all_players(50, message, nil, nil, 1) + Core.iter_connected_players( + ---@param player LuaPlayer + function (player) + local pos = surface.find_non_colliding_position('character', locomotive.position, 32, 1) + + Public.stateful_gui.boss_frame(player, true) + + if pos then + player.teleport(pos, surface) + else + player.teleport(locomotive.position, surface) + Public.unstuck_player(player.index) + end + end + ) + + if _DEBUG then + Core.iter_fake_connected_players( + global.characters, + function (player) + local pos = surface.find_non_colliding_position('character', locomotive.position, 32, 1) + + if pos then + player.teleport(pos, surface) + else + player.teleport(locomotive.position, surface) + Public.unstuck_player(player.index) + end + end + ) + end +end + +function Public.set_final_battle() + if this.final_battle then + return + end + + local es_settings = WD.get_es('settings') + WD.set_es('final_battle', true) + es_settings.final_battle = true + Public.set('final_battle', true) +end + +function Public.allocate() + local moved_all_players = Public.get_stateful('moved_all_players') + if not moved_all_players then + Task.set_timeout_in_ticks(10, move_all_players_token, {}) + + Beam.new_valid_targets({ 'wall', 'turret', 'furnace', 'gate' }) + + Public.set_stateful('moved_all_players', true) + + ICWT.set('speed', 0.3) + ICWT.set('final_battle', true) + + local collection = Public.get_stateful('collection') + if not collection then + return + end + + Server.to_discord_embed('Final boss wave is occuring soon!') + + WD.set('final_battle', true) + + if Gui.get_mod_gui_top_frame() then + Core.iter_players( + function (player) + local g = Gui.get_button_flow(player)['wave_defense'] + if g and g.valid then + g.destroy() + end + end + ) + else + Core.iter_connected_players( + function (player) + local wd = player.gui.top['wave_defense'] + if wd and wd.valid then + wd.destroy() + end + end + ) + end + end +end + +function Public.increase_enemy_damage_and_health() + if this.enemies_boosted then + return + end + + this.enemies_boosted = true + + if this.rounds_survived == 1 then + Event.raise(WD.events.on_biters_evolved, { force = game.forces.enemy, health_increase = true }) + Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors }) + Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors_frenzy }) + else + for _ = 1, this.rounds_survived do + Event.raise(WD.events.on_biters_evolved, { force = game.forces.enemy, health_increase = true }) + Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors }) + Event.raise(WD.events.on_biters_evolved, { force = game.forces.aggressors_frenzy }) + end + end +end + +function Public.get_stateful(key) + if key then + return this[key] + else + return this + end +end + +function Public.set_stateful(key, value) + if key and (value or value == false) then + this[key] = value + return this[key] + elseif key then + return this[key] + else + return this + end +end + +function Public.remove_stateful(key, sub_key) + if key and sub_key then + if this[key] and this[key][sub_key] then + this[key][sub_key] = nil + end + elseif key then + if this[key] then + this[key] = nil + end + end +end + +function Public.enable(state) + this.enabled = state or false +end + +function Public.stateful_on_server_started() + if this.settings_applied then + return + end + + local server_name_matches = Server.check_server_name(scenario_name) + + this.settings_applied = true + + if server_name_matches then + Server.try_get_data(dataset, dataset_key, apply_settings_token) + else + Server.try_get_data(dataset, dataset_key_dev, apply_settings_token) + this.test_mode = true + end +end + +Event.add( + Server.events.on_server_started, + function () + if this.settings_applied then + return + end + + local server_name_matches = Server.check_server_name(scenario_name) + + this.settings_applied = true + + if server_name_matches then + Server.try_get_data(dataset, dataset_key, apply_settings_token) + else + Server.try_get_data(dataset, dataset_key_dev, apply_settings_token) + this.test_mode = true + end + end +) + +Server.on_data_set_changed( + dataset_key, + function (data) + if data.value then + local settings = data.value + if settings.rounds_survived ~= nil then + this.rounds_survived = settings.rounds_survived + end + if settings.season ~= nil then + this.season = settings.season + end + if settings.test_mode ~= nil then + this.test_mode = settings.test_mode + end + if settings.buffs ~= nil then + this.buffs = settings.buffs + end + if settings.current_date ~= nil then + this.current_date = settings.current_date + end + end + end +) + +Server.on_data_set_changed( + dataset_key_dev, + function (data) + if data.value then + local settings = data.value + if settings.rounds_survived ~= nil then + this.rounds_survived = settings.rounds_survived + end + if settings.season ~= nil then + this.season = settings.season + end + if settings.test_mode ~= nil then + this.test_mode = settings.test_mode + end + if settings.buffs ~= nil then + this.buffs = settings.buffs + end + if settings.current_date ~= nil then + this.current_date = settings.current_date + end + end + end +) + +Public.buff_to_string = buff_to_string +Public.get_random_buff = get_random_buff +Public.get_item_produced_count = get_item_produced_count +Public.get_entity_mined_count = get_entity_mined_count +Public.get_killed_enemies_count = get_killed_enemies_count +Public.apply_startup_settings = apply_startup_settings +Public.scale = scale +Public.on_pre_player_died = on_pre_player_died +Public.on_market_item_purchased = on_market_item_purchased + +return Public