1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-10-30 23:47:41 +02:00

Push new map

Infestation island - mini arena per island game where you fight bugs and progress through levels
This commit is contained in:
Gerkiz
2025-09-28 23:52:14 +02:00
parent 050c977fe3
commit d2ecc9d98f
6 changed files with 2097 additions and 5 deletions

View File

@@ -202,6 +202,9 @@ require 'utils.remote_chunks'
--![[Prebuilt buildings on the map that can not be removed, you will hate this map]]--
--require 'maps.spaghettorio'
--![[Infestation Islands]]--
require 'maps.infestation_islands.main'
--![[Misc / WIP]]--
--require 'maps.rainbow_road'
--require 'maps.cratewood_forest'

View File

@@ -150,13 +150,13 @@ map_info_main_caption=T E R R I T O R I A L C O N T R O L
map_info_sub_caption= ..alone in the darkness..
map_info_text=Citizen Log #468-2A-3287, Freelancer Trent. \n\nTo whoever is reading this message, \nAny natural resources are rare and the ones worth while are too hard for me to reach. \nLuckily, the wrecks yield all kinds of useful scraps, but also various dangers. \nAlmost lost half a leg some days ago while digging out some scrap. \nThe wildlife is extremely aggressive, especially at the time of night. \nMost of these insect appearing like creatures seem to live underground. \nStay near your light sources, if you want to have a chance of surviving here! \n\n###Log End###
[wip]
map_info_main_caption=S T A R S H I P T R O O P E R S
map_info_sub_caption= ~~ shooty shooty bug bug ~~
map_info_text=Some useful map info here.
[infestation_islands]
map_info_main_caption=I N F E S T A T I O N I S L E S
map_info_sub_caption= ~~ clear the swarm, claim the land ~~
map_info_text=You start on a small island infested with biters.\nYour mission is simple: clear the island of all bugs.\n\nOnce secure, head to the market to unlock the next island.\nEach new island will be more dangerous — more bugs, more nests, more chaos.\n\nKill the biters. Unlock the next island. Rinse and repeat.\nOnly when the swarm is wiped out will true victory be achieved.\n\nGood luck, trooper.
[minesweeper]
map_info_main_caption=Minesweeper
map_info_sub_caption=''
map_info_text=Mechanical lifeforms once dominated this world.\nThey have left long ago, leaving an inhabitable wasteland.\nIt also seems riddled with buried explosives.\n\nMark mines with your stone furnace.\nMarked mines are save to walk on.\nWhen enough mines in an area are marked,\nthey will disarm and yield rewards!\nFaulty marking may trigger surrounding mines!!\n\nAs you move away from spawn,\nmine density and radius required to disarm will increase.\nCrates will contain more loot and ore will have higher yield.\n\nThe paint for the numerics does not work very well with the dirt.\nLaying some stone bricks or better may help.\n

View File

@@ -0,0 +1,441 @@
local Event = require 'utils.event'
local Public = require 'maps.infestation_islands.table'
local Global = require 'utils.global'
local BiterHealthBooster = require 'modules.biter_health_booster_v2'
local Server = require 'utils.server'
local this = {}
Global.register(
this,
function (t)
this = t
end
)
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
}
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',
'dirt-4-dust-particle',
'coal-particle'
}
local s_random_particles = #random_particles
local function create_particles(data)
local surface = data.surface
local position = data.position
local amount = data.amount
if not surface or not surface.valid then
return
end
for _ = 1, amount, 1 do
local m = random(6, 12)
local m2 = m * 0.005
surface.create_particle(
{
name = random_particles[random(1, s_random_particles)],
position = position,
frame_speed = 0.1,
vertical_speed = 0.1,
height = 0.1,
movement = { m2 - (random(0, m) * 0.01), m2 - (random(0, m) * 0.01) }
}
)
end
end
local function roll_biter(level)
if not level or level <= 3 then
return 'small-biter'
elseif level <= 6 then
local choices = { 'small-biter', 'medium-biter' }
return choices[random(1, #choices)]
elseif level <= 10 then
local choices = { 'small-biter', 'medium-biter', 'big-biter' }
return choices[random(1, #choices)]
else
local choices = { 'medium-biter', 'big-biter', 'behemoth-biter' }
return choices[random(1, #choices)]
end
end
local function roll_worm(level)
if not level or level <= 3 then
return 'small-worm-turret'
elseif level <= 6 then
local choices = { 'small-worm-turret', 'medium-worm-turret' }
return choices[random(1, #choices)]
elseif level <= 10 then
local choices = { 'small-worm-turret', 'medium-worm-turret', 'big-worm-turret' }
return choices[random(1, #choices)]
else
local choices = { 'medium-worm-turret', 'big-worm-turret', 'behemoth-worm-turret' }
return choices[random(1, #choices)]
end
end
local function roll_health_boost(level)
if not level or level <= 3 then
return 1
elseif level <= 6 then
return 1.5
elseif level <= 10 then
return 2
end
end
local function spawn_biters(data)
local alive_enemies = Public.get('alive_enemies')
local max_biters_per_island = Public.get('max_biters_per_island')
if alive_enemies >= max_biters_per_island then
return false
end
local surface = data.surface
if not (surface and surface.valid) then
return false
end
local current_level = Public.get('current_level')
local position = surface.find_non_colliding_position('small-biter', data.position, 10, 1)
if not position then
position = data.position
end
local unit_to_create = roll_biter(current_level)
if not unit_to_create then
Server.output_script_data('buried_enemies - unit_to_create was nil?')
return
end
local unit = surface.create_entity({ name = unit_to_create, position = position, force = data.force or 'enemy' })
if not unit or not unit.valid then
return
end
Public.set('alive_enemies', alive_enemies + 1)
local health_boost = roll_health_boost(current_level)
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
return true
end
local function spawn_tech(data)
local alive_enemies = Public.get('alive_enemies')
local max_biters_per_island = Public.get('max_biters_per_island')
if alive_enemies >= max_biters_per_island then
return false
end
local surface = data.surface
if not (surface and surface.valid) then
return false
end
local position = surface.find_non_colliding_position('small-biter', data.position, 10, 1)
if not position then
position = data.position
end
local current_level = Public.get('current_level')
local rand_tech =
{
'defender',
'destroyer',
'distractor'
}
local unit_to_create
if random(1, 3) == 1 then
unit_to_create = rand_tech[random(1, #rand_tech)]
else
unit_to_create = rand_tech[random(1, #rand_tech)]
end
if not unit_to_create then
Server.output_script_data('spawn_tech - unit_to_create was nil?')
return
end
local health_boost = roll_health_boost(current_level)
local unit = surface.create_entity({ name = unit_to_create, position = position, force = data.force or 'enemy' })
if not unit or not unit.valid then
return
end
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
local function spawn_worms(data)
local alive_enemies = Public.get('alive_enemies')
local max_biters_per_island = Public.get('max_biters_per_island')
if alive_enemies >= max_biters_per_island then
return false
end
local current_level = Public.get('current_level')
local unit_to_create = roll_worm(current_level)
if not unit_to_create then
return false
end
local surface = data.surface
if not (surface and surface.valid) then
return false
end
local position = surface.find_non_colliding_position('small-worm-turret', data.position, 10, 1)
if not position then
position = data.position
end
local unit = surface.create_entity({ name = unit_to_create, position = position })
if not unit or not unit.valid then
return
end
Public.set('alive_enemies', alive_enemies + 1)
local health_boost = roll_health_boost(current_level)
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
function Public.buried_biter(surface, position, count, force)
if not (surface and surface.valid) then
return
end
if not position then
return
end
if not position.x then
return
end
if not position.y then
return
end
if not count then
count = 1
end
for t = 1, 60, 1 do
if not this[game.tick + t] then
this[game.tick + t] = {}
end
this[game.tick + t][#this[game.tick + t] + 1] =
{
callback = 'create_particles',
data = { surface = surface, position = { x = position.x, y = position.y }, amount = math.ceil(t * 0.05) }
}
if t == 60 then
if count == 1 then
this[game.tick + t][#this[game.tick + t] + 1] =
{
callback = 'spawn_biters',
data = { surface = surface, position = { x = position.x, y = position.y }, count = count or 1, force = force or 'enemy' }
}
else
local tick = 2
for _ = 1, count do
this[game.tick + t][#this[game.tick + t] + 1 + tick] =
{
callback = 'spawn_biters',
data = { surface = surface, position = { x = position.x, y = position.y }, count = count or 1, force = force or 'enemy' }
}
tick = tick + 2
end
end
end
end
end
function Public.buried_tech(surface, position, count, force)
if not (surface and surface.valid) then
return
end
if not position then
return
end
if not position.x then
return
end
if not position.y then
return
end
if not count then
count = 1
end
for t = 1, 60, 1 do
if not this[game.tick + t] then
this[game.tick + t] = {}
end
this[game.tick + t][#this[game.tick + t] + 1] =
{
callback = 'create_particles',
data = { surface = surface, position = { x = position.x, y = position.y }, amount = math.ceil(t * 0.05) }
}
if t == 60 then
if count == 1 then
this[game.tick + t][#this[game.tick + t] + 1] =
{
callback = 'spawn_tech',
data = { surface = surface, position = { x = position.x, y = position.y }, count = count or 1, force = force or 'enemy' }
}
else
local tick = 2
for _ = 1, count do
this[game.tick + t][#this[game.tick + t] + 1 + tick] =
{
callback = 'spawn_tech',
data = { surface = surface, position = { x = position.x, y = position.y }, count = count or 1, force = force or 'enemy' }
}
tick = tick + 2
end
end
end
end
end
function Public.buried_worm(surface, position)
if not (surface and surface.valid) then
return
end
if not position then
return
end
if not position.x then
return
end
if not position.y then
return
end
for t = 1, 60, 1 do
if not this[game.tick + t] then
this[game.tick + t] = {}
end
this[game.tick + t][#this[game.tick + t] + 1] =
{
callback = 'create_particles',
data = { surface = surface, position = { x = position.x, y = position.y }, amount = math.ceil(t * 0.05) }
}
if t == 60 then
this[game.tick + t][#this[game.tick + t] + 1] =
{
callback = 'spawn_worms',
data = { surface = surface, position = { x = position.x, y = position.y } }
}
end
end
end
local callbacks =
{
['create_particles'] = create_particles,
['spawn_biters'] = spawn_biters,
['spawn_worms'] = spawn_worms,
['spawn_tech'] = spawn_tech
}
local function on_tick()
local t = game.tick
if not this[t] then
return
end
for _, token in pairs(this[t]) do
local callback = token.callback
local data = token.data
local cbl = callbacks[callback]
if callbacks[callback] then
cbl(data)
end
end
this[t] = nil
end
function Public.reset_buried_biters()
for k, _ in pairs(this) do
this[k] = nil
end
end
Event.add(defines.events.on_tick, on_tick)
return Public

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,340 @@
--created by Gerkiz
local Public = require 'maps.infestation_islands.table'
local Event = require 'utils.event'
local Func = require 'maps.infestation_islands.func'
local Map = require 'modules.map_info'
local Task = require 'utils.task_token'
local Scheduler = require 'utils.scheduler'
local set_gamestate_token =
Task.register(
function ()
local this = Public.get()
this.gamestate = 1
end
)
local reset_players_token =
Task.register(
function ()
local surface = game.get_surface(1)
for _, f in pairs(game.forces) do
f.reset()
f.clear_chart(surface)
f.reset_evolution()
end
for _, tech in pairs(game.forces.player.technologies) do
tech.researched = false
tech.saved_progress = 0
end
local players = game.connected_players
for i = 1, #players do
local player = players[i]
player.set_controller { type = defines.controllers.god }
player.create_character()
player.insert({ name = 'raw-fish', count = 3 })
player.insert({ name = 'grenade', count = 1 })
player.insert({ name = 'iron-plate', count = 16 })
player.insert({ name = 'iron-gear-wheel', count = 8 })
player.insert({ name = 'stone', count = 5 })
player.insert({ name = 'pistol', count = 1 })
player.insert({ name = 'firearm-magazine', count = 16 })
local p = surface.find_non_colliding_position('character', { 0, 2 }, 8, 0.5)
if not p then
player.teleport({ 0, 2 }, surface)
else
player.teleport(p, surface)
end
end
end
)
local function create_stage_gui(player)
if player.gui.top.stage_gui then
return
end
local element = player.gui.top.add({ type = 'frame', name = 'stage_gui', caption = ' ' })
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'
end
local function update_stage_gui(caption_override)
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
if stage > #this.stages - 1 then
stage = #this.stages - 1
end
caption = caption .. stage
caption = caption .. '/'
caption = caption .. #this.stages - 1
caption = caption .. ' | Bugs remaining: '
caption = caption .. this.alive_enemies
for _, player in pairs(game.connected_players) do
if player.gui.top.stage_gui then
player.gui.top.stage_gui.caption = caption_override or caption
player.gui.top.stage_gui.tooltip = 'Max biter count: ' .. this.max_biters_per_island
end
end
end
local function bring_players()
local surface = game.surfaces[1]
for _, player in pairs(game.connected_players) do
if player.position.y < -1 then
if player.character then
if player.character.valid then
local p = surface.find_non_colliding_position('character', { 0, 2 }, 8, 0.5)
if not p then
player.teleport({ 0, 2 }, surface)
else
player.teleport(p, surface)
end
end
end
end
end
local this = Public.get()
this.gamestate = 2
end
local function drift_corpses_toward_beach()
local surface = game.surfaces[1]
for _, corpse in pairs(surface.find_entities_filtered({ name = 'character-corpse' })) do
if corpse.position.y < 0 then
if surface.get_tile(corpse.position.x, corpse.position.y).collides_with('resource') then
corpse.clone
{
position = { corpse.position.x, corpse.position.y + (math.random(50, 250) * 0.01) },
surface = surface,
force = corpse.force.name
}
corpse.destroy()
end
end
end
end
local function clear_surface()
local surface = game.get_surface(1)
surface.clear()
end
local function on_player_joined_game(event)
local player = game.players[event.player_index]
create_stage_gui(player)
update_stage_gui()
if player.online_time == 0 then
player.insert({ name = 'raw-fish', count = 3 })
player.insert({ name = 'grenade', count = 1 })
player.insert({ name = 'iron-plate', count = 16 })
player.insert({ name = 'iron-gear-wheel', count = 8 })
player.insert({ name = 'stone', count = 5 })
player.insert({ name = 'pistol', count = 1 })
player.insert({ name = 'firearm-magazine', count = 16 })
return
end
end
local function on_init()
local storage = Public.get()
for index, _ in pairs(storage) do
storage[index] = nil
end
local T = Map.Pop_info()
T.localised_category = 'infestation_islands'
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)
local this = Public.get()
this.game_lost = false
local surface = game.surfaces[1]
surface.request_to_generate_chunks({ x = 0, y = 0 }, 6)
local mgs = game.surfaces[1].map_gen_settings
mgs.water = 9.9
mgs.property_expression_names =
{
['control-setting:aux:bias'] = '0.500000',
['control-setting:aux:frequency:multiplier'] = '6.000000',
['control-setting:moisture:bias'] = '-0.050000',
['control-setting:moisture:frequency:multiplier'] = '6.000000',
}
game.surfaces[1].map_gen_settings = mgs
local blacklist =
{
['dark-mud-decal'] = true,
['sand-dune-decal'] = true,
['light-mud-decal'] = true,
['puberty-decal'] = true,
['sand-decal'] = true,
['red-desert-decal'] = true
}
this.decorative_names = {}
for k, v in pairs(prototypes.decorative) do
if not blacklist[k] then
if v.autoplace_specification then
this.decorative_names[#this.decorative_names + 1] = k
end
end
end
local tree_raffle = {}
for _, e in pairs(prototypes.entity) do
if e.type == 'tree' then
table.insert(tree_raffle, e.name)
end
end
this.tree_raffle = tree_raffle
local corpses_raffle = {}
for _, e in pairs(prototypes.entity) do
if e.type == 'corpse' then
table.insert(corpses_raffle, e.name)
end
end
this.corpses_raffle = corpses_raffle
this.stages = {}
local island_level = 12
for _ = 1, 30 do
this.stages[#this.stages + 1] =
{
size = 16 + (32 + island_level) * 1.5
}
island_level = island_level + 5
end
this.stages[#this.stages].final = true
this.level_vectors = {}
this.alive_boss_enemy_entities = {}
this.current_level = 0
this.gamestate = 0
Task.set_timeout_in_ticks(30, set_gamestate_token)
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 = {}
this.centered_points = {}
this.tiles = {}
this.spawned_markets = {}
this.path_tiles = nil
this.max_biters_per_island = 150
this.seeds = nil
this.nomed_marked = nil
Func.reset_buried_biters()
Func.generate_decoratives()
game.forces['player'].technologies['landfill'].enabled = false
game.forces['player'].technologies['night-vision-equipment'].enabled = false
game.forces['player'].technologies['artillery-shell-range-1'].enabled = false
game.forces['player'].technologies['artillery-shell-speed-1'].enabled = false
game.forces['player'].technologies['artillery'].enabled = false
game.forces['player'].technologies['atomic-bomb'].enabled = false
end
local gamestate_functions =
{
[1] = bring_players,
[2] = Func.draw_main_island,
}
local function on_tick()
local this = Public.get()
if game.tick % 25 == 0 and gamestate_functions[this.gamestate] then
gamestate_functions[this.gamestate]()
end
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()
end
end
if game.tick % 150 == 0 then
drift_corpses_toward_beach()
if this.infini_chest and this.infini_chest.valid then
if this.current_level > 0 and this.current_level <= 10 then
this.infini_chest.insert({ name = 'firearm-magazine', count = 1 })
elseif this.current_level > 10 then
this.infini_chest.insert({ name = 'piercing-rounds-magazine', count = 1 })
end
end
end
if game.tick % 200 == 0 then
if this.game_lost then return end
if this.completed_levels[this.current_level] then
return
end
Func.do_buried_biters()
end
if this.game_lost and this.game_reset_tick then
this.game_reset_tick = this.game_reset_tick - 1
if this.game_reset_tick % 600 == 0 then
game.print('Game will reset in ' .. this.game_reset_tick / 60 .. ' seconds!', { color = { r = 0.22, g = 0.88, b = 0.22 } })
end
if this.game_reset_tick <= 0 then
this.game_reset_tick = nil
this.game_lost = false
Scheduler.can_run_scheduler(false)
clear_surface()
on_init()
Task.set_timeout_in_ticks(500, reset_players_token)
end
end
end
Event.on_init(on_init)
Event.add(defines.events.on_tick, on_tick)
Event.add(defines.events.on_player_joined_game, on_player_joined_game)

View File

@@ -0,0 +1,58 @@
-- one table to rule them all!
local Global = require 'utils.global'
local Event = require 'utils.event'
local this =
{
}
local Public = {}
Global.register(
this,
function (tbl)
this = tbl
end
)
function Public.reset_main_table()
end
function Public.get(key)
if key then
return this[key]
else
return this
end
end
function Public.is_game_lost()
return this.storage.game_lost or false
end
function Public.set(key, value)
if key and (value or value == false) then
this[key] = value
return this[key]
elseif key then
return this[key]
else
return this
end
end
function Public.remove(key, sub_key)
if key and sub_key then
if this[key] and this[key][sub_key] then
this[key][sub_key] = nil
end
elseif key then
if this[key] then
this[key] = nil
end
end
end
Event.on_init(Public.reset_main_table)
return Public