1
0
mirror of https://github.com/Refactorio/RedMew.git synced 2025-03-17 21:08:08 +02:00

Update Frontier to V3 (#1425)

* Enemy AI

* Change enemy spawn wave logic

* Add bard messages

* Update changelog

* Fox reseed

* Fix luachecks
This commit is contained in:
RedRafe 2024-08-15 21:05:18 +02:00 committed by GitHub
parent 162b3aa0f0
commit 3dabc84ccc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 548 additions and 108 deletions

View File

@ -1,3 +1,4 @@
local Color = require 'resources.color_presets'
local table = require 'utils.table' local table = require 'utils.table'
local Event = require 'utils.event' local Event = require 'utils.event'
local Global = require 'utils.global' local Global = require 'utils.global'
@ -27,11 +28,11 @@ function Public.show_start_up(player)
end end
if _DEBUG and game.is_multiplayer() then if _DEBUG and game.is_multiplayer() then
game.print('THIS MULTIPLAYER MAP IS IN DEBUG!!!') game.print('THIS MULTIPLAYER MAP IS IN DEBUG!!!', Color.warning)
elseif _DEBUG then elseif _DEBUG then
game.print("DON'T LAUNCH THIS MAP! DEBUG MODE IS ENABLED!!!") game.print("DON'T LAUNCH THIS MAP! DEBUG MODE IS ENABLED!!!", Color.warning)
elseif not _DEBUG and not game.is_multiplayer() then elseif not _DEBUG and not game.is_multiplayer() then
player.print('To change your name in single-player, open chat and type the following /c game.player.name = "your_name"') player.print('To change your name in single-player, open chat and type the following /c game.player.name = "your_name"', Color.info)
end end
end end

View File

@ -215,5 +215,5 @@ err_no_armor=[color=blue][Battery recharge][/color] No valid armor to charge was
err_no_accumulators=[color=blue][Battery recharge][/color] No accumulators nearby. err_no_accumulators=[color=blue][Battery recharge][/color] No accumulators nearby.
[clear_corpses] [clear_corpses]
count=[color=blue][Cleaner][/color] __1__ __plural_for_parameter_1_{1=__1__ corpse|rest=__1__ corpses}__ removed. count=[color=blue][Cleaner][/color] __1__ __plural_for_parameter_1_{1=corpse|rest=corpses}__ removed.
clear=[color=blue][Cleaner][/color] already clear. clear=[color=blue][Cleaner][/color] already clear.

View File

@ -8,10 +8,12 @@ local math = require 'utils.math'
local MGSP = require 'resources.map_gen_settings' local MGSP = require 'resources.map_gen_settings'
local Noise = require 'map_gen.shared.simplex_noise' local Noise = require 'map_gen.shared.simplex_noise'
local PriceRaffle = require 'features.price_raffle' local PriceRaffle = require 'features.price_raffle'
local Ranks = require 'resources.ranks'
local RS = require 'map_gen.shared.redmew_surface' local RS = require 'map_gen.shared.redmew_surface'
local ScenarioInfo = require 'features.gui.info' local ScenarioInfo = require 'features.gui.info'
local ScoreTracker = require 'utils.score_tracker' local ScoreTracker = require 'utils.score_tracker'
local Sounds = require 'utils.sounds' local Sounds = require 'utils.sounds'
local Table = require 'utils.table'
local Toast = require 'features.gui.toast' local Toast = require 'features.gui.toast'
local Token = require 'utils.token' local Token = require 'utils.token'
local Task = require 'utils.task' local Task = require 'utils.task'
@ -28,6 +30,8 @@ local simplex = Noise.d2
local SECOND = 60 local SECOND = 60
local MINUTE = SECOND * 60 local MINUTE = SECOND * 60
local register_on_entity_destroyed = script.register_on_entity_destroyed
--[[ --[[
Scenario info: Frontier Scenario info: Frontier
From 'Frontier Extended' mod: https://mods.factorio.com/mod/Frontier-Extended From 'Frontier Extended' mod: https://mods.factorio.com/mod/Frontier-Extended
@ -58,6 +62,9 @@ Your mission, should you choose to accept it, is to journey through this ribbon
In [font=default-bold]Frontier[/font], your wits will be tested as you evolve from a mere survivor to an engineering genius capable of taming the land and launching your final escape. Build a thriving factory, and prepare to conquer both nature and the relentless horde in a race against time. But remember, the frontier waits for no one. Will you make your mark on this alien world or become another lost tale in the void of space? In [font=default-bold]Frontier[/font], your wits will be tested as you evolve from a mere survivor to an engineering genius capable of taming the land and launching your final escape. Build a thriving factory, and prepare to conquer both nature and the relentless horde in a race against time. But remember, the frontier waits for no one. Will you make your mark on this alien world or become another lost tale in the void of space?
]]) ]])
ScenarioInfo.set_new_info([[ ScenarioInfo.set_new_info([[
2024-08-15:
- Fixed desyncs
- Fixed biter waves
2024-08-10: 2024-08-10:
- Added enemy turrets - Added enemy turrets
- Added soft reset - Added soft reset
@ -121,8 +128,16 @@ local this = {
min_step = 500, -- minimum tiles to move min_step = 500, -- minimum tiles to move
max_distance = 100000, -- maximum x distance of rocket silo max_distance = 100000, -- maximum x distance of rocket silo
-- Revived enemies -- Enemy data
invincible = {} invincible = {},
target_entities = {},
unit_groups = {},
-- Lobby
lobby_enabled = false,
-- Debug
_DEBUG_AI = false,
} }
Global.register(this, function(tbl) this = tbl end) Global.register(this, function(tbl) this = tbl end)
@ -148,11 +163,32 @@ ScoreTracker.register(rocket_launches_name, {'frontier.rockets_to_launch'}, '[im
local escape_player = false local escape_player = false
-- == DEBUG ===================================================================
local Debug = {}
function Debug.print_admins(msg, color)
for _, p in pairs(game.connected_players) do
if p.admin then
p.print(msg, color)
end
end
end
function Debug.print(msg, color)
for _, p in pairs(game.connected_players) do
p.print(msg, color)
end
end
function Debug.log(data)
log(serpent.block(data))
end
-- == LOBBY =================================================================== -- == LOBBY ===================================================================
local Lobby = {} local Lobby = {}
Lobby.enabled = false
Lobby.name = 'nauvis' Lobby.name = 'nauvis'
Lobby.mgs = { Lobby.mgs = {
water = 0, water = 0,
@ -403,7 +439,7 @@ function Terrain.create_wall(x, w)
end end
for y = -this.height * 16, this.height * 16 do for y = -this.height * 16, this.height * 16 do
for j = 0, w do for j = 0, w - 1 do
local e = surface.create_entity { local e = surface.create_entity {
name = 'stone-wall', name = 'stone-wall',
position = { x + j, y }, position = { x + j, y },
@ -413,36 +449,207 @@ function Terrain.create_wall(x, w)
e.destructible = false e.destructible = false
end end
end end
local tiles = {}
for j = -4, w - 1 + 4 do
for y = -this.height * 16, this.height * 16 do
tiles[#tiles +1] = { name = 'hazard-concrete-left', position = { x = x + j, y = y }}
end
end
for j = -1, w do
for y = -this.height * 16, this.height * 16 do
tiles[#tiles +1] = { name = 'concrete', position = { x = x + j, y = y }}
end
end
surface.set_tiles(tiles, true)
end end
-- == MAIN ==================================================================== -- == Enemy ===================================================================
function Main.nuclear_explosion(entity) local Enemy = {}
local surface = entity.surface
local center_position = entity.position Enemy.target_entity_types = {
local force = entity.force ['accumulator'] = true,
surface.create_entity { ['assembling-machine'] = true,
name = 'atomic-rocket', ['beacon'] = true,
position = center_position, ['boiler'] = true,
force = force, ['furnace'] = true,
source = center_position, ['generator'] = true,
target = center_position, ['heat-interface'] = true,
max_range = 1, ['lab'] = true,
speed = 0.1 ['mining-drill'] = true,
['offshore-pump'] = true,
['reactor'] = true,
['roboport'] = true,
['solar-panel'] = true,
}
Enemy.commands = {
move = function(unit_group, position)
local data = Enemy.ai_take_control(unit_group)
if not position then
Enemy.ai_processor(unit_group)
return
end
data.position = position
data.stage = Enemy.stages.move
unit_group.set_command {
type = defines.command.go_to_location,
destination = position,
radius = 3,
distraction = defines.distraction.by_enemy
} }
unit_group.start_moving()
if this._DEBUG_AI then
Debug.print_admins(string.format('AI [id=%d] | cmd: MOVE [gps=%.2f,%.2f,%s]', unit_group.group_number, position.x, position.y, unit_group.surface.name), Color.dark_gray)
end
end,
scout = function(unit_group, position)
local data = Enemy.ai_take_control(unit_group)
if not position then
Enemy.ai_processor(unit_group)
return
end
data.position = position
data.stage = Enemy.stages.scout
unit_group.set_command {
type = defines.command.attack_area,
destination = position,
radius = 15,
distraction = defines.distraction.by_enemy
}
unit_group.start_moving()
if this._DEBUG_AI then
Debug.print_admins(string.format('AI [id=%d] | cmd: SCOUT [gps=%.2f,%.2f,%s]', unit_group.group_number, position.x, position.y, unit_group.surface.name), Color.dark_gray)
end
end,
attack = function(unit_group, target)
local data = Enemy.ai_take_control(unit_group)
if not (target and target.valid) then
Enemy.ai_processor(unit_group)
return
end
data.target = target
data.stage = Enemy.stages.attack
unit_group.set_command {
type = defines.command.attack,
target = target,
distraction = defines.distraction.by_damage
}
if this._DEBUG_AI then
Debug.print_admins(string.format('AI [id=%d] | cmd: ATTACK [gps=%.2f,%.2f,%s] (type = %s)', unit_group.group_number, target.position.x, target.position.y, unit_group.surface.name, target.type), Color.dark_gray)
end
end
}
Enemy.stages = {
pending = 1,
move = 2,
scout = 3,
attack = 4,
fail = 5,
}
function Enemy.ai_take_control(unit_group)
if not this.unit_groups[unit_group.group_number] then
this.unit_groups[unit_group.group_number] = {
unit_group = unit_group
}
end
return this.unit_groups[unit_group.group_number]
end end
function Main.spawn_enemy_wave(position) function Enemy.ai_stage_by_distance(posA, posB)
local x_axis = posA.x - posB.x
local y_axis = posA.y - posB.y
local distance = math_sqrt(x_axis * x_axis + y_axis * y_axis)
if distance <= 15 then
return Enemy.stages.attack
elseif distance <= 32 then
return Enemy.stages.scout
else
return Enemy.stages.move
end
end
function Enemy.ai_processor(unit_group, result)
if not (unit_group and unit_group.valid) then
return
end
local data = this.unit_groups[unit_group.group_number]
if not data then
return
end
if data.failed_attempts and data.failed_attempts >= 3 then
this.unit_groups[unit_group.group_number] = nil
return
end
if not result or result == defines.behavior_result.fail or result == defines.behavior_result.deleted then
data.stage = Enemy.stages.pending
end
if result == defines.behavior_result.success and (data.stage and data.stage == Enemy.stages.attack) then
data.stage = Enemy.stages.pending
end
data.stage = data.stage or Enemy.stages.pending
if data.stage == Enemy.stages.pending then
local surface = unit_group.surface
data.target = surface.find_nearest_enemy_entity_with_owner {
position = unit_group.position,
max_distance = this.rocket_step * 4,
force = 'enemy',
}
if not (data.target and data.target.valid) then
this.unit_groups[unit_group.group_number] = nil
return
end
data.position = data.target.position
data.stage = Enemy.ai_stage_by_distance(data.position, unit_group.position)
else
data.stage = data.stage + 1
end
if this._DEBUG_AI then
Debug.print_admins(string.format('AI [id=%d] | status: %d', unit_group.group_number, data.stage), Color.dark_gray)
end
if data.stage == Enemy.stages.move then
Enemy.commands.move(unit_group, data.target)
elseif data.stage == Enemy.stages.scout then
Enemy.commands.scout(unit_group, data.target)
elseif data.stage == Enemy.stages.attack then
Enemy.commands.attack(unit_group, data.target)
else
data.failed_attempts = (data.failed_attempts or 0) + 1
if this._DEBUG_AI then
Debug.print_admins(string.format('AI [id=%d] | FAIL | stage: %d | attempts: %d', unit_group.group_number, data.stage, data.failed_attempts), Color.dark_gray)
end
data.stage, data.position, data.target = nil, nil, nil
Enemy.ai_processor(unit_group, nil)
end
end
function Enemy.spawn_enemy_wave(position)
local surface = RS.get_surface() local surface = RS.get_surface()
local find_position = surface.find_non_colliding_position local find_position = surface.find_non_colliding_position
local spawn = surface.create_entity local spawn = surface.create_entity
local current_tick = game.tick local current_tick = game.tick
local unit_group = surface.create_unit_group { position = position, force = 'enemy' }
local max_time = math_max(MINUTE, MINUTE * math_ceil(0.5 * (this.rockets_launched ^ 0.5))) local max_time = math_max(MINUTE, MINUTE * math_ceil(0.5 * (this.rockets_launched ^ 0.5)))
local radius = 20 local radius = 20
for _ = 1, 24 do for _ = 1, 12 do
local name = math_random() > 0.15 and 'behemoth-worm-turret' or 'big-worm-turret' local name
if this.rockets_launched < 3 then
name = math_random() > 0.15 and 'big-worm-turret' or 'medium-worm-turret'
else
name = math_random() > 0.15 and 'behemoth-worm-turret' or 'big-worm-turret'
end
local about = find_position(name, { x = position.x + math_random(), y = position.y + math_random() }, radius, 0.2) local about = find_position(name, { x = position.x + math_random(), y = position.y + math_random() }, radius, 0.2)
if about then if about then
local worm = spawn { name = name, position = about, force = 'enemy', move_stuck_players = true } local worm = spawn { name = name, position = about, force = 'enemy', move_stuck_players = true }
@ -454,20 +661,97 @@ function Main.spawn_enemy_wave(position)
radius = 32 radius = 32
for _ = 1, 20 do for _ = 1, 20 do
local name = math_random() > 0.3 and 'behemoth-biter' or 'behemoth-spitter' local name
if this.rockets_launched < 3 then
name = math_random() > 0.3 and 'big-biter' or 'big-spitter'
else
name = math_random() > 0.3 and 'behemoth-biter' or 'behemoth-spitter'
end
local about = find_position(name, { x = position.x + math_random(), y = position.y + math_random() }, radius, 0.6) local about = find_position(name, { x = position.x + math_random(), y = position.y + math_random() }, radius, 0.6)
if about then if about then
local unit = spawn { name = name, position = about, force = 'enemy', move_stuck_players = true } local unit = spawn { name = name, position = about, force = 'enemy', move_stuck_players = true }
this.invincible[unit.unit_number] = { this.invincible[unit.unit_number] = {
time_to_live = current_tick + math_random(MINUTE, max_time) time_to_live = current_tick + math_random(MINUTE, max_time)
} }
unit_group.add_member(unit)
end
end
if unit_group.valid then
Enemy.ai_take_control(unit_group)
Enemy.ai_processor(unit_group)
end
end
Enemy.spawn_enemy_wave_token = Token.register(Enemy.spawn_enemy_wave)
function Enemy.on_enemy_died(entity)
local uid = entity.unit_number
local data = this.invincible[uid]
if not data then
return
end
if game.tick > data.time_to_live then
this.invincible[uid] = nil
return
end
local new_entity = entity.surface.create_entity {
name = entity.name,
position = entity.position,
force = entity.force,
}
this.invincible[new_entity.unit_number] = {
time_to_live = data.time_to_live,
}
this.invincible[uid] = nil
if new_entity.type == 'unit' then
new_entity.set_command(entity.command)
if entity.unit_group then
entity.unit_group.add_member(new_entity)
end end
end end
end end
Main.spawn_enemy_wave_token = Token.register(Main.spawn_enemy_wave)
function Main.spawn_turret_outpost(position) function Enemy.on_spawner_died(event)
if position.x < this.right_boundary + this.wall_width then local entity = event.entity
local chance = math_random()
if chance > this.loot_chance then
return
end
local budget = this.loot_budget + entity.position.x * 2.75
budget = budget * math_random(25, 175) * 0.01
local player = false
if event.cause and event.cause.type == 'character' then
player = event.cause.player
end
if player and player.valid then
budget = budget + (this.death_contributions[player.name] or 0) * 80
end
if math_random(1, 128) == 1 then budget = budget * 4 end
if math_random(1, 256) == 1 then budget = budget * 4 end
budget = budget * this.loot_richness
local chest = entity.surface.create_entity { name = 'steel-chest', position = entity.position, force = 'player', move_stuck_players = true }
chest.destructible = false
for i = 1, 3 do
local item_stacks = PriceRaffle.roll(math_floor(budget / 3 ) + 1, 48)
for _, item_stack in pairs(item_stacks) do
chest.insert(item_stack)
end
end
if player then
Toast.toast_player(player, nil, {'frontier.loot_chest'})
end
end
function Enemy.spawn_turret_outpost(position)
if position.x < this.right_boundary * 32 + this.wall_width then
return return
end end
@ -518,6 +802,136 @@ function Main.spawn_turret_outpost(position)
end end
end end
function Enemy.start_tracking(entity)
if not Enemy.target_entity_types[entity.type] then
return
end
if entity.force.name == 'enemy' or entity.force.name == 'neutral' then
return
end
register_on_entity_destroyed(entity)
this.target_entities[entity.unit_number] = entity
end
function Enemy.stop_tracking(entity)
this.target_entities[entity.unit_number] = nil
end
function Enemy.get_target()
return Table.get_random_dictionary_entry(this.target_entities, false)
end
function Enemy.nuclear_explosion(entity)
local surface = entity.surface
local center_position = entity.position
surface.create_entity {
name = 'atomic-rocket',
position = center_position,
force = 'enemy',
source = center_position,
target = center_position,
max_range = 1,
speed = 0.1
}
end
function Enemy.artillery_explosion(data)
local surface = game.get_surface(data.surface_name)
local position = data.position
local r = 20
surface.create_entity {
name = 'artillery-projectile',
position = position,
force = 'enemy',
source = position,
target = { x = position.x + math_random(-r, r) + math_random(), y = position.y + math_random(-r, r) + math_random() },
max_range = 1,
speed = 0.1
}
end
Enemy.artillery_explosion_token = Token.register(Enemy.artillery_explosion)
-- == MAIN ====================================================================
local bard_messages_1 = {
[1] = {
[[The rocket has successfully launched! The Kraken has accepted your offering... for now.]],
[[A distant rumble echoes through the air. The Kraken stirs in the depths.]],
[[You can almost feel the waters shifting. What will the Kraken do with your gift?]],
[[The sky darkens as the rocket ascends. Is the Kraken pleased or plotting revenge?]],
[[You have awakened something ancient. Expect the unknown in the next moments.]],
[[A whisper echoes in your mind: 'You dare disturb my slumber?']],
[[The Kraken watches from below, its tendrils coiling in anticipation.]],
[[A chilling wind sweeps across your factory—a sign the Kraken is not to be trifled with.]],
[[The ground trembles as the rocket disappears into the sky... what price will you pay?]],
[[An ominous shadow looms beneath the waves. The Kraken has taken notice.]],
},
[2] = {
[[As the rocket pierces the sky, dark waters tremble in anticipation... something stirs.]],
[[A whispering gale caresses the land; the Kraken's essence begins to awaken.]],
[[Shadows flicker at the water's edge. The depths conceal secrets you cannot fathom.]],
[[Eyes unblinking watch from the abyss; your offering has been noted with curiosity... or contempt.]],
[[The air grows thick with foreboding. An ancient power rouses from its slumber.]],
[[From the deep, a voice resonates: 'What price have you paid for your hubris?']],
[[The ocean churns as if agitated. The Kraken's mood is as unpredictable as the tempest.]],
[[Unseen tendrils drift closer to your shores. What has been awakened cannot be unmade.]],
[[A shiver runs through the ground, as if the earth itself fears the Kraken's gaze.]],
[[With each second that passes, the Kraken's presence suffocates the air around you.]],
},
[3] = {
[[Hark! The rocket soars to the heavens, yet below, the Kraken stirs in its slumber deep, its ancient wrath looms ever near!]],
[[Lo, the winds whisper secrets of the abyss; the Kraken watches, its tendrils twitching in delight or dread—can you tell which it shall be?]],
[[By the light of the fading suns, shadows dance upon the waves. A gift offered, but at what terrible cost? Beware the storm that brews!]],
[[Listen well, dear traveler! For the depths grow restless, and the Kraken, master of the abyss, awakens to claim its due!]],
[[Oh, fear the echo of the deep! A creature of legend stirs, its gaze upon your fortress—wreathed in shadows, it feasts on your hubris!]],
[[Beware the churning sea, where the ancient beast stirs; your paid price may be your eternal plight—what horrors shall it unleash?]],
[[From depths unknown, an unsettling murmur rises, 'You dared disturb me, foolish one! Know now the depths of my disdain!']],
[[As the rocket ascends, the sky darkens and trembles, for the Kraken's heart beats wildly—can you sense its lurking fury?]],
[[Oremus, oh heed my words! For beneath the surface lies a horror awakened—a vengeful force hungering for the taste of calamity!]],
[[An eternal shadow looms, beckoned by your ambition! What horrors have you invited to dance upon your very threshold?]],
},
}
local bard_messages_2 = {
[1] = {
[[The surface of the water begins to churn ominously... something awakens.]],
[[An unsettling roar reverberates through the land. The Kraken's wrath is near.]],
[[A dark cloud forms above, casting a shadow over your factory. The Kraken is displeased.]],
[[Tentacles rise from the deep, a harbinger of chaos approaching your base.]],
[[The Kraken demands retribution! Prepare for the onslaught!]],
[[A storm brews on the horizon; the Kraken lashes out in fury.]],
[[The air grows thick with tension as a monstrous wave approaches your shores.]],
[[All around you, the atmosphere shifts—something is very wrong.]],
[[The Kraken's vengeance is upon you! Brace yourself for the inevitable.]],
[[In its rage, the Kraken unleashes its fury! The biter swarm descends!]],
},
[2] = {
[[The surface roils ominously, dark waters boiling as wrath takes form.]],
[[A haunting cry echoes across the landscape—an ancient beast calls for retribution.]],
[[Dark clouds gather like a shroud, heralding calamity born of the abyss.]],
[[Tendrils of shadow writhe beneath the waves—a prelude to the storm of vengeance.]],
[[The Kraken's disdain unfurls like a tempest, a dark promise of chaos and destruction.]],
[[An unnatural stillness settles, broken only by the distant crash of furious waves.]],
[[The deep stirs with malice. Can you hear the heartbeat of your impending doom?]],
[[In the twilight, the Kraken's fury eclipses all hope, a symphony of despair draws near.]],
[[As specters rise from the depths, their intent is clear: retribution is swift and merciless.]],
[[Your fate is entwined with the Kraken's ire—prepare for the inexorable tide of darkness.]],
},
[3] = {
[[Attend! A tempest brews upon darkened waters, rage unfurling like a ravenous beast—your time is nigh!]],
[[The Kraken's call resounds, echoing through the night; from the abyss it comes, cloaked in shadows and dread!]],
[[A shudder passes through the land, and ominous clouds converge—gaze now upon the darkening sky, for doom draws near!]],
[[Dread whisperings of the deep herald the coming tempest; the Kraken rises, eager to reclaim what is owed with swift malice!]],
[[Foul winds carry the scent of vengeance. The Kraken's ire is unbound, and soon your fortress shall feel its dark embrace!]],
[[In the twilight haze, a cacophony of doom stirs—behold, the tide of destruction approaches with unholy intent!]],
[[Tremble now, for the Kraken awakens! A chorus of despair sings forth, heralding the swarm that comes, hungry and relentless!]],
[[The ancient beast unleashes fury upon your path—a storm of chaos born from the depths, bringing forth a wretched tide!]],
[[Beware! The Kraken's wrath is a specter unshackled, and every heartbeat draws nearer to the end of your peace!]],
[[Thus, from beneath the waves, chaos and slaughter arise—oh, brave souls, face the horrors your hubris has conjured!]],
},
}
function Main.win() function Main.win()
this.scenario_finished = true this.scenario_finished = true
game.set_game_state { game_finished = true, player_won = true, can_continue = true, victorious_force = 'player' } game.set_game_state { game_finished = true, player_won = true, can_continue = true, victorious_force = 'player' }
@ -531,69 +945,6 @@ function Main.win()
Task.set_timeout(92, Main.restart_game_token) Task.set_timeout(92, Main.restart_game_token)
end end
function Main.on_spawner_died(event)
local entity = event.entity
local chance = math_random()
if chance > this.loot_chance then
return
end
local budget = this.loot_budget + entity.position.x * 2.75
budget = budget * math_random(25, 175) * 0.01
local player = false
if event.cause and event.cause.type == 'character' then
player = event.cause.player
end
if player and player.valid then
budget = budget + (this.death_contributions[player.name] or 0) * 80
end
if math_random(1, 128) == 1 then budget = budget * 4 end
if math_random(1, 256) == 1 then budget = budget * 4 end
budget = budget * this.loot_richness
local chest = entity.surface.create_entity { name = 'steel-chest', position = entity.position, force = 'player', move_stuck_players = true }
chest.destructible = false
for i = 1, 3 do
local item_stacks = PriceRaffle.roll(math_floor(budget / 3 ) + 1, 48)
for _, item_stack in pairs(item_stacks) do
chest.insert(item_stack)
end
end
if player then
Toast.toast_player(player, nil, {'frontier.loot_chest'})
end
end
function Main.on_enemy_died(entity)
local uid = entity.unit_number
local data = this.invincible[uid]
if not data then
return
end
if game.tick > data.time_to_live then
this.invincible[uid] = nil
return
end
local new_entity = entity.surface.create_entity {
name = entity.name,
position = entity.position,
force = entity.force,
}
this.invincible[new_entity.unit_number] = {
time_to_live = data.time_to_live,
}
this.invincible[uid] = nil
if new_entity.type == 'unit' then
new_entity.set_command(entity.command)
end
end
Main.play_sound_token = Token.register(Sounds.notify_all) Main.play_sound_token = Token.register(Sounds.notify_all)
Main.restart_message_token = Token.register(function(seconds) Main.restart_message_token = Token.register(function(seconds)
@ -645,8 +996,18 @@ function Main.move_silo(position)
end end
end end
game.print({'frontier.empty_rocket'}) game.print({'frontier.empty_rocket'})
Main.nuclear_explosion(chest) Enemy.nuclear_explosion(chest)
Task.set_timeout(5, Main.spawn_enemy_wave_token, old_position)
for _ = 1, 3 do
local spawn_target = Enemy.get_target()
if spawn_target and spawn_target.valid then
for _ = 1, 12 do
Task.set_timeout_in_ticks(math_random(30, 4 * 60), Enemy.artillery_explosion_token, { surface_name = surface.name, position = spawn_target.position })
end
Task.set_timeout(6, Enemy.spawn_enemy_wave_token, spawn_target.position)
break
end
end
game.forces.enemy.reset_evolution() game.forces.enemy.reset_evolution()
local enemy_evolution = game.map_settings.enemy_evolution local enemy_evolution = game.map_settings.enemy_evolution
@ -748,6 +1109,8 @@ function Main.on_game_started()
this.rocket_silo = nil this.rocket_silo = nil
this.move_buffer = 0 this.move_buffer = 0
this.invincible = {} this.invincible = {}
this.target_entities = {}
this.unit_groups = {}
if _DEBUG then if _DEBUG then
this.silo_starting_x = 30 this.silo_starting_x = 30
@ -772,18 +1135,25 @@ Main.restart_game_token = Token.register(function()
end) end)
function Main.on_game_finished() function Main.on_game_finished()
Lobby.enabled = true this.lobby_enabled = true
Lobby.teleport_all_to() Lobby.teleport_all_to()
local surface = RS.get_surface() local surface = RS.get_surface()
surface.clear(true) surface.clear(true)
surface.map_gen_settings.seed = surface.map_gen_settings.seed + 1 local mgs = table.deepcopy(surface.map_gen_settings)
mgs.seed = mgs.seed + 1e4
surface.map_gen_settings = mgs
end end
Main.end_game_token = Token.register(function() Main.end_game_token = Token.register(function()
script.raise_event(Main.events.on_game_finished, {}) script.raise_event(Main.events.on_game_finished, {})
end) end)
function Main.bard_message(list)
game.print('[color=orange][Bard][/color] ' .. list[math_random(#list)], { sound_path = 'utility/axe_fighting', color = Color.brown })
end
Main.bard_message_token = Token.register(Main.bard_message)
-- == EVENTS ================================================================== -- == EVENTS ==================================================================
local function on_init() local function on_init()
@ -791,7 +1161,7 @@ local function on_init()
Main.on_game_started() Main.on_game_started()
Main.reveal_spawn_area() Main.reveal_spawn_area()
Lobby.enabled = false this.lobby_enabled = false
Lobby.teleport_all_from() Lobby.teleport_all_from()
end end
Event.on_init(on_init) Event.on_init(on_init)
@ -800,7 +1170,7 @@ local function on_game_started()
Main.on_game_started() Main.on_game_started()
Main.reveal_spawn_area() Main.reveal_spawn_area()
Lobby.enabled = false this.lobby_enabled = false
Lobby.teleport_all_from() Lobby.teleport_all_from()
end end
Event.add(Main.events.on_game_started, on_game_started) Event.add(Main.events.on_game_started, on_game_started)
@ -816,7 +1186,7 @@ local function on_player_created(event)
return return
end end
if Lobby.enabled then if this.lobby_enabled then
Lobby.teleport_to(player) Lobby.teleport_to(player)
end end
end end
@ -855,12 +1225,14 @@ local function on_entity_died(event)
end end
local entity_type = entity.type local entity_type = entity.type
if entity_type == 'unit-spawner' then
Main.on_spawner_died(event)
elseif entity_type == 'unit' or entity.type == 'turret' then
if entity.force.name == 'enemy' then if entity.force.name == 'enemy' then
Main.on_enemy_died(entity) if entity_type == 'unit-spawner' then
Enemy.on_spawner_died(event)
elseif entity_type == 'unit' or entity_type == 'turret' then
Enemy.on_enemy_died(entity)
end end
elseif entity_type == 'simple-entity' then
Enemy.spawn_turret_outpost(entity.position)
end end
end end
Event.add(defines.events.on_entity_died, on_entity_died) Event.add(defines.events.on_entity_died, on_entity_died)
@ -894,7 +1266,7 @@ local function on_player_died(event)
end end
this.rockets_to_win = this.rockets_to_win + this.rockets_per_death this.rockets_to_win = this.rockets_to_win + this.rockets_per_death
ScoreTracker.set_for_global(rocket_launches_name, this.rockets_to_win - this.rocket_launched) ScoreTracker.set_for_global(rocket_launches_name, this.rockets_to_win - this.rockets_launched)
game.print({'frontier.add_rocket', this.rockets_per_death, player_name, (this.rockets_to_win - this.rockets_launched)}) game.print({'frontier.add_rocket', this.rockets_per_death, player_name, (this.rockets_to_win - this.rockets_launched)})
end end
@ -931,14 +1303,14 @@ local function on_rocket_launched(event)
end end
this.rockets_launched = this.rockets_launched + 1 this.rockets_launched = this.rockets_launched + 1
ScoreTracker.set_for_global(rocket_launches_name, (this.rockets_to_win - this.rockets_launched))
if this.rockets_launched >= this.rockets_to_win then if this.rockets_launched >= this.rockets_to_win then
Main.win() Main.win()
return return
end end
game.print({'frontier.rocket_launched', this.rockets_launched, (this.rockets_to_win - this.rockets_launched) }) game.print({'frontier.rocket_launched', this.rockets_launched, (this.rockets_to_win - this.rockets_launched) })
ScoreTracker.set_for_global(rocket_launches_name, (this.rockets_to_win - this.rockets_launched)) Main.compute_silo_coordinates(this.rocket_step + math_random(200))
Main.compute_silo_coordinates(500)
local ticks = 60 local ticks = 60
for _, delay in pairs{60, 40, 20} do for _, delay in pairs{60, 40, 20} do
@ -947,6 +1319,8 @@ local function on_rocket_launched(event)
Task.set_timeout_in_ticks(ticks, Main.play_sound_token, 'utility/alert_destroyed') Task.set_timeout_in_ticks(ticks, Main.play_sound_token, 'utility/alert_destroyed')
end end
end end
Task.set_timeout( 5, Main.bard_message_token, bard_messages_1[3])
Task.set_timeout(25, Main.bard_message_token, bard_messages_2[3])
Task.set_timeout_in_ticks(ticks + 30, Main.move_silo_token) Task.set_timeout_in_ticks(ticks + 30, Main.move_silo_token)
local silo = event.rocket_silo local silo = event.rocket_silo
if silo then silo.active = false end if silo then silo.active = false end
@ -960,12 +1334,44 @@ local function on_entity_mined(event)
end end
if entity.type == 'simple-entity' then if entity.type == 'simple-entity' then
Main.spawn_turret_outpost(entity.position) Enemy.spawn_turret_outpost(entity.position)
end end
end end
Event.add(defines.events.on_robot_mined_entity, on_entity_mined) Event.add(defines.events.on_robot_mined_entity, on_entity_mined)
Event.add(defines.events.on_player_mined_entity, on_entity_mined) Event.add(defines.events.on_player_mined_entity, on_entity_mined)
local function on_built_entity(event)
local entity = event.created_entity
if not (entity and entity.valid) then
return
end
if entity.name == 'entity-ghost' then
return
end
Enemy.start_tracking(entity)
end
Event.add(defines.events.on_built_entity, on_built_entity)
Event.add(defines.events.on_robot_built_entity, on_built_entity)
local function on_entity_destroyed(event)
local unit_number = event.unit_number
--local registration_number = event.registration_number
Enemy.stop_tracking({ unit_number = unit_number })
end
Event.add(defines.events.on_entity_destroyed, on_entity_destroyed)
local function on_ai_command_completed(event)
if not event.was_distracted then
local data = this.unit_groups[event.unit_number]
if data and data.unit_group and data.unit_group.valid then
Enemy.ai_processor(data.unit_group, event.result)
end
end
end
Event.add(defines.events.on_ai_command_completed, on_ai_command_completed)
-- == COMMANDS ================================================================ -- == COMMANDS ================================================================
@ -989,6 +1395,39 @@ Command.add('ping-silo',
end end
) )
Command.add('toggle-debug-ai',
{
description = 'Toggle ON/OFF AI debug mode',
allowed_by_server = true,
required_rank = Ranks.admin,
},
function()
this._DEBUG_AI = not this._DEBUG_AI
end
)
Command.add('print-global',
{
description = 'Prints the global table',
allowed_by_server = false,
required_rank = Ranks.admin,
},
function(_, player)
player.print(serpent.line(this))
end
)
Command.add('log-global',
{
description = 'Logs the global table',
allowed_by_server = true,
required_rank = Ranks.admin,
},
function()
Debug.log(this)
end
)
-- ============================================================================ -- ============================================================================
return map return map