Add new enemies

This commit is contained in:
Simon Brodtmann 2025-02-14 16:19:22 +01:00
parent c648261b3c
commit 53699b9a9c
11 changed files with 538 additions and 0 deletions

View file

@ -115,6 +115,9 @@ If your modded lab is special and it should not support these science packs, use
- Add information in Factoriopedia
- Compatibility with [On Wayward Seas](https://mods.factorio.com/mod/wayward-seas)
- Compatibility with [Noble Metals](https://mods.factorio.com/mod/bzgold) once it's updated
- Check if creating a separate group for Lignumis recipes/items makes sense
- Only create 1 cutscene per game in multiplayer
- Think about moving Lumber mill to later and let it use electricity.
## Credits

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View file

@ -30,5 +30,6 @@ require("quality-assembler")
require("decoratives")
require("wood-military")
require("basic-circuit-boards")
require("enemies")
require("noise")

View file

@ -0,0 +1,534 @@
local particle_animations = require("__space-age__/prototypes/particle-animations")
local base_sounds = require("__base__.prototypes.entity.sounds")
local space_age_sounds = require("__space-age__.prototypes.entity.sounds")
local simulations = require("__space-age__.prototypes.factoriopedia-simulations")
local enemy_autoplace = require("__base__.prototypes.entity.enemy-autoplace-utils")
local biter_ai_settings = require ("__base__.prototypes.entity.biter-ai-settings")
local blood_particles = {
"blood-particle-small",
}
for _, particle_name in ipairs(blood_particles) do
local new_particle = table.deepcopy(data.raw["optimized-particle"][particle_name])
local scale = new_particle.pictures.sheet.scale
local shift = new_particle.pictures.sheet.shift
new_particle.name = "lignumis-" .. particle_name
new_particle.pictures = {
sheet = {
filename = "__space-age__/graphics/particle/gleba-blood-particle/gleba-blood-particle.png",
line_length = 12,
width = 32,
height = 24,
frame_count = 12,
variation_count = 7,
scale = scale * 0.25,
shift = shift
}
}
data:extend({ new_particle })
end
function gleba_hit_effects(offset_deviation, offset)
local offset = offset or { 0, 0 }
return {
type = "create-entity",
entity_name = "gleba-enemy-damaged-explosion",
offset_deviation = offset_deviation or { { -0.5, -0.5 }, { 0.5, 0.5 } },
offsets = { offset },
damage_type_filters = "fire"
}
end
function wriggler_spritesheet(name, frames, speed, scale, tint, flag)
speed = speed or 1.0
local is_shadow = string.sub(name, -string.len("-shadow")) == "-shadow"
local is_decay = string.find(name, "decay")
if is_decay and is_shadow then return nil end
return util.sprite_load("__space-age__/graphics/entity/wriggler/wriggler-" .. name,
{
slice = 5,
frame_count = frames,
direction_count = 16,
scale = 0.5 * 1.2 * scale,
multiply_shift = scale,
animation_speed = speed,
draw_as_shadow = is_shadow,
tint_as_overlay = tint and true or nil,
tint = tint,
flags = (not is_shadow) and { flag } or nil,
surface = "gleba",
usage = "enemy"
}
)
end
function wriggler_corpse_spritesheet(name, frames, speed, scale, tint)
return wriggler_spritesheet(name, frames, speed, scale, tint, "corpse-decay")
end
local default_ended_in_water_trigger_effect = function()
return {
{
type = "create-particle",
probability = 1,
affects_target = false,
show_in_tooltip = false,
particle_name = "tintable-water-particle",
apply_tile_tint = "secondary",
offset_deviation = { { -0.05, -0.05 }, { 0.05, 0.05 } },
initial_height = 0,
initial_height_deviation = 0.02,
initial_vertical_speed = 0.05,
initial_vertical_speed_deviation = 0.05,
speed_from_center = 0.01,
speed_from_center_deviation = 0.006,
frame_speed = 1,
frame_speed_deviation = 0,
tail_length = 2,
tail_length_deviation = 1,
tail_width = 3
},
{
type = "create-particle",
repeat_count = 10,
repeat_count_deviation = 6,
probability = 0.03,
affects_target = false,
show_in_tooltip = false,
particle_name = "tintable-water-particle",
apply_tile_tint = "primary",
offsets = {
{ 0, 0 },
{ 0.01563, -0.09375 },
{ 0.0625, 0.09375 },
{ -0.1094, 0.0625 }
},
offset_deviation = { { -0.2969, -0.1992 }, { 0.2969, 0.1992 } },
initial_height = 0,
initial_height_deviation = 0.02,
initial_vertical_speed = 0.053,
initial_vertical_speed_deviation = 0.005,
speed_from_center = 0.02,
speed_from_center_deviation = 0.006,
frame_speed = 1,
frame_speed_deviation = 0,
tail_length = 9,
tail_length_deviation = 0,
tail_width = 1
},
{
type = "play-sound",
sound = base_sounds.small_splash
}
}
end
local make_particle = function(params)
if not params then error("No params given to make_particle function") end
local name = params.name or error("No name given")
local ended_in_water_trigger_effect = params.ended_in_water_trigger_effect or default_ended_in_water_trigger_effect()
if params.ended_in_water_trigger_effect == false then
ended_in_water_trigger_effect = nil
end
local particle = {
type = "optimized-particle",
name = name,
life_time = params.life_time or (60 * 15),
fade_away_duration = params.fade_away_duration,
render_layer = params.render_layer or "projectile",
render_layer_when_on_ground = params.render_layer_when_on_ground or "corpse",
regular_trigger_effect_frequency = params.regular_trigger_effect_frequency or 2,
regular_trigger_effect = params.regular_trigger_effect,
ended_in_water_trigger_effect = ended_in_water_trigger_effect,
pictures = params.pictures,
shadows = params.shadows,
draw_shadow_when_on_ground = params.draw_shadow_when_on_ground,
movement_modifier_when_on_ground = params.movement_modifier_when_on_ground,
movement_modifier = params.movement_modifier,
vertical_acceleration = params.vertical_acceleration,
mining_particle_frame_speed = params.mining_particle_frame_speed,
}
return particle
end
local function lerp_color(a, b, amount)
return {
a[1] + amount * (b[1] - a[1]),
a[2] + amount * (b[2] - a[2]),
a[3] + amount * (b[3] - a[3]),
a[4] + amount * (b[4] - a[4]),
}
end
function make_wriggler(prefix, scale, health, damage, tints, factoriopedia_simulation, factoriopedia_simulation_premature,
sounds)
-- Premature version loses health so that the swarm will get removed (more efficient).
-- Spawner-spawned versions are stable so that the area is not full of corpses.
local tint_mask = tints.mask
local tint_body = tints.body
local function attack_parameters(lifesteal)
local cooldown = 26
return {
ammo_category = "melee",
ammo_type = {
target_type = "entity",
action = {
type = "direct",
action_delivery = {
type = "instant",
source_effects = lifesteal and {
{
type = "damage",
damage = { amount = -health / 50 / 60 * cooldown * 1.1, type = "poison" } -- offsets negative regeneration when attacking
}
} or nil,
target_effects = {
{
type = "damage",
damage = { amount = 5 * damage, type = "physical" }
},
{
type = "damage",
damage = { amount = 5 * damage, type = "poison" }
}
}
}
}
},
animation = {
layers = {
wriggler_spritesheet("attack", 19, 0.48, scale, tint_body),
wriggler_spritesheet("attack-tint", 19, 0.48, scale, tint_mask),
wriggler_spritesheet("attack-shadow", 19, 0.48, scale),
}
},
cooldown = cooldown,
cooldown_deviation = 0.1,
range = 1.8 * scale,
range_mode = "bounding-box-to-bounding-box",
sound = sounds.attack_sound,
type = "projectile"
}
end
local wriggler = {
type = "unit",
name = prefix .. "wriggler-pentapod-premature",
icon = "__lignumis__/graphics/icons/" .. prefix .. "wriggler.png",
subgroup = "enemies",
order = "gleba-a-wriggler-" .. tostring(scale),
factoriopedia_simulation = factoriopedia_simulation_premature,
collision_box = { { -0.2 * scale, -0.2 * scale }, { 0.2 * scale, 0.2 * scale } },
sticker_box = { { -0.5 * scale, -0.5 * scale }, { 0.5 * scale, 0.5 * scale } },
selection_box = { { -0.9 * scale, -0.9 * scale }, { 0.9 * scale, 0.9 * scale } },
collision_mask = { layers = { player = true, train = true, is_object = true }, not_colliding_with_itself = true },
flags = { "placeable-player", "placeable-enemy", "placeable-off-grid", "not-repairable", "breaths-air" },
absorptions_to_join_attack = { noise = 1 },
ai_settings = biter_ai_settings,
attack_parameters = attack_parameters(true),
corpse = prefix .. "wriggler-pentapod-corpse",
damaged_trigger_effect = gleba_hit_effects(),
distance_per_frame = 0.125,
distraction_cooldown = 300,
dying_explosion = prefix .. "wriggler-die",
dying_sound = sounds.dying_sound,
healing_per_tick = -health / 50 / 60,
impact_category = "organic",
max_health = health,
max_pursue_distance = 50,
min_pursue_time = 600,
movement_speed = 0.2 * (1 + (scale - 1) / 2),
resistances = {
{
percent = 50,
type = "laser"
}
},
run_animation = {
layers = {
wriggler_spritesheet("run", 21, 0.48, scale, tint_body),
wriggler_spritesheet("run-tint", 21, 0.48, scale, tint_mask),
wriggler_spritesheet("run-shadow", 21, 0.48, scale),
}
},
running_sound_animation_positions = { 2 },
vision_distance = 20,
water_reflection = {
orientation_to_variation = false,
rotate = true,
pictures = {
filename = "__base__/graphics/entity/biter/biter-reflection.png",
height = 28,
priority = "extra-high",
scale = 2.5 * scale,
shift = { 0.15625, 0.46875 },
variation_count = 1,
width = 20
}
},
walking_sound = sounds.walking_sound,
working_sound = sounds.working_sound,
warcry = sounds.warcry,
}
local wriggler_stable = table.deepcopy(wriggler)
wriggler_stable.name = prefix .. "wriggler-pentapod"
wriggler_stable.factoriopedia_simulation = factoriopedia_simulation
wriggler_stable.healing_per_tick = health / 500 / 60
wriggler_stable.absorptions_to_join_attack = { noise = 1 }
wriggler_stable.attack_parameters = attack_parameters(false)
local wriggler_corpse = {
type = "corpse",
name = prefix .. "wriggler-pentapod-corpse",
icon = "__lignumis__/graphics/icons/" .. prefix .. "wriggler-corpse.png",
subgroup = "corpses",
order = "c[corpse]-d[gleba-enemies-corpses]-d[wriggler]" .. tostring(scale),
hidden_in_factoriopedia = true,
selection_box = { { -0.8, -0.8 }, { 0.8, 0.8 } },
selectable_in_game = false,
animation = {
layers = {
wriggler_corpse_spritesheet("death", 17, 0.48, scale, tint_body),
wriggler_corpse_spritesheet("death-tint", 17, 0.48, scale, tint_mask),
wriggler_corpse_spritesheet("death-shadow", 17, 0.48, scale),
}
},
decay_animation = {
layers = {
wriggler_corpse_spritesheet("decay", 9, nil, scale, tint_body),
wriggler_corpse_spritesheet("decay-tint", 9, nil, scale, tint_mask),
wriggler_corpse_spritesheet("decay-shadow", 9, nil, scale),
}
},
dying_speed = 0.015 / scale,
decay_frame_transition_duration = 150,
time_before_removed = 1 * 60 * 60, -- 1 minute
use_decay_layer = true,
direction_shuffle = { { 1, 2, 3, 16 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 }, { 12, 13, 14, 15 } },
shuffle_directions_at_frame = 0,
final_render_layer = "lower-object-above-shadow",
flags = {
"placeable-neutral",
"placeable-off-grid",
"building-direction-8-way",
"not-repairable",
"not-on-map"
},
ground_patch = {
sheet =
util.sprite_load("__space-age__/graphics/entity/wriggler/blood-puddle-var-main",
{
flags = { "low-object" },
variation_count = 4,
scale = 0.4 * scale,
multiply_shift = 0.125,
}
)
},
ground_patch_fade_in_delay = 20,
ground_patch_fade_in_speed = 0.002,
ground_patch_fade_out_duration = 50 * 60 / 7.5,
ground_patch_fade_out_start = 50 * 60 / 7.5,
ground_patch_render_layer = "decals"
}
local wrigger_explosion = {
type = "explosion",
name = prefix .. "wriggler-die",
scale = 0.25,
icon = "__space-age__/graphics/icons/medium-wriggler-corpse.png",
order = "a[corpse]-f[wriggler]",
flags = { "not-on-map" },
hidden = true,
subgroup = "enemy-death-explosions",
animations = util.empty_sprite(),
created_effect = {
type = "direct",
action_delivery = {
type = "instant",
target_effects = {
{
type = "create-particle",
repeat_count = 13,
repeat_count_deviation = 1,
probability = 1,
affects_target = false,
show_in_tooltip = false,
particle_name = "lignumis-blood-particle-small",
offsets = { { 0, 0 } },
offset_deviation = { { -0.5, -0.5 }, { 0.5, 0.5 } },
initial_height = 0.1,
initial_height_deviation = 0.1,
initial_vertical_speed = 0.009,
initial_vertical_speed_deviation = 0.009,
speed_from_center = 0.05,
speed_from_center_deviation = 0.05,
frame_speed = 1,
frame_speed_deviation = 0,
tail_length = 5,
tail_length_deviation = 5,
tail_width = 3,
rotate_offsets = false
},
{
type = "create-particle",
repeat_count = 12,
repeat_count_deviation = 3,
probability = 1,
affects_target = false,
show_in_tooltip = false,
particle_name = "lignumis-blood-particle-small",
offsets = {
{ 0, -0.4 },
{ 0, 0.5 },
{ 0, 0.6 }
},
offset_deviation = { { -0.25, -0.25 }, { 0.25, 0.25 } },
initial_height = 0.1,
initial_height_deviation = 0.1,
initial_vertical_speed = 0.055,
initial_vertical_speed_deviation = 0.075,
speed_from_center = 0.03,
speed_from_center_deviation = 0.03,
frame_speed = 1,
frame_speed_deviation = 0,
tail_length = 52,
tail_length_deviation = 25,
tail_width = 3,
rotate_offsets = false
},
--{
-- type = "create-particle",
-- repeat_count = 2,
-- repeat_count_deviation = 0,
-- probability = 1,
-- affects_target = false,
-- show_in_tooltip = false,
-- particle_name = "pentapod-entrails-particle-small",
-- offsets = {
-- { 0, -0.4 }
-- },
-- offset_deviation = { { -0.5, -0.5 }, { 0.5, 0.5 } },
-- initial_height = 0.1,
-- initial_height_deviation = 0.1,
-- initial_vertical_speed = 0.06,
-- initial_vertical_speed_deviation = 0.05,
-- speed_from_center = 0.07,
-- speed_from_center_deviation = 0,
-- frame_speed = 1,
-- frame_speed_deviation = 0,
-- rotate_offsets = false
--},
--{
-- type = "create-particle",
-- repeat_count = 10,
-- repeat_count_deviation = 0,
-- probability = 1,
-- affects_target = false,
-- show_in_tooltip = false,
-- particle_name = prefix .. "wriggler-skin-particle",
-- offsets = {
-- { 0, -0.4 }
-- },
-- offset_deviation = { { -0.5, -0.5 }, { 0.5, 0.5 } },
-- initial_height = 0.1,
-- initial_height_deviation = 0.1,
-- initial_vertical_speed = 0.05,
-- initial_vertical_speed_deviation = 0.02,
-- speed_from_center = 0.02,
-- speed_from_center_deviation = 0.1,
-- frame_speed = 1,
-- frame_speed_deviation = 0,
-- rotate_offsets = false
--},
{
type = "play-sound",
sound = base_sounds.medium_gore
},
}
}
}
}
data:extend {
--wriggler,
wriggler_stable,
wriggler_corpse,
wrigger_explosion,
make_particle
{
name = prefix .. "wriggler-skin-particle",
life_time = 300,
pictures = particle_animations.get_pentpod_skin_particles_small({ scale = 1 * scale, tint = lerp_color(tint_mask, { 255, 255, 255, 255 }, 0.7) }),
shadows = particle_animations.get_pentpod_skin_particles_small({ scale = 1 * scale, tint = shadowtint(), shift = util.by_pixel(1, 0) }),
ended_in_water_trigger_effect = default_ended_in_water_trigger_effect(),
render_layer_when_on_ground = "lower-object-above-shadow"
},
}
end
local function fade(tint, amount) -- fades to minimal opacity grey. Low opacity is good for the mask to let the base layer show htough (instead of having a grey mask)
return lerp_color(tint, { 1, 1, 1, 2 }, amount)
end
local function grey_overlay(tint, amount) -- fades to opaque grey. Full opacity is required for body.
return lerp_color(tint, { 127, 127, 127, 255 }, amount)
end
-- mask tint is vibrant and only on the mask
-- body tint applies to the whole body and should be near 127 grey, just adds a hint of saturation
local gleba_small_mask_tint = { 103, 151, 11, 255 }
local gleba_small_body_tint = { 125, 124, 111, 255 }
make_wriggler("lignumis-small-", 0.2, 20, 0.1,
{
mask = fade(lerp_color(gleba_small_mask_tint, { 255, 200, 0, 255 }, 0.2), 0.2),
body = grey_overlay(lerp_color(gleba_small_body_tint, { 255, 0, 0, 255 }, 0.2), 0.2)
},
simulations.factoriopedia_gleba_enemy_small_wriggler,
simulations.factoriopedia_gleba_enemy_small_wriggler_premature,
space_age_sounds.wriggler_pentapod.small
)
make_wriggler("lignumis-medium-", 0.4, 40, 0.2,
{
mask = fade(lerp_color(gleba_small_mask_tint, { 255, 200, 0, 255 }, 0.4), 0.2),
body = grey_overlay(lerp_color(gleba_small_body_tint, { 255, 0, 0, 255 }, 0.4), 0.2)
},
simulations.factoriopedia_gleba_enemy_small_wriggler,
simulations.factoriopedia_gleba_enemy_small_wriggler_premature,
space_age_sounds.wriggler_pentapod.small
)
local spawner = table.deepcopy(data.raw["unit-spawner"]["gleba-spawner-small"])
table.assign(spawner, {
name = "lignumis-spawner-small",
result_units = {
{ "lignumis-small-wriggler-pentapod", { { 0.0, 0.9 }, { 0.5, 0.9 }, { 0.6, 0.5 } } },
{ "lignumis-medium-wriggler-pentapod", { { 0.1, 0 }, { 0.4, 0 }, { 1, 0.9 } } }
},
max_count_of_owned_units = 7,
max_friends_around_to_spawn = 5,
spawning_cooldown = { 360, 150 },
spawning_radius = 10,
spawning_spacing = 3,
max_spawn_shift = 0,
max_richness_for_spawn_shift = 100,
call_for_help_radius = 50,
--spawning_cooldown = { 60, 20 },
--spawning_radius = 4,
--call_for_help_radius = 20,
autoplace = enemy_autoplace.enemy_spawner_autoplace("enemy_autoplace_base(0, 6)"),
absorptions_per_second = { noise = { absolute = 20, proportional = 0.01 } },
})
spawner.collision_mask = nil
spawner.loot = nil
spawner.dying_trigger_effect[1].entity_name = "lignumis-small-wriggler-pentapod"
data:extend({ spawner })

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB