diff --git a/README.md b/README.md index 7c16569..a26b8a4 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,9 @@ If your modded lab is special and it should not support these science packs, use - Compatibility with [Planet Picker](https://mods.factorio.com/mod/planet-picker) - Compatibility with [Visible Planets in Space](https://mods.factorio.com/mod/visible-planets) - 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 diff --git a/lignumis/graphics/icons/lignumis-medium-wriggler-corpse.png b/lignumis/graphics/icons/lignumis-medium-wriggler-corpse.png new file mode 100644 index 0000000..4d78edf Binary files /dev/null and b/lignumis/graphics/icons/lignumis-medium-wriggler-corpse.png differ diff --git a/lignumis/graphics/icons/lignumis-medium-wriggler.png b/lignumis/graphics/icons/lignumis-medium-wriggler.png new file mode 100644 index 0000000..55fd48b Binary files /dev/null and b/lignumis/graphics/icons/lignumis-medium-wriggler.png differ diff --git a/lignumis/graphics/icons/lignumis-small-wriggler-corpse.png b/lignumis/graphics/icons/lignumis-small-wriggler-corpse.png new file mode 100644 index 0000000..532e60d Binary files /dev/null and b/lignumis/graphics/icons/lignumis-small-wriggler-corpse.png differ diff --git a/lignumis/graphics/icons/lignumis-small-wriggler.png b/lignumis/graphics/icons/lignumis-small-wriggler.png new file mode 100644 index 0000000..0337f96 Binary files /dev/null and b/lignumis/graphics/icons/lignumis-small-wriggler.png differ diff --git a/lignumis/prototypes/content/data.lua b/lignumis/prototypes/content/data.lua index 42fdb57..fa3d8c9 100644 --- a/lignumis/prototypes/content/data.lua +++ b/lignumis/prototypes/content/data.lua @@ -28,5 +28,6 @@ require("active-noise-cancelling-tower") require("quality-assembler") require("decoratives") require("wood-military") +require("enemies") require("noise") diff --git a/lignumis/prototypes/content/enemies.lua b/lignumis/prototypes/content/enemies.lua new file mode 100644 index 0000000..f2539b1 --- /dev/null +++ b/lignumis/prototypes/content/enemies.lua @@ -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 }) diff --git a/sources/icons/lignumis-medium-wriggler-corpse.png b/sources/icons/lignumis-medium-wriggler-corpse.png new file mode 100644 index 0000000..4d78edf Binary files /dev/null and b/sources/icons/lignumis-medium-wriggler-corpse.png differ diff --git a/sources/icons/lignumis-medium-wriggler.png b/sources/icons/lignumis-medium-wriggler.png new file mode 100644 index 0000000..55fd48b Binary files /dev/null and b/sources/icons/lignumis-medium-wriggler.png differ diff --git a/sources/icons/lignumis-small-wriggler-corpse.png b/sources/icons/lignumis-small-wriggler-corpse.png new file mode 100644 index 0000000..532e60d Binary files /dev/null and b/sources/icons/lignumis-small-wriggler-corpse.png differ diff --git a/sources/icons/lignumis-small-wriggler.png b/sources/icons/lignumis-small-wriggler.png new file mode 100644 index 0000000..0337f96 Binary files /dev/null and b/sources/icons/lignumis-small-wriggler.png differ