1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-11-27 22:38:18 +02:00

More refactor and performance fixes

This commit is contained in:
Gerkiz
2025-10-26 01:00:31 +02:00
parent 27aaef3762
commit c43ec325cb
22 changed files with 5094 additions and 4476 deletions

View File

@@ -3,55 +3,9 @@ local Public = require 'maps.infestation_islands.table'
local BiterHealthBooster = require 'modules.biter_health_booster_v2'
local Server = require 'utils.server'
local scale_units_by_health =
{
['small-biter'] = 1,
['medium-biter'] = 0.75,
['big-biter'] = 0.5,
['behemoth-biter'] = 0.25,
['small-spitter'] = 1,
['medium-spitter'] = 0.75,
['big-spitter'] = 0.5,
['behemoth-spitter'] = 0.25,
['small-wriggler-pentapod'] = 1,
['medium-wriggler-pentapod'] = 0.75,
['big-wriggler-pentapod'] = 0.5,
['small-strafer-pentapod'] = 1,
['medium-strafer-pentapod'] = 0.75,
['big-strafer-pentapod'] = 0.5,
}
local scale_spawners_by_health =
{
['biter-spawner'] = 0.5,
['spitter-spawner'] = 0.5,
['gleba-spawner-small'] = 0.5,
['gleba-spawner'] = 0.5,
}
local scale_worms_by_health =
{
['land-mine'] = 0.5, -- not active as of now
['gun-turret'] = 0.5, -- not active as of now
['flamethrower-turret'] = 0.4, -- not active as of now
['artillery-turret'] = 0.25, -- not active as of now
['small-worm-turret'] = 0.8,
['medium-worm-turret'] = 0.6,
['big-worm-turret'] = 0.3,
['behemoth-worm-turret'] = 0.3
}
local round = math.round
local floor = math.floor
local random = math.random
local spawn_amount_rolls = {}
for a = 48, 1, -1 do
spawn_amount_rolls[#spawn_amount_rolls + 1] = floor(a ^ 5)
end
local random_particles =
{
'dirt-2-stone-particle-medium',
@@ -143,17 +97,13 @@ local function roll_spawner(level)
end
local function roll_health_boost(level)
if not level or level <= 3 then
if not level or level <= 1 then
return 1
elseif level <= 6 then
return 1.5
elseif level <= 10 then
return 2
elseif level > 10 then
return 3
end
return 1 + (level ^ 1.25) * 0.15
end
local function spawn_spawner(data)
local alive_enemies = Public.get('alive_enemies')
local max_biters_per_island = Public.get('max_biters_per_island')
@@ -185,20 +135,10 @@ local function spawn_spawner(data)
return
end
alive_enemies = Public.get('alive_enemies')
Public.set('alive_enemies', alive_enemies + 1)
local health_boost = roll_health_boost(current_level) or 1
local health_boost = roll_health_boost(current_level + 5) or 1
if random(1, 30) == 1 then
BiterHealthBooster.add_boss_unit(unit, health_boost, 0.38)
else
local final_health = round(health_boost * scale_spawners_by_health[unit.name], 3)
if final_health < 1 then
final_health = 1
end
BiterHealthBooster.add_unit(unit, final_health)
end
return true
end
@@ -234,20 +174,10 @@ local function spawn_biters(data)
return
end
alive_enemies = Public.get('alive_enemies')
Public.set('alive_enemies', alive_enemies + 1)
local health_boost = roll_health_boost(current_level) or 1
local health_boost = roll_health_boost(current_level + 5) or 1
if random(1, 30) == 1 then
BiterHealthBooster.add_boss_unit(unit, health_boost, 0.38)
else
local final_health = round(health_boost * scale_units_by_health[unit.name], 3)
if final_health < 1 then
final_health = 1
end
BiterHealthBooster.add_unit(unit, final_health)
end
local market_target = Public.get('market_target')
@@ -302,7 +232,7 @@ local function spawn_tech(data)
return
end
local health_boost = roll_health_boost(current_level) or 1
local health_boost = roll_health_boost(current_level + 5) or 1
local unit = surface.create_entity({ name = unit_to_create, position = position, force = data.force or 'enemy', quality = data.quality or 'normal' })
@@ -312,12 +242,6 @@ local function spawn_tech(data)
if random(1, 30) == 1 then
BiterHealthBooster.add_boss_unit(unit, health_boost, 0.38)
else
local final_health = round(health_boost * 0.5, 3)
if final_health < 1 then
final_health = 1
end
BiterHealthBooster.add_unit(unit, final_health)
end
return true
end
@@ -349,23 +273,18 @@ local function spawn_worms(data)
if not unit or not unit.valid then
return
end
alive_enemies = Public.get('alive_enemies')
Public.set('alive_enemies', alive_enemies + 1)
local health_boost = roll_health_boost(current_level) or 1
local health_boost = roll_health_boost(current_level + 5) or 1
if random(1, 30) == 1 then
BiterHealthBooster.add_boss_unit(unit, health_boost, 0.38)
else
local final_health = round(health_boost * scale_worms_by_health[unit.name], 3)
if final_health < 1 then
final_health = 1
end
BiterHealthBooster.add_unit(unit, final_health)
end
end
---@param surface LuaSurface
---@param position MapPosition
---@param count number
---@param force string
function Public.buried_spawner(surface, position, count, force)
if not (surface and surface.valid) then
return
@@ -386,7 +305,7 @@ function Public.buried_spawner(surface, position, count, force)
local buried_biters = Public.get('buried_biters')
for t = 1, 60, 1 do
for t = 1, 220, 1 do
if not buried_biters[game.tick + t] then
buried_biters[game.tick + t] = {}
end
@@ -397,7 +316,7 @@ function Public.buried_spawner(surface, position, count, force)
data = { surface = surface, position = { x = position.x, y = position.y }, amount = math.ceil(t * 0.05) }
}
if t == 60 then
if t == 220 then
if count == 1 then
buried_biters[game.tick + t][#buried_biters[game.tick + t] + 1] =
{
@@ -419,6 +338,11 @@ function Public.buried_spawner(surface, position, count, force)
end
end
---@param surface LuaSurface
---@param position MapPosition
---@param count number
---@param force string
---@param quality string
function Public.buried_biter(surface, position, count, force, quality)
if not (surface and surface.valid) then
return
@@ -439,7 +363,7 @@ function Public.buried_biter(surface, position, count, force, quality)
local buried_biters = Public.get('buried_biters')
for t = 1, 60, 1 do
for t = 1, 220, 1 do
if not buried_biters[game.tick + t] then
buried_biters[game.tick + t] = {}
end
@@ -450,7 +374,7 @@ function Public.buried_biter(surface, position, count, force, quality)
data = { surface = surface, position = { x = position.x, y = position.y }, amount = math.ceil(t * 0.05) }
}
if t == 60 then
if t == 220 then
if count == 1 then
buried_biters[game.tick + t][#buried_biters[game.tick + t] + 1] =
{
@@ -472,6 +396,11 @@ function Public.buried_biter(surface, position, count, force, quality)
end
end
---@param surface LuaSurface
---@param position MapPosition
---@param count number
---@param force string
---@param quality string
function Public.buried_tech(surface, position, count, force, quality)
if not (surface and surface.valid) then
return
@@ -492,7 +421,7 @@ function Public.buried_tech(surface, position, count, force, quality)
local buried_biters = Public.get('buried_biters')
for t = 1, 60, 1 do
for t = 1, 220, 1 do
if not buried_biters[game.tick + t] then
buried_biters[game.tick + t] = {}
end
@@ -503,7 +432,7 @@ function Public.buried_tech(surface, position, count, force, quality)
data = { surface = surface, position = { x = position.x, y = position.y }, amount = math.ceil(t * 0.05) }
}
if t == 60 then
if t == 220 then
if count == 1 then
buried_biters[game.tick + t][#buried_biters[game.tick + t] + 1] =
{
@@ -525,6 +454,9 @@ function Public.buried_tech(surface, position, count, force, quality)
end
end
---@param surface LuaSurface
---@param position MapPosition
---@param quality string
function Public.buried_worm(surface, position, quality)
if not (surface and surface.valid) then
return
@@ -541,7 +473,7 @@ function Public.buried_worm(surface, position, quality)
local buried_biters = Public.get('buried_biters')
for t = 1, 60, 1 do
for t = 1, 220, 1 do
if not buried_biters[game.tick + t] then
buried_biters[game.tick + t] = {}
end
@@ -552,7 +484,7 @@ function Public.buried_worm(surface, position, quality)
data = { surface = surface, position = { x = position.x, y = position.y }, amount = math.ceil(t * 0.05) }
}
if t == 60 then
if t == 220 then
buried_biters[game.tick + t][#buried_biters[game.tick + t] + 1] =
{
callback = 'spawn_worms',

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,425 @@
local Public = require 'maps.infestation_islands.table'
local Server = require 'utils.server'
local Color = require 'utils.color_presets'
local Difficulty = require 'modules.difficulty_vote_by_amount'
local Scheduler = require 'utils.scheduler'
local Commands = require 'utils.commands'
local Discord = require 'utils.discord_handler'
Commands.new('toggle_auto_create_islands', 'Toggles the autogenerate islands.')
:require_admin()
:require_validation('Utilize this only when testing the map generation!')
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
if not state then
Public.set('auto_create_islands', false)
player.print('The autogenerate islands has been disabled!', { color = Color.warning })
return
end
Public.set('auto_create_islands', true)
player.print('The autogenerate islands has been enabled!', { color = Color.warning })
local this = Public.get()
if this.market_target then
this.position = this.market_target.position
else
this.position = { x = 0, y = 0 }
end
this.current_level = this.current_level + 1
this.attack_grace_period = game.tick + 54000
this.cooldown_complete_level = game.tick + (60 * 60)
this.alive_enemies = 999
Scheduler.new(1, Public.init_next_island_token)
:set_data({ surface = game.surfaces[1], position = this.position })
end
)
Commands.new('show_centered_gps', 'Shows the centered points of the map.')
:require_admin()
:callback(
function (player)
local this = Public.get()
for level, point in pairs(this.islands_data) do
player.print('Level ' .. level .. ':')
player.print('[gps=' .. point.position.x .. ',' .. point.position.y .. ',' .. player.surface.name .. ']')
end
end
)
Commands.new('set_biter_count', 'Sets the biter count.')
:require_admin()
:add_parameter('count', false, 'number')
:callback(
function (player, count)
local this = Public.get()
this.max_biters_per_island = count
player.print('The biter count has been set to ' .. count .. '!', { color = Color.warning })
end
)
Commands.new('send_enemies', 'Sends enemies to the market.')
:require_admin()
:callback(
function (player)
Public.set_multi_command()
player.print('Enemies have been sent to the market!', { color = Color.warning })
return true
end
)
Commands.new('toggle_drift_corpses_toward_beach', 'Toggles the drift corpses toward beach.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
Public.set('drift_corpses_toward_beach_enabled', state)
player.print('The drift corpses toward beach has been ' .. (state and 'enabled' or 'disabled') .. '!', { color = Color.warning })
end
)
Commands.new('set_infinite_ammo_tick', 'Sets the infinite ammo tick.')
:require_admin()
:add_parameter('tick', false, 'number')
:callback(
function (player, tick)
if tick < 10 then
return player.print('The infinite ammo tick must be at least 10 ticks!', { color = Color.warning })
end
if tick > 100 then
return player.print('The infinite ammo tick must be less than 100 ticks!', { color = Color.warning })
end
Public.set('infinite_ammo_tick', tick)
player.print('The infinite ammo tick has been set to ' .. tick .. '!', { color = Color.warning })
end
)
Commands.new('skip_difficulty_vote', 'Skips the difficulty vote.')
:require_admin()
:callback(
function (player)
Difficulty.set_poll_closing_timeout(game.tick)
player.print('The difficulty vote has been skipped!', { color = Color.warning })
end
)
Commands.new('skip_voting_to_progress', 'Toggles the voting to progress.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
if not state then
Public.set('voting_to_progress_enabled', true)
player.print('The voting to progress has been enabled!', { color = Color.warning })
return
end
Public.set('voting_to_progress_enabled', false)
player.print('The voting to progress has been disabled!', { color = Color.warning })
end
)
Commands.new('reward_level', 'Rewards the level.')
:require_admin()
:callback(
function (player)
local level = Public.get('current_level')
local center_position = Public.get('islands_data')[level]
if not center_position then
center_position =
{
position = { x = 0, y = 0 }
}
end
Public.reward_level(game.surfaces[1], center_position)
player.print('Level ' .. level .. ' has been rewarded!', { color = Color.warning })
end
)
Commands.new('set_clear_items_on_ground', 'Sets the clear items on ground state.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
Public.set('clear_items_on_ground_state', state)
player.print('Clear items on ground has been ' .. (state and 'enabled' or 'disabled') .. '!', { color = Color.warning })
end
)
Commands.new('toggle_check_surface_daytime', 'Checks the surface daytime if an attack towards the market should be sent.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
Public.set('check_surface_daytime_for_attacks', state)
player.print('The check surface daytime has been ' .. (state and 'enabled' or 'disabled') .. '!', { color = Color.warning })
end
)
Commands.new('toggle_disable_multi_command_attack', 'Disables waves of enemies from being sent to the market.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
Public.set('disable_multi_command_attack', state)
player.print('The disable multi command attack has been ' .. (state and 'enabled' or 'disabled') .. '!', { color = Color.warning })
end
)
Commands.new('scenario', 'Usable only for admins - controls the scenario!')
:require_admin()
:require_validation()
:add_parameter('reset', false, 'string')
:callback(
function (player, action)
local this = Public.get()
if action == 'reset' then
goto continue
else
player.print('Invalid action.')
return false
end
::continue::
if action == 'reset' then
this.reset_are_you_sure = nil
if player and player.valid then
game.print(Public.island_keeper .. player.name .. ', has reset the game!',
{ color = Public.command_color })
Discord.send_notification(
{
title = "Game reset",
description = player.name .. ' has reset the game!',
color = "success",
fields =
{
{
title = "Server",
description = Public.discord_name,
inline = "false"
}
}
})
else
game.print(Public.island_keeper .. 'server, has reset the game!', { color = Public.command_color })
Discord.send_notification(
{
title = "Game reset",
description = 'Server has reset the game!',
color = "success",
fields =
{
{
title = "Server",
description = Public.discord_name,
inline = "false"
}
}
})
end
this.game_lost = true
this.game_reset_tick = 1
player.print('Game has been reset!')
end
end
)
Commands.new('server', 'Usable only for admins - controls the server!')
:require_admin()
:require_validation()
:add_parameter('restart/shutdown/restart-now', false, 'string')
:callback(
function (player, action)
local this = Public.get()
if action == 'restart' or action == 'shutdown' or action == 'restart-now' then
goto continue
else
player.print('Invalid action.')
return false
end
::continue::
if action == 'restart' then
if this.restart then
this.reset_are_you_sure = nil
this.restart = false
this.soft_reset = true
Discord.send_notification(
{
title = "Soft-reset enabled",
description = player.name .. ' has enabled soft-reset!',
color = "info",
fields =
{
{
title = "Server",
description = Public.discord_name,
inline = "false"
}
}
})
player.print('Soft-reset is enabled.')
else
this.reset_are_you_sure = nil
this.restart = true
this.soft_reset = false
if this.shutdown then
this.shutdown = false
end
Discord.send_notification(
{
title = "Soft-reset disabled",
description = player.name .. ' has disabled soft-reset! Restart will happen from scenario.',
color = "warning",
fields =
{
{
title = "Server",
description = Public.discord_name,
inline = "false"
}
}
})
player.print('Soft-reset is disabled! Server will restart from scenario to load new changes.')
end
elseif action == 'restart-now' then
this.reset_are_you_sure = nil
Server.start_scenario('Infestation_Islands')
Discord.send_notification(
{
title = "Server restarted",
description = player.name .. ' restarted the server.',
color = "success",
fields =
{
{
title = "Server",
description = Public.discord_name,
inline = "false"
}
}
})
player.print('Restarted the server.')
elseif action == 'shutdown' then
if this.shutdown then
this.reset_are_you_sure = nil
this.shutdown = false
this.soft_reset = true
Discord.send_notification(
{
title = "Soft-reset enabled",
description = player.name .. ' has enabled soft-reset. Server will NOT shutdown!',
color = "success",
fields =
{
{
title = "Server",
description = Public.discord_name,
inline = "false"
}
}
})
player.print('Soft-reset is enabled.')
else
this.reset_are_you_sure = nil
this.shutdown = true
this.soft_reset = false
if this.restart then
this.restart = false
end
Discord.send_notification(
{
title = "Soft-reset disabled",
description = player.name .. ' has disabled soft-reset. Server will shutdown!',
color = "warning",
fields =
{
{
title = "Server",
description = Public.discord_name,
inline = "false"
}
}
})
player.print('Soft-reset is disabled! Server will shutdown.')
end
end
end
)
Commands.new('switch_game_mode', 'Switches the game mode - for admins.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
if state then
Public.set('game_over_if_market_dies', true)
player.print('Game mode switched!')
player.print('The game will be over if any market dies!', { color = Color.warning })
else
Public.set('game_over_if_market_dies', false)
player.print('Game mode switched!')
player.print('The biters will try to conquer islands, but the game will not be over if any market dies unless it\'s the last one!', { color = Color.warning })
end
end
)
Commands.new('do_buried_biters', 'Spawns some biters at a given explored level!')
:require_admin()
:add_parameter('level', false, 'number')
:add_parameter('count', false, 'number')
:callback(
function (player, level, count)
Public.do_buried_biters(level)
local islands_data = Public.get('islands_data')
local last_level = Public.get('current_level')
local position = islands_data[level] and islands_data[level].position
if not position then
return player.print('Level ' .. level .. ' has not been explored yet!', { color = Color.warning })
end
if level > last_level then
return player.print('Level ' .. level .. ' is not the last level!', { color = Color.warning })
end
Public.buried_biter(game.surfaces[1], position, count, 'enemy', Public.qualities[math.random(1, #Public.qualities)])
player.print('Buried biters have been spawned at level ' .. level .. '!', { color = Color.warning })
end
)
Commands.new('reverse_start_position', 'Reverses the start position from where the snake should start at.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
Public.set('reverse_start_position', state)
if not state then
return player.print('The snake will start from the parent island and move towards the new island!', { color = Color.warning })
else
return player.print('The snake will start from the new island and move towards the parent island!', { color = Color.warning })
end
end
)
Commands.new('set_auto_generate_upon_idle', 'Sets whether the next island should be automatically generated upon idle.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
if not state then
Public.set('auto_generate_upon_idle', false)
player.print('New islands will not be automatically generated upon idle!', { color = Color.warning })
else
Public.set('auto_generate_upon_idle', true)
player.print('New islands will be automatically generated upon idle!', { color = Color.warning })
end
end
)

View File

@@ -1,8 +1,10 @@
local Public = require 'maps.infestation_islands.table'
Public.func = require 'maps.infestation_islands.func'
Public.functions = require 'maps.infestation_islands.functions'
Public.callbacks = require 'maps.infestation_islands.callbacks'
Public.basic_markets = require 'maps.infestation_islands.basic_markets'
Public.island_settings = require 'maps.infestation_islands.island_settings'
Public.loot = require 'maps.infestation_islands.loot'
Public.buried_biters = require 'maps.infestation_islands.buried_biters'
Public.commands = require 'maps.infestation_islands.commands'
return Public

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,14 @@
--created by Gerkiz
local Public = require 'maps.infestation_islands.core'
local Event = require 'utils.event'
local Func = Public.func
local Task = require 'utils.task_token'
local Func = Public.functions
local Scheduler = require 'utils.scheduler'
local Difficulty = require 'modules.difficulty_vote_by_amount'
local Server = require 'utils.server'
local Gui = require 'utils.gui'
local stage_gui_name = Gui.uid()
local random = math.random
if not script.active_mods.quality then
error('Quality mod is not enabled!')
@@ -48,7 +48,8 @@ local function reset_player(player)
end
local reset_players_token =
Task.register(
Scheduler.register_function(
'reset_players_token',
function ()
local surface = game.get_surface(1)
@@ -82,69 +83,82 @@ end
local function create_stage_gui(player)
local button = get_top_frame(player, stage_gui_name)
if button then
return
end
if button and button.valid then button.destroy() end
if Gui.get_mod_gui_top_frame() then
local frame =
local element =
Gui.add_mod_button(
player,
{
type = 'frame',
name = stage_gui_name,
caption = ' '
}
)
if frame then
frame.style.minimal_height = 36
frame.style.maximal_height = 36
frame.style.minimal_width = 140
frame.style.maximal_width = 420
frame.style.font_color = { r = 155, g = 85, b = 25 }
frame.style.font = 'heading-2'
if element and element.valid then
local style = element.style
style.minimal_height = 36
style.maximal_height = 36
style.padding = 0
local label = element.add({ type = 'label', caption = ' ', name = 'label' })
label.style.font_color = { r = 0.88, g = 0.88, b = 0.88 }
label.style.font = 'heading-1'
end
else
local element = player.gui.top.add({ type = 'frame', name = stage_gui_name, caption = ' ' })
if element and element.valid then
local style = element.style
style.minimal_height = 54
style.maximal_height = 54
style.minimal_width = 140
style.maximal_width = 420
style.top_padding = 12
style.left_padding = 4
style.right_padding = 4
style.bottom_padding = 2
style.font_color = { r = 155, g = 85, b = 25 }
style.font = 'default-large-bold'
style.padding = 0
local label = element.add({ type = 'label', caption = ' ', name = 'label' })
label.style.font_color = { r = 0.88, g = 0.88, b = 0.88 }
label.style.font = 'heading-1'
end
end
end
local function update_stage_gui(caption_override)
local function update_stage_gui()
local this = Public.get()
if not this.stages then
return
end
local caption = 'Level: ' .. this.current_level
caption = caption .. ' | Stage: '
local stage = this.current_stage
local caption = 'Level: '
local stage = this.current_level
if stage > #this.stages - 1 then
stage = #this.stages - 1
end
local islands_data = Public.get('islands_data')
local island_data = islands_data[stage]
caption = caption .. stage
caption = caption .. '/'
caption = caption .. #this.stages - 1
if this.alive_enemies == 0 then
local tooltip
if not (island_data and island_data.spawned_biters) then
caption = caption .. ' | Generating...'
tooltip = 'The biters are still generating on the island. Please wait for them to finish.'
elseif (this.auto_generate_upon_idle and island_data and island_data.spawned_biters and island_data.completed and this.time_until_next_island_is_created and this.time_until_next_island_is_created > game.tick and Difficulty.has_votes_ended()) then
caption = caption .. ' | Level cleared!'
caption = caption .. ' | [entity=small-biter,quality=' .. Public.qualities[random(1, #Public.qualities)] .. ']: ' .. Public.normalize_time_until_next_island_is_created()
tooltip = 'The next island will be generated in ' .. Public.normalize_time_until_next_island_is_created() .. '.\nUnless you progress to the next island, it will be generated automatically.\nIf you do not progress to the next island, you will not be able to reroll the next island market.\nMarket rerolls are unlocked when you manually progress to the next island.'
elseif (island_data and island_data.spawned_biters and island_data.completed) then
caption = caption .. ' | Level cleared!'
tooltip = 'Defenses would sure be helpful right now.\nVotes close in ' .. Difficulty.get_closing_timeout() .. ' seconds.'
elseif (this.auto_generate_upon_idle and island_data and island_data.auto_generated_bridge == false) then
caption = caption .. ' | Bugs remaining: '
caption = caption .. this.alive_enemies
caption = caption .. ' | [entity=small-biter,quality=' .. Public.qualities[random(1, #Public.qualities)] .. ']: ' .. Public.normalize_time_until_next_island_is_created()
tooltip = 'The bridge to the next island will be generated in ' .. Public.normalize_time_until_next_island_is_created() .. '.\nUnless you progress to the next island, it will be generated automatically.\nMarket rerolls have been removed for the next island.\nMarket rerolls are unlocked when you manually progress to the next island.'
else
caption = caption .. ' | Bugs remaining: '
caption = caption .. this.alive_enemies
tooltip = 'Vanquish the biters to capture the island. ' .. this.alive_enemies .. ' biters remaining.'
end
for _, player in pairs(game.connected_players) do
local frame = get_top_frame(player, stage_gui_name)
if frame then
frame.caption = caption_override or caption
if frame and frame.valid and frame.label and frame.label.valid then
frame.label.caption = this.top_label_caption_override or caption
frame.label.tooltip = tooltip
end
end
end
@@ -172,6 +186,16 @@ end
local function clear_surface()
local surface = game.get_surface(1)
surface.clear()
local planet = game.planets['nauvis']
local platforms = planet.get_space_platforms('player')
if platforms then
for _, platform in pairs(platforms) do
if platform and platform.valid then
platform.destroy()
end
end
end
end
local function on_player_joined_game(event)
@@ -195,7 +219,9 @@ local function has_the_game_ended(this)
end
game.forces.enemy.set_friend('player', true)
game.forces.enemy.set_cease_fire('player', true)
game.forces.player.set_friend('enemy', true)
game.forces.player.set_cease_fire('enemy', true)
this.game_reset_tick = this.game_reset_tick - 1
if this.game_reset_tick % 600 == 0 then
@@ -224,7 +250,7 @@ local function has_the_game_ended(this)
this.ammo_chest = nil
end
for _, market_data in pairs(this.spawned_markets) do
for _, market_data in pairs(this.islands_data) do
if market_data and market_data.market and market_data.market.valid then
if market_data.render_protect_text then
market_data.render_protect_text.destroy()
@@ -235,16 +261,17 @@ local function has_the_game_ended(this)
market_data.render_checkpoint_text = nil
end
market_data.market.destroy()
market_data.market = nil
end
end
this.game_reset_tick = nil
this.game_lost = false
this.game_won = false
Scheduler.can_run_scheduler(false)
Scheduler.clear_tasks()
clear_surface()
Public.on_init()
Task.set_timeout_in_ticks(500, reset_players_token)
Scheduler.new(500, reset_players_token)
return
end
@@ -274,27 +301,83 @@ end
local function on_tick()
local this = Public.get()
if game.tick % 25 == 0 then
if this.alive_enemies < 0 then this.alive_enemies = 0 end
if this.game_lost then
local message = this.nomed_marked and 'The bugs had a feast on the marked at level ' .. this.nomed_marked .. '!' or 'The bugs had a feast on the marked!'
update_stage_gui(message)
else
update_stage_gui()
local island_data = this.islands_data[this.current_level]
local tick = game.tick
if this.delayed_messages[tick] then
game.print(this.delayed_messages[tick])
this.delayed_messages[tick] = nil
end
if tick % 25 == 0 then
Func.check_alive_enemies()
end
has_the_game_ended(this)
if tick % 40 == 0 and this.auto_generate_upon_idle then
if island_data and Difficulty.has_votes_ended() then
local _, time = Public.normalize_time_until_next_island_is_created()
if island_data.completed then
if not this.time_until_next_island_is_created then
local difficulty_index = Difficulty.get('index')
local hour
if difficulty_index == 1 then
hour = random(60, 120)
elseif difficulty_index == 2 then
hour = random(30, 60)
elseif difficulty_index == 3 then
hour = random(15, 30)
end
this.time_until_next_island_is_created = tick + (60 * 60 * hour * this.current_level)
this.time_until_next_island_is_created_static = math.round((this.time_until_next_island_is_created - tick) / 60 / 60, 0)
if _DEBUG then
this.time_until_next_island_is_created = tick + (60 * 60 * 10)
this.time_until_next_island_is_created_static = math.round((this.time_until_next_island_is_created - tick) / 60 / 60, 0)
end
return
end
local time_limit = this.time_until_next_island_is_created_static / 2
-- spawn the island before the time limit is reached
if time <= time_limit then
if not island_data.auto_generated_island then
island_data.auto_generated_island = true
game.print('The biters are getting hungry!!!', { color = { r = 0.88, g = 0.22, b = 0.22 } })
Scheduler.new(1, Public.init_next_island_without_bridge_token):set_data({ surface = game.surfaces[1] })
end
end
else
-- spawn the island after the time limit is reached
if time <= 0 then
if not (island_data and island_data.auto_generated_bridge) then
island_data.auto_generated_bridge = true
game.print('The biters are forming a bridge to our island! They are coming!!!', { color = { r = 0.88, g = 0.22, b = 0.22 } })
Scheduler.new(1, Public.do_generate_bridge_token):set_data({ surface = game.surfaces[1] })
this.time_until_next_island_is_created = nil
end
end
if not this.game_lost then
if Difficulty.has_votes_ended() and not this.difficulty_vote_ended then
this.difficulty_vote_ended = true
game.print('The difficulty vote has ended! You may now progress to the next island!', { color = { r = 0.22, g = 0.88, b = 0.22 } })
Server.to_discord_embed('** The difficulty vote has ended! You may now progress to the next island! **')
game.print('The difficulty is ' .. Difficulty.get('name') .. '!', { color = Difficulty.get('print_color') })
Server.to_discord_embed('** The difficulty is ' .. Difficulty.get('name') .. '! **')
end
end
end
local infinite_ammo_tick = Public.get('infinite_ammo_tick')
if game.tick % infinite_ammo_tick == 0 then
if tick % 50 == 0 then
update_stage_gui()
if this.game_lost then return end
local position = island_data and island_data.position or { x = 0, y = 0 }
local radius = island_data and island_data.radius or 0
game.forces.player.chart(game.surfaces[1], { { position.x - radius, position.y - radius }, { position.x + radius, position.y + radius } })
Func.is_rocket_silo_alive()
end
if tick % this.infinite_ammo_tick == 0 then
drift_corpses_toward_beach()
if this.ammo_chest and this.ammo_chest.valid then
local magazine_name = 'firearm-magazine'
@@ -309,60 +392,45 @@ local function on_tick()
end
end
if game.tick % 150 == 0 then
if this.game_lost then return end
Func.is_rocket_silo_alive()
local center_position = this.centered_points[this.current_level]
if not center_position then
center_position =
{
position = { x = 0, y = 0 }
}
if tick % 100 == 0 then
if not this.game_lost then
if Difficulty.has_votes_ended() and not this.difficulty_vote_ended then
this.difficulty_vote_ended = true
game.print('The difficulty vote has ended! You may now progress to the next island!', { color = { r = 0.22, g = 0.88, b = 0.22 } })
Server.to_discord_embed('** The difficulty vote has ended! You may now progress to the next island! **')
game.print('The difficulty is ' .. Difficulty.get('name') .. '!', { color = Difficulty.get('print_color') })
Server.to_discord_embed('** The difficulty is ' .. Difficulty.get('name') .. '! **')
end
end
end
game.forces.player.chart(game.surfaces[1], { { center_position.position.x - 124, center_position.position.y - 124 }, { center_position.position.x + 124, center_position.position.y + 124 } })
Func.check_alive_enemies()
if not this.completed_levels[this.current_level] then
if tick % 150 == 0 then
if not this.game_lost then
if not island_data.completed then
if not this.disable_multi_command_attack then
Func.do_buried_biters()
end
end
end
end
if game.tick % 400 == 0 then
if tick % 400 == 0 then
Func.set_multi_command()
end
if game.tick % 500 == 0 then
if this.completed_levels[this.current_level] then
if not this.disable_multi_command_attack then
Func.do_buried_biters_on_completed_levels()
end
end
end
if this.delayed_messages[game.tick] then
game.print(this.delayed_messages[game.tick])
this.delayed_messages[game.tick] = nil
end
if game.tick % 500 == 0 then
if tick % 500 == 0 then
Func.update_evolution_static()
end
if this.clear_items_on_ground_state then
if game.tick % 450 == 0 then
if tick % 450 == 0 then
Func.do_clear_items_on_ground_slowly()
end
if game.tick % 4500 == 0 then
if tick % 4500 == 0 then
Func.run_clear_items_on_ground()
end
end
has_the_game_ended(this)
end
local handle_changes = function ()

View File

@@ -5,7 +5,6 @@ local Autostash = require 'modules.autostash'
local BottomFrame = require 'utils.gui.bottom_frame'
local Misc = require 'utils.commands.misc'
local Map = require 'modules.map_info'
local Scheduler = require 'utils.scheduler'
local Task = require 'utils.task_token'
local Difficulty = require 'modules.difficulty_vote_by_amount'
local Server = require 'utils.server'
@@ -22,11 +21,115 @@ Global.register(
end
)
local set_tech_limit_token = Task.register(
function ()
Public.func.disable_tech()
Public.island_keeper = '[color=blue]Island Keeper: [/color]'
Public.command_color = { r = 0.98, g = 0.66, b = 0.22 }
Public.island_radius_param = 6
Public.decoratives =
{
'red-croton',
'brown-hairy-grass',
'muddy-stump',
'green-bush-mini',
'nuclear-ground-patch',
}
Public.spooky_lines =
{
"The market does not feel as safe as before...",
"Something feels… off around the market.",
"The guards whisper of strange noises beneath the ground.",
"The calm around the market feels forced — too quiet.",
"The soil near the market seems to move when no one is looking."
}
Public.overrun_messages =
{
"[color=red]The ground trembles where the market once stood.[/color]",
"[color=red]Something vast is crawling out from beneath the ruins.[/color]",
"[color=red]The earth splits open — a tide of biters surges forth.[/color]",
"[color=red]The market’s ashes stir… the hive awakens.[/color]",
"[color=red]A dark roar echoes from the crater — they’re not done yet.[/color]",
"[color=red]The air thickens with the sound of chittering and claws.[/color]",
"[color=red]The soil itself seems alive where the market once stood.[/color]",
"[color=red]Smoke rises… and with it, the swarm.[/color]",
"[color=red]The silence breaks — and the ground moves.[/color]",
"[color=red]Biters are pouring out of the ruins![/color]",
"[color=red]The island is being overrun — the swarm is spreading fast![/color]",
"[color=red]A massive horde erupts from the fallen market![/color]",
"[color=red]The market’s collapse has unleashed the swarm![/color]",
"[color=red]The ground bursts open — enemies everywhere![/color]",
"[color=red]The swarm is reclaiming the island![/color]",
"[color=red]The defenders are gone — the biters take everything.[/color]",
"[color=red]They’re coming from below! The island is lost![/color]",
"[color=red]The market is gone… and the swarm claims what’s left.[/color]",
"[color=red]Only ruin remains — the swarm feasts in silence.[/color]",
"[color=red]The island falls quiet, except for the sound of wings and claws.[/color]",
"[color=red]The market’s fall has awakened something unstoppable.[/color]",
}
Public.quality_per_level = {}
for i = 0, 50 do
local n = i / 50
local w_normal, w_uncommon, w_rare, w_epic, w_legendary = 100, 0, 0, 0, 0
if n > 0.2 then
w_normal, w_uncommon = 70, 30
end
)
if n > 0.4 then
w_normal, w_uncommon, w_rare = 65, 25, 10
end
if n > 0.6 then
w_normal, w_uncommon, w_rare, w_epic = 50, 30, 15, 5
end
if n > 0.8 then
w_normal, w_uncommon, w_rare, w_epic, w_legendary = 40, 30, 20, 8, 2
end
local total = w_normal + w_uncommon + w_rare + w_epic + w_legendary
Public.quality_per_level[i] =
{
thresholds =
{
w_normal / total,
(w_normal + w_uncommon) / total,
(w_normal + w_uncommon + w_rare) / total,
(w_normal + w_uncommon + w_rare + w_epic) / total,
}
}
end
Public.valid_enemy_types =
{
['unit'] = true,
['turret'] = true,
['unit-spawner'] = true
}
Public.rock_raffle =
{
'big-sand-rock',
'big-sand-rock',
'big-rock',
'big-rock',
'big-rock',
'big-rock',
'big-rock',
'huge-rock',
'huge-rock'
}
Public.plantable_soil =
{
'natural-jellynut-soil',
'artificial-jellynut-soil',
'natural-yumako-soil',
'artificial-yumako-soil',
'wetland-yumako',
'wetland-jellynut',
}
Public.qualities =
{
@@ -37,6 +140,175 @@ Public.qualities =
'legendary'
}
Public.mining_chances_ores =
{
{ name = 'coal', chance = 26 },
{ name = 'copper-ore', chance = 21 },
{ name = 'iron-ore', chance = 20 },
{ name = 'stone', chance = 15 },
{ name = 'uranium-ore', chance = 10 },
{ name = 'spoilage', chance = 10 },
{ name = 'tungsten-ore', chance = 5 },
{ name = 'holmium-ore', chance = 5 },
{ name = 'calcite', chance = 10 },
{ name = 'lithium', chance = 5 },
{ name = 'jellynut', chance = 5 },
{ name = 'yumako', chance = 5 },
{ name = 'carbon', chance = 5 },
{ name = 'scrap', chance = 5 },
{ name = 'ice', chance = 5 },
}
Public.harvest_raffle_ores = {}
for _, data in pairs(Public.mining_chances_ores) do
for _ = 1, data.chance, 1 do
Public.harvest_raffle_ores[#Public.harvest_raffle_ores + 1] = data.name
end
end
Public.size_of_ore_raffle = #Public.harvest_raffle_ores
Public.raw_ores =
{
'copper-ore',
'iron-ore',
'coal',
'stone',
'uranium-ore',
'calcite',
'tungsten-ore',
'scrap',
}
Public.oil_raffle =
{
'sulfuric-acid-geyser',
'lithium-brine',
'fluorine-vent',
'crude-oil',
}
Public.draw_path_tile_whitelist =
{
['water'] = true,
['deepwater'] = true,
['brash-ice'] = true,
['lava-hot'] = true,
}
Public.path_tile_names =
{
'highland-yellow-rock',
'highland-yellow-rock',
'highland-dark-rock-2',
'highland-dark-rock-2',
'highland-dark-rock',
'highland-dark-rock',
'midland-cracked-lichen-dull',
'midland-cracked-lichen-dull',
'midland-cracked-lichen-dark',
'midland-cracked-lichen-dark',
'midland-turquoise-bark-2',
'midland-turquoise-bark-2',
'midland-turquoise-bark',
'midland-turquoise-bark',
'lowland-dead-skin',
'lowland-dead-skin',
'lowland-dead-skin-2',
'lowland-dead-skin-2',
'lowland-red-vein-dead',
'lowland-red-vein-dead',
}
Public.path_tile_names_dict =
{
['highland-yellow-rock'] = true,
['highland-dark-rock-2'] = true,
['highland-dark-rock'] = true,
['midland-cracked-lichen-dull'] = true,
['midland-cracked-lichen-dark'] = true,
['midland-turquoise-bark-2'] = true,
['midland-turquoise-bark'] = true,
['lowland-dead-skin'] = true,
['lowland-dead-skin-2'] = true,
['lowland-red-vein-dead'] = true,
}
Public.messages =
{
"The infestation spreads its reach...",
"Extending the corruption — please stand by...",
"Creeping tendrils are forming new islands...",
"Nature’s wrath forges a new connection...",
"The island keeper senses movement beneath the waters...",
"Roots dig deep — a new island awakens...",
"The corruption coils ever closer...",
"Spawning path tiles... and probably a few regrets...",
"The ground trembles as the next path takes shape...",
"Building a new route for our doom — hang tight...",
"The infestation hums... something new emerges...",
"Path formation in progress — please don’t fall in...",
"The snake slithers onward... destination unknown...",
"Twisting and turning — the way forward is being formed...",
"Stretching the tendrils of chaos to new lands...",
"Bridging the gap between survival and regret..."
}
Public.gleba_trees =
{
'jellystem',
'yumako-tree'
}
Public.enemy_progression =
{
{
max_level = 2,
biter_types = { 'small-biter', 'small-wriggler-pentapod' },
spitter_types = { 'small-spitter' },
worm_types = { 'small-worm-turret' },
spawner_types = { 'biter-spawner', 'spitter-spawner', 'gleba-spawner-small' },
spawn_qualities = { 'normal' }
},
{
max_level = 5,
biter_types = { 'small-biter', 'medium-biter', 'small-wriggler-pentapod' },
spitter_types = { 'small-spitter', 'medium-spitter', 'small-strafer-pentapod' },
worm_types = { 'small-worm-turret', 'medium-worm-turret' },
spawner_types = { 'biter-spawner', 'spitter-spawner', 'gleba-spawner-small' },
spawn_qualities = { 'normal', 'uncommon' }
},
{
max_level = 8,
biter_types = { 'medium-biter', 'big-biter', 'medium-wriggler-pentapod', 'big-wriggler-pentapod', 'small-stomper-pentapod' },
spitter_types = { 'medium-spitter', 'big-spitter', 'small-strafer-pentapod', 'medium-strafer-pentapod' },
worm_types = { 'medium-worm-turret', 'big-worm-turret' },
spawner_types = { 'biter-spawner', 'spitter-spawner', 'gleba-spawner' },
spawn_qualities = { 'uncommon', 'rare' }
},
{
max_level = 15,
biter_types = { 'big-biter', 'behemoth-biter', 'big-wriggler-pentapod', 'medium-stomper-pentapod' },
spitter_types = { 'big-spitter', 'behemoth-spitter', 'big-strafer-pentapod', 'medium-strafer-pentapod' },
worm_types = { 'big-worm-turret', 'behemoth-worm-turret' },
spawner_types = { 'biter-spawner', 'spitter-spawner', 'gleba-spawner' },
spawn_qualities = { 'rare', 'epic' }
},
{
max_level = math.huge,
biter_types = { 'big-biter', 'behemoth-biter', 'big-wriggler-pentapod', 'big-stomper-pentapod' },
spitter_types = { 'big-spitter', 'behemoth-spitter', 'big-strafer-pentapod', 'medium-strafer-pentapod' },
worm_types = { 'big-worm-turret', 'behemoth-worm-turret' },
spawner_types = { 'biter-spawner', 'spitter-spawner', 'gleba-spawner' },
spawn_qualities = { 'epic', 'legendary' }
}
}
local set_tech_limit_token = Task.register(
function ()
Public.functions.disable_tech()
end
)
local function init_mirror_surface()
if game.surfaces['island'] then
return
@@ -86,9 +358,8 @@ function Public.on_init()
T.main_caption_color = { r = 150, g = 150, b = 0 }
T.sub_caption_color = { r = 0, g = 150, b = 0 }
Scheduler.can_run_scheduler(true)
this.game_lost = false
this.top_label_caption_override = nil
local surface = game.surfaces[1]
surface.ignore_surface_conditions = true
@@ -102,9 +373,9 @@ function Public.on_init()
this.soft_reset = true
this.bridge_position = { x = 0, y = 0 }
this.game_over_if_market_dies = false
this.notified_market_safe = false
this.bridge_position = { x = 0, y = 0 }
local mgs = surface.map_gen_settings
mgs.water = 9.9
@@ -170,7 +441,7 @@ function Public.on_init()
this.player_options = {}
this.autogenerate_islands = false
this.auto_create_islands = false
this.vector = {}
@@ -183,12 +454,8 @@ function Public.on_init()
game.forces.player.set_spawn_position({ 0, 2 }, surface)
this.alive_enemies = 0
this.alive_boss_enemy_count = 0
this.current_level = this.current_level + 1
this.current_stage = 1
this.completed_levels = {}
this.market_positions = {}
@@ -196,9 +463,7 @@ function Public.on_init()
this.rocket_silo = nil
this.connected_islands = {}
this.centered_points =
this.islands_data =
{
[1] = { position = { x = 0, y = 0 }, radius = 200, level = 1 }
}
@@ -223,10 +488,6 @@ function Public.on_init()
'legendary'
}
this.tiles = {}
this.spawned_markets = {}
this.path_tiles = nil
this.max_biters_per_island = 150
@@ -243,7 +504,8 @@ function Public.on_init()
}
end
this.nomed_marked = nil
this.fallen_market = nil
this.printed_location_for_fallen_market = nil
this.loot_stats =
{
@@ -267,6 +529,8 @@ function Public.on_init()
this.market_prices = {}
this.game_over_tasks_done = false
this.drift_corpses_toward_beach_enabled = true
this.clear_items_on_ground_state = true
@@ -291,16 +555,61 @@ function Public.on_init()
this.cooldown_complete_level = game.tick + 100
this.voting_to_progress_enabled = true
this.reverse_start_position = true
this.checked_island = {}
this.time_until_next_island_is_created = nil -- 60 * 60 * 60 -- 1 hour
this.time_until_next_island_is_created_static = nil
this.auto_generate_upon_idle = true
game.forces.enemy.set_friend('player', false)
game.forces.player.set_friend('enemy', false)
game.forces.enemy.set_cease_fire('player', false)
game.forces.player.set_cease_fire('enemy', false)
Public.draw_main_island({ x = 0, y = 0 }, 200)
Difficulty.reset_difficulty_poll({ closing_timeout = game.tick + 36000 })
Difficulty.set_gui_width(20)
Difficulty.set('button_height', 54)
Difficulty.set_difficulties(
{
[1] =
{
name = "I'm too young to die",
index = 1,
value = 1,
color = { r = 0.00, g = 0.25, b = 0.00 },
print_color = { r = 0.00, g = 0.4, b = 0.00 },
count = 0,
strength_modifier = 1.00,
boss_modifier = 6.0
},
[2] =
{
name = 'Hurt me plenty',
index = 2,
value = 4,
color = { r = 0.00, g = 0.00, b = 0.25 },
print_color = { r = 0.0, g = 0.0, b = 0.5 },
count = 0,
strength_modifier = 5,
boss_modifier = 7.0
},
[3] =
{
name = 'Ultra-violence',
index = 3,
value = 10,
color = { r = 255, g = 128, b = 0.00 },
print_color = { r = 255, g = 128, b = 0.00 },
count = 0,
strength_modifier = 12,
boss_modifier = 8.0
}
})
this.difficulty_vote_ended = false
Server.to_discord_embed('** A fresh round of Infestation Islands has begun! **')
Task.set_timeout_in_ticks(100, set_tech_limit_token)

View File

@@ -808,6 +808,9 @@ local set_unit_raffle_token =
local boss_raffle = WD.get('boss_raffle') --[[@as table]]
if level >= 100 and level < 200 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-small-piercing-biter-t2'] = round(2000 - level * 1.75, 6)
biter_raffle['mtn-addon-small-acid-biter-t2'] = round(2000 - level * 1.75, 6)
biter_raffle['mtn-addon-small-explosive-biter-t2'] = round(2000 - level * 1.75, 6)
@@ -833,6 +836,9 @@ local set_unit_raffle_token =
end
if level >= 200 and level < 250 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-small-piercing-biter-t3'] = round(1500 - level * 1.75, 6)
biter_raffle['mtn-addon-small-acid-biter-t3'] = round(1500 - level * 1.75, 6)
biter_raffle['mtn-addon-small-explosive-biter-t3'] = round(1500 - level * 1.75, 6)
@@ -858,6 +864,8 @@ local set_unit_raffle_token =
boss_raffle['mtn-addon-medium-fire-biter-t3'] = round(1500 - (level - 1.50), 6)
end
if level >= 250 and level < 300 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
if Public.is_modded_pt2 then
biter_raffle['medium-wriggler-pentapod'] = round(250 - (level - 250), 6)
end
@@ -874,6 +882,9 @@ local set_unit_raffle_token =
spitter_raffle['mtn-addon-medium-fire-spitter-t1'] = round(250 - (level - 250), 6)
end
if level >= 300 and level < 350 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-medium-piercing-biter-t2'] = round(300 - (level - 300), 6)
biter_raffle['mtn-addon-medium-acid-biter-t2'] = round(300 - (level - 300), 6)
biter_raffle['mtn-addon-medium-explosive-biter-t2'] = round(300 - (level - 300), 6)
@@ -899,6 +910,9 @@ local set_unit_raffle_token =
boss_raffle['mtn-addon-big-fire-biter-t1'] = round(300 - (level - 300), 6)
end
if level >= 350 and level < 400 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-medium-piercing-biter-t3'] = round(350 - (level - 350), 6)
biter_raffle['mtn-addon-medium-acid-biter-t3'] = round(350 - (level - 350), 6)
biter_raffle['mtn-addon-medium-explosive-biter-t3'] = round(350 - (level - 350), 6)
@@ -925,6 +939,8 @@ local set_unit_raffle_token =
end
if level >= 400 and level < 500 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
boss_raffle['mtn-addon-big-piercing-spitter-t3'] = round(400 - (level - 400), 6)
boss_raffle['mtn-addon-big-acid-spitter-t3'] = round(400 - (level - 400), 6)
boss_raffle['mtn-addon-big-explosive-spitter-t3'] = round(400 - (level - 400), 6)
@@ -939,6 +955,9 @@ local set_unit_raffle_token =
end
if level >= 500 and level < 550 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
if Public.is_modded_pt2 then
biter_raffle['big-wriggler-pentapod'] = round(500 - (level - 500) * 2, 6)
end
@@ -967,6 +986,9 @@ local set_unit_raffle_token =
boss_raffle['mtn-addon-behemoth-fire-biter-t1'] = round(500 - (level - 500) * 2, 6)
end
if level >= 550 and level < 600 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-big-piercing-biter-t2'] = round(550 - (level - 550) * 2, 6)
biter_raffle['mtn-addon-big-acid-biter-t2'] = round(550 - (level - 550) * 2, 6)
biter_raffle['mtn-addon-big-explosive-biter-t2'] = round(550 - (level - 550) * 2, 6)
@@ -993,6 +1015,9 @@ local set_unit_raffle_token =
end
if level >= 600 and level < 800 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-big-piercing-biter-t3'] = round(600 - (level - 600) * 2, 6)
biter_raffle['mtn-addon-big-acid-biter-t3'] = round(600 - (level - 600) * 2, 6)
biter_raffle['mtn-addon-big-explosive-biter-t3'] = round(600 - (level - 600) * 2, 6)
@@ -1019,6 +1044,9 @@ local set_unit_raffle_token =
end
if level >= 800 and level < 900 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-behemoth-piercing-biter-t1'] = round((level - 800) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-acid-biter-t1'] = round((level - 800) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-explosive-biter-t1'] = round((level - 800) * 2.75, 6)
@@ -1044,6 +1072,9 @@ local set_unit_raffle_token =
boss_raffle['mtn-addon-boss-fire-spitter-t1'] = round((level - 800) * 2.75, 6)
end
if level >= 900 and level < 1000 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-behemoth-piercing-biter-t2'] = round((level - 900) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-acid-biter-t2'] = round((level - 900) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-explosive-biter-t2'] = round((level - 900) * 2.75, 6)
@@ -1070,6 +1101,9 @@ local set_unit_raffle_token =
end
if level >= 1000 and level < 1100 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-behemoth-piercing-biter-t3'] = round((level - 1000) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-acid-biter-t3'] = round((level - 1000) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-explosive-biter-t3'] = round((level - 1000) * 2.75, 6)
@@ -1096,6 +1130,9 @@ local set_unit_raffle_token =
end
if level >= 1100 and level < 1200 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-behemoth-piercing-biter-t3'] = round((level - 1100) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-acid-biter-t3'] = round((level - 1100) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-explosive-biter-t3'] = round((level - 1100) * 2.75, 6)
@@ -1122,6 +1159,9 @@ local set_unit_raffle_token =
end
if level >= 1200 and level < 1300 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-behemoth-piercing-biter-t3'] = round((level - 1200) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-acid-biter-t3'] = round((level - 1200) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-explosive-biter-t3'] = round((level - 1200) * 2.75, 6)
@@ -1148,6 +1188,9 @@ local set_unit_raffle_token =
end
if level >= 1300 and level < 1400 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-behemoth-piercing-biter-t3'] = round((level - 1300) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-acid-biter-t3'] = round((level - 1300) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-explosive-biter-t3'] = round((level - 1300) * 2.75, 6)
@@ -1174,6 +1217,9 @@ local set_unit_raffle_token =
end
if level >= 1400 then
WD.set('biter_raffle', {})
WD.set('spitter_raffle', {})
WD.set('boss_raffle', {})
biter_raffle['mtn-addon-behemoth-piercing-biter-t3'] = round((level - 1400) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-acid-biter-t3'] = round((level - 1400) * 2.75, 6)
biter_raffle['mtn-addon-behemoth-explosive-biter-t3'] = round((level - 1400) * 2.75, 6)

View File

@@ -411,6 +411,10 @@ function Public.set(key, value)
end
end
function Public.get_closing_timeout()
return math.round((this.closing_timeout - game.tick) / 60, 0)
end
function Public.has_votes_ended()
return game.tick > this.closing_timeout
end

View File

@@ -226,7 +226,7 @@ end
Gui.on_click(
melee_mode_name,
function (event)
local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Mtn v3 Spectate Ready Button')
local is_spamming = SpamProtection.is_spamming(event.player, nil, 'Mtn v3 Melee Mode Button')
if is_spamming then
return
end

View File

@@ -2,7 +2,7 @@
local Global = require 'utils.global'
local Color = require 'utils.color_presets'
-- local SpamProtection = require 'utils.spam_protection'
local SpamProtection = require 'utils.spam_protection'
local Event = require 'utils.event'
local Gui = require 'utils.gui'
local Commands = require 'utils.commands'
@@ -382,10 +382,10 @@ local function on_gui_click(event)
return
end
-- local is_spamming = SpamProtection.is_spamming(player, nil, 'Player Inventory')
-- if is_spamming then
-- return
-- end
local is_spamming = SpamProtection.is_spamming(player, nil, 'Player Inventory')
if is_spamming then
return
end
local data = get_player_data(player)
if not data then

View File

@@ -24,8 +24,6 @@ local floor = math.floor
local random = math.random
local abs = math.abs
local flush_interval = 120 -- every 2 seconds (120 ticks)
local this =
{
enabled = true,
@@ -250,80 +248,6 @@ local function do_action(player, prefix, msg, ban_msg, kill)
end
end
local function flush_deconstruct_log()
if not next(this.deconstruct_queue) then return end
local grouped = {}
for _, data in ipairs(this.deconstruct_queue) do
local player_name = data.player_name
if not grouped[player_name] then
grouped[player_name] = {}
end
table.insert(grouped[player_name], data)
end
local ind = 0
local success_count = 0
local t = math.abs(math.floor(game.tick / 60))
local formatted = FancyTime.short_fancy_time(t)
for player_name, entries in pairs(grouped) do
local entity_counts = {}
local details = {}
for _, e in ipairs(entries) do
local str = '[' .. formatted .. '] '
str = str .. player_name .. ' marked ' .. e.entity_name .. ' for deconstruction'
str = str .. ' at X:'
str = str .. e.x
str = str .. ' Y:'
str = str .. e.y
str = str .. ' '
str = str .. 'surface:' .. e.surface
entity_counts[e.entity_name] = (entity_counts[e.entity_name] or 0) + 1
increment(this.deconstruct_history, str)
if ind == 0 then
table.insert(details, string.format("(%d,%d,surface:%d)", e.x, e.y, e.surface))
end
ind = ind + 1
if ind == #entries then
table.insert(details, string.format("(%d,%d,surface:%d)", e.x, e.y, e.surface))
ind = 0
end
if e.success then
success_count = success_count + 1
end
end
local total = #entries
local summary_parts = {}
for name, count in pairs(entity_counts) do
table.insert(summary_parts, string.format("%dx %s", count, name))
end
local summary_str = table.concat(summary_parts, ", ")
local message = string.format(
"[%s] %s marked %d entities for deconstruction: %s | Positions: %s | Mined: %s",
formatted,
player_name,
total,
summary_str,
table.concat(details, ", "),
success_count .. ' / ' .. total
)
Server.log_antigrief_data('deconstruct', message)
end
this.deconstruct_queue = {}
end
local function do_action_task()
flush_deconstruct_log()
end
local function on_marked_for_deconstruction(event)
if not this.enabled or not event.player_index then return end
@@ -332,27 +256,15 @@ local function on_marked_for_deconstruction(event)
if this.do_not_check_trusted then return end
local playtime = player.online_time
local success = false
local is_trusted = Session.get_trusted_player(player)
if Session.get_session_player(player) then
playtime = player.online_time + Session.get_session_player(player)
success = true
end
if playtime < this.required_playtime and not is_trusted then
event.entity.cancel_deconstruction(player.force.name, player.index)
player.print('You are not accustomed to deconstructing yet.', { r = 0.22, g = 0.99, b = 0.99 })
return
end
table.insert(this.deconstruct_queue,
{
player_name = player.name,
entity_name = event.entity.name,
x = math.floor(event.entity.position.x),
y = math.floor(event.entity.position.y),
surface = event.entity.surface.index,
success = success
})
end
local function on_player_ammo_inventory_changed(event)
@@ -1076,8 +988,6 @@ local function on_player_deconstructed_area(event)
return
end
local area = event.area
local count = surface.count_entities_filtered({ area = area, type = 'resource', invert = true })
local max_count = 0
@@ -1519,6 +1429,5 @@ Event.add(de.on_console_command, on_console_command)
Event.add(de.on_console_chat, on_console_chat)
Event.add(de.on_player_muted, on_player_muted)
Event.add(de.on_player_unmuted, on_player_unmuted)
Event.on_nth_tick(flush_interval, do_action_task)
return Public

View File

@@ -79,12 +79,11 @@ Global.register(
this,
function (tbl)
this = tbl
for _, command in pairs(this.commands) do
setmetatable(command, Public.metatable)
end
end
)
script.register_metatable('CommandData', Public.metatable)
local function conv(v)
if tonumber(v) then
return tonumber(v)

View File

@@ -42,6 +42,7 @@ local function get_lua_object_type_safe(obj)
end
local function inspect_process(item)
if _DEBUG then
local object_name = get_lua_object_type_safe(item)
if object_name and classes[object_name] then
local class = classes[object_name]
@@ -62,6 +63,7 @@ local function inspect_process(item)
end
return serpent.line(info, { comment = false, numformat = '%g' })
end
end
if type(item) ~= 'table' or type(item.__self) ~= 'userdata' then
return item

View File

@@ -116,6 +116,8 @@ Gui.on_click(
local id = Global.get_global(token_id)
local content = dump(id) or 'Could not load data.'
content = 'return ' .. content
if not game.is_multiplayer() then
helpers.write_file(token_id .. '.lua', content, false, 1)
right_panel.text = 'Content written to file on the client: ..\\script-output\\' .. token_id .. '.lua'
@@ -123,8 +125,6 @@ Gui.on_click(
helpers.write_file(token_id .. '.lua', content, false, 0)
right_panel.text = 'Content written to file on the server: ..\\script-output\\' .. token_id .. '.lua'
end
end
)

View File

@@ -34,6 +34,7 @@ local concat = table.concat
local names = {}
-- global
local data = {}
local removed_objects = {}
local settings =
{
mod_gui_top_frame = true,
@@ -43,9 +44,10 @@ local settings =
Public.token =
Global.register(
{ data = data, settings = settings },
{ data = data, removed_objects = removed_objects, settings = settings },
function (tbl)
data = tbl.data
removed_objects = tbl.removed_objects
settings = tbl.settings
end
)
@@ -155,7 +157,10 @@ function Public.set_data(element, value)
data[player_index] = values
end
values[element.index] = { value = value, name = element.name }
local registration_number = script.register_on_object_destroyed(element)
removed_objects[registration_number] = player_index
values[element.index] = { value = value, name = element.name, registration_number = registration_number }
end
end
@@ -392,34 +397,6 @@ function Public.clear(element)
element.clear()
end
local function clear_invalid_data()
if settings.disable_clear_invalid_data then
return
end
for _, player in pairs(game.players) do
local player_index = player.index
local values = data[player_index]
if values then
for k, element in next, values do
if type(element) == 'table' then
for key, obj in next, element do
if type(obj) == 'table' and obj.valid ~= nil then
if not obj.valid then
element[key] = nil
end
end
end
if type(element) == 'userdata' and not element.valid then
values[k] = nil
end
end
end
end
end
end
Event.on_nth_tick(300, clear_invalid_data)
local function handler_factory(event_id)
local handlers
@@ -922,6 +899,19 @@ local function draw_main_frame(player)
return frame, inside_frame
end
local function on_object_destroyed(event)
local player_index = removed_objects[event.registration_number]
if not player_index then return end
local element_index = event.useful_id
removed_objects[event.registration_number] = nil
local player_data = data[player_index]
if player_data then
player_data[element_index] = nil
end
end
function Public.get_content(player)
local left_frame = Public.get_main_frame(player)
@@ -1168,6 +1158,8 @@ Event.add(
end
)
Event.add(defines.events.on_object_destroyed, on_object_destroyed)
function Public.data()
return data
end

View File

@@ -276,12 +276,12 @@ function Inspector:putTable(t)
if t == inspect.KEY or t == inspect.METATABLE then
self:puts(tostring(t))
elseif self:alreadyVisited(t) then
self:puts('<table ', self:getId(t), '>')
self:puts('[[<ref to table ', self:getId(t), '>]]')
elseif self.level >= self.depth then
self:puts('{...}')
else
if self.tableAppearances[t] > 1 then
self:puts('<', self:getId(t), '>')
self:puts('[[<', self:getId(t), '>]],')
end
local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t)

View File

@@ -1,7 +1,5 @@
-- Created by Gerkiz
local Global = require 'utils.global'
local Server = require 'utils.server'
local Event = require 'utils.event'
local Core = require 'utils.core'
@@ -42,9 +40,9 @@ Task.__index = Task
script.register_metatable('Task', Task)
--- Registers a callback for a task (data stage / module load time; not at runtime).
---@param name string
---@param fn function
---@param uid? string|number
---@param name string - Helps identify the task in the debugger
---@param fn function - The callback function
---@param uid? string|number - The unique identifier for the task
---@return number|string|nil
function Public.register_function(name, fn, uid)
if game then error('Cannot register functions in runtime') end
@@ -62,9 +60,9 @@ function Public.register_function(name, fn, uid)
end
--- Gets the function by id
---@param id number|string
---@return function|nil
---@return string|nil
---@param id number|string - The unique identifier for the task
---@return function|nil - The callback function
---@return string|nil - The name of the task
function Public.get_function_by_id(id)
return loaded[id], named[id]
end
@@ -96,13 +94,13 @@ end
--- Gets the callback for this task
---@return function|nil
---@return function|nil - The callback function
function Task:get_callback()
return Public.get_function_by_id(self._uid)
end
--- Sets the data for this task
---@param tbl table
---@param tbl table - The data for the task
---@return Task - Self for chaining
function Task:set_data(tbl)
self._data = tbl
@@ -117,9 +115,9 @@ function Task:log()
end
--- Creates a new child task
---@param delay number
---@param uid number|string|nil
---@return Task
---@param delay number - The delay in ticks before the task is executed
---@param uid number|string|nil - The unique identifier for the task
---@return Task - The new child task
function Task:new_child(delay, uid)
local c = new_task(delay, uid)
c._parent = self
@@ -128,6 +126,9 @@ function Task:new_child(delay, uid)
return c
end
--- Runs the task
---@param current_tick number - The current tick
---@return boolean - Whether the task was executed
function Task:run(current_tick)
if self._tick and self._tick > current_tick then return false end
if self._completed then return false end
@@ -143,6 +144,9 @@ function Task:run(current_tick)
return true
end
--- Schedules the next task in the DFS
---@param n Task - The task to schedule
---@param current_tick number - The current tick
local function schedule_next_in_dfs(n, current_tick)
while n do
if n._next_child_ix <= #n._children then
@@ -159,9 +163,9 @@ local function schedule_next_in_dfs(n, current_tick)
end
--- Creates a new task
---@param delay number
---@param uid number|string|nil
---@return Task
---@param delay number - The delay in ticks before the task is executed
---@param uid number|string|nil - The unique identifier for the task
---@return Task - The new task
function Public.new(delay, uid)
local t = new_task(delay, uid)
t._tick = game.tick + normalize_delay(delay)
@@ -170,19 +174,29 @@ function Public.new(delay, uid)
end
--- Sets whether the scheduler can run
---@param condition boolean - Whether the scheduler can run
function Public.can_run_scheduler(condition)
---@param condition boolean - Whether the scheduler can run (true to run, false to stop)
function Public.set_can_run_scheduler(condition)
this.can_run_scheduler = condition or false
end
--- Clears the tasks
function Public.clear_tasks()
this.tasks = {}
this.next_id = 0
Core.log('Scheduler tasks have been cleared!')
end
Event.add(defines.events.on_tick,
function ()
local tick = game.tick
local can_run_scheduler = this.can_run_scheduler
if not can_run_scheduler then
this.tasks = {}
Server.output_script_data('Scheduler task has been cleared and stopped!')
return
end
if not this.tasks or #this.tasks == 0 then
this.next_id = 0
return
end

View File

@@ -1,12 +1,16 @@
local Event = require 'utils.event'
local Global = require 'utils.global'
local Commands = require 'utils.commands'
local Server = require 'utils.server'
local Public = {}
local this = {
local this =
{
prevent_spam = {}, -- the default table where all player indexes will be stored
default_tick = 10, -- this defines the default tick to check whether or not a user is spamming a button.
debug_text = false,
debug_spam = false
debug_spam = false,
show_debug_text_for = {}
}
local main_text = '[Spam Info] '
@@ -22,14 +26,14 @@ local function debug_text(str)
if not this.debug_text then
return
end
log(main_text .. str)
Server.output_script_data(main_text .. str)
end
local function debug_spam(str)
if not this.debug_spam then
return
end
log(main_text .. str)
Server.output_script_data(main_text .. str)
end
function Public.reset_spam_table()
@@ -59,7 +63,15 @@ function Public.is_spamming(player, value_to_compare, text)
end
if text then
debug_text('Frame: ' .. text)
if this.show_debug_text_for then
for name, _ in pairs(this.show_debug_text_for) do
local debug_player = game.get_player(name)
if debug_player and debug_player.valid then
debug_player.print('Player ' .. player.name .. ' clicked on: ' .. text .. ' on surface: ' .. player.surface.name .. ' at position: ' .. player.position.x .. ', ' .. player.position.y .. ' at tick: ' .. game.tick)
end
end
end
debug_text('Player ' .. player.name .. ' clicked on: ' .. text .. ' on surface: ' .. player.surface.name .. ' at position: ' .. player.position.x .. ', ' .. player.position.y .. ' at tick: ' .. game.tick)
end
if game.tick_paused then
@@ -67,7 +79,7 @@ function Public.is_spamming(player, value_to_compare, text)
end
if this.debug_spam then
log(debug.traceback())
Server.output_script_data(debug.traceback())
end
local tick = game.tick
@@ -126,4 +138,41 @@ Event.on_init(
end
)
Commands.new('sp_debug_text', 'Spam Protection - Shows the debug text for when players are clicking gui buttons.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
this.debug_text = state
player.print('Debug text for spam protection has been ' .. (state and 'enabled' or 'disabled') .. '!')
end
)
Commands.new('sp_debug_spam', 'Spam Protection - Shows the debug spam for when players are clicking gui buttons.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
this.debug_spam = state
player.print('Debug spam for spam protection has been ' .. (state and 'enabled' or 'disabled') .. '!')
end
)
Commands.new('sp_print_text', 'Spam Protection - Prints the debug text for when players are clicking gui buttons to your console.')
:require_admin()
:add_parameter('state', false, 'boolean')
:callback(
function (player, state)
this.show_debug_text_for = this.show_debug_text_for or {}
if state then
this.show_debug_text_for[player.name] = true
player.print('Debug text for spam protection has been enabled!')
else
this.show_debug_text_for[player.name] = nil
player.print('Debug text for spam protection has been disabled!')
end
end
)
return Public

View File

@@ -275,7 +275,7 @@ end)
Commands.new('undo_player_actions', 'Undoes the actions of a player as a player by creating a poll.')
:add_parameter('player', false, 'player')
:require_validation('Only utilize this command if the player is jailed and has entities in the undo queue.')
:require_playtime(60 * 60 * 60 * 24 * 40) -- 30 days
:require_playtime(60 * 60 * 60 * 24 * 40) -- 40 days
:callback(function (player, target_player)
if not target_player or not target_player.valid then
return player.print('Player is not valid.')