1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2024-12-28 23:06:38 +02:00

Mtn v3 - changes for new season

This commit is contained in:
Gerkiz 2024-05-27 20:30:03 +02:00
parent b3b939b902
commit b39ed27725
45 changed files with 2921 additions and 426 deletions

View File

@ -13,17 +13,7 @@ require 'utils.utils'
require 'utils.pause_game'
require 'utils.table'
require 'utils.whisper_notice'
require 'utils.datastore.server_ups_data'
require 'utils.datastore.current_time_data'
require 'utils.datastore.color_data'
require 'utils.datastore.session_data'
require 'utils.datastore.jail_data'
require 'utils.datastore.quickbar_data'
require 'utils.datastore.warning_on_join_data'
require 'utils.datastore.message_on_join_data'
require 'utils.datastore.player_tag_data'
require 'utils.datastore.supporters'
require 'utils.datastore.banhandler'
require 'utils.datastore.init'
require 'utils.chatbot'
require 'utils.commands'
require 'utils.antigrief'
@ -35,14 +25,7 @@ require 'modules.inserter_drops_pickup'
require 'modules.autostash'
require 'modules.blueprint_requesting'
require 'utils.gui'
require 'utils.gui.player_list'
require 'utils.gui.admin'
require 'utils.gui.group'
require 'utils.gui.score'
require 'utils.gui.config'
require 'utils.gui.poll'
require 'utils.gui.server_select'
require 'utils.gui.init'
require 'utils.freeplay'
require 'utils.remote_chunks'

View File

@ -148,9 +148,12 @@ linked=[font=default-bold]Convert chests to linked: [/font]
linked_static=[font=default-bold]Purchase linked chests: [/font]
mystical_chest=[font=default-bold]Feeding the hungry chest: [/font]
enemies_killed=[font=default-bold]Enemies killed: [/font]
enemies_killed_type=[font=default-bold]Enemies killed (__1__): [/font]
crafted_items=[font=default-bold]Handcraft item (__1__): [/font]
cast_spell=[font=default-bold]Cast spell (__1__): [/font]
launch_item=[font=default-bold]Launch item __1__ to orbit: [/font]
launch_rockets=[font=default-bold]Rockets launched: [/font]
rocks_mined=[font=default-bold]Rocks mined: [/font]
trees_mined=[font=default-bold]Trees mined: [/font]
minerals_mined=[font=default-bold]Minerals mined: [/font]
production=[font=default-bold]Produce the following items: [/font]
production_single=[font=default-bold]Produce the following item: [/font]
market_spent=[font=default-bold]Spend coins in market: [/font]
@ -158,14 +161,15 @@ research=[font=default-bold]Research __1__: [/font]
season_tooltip=Whenever a new season starts, all buffs are reset.\nGerkiz tries to add new content for each season that starts.\nSeason resets in __1__ days.
rounds_survived_tooltip=Winning the game increases this number by 1.\nThis number resets as of now every 2 months.
buff_tooltip=Each buff that is given to the players is listed here.\nHover over the icon to the right to view the buffs.
buff_tooltip=Each buff that is given to the players is listed here.\nClick the icon to the right to view the buffs.
buff_tooltip_click=Click the icon view the buffs.
zone_tooltip=Complete this objective by breaching/moving forward until you've reached the given zone.
wave_tooltip=Complete this objective by surviving until the given wave.
linked_tooltip=Complete this objective by converting the given amount of chests to linked chests.
linked_static_tooltip=Complete this objective by purchasing the given amount of linked chests.
production_tooltip=Complete this objective by producing the given item(s).
time_until_attack_tooltip=Time in either minutes or seconds until the biters attack.
generic_tooltip=Complete this and this objective will be marked as complete.
generic_tooltip=Complete this to mark the objective as complete.
win_conditions_tooltip=In order to win the game, you must complete all these objectives that are listed below! [img=utility/force_editor_icon]
tooltip_failed=You've failed to complete this objective. [img=utility/not_available]
tooltip_not_completed=This objective has not been completed. [img=utility/not_available]

View File

@ -147,6 +147,8 @@ distractor=Distractor Capsule
defender=Defender Capsule
destroyer=Destroyer Capsule
warp=Warp Gate
mark_spot=X Marks The Spot
tidal_wave=Tidal Wave
pointy_explosives=Detonate Chest
repair_aoe=Repair AOE
charge=Charge

View File

@ -2,6 +2,7 @@ local Event = require 'utils.event'
local Public = require 'maps.mountain_fortress_v3.table'
local RPG = require 'modules.rpg.main'
local BiterHealthBooster = require 'modules.biter_health_booster_v2'
local StatData = require 'utils.datastore.statistics'
local insert = table.insert
local floor = math.floor
local random = math.random
@ -120,9 +121,15 @@ local function on_entity_died(event)
if forest_zone then
if random(1, 12) == 1 then
player.insert({name = 'coin', count = coin_count})
if p then
StatData.get_data(p.index):increase('coins', coin_count)
end
end
else
player.insert({name = 'coin', count = coin_count})
if p then
StatData.get_data(p.index):increase('coins', coin_count)
end
end
end
end

View File

@ -9,14 +9,15 @@ local Color = require 'utils.color_presets'
local Public = {}
local module_name = '[color=blue][Charging station][/color] '
local charging_station_name = Gui.uid_name()
local function draw_charging_gui(player, activate_custom_buttons)
local button =
player.gui.top['charging_station'] or
player.gui.top[charging_station_name] or
player.gui.top.add(
{
type = 'sprite-button',
name = 'charging_station',
name = charging_station_name,
sprite = 'item/battery-mk2-equipment',
tooltip = {
'modules.charging_station_tooltip'
@ -73,6 +74,12 @@ local function charge(player)
if not grid or not grid.valid then
return player.print(module_name .. 'No valid armor to charge was found.', Color.warning)
end
local ents = player.surface.find_entities_filtered {name = 'accumulator', force = player.force, position = player.position, radius = 13}
if not ents or not next(ents) then
return player.print(module_name .. 'No accumulators nearby.', Color.warning)
end
local equip = grid.equipment
for _, piece in pairs(equip) do
if piece.valid and piece.generator_power == 0 then
@ -104,7 +111,7 @@ local function on_player_joined_game(event)
BottomFrame.add_inner_frame(
{
player = player,
element_name = 'charging_station',
element_name = charging_station_name,
tooltip = {
'modules.charging_station_tooltip'
},
@ -114,29 +121,24 @@ local function on_player_joined_game(event)
end
end
local function on_gui_click(event)
if not event then
return
end
if not event.element then
return
end
if not event.element.valid then
return
end
if event.element.name == 'charging_station' then
local player = game.players[event.player_index]
Gui.on_click(
charging_station_name,
function(event)
local player = game.get_player(event.player_index)
if not player or not player.valid then
return
end
local is_spamming = SpamProtection.is_spamming(player, nil, 'Charging Station Gui Click')
if is_spamming then
return
end
charge(player)
return
end
end
)
Event.add(defines.events.on_player_joined_game, on_player_joined_game)
Event.add(defines.events.on_gui_click, on_gui_click)
Event.add(
BottomFrame.events.bottom_quickbar_location_changed,

View File

@ -6,7 +6,7 @@ local Collapse = require 'modules.collapse'
local WD = require 'modules.wave_defense.table'
local Discord = require 'utils.discord_handler'
local mapkeeper = '[color=blue]Mapkeeper:[/color]'
local scenario_name = 'Mtn Fortress'
local scenario_name = Public.scenario_name
commands.add_command(
'scenario',

View File

@ -14,6 +14,9 @@ local Diff = require 'modules.difficulty_vote_by_amount'
local format_number = require 'util'.format_number
local RPG_Progression = require 'utils.datastore.rpg_data'
local WD = require 'modules.wave_defense.table'
local scenario_name = Public.scenario_name
local StatData = require 'utils.datastore.statistics'
StatData.add_normalize('coins', 'Coins collected'):set_tooltip('The amount of coins the player has collected through mining/killed enemies.')
local random = math.random
local floor = math.floor
@ -470,8 +473,12 @@ local function give_coin(player)
if coin_amount >= 1 then
if coin_override then
player.insert({name = 'coin', count = coin_override})
StatData.get_data(player):increase('coins', coin_override)
else
---@diagnostic disable-next-line: param-type-mismatch
player.insert({name = 'coin', count = random(1, coin_amount)})
StatData.get_data(player):increase('coins', coin_amount)
end
end
end
@ -1230,7 +1237,7 @@ local function show_mvps(player)
miners_label_text.style.font_color = {r = 0.33, g = 0.66, b = 0.9}
local sent_to_discord = Public.get('sent_to_discord')
local server_name_matches = Server.check_server_name('Mtn Fortress')
local server_name_matches = Server.check_server_name(scenario_name)
if not sent_to_discord and server_name_matches then
local message = {
@ -1704,7 +1711,7 @@ local on_player_or_robot_built_tile = function(event)
if not tiles then
return
end
for k, v in pairs(tiles) do
for _, v in pairs(tiles) do
local old_tile = v.old_tile
if old_tile.name == 'black-refined-concrete' then
surface.set_tiles({{name = 'black-refined-concrete', position = v.position}}, true)

View File

@ -30,6 +30,8 @@ local this = {
magic_fluid_crafters = {index = 1},
art_table = {index = 1},
editor_mode = {},
techs = {},
limit_types = {},
starting_items = {
['pistol'] = {
count = 1
@ -1342,6 +1344,7 @@ function Public.on_player_joined_game(event)
local death_message = ({'main.death_mode_warning'})
Alert.alert_player(player, 15, death_message)
end
player.clear_items_inside()
for item, data in pairs(this.starting_items) do
player.insert({name = item, count = data.count})
end
@ -1793,12 +1796,12 @@ function Public.equip_players(starting_items, recreate)
if player.character and player.character.valid then
player.character.destroy()
end
player.clear_items_inside()
if player.connected then
if not player.character then
player.set_controller({type = defines.controllers.god})
player.create_character()
end
player.clear_items_inside()
Modifiers.update_player_modifiers(player)
if not recreate then
starting_items = starting_items or this.starting_items
@ -1821,6 +1824,8 @@ function Public.reset_func_table()
this.refill_turrets = {index = 1}
this.magic_crafters = {index = 1}
this.magic_fluid_crafters = {index = 1}
this.techs = {}
this.limit_types = {}
this.starting_items = {
['pistol'] = {
count = 1

View File

@ -25,6 +25,22 @@ local function validate_entity(entity)
return true
end
local function get_top_frame(player)
if Gui.get_mod_gui_top_frame() then
return Gui.get_button_flow(player)[main_frame_name]
else
return player.gui.top[main_frame_name]
end
end
local function get_top_frame_custom(player, name)
if Gui.get_mod_gui_top_frame() then
return Gui.get_button_flow(player)[name]
else
return player.gui.top[name]
end
end
local function validate_player(player)
if not player then
return false
@ -45,22 +61,44 @@ local function validate_player(player)
end
local function create_button(player)
local b =
player.gui.top.add(
{
type = 'sprite-button',
name = main_button_name,
sprite = 'item/dummy-steel-axe',
tooltip = 'Shows statistics!',
style = Gui.button_style
}
)
b.style.minimal_height = 38
b.style.maximal_height = 38
if Gui.get_mod_gui_top_frame() then
local b =
Gui.add_mod_button(
player,
{
type = 'sprite-button',
name = main_button_name,
sprite = 'item/dummy-steel-axe',
tooltip = 'Shows statistics!',
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 = 'item/dummy-steel-axe',
tooltip = 'Shows statistics!',
style = Gui.button_style
}
)
b.style.minimal_height = 38
b.style.maximal_height = 38
end
end
local function spectate_button(player)
if player.gui.top[spectate_button_name] then
if get_top_frame_custom(player, spectate_button_name) then
return
end
@ -68,29 +106,68 @@ local function spectate_button(player)
return
end
local b =
player.gui.top.add {
type = 'sprite-button',
name = spectate_button_name,
sprite = 'utility/ghost_time_to_live_modifier_icon',
tooltip = 'Spectate!\nThis will kill your character.',
style = Gui.button_style
}
if Gui.get_mod_gui_top_frame() then
local b =
Gui.add_mod_button(
player,
{
type = 'sprite-button',
name = spectate_button_name,
sprite = 'utility/ghost_time_to_live_modifier_icon',
tooltip = 'Spectate!\nThis will kill your character.',
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 = spectate_button_name,
sprite = 'utility/ghost_time_to_live_modifier_icon',
tooltip = 'Spectate!\nThis will kill your character.',
style = Gui.button_style
}
b.style.maximal_height = 38
b.style.maximal_height = 38
end
end
local function create_main_frame(player)
local label
local line
if player.gui.top['wave_defense'] then
player.gui.top['wave_defense'].visible = true
if get_top_frame_custom(player, 'wave_defense') then
get_top_frame_custom(player, 'wave_defense').visible = true
end
local frame = player.gui.top.add({type = 'frame', name = main_frame_name, style = 'finished_game_subheader_frame'})
frame.location = {x = 1, y = 40}
frame.style.minimal_height = 38
frame.style.maximal_height = 38
local frame
if Gui.get_mod_gui_top_frame() then
frame =
Gui.add_mod_button(
player,
{
type = 'frame',
name = main_frame_name,
style = 'finished_game_subheader_frame'
}
)
frame.location = {x = 1, y = 38}
frame.style.minimal_height = 36
frame.style.maximal_height = 36
else
frame = player.gui.top.add({type = 'frame', name = main_frame_name, style = 'finished_game_subheader_frame'})
frame.location = {x = 1, y = 40}
frame.style.minimal_height = 38
frame.style.maximal_height = 38
end
label = frame.add({type = 'label', caption = ' ', name = 'label'})
label.style.font_color = {r = 0.88, g = 0.88, b = 0.88}
@ -166,17 +243,33 @@ local function create_main_frame(player)
end
local function hide_all_gui(player)
for _, child in pairs(player.gui.top.children) do
if child.name ~= spectate_button_name and child.name ~= 'minimap_button' and child.name ~= 'wave_defense' then
child.visible = false
if Gui.get_mod_gui_top_frame() then
for _, child in pairs(player.gui.top.mod_gui_top_frame.mod_gui_inner_frame.children) do
if child.name ~= spectate_button_name and child.name ~= 'minimap_button' and child.name ~= 'wave_defense' then
child.visible = false
end
end
else
for _, child in pairs(player.gui.top.children) do
if child.name ~= spectate_button_name and child.name ~= 'minimap_button' and child.name ~= 'wave_defense' then
child.visible = false
end
end
end
end
local function show_all_gui(player)
for _, child in pairs(player.gui.top.children) do
if child.name ~= spectate_button_name and child.name ~= 'minimap_button' then
child.visible = true
if Gui.get_mod_gui_top_frame() then
for _, child in pairs(player.gui.top.mod_gui_top_frame.mod_gui_inner_frame.children) do
if child.name ~= spectate_button_name and child.name ~= 'minimap_button' then
child.visible = true
end
end
else
for _, child in pairs(player.gui.top.children) do
if child.name ~= spectate_button_name and child.name ~= 'minimap_button' then
child.visible = true
end
end
end
end
@ -187,11 +280,11 @@ local function on_player_joined_game(event)
return
end
if not player.gui.top[spectate_button_name] then
if not get_top_frame_custom(player, spectate_button_name) then
spectate_button(player)
end
if not player.gui.top[main_button_name] then
if not get_top_frame_custom(player, main_button_name) then
create_button(player)
end
end
@ -209,19 +302,19 @@ local function changed_surface(player)
end
local wagon_surface = icw_locomotive.surface
local main_toggle_button = player.gui.top[main_toggle_button_name]
local info = player.gui.top[main_button_name]
local wd = player.gui.top['wave_defense']
local spectate = player.gui.top[spectate_button_name]
local minimap_button = player.gui.top['minimap_button']
local rpg_b = player.gui.top[rpg_button]
local poll_b = player.gui.top[poll_button]
local main_toggle_button = get_top_frame_custom(player, main_toggle_button_name)
local info = get_top_frame_custom(player, main_button_name)
local wd = get_top_frame_custom(player, 'wave_defense')
local spectate = get_top_frame_custom(player, spectate_button_name)
local minimap_button = get_top_frame_custom(player, 'minimap_button')
local rpg_b = get_top_frame_custom(player, rpg_button)
local poll_b = get_top_frame_custom(player, poll_button)
local rpg_f = player.gui.screen[rpg_frame]
local rpg_s = player.gui.screen[rpg_settings]
local diff = player.gui.top[Difficulty.top_button_name]
local charging = player.gui.top['charging_station']
local diff = get_top_frame_custom(player, Difficulty.top_button_name)
local charging = get_top_frame_custom(player, 'charging_station')
local charging_frame = BottomFrame.get_section(player, 'charging_station')
local frame = player.gui.top[main_frame_name]
local frame = get_top_frame(player)
local spell_gui_frame_name = RPG.spell_gui_frame_name
local spell_cast_buttons = player.gui.screen[spell_gui_frame_name]
@ -326,7 +419,7 @@ local function changed_surface(player)
info.sprite = 'utility/map'
info.visible = true
end
if player.gui.top[main_frame_name] then
if get_top_frame(player) then
if frame then
frame.visible = false
return
@ -391,10 +484,10 @@ local function on_gui_click(event)
end
return
end
if player.gui.top[main_frame_name] then
local info = player.gui.top[main_frame_name]
local wd = player.gui.top['wave_defense']
local diff = player.gui.top[Difficulty.top_button_name]
if get_top_frame(player) then
local info = get_top_frame(player)
local wd = get_top_frame_custom(player, 'wave_defense')
local diff = get_top_frame_custom(player, Difficulty.top_button_name)
if info and info.visible then
if wd then
@ -453,14 +546,14 @@ local function enable_guis(event)
end
local main_toggle_button_name = Gui.main_toggle_button_name
local main_toggle_button = player.gui.top[main_toggle_button_name]
local main_toggle_button = get_top_frame_custom(player, main_toggle_button_name)
local rpg_button = RPG.draw_main_frame_name
local info = player.gui.top[main_button_name]
local wd = player.gui.top['wave_defense']
local spectate = player.gui.top[spectate_button_name]
local rpg_b = player.gui.top[rpg_button]
local diff = player.gui.top[Difficulty.top_button_name]
local charging = player.gui.top['charging_station']
local info = get_top_frame_custom(player, main_button_name)
local wd = get_top_frame_custom(player, 'wave_defense')
local spectate = get_top_frame_custom(player, spectate_button_name)
local rpg_b = get_top_frame_custom(player, rpg_button)
local diff = get_top_frame_custom(player, Difficulty.top_button_name)
local charging = get_top_frame_custom(player, 'charging_station')
local charging_frame = BottomFrame.get_section(player, 'charging_station')
IC_Gui.remove_toolbar(player)
@ -511,14 +604,14 @@ function Public.update_gui(player)
return
end
if not player.gui.top[main_frame_name] then
if not get_top_frame(player) then
return
end
if not player.gui.top[main_frame_name].visible then
if not get_top_frame(player).visible then
return
end
local gui = player.gui.top[main_frame_name]
local gui = get_top_frame(player)
local rpg_extra = RPG.get('rpg_extra')
local mined_scrap = Public.get('mined_scrap')

View File

@ -957,22 +957,42 @@ end
function Public.kill_car_but_save_surface(entity)
if not validate_entity(entity) then
return
return nil
end
local entity_type = IC.get('entity_type')
if not entity_type[entity.type] then
return
return nil
end
local cars = IC.get('cars')
local car = cars[entity.unit_number]
if not car then
return nil
end
local surface_index = car.surface
local surface = game.get_surface(surface_index)
if not surface then
return nil
end
local c = 0
local entities = surface.find_entities_filtered({area = car.area, force = 'player'})
if entities and #entities > 0 then
for _, e in pairs(entities) do
if e and e.valid and e.name ~= 'character' then
c = c + 1
end
end
if c > 0 then
return false, c
end
end
local surfaces = IC.get('surfaces')
local surfaces_deleted_by_button = IC.get('surfaces_deleted_by_button')
local cars = IC.get('cars')
local car = cars[entity.unit_number]
if not car then
return
end
kick_players_out_of_vehicles(car)
kick_players_from_surface(car)
@ -992,7 +1012,7 @@ function Public.kill_car_but_save_surface(entity)
end
if not owner then
return
return nil
end
local renders = IC.get('renders')
@ -1017,9 +1037,6 @@ function Public.kill_car_but_save_surface(entity)
misc_settings[owner.index] = nil
end
local surface_index = car.surface
local surface = game.surfaces[surface_index]
if not surfaces_deleted_by_button[owner.name] then
surfaces_deleted_by_button[owner.name] = {}
end
@ -1030,6 +1047,7 @@ function Public.kill_car_but_save_surface(entity)
car.entity.force.chart(surface, car.area)
surfaces[entity.unit_number] = nil
cars[entity.unit_number] = nil
return true
end
function Public.validate_owner(player, entity)

View File

@ -6,6 +6,7 @@ local Tabs = require 'utils.gui'
local Event = require 'utils.event'
local Task = require 'utils.task_token'
local SpamProtection = require 'utils.spam_protection'
local Discord = require 'utils.discord_handler'
local Public = {}
local insert = table.insert
@ -29,10 +30,19 @@ local allow_anyone_to_enter_name = Gui.uid_name()
local auto_upgrade_name = Gui.uid_name()
local kick_player_name = Gui.uid_name()
local destroy_surface_name = Gui.uid_name()
local scenario_name = 'Mtn Fortress'
local add_toolbar
local remove_toolbar
local function get_top_frame(player)
if Gui.get_mod_gui_top_frame() then
return Gui.get_button_flow(player)[main_toolbar_name]
else
return player.gui.top[main_toolbar_name]
end
end
local function increment(t, k)
t[k] = true
end
@ -448,28 +458,50 @@ end
add_toolbar = function(player, remove)
if remove then
if player.gui.top[main_toolbar_name] then
player.gui.top[main_toolbar_name].destroy()
if get_top_frame(player) then
get_top_frame(player).destroy()
return
end
end
if player.gui.top[main_toolbar_name] then
if get_top_frame(player) then
return
end
local tooltip = ({'ic.control'})
local button =
player.gui.top.add(
{
type = 'sprite-button',
sprite = 'item/spidertron',
name = main_toolbar_name,
tooltip = tooltip,
style = Gui.button_style
}
)
button.style.minimal_height = 38
button.style.maximal_height = 38
if Gui.get_mod_gui_top_frame() then
local b =
Gui.add_mod_button(
player,
{
type = 'sprite-button',
name = main_toolbar_name,
sprite = 'item/spidertron',
tooltip = tooltip,
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 button =
player.gui.top.add(
{
type = 'sprite-button',
sprite = 'item/spidertron',
name = main_toolbar_name,
tooltip = tooltip,
style = Gui.button_style
}
)
button.style.minimal_height = 38
button.style.maximal_height = 38
end
end
remove_toolbar = function(player)
@ -480,8 +512,8 @@ remove_toolbar = function(player)
remove_main_frame(main_frame)
end
if player.gui.top[main_toolbar_name] then
player.gui.top[main_toolbar_name].destroy()
if get_top_frame(player) then
get_top_frame(player).destroy()
return
end
end
@ -811,8 +843,14 @@ Gui.on_click(
local entity = car.entity
if entity and entity.valid then
Functions.kill_car_but_save_surface(entity)
game.print('[IC] ' .. player.name .. ' has destroyed their surface!', Color.warning)
local position = entity.position
local suc, count = Functions.kill_car_but_save_surface(entity)
if suc then
game.print('[IC] ' .. player.name .. ' has destroyed their surface!', Color.warning)
Discord.send_notification_raw(scenario_name, player.name .. ' deleted their vehicle surface at x = ' .. position.x .. ' y = ' .. position.y .. '.')
else
player.print('[IC] Entities are still on the surface. Please remove any entities and retry this operation. Found ' .. count .. ' entities!', Color.warning)
end
end
remove_main_frame(event.element)

View File

@ -24,28 +24,58 @@ local function validate_player(player)
return true
end
local function get_top_frame(player)
if CoreGui.get_mod_gui_top_frame() then
return CoreGui.get_button_flow(player)['minimap_button']
else
return player.gui.top['minimap_button']
end
end
local function create_button(player)
local button =
player.gui.top['minimap_button'] or
player.gui.top.add(
if CoreGui.get_mod_gui_top_frame() then
local b =
CoreGui.add_mod_button(
player,
{
type = 'sprite-button',
name = 'minimap_button',
sprite = 'utility/map',
tooltip = 'Open or close minimap.',
style = CoreGui.button_style
style = Gui.button_style
}
)
button.style.minimal_height = 38
button.style.maximal_height = 38
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 button =
player.gui.top['minimap_button'] or
player.gui.top.add(
{
type = 'sprite-button',
name = 'minimap_button',
sprite = 'utility/map',
tooltip = 'Open or close minimap.',
style = CoreGui.button_style
}
)
button.style.minimal_height = 38
button.style.maximal_height = 38
end
end
function Public.toggle_button(player)
if not player.gui.top['minimap_button'] then
if not get_top_frame(player) then
create_button(player)
end
local button = player.gui.top['minimap_button']
local button = get_top_frame(player)
if Functions.get_player_surface(player) then
create_button(player)
else
@ -175,7 +205,7 @@ function Public.minimap(player, surface, position)
end
function Public.update_minimap()
for k, player in pairs(game.connected_players) do
for _, player in pairs(game.connected_players) do
local player_data = get_player_data(player)
if Functions.get_player_surface(player) and player.gui.left.minimap_toggle_frame and player_data.auto then
kill_frame(player)

View File

@ -429,6 +429,27 @@ local function get_items()
upgrade = false,
static = false
}
if upgrades.burner_generator.bought >= upgrades.burner_generator.limit then
main_market_items['burner-generator'] = {
stack = 1,
value = 'coin',
price = 300,
tooltip = ({'main_market.sold_out'}),
enabled = false,
upgrade = false,
static = true
}
else
main_market_items['burner-generator'] = {
stack = 1,
value = 'coin',
price = 300,
tooltip = {'', {'item-name.burner-generator'}, ' bought: ', upgrades.burner_generator.bought, '/', upgrades.burner_generator.limit},
upgrade = false,
static = true
}
end
main_market_items['car'] = {
stack = 1,
value = 'coin',
@ -974,6 +995,31 @@ local function gui_click(event)
LinkedChests.set('converted_chests', converted_chests + 1)
end
if name == 'burner-generator' then
if this.upgrades.burner_generator.bought >= this.upgrades.burner_generator.limit then
redraw_market_items(data.item_frame, player, data.search_text)
player.print(({'locomotive.limit_reached'}), {r = 0.98, g = 0.66, b = 0.22})
return
end
player.remove_item({name = item.value, count = item.price})
player.insert({name = name, count = item.stack})
Event.raise(Public.events.on_market_item_purchased, {cost = item.price})
if item.stack > this.upgrades.burner_generator.limit then
item.stack = this.upgrades.burner_generator.limit
end
this.upgrades.burner_generator.bought = this.upgrades.burner_generator.bought + item.stack
this.upgrades.train_upgrade_contribution = this.upgrades.train_upgrade_contribution + item.price
redraw_market_items(data.item_frame, player, data.search_text)
redraw_coins_left(data.coins_left, player)
return
end
if name == 'upgrade_pickaxe' then
player.remove_item({name = item.value, count = item.price})

View File

@ -195,11 +195,17 @@ function Public.locomotive_spawn(surface, position, reversed)
)
end
local s = 'entity/compilatron'
if random(1, 10) == 1 then
s = 'entity/character-corpse'
end
for y = -1, 0, 0.05 do
local scale = random(50, 100) * 0.01
rendering.draw_sprite(
{
sprite = 'entity/small-biter',
sprite = s,
orientation = random(0, 100) * 0.01,
x_scale = scale,
y_scale = scale,
@ -229,6 +235,8 @@ function Public.locomotive_spawn(surface, position, reversed)
surface = locomotive.surface
}
log(serpent.block(extra_wagons))
if extra_wagons and extra_wagons > 0 then
local inc = 7

View File

@ -46,6 +46,7 @@ local Beam = require 'modules.render_beam'
-- Use these settings for live
local send_ping_to_channel = Discord.channel_names.mtn_channel
local role_to_mention = Discord.role_mentions.mtn_fortress
local scenario_name = Public.scenario_name
-- Use these settings for testing
-- bot-lounge
-- local send_ping_to_channel = Discord.channel_names.bot_quarters
@ -107,7 +108,7 @@ end
local announce_new_map =
Task.register(
function()
local server_name = Server.check_server_name('Mtn Fortress')
local server_name = Server.check_server_name(scenario_name)
if server_name then
Server.to_discord_named_raw(send_ping_to_channel, role_to_mention .. ' ** Mtn Fortress was just reset! **')
end
@ -115,9 +116,12 @@ local announce_new_map =
)
function Public.reset_map()
log(serpent.block('resetting map'))
game.forces.player.reset()
Public.reset_main_table()
Difficulty.show_gui(false)
local this = Public.get()
local wave_defense_table = WD.get_table()
Misc.reset()
@ -199,6 +203,7 @@ function Public.reset_map()
local players = game.connected_players
for i = 1, #players do
local player = players[i]
Difficulty.clear_top_frame(player)
Score.init_player_table(player, true)
Misc.insert_all_items(player)
Modifiers.reset_player_modifiers(player)
@ -211,7 +216,6 @@ function Public.reset_map()
Difficulty.reset_difficulty_poll({closing_timeout = game.tick + 36000})
Difficulty.set_gui_width(20)
Difficulty.show_gui(false)
Collapse.set_kill_entities(false)
Collapse.set_kill_specific_entities(collapse_kill)
@ -224,6 +228,12 @@ function Public.reset_map()
Collapse.start_now(false)
Collapse.disable_collapse(false)
Public.stateful.enable(true)
Public.stateful.create()
Public.stateful.reset_stateful(true, true)
Public.stateful.increase_enemy_damage_and_health()
Public.stateful.apply_startup_settings()
this.locomotive_health = 10000
this.locomotive_max_health = 10000
@ -298,11 +308,6 @@ function Public.reset_map()
this.game_lost = false
RPG.reset_table()
Public.stateful.enable(true)
Public.stateful.create()
Public.stateful.reset_stateful(true, true)
Public.stateful.increase_enemy_damage_and_health()
Public.stateful.apply_startup_settings()
RPG.rpg_reset_all_players()
RPG.set_surface_name({game.surfaces[this.active_surface_index].name, 'boss_room'})

View File

@ -4,6 +4,7 @@ local Public = require 'maps.mountain_fortress_v3.table'
local RPG = require 'modules.rpg.main'
local Alert = require 'utils.alert'
local Task = require 'utils.task_token'
local StatData = require 'utils.datastore.statistics'
local shuffle = table.shuffle_table
local random = math.random
@ -330,6 +331,7 @@ local mc_random_rewards = {
if player and player.valid then
if player.can_insert({name = 'coin', count = rng}) then
player.insert({name = 'coin', count = rng})
StatData.get_data(player):increase('coins', rng)
end
end
end

View File

@ -12,6 +12,7 @@ local score_key = 'mtn_v3'
local score_key_dev = 'mtn_v3_dev'
local set_data = Server.set_data
local try_get_data = Server.try_get_data
local scenario_name = Public.scenario_name
local insert = table.insert
@ -87,7 +88,7 @@ function Public.get_season_scores()
if not secs then
return
else
local server_name_matches = Server.check_server_name('Mtn Fortress')
local server_name_matches = Server.check_server_name(scenario_name)
if server_name_matches then
try_get_data(score_dataset, score_key, get_scores)
else
@ -102,7 +103,7 @@ function Public.set_season_scores()
if not secs then
return
else
local server_name_matches = Server.check_server_name('Mtn Fortress')
local server_name_matches = Server.check_server_name(scenario_name)
if server_name_matches then
write_additional_stats(score_key)
else

View File

@ -19,8 +19,12 @@ 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)
@ -68,11 +72,17 @@ local spread_particles_token =
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('Mtn Fortress')
local server_name_matches = Server.check_server_name(scenario_name)
local stateful = Public.get_stateful()
@ -182,18 +192,62 @@ local warn_player_sound_token =
)
local function create_button(player)
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
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()
@ -321,6 +375,111 @@ local function objective_frames(stateful, player_frame, objective, data)
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
@ -480,7 +639,11 @@ main_frame = function(player)
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'}}
frame.location = {x = 1, y = 45}
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
@ -533,31 +696,7 @@ main_frame = function(player)
buff_right_flow.style.horizontal_align = 'right'
buff_right_flow.style.horizontally_stretchable = true
local buffs = ''
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 .. Stateful.buff_to_string[name] .. ': ' .. buff_data.count
else
buffs = buffs .. Stateful.buff_to_string[name] .. ': ' .. (buff_data.count * 100) .. '%'
end
buffs = buffs .. '\n'
end
end
end
buff_right_flow.add({type = 'label', caption = '[img=utility/center]', tooltip = buffs})
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
@ -874,6 +1013,7 @@ local function update_raw()
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(10, 'Objective: **breach zone** has been complete!')
Server.to_discord_embed('Objective: **breach zone** has been complete!')
@ -886,6 +1026,8 @@ local function update_raw()
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(10, 'Objective: **survive until wave** has been complete!')
Server.to_discord_embed('Objective: **survive until wave** has been complete!')
@ -911,6 +1053,7 @@ local function update_raw()
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(10, '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()
@ -937,6 +1080,7 @@ local function update_raw()
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(10, 'Objective: **produce an item multiple times** has been completed!')
Server.to_discord_embed('Objective: **produce an item multiple times** has been completed!')
@ -1042,6 +1186,7 @@ local function update_raw()
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(10, 'Objective: **' .. objective_name .. '** has been completed!')
Server.to_discord_embed('Objective: **' .. objective_name .. '** has been completed!')
play_achievement_unlocked()
@ -1189,6 +1334,71 @@ Gui.on_click(
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)

View File

@ -2,6 +2,7 @@ local Public = require 'maps.mountain_fortress_v3.stateful.table'
local Event = require 'utils.event'
local WD = require 'modules.wave_defense.table'
local Beam = require 'modules.render_beam'
local RPG = require 'modules.rpg.main'
Public.stateful_gui = require 'maps.mountain_fortress_v3.stateful.gui'
Public.stateful_terrain = require 'maps.mountain_fortress_v3.stateful.terrain'
@ -82,6 +83,123 @@ Event.on_nth_tick(
end
)
Event.add(
defines.events.on_player_crafted_item,
function(event)
local player = game.get_player(event.player_index)
if not player or not player.valid then
return
end
local item = event.item_stack
if not item or not item.valid_for_read then
return
end
local objectives = Public.get_stateful('objectives')
local handcrafted_items_any = objectives.handcrafted_items_any
if handcrafted_items_any then
handcrafted_items_any.actual = handcrafted_items_any.actual + item.count
end
local handcrafted_items = objectives.handcrafted_items
if handcrafted_items then
if item.name ~= handcrafted_items.name then
return
end
handcrafted_items.actual = handcrafted_items.actual + item.count
end
end
)
Event.add(
defines.events.on_entity_died,
function(event)
local entity = event.entity
if not entity or not entity.valid then
return
end
if not Public.valid_enemy_forces[entity.force.name] then
return
end
local objectives = Public.get_stateful('objectives')
local damage_type = event.damage_type
if not damage_type then
return
end
local killed_enemies = objectives.killed_enemies_type
if not killed_enemies then
return
end
if killed_enemies.damage_type ~= damage_type.name then
return
end
if entity.type == 'unit' then
killed_enemies.actual = killed_enemies.actual + 1
end
end
)
Event.add(
defines.events.on_rocket_launched,
function(event)
local rocket_inventory = event.rocket.get_inventory(defines.inventory.rocket)
local slot = rocket_inventory[1]
if slot and slot.valid and slot.valid_for_read then
local objectives = Public.get_stateful('objectives')
local launch_item = objectives.launch_item
if launch_item then
if slot.name ~= launch_item.name then
return
end
launch_item.actual = launch_item.actual + slot.count
end
end
end
)
Event.add(
RPG.events.on_spell_cast_success,
function(event)
local player = game.get_player(event.player_index)
if not player or not player.valid then
return
end
local spell_name = event.spell_name
local amount = event.amount
if not player.character or not player.character.valid then
return
end
local objectives = Public.get_stateful('objectives')
local cast_spell_any = objectives.cast_spell_any
if cast_spell_any then
cast_spell_any.actual = cast_spell_any.actual + amount
end
local cast_spell = objectives.cast_spell
if cast_spell then
if spell_name ~= cast_spell.name then
return
end
cast_spell.actual = cast_spell.actual + amount
end
end
)
Event.on_nth_tick(
14400,
function()

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,9 @@ Public.events = {
on_market_item_purchased = Event.generate_event_name('on_market_item_purchased')
}
local scenario_name = 'Mtn Fortress'
Public.scenario_name = scenario_name
Global.register(
this,
function(tbl)
@ -134,6 +137,7 @@ function Public.reset_main_table()
this.locomotive_health = 10000
this.locomotive_max_health = 10000
this.extra_wagons = 0
this.all_the_fish = false
this.gap_between_zones = {
set = false,
gap = 900,
@ -177,6 +181,10 @@ function Public.reset_main_table()
this.robotics_deployed = false
this.upgrades = {
showed_text = false,
burner_generator = {
limit = 50,
bought = 0
},
landmine = {
limit = 25,
bought = 0,
@ -375,7 +383,7 @@ function Public.remove(key, sub_key)
end
function Public.save_stateful_settings()
local server_name_matches = Server.check_server_name('Mtn Fortress')
local server_name_matches = Server.check_server_name(scenario_name)
if server_name_matches then
Server.set_data(dataset, dataset_key, stateful_settings)
@ -387,7 +395,7 @@ end
local apply_settings_token =
Task.register(
function(data)
local server_name_matches = Server.check_server_name('Mtn Fortress')
local server_name_matches = Server.check_server_name(scenario_name)
local settings = data and data.value or nil
if not settings then
@ -412,7 +420,7 @@ Event.add(
local start_data = Server.get_start_data()
if not start_data.initialized then
local server_name_matches = Server.check_server_name('Mtn Fortress')
local server_name_matches = Server.check_server_name(scenario_name)
if server_name_matches then
Server.try_get_data(dataset, dataset_key, apply_settings_token)

View File

@ -79,6 +79,13 @@ local function clear_main_frame(player)
end
end
local function clear_top_frame(player)
local top = player.gui.top
if top[top_button_name] and top[top_button_name].valid then
top[top_button_name].destroy()
end
end
function Public.difficulty_gui()
if not this.show_gui then
return
@ -459,5 +466,7 @@ Event.add(defines.events.on_player_joined_game, on_player_joined_game)
Event.add(defines.events.on_player_left_game, on_player_left_game)
Public.top_button_name = top_button_name
Public.clear_main_frame = clear_main_frame
Public.clear_top_frame = clear_top_frame
return Public

View File

@ -739,6 +739,114 @@ function Public.aoe_punch(cause, entity, damage, final_damage_amount)
end
end
function Public.add_tidal_wave(cause, ent_position, shape, length, max_spread)
local rpg_extra = Public.get('rpg_extra')
if not cause or not cause.valid then
return
end
local wave = {
cause = cause,
start_position = cause.position,
direction = {ent_position.x - cause.position.x, ent_position.y - cause.position.y},
length = length or 24,
base_spread = 0.5,
max_spread = max_spread or 5,
shape = shape or false,
tick = 0
}
local vector_length = math.sqrt(wave.direction[1] ^ 2 + wave.direction[2] ^ 2)
wave.direction = {wave.direction[1] / vector_length, wave.direction[2] / vector_length}
rpg_extra.tidal_waves = rpg_extra.tidal_waves or {}
rpg_extra.tidal_waves[#rpg_extra.tidal_waves + 1] = wave
end
--Melee damage modifier
function Public.update_tidal_wave()
local rpg_extra = Public.get('rpg_extra')
if not rpg_extra.tidal_waves or not next(rpg_extra.tidal_waves) then
return
end
for id, wave in pairs(rpg_extra.tidal_waves) do
if not wave then
break
end
local cone = wave.shape and wave.shape == 'cone' or false
local wave_player = wave.cause
if not wave_player or not wave_player.valid then
rpg_extra.tidal_waves[id] = nil
return
end
if wave.tick < wave.length then
local surface = wave.cause.surface
local cause_position = wave.start_position
local i = wave.tick + 1
local current_spread = wave.base_spread + (wave.max_spread - wave.base_spread) * (i / wave.length)
if not cone then
for j = -wave.max_spread, wave.max_spread do
local offset_x = cause_position.x + wave.direction[1] * i + j * wave.direction[2]
local offset_y = cause_position.y + wave.direction[2] * i - j * wave.direction[1]
local position = {offset_x, offset_y}
local next_offset_x = cause_position.x + wave.direction[1] * (i + 1) + j * wave.direction[2]
local next_offset_y = cause_position.y + wave.direction[2] * (i + 1) - j * wave.direction[1]
local next_position = {next_offset_x, next_offset_y}
surface.create_entity({name = 'water-splash', position = position})
-- surface.create_trivial_smoke({name = 'poison-capsule-smoke', position = position})
local sound = 'utility/build_small'
wave_player.play_sound {path = sound, volume_modifier = 1}
for _, entity in pairs(surface.find_entities({{position[1] - 1, position[2] - 1}, {position[1] + 1, position[2] + 1}})) do
if entity.valid and entity.name ~= 'character' and entity.destructible and entity.type == 'unit' and entity.force.index ~= 3 then
local new_pos = surface.find_non_colliding_position('character', next_position, 3, 0.5)
if new_pos then
entity.teleport(new_pos)
end
end
end
end
else
for j = -current_spread, current_spread, wave.base_spread do
local offset_x = cause_position.x + wave.direction[1] * i + j * wave.direction[2]
local offset_y = cause_position.y + wave.direction[2] * i - j * wave.direction[1]
local position = {offset_x, offset_y}
local next_offset_x = cause_position.x + wave.direction[1] * (i + 1) + j * wave.direction[2]
local next_offset_y = cause_position.y + wave.direction[2] * (i + 1) - j * wave.direction[1]
local next_position = {next_offset_x, next_offset_y}
-- surface.create_trivial_smoke({name = 'poison-capsule-smoke', position = position})
surface.create_entity({name = 'water-splash', position = position})
local sound = 'utility/build_small'
wave_player.play_sound {path = sound, volume_modifier = 1}
for _, entity in pairs(surface.find_entities({{position[1] - 1, position[2] - 1}, {position[1] + 1, position[2] + 1}})) do
if entity.valid and entity.name ~= 'character' and entity.destructible and entity.type == 'unit' and entity.force.index ~= 3 then
local new_pos = surface.find_non_colliding_position('character', next_position, 3, 0.5)
if new_pos then
entity.teleport(new_pos)
end
end
end
end
end
wave.tick = wave.tick + 1
else
rpg_extra.tidal_waves[id] = nil
end
end
end
function Public.level_limit_exceeded(player, value)
local rpg_extra = Public.get('rpg_extra')
local rpg_t = Public.get_value_from_player(player.index)
@ -1219,6 +1327,7 @@ function Public.rpg_reset_player(player, one_time_reset)
dropdown_select_index_3 = 1,
dropdown_select_name_3 = Public.all_spells[1].name[1],
allocate_index = 1,
amount = 0,
explosive_bullets = false,
enable_entity_spawn = false,
health_bar = rpg_t.health_bar,
@ -1268,6 +1377,7 @@ function Public.rpg_reset_player(player, one_time_reset)
dropdown_select_index_3 = 1,
dropdown_select_name_3 = Public.all_spells[1].name[1],
allocate_index = 1,
amount = 0,
explosive_bullets = false,
enable_entity_spawn = false,
points_left = 0,

View File

@ -49,11 +49,10 @@ function Public.draw_gui_char_button(player)
if b then
b.style.font_color = {165, 165, 165}
b.style.font = 'heading-3'
b.style.minimal_height = 38
b.style.maximal_height = 38
b.style.minimal_width = 50
b.style.padding = 0
b.style.margin = 0
b.style.minimal_height = 36
b.style.maximal_height = 36
b.style.minimal_width = 40
b.style.padding = -2
end
else
if player.gui.top[draw_main_frame_name] then
@ -218,7 +217,7 @@ local function draw_main_frame(player, location)
main_frame.location = location
else
if ComfyGui.get_mod_gui_top_frame() then
main_frame.location = {x = 1, y = 55}
main_frame.location = {x = 0, y = 67}
else
main_frame.location = {x = 1, y = 45}
end
@ -543,6 +542,7 @@ Gui.on_click(
if is_spamming then
return
end
local player = event.player
if not player or not player.valid or not player.character then
return

View File

@ -6,10 +6,12 @@ local AntiGrief = require 'utils.antigrief'
local SpamProtection = require 'utils.spam_protection'
local BiterHealthBooster = require 'modules.biter_health_booster_v2'
local Explosives = require 'modules.explosives'
local StatData = require 'utils.datastore.statistics'
local WD = require 'modules.wave_defense.table'
local Math2D = require 'math2d'
StatData.add_normalize('spells', 'Spells casted')
--RPG Settings
local enemy_types = Public.enemy_types
local die_cause = Public.die_cause
@ -1009,11 +1011,21 @@ local function on_player_used_capsule(event)
cast_spell = Public.cast_spell
}
rpg_t.amount = 0
local cast_spell = spell.callback(data, funcs)
if not cast_spell then
return
end
if rpg_t.amount == 0 then
rpg_t.amount = 1
end
Event.raise(Public.events.on_spell_cast_success, {player_index = player.index, spell_name = spell.entityName, amount = rpg_t.amount})
StatData.get_data(player):increase('spells')
if spell.enforce_cooldown then
Public.register_cooldown_for_player(player, spell)
end
@ -1078,6 +1090,7 @@ Event.add(defines.events.on_player_used_capsule, on_player_used_capsule)
Event.add(defines.events.on_player_changed_surface, on_player_changed_surface)
Event.add(defines.events.on_player_removed, on_player_removed)
Event.on_nth_tick(10, tick)
Event.on_nth_tick(2, Public.update_tidal_wave)
Event.add(
defines.events.on_gui_closed,

View File

@ -1,6 +1,5 @@
local Public = require 'modules.rpg.table'
local Token = require 'utils.token'
local Task = require 'utils.task'
local Task = require 'utils.task_token'
local Ai = require 'modules.ai'
local Modifiers = require 'utils.player_modifiers'
@ -14,9 +13,10 @@ local states = {
}
local restore_movement_speed_token
local repeat_sound_token
local repair_buildings =
Token.register(
Task.register(
function(data)
local entity = data.entity
if entity and entity.valid then
@ -34,6 +34,52 @@ local repair_buildings =
end
)
repeat_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 sound = event.sound or 'utility/armor_insert'
local spell_active = Public.get_value_from_player(player_index, 'has_custom_spell_active')
if spell_active then
player.play_sound {path = sound, volume_modifier = 1}
if player.character ~= nil then
player.character.surface.create_entity({name = 'water-splash', position = player.position})
end
Task.set_timeout_in_ticks(30, repeat_sound_token, event)
else
player.play_sound {path = sound, volume_modifier = 1}
return
end
end
)
local x_marks_the_spot_token =
Task.register(
function(event)
local player_index = event.player_index
local old_surface_index = event.old_surface_index
local player = game.get_player(player_index)
if not player or not player.valid then
return
end
local old_position = event.old_position
if not old_position then
return
end
player.teleport(old_position, old_surface_index)
Public.set_active_spell_disabled(player_index)
Task.set_timeout_in_ticks(5, repeat_sound_token, {player_index = player.index, sound = 'utility/new_objective'})
end
)
local function get_area(pos, dist)
local area = {
left_top = {
@ -48,6 +94,33 @@ local function get_area(pos, dist)
return area
end
local levels = {
[150] = {length = 26, max_spread = 6},
[200] = {length = 27, max_spread = 6},
[250] = {length = 28, max_spread = 7},
[300] = {length = 29, max_spread = 7},
[350] = {length = 30, max_spread = 8},
[400] = {length = 31, max_spread = 8}
}
local function get_level_data(player_level)
local closest_level = nil
for level, _ in pairs(levels) do
if player_level >= level then
closest_level = level
else
break
end
end
if closest_level then
return levels[closest_level]
else
return {length = 26, max_spread = 6}
end
end
local function area_of_effect(player, position, state, radius, callback, find_entities)
if not radius then
return
@ -87,7 +160,7 @@ local function area_of_effect(player, position, state, radius, callback, find_en
end
restore_movement_speed_token =
Token.register(
Task.register(
function(event)
local player_index = event.player_index
local rpg_t = event.rpg_t
@ -152,6 +225,7 @@ local function create_projectiles(data)
}
do_projectile(surface, projectile_types[self.entityName].name, position, force, target_pos, range)
Public.remove_mana(player, self.mana_cost)
rpg_t.amount = rpg_t.amount + 1
if self.damage then
for _, e in pairs(surface.find_entities_filtered({area = damage_area})) do
damage_entity(e)
@ -222,6 +296,7 @@ local function create_entity(data)
has_cast = true
e.direction = player.character.direction
Public.remove_mana(player, self.mana_cost)
rpg_t.amount = rpg_t.amount + 1
end
end
end
@ -259,6 +334,7 @@ local function insert_onto(data)
player.insert({name = self.entityName, count = self.amount})
Public.remove_mana(player, self.mana_cost)
rpg_t.amount = rpg_t.amount + 1
end
else
player.insert({name = self.entityName, count = self.amount})
@ -1064,6 +1140,82 @@ spells[#spells + 1] = {
return true
end
}
spells[#spells + 1] = {
name = {'spells.mark_spot'},
entityName = 'mark-spot',
target = true,
force = 'player',
level = 60,
type = 'special',
mana_cost = 340,
cooldown = 2000,
enforce_cooldown = true,
check_if_active = true,
enabled = true,
log_spell = true,
sprite = 'virtual-signal/signal-X',
special_sprite = 'virtual-signal=signal-X',
tooltip = 'Warps you back to the locomotive and after a couple of seconds you return to your previous location.',
callback = function(data)
local player = data.player
local surface = data.surface
local old_position = player.position
local rpg_t = data.rpg_t
rpg_t.has_custom_spell_active = true
local pos = surface.find_non_colliding_position('character', game.forces.player.get_spawn_position(surface), 3, 0, 5)
if pos then
player.teleport(pos, surface)
else
pos = game.forces.player.get_spawn_position(surface)
player.teleport(pos, surface)
end
Task.set_timeout_in_ticks(5, repeat_sound_token, {player_index = player.index})
Task.set_timeout_in_ticks(300, x_marks_the_spot_token, {player_index = player.index, old_position = old_position, old_surface_index = surface.index})
Public.remove_mana(player, 340)
Public.cast_spell(player)
return true
end
}
spells[#spells + 1] = {
name = {'spells.tidal_wave'},
entityName = 'tidal-wave',
target = true,
force = 'player',
level = 100,
type = 'special',
mana_cost = 340,
cooldown = 1000,
enforce_cooldown = true,
check_if_active = false,
enabled = true,
log_spell = false,
sprite = 'virtual-signal/signal-T',
special_sprite = 'virtual-signal=signal-T',
tooltip = 'Spawns a tidal wave that pushes the enemies back.',
callback = function(data)
local player = data.player
local rpg_t = data.rpg_t
local cursor_position = data.position
local shape = 'cone'
if random(1, 2) == 1 then
shape = 'square'
end
local level_data = get_level_data(rpg_t.level)
Public.add_tidal_wave(player, cursor_position, shape, level_data.length, level_data.max_spread)
Public.remove_mana(player, 340)
Public.cast_spell(player)
return true
end
}
spells[#spells + 1] = {
name = {'spells.charge'},
entityName = 'haste',

View File

@ -34,6 +34,10 @@ Global.register(
)
local Public = {}
Public.events = {
on_spell_cast_success = Event.generate_event_name('on_spell_cast_success'),
on_spell_cast_failure = Event.generate_event_name('on_spell_cast_failure')
}
Public.points_per_level = 5
@ -195,6 +199,22 @@ function Public.set_value_to_player(key, value, setter)
end
end
--- Sets set_active_spell_enabled as enabled.
---@param player_index string
function Public.set_active_spell_enabled(player_index)
if (this.rpg_t[player_index]) then
this.rpg_t[player_index].has_custom_spell_active = true
end
end
--- Sets set_active_spell_disabled as nil.
---@param player_index string
function Public.set_active_spell_disabled(player_index)
if this.rpg_t[player_index] then
this.rpg_t[player_index].has_custom_spell_active = false
end
end
--- Sets a new table to rpg_t table
---@param key string
---@param tbl table

View File

@ -1,11 +1,35 @@
local Public = require 'modules.wave_defense.table'
local Gui = require 'utils.gui'
local BiterHealthBooster = require 'modules.biter_health_booster_v2'
local floor = math.floor
local function get_top_frame_custom(player, name)
if Gui.get_mod_gui_top_frame() then
return Gui.get_button_flow(player)[name]
else
return player.gui.top[name]
end
end
local function create_gui(player)
local frame = player.gui.top.add({type = 'frame', name = 'wave_defense', style = 'finished_game_subheader_frame'})
frame.style.maximal_height = 38
local frame
if Gui.get_mod_gui_top_frame() then
frame =
Gui.add_mod_button(
player,
{
type = 'frame',
name = 'wave_defense',
style = 'finished_game_subheader_frame'
}
)
frame.style.maximal_height = 36
else
frame = player.gui.top.add({type = 'frame', name = 'wave_defense', style = 'finished_game_subheader_frame'})
frame.style.maximal_height = 38
end
local label = frame.add({type = 'label', caption = ' ', name = 'label'})
label.style.font_color = {r = 0.88, g = 0.88, b = 0.88}
@ -19,7 +43,7 @@ local function create_gui(player)
wave_number_label.style.font_color = {r = 0.33, g = 0.66, b = 0.9}
local progressbar = frame.add({type = 'progressbar', name = 'progressbar', value = 0})
progressbar.style = 'achievement_progressbar'
progressbar.style = 'achievement_progressbar' ---@class LuaGuiElementStyle
progressbar.style.minimal_width = 96
progressbar.style.maximal_width = 96
progressbar.style.padding = -1
@ -61,16 +85,16 @@ end
function Public.update_gui(player)
local final_battle = Public.get('final_battle')
if final_battle then
if player.gui.top.wave_defense and player.gui.top.wave_defense.valid then
player.gui.top.wave_defense.destroy()
if get_top_frame_custom(player, 'wave_defense') and get_top_frame_custom(player, 'wave_defense').valid then
get_top_frame_custom(player, 'wave_defense').destroy()
end
return
end
if not player.gui.top.wave_defense then
if not get_top_frame_custom(player, 'wave_defense') then
create_gui(player)
end
local gui = player.gui.top.wave_defense
local gui = get_top_frame_custom(player, 'wave_defense')
local biter_health_boost = 1
local biter_health_boosts = BiterHealthBooster.get('biter_health_boost')
@ -93,6 +117,7 @@ function Public.update_gui(player)
gui.wave_number.caption = wave_number
if wave_number == 0 then
gui.label.caption = {'wave_defense.gui_1'}
gui.label.tooltip = 'Next pause will occur in: ' .. floor((Public.get('next_pause_interval') - game.tick) / 60 / 60) + 1 .. ' minute(s)'
gui.wave_number.caption = floor((next_wave - game.tick) / 60) + 1 .. 's'
end
local interval = next_wave - last_wave
@ -105,11 +130,13 @@ function Public.update_gui(player)
gui.progressbar.value = value
else
gui.label.caption = {'wave_defense.gui_4'}
gui.label.tooltip = 'Wave: ' .. wave_number
local pause_for = floor((paused_waves_for - game.tick) / 60) + 1
if pause_for < 0 then
pause_for = 0
end
gui.wave_number.caption = pause_for .. 's'
gui.wave_number.tooltip = 'Wave: ' .. wave_number
local interval = paused_waves_for - last_pause
local value = 1 - (paused_waves_for - game.tick) / interval
@ -124,6 +151,7 @@ function Public.update_gui(player)
gui.threat.caption = {'wave_defense.gui_3'}
gui.threat.tooltip = {'wave_defense.tooltip_1', biter_health_boost * 100, max_active_biters}
---@diagnostic disable-next-line: param-type-mismatch
gui.threat_value.caption = floor(threat)
gui.threat_value.tooltip = {
'wave_defense.tooltip_1',

View File

@ -259,6 +259,8 @@ Event.on_nth_tick(
return
end
Public.set('next_pause_interval', game.tick + 216000)
local pause_without_votes = Public.get('pause_without_votes')
if pause_without_votes then
Public.toggle_pause_wave_without_votes()

View File

@ -70,6 +70,7 @@ function Public.reset_wave_defense()
this.paused = false
this.pause_without_votes = true
this.pause_wave_in_ticks = 18000 -- 5 minutes
this.next_pause_interval = game.tick + 216000 -- 1 hour
this.game_lost = false
this.get_random_close_spawner_attempts = 5
this.group_size = 2

View File

@ -10,6 +10,11 @@ local function on_console_command(event)
local commands = {
['editor'] = true,
['open'] = true,
['cheat'] = true,
['permissions'] = true,
['banlist'] = true,
['config'] = true,
['command'] = true,
['silent-command'] = true,
['sc'] = true,

View File

@ -7,6 +7,7 @@ local Global = require 'utils.global'
local BottomFrame = require 'utils.gui.bottom_frame'
local Gui = require 'utils.gui'
local SpamProtection = require 'utils.spam_protection'
local Discord = require 'utils.discord_handler'
local this = {
players = {},
@ -163,6 +164,62 @@ commands.add_command(
end
)
commands.add_command(
'repair',
'Revives all ghost entities.',
function(cmd)
local player = game.player
local param = tonumber(cmd.parameter)
if not (player and player.valid) then
return
end
local p = player.print
if not player.admin then
p("[ERROR] You're not admin!", Color.fail)
return
end
if param == nil then
player.print('[ERROR] Must specify radius!', Color.fail)
return
end
if param > 50 then
player.print('[ERROR] Value is too big.', Color.fail)
return
end
if not this.revive_warning then
this.revive_warning = true
player.print('[WARNING] This command will revive all the ghost entities in the given radius, run this command again if you really want to do this!', Color.yellow)
return
end
local radius = {{x = (player.position.x + -param), y = (player.position.y + -param)}, {x = (player.position.x + param), y = (player.position.y + param)}}
local c = 0
for _, v in pairs(player.surface.find_entities_filtered {type = 'entity-ghost', area = radius}) do
if v and v.valid then
c = c + 1
v.silent_revive()
end
end
if c == 0 then
player.print('No entities to repair were found!', Color.warning)
this.revive_warning = nil
return
end
Discord.send_notification_raw(nil, player.name .. ' repaired ' .. c .. ' entities!')
player.play_sound {path = 'utility/new_objective', volume_modifier = 1}
player.print('Repaired ' .. c .. ' entities!', Color.success)
this.revive_warning = nil
end
)
commands.add_command(
'dump_layout',
'Dump the current map-layout.',

12
utils/datastore/init.lua Normal file
View File

@ -0,0 +1,12 @@
require 'utils.datastore.server_ups_data'
require 'utils.datastore.current_time_data'
require 'utils.datastore.color_data'
require 'utils.datastore.session_data'
require 'utils.datastore.statistics'
require 'utils.datastore.jail_data'
require 'utils.datastore.quickbar_data'
require 'utils.datastore.warning_on_join_data'
require 'utils.datastore.message_on_join_data'
require 'utils.datastore.player_tag_data'
require 'utils.datastore.supporters'
require 'utils.datastore.banhandler'

View File

@ -9,6 +9,9 @@ local Event = require 'utils.event'
local Utils = require 'utils.core'
local table = require 'utils.table'
local Gui = require 'utils.gui'
local StatData = require 'utils.datastore.statistics'
StatData.add_normalize('jailed', 'Jailed')
local module_name = '[Jail handler] '
@ -72,7 +75,12 @@ Global.register(
end
)
local Public = {}
local Public = {
events = {
on_player_jailed = Event.generate_event_name('on_player_jailed'),
on_player_unjailed = Event.generate_event_name('on_player_unjailed')
}
}
local function validate_entity(entity)
if not (entity and entity.valid) then
@ -641,6 +649,10 @@ local function jail(player, offender, msg, raised, mute)
set_data(jailed_data_set, offender, {jailed = true, actor = player, reason = msg, date = date})
end
Event.raise(Public.events.on_player_jailed, {player_index = offender.index})
StatData.get_data(player):increase('jailed')
Utils.print_to(nil, message)
local data = Server.build_embed_data()
data.username = offender
@ -703,6 +715,10 @@ local function jail_temporary(player, offender, msg, mute)
votejail[offender.name].jailed = true
end
Event.raise(Public.events.on_player_jailed, {player_index = offender.index})
StatData.get_data(player):increase('jailed')
offender.clear_console()
draw_notice_frame(offender)
@ -728,6 +744,8 @@ local function free(player, offender)
set_data(jailed_data_set, offender, nil)
Event.raise(Public.events.on_player_unjailed, {player_index = offender.index})
Utils.print_to(nil, message)
local data = Server.build_embed_data()
data.username = offender

View File

@ -18,6 +18,14 @@ local this = {
logistics = {}
}
local ignored_items = {
['blueprint'] = true,
['blueprint-book'] = true,
['deconstruction-planner'] = true,
['spidertron-remote'] = true,
['upgrade-planner'] = true
}
Global.register(
this,
function(t)
@ -123,7 +131,7 @@ function Public.save_quickbar(player)
for i = 1, 100 do
local slot = player.get_quick_bar_slot(i)
if slot ~= nil then
if slot ~= nil and not ignored_items[slot.name] then
slots[i] = slot.name
end
end

View File

@ -10,15 +10,15 @@ local Event = require 'utils.event'
local table = require 'utils.table'
local set_timeout_in_ticks = Task.set_timeout_in_ticks
local session_data_set = 'sessions'
local session = {}
local online_track = {}
local trusted = {}
local settings = {
-- local trusted_value = 2592000 -- 12h
trusted_value = 5184000, -- 24h
required_only_time_to_save_time = 36000, -- nearest prime to 10 minutes in ticks
nth_tick = 18000 -- nearest prime to 5 minutes in ticks
trusted_value = 24 * 60 * 3600, -- 24h
required_only_time_to_save_time = 10 * 3600,
nth_tick = 5 * 3600
}
local set_data = Server.set_data
local try_get_data = Server.try_get_data

View File

@ -0,0 +1,515 @@
-- created by Gerkiz for ComfyFactorio
local Global = require 'utils.global'
local Token = require 'utils.token'
local Task = require 'utils.task'
local Server = require 'utils.server'
local Event = require 'utils.event'
local set_timeout_in_ticks = Task.set_timeout_in_ticks
local statistics_dataset = 'statistics'
local set_data = Server.set_data
local try_get_data = Server.try_get_data
local e = defines.events
local floor = math.floor
local events = {
map_tags_made = e.on_chart_tag_added,
chat_messages = e.on_console_chat,
commands_used = e.on_console_command,
machines_built = e.on_built_entity,
items_picked_up = e.on_picked_up_item,
tiles_built = e.on_player_built_tile,
join_count = e.on_player_joined_game,
deaths = e.on_player_died,
entities_repaired = e.on_player_repaired_entity,
items_crafted = e.on_player_crafted_item,
capsules_used = e.on_player_used_capsule,
tiles_removed = e.on_player_mined_tile,
deconstructer_planner_used = e.on_player_deconstructed_area
}
local settings = {
required_only_time_to_save_time = 10 * 3600,
afk_time = 5 * 3600,
nth_tick = 5 * 3600
}
local Public = {
events = {
on_player_removed = Event.generate_event_name('on_player_removed')
}
}
local normalized_names = {
['map_tags_made'] = {name = 'Map-tags created', tooltip = "Tags that you've created in minimap."},
['chat_messages'] = {name = 'Messages', tooltip = 'Messages sent in chat.'},
['commands_used'] = {name = 'Commands', tooltip = 'Commands used in console.'},
['machines_built'] = {name = 'Entities built', tooltip = 'Entities built by the player.'},
['items_picked_up'] = {name = 'Items picked-up', tooltip = 'Items picked-up by the player.'},
['tiles_built'] = {name = 'Tiles placed', tooltip = 'Tiles placed by the player.'},
['join_count'] = {name = 'Join count', tooltip = 'How many times the player has joined the game.'},
['deaths'] = {name = 'Deaths', tooltip = 'How many times the player has died.'},
['entities_repaired'] = {name = 'Entities repaired', tooltip = 'How many entities the player has repaired.'},
['items_crafted'] = {name = 'Items crafted', tooltip = 'How many items the player has crafted.'},
['capsules_used'] = {name = 'Capsules used', tooltip = 'How many capsules the player has used.'},
['tiles_removed'] = {name = 'Tiles removed', tooltip = 'How many tiles the player has removed.'},
['deconstructer_planner_used'] = {name = 'Decon planner used', tooltip = 'How many times the player has used the deconstruction planner.'},
['maps_played'] = {name = 'Maps played', tooltip = 'How many maps the player has played.'},
['afk_time'] = {name = 'Total AFK', tooltip = 'How long the player has been AFK.'},
['distance_moved'] = {name = 'Distance travelled', tooltip = 'How far the player has travelled.\nIncluding standing still in looped belts.'},
['damage_dealt'] = {name = 'Damage dealt', tooltip = 'How much damage the player has dealt.'},
['enemies_killed'] = {name = 'Enemies killed', tooltip = 'How many enemies the player has killed.'},
['friendly_killed'] = {name = 'Friendlies killed', tooltip = 'How many friendlies the player has killed.\n This includes entities such as buildings etc.'},
['rockets_launched'] = {name = 'Rockets launched', tooltip = 'How many rockets the player has launched.'},
['research_complete'] = {name = 'Research completed', tooltip = 'How many researches the player has completed.'},
['force_mined_machines'] = {name = 'Mined friendly entities', tooltip = 'How many friendly entities the player has mined.'},
['trees'] = {name = 'Trees chopped', tooltip = 'How many trees the player has chopped.'},
['rocks'] = {name = 'Rocks mined', tooltip = 'How many rocks the player has mined.'},
['resources'] = {name = 'Ores mined', tooltip = 'How many ores the player has mined.'},
['kicked'] = {name = 'Kicked', tooltip = 'How many times the player has been kicked.'}
}
local statistics = {}
-- Register the statistics table in the global table
Global.register(
{
statistics = statistics
},
function(tbl)
statistics = tbl.statistics
for _, stat in pairs(statistics) do
setmetatable(stat, Public.metatable)
end
end
)
-- Metatable for the statistics table
Public.metatable = {__index = Public}
-- Add a normalization entry to the normalized_names table
function Public.add_normalize(name, normalize)
if _LIFECYCLE == _STAGE.runtime then
error('cannot call during runtime', 2)
end
local mt = setmetatable({name = normalize}, Public.metatable)
normalized_names[name] = mt
return mt
end
-- Set the tooltip for a statistic
function Public:set_tooltip(tooltip)
if _LIFECYCLE == _STAGE.runtime then
error('cannot call during runtime', 2)
end
self.tooltip = tooltip
end
--- Returns the player table or false
---@param player LuaPlayer|number
---@return any
local function get_data(player)
local player_index = player and type(player) == 'number' and player or player and player.valid and player.index or false
if not player_index then
log('Invalid player index at get_data')
return false
end
local data = statistics[player_index]
if not data then
local p = game.get_player(player_index)
local name = p and p.valid and p.name or nil
local player_data = {
name = name,
tick = 0
}
for event, _ in pairs(events) do
player_data[event] = 0
end
local mt = setmetatable(player_data, Public.metatable)
statistics[player_index] = mt
end
return statistics[player_index]
end
local try_download_data_token =
Token.register(
function(data)
local player_name = data.key
local player = game.get_player(player_name)
if not player or not player.valid then
return
end
local stats = data.value
if stats then
local s = setmetatable(stats, Public.metatable)
statistics[player.index] = s
else
get_data(player)
end
end
)
local try_upload_data_token =
Token.register(
function(data)
local player_name = data.key
if not player_name then
return
end
local stats = data.value
local player = game.get_player(player_name)
if not player or not player.valid then
return
end
if stats then
-- we don't want to clutter the database with players less than 10 minutes played.
if player.online_time <= settings.required_only_time_to_save_time then
return
end
set_data(statistics_dataset, player_name, get_data(player))
else
local d = get_data(player)
if player.online_time >= settings.required_only_time_to_save_time then
set_data(statistics_dataset, player_name, d)
end
end
end
)
-- Increase a statistic by a delta value
function Public:increase(name, delta)
if not self[name] then
self[name] = 0
end
self[name] = self[name] + (delta or 1)
self.tick = self.tick + 1
return self
end
-- Save the player's statistics
function Public:save()
local player = game.get_player(self.name)
if not player or not player.valid then
return
end
if player.online_time <= settings.required_only_time_to_save_time then
return
end
if self.tick < 10 then
return
end
set_data(statistics_dataset, player.name, self)
return self
end
-- Clear the player's statistics
function Public:clear(force_clear)
if force_clear then
statistics[self.name] = nil
else
local player = game.get_player(self.name)
if not player or not player.valid then
statistics[self.name] = nil
return
end
local connected = player.connected
if not connected then
statistics[self.name] = nil
end
end
end
-- Try to get the player's data from the dataset
function Public:try_get_data()
try_get_data(statistics_dataset, self.name, try_download_data_token)
return self
end
-- Try to upload the player's data to the dataset
function Public:try_upload_data()
try_get_data(statistics_dataset, self.name, try_upload_data_token)
return self
end
local nth_tick_token =
Token.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
get_data(player):save()
end
)
--- Uploads each connected players play time to the dataset
local function upload_data()
local players = game.connected_players
local count = 0
for i = 1, #players do
count = count + 10
local player = players[i]
set_timeout_in_ticks(count, nth_tick_token, {player_index = player.index})
end
end
--- Checks if a player exists within the table
---@param player_index string
---@return boolean
function Public.exists(player_index)
return statistics[player_index] ~= nil
end
--- Returns the table of statistics
---@param player LuaPlayer
---@return table|boolean
function Public.get_player(player)
return statistics and player and player.valid and statistics[player.index] or false
end
-- Event handlers
Event.add(
e.on_player_joined_game,
function(event)
get_data(event.player_index):try_get_data()
end
)
Event.add(
e.on_player_left_game,
function(event)
get_data(event.player_index):try_upload_data()
end
)
Event.add(
Public.events.on_player_removed,
function(event)
local player_index = event.player_index
statistics[player_index] = nil
end
)
Event.add(
e.on_player_removed,
function(event)
local player_index = event.player_index
statistics[player_index] = nil
end
)
Event.on_nth_tick(settings.nth_tick, upload_data)
Server.on_data_set_changed(
statistics_dataset,
function(data)
local player = game.get_player(data.key)
if player and player.valid then
local stats = data.value
if stats then
local s = setmetatable(stats, Public.metatable)
statistics[data.key] = s
end
end
end
)
local function on_marked_for_deconstruction_on_player_mined_entity(event)
if not event.player_index then
return
end
local player = game.get_player(event.player_index)
if not player.valid or not player.connected then
return
end
local entity = event.entity
if not entity.valid then
return
end
local data = get_data(event.player_index)
if entity.type == 'resource' then
data:increase('resources')
elseif entity.type == 'tree' then
data:increase('trees')
elseif entity.type == 'simple-entity' then
data:increase('rocks')
elseif entity.force == player.force then
data:increase('force_mined_machines')
end
end
for stat_name, event_name in pairs(events) do
Event.add(
event_name,
function(event)
if not event.player_index then
return
end
local player = game.get_player(event.player_index)
if not player.valid or not player.connected then
return
end
local data = get_data(event.player_index)
data:increase(stat_name)
end
)
end
Event.add(
e.on_research_finished,
function(event)
local research = event.research
if event.by_script or not research or not research.valid then
return
end
local force = research.force
if not force or not force.valid then
return
end
for _, player in pairs(force.connected_players) do
local data = get_data(player)
data:increase('research_complete')
end
end
)
Event.add(
e.on_rocket_launched,
function(event)
local silo = event.rocket_silo
if not silo or not silo.valid then
return
end
local force = silo.force
if not force or not force.valid then
return
end
for _, player in pairs(force.connected_players) do
local data = get_data(player)
data:increase('rockets_launched')
end
end
)
Event.add(
e.on_entity_died,
function(event)
local character = event.cause
if not character or not character.valid or character.type ~= 'character' then
return
end
local player = character.player
if not player or not player.valid or not player.connected then
return
end
local entity = event.entity
if not entity.valid or entity.force.name == 'neutral' then
return
end
local data = get_data(player)
if entity.force == player.force then
data:increase('friendly_killed')
return
end
data:increase('enemies_killed')
end
)
Event.add(
e.on_entity_damaged,
function(event)
local character = event.cause
if not character or not character.valid or character.type ~= 'character' then
return
end
local player = character.player
if not player.valid or not player.connected then
return
end
local entity = event.entity
if not entity.valid or entity.force == player.force or entity.force.name == 'neutral' then
return
end
local final_damage = event.final_damage_amount
local data = get_data(player)
data:increase('damage_dealt', floor(final_damage))
end
)
Event.add(
e.on_player_changed_position,
function(event)
local player = game.get_player(event.player_index)
if not player.valid or not player.connected or player.afk_time > settings.required_only_time_to_save_time then
return
end
local data = get_data(event.player_index)
data:increase('distance_moved')
end
)
Event.on_nth_tick(
3600,
function()
if game.tick == 0 then
return
end
for _, player in pairs(game.connected_players) do
local data = get_data(player)
if player.afk_time > settings.afk_time then
data:increase('afk_time')
end
end
end
)
Event.add(
e.on_player_created,
function(event)
get_data(event.player_index):increase('maps_played')
end
)
Event.add(
e.on_player_kicked,
function(event)
get_data(event.player_index):increase('kicked')
end
)
Event.add(e.on_marked_for_deconstruction, on_marked_for_deconstruction_on_player_mined_entity)
Event.add(e.on_player_mined_entity, on_marked_for_deconstruction_on_player_mined_entity)
Public.upload_data = upload_data
Public.get_data = get_data
Public.normalized_names = normalized_names
return Public

View File

@ -19,14 +19,17 @@ local name_lookup = {}
-- GUI names
local checkbox_name = Gui.uid_name()
local checkbox_all_name = Gui.uid_name()
local filter_name = Gui.uid_name()
local clear_filter_name = Gui.uid_name()
-- global tables
local enabled = {}
local enabled_for_all = false
local last_events = {}
global.debug_event_view = {
enabled = enabled,
enabled_for_all = enabled_for_all,
last_events = last_events,
filter = ''
}
@ -83,6 +86,25 @@ local function on_gui_checked_state_changed(event)
end
end
local function on_gui_checked_state_changed_all(event)
local element = event.element
local state = element.state and true or false
element.state = state
if state then
for name, _ in pairs(events) do
local id = events[name]
enabled[id] = true
end
enabled_for_all = true
else
for name, _ in pairs(events) do
local id = events[name]
enabled[id] = false
end
enabled_for_all = false
end
end
-- GUI
-- Create a table with events sorted by their names
@ -116,6 +138,12 @@ function Public.show(container)
filter_flow.add({type = 'label', caption = 'filter'})
local filter_textfield = filter_flow.add({type = 'textfield', name = filter_name, text = filter})
local clear_button = filter_flow.add({type = 'button', name = clear_filter_name, caption = 'clear'})
filter_flow.add({type = 'flow'}).add {
name = checkbox_all_name,
type = 'checkbox',
state = enabled_for_all or false,
caption = 'Toggle all events'
}
local scroll_pane = main_frame_flow.add({type = 'scroll-pane'})
local gui_table = scroll_pane.add({type = 'table', column_count = 3, draw_horizontal_lines = true})
@ -127,6 +155,7 @@ function Public.show(container)
end
Gui.on_checked_state_changed(checkbox_name, on_gui_checked_state_changed)
Gui.on_checked_state_changed(checkbox_all_name, on_gui_checked_state_changed_all)
Gui.on_text_changed(
filter_name,

View File

@ -108,7 +108,7 @@ end
--- Send a message to the connected channel.
--- Requires a title and a description
---@param scenario_name string
---@param scenario_name string|nil
---@param message string
function Public.send_notification_raw(scenario_name, message)
if not scenario_name then

View File

@ -14,7 +14,8 @@ local this = {
disable_crashsite = false,
crashed_ship_items = {},
crashed_debris_items = {},
custom_surface_name = nil
custom_surface_name = nil,
clear_mod_gui_top = false
}
Global.register(
@ -102,6 +103,10 @@ local chart_starting_area = function()
end
local on_player_joined_game = function(event)
if not this.clear_mod_gui_top then
return
end
Task.set_timeout_in_ticks(5, clear_mod_gui_top_frame_token, event)
end

View File

@ -27,7 +27,7 @@ local remove_data_recursively
local data = {}
local element_map = {}
local settings = {
mod_gui_top_frame = false,
mod_gui_top_frame = true,
disabled_tabs = {},
disable_clear_invalid_data = true
}
@ -66,17 +66,17 @@ local main_toggle_button_name = Public.uid_name()
local main_button_name = Public.uid_name()
local close_button_name = Public.uid_name()
Public.button_style = 'mod_gui_button'
if not Public.mod_gui_button_enabled then
Public.button_style = nil
end
Public.frame_style = 'non_draggable_frame'
Public.top_main_gui_button = main_button_name
Public.main_frame_name = main_frame_name
Public.main_toggle_button_name = main_toggle_button_name
Public.frame_style = 'non_draggable_frame'
Public.button_style = 'mod_gui_button'
Public.top_flow_button_enabled_style = 'menu_button_continue'
Public.top_flow_button_disabled_style = Public.button_style
--- Verifies if a frame is valid and destroys it.
---@param align userdata
@ -575,7 +575,7 @@ function Public.add_mod_button(player, frame)
return Public.get_button_flow(player)[frame.name]
end
Public.get_button_flow(player).add(frame)
return Public.get_button_flow(player).add(frame)
end
---@param state boolean
@ -764,7 +764,13 @@ end
local function top_button(player)
if settings.mod_gui_top_frame then
Public.add_mod_button(player, {type = 'sprite-button', name = main_button_name, sprite = 'item/raw-fish', style = Public.button_style})
local button = Public.add_mod_button(player, {type = 'sprite-button', name = main_button_name, sprite = 'item/raw-fish', style = Public.button_style})
if button then
button.style.minimal_height = 36
button.style.maximal_height = 36
button.style.minimal_width = 40
button.style.padding = -2
end
else
if player.gui.top[main_button_name] then
return
@ -782,19 +788,42 @@ local function top_toggle_button(player)
return
end
local b =
player.gui.top.add(
{
type = 'sprite-button',
name = main_toggle_button_name,
sprite = 'utility/preset',
style = Public.button_style,
tooltip = 'Click to hide top buttons!'
}
)
b.style.padding = 2
b.style.width = 20
b.style.maximal_height = 38
if Public.get_mod_gui_top_frame() then
local b =
Public.add_mod_button(
player,
{
type = 'sprite-button',
name = main_toggle_button_name,
sprite = 'utility/preset',
tooltip = 'Click to hide top buttons!',
style = Public.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 = 15
b.style.maximal_width = 15
b.style.padding = -2
end
else
local b =
player.gui.top.add(
{
type = 'sprite-button',
name = main_toggle_button_name,
sprite = 'utility/preset',
style = Public.button_style,
tooltip = 'Click to hide top buttons!'
}
)
b.style.padding = 2
b.style.width = 20
b.style.maximal_height = 38
end
end
local function draw_main_frame(player)
@ -951,6 +980,7 @@ Public.on_click(
end
local player = event.player
local frame = Public.get_parent_frame(player)
if frame then
remove_data_recursively(frame)
frame.destroy()
@ -999,9 +1029,17 @@ Public.on_click(
local top = player.gui.top
if button.sprite == 'utility/preset' then
for _, ele in pairs(top.children) do
if ele and ele.valid and ele.name ~= main_toggle_button_name then
ele.visible = false
if Public.get_mod_gui_top_frame() then
for _, ele in pairs(top.mod_gui_top_frame.mod_gui_inner_frame.children) do
if ele and ele.valid and ele.name ~= main_toggle_button_name then
ele.visible = false
end
end
else
for _, ele in pairs(top.children) do
if ele and ele.valid and ele.name ~= main_toggle_button_name then
ele.visible = false
end
end
end
@ -1015,9 +1053,17 @@ Public.on_click(
button.sprite = 'utility/expand_dots_white'
button.tooltip = 'Click to show top buttons!'
else
for _, ele in pairs(top.children) do
if ele and ele.valid and ele.name ~= main_toggle_button_name then
ele.visible = true
if Public.get_mod_gui_top_frame() then
for _, ele in pairs(top.mod_gui_top_frame.mod_gui_inner_frame.children) do
if ele and ele.valid and ele.name ~= main_toggle_button_name then
ele.visible = true
end
end
else
for _, ele in pairs(top.children) do
if ele and ele.valid and ele.name ~= main_toggle_button_name then
ele.visible = true
end
end
end

9
utils/gui/init.lua Normal file
View File

@ -0,0 +1,9 @@
require 'utils.gui'
require 'utils.gui.player_list'
require 'utils.gui.admin'
require 'utils.gui.group'
require 'utils.gui.score'
require 'utils.gui.statistics'
require 'utils.gui.config'
require 'utils.gui.poll'
require 'utils.gui.server_select'

View File

@ -767,7 +767,8 @@ local function player_joined(event)
end
if Gui.get_mod_gui_top_frame() then
Gui.add_mod_button(
local button =
Gui.add_mod_button(
player,
{
type = 'sprite-button',
@ -777,6 +778,14 @@ local function player_joined(event)
style = Gui.button_style
}
)
if button then
button.style.font_color = {165, 165, 165}
button.style.font = 'heading-3'
button.style.minimal_height = 36
button.style.maximal_height = 36
button.style.minimal_width = 40
button.style.padding = -2
end
else
if player.gui.top[main_button_name] ~= nil then
local frame = player.gui.top[main_frame_name]

View File

@ -132,8 +132,13 @@ local function create_main_button(event)
end
local player = game.get_player(event.player_index)
if not player or not player.valid or not player.character then
return
end
if Gui.get_mod_gui_top_frame() then
Gui.add_mod_button(
local b =
Gui.add_mod_button(
player,
{
type = 'sprite-button',
@ -143,6 +148,14 @@ local function create_main_button(event)
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 main_button = player.gui.top[main_button_name]
if not main_button or not main_button.valid then

104
utils/gui/statistics.lua Normal file
View File

@ -0,0 +1,104 @@
-- created by Gerkiz for ComfyFactorio
local Gui = require 'utils.gui'
local Token = require 'utils.token'
local StatData = require 'utils.datastore.statistics'
local format_number = require 'util'.format_number
local Server = require 'utils.server'
local Public = {}
local module_name = Gui.uid_name()
local ignored_stats = {
['name'] = true,
['tick'] = true
}
local normalized_names = StatData.normalized_names
local function show_score(data)
local player = data.player
local frame = data.frame
frame.clear()
local t = frame.add {type = 'table', column_count = 2}
local tooltip = 'Your statistics that are gathered throughout Comfy servers.'
local secs = Server.get_current_time()
if not secs then
tooltip = 'Not currently connected to any backend. Statistics will reset.'
end
local label =
t.add {
type = 'label',
caption = tooltip
}
label.style.font = 'heading-2'
label.style.font_color = {r = 0.98, g = 0.66, b = 0.22}
label.style.minimal_width = 125
label.style.horizontal_align = 'center'
local line = frame.add {type = 'line'}
line.style.top_margin = 8
line.style.bottom_margin = 8
local stat_data = StatData.get_data(player)
local scroll_pane =
frame.add(
{
type = 'scroll-pane',
name = 'score_scroll_pane',
direction = 'vertical',
horizontal_scroll_policy = 'never',
vertical_scroll_policy = 'auto'
}
)
scroll_pane.style.maximal_height = 400
local column_table = scroll_pane.add {type = 'table', column_count = 4}
for name, stat in pairs(stat_data) do
if not ignored_stats[name] then
local c = stat
if stat and type(stat) == 'number' then
c = format_number(stat, true)
end
local lines = {
{caption = normalized_names[name].name, tooltip = normalized_names[name].tooltip or ''},
{caption = c}
}
local default_color = {r = 0.9, g = 0.9, b = 0.9}
for _, column in ipairs(lines) do
local l =
column_table.add {
type = 'label',
caption = column.caption,
tooltip = column.tooltip,
color = column.color or default_color
}
l.style.font = 'heading-3'
l.style.minimal_width = 175
l.style.maximal_width = 175
l.style.horizontal_align = 'left'
end
end
end
end
local show_stats_token = Token.register(show_score)
Gui.add_tab_to_gui({name = module_name, caption = 'Statistics', id = show_stats_token, admin = false})
Gui.on_click(
module_name,
function(event)
local player = event.player
Gui.reload_active_tab(player)
end
)
return Public