Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e6f6ecc
Merge pull request #2 from Monkestation/master
Sun-Soaked Oct 30, 2025
c952855
ambush controller
Sun-Soaked Nov 8, 2025
c4e3959
misinput
Sun-Soaked Nov 8, 2025
d27946a
comment cleanup
Sun-Soaked Nov 8, 2025
fd071a6
improve burrow placement logic + customization
Sun-Soaked Nov 8, 2025
d8c0292
Update code/modules/mining/ambush.dm
Sun-Soaked Nov 8, 2025
f7f3b64
Update code/modules/mining/ambush.dm
Sun-Soaked Nov 8, 2025
bd1443a
fixes
Sun-Soaked Nov 8, 2025
0de469a
Update code/modules/mining/ambush.dm
Sun-Soaked Nov 8, 2025
10c99f8
Update code/modules/mining/ambush.dm
Sun-Soaked Nov 8, 2025
395c1d3
Update code/modules/mining/ambush.dm
Sun-Soaked Nov 8, 2025
05dc267
removes stop() from burrows
Sun-Soaked Nov 8, 2025
18dfc01
Merge branch 'ambush' of https://github.com/Sun-Soaked/CEV-Eris into …
Sun-Soaked Nov 8, 2025
4f96488
Update code/modules/mining/ambush_structures.dm
Sun-Soaked Nov 8, 2025
9abcce8
Update code/modules/mining/ambush_structures.dm
Sun-Soaked Nov 8, 2025
99e729b
Update code/modules/mining/ambush_structures.dm
Sun-Soaked Nov 8, 2025
2ce1cc8
Update code/modules/mining/ambush_structures.dm
Sun-Soaked Nov 8, 2025
f2fce41
oops
Sun-Soaked Nov 8, 2025
7f3debb
total var/ambushed death
Sun-Soaked Nov 8, 2025
424ad17
minor stuff
Sun-Soaked Nov 8, 2025
9ba4d7c
WIP anmbush implementation & golem pathing
Sun-Soaked Nov 9, 2025
2698a2c
skill issue
Sun-Soaked Nov 9, 2025
2b27d99
fixes & shorter warn time
Sun-Soaked Nov 9, 2025
0a67f9c
fixes ambush check in gen
Sun-Soaked Nov 11, 2025
82dce97
Merge branch 'master' into ambush
flleeppyy Nov 25, 2025
041ebba
Merge branch 'master' into ambush
flleeppyy Dec 16, 2025
2536349
booboo the fool
Sun-Soaked Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cev_eris.dme
Original file line number Diff line number Diff line change
Expand Up @@ -1954,6 +1954,8 @@
#include "code\modules\media\mediamanager.dm"
#include "code\modules\mining\abandonedcrates.dm"
#include "code\modules\mining\alloys.dm"
#include "code\modules\mining\ambush.dm"
#include "code\modules\mining\ambush_structures.dm"
#include "code\modules\mining\coins.dm"
#include "code\modules\mining\machine_processing.dm"
#include "code\modules\mining\machine_stacking.dm"
Expand Down
4 changes: 2 additions & 2 deletions code/__HELPERS/matrices.dm
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@
animate(transform = transforms[3], time = 0.2)
animate(transform = transforms[4], time = 0.3)

/atom/proc/shake_animation(intensity = 8)
/atom/proc/shake_animation(intensity = 8, duration = 0.5 SECONDS)
var/init_px = pixel_x
var/shake_dir = pick(-1, 1)
animate(src, transform=turn(matrix(), intensity*shake_dir), pixel_x=init_px + 2*shake_dir, time=1)
animate(transform=null, pixel_x=init_px, time=6, easing=ELASTIC_EASING)
animate(transform=null, pixel_x=init_px, time=duration, easing=ELASTIC_EASING)

/// Perform a shake on an atom, resets its position afterwards
/atom/proc/Shake(pixelshiftx = 2, pixelshifty = 2, duration = 2.5 SECONDS, shake_interval = 0.02 SECONDS)
Expand Down
3 changes: 3 additions & 0 deletions code/controllers/subsystems/processing/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ PROCESSING_SUBSYSTEM_DEF(mobs)

var/list/mob_list
var/list/mob_living_by_zlevel[][]
///used by ambushcode to keep track of which mobs are currently involved in ambushes
var/list/ambushed_mobs

/datum/controller/subsystem/processing/mobs/PreInit()
mob_list = processing // Simply setups a more recognizable var name than "processing"
ambushed_mobs = new()
MaxZChanged()

/datum/controller/subsystem/processing/mobs/proc/MaxZChanged()
Expand Down
11 changes: 11 additions & 0 deletions code/modules/dungeons/procedural/deepmaint.dm
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ var/global/list/big_deepmaint_room_templates = list()

/obj/procedural/jp_DungeonGenerator/deepmaint/proc/makeNiche(turf/T)
var/list/nicheline = list()
//only 1 hard encounter per niche zone please
var/ambush_placed = FALSE
for(var/i in list(NORTH,EAST,SOUTH,WEST)) //Checks range of 5 tiles in all 4 directions from the turf tile being passed
switch(i)
if(NORTH)
Expand All @@ -146,6 +148,9 @@ var/global/list/big_deepmaint_room_templates = list()
for(var/turf/W in nicheline) //Every turf in the path returned by findNicheTurfs has a 30% chance of becoming a random deepmaint machine
if(prob(30))
new /obj/spawner/pack/deep_machine(W)
if(!ambush_placed && prob(1))
new /obj/effect/ambush_snare
ambush_placed = TRUE
for(var/turf/W in wall_line) //Every turf in the path returned by checkForWalls is turned into a floor tile, and has a 70% chance of becoming a random deepmaint machine
if(locate(/obj/machinery/light/small/autoattach, W))
var/obj/machinery/light/small/autoattach/L = locate(/obj/machinery/light/small/autoattach, W)
Expand Down Expand Up @@ -176,6 +181,7 @@ var/global/list/big_deepmaint_room_templates = list()
var/niche_count = 20
var/try_count = niche_count * 7 //In case it somehow zig-zags all of the corridors and stucks in a loop
var/trap_count = 100
var/ambush_count = rand(4, 8)// Add a few ambushes in corridors to keep players on their toes
var/list/path_turfs_copy = path_turfs.Copy()
while(niche_count > 0 && try_count > 0)
try_count = try_count - 1
Expand All @@ -188,6 +194,11 @@ var/global/list/big_deepmaint_room_templates = list()
var/turf/N = pick(path_turfs_copy)
path_turfs_copy -= N
new /obj/spawner/traps(N)
while(ambush_count > 0)
ambush_count = ambush_count - 1
var/turf/ourturf = pick(path_turfs_copy)
path_turfs_copy -= ourturf
new /obj/effect/ambush_snare(ourturf)
for(var/turf/T in path_turfs)
if(prob(30))
new /obj/effect/decal/cleanable/dirt(T) //Wanted to put rust on the floors in deep maint, but by god, the overlay looks like ASS
Expand Down
311 changes: 311 additions & 0 deletions code/modules/mining/ambush.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
#define AMBUSH_CLEANUP_DELAY 3 MINUTES //after the ambush ends, living mobs & any remaining burrows will be deleted this many minutes later.

#define AMBUSH_SKIRMISH "ambush_skirmish" //burrows will immediately unload 1 wave of enemies, then close up
#define AMBUSH_SIEGE "ambush_siege" //burrows will periodically spawn mobs according to spawn_interval until the ambush ends.

/datum/ambush_controller

///the mob that triggered the ambush
var/mob/living/ambushed_mob
/// The location of the ambush
var/turf/ambush_loc
/// A List of burrows tied to this controller
var/list/obj/structure/ambush_burrow/burrows = list()
/// A List of mobs tied to this controller
var/list/mob/living/carbon/superior_animal/ourmobs = list()
///Allegedly used to keep processing() from runtiming
var/processing = TRUE

///A datum containing the behavior of our ambush
var/datum/ambush_type/our_datum
/// Number of burrows created since the start of the ambush
var/count = 0
/// A Timestamp of the last created burrow
var/time_burrow = 0
// A Timestamp of last spawn wave (in siege mode)
var/time_spawn = 0
/// reference to time ambush began
var/start_time = 0
///Do ambush burrows appear around the mob who spawned them, even if they move?
var/following = TRUE

///Essentially local list of ambushed mobs, to compare ssmobs.ambushed_mobs to
var/list/our_ambushed_mobs

/datum/ambush_controller/New(turf/trigger_location, new_ambushed_mob, ambush_datum)
our_datum = new ambush_datum()
ambushed_mob = new_ambushed_mob

if(!trigger_location || !our_datum)//if they forgot to pass a wave datum or trigger location, explode
log_runtime("[src.type] is missing required new() arguments!")
QDEL_NULL(src)
return

if(ambushed_mob) //get the starting location for our ambush
ambush_loc = ambush_follow_check()
if(!ambush_loc) //if no ambushed mob, just use the trigger location
ambush_loc = trigger_location

var/list/our_ambushed_mobs = new()
//give mobs in range a warning they're about to be ambushed
for(var/mob/living/ourmob as anything in hearers(8, ambush_loc))
//add the mob to ambush reference lists
our_ambushed_mobs += ourmob
SSmobs.ambushed_mobs += ourmob
ourmob.show_message(span_userdanger("You feel the ground tremble beneath you..."),2)
shake_camera(ourmob, 6, 0.5, 0.25)
playsound(ambush_loc, 'sound/effects/impacts/rumble4.ogg', 75, TRUE, extrarange = 4)

start_time = world.time
START_PROCESSING(SSobj, src)

/// gets the target's current loc if following is on or it hasn't been gotten before.
/datum/ambush_controller/proc/ambush_follow_check()
if((following || !ambush_loc) && ambushed_mob)
return ambushed_mob.loc
else//if no ambushed mob, just use existing loc
return ambush_loc


/datum/ambush_controller/Destroy()
processing = FALSE
//just in case the controller was qdel'd
for(var/mob/living/ambushee as anything in our_ambushed_mobs)
SSmobs.ambushed_mobs.Remove(ambushee)
for(var/obj/structure/ambush_burrow/burrow in burrows) // Unlink burrows and controller
qdel(burrow)
QDEL_NULL(our_datum)
. = ..()

/datum/ambush_controller/Process()
// I'm too scared to test this -Sun
// Currently, STOP_PROCESSING does NOT instantly remove the object from processing queue
// This is a quick and dirty fix for runtime error spam caused by this
if(!processing)
return

//our work here is done. End ambush
if(ambushed_mob.stat == DEAD || (world.time - start_time) - our_datum.setup_time >= our_datum.ambush_duration || count >= our_datum.spawn_cap)
stop()

var/burrow_num = burrows.len
// Check if new burrows can be created
if((burrow_num < our_datum.max_burrows) && (world.time - time_burrow) > our_datum.burrow_interval)
time_burrow = world.time
for(var/mob/ourmob as anything in hearers(8, ambush_loc))
shake_camera(ourmob, 6, 0.5, 0.25)
for(var/burrow in 1 to our_datum.burrow_number)
count++
spawn_burrow()

// if we're in siege mode, ambush controller handles spawns directly
if(our_datum.ambush_type != AMBUSH_SIEGE)
return

if((world.time - start_time) <= our_datum.setup_time)
return

// Check if a new spawn wave should occur
if((world.time - time_spawn) <= our_datum.spawn_interval)
return

time_spawn = world.time

for(var/obj/structure/ambush_burrow/ourburrow as anything in burrows)
if(!get_turf(ourburrow)) // If the burrow is in nullspace for some reason
burrows -= ourburrow // Remove it from the pool of burrows
continue
ourburrow.spawn_mobs()


///locates an appropriate turf to spawn a burrow, then creates it
/datum/ambush_controller/proc/spawn_burrow()
ambush_loc = ambush_follow_check()
// Spawn burrow randomly in a donut around our ambush turf
var/radius = our_datum.burrow_spawn_range
var/turf/burrow_turf
while(radius > 2)
burrow_turf = pick(getcircle(ambush_loc, radius))
if(!istype(burrow_turf)) // Try again with a smaller circle
radius--
continue
break
if(!istype(burrow_turf)) // Something wrong is happening
log_and_message_admins("Ambush controller failed to create a new burrow around ([ambush_loc.x], [ambush_loc.y], [ambush_loc.z]).")
return

//if we are in a closed space or target is not visible, move towards the spawn turf
while(ambush_loc && check_density_no_mobs(burrow_turf) && burrow_turf != ambush_loc || !can_see(burrow_turf, ambush_loc))
burrow_turf = get_step(burrow_turf, get_dir(burrow_turf, ambush_loc))
// If we end up on top of the trigger loc, just spawn next to it
if(burrow_turf == ambush_loc)
burrow_turf = get_step(ambush_loc, pick(GLOB.cardinal))

burrow_turf.shake_animation(14)//HEY! Pay attention to this spot
burrows += new /obj/structure/ambush_burrow(burrow_turf, src, our_datum) // Spawn burrow at final location

///ends the ambush. Should preferentially be called before cleanup or a qdel
/datum/ambush_controller/proc/stop()
// Disable processing
processing = FALSE
//give mobs in range an indicate that it's over
for(var/mob/ourmob as anything in hearers(8, ambush_loc))
ourmob.show_message(span_danger("The shaking in the ground finally subsides."),2)
for(var/obj/structure/ambush_burrow/burrow in burrows) //visibly collapse burrows to show players it's over
burrow.crumble()
//allow mobs in the original ambush range to once again trigger ambushes
for(var/mob/living/ambushee as anything in our_ambushed_mobs)
SSmobs.ambushed_mobs.Remove(ambushee)
// Clean up controller and all remaining objects after given delay
addtimer(CALLBACK(src, PROC_REF(cleanup)), AMBUSH_CLEANUP_DELAY)

///properly cleans up the controller. Final step of the deletion chain
/datum/ambush_controller/proc/cleanup()
// Delete any remaining burrows
for(var/obj/structure/ambush_burrow/burrow as anything in burrows)
qdel(burrow)

// Delete mobs
for(var/mob/living/carbon/superior_animal/mob as anything in ourmobs)
if(mob.stat == DEAD)
continue
qdel(mob)

qdel(src)

//inherited from old golem controller. Why is it defined here? Good question
///check that determines if a turf is a wall or already holding a mob
/datum/ambush_controller/proc/check_density_no_mobs(turf/F)
if(F.density)
return TRUE
for(var/atom/A in F)
if(A.density && !(A.flags & ON_BORDER) && !ismob(A))
return TRUE
return FALSE


///contains the code that manages an ambush controller's behavior
/datum/ambush_type
//ambush behavior vars
/// Determines the logic burrows follow when spawning mobs
var/ambush_type = AMBUSH_SKIRMISH
/// Once this time passes, ambush ends
var/ambush_duration = 20 SECONDS
/// If set, ambush ends once this many burrows have spawned
var/spawn_cap = 8
/// Amnt. of prep time given between when burrows first appear & mobs start spawning
var/setup_time = 3 SECONDS

//burrow vars
/// If set, total number of burrows that can exist at any single time
var/max_burrows = 4
/// the number of burrows to spawn in a single wave
var/burrow_number = 2
/// Number of seconds that pass between each new burrow spawn wave
var/burrow_interval = 10 SECONDS
///the starting range at which the ambush controller will try to place burrows
var/burrow_spawn_range = 6

//mob vars
/// Number of mobs spawned by each burrow on spawn event
var/mob_spawn = 3
/// Probability of a mob being a special one instead of a normal one
var/special_probability = 35
/// Types of mobs normally spawned by the ambush
var/list/normal_types = list(/mob/living/carbon/superior_animal/roach,
/mob/living/carbon/superior_animal/roach/hunter,
/mob/living/carbon/superior_animal/roach/support)
/// Types of unusual mobs to be passed to the spawn pool according to special_probability
var/list/special_types = list(/mob/living/carbon/superior_animal/roach/fuhrer,
/mob/living/carbon/superior_animal/roach/nanite,
/mob/living/carbon/superior_animal/roach/tank,
/mob/living/carbon/superior_animal/roach/toxic)

///Sound played before the ambush triggers
//var/ambush_sound = 'sound/effects/impacts/rumble4.ogg'

//siege-specific vars
/// Number of seconds that pass between spawn events of burrows - if siege mode is enabled.
var/spawn_interval = 15 SECONDS


// AMBUSH TYPE DEFINES

/datum/ambush_type/golem
ambush_duration = 30 SECONDS
spawn_cap = 12

max_burrows = 6
burrow_number = 3

special_probability = 25

normal_types = list(/mob/living/carbon/superior_animal/golem/coal,
/mob/living/carbon/superior_animal/golem/iron)

special_types = list(/mob/living/carbon/superior_animal/golem/silver,
/mob/living/carbon/superior_animal/golem/silver/enhanced,
/mob/living/carbon/superior_animal/golem/gold,
/mob/living/carbon/superior_animal/golem/plasma,
/mob/living/carbon/superior_animal/golem/ansible,
/mob/living/carbon/superior_animal/golem/coal/enhanced,
/mob/living/carbon/superior_animal/golem/diamond,
/mob/living/carbon/superior_animal/golem/uranium)


/datum/ambush_type/golem/beginner
special_probability = 0


/datum/ambush_type/golem/novice
special_probability = 25

special_types = list(/mob/living/carbon/superior_animal/golem/silver)


/datum/ambush_type/golem/adept
normal_types = list(/mob/living/carbon/superior_animal/golem/coal,
/mob/living/carbon/superior_animal/golem/iron,
/mob/living/carbon/superior_animal/golem/silver)

special_types = list(/mob/living/carbon/superior_animal/golem/platinum,
/mob/living/carbon/superior_animal/golem/coal/enhanced,
/mob/living/carbon/superior_animal/golem/plasma,
/mob/living/carbon/superior_animal/golem/uranium)


/datum/ambush_type/golem/experienced
normal_types = list(/mob/living/carbon/superior_animal/golem/coal/enhanced,
/mob/living/carbon/superior_animal/golem/iron,
/mob/living/carbon/superior_animal/golem/silver)

special_types = list(/mob/living/carbon/superior_animal/golem/platinum,
/mob/living/carbon/superior_animal/golem/plasma,
/mob/living/carbon/superior_animal/golem/uranium)


/datum/ambush_type/golem/expert
special_probability = 30
normal_types = list(/mob/living/carbon/superior_animal/golem/coal/enhanced,
/mob/living/carbon/superior_animal/golem/iron,
/mob/living/carbon/superior_animal/golem/uranium,
/mob/living/carbon/superior_animal/golem/platinum,
/mob/living/carbon/superior_animal/golem/silver/enhanced)

special_types = list(/mob/living/carbon/superior_animal/golem/plasma,
/mob/living/carbon/superior_animal/golem/gold)


/datum/ambush_type/golem/nightmare
special_probability = 35
normal_types = list(/mob/living/carbon/superior_animal/golem/coal/enhanced,
/mob/living/carbon/superior_animal/golem/iron,
/mob/living/carbon/superior_animal/golem/uranium,
/mob/living/carbon/superior_animal/golem/platinum,
/mob/living/carbon/superior_animal/golem/silver/enhanced)

special_types = list(/mob/living/carbon/superior_animal/golem/plasma,
/mob/living/carbon/superior_animal/golem/ansible,
/mob/living/carbon/superior_animal/golem/diamond,
/mob/living/carbon/superior_animal/golem/gold)

Loading
Loading