diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_shuttle_wreckage.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_shuttle_wreckage.dmm index 7a8c7bfacdcb..b81d20e262d2 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_shuttle_wreckage.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_shuttle_wreckage.dmm @@ -284,7 +284,7 @@ /turf/open/misc/asteroid/basalt/lava_land_surface/biome_replace, /area/ruin/unpowered) "KN" = ( -/obj/structure/spawner/lavaland/legion, +/mob/living/basic/mining/tendril, /turf/open/misc/asteroid/basalt/lava_land_surface/biome_replace, /area/ruin/unpowered) "Lz" = ( diff --git a/_maps/templates/lazy_templates/nukie_base.dmm b/_maps/templates/lazy_templates/nukie_base.dmm index 14088985fbcb..f625537b0fe0 100644 --- a/_maps/templates/lazy_templates/nukie_base.dmm +++ b/_maps/templates/lazy_templates/nukie_base.dmm @@ -254,7 +254,7 @@ /area/centcom/syndicate_mothership) "du" = ( /obj/structure/chair/stool/directional/north, -/obj/effect/landmark/start/nukeop, +/obj/effect/landmark/start/nukeop_base, /obj/structure/sign/poster/contraband/donk_co/directional/south, /turf/open/floor/wood/tile, /area/centcom/syndicate_mothership/control) @@ -1110,7 +1110,7 @@ /turf/open/floor/mineral/plastitanium/red, /area/centcom/syndicate_mothership/expansion_bioterrorism) "my" = ( -/obj/effect/landmark/start/nukeop_leader, +/obj/effect/landmark/start/nukeop_base/leader, /obj/effect/turf_decal/tile/bar/opposingcorners, /turf/open/floor/iron, /area/centcom/syndicate_mothership/control) @@ -1168,7 +1168,8 @@ /obj/structure/fans/tiny, /obj/machinery/door/poddoor/shutters/syndicate{ id = "FBBZ1"; - name = "Security Shutters" + name = "Security Shutters"; + dir = 1 }, /turf/open/floor/mineral/titanium, /area/centcom/syndicate_mothership/control) @@ -1425,7 +1426,7 @@ /obj/structure/chair/greyscale{ dir = 4 }, -/obj/effect/landmark/start/nukeop_overwatch, +/obj/effect/landmark/start/nukeop_base/overwatch, /turf/open/floor/mineral/plastitanium, /area/centcom/syndicate_mothership) "pU" = ( @@ -1612,10 +1613,19 @@ }, /obj/effect/turf_decal/siding/wideplating, /obj/machinery/door/poddoor/shutters/syndicate/indestructible{ - name = "Subterrainian Cargo Lift" + name = "Base Lift"; + dir = 1; + id_tag = "nukiespawnlift"; + id = "nukiespawnlift" }, /turf/open/floor/iron/dark/textured_half, /area/centcom/syndicate_mothership/control) +"rw" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 6 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) "rA" = ( /obj/effect/turf_decal/siding/purple{ dir = 1 @@ -2016,6 +2026,12 @@ /obj/effect/turf_decal/siding/thinplating_new/dark, /turf/open/floor/mineral/titanium, /area/centcom/syndicate_mothership/control) +"we" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 5 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) "wg" = ( /obj/structure/fence/corner{ dir = 6 @@ -2080,6 +2096,10 @@ dir = 8 }, /area/centcom/syndicate_mothership/control) +"wS" = ( +/obj/effect/landmark/nukeop_elevator/exterior, +/turf/closed/indestructible/syndicate, +/area/centcom/syndicate_mothership/control) "wW" = ( /obj/structure/flora/rock/pile/style_random, /obj/effect/light_emitter{ @@ -2376,7 +2396,9 @@ }, /obj/structure/fans/tiny, /obj/effect/turf_decal/tile/bar/opposingcorners, -/obj/machinery/door/poddoor/shutters/syndicate, +/obj/machinery/door/poddoor/shutters/syndicate{ + dir = 1 + }, /turf/open/floor/iron, /area/centcom/syndicate_mothership/control) "AO" = ( @@ -2405,7 +2427,7 @@ icon_state = "map-left-MS"; pixel_y = 32 }, -/obj/effect/landmark/start/nukeop, +/obj/effect/landmark/start/nukeop_base, /turf/open/floor/wood/tile, /area/centcom/syndicate_mothership/control) "AW" = ( @@ -2721,9 +2743,6 @@ /obj/structure/flora/rock/icy/style_random, /turf/open/misc/asteroid/snow/icemoon, /area/centcom/syndicate_mothership/control) -"Ev" = ( -/turf/open/floor/plating/elevatorshaft, -/area/centcom/syndicate_mothership/control) "Ez" = ( /obj/machinery/atmospherics/pipe/smart/manifold4w/general/hidden, /obj/machinery/atmospherics/pipe/smart/manifold4w/yellow/hidden/layer2, @@ -3206,6 +3225,12 @@ }, /turf/open/floor/iron/dark/textured_large, /area/centcom/syndicate_mothership/control) +"Ja" = ( +/obj/machinery/light/small/red/directional/north{ + allow_break_on_init = 0 + }, +/turf/open/floor/mineral/plastitanium, +/area/centcom/syndicate_mothership/control) "Jc" = ( /obj/structure/table/wood, /obj/machinery/chem_dispenser/drinks/beer{ @@ -3596,7 +3621,7 @@ dir = 8 }, /obj/structure/chair/stool/directional/east, -/obj/effect/landmark/start/nukeop, +/obj/effect/landmark/start/nukeop_base, /turf/open/floor/wood/tile, /area/centcom/syndicate_mothership/control) "Nb" = ( @@ -3883,15 +3908,6 @@ /obj/structure/flora/grass/both/style_random, /turf/open/misc/asteroid/snow/airless, /area/centcom/syndicate_mothership) -"Qe" = ( -/obj/machinery/button/door/directional/south{ - desc = "Looks like the elevator is under maintenance.."; - name = "Elevator Button" - }, -/turf/open/floor/iron/smooth_half{ - dir = 1 - }, -/area/centcom/syndicate_mothership/control) "Qh" = ( /obj/structure/window/reinforced/survival_pod/spawner/directional/south{ name = "Frosted Window" @@ -4336,6 +4352,12 @@ }, /turf/open/floor/wood/tile, /area/centcom/syndicate_mothership/control) +"TH" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 10 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) "TK" = ( /obj/structure/barricade/sandbags, /obj/effect/light_emitter{ @@ -4643,7 +4665,7 @@ /area/centcom/syndicate_mothership) "Xu" = ( /obj/structure/chair/stool/directional/west, -/obj/effect/landmark/start/nukeop, +/obj/effect/landmark/start/nukeop_base, /turf/open/floor/wood/tile, /area/centcom/syndicate_mothership/control) "Xv" = ( @@ -4726,6 +4748,12 @@ dir = 1 }, /area/centcom/syndicate_mothership/control) +"XZ" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 9 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) "Yd" = ( /obj/machinery/camera/autoname/directional/south{ network = list("nukie") @@ -6727,7 +6755,7 @@ sk DW RD EM -RD +EM RD Ox sU @@ -9569,10 +9597,10 @@ ZZ Me wc wG -Qe +wG DZ -Ev -Ev +Ja +Ov DZ DZ nS @@ -9673,9 +9701,9 @@ zH wG wG ru -Ev -Ev -Ev +XZ +TH +Ov DZ Ox Vm @@ -9775,9 +9803,9 @@ zH wG wG ru -Ev -Ev -Ev +we +rw +Ov DZ Ox To @@ -9877,8 +9905,8 @@ wc wG lE DZ -Ev -Ev +Ja +Ov DZ DZ Ox @@ -9978,7 +10006,7 @@ go as wG wG -DZ +wS DZ DZ DZ diff --git a/_maps/templates/lazy_templates/nukie_elevator.dmm b/_maps/templates/lazy_templates/nukie_elevator.dmm new file mode 100644 index 000000000000..6993eedc19ca --- /dev/null +++ b/_maps/templates/lazy_templates/nukie_elevator.dmm @@ -0,0 +1,100 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/indestructible/syndicate, +/area/centcom/syndicate_mothership/control) +"j" = ( +/obj/effect/landmark/start/nukeop_elevator, +/obj/machinery/light/small/red/directional/north{ + allow_break_on_init = 0 + }, +/turf/open/floor/mineral/plastitanium, +/area/centcom/syndicate_mothership/control) +"l" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 6 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) +"s" = ( +/turf/open/floor/plating, +/area/centcom/syndicate_mothership/control) +"u" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 5 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) +"w" = ( +/obj/effect/landmark/start/nukeop_elevator, +/turf/open/floor/mineral/plastitanium, +/area/centcom/syndicate_mothership/control) +"F" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 10 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) +"I" = ( +/obj/effect/turf_decal/siding/thinplating_new/dark{ + dir = 9 + }, +/turf/open/floor/mineral/plastitanium/red, +/area/centcom/syndicate_mothership/control) +"T" = ( +/obj/effect/landmark/nukeop_elevator/interior, +/turf/closed/indestructible/syndicate, +/area/centcom/syndicate_mothership/control) +"W" = ( +/obj/effect/turf_decal/siding/wideplating{ + dir = 1 + }, +/obj/effect/turf_decal/siding/wideplating, +/obj/machinery/door/poddoor/shutters/syndicate/indestructible{ + name = "Base Lift"; + dir = 1 + }, +/turf/open/floor/iron/dark/textured_half, +/area/centcom/syndicate_mothership/control) + +(1,1,1) = {" +a +a +a +a +s +"} +(2,1,1) = {" +a +j +w +a +a +"} +(3,1,1) = {" +W +I +F +w +a +"} +(4,1,1) = {" +W +u +l +w +a +"} +(5,1,1) = {" +a +j +w +a +a +"} +(6,1,1) = {" +T +a +a +a +s +"} diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 1f54a54784d0..8a4e9d56aa53 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -52,6 +52,11 @@ /// Key where goliath stores a hole it wants to get into #define BB_GOLIATH_HOLE_TARGET "BB_goliath_hole" +// Tendril AI keys +#define BB_TENDRIL_LASH "tendril_lash" +#define BB_TENDRIL_CHASER "tendril_chaser" +#define BB_TENDRIL_SPIKES "tendril_spikes" + // bee keys ///the bee hive we live inside #define BB_CURRENT_HOME "BB_current_home" diff --git a/code/__DEFINES/dcs/signals/signals_mining.dm b/code/__DEFINES/dcs/signals/signals_mining.dm index 13ec648c1ba6..494068f37078 100644 --- a/code/__DEFINES/dcs/signals/signals_mining.dm +++ b/code/__DEFINES/dcs/signals/signals_mining.dm @@ -4,6 +4,9 @@ /// Fired by a mob which has been grabbed by a goliath #define COMSIG_GOLIATH_TENTACLED_GRABBED "comsig_goliath_tentacle_grabbed" #define COMPONENT_GOLIATH_CANCEL_TENTACLE_GRAB (1<<0) +/// Fired by a mob which has been grabbed by a tendril +#define COMSIG_TENDRIL_TENTACLED_GRABBED "comsig_tendril_tentacle_grabbed" + #define COMPONENT_TENDRIL_CANCEL_TENTACLE_GRAB (1<<0) /// Fired by a goliath tentacle which is returning to the earth #define COMSIG_GOLIATH_TENTACLE_RETRACTING "comsig_goliath_tentacle_retracting" /// Fired by a mob which has triggered a brimdust explosion from itself (not the mobs that get hit) diff --git a/code/__DEFINES/lazy_templates.dm b/code/__DEFINES/lazy_templates.dm index 1e8dca9a7a11..77dac7bd96e0 100644 --- a/code/__DEFINES/lazy_templates.dm +++ b/code/__DEFINES/lazy_templates.dm @@ -1,3 +1,4 @@ +#define LAZY_TEMPLATE_KEY_NUKIEELEVATOR "LT_NUKIEELEVATOR" #define LAZY_TEMPLATE_KEY_NUKIEBASE "LT_NUKIEBASE" #define LAZY_TEMPLATE_KEY_WIZARDDEN "LT_WIZARDDEN" #define LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY "LT_NINJAHOLDING" @@ -6,6 +7,7 @@ #define LAZY_TEMPLATE_KEY_VOIDWALKER_VOID "LT_VOIDWALKERVOID" #define LAZY_TEMPLATE_KEY_LIST_ALL(...) list( \ + "Nukie Elevator" = LAZY_TEMPLATE_KEY_NUKIEELEVATOR, \ "Nukie Base" = LAZY_TEMPLATE_KEY_NUKIEBASE, \ "Wizard Den" = LAZY_TEMPLATE_KEY_WIZARDDEN, \ "Ninja Holding" = LAZY_TEMPLATE_KEY_NINJA_HOLDING_FACILITY, \ diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index d8f1c9539edf..a9342860e213 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1669,6 +1669,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Sunlight on this turf is blocked and thus you can't get solar power or whatever #define TRAIT_TURF_SUN_BLOCKED "turf_sun_blocked" +/// Cannot be backstabbed with a crusher +#define TRAIT_BACKSTAB_IMMUNE "backstab_immune" + /// Makes the owner immune from the pacification from synthpax #define TRAIT_SYNTHPAX_IMMUNE "synthpax_immune" diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 11b20cfb2104..3380da135819 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1426,7 +1426,7 @@ GLOBAL_LIST_EMPTY(transformation_animation_objects) // Reorder the 2d pixel data of the passed in frames into a data string that can be passed to rustg_dmi_create_png /proc/reorder_pixels(icon_width, icon_height, grid_width, grid_height, list/frames) - var/file_height = icon_height * grid_height + var/file_width = icon_width * grid_width // This little trick right here reduces the total iteration of repeat_string from the product of the arguments to their sum. // Can't be applied to the general case without a complex partitioning algorithm, @@ -1443,7 +1443,7 @@ GLOBAL_LIST_EMPTY(transformation_animation_objects) var/column = (i-1)%grid_width for(var/y in 1 to length(frame)) var/list/row = jointext(frame[y], "") - var/splice_start = (row_index+y-1)*file_height + column*icon_width + 1 + var/splice_start = (row_index*icon_height+y-1)*file_width + column*icon_width + 1 linear_pixels = splicetext(linear_pixels, (splice_start-1)*9+1, (splice_start+icon_width-1)*9+1, row) var/zero_alpha_regex = regex(@@#(?:(?!a0a0a0)([0-9]|[a-f]){6}00)@, "gi") linear_pixels = replacetext(linear_pixels, zero_alpha_regex, COLOR_DMI_MASK) diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm index ee0666fd90c2..9cc1fd3f9960 100644 --- a/code/__HELPERS/text.dm +++ b/code/__HELPERS/text.dm @@ -302,6 +302,16 @@ return TRUE +/proc/filter_illegal_filename_chars(name) + var/regex/illegal_chars = new("\[\\/:*?\"<>|]", "g") + return !findtext(name, illegal_chars) + +/proc/filter_filename_pda(name) + if(is_ic_filtered_for_pdas(name) || is_soft_ic_filtered_for_pdas(name)) + return FALSE + if(!filter_illegal_filename_chars(name)) + return FALSE + return TRUE //html_encode helper proc that returns the smallest non null of two numbers //or 0 if they're both null (needed because of findtext returning 0 when a value is not present) diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index de7dd1494c62..548f15570536 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -319,7 +319,7 @@ GLOBAL_LIST_INIT(rarity_loot, list(//rare: really good items /obj/item/weldingtool/hugetank = 1, /obj/item/fishing_rod/telescopic/master = 1, /obj/item/spess_knife = 1, - /obj/item/gun/ballistic/automatic/pistol/doorhickey = 1, + /obj/item/gun/ballistic/automatic/pistol/doohickey = 1, ) = 1, list(//equipment diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm index f7e4a4cc0b9f..7264922bfce6 100644 --- a/code/_globalvars/lists/mapping.dm +++ b/code/_globalvars/lists/mapping.dm @@ -126,9 +126,6 @@ GLOBAL_LIST_EMPTY(jobspawn_overrides) GLOBAL_LIST_EMPTY(gorilla_start) GLOBAL_LIST_EMPTY(wizardstart) -GLOBAL_LIST_EMPTY(nukeop_start) -GLOBAL_LIST_EMPTY(nukeop_leader_start) -GLOBAL_LIST_EMPTY(nukeop_overwatch_start) GLOBAL_LIST_EMPTY(newplayer_start) GLOBAL_LIST_EMPTY(prisonwarp) //admin prisoners go to these GLOBAL_LIST_EMPTY(holdingfacility) //captured people go here (ninja energy net) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index a4dbd2ff5b06..1b36312f0129 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -196,6 +196,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_ANXIOUS" = TRAIT_ANXIOUS, "TRAIT_APATHETIC" = TRAIT_APATHETIC, "TRAIT_ATTEMPTING_CHRISTENING" = TRAIT_ATTEMPTING_CHRISTENING, + "TRAIT_BACKSTAB_IMMUNE" = TRAIT_BACKSTAB_IMMUNE, "TRAIT_BADDNA" = TRAIT_BADDNA, "TRAIT_BADTOUCH" = TRAIT_BADTOUCH, "TRAIT_BALD" = TRAIT_BALD, diff --git a/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm b/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm index c967112f14fc..2143b6cbb001 100644 --- a/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm +++ b/code/controllers/subsystem/dynamic/dynamic_ruleset_roundstart.dm @@ -250,12 +250,76 @@ ) min_pop = 30 min_antag_cap = list("denominator" = 18, "offset" = 1) - ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEBASE) + ruleset_lazy_templates = list(LAZY_TEMPLATE_KEY_NUKIEELEVATOR) repeatable = FALSE /datum/dynamic_ruleset/roundstart/nukies/prepare_for_role(datum/mind/candidate) + // they all get the normal operative job, even the leader. (leader's job is updated when they get the antag datum) + // all this ultimately matters for is 1. ensuring they *don't* get a normal job and 2. spawning them in the elevator LAZYSET(SSjob.forced_occupations, candidate, /datum/job/nuclear_operative) +/datum/dynamic_ruleset/roundstart/nukies/execute() + . = ..() + addtimer(CALLBACK(src, PROC_REF(load_nukie_base)), 3 SECONDS) + +#define ELEVATOR_WIDTH 6 +#define ELEVATOR_HEIGHT 5 + +/datum/dynamic_ruleset/roundstart/nukies/proc/load_nukie_base() + SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE) + addtimer(CALLBACK(src, PROC_REF(teleport_elevator_contents)), 2 SECONDS) + addtimer(CALLBACK(src, PROC_REF(open_nukie_elevator)), 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(call_infiltrator)), 5 SECONDS) + +/datum/dynamic_ruleset/roundstart/nukies/proc/teleport_elevator_contents() + var/obj/effect/landmark/nukeop_elevator/interior/interior = locate() in GLOB.landmarks_list + var/obj/effect/landmark/nukeop_elevator/exterior/exterior = locate() in GLOB.landmarks_list + if(isnull(interior) || isnull(exterior)) + stack_trace("Failed to find nukie elevator landmarks during load!") + message_admins("Failed to find nukie elevator landmarks during load, you might have some stuck nukies that need help.") + return + + var/turf/top_right_interior = get_turf(interior) + var/turf/top_right_exterior = get_turf(exterior) + + var/list/turf/elevator_turfs = block( + top_right_interior.x, + top_right_interior.y, + top_right_interior.z, + top_right_interior.x - ELEVATOR_WIDTH + 1, + top_right_interior.y - ELEVATOR_HEIGHT + 1, + top_right_interior.z, + ) + + for(var/turf/elevator_turf as anything in elevator_turfs) + var/turf/destination_turf = locate( + top_right_exterior.x - (top_right_interior.x - elevator_turf.x), + top_right_exterior.y - (top_right_interior.y - elevator_turf.y), + top_right_exterior.z, + ) + if(isnull(destination_turf)) + stack_trace("Failed to find destination turf for nukie elevator!") + message_admins("Failed to find corresponding elevator turf for nuke ops, you might have some stuck nukies that need help.") + continue + + for(var/atom/movable/thing in elevator_turf) + if(thing.anchored || thing.orbit_target) + continue + thing.forceMove(destination_turf) + +/datum/dynamic_ruleset/roundstart/nukies/proc/open_nukie_elevator() + for(var/obj/machinery/door/poddoor/shutter in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor)) + if(shutter.id_tag == "nukiespawnlift") + shutter.open() + +/datum/dynamic_ruleset/roundstart/nukies/proc/call_infiltrator() + for(var/datum/mind/leader_mind as anything in selected_minds) + var/datum/antagonist/nukeop/leader/nuke_leader = leader_mind.has_antag_datum(/datum/antagonist/nukeop/leader) + nuke_leader?.spawn_infiltrator() + +#undef ELEVATOR_WIDTH +#undef ELEVATOR_HEIGHT + /datum/dynamic_ruleset/roundstart/nukies/create_execute_args() return list( new /datum/team/nuclear(), @@ -264,9 +328,14 @@ /datum/dynamic_ruleset/roundstart/nukies/assign_role(datum/mind/candidate, datum/team/nuke_team, datum/mind/most_experienced) if(most_experienced == candidate) - candidate.add_antag_datum(/datum/antagonist/nukeop/leader, nuke_team) + var/datum/antagonist/nukeop/leader/nuke_leader = new() + nuke_leader.send_to_spawnpoint = FALSE // because they get spawned in the elevator + nuke_leader.spawn_ship = FALSE // spawned manually later + candidate.add_antag_datum(nuke_leader, nuke_team) else - candidate.add_antag_datum(/datum/antagonist/nukeop, nuke_team) + var/datum/antagonist/nukeop/nukie = new() + nukie.send_to_spawnpoint = FALSE // ditto + candidate.add_antag_datum(nukie, nuke_team) /datum/dynamic_ruleset/roundstart/nukies/round_result() var/datum/antagonist/nukeop/nukie = selected_minds[1].has_antag_datum(/datum/antagonist/nukeop) diff --git a/code/datums/actions/mobs/projectileattack.dm b/code/datums/actions/mobs/projectileattack.dm index f27c0671c1f3..183df71e03f1 100644 --- a/code/datums/actions/mobs/projectileattack.dm +++ b/code/datums/actions/mobs/projectileattack.dm @@ -5,7 +5,7 @@ desc = "Fires a set of projectiles at a selected target." cooldown_time = 1.5 SECONDS /// The type of the projectile to be fired - var/projectile_type + var/obj/projectile/projectile_type /// The sound played when a projectile is fired var/projectile_sound /// If the projectile should home in on its target @@ -58,7 +58,7 @@ our_projectile.set_homing_target(target) if(isnum(set_angle)) our_projectile.fire(set_angle) - return + return our_projectile our_projectile.fire() return our_projectile diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm index 9d9f995e17fd..5b41ae5254d2 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm @@ -24,17 +24,8 @@ var/atom/target = controller.blackboard[target_key] if (isnull(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if (!target.IsReachableBy(controller.pawn)) - controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) - return AI_BEHAVIOR_INSTANT - var/can_attack_time = controller.blackboard[BB_BASIC_MOB_MELEE_COOLDOWN_TIMER] - if (isnull(can_attack_time)) - var/blackboard_delay = controller.blackboard[BB_BASIC_MOB_MELEE_DELAY] - var/attack_delay = isnull(blackboard_delay) ? DEFAULT_ATTACK_DELAY : blackboard_delay - controller.set_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER, world.time + attack_delay) - return AI_BEHAVIOR_INSTANT - if (can_attack_time > world.time) + if (!can_attack(controller, target)) return AI_BEHAVIOR_INSTANT if (isliving(controller.pawn)) @@ -56,6 +47,23 @@ return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED return AI_BEHAVIOR_DELAY +/datum/ai_behavior/basic_melee_attack/proc/can_attack(datum/ai_controller/controller, atom/target) + if (!target.IsReachableBy(controller.pawn)) + controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) + return FALSE + + var/can_attack_time = controller.blackboard[BB_BASIC_MOB_MELEE_COOLDOWN_TIMER] + if (isnull(can_attack_time)) + var/blackboard_delay = controller.blackboard[BB_BASIC_MOB_MELEE_DELAY] + var/attack_delay = isnull(blackboard_delay) ? DEFAULT_ATTACK_DELAY : blackboard_delay + controller.set_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER, world.time + attack_delay) + return FALSE + + if (can_attack_time > world.time) + return FALSE + + return TRUE + /datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) . = ..() controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) @@ -78,7 +86,7 @@ /// range we will try chasing the target before giving up var/chase_range = 9 ///do we care about avoiding friendly fire? - var/avoid_friendly_fire = FALSE + var/avoid_friendly_fire = FALSE /datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) . = ..() diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm index bf7d128fef9c..b2aaaa235788 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -35,7 +35,6 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/aggro_range = controller.blackboard[aggro_range_key] || vision_range - controller.clear_blackboard_key(target_key) // If we're using a field rn, just don't do anything yeah? diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm index 525248529615..9824d41f8238 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm @@ -3,10 +3,12 @@ var/target_key = BB_BASIC_MOB_CURRENT_TARGET /// Targeting strategy key to use var/strategy_key = BB_TARGETING_STRATEGY + /// Behavior to use to find targets + var/target_behavior = /datum/ai_behavior/find_potential_targets /datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) . = ..() - controller.queue_behavior(/datum/ai_behavior/find_potential_targets, target_key, strategy_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + controller.queue_behavior(target_behavior, target_key, strategy_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) // Prevents finding a target if a human is nearby /datum/ai_planning_subtree/simple_find_target/not_while_observed @@ -21,9 +23,8 @@ target_key = BB_BASIC_MOB_FLEE_TARGET /datum/ai_planning_subtree/simple_find_target/increased_range - -/datum/ai_planning_subtree/simple_find_target/increased_range/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/bigger_range, target_key, strategy_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) + target_behavior = /datum/ai_behavior/find_potential_targets/bigger_range /datum/ai_planning_subtree/simple_find_target/hunt strategy_key = BB_HUNT_TARGETING_STRATEGY + diff --git a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm index f1c04e1d8a78..c86994054613 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm @@ -9,9 +9,11 @@ var/hiding_place_key = BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION /// do we check for faction? var/check_faction = FALSE + /// Behavior to use to select our target + var/target_behavior = /datum/ai_behavior/target_from_retaliate_list /datum/ai_planning_subtree/target_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_strategy_key, hiding_place_key, check_faction) + controller.queue_behavior(target_behavior, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_strategy_key, hiding_place_key, check_faction) /datum/ai_planning_subtree/target_retaliate/check_faction check_faction = TRUE diff --git a/code/datums/ai/telegraph_effects.dm b/code/datums/ai/telegraph_effects.dm index 62bbe9a0397e..b658c651988a 100644 --- a/code/datums/ai/telegraph_effects.dm +++ b/code/datums/ai/telegraph_effects.dm @@ -6,6 +6,14 @@ light_range = 1 duration = 2 SECONDS +/obj/effect/temp_visual/telegraphing/Initialize(mapload) + . = ..() + update_appearance(UPDATE_OVERLAYS) + +/obj/effect/temp_visual/telegraphing/update_overlays() + . = ..() + . += emissive_appearance(icon, icon_state, src, alpha = 90) + /obj/effect/temp_visual/telegraphing/vending_machine_tilt duration = 1 SECONDS @@ -15,7 +23,18 @@ src.duration = duration return ..() -/obj/effect/temp_visual/telegraphing/thunderbolt +/obj/effect/temp_visual/telegraphing/circle icon = 'icons/mob/telegraphing/telegraph.dmi' icon_state = "target_circle" duration = 2 SECONDS + +/obj/effect/temp_visual/telegraphing/circle/short + duration = 1 SECONDS + +/obj/effect/temp_visual/telegraphing/line + icon = 'icons/mob/telegraphing/telegraph.dmi' + icon_state = "line" + duration = 0.8 SECONDS + +/obj/effect/temp_visual/telegraphing/line/short + duration = 0.5 SECONDS diff --git a/code/datums/components/crafting/ranged_weapon.dm b/code/datums/components/crafting/ranged_weapon.dm index 53929a6f68db..ae234ea7516b 100644 --- a/code/datums/components/crafting/ranged_weapon.dm +++ b/code/datums/components/crafting/ranged_weapon.dm @@ -292,8 +292,8 @@ return ..() /datum/crafting_recipe/deagle_prime_mag - name = "Regal Condor Magazine (10mm Reaper)" - result = /obj/item/ammo_box/magazine/r10mm + name = "Regal Condor Magazine (.45 Reaper)" + result = /obj/item/ammo_box/magazine/r45 reqs = list( /obj/item/stack/sheet/iron = 10, /obj/item/stack/sheet/mineral/gold = 10, diff --git a/code/datums/components/crafting/tools.dm b/code/datums/components/crafting/tools.dm index 29ea7a60010f..b1d2c107866e 100644 --- a/code/datums/components/crafting/tools.dm +++ b/code/datums/components/crafting/tools.dm @@ -131,6 +131,10 @@ ) category = CAT_TOOLS +/datum/crafting_recipe/jaws_of_recovery/New() + LAZYADD(blacklist, typecacheof(/obj/item/crowbar/power/paramedic, ignore_root_path = TRUE)) + return ..() + /datum/crafting_recipe/lantern name = "Lantern" result = /obj/item/flashlight/lantern diff --git a/code/datums/lazy_template.dm b/code/datums/lazy_template.dm index 9fe85cbcbcd2..d527d46b6d65 100644 --- a/code/datums/lazy_template.dm +++ b/code/datums/lazy_template.dm @@ -112,6 +112,10 @@ reservations += reservation return reservation +/datum/lazy_template/nukie_elevator + key = LAZY_TEMPLATE_KEY_NUKIEELEVATOR + map_name = "nukie_elevator" + /datum/lazy_template/nukie_base key = LAZY_TEMPLATE_KEY_NUKIEBASE map_name = "nukie_base" diff --git a/code/datums/mapgen/CaveGenerator.dm b/code/datums/mapgen/CaveGenerator.dm index 097e8740aff7..63b47165db47 100644 --- a/code/datums/mapgen/CaveGenerator.dm +++ b/code/datums/mapgen/CaveGenerator.dm @@ -325,7 +325,7 @@ is_megafauna = TRUE var/can_spawn = TRUE - if(ispath(picked_mob, /obj/structure/spawner/lavaland)) + if(ispath(picked_mob, /mob/living/basic/mining/tendril)) // Prevents tendrils spawning in each other's collapse range for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_TENDRIL]) if (get_dist(spawn_turf, target_turf) <= 2) @@ -352,7 +352,7 @@ if (!can_spawn) continue - if (ispath(picked_mob, /obj/structure/spawner/lavaland)) + if (ispath(picked_mob, /mob/living/basic/mining/tendril)) spawn_data[CAVE_SPAWN_TENDRIL] += target_turf else if (is_megafauna) diff --git a/code/datums/mapgen/biomes/_biome.dm b/code/datums/mapgen/biomes/_biome.dm index 8dc588680a8c..dc2a17563b37 100644 --- a/code/datums/mapgen/biomes/_biome.dm +++ b/code/datums/mapgen/biomes/_biome.dm @@ -33,6 +33,8 @@ var/mob_exclusion_radius = 12 /// Radius around megafauna within which we avoid spawning tendrils var/megafauna_exclusion_radius = 7 + /// Minimum distance between tendril spawns + var/tendril_exclusion_radius = 12 /datum/biome/New() . = ..() @@ -168,18 +170,21 @@ is_megafauna = TRUE var/can_spawn = TRUE - if(ispath(picked_mob, /obj/structure/spawner/lavaland)) - // Prevents tendrils spawning in each other's collapse range + if(ispath(picked_mob, /mob/living/basic/mining/tendril)) for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_TENDRIL]) - if (get_dist(spawn_turf, target_turf) <= 2) + if (get_dist(spawn_turf, target_turf) <= tendril_exclusion_radius) can_spawn = FALSE break + if (!can_spawn) + continue + // Also avoid spawning them next to megafauna for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_MEGAFAUNA]) if (get_dist(spawn_turf, target_turf) <= megafauna_exclusion_radius) can_spawn = FALSE break + else if (is_megafauna) // Megafauna can spawn wherever it wants as long as its not next to another mega for(var/turf/spawn_turf as anything in spawn_data[CAVE_SPAWN_MEGAFAUNA]) @@ -195,12 +200,11 @@ if (!can_spawn) continue - if (ispath(picked_mob, /obj/structure/spawner/lavaland)) + if (ispath(picked_mob, /mob/living/basic/mining/tendril)) spawn_data[CAVE_SPAWN_TENDRIL] += target_turf - else - if (is_megafauna) - spawn_data[CAVE_SPAWN_MEGAFAUNA] += target_turf - spawn_data[CAVE_SPAWN_MOB] += target_turf + else if (is_megafauna) + spawn_data[CAVE_SPAWN_MEGAFAUNA] += target_turf + spawn_data[CAVE_SPAWN_MOB] += target_turf new picked_mob(target_turf) diff --git a/code/datums/mapgen/biomes/lavaland.dm b/code/datums/mapgen/biomes/lavaland.dm index 7461ef33d8a5..88c907dc23fe 100644 --- a/code/datums/mapgen/biomes/lavaland.dm +++ b/code/datums/mapgen/biomes/lavaland.dm @@ -1,6 +1,7 @@ /datum/biome/lavaland open_turf_type = /turf/open/misc/asteroid/basalt/lava_land_surface closed_turf_type = /turf/closed/mineral/volcanic + mob_exclusion_radius = 7 /datum/biome/lavaland/basalt closed_turf_type = /turf/closed/mineral/random/volcanic @@ -19,9 +20,7 @@ /mob/living/basic/mining/lobstrosity/lava = 20, /obj/effect/spawner/random/lavaland_mob/raptor = 15, /mob/living/basic/mining/goldgrub = 15, - /obj/structure/spawner/lavaland = 1, - /obj/structure/spawner/lavaland/goliath = 3, - /obj/structure/spawner/lavaland/legion = 3, + /mob/living/basic/mining/tendril = 7, ) flora_types = list( @@ -58,9 +57,7 @@ /mob/living/basic/mining/lobstrosity/lava = 40, /obj/effect/spawner/random/lavaland_mob/raptor = 15, /mob/living/basic/mining/goldgrub = 35, - /obj/structure/spawner/lavaland = 2, - /obj/structure/spawner/lavaland/goliath = 3, - /obj/structure/spawner/lavaland/legion = 3, + /mob/living/basic/mining/tendril = 8, ) flora_types = list( @@ -96,9 +93,7 @@ /mob/living/basic/mining/lobstrosity/lava = 25, /obj/effect/spawner/random/lavaland_mob/raptor = 20, /mob/living/basic/mining/goldgrub = 15, - /obj/structure/spawner/lavaland = 2, - /obj/structure/spawner/lavaland/goliath = 1, - /obj/structure/spawner/lavaland/legion = 3, + /mob/living/basic/mining/tendril = 6, ) flora_types = list( diff --git a/code/datums/materials/requirements/_requirement.dm b/code/datums/materials/requirements/_requirement.dm index d23e11c3fb98..3d953098927e 100644 --- a/code/datums/materials/requirements/_requirement.dm +++ b/code/datums/materials/requirements/_requirement.dm @@ -3,6 +3,8 @@ abstract_type = /datum/material_requirement /// Flags which materials need to have to pass var/required_flags = NONE + /// Flags which materials need to not have in order to pass + var/blacklisted_flags = NONE /// Minimum property values that materials need to have to pass var/list/property_minimums = null /// Maximum property values that materials need to have to pass @@ -13,6 +15,8 @@ var/list/flag_strings = list() for (var/flag in bitfield_to_list(required_flags)) flag_strings += GLOB.material_flags_to_string[flag] + for (var/flag in bitfield_to_list(blacklisted_flags)) + flag_strings += "not [GLOB.material_flags_to_string[flag]]" if (!length(property_minimums) && !length(property_maximums)) return "[capitalize(english_list(flag_strings, and_text = " or "))] material" @@ -29,37 +33,28 @@ return "[capitalize(english_list(flag_strings, and_text = " or "))] material with [english_list(prop_reqs)]" /datum/material_requirement/proc/valid_material(datum/material/material) - if (required_flags > 0 && !(material.mat_flags & required_flags)) + if (required_flags && !(material.mat_flags & required_flags)) return FALSE - if (required_flags < 0 && (material.mat_flags & (-required_flags))) + if (blacklisted_flags && (material.mat_flags & blacklisted_flags)) return FALSE for (var/prop_id, min_val in property_minimums) if (material.get_property(prop_id) < min_val) return FALSE + for (var/prop_id, max_val in property_maximums) if (material.get_property(prop_id) > max_val) return FALSE + return TRUE // Actual requirement datums /datum/material_requirement/armor_material required_flags = ITEM_MATERIAL_CLASSES - property_minimums = list( - MATERIAL_HARDNESS = 2, - ) - property_maximums = list( - MATERIAL_FLEXIBILITY = 5, - ) /datum/material_requirement/solid_material - property_minimums = list( - MATERIAL_HARDNESS = 2, - ) - property_maximums = list( - MATERIAL_FLEXIBILITY = 5, - ) + blacklisted_flags = MATERIAL_CLASS_AMORPHOUS /datum/material_requirement/rigid_material required_flags = MATERIAL_CLASS_RIGID diff --git a/code/datums/mind/antag.dm b/code/datums/mind/antag.dm index 2cb0306ebb99..6422ee8c3d19 100644 --- a/code/datums/mind/antag.dm +++ b/code/datums/mind/antag.dm @@ -185,6 +185,7 @@ var/datum/antagonist/nukeop/converter = creator.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE) var/datum/antagonist/nukeop/N = new() N.send_to_spawnpoint = FALSE + N.give_bonus_tc = FALSE N.nukeop_outfit = null add_antag_datum(N,converter.nuke_team) diff --git a/code/datums/mutations/touch.dm b/code/datums/mutations/touch.dm index acaddb335388..8ce75e746c58 100644 --- a/code/datums/mutations/touch.dm +++ b/code/datums/mutations/touch.dm @@ -260,7 +260,7 @@ var/healing_budget = 35 * heal_multiplier // Transfer damage from each limb to the mendicant's counterpart. for(var/obj/item/bodypart/affected_limb as anything in hurtguy.get_bodyparts()) - if(!(affected_limb & BODYTYPE_ORGANIC)) + if(!(affected_limb.bodytype & BODYTYPE_ORGANIC)) continue var/obj/item/bodypart/mendicant_transfer_limb = mendicant.get_bodypart(affected_limb.body_zone) var/list/mendicant_organic_limbs = list() diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 31ea87918487..57dffa377e5f 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -322,6 +322,10 @@ return return ..() +/obj/machinery/door/allowed(mob/accessor) + return ..() || emergency + +/// A mob is trying to open or close the door /obj/machinery/door/proc/try_to_activate_door(mob/user, access_bypass = FALSE, bumped = FALSE) add_fingerprint(user) if(operating || (obj_flags & EMAGGED)) @@ -333,24 +337,8 @@ if(elevator_mode && elevator_status != LIFT_PLATFORM_UNLOCKED) return - var/access_check = access_bypass - if(emergency) - access_check = TRUE - else if(unrestricted_side(user) && !delayed_unres_open) - access_check = TRUE - else if(!requiresID()) - access_check = TRUE - else if(allowed(user)) // You - access_check = TRUE - else for(var/mob/living/human_backpack in user.buckled_mobs) - if(allowed(human_backpack)) // Your partner in crime - access_check = TRUE - break - - if(!access_check && unrestricted_side(user) && attempt_delayed_unres_open(user)) - access_check = TRUE - - if(access_check) + // note: if the ID wire is cut no ID cards are checked at all! (This is intentional!) + if(access_bypass || (requiresID() && user_can_activate_door(user))) if(density) open() else @@ -360,6 +348,18 @@ else if(!operating && density) run_animation(DOOR_DENY_ANIMATION) +/// Used in try_to_activate_door +/obj/machinery/door/proc/user_can_activate_door(mob/user) + PRIVATE_PROC(TRUE) + if(allowed(user)) + return TRUE + for(var/mob/living/human_backpack in user.buckled_mobs) + if(allowed(human_backpack)) + return TRUE + if(unrestricted_side(user)) + return !delayed_unres_open || attempt_delayed_unres_open(user) + return FALSE + /// Allows for specific side of airlocks to be unrestricted (IE, can exit maint freely, but need access to enter) /obj/machinery/door/proc/unrestricted_side(mob/opener) return get_dir(src, opener) & unres_sides diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 4e7ca61d0dd0..91e858273ec2 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -82,12 +82,16 @@ RETURN_TYPE(/datum/reagents) if (reagents) return reagents + return init_reagents(decal_reagent, reagent_amount) - if (!decal_reagent) +/obj/effect/decal/cleanable/proc/init_reagents(reagent = null, amount = null) + RETURN_TYPE(/datum/reagents) + + if (!reagent) return - create_reagents(reagent_amount) - reagents.add_reagent(decal_reagent, reagent_amount) + create_reagents(amount) + reagents.add_reagent(reagent, amount) return reagents /obj/effect/decal/cleanable/item_interaction(mob/living/user, obj/item/tool, list/modifiers) diff --git a/code/game/objects/effects/decals/cleanable/blood.dm b/code/game/objects/effects/decals/cleanable/blood.dm index bf53d00021a2..82253bfabf1a 100644 --- a/code/game/objects/effects/decals/cleanable/blood.dm +++ b/code/game/objects/effects/decals/cleanable/blood.dm @@ -542,13 +542,7 @@ /obj/effect/decal/cleanable/blood/gibs/lazy_init_reagents() if (reagents) return reagents - - if (!decal_reagent) - return - - create_reagents(reagent_amount) - reagents.add_reagent(decal_reagent, reagent_amount) - return reagents + return init_reagents(decal_reagent, reagent_amount) /obj/effect/decal/cleanable/blood/gibs/update_overlays() . = ..() diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index 77ae51ea07ca..db968042c6d9 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -264,36 +264,64 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark) GLOB.wizardstart += loc return INITIALIZE_HINT_QDEL -/obj/effect/landmark/start/nukeop +/// Places to put midround nukeops (normal) +GLOBAL_LIST_EMPTY(nukeop_base_start) +/// Places to put midround nukeops (leader) +GLOBAL_LIST_EMPTY(nukeop_base_leader_start) +/// Places to put nukeops overwatch agents +GLOBAL_LIST_EMPTY(nukeop_base_overwatch_start) +/// Places to spawn nukeops on roundstart +GLOBAL_LIST_EMPTY(nukeop_elevator_start) + +/obj/effect/landmark/start/nukeop_base name = "nukeop" icon = 'icons/effects/landmarks_static.dmi' icon_state = "snukeop_spawn" -/obj/effect/landmark/start/nukeop/Initialize(mapload) +/obj/effect/landmark/start/nukeop_base/Initialize(mapload) ..() - GLOB.nukeop_start += loc + var/list/add_to = get_global_list() + add_to += loc return INITIALIZE_HINT_QDEL -/obj/effect/landmark/start/nukeop_leader +/obj/effect/landmark/start/nukeop_base/proc/get_global_list() + return GLOB.nukeop_base_start + +/obj/effect/landmark/start/nukeop_base/leader name = "nukeop leader" icon = 'icons/effects/landmarks_static.dmi' icon_state = "snukeop_leader_spawn" -/obj/effect/landmark/start/nukeop_leader/Initialize(mapload) - ..() - GLOB.nukeop_leader_start += loc - return INITIALIZE_HINT_QDEL +/obj/effect/landmark/start/nukeop_base/leader/get_global_list() + return GLOB.nukeop_base_leader_start -/obj/effect/landmark/start/nukeop_overwatch +/obj/effect/landmark/start/nukeop_base/overwatch name = "nukeop overwatch" icon = 'icons/effects/landmarks_static.dmi' icon_state = "snukeop_leader_spawn" -/obj/effect/landmark/start/nukeop_overwatch/Initialize(mapload) - ..() - GLOB.nukeop_overwatch_start += loc +/obj/effect/landmark/start/nukeop_base/overwatch/get_global_list() + return GLOB.nukeop_base_overwatch_start + +/obj/effect/landmark/start/nukeop_elevator + name = "nukeop (elevator)" + icon = /obj/effect/landmark/start/nukeop_base::icon + icon_state = /obj/effect/landmark/start/nukeop_base::icon_state + +/obj/effect/landmark/start/nukeop_elevator/Initialize(mapload) + . = ..() + GLOB.nukeop_elevator_start += loc return INITIALIZE_HINT_QDEL +/obj/effect/landmark/nukeop_elevator + icon_state = "x" + +/obj/effect/landmark/nukeop_elevator/interior + name = "nukeop elevator interior top right corner" + +/obj/effect/landmark/nukeop_elevator/exterior + name = "nukeop elevator exterior top right corner" + // Must be immediate because players will // join before SSatom initializes everything. INITIALIZE_IMMEDIATE(/obj/effect/landmark/start/new_player) diff --git a/code/game/objects/items/devices/aicard_evil.dm b/code/game/objects/items/devices/aicard_evil.dm index d5146c0a54ac..307748172db5 100644 --- a/code/game/objects/items/devices/aicard_evil.dm +++ b/code/game/objects/items/devices/aicard_evil.dm @@ -76,6 +76,7 @@ // create and apply syndie datum var/datum/antagonist/nukeop/nuke_datum = new() nuke_datum.send_to_spawnpoint = FALSE + nuke_datum.give_bonus_tc = FALSE new_ai.mind.add_antag_datum(nuke_datum, op_datum.nuke_team) LAZYADD(new_ai.mind.special_roles, "Syndicate AI") new_ai.add_faction(ROLE_SYNDICATE) diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 6d0ddc9e0802..aff0ccc4e800 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -803,6 +803,7 @@ var/datum/antagonist/nukeop/nuke_datum = new() nuke_datum.send_to_spawnpoint = FALSE + nuke_datum.give_bonus_tc = FALSE nuke_datum.nukeop_outfit = null human_target.mind?.add_antag_datum(nuke_datum) human_target.add_faction(ROLE_SYNDICATE) diff --git a/code/game/objects/items/weaponry/melee/baton.dm b/code/game/objects/items/weaponry/melee/baton.dm index 3c5f0a4991ac..a4ceccaa4ebb 100644 --- a/code/game/objects/items/weaponry/melee/baton.dm +++ b/code/game/objects/items/weaponry/melee/baton.dm @@ -1009,22 +1009,19 @@ AddElement(/datum/element/examine_lore, \ lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [src]."), \ lore = "The Secure Apprehension Device (sometimes referred to as the SAD in the officer training manuals) is \ - the unholy union of a mace and a cattleprod. This nonlethal device was designed to put a stop to ruffians, \ - scoundrels, ne'er-do-wells and criminals wherever they may rear their ugly heads.
\ + an unholy union of mace and cattleprod. Designed to stop criminals in their tracks, Nanotrasen security members \ + are rarely without their trusty stun baton. Assuming they haven't lost it somewhere.
\
\ - A symbol of Nanotrasen security forces, the stun baton is the primary tool officers employ against the \ - unlawful scum and villainy of the Spinward and abroad. Trained to 'baton first, interrogate later', \ - Nanotrasen security has long since earned itself a mixed reputation. Able to rapidly shut down the \ - central nervous system of a criminal with only a few direct applications of the conductive striking head \ - of the device, few would-be troublemakers want to find themselves on the wrong end of an officer brandishing \ - this baton.
\ + Trained to 'baton first, interrogate later', Nanotrasen security has long since earned itself a mixed reputation. \ + The device is able to rapidly shut down the central nervous system of a criminal with only a few direct applications \ + of the conductive striking head.
\
\ TerraGov law enforcement has avoided the adoption of stun batons due to various ethical dilemmas posed by \ - their usage, largely because of the longterm physical and mental ramifications of being struck by a human cattleprod. \ - Citizens' rights advocacy groups protest against the proliferation of stun batons as a policing tool, \ - arguing that they are 'inhumane' and 'authoritarian'. Nanotrasen, on the other hand, has had no such qualms \ - when deploying stun batons as a compliance measure across all of their existing stations and facilities against \ - unruly members of staff." \ + their utilization. Studies of their usage have shown numerous longterm physical and mental ramifications caused by \ + being struck by a human cattleprod. Citizens' rights advocacy groups protest against the proliferation of stun \ + batons as a policing tool, arguing that they are 'inhumane' and 'authoritarian'. Nanotrasen, on the other hand, \ + has had no such qualms when deploying stun batons as a compliance measure across all of their existing stations \ + and facilities against their own unruly members of staff." \ ) // Contractor Baton @@ -1033,25 +1030,14 @@ AddElement(/datum/element/examine_lore, \ lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [src]."), \ lore = "The Contract Acquisition Device (sometimes referred to as the CAD in encrypted correspondence) is \ - one of the more frequently encountered examples of Cybersun Industries weaponry. Extremely similar to Nanotrasen's \ - own Secure Apprehension Device (also simply known as the stun baton), the contractor baton is able to induce \ - CNS disruption in a target to render them helpless. It is also capable of devastating blunt force trauma if \ - used as a bludgeon. The contractor baton is also capable of telescopic deployment, allowing for discretion while \ - making an approach towards a target.
\ + the most frequently encountered example of Cybersun Industries weaponry. Similar in purpose to Nanotrasen's \ + own Secure Apprehension Device, the baton is capable of inducing rapid CNS disruption in a target to render them \ + helpless. It also makes for an effective bludgeon, another quality it shares with the stun baton. To maximize \ + ease of concealment, the baton is also able to be telescopically collapsed, to then be rapidly deployed at the \ + pull of a trigger.
\
\ - The contractor baton is famously associated with contractors, elite Cybersun field agents. While the standard \ - agent would often be tasked with sabotage, terrorism, murder or theft, contractors have the critical task of \ - kidnapping high value personnel. Anyone with the potential to possess classified or sensitive data about Nanotrasen \ - security systems and devices could be a target for Cybersun.
\ -
\ - Extracting this information is most easily performed on living subjects. As such, the contractor baton was designed \ - with nonlethal incapacitation in mind. However, Cybersun Industries has long since found workarounds for extracting \ - data from the recently deceased, should the contractor find themselves with only a corpse left to send back. Death \ - may not spare you from the machinations of Cybersun Industries if they deem you a valuable asset towards their goals.
\ -
\ - Nanotrasen utilizes a number of countermeasures to contractor insurgencies, such as employing selective memory wiping \ - or falsified memory injection, the establishment of 'dummy' command staff through the artificial acceleration \ - of otherwise incompetent but useful crewmembers (whose incompetence will often result in an acceptable degree \ - of operational disruption), which provides convenient scapegoats in the event of a security breach as well as \ - frequent staff turnover and reassignment." \ + The contractor baton is famously associated with contractors, elite Cybersun field agents sent to kidnap and extract \ + high value enemy personnel for interrogation. Anyone with the potential to possess classified or sensitive data about \ + Nanotrasen could find themselves a target for Cybersun. The company relentlessly employs contractors to probe Nanotrasen \ + for vulnerabilities, starting with their employees." \ ) diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm deleted file mode 100644 index e6ca59016ed5..000000000000 --- a/code/game/objects/structures/lavaland/necropolis_tendril.dm +++ /dev/null @@ -1,141 +0,0 @@ -//Necropolis Tendrils, which spawn lavaland monsters and break into a chasm when killed -/obj/structure/spawner/lavaland - name = "necropolis tendril" - desc = "A vile tendril of corruption, originating deep underground. Terrible monsters are pouring out of it." - - icon = 'icons/mob/simple/lavaland/nest.dmi' - icon_state = "tendril" - - faction = list(FACTION_MINING, FACTION_ASHWALKER) - max_mobs = 3 - max_integrity = 250 - mob_types = list(/mob/living/basic/mining/watcher) - - move_resist=INFINITY // just killing it tears a massive hole in the ground, let's not move it - anchored = TRUE - resistance_flags = FIRE_PROOF | LAVA_PROOF - var/obj/effect/light_emitter/tendril/emitted_light - scanner_taggable = TRUE - mob_gps_id = "WT" - spawner_gps_id = "Necropolis Tendril" - -/obj/structure/spawner/lavaland/goliath - mob_types = list(/mob/living/basic/mining/goliath) - mob_gps_id = "GL" - -/obj/structure/spawner/lavaland/legion - mob_types = list(/mob/living/basic/mining/legion/spawner_made) - mob_gps_id = "LG" - -/obj/structure/spawner/lavaland/icewatcher - mob_types = list(/mob/living/basic/mining/watcher/icewing) - mob_gps_id = "WT|I" // icewing - -GLOBAL_LIST_INIT(tendrils, list()) -/obj/structure/spawner/lavaland/Initialize(mapload) - . = ..() - emitted_light = new(loc) - for(var/F in RANGE_TURFS(1, src)) - if(ismineralturf(F)) - var/turf/closed/mineral/M = F - M.ScrapeAway(null, CHANGETURF_IGNORE_AIR) - AddComponent(/datum/component/gps, "Eerie Signal") - GLOB.tendrils += src - -/obj/structure/spawner/lavaland/atom_deconstruct(disassembled) - new /obj/effect/collapse(loc) - -/obj/structure/spawner/lavaland/examine(mob/user) - var/list/examine_messages = ..() - examine_messages += span_notice("Once this thing gets hurts enough, it triggers a violent final retaliation.") - examine_messages += span_notice("You'll only have a few moments to run up, grab some loot with an open hand, and get out with it.") - return examine_messages - -/obj/structure/spawner/lavaland/Destroy() - var/last_tendril = TRUE - if(GLOB.tendrils.len>1) - last_tendril = FALSE - - if(last_tendril && !(flags_1 & ADMIN_SPAWNED_1)) - if(SSachievements.achievements_enabled) - for(var/mob/living/L in view(7,src)) - if(L.stat || !L.client) - continue - L.client.give_award(/datum/award/achievement/boss/tendril_exterminator, L) - L.client.give_award(/datum/award/score/tendril_score, L) //Progresses score by one - GLOB.tendrils -= src - QDEL_NULL(emitted_light) - return ..() - -/obj/effect/light_emitter/tendril - set_luminosity = 4 - set_cap = 2.5 - light_color = LIGHT_COLOR_LAVA - -/obj/effect/collapse - name = "collapsing necropolis tendril" - desc = "Get your loot and get clear!" - layer = TABLE_LAYER - icon = 'icons/mob/simple/lavaland/nest.dmi' - icon_state = "tendril" - anchored = TRUE - density = TRUE - /// weakref list of which mobs have gotten their loot from this effect. - var/list/collected = list() - /// a bit of light as to make less unfair deaths from the chasm - var/obj/effect/light_emitter/tendril/emitted_light - -/obj/effect/collapse/Initialize(mapload) - . = ..() - emitted_light = new(loc) - visible_message(span_bolddanger("The tendril writhes in fury as the earth around it begins to crack and break apart! Get back!")) - balloon_alert_to_viewers("interact to grab loot before collapse!", vision_distance = 7) - playsound(loc,'sound/effects/tendril_destroyed.ogg', 200, FALSE, 50, TRUE, TRUE) - addtimer(CALLBACK(src, PROC_REF(collapse)), 5 SECONDS) - -/obj/effect/collapse/examine(mob/user) - var/list/examine_messages = ..() - if(isliving(user)) - if(has_collected(user)) - examine_messages += span_boldnotice("You've grabbed what you can, now get out!") - else - examine_messages += span_boldnotice("You might have some time to grab some goodies with an open hand before it collapses!") - return examine_messages - -/obj/effect/collapse/attack_hand(mob/living/collector, list/modifiers) - . = ..() - if(has_collected(collector)) - to_chat(collector, span_danger("You've already gotten some loot, just get out of there with it!")) - return - visible_message(span_warning("Something falls free of the tendril!")) - var/obj/structure/closet/crate/necropolis/tendril/loot = new /obj/structure/closet/crate/necropolis/tendril(loc) - collector.start_pulling(loot) - collected += WEAKREF(collector) - -/obj/effect/collapse/Destroy() - collected.Cut() - QDEL_NULL(emitted_light) - return ..() - -///Helper proc that resolves weakrefs to determine if collector is in collected list, returning a boolean. -/obj/effect/collapse/proc/has_collected(mob/collector) - for(var/datum/weakref/weakref as anything in collected) - var/mob/living/resolved = weakref.resolve() - //it could have been collector, it could not have been, we don't care - if(!resolved) - continue - if(resolved == collector) - return TRUE - return FALSE - -/obj/effect/collapse/proc/collapse() - for(var/mob/M in range(7,src)) - shake_camera(M, 15, 1) - playsound(get_turf(src),'sound/effects/explosion/explosionfar.ogg', 200, TRUE) - visible_message(span_bolddanger("The tendril falls inward, the ground around it widening into a yawning chasm!")) - for(var/turf/T in RANGE_TURFS(2,src)) - if(HAS_TRAIT(T, TRAIT_NO_TERRAFORM)) - continue - if(!T.density) - T.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland, flags = CHANGETURF_INHERIT_AIR) - qdel(src) diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index e8059c7d0eac..53febcc363f0 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -185,8 +185,8 @@ /obj/item/antag_spawner/nuke_ops/overwatch/Initialize(mapload) . = ..() - if(length(GLOB.nukeop_overwatch_start)) //Otherwise, it will default to the datum's spawn point anyways - spawn_location = pick(GLOB.nukeop_overwatch_start) + if(length(GLOB.nukeop_base_overwatch_start)) //Otherwise, it will default to the datum's spawn point anyways + spawn_location = pick(GLOB.nukeop_base_overwatch_start) //////CLOWN OP /obj/item/antag_spawner/nuke_ops/clown diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 20c4b7e220de..3dc6e81a6ef0 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -478,7 +478,7 @@ return BULLET_ACT_HIT /obj/projectile/tentacle/Destroy() - qdel(chain) + QDEL_NULL(chain) source = null return ..() diff --git a/code/modules/antagonists/nukeop/datums/operative.dm b/code/modules/antagonists/nukeop/datums/operative.dm index dccd7450f978..ae3d3c9d777f 100644 --- a/code/modules/antagonists/nukeop/datums/operative.dm +++ b/code/modules/antagonists/nukeop/datums/operative.dm @@ -15,6 +15,8 @@ var/datum/team/nuclear/nuke_team /// Should the user be moved to default spawnpoint after being granted this datum. var/send_to_spawnpoint = TRUE + /// Should the nukie get a little bonus tc depending on how many players there are + var/give_bonus_tc = TRUE var/job_type = /datum/job/nuclear_operative /// The DEFAULT outfit we will give to players granted this datum @@ -49,11 +51,11 @@ equip_op() if(send_to_spawnpoint) move_to_spawnpoint() - // grant extra TC for the people who start in the nukie base ie. not the lone op - var/extra_tc = CEILING(GLOB.joined_player_list.len/5, 5) + if(give_bonus_tc) + var/extra_tc = CEILING(GLOB.joined_player_list.len / 5, 5) var/datum/component/uplink/uplink = owner.find_syndicate_uplink() - if (uplink) - uplink.uplink_handler.add_telecrystals(extra_tc) + uplink?.uplink_handler.add_telecrystals(extra_tc) + var/datum/component/uplink/uplink = owner.find_syndicate_uplink() if(uplink) var/datum/team/nuclear/nuke_team = get_team() @@ -81,7 +83,7 @@ objectives |= nuke_team.objectives /datum/antagonist/nukeop/leader/get_spawnpoint() - return pick(GLOB.nukeop_leader_start) + return pick(GLOB.nukeop_base_leader_start) /datum/antagonist/nukeop/create_team(datum/team/nuclear/new_team) if(!new_team) @@ -146,7 +148,7 @@ return TRUE /datum/antagonist/nukeop/proc/admin_send_to_base(mob/admin) - owner.current.forceMove(pick(GLOB.nukeop_start)) + owner.current.forceMove(pick(GLOB.nukeop_base_start)) /datum/antagonist/nukeop/proc/admin_tell_code(mob/admin) var/code @@ -192,7 +194,7 @@ if(nuke_team) team_number = nuke_team.members.Find(owner) - return GLOB.nukeop_start[((team_number - 1) % GLOB.nukeop_start.len) + 1] + return GLOB.nukeop_base_start[((team_number - 1) % GLOB.nukeop_base_start.len) + 1] /datum/antagonist/nukeop/proc/spawn_infiltrator() var/datum/map_template/shuttle/infiltrator/ship = SSmapping.shuttle_templates[infiltrator_id] @@ -221,6 +223,6 @@ mobile_port.setTimer(mobile_port.ignitionTime) /datum/antagonist/nukeop/on_respawn(mob/new_character) - new_character.forceMove(pick(GLOB.nukeop_start)) + new_character.forceMove(pick(GLOB.nukeop_base_start)) equip_op() return TRUE diff --git a/code/modules/antagonists/nukeop/datums/operative_leader.dm b/code/modules/antagonists/nukeop/datums/operative_leader.dm index 7815c8c8631c..3f3aaaf31852 100644 --- a/code/modules/antagonists/nukeop/datums/operative_leader.dm +++ b/code/modules/antagonists/nukeop/datums/operative_leader.dm @@ -1,6 +1,8 @@ /datum/antagonist/nukeop/leader name = "Nuclear Operative Leader" nukeop_outfit = /datum/outfit/syndicate/leader + /// Whether to spawn the infiltrator + var/spawn_ship = TRUE /// Randomly chosen honorific, for distinction var/title /// The nuclear challenge remote we will spawn this player with. @@ -63,7 +65,8 @@ return capitalize(newname) /datum/antagonist/nukeop/leader/create_team(datum/team/nuclear/new_team) - spawn_infiltrator() + if(spawn_ship) + spawn_infiltrator() if(new_team) return ..() // Leaders always make new teams diff --git a/code/modules/antagonists/nukeop/datums/operative_lone.dm b/code/modules/antagonists/nukeop/datums/operative_lone.dm index b074016890aa..ba6d001ff807 100644 --- a/code/modules/antagonists/nukeop/datums/operative_lone.dm +++ b/code/modules/antagonists/nukeop/datums/operative_lone.dm @@ -1,6 +1,7 @@ /datum/antagonist/nukeop/lone name = "Lone Operative" send_to_spawnpoint = FALSE //Handled by event + give_bonus_tc = FALSE nukeop_outfit = /datum/outfit/syndicate/full/loneop preview_outfit = /datum/outfit/nuclear_operative preview_outfit_behind = null diff --git a/code/modules/antagonists/nukeop/datums/operative_reinforcement.dm b/code/modules/antagonists/nukeop/datums/operative_reinforcement.dm index eb386342c09a..9cbad2572f87 100644 --- a/code/modules/antagonists/nukeop/datums/operative_reinforcement.dm +++ b/code/modules/antagonists/nukeop/datums/operative_reinforcement.dm @@ -2,6 +2,7 @@ name = "Nuclear Operative Reinforcement" show_in_antagpanel = FALSE send_to_spawnpoint = FALSE + give_bonus_tc = FALSE nukeop_outfit = /datum/outfit/syndicate/reinforcement /datum/antagonist/nukeop/reinforcement/cyborg diff --git a/code/modules/antagonists/nukeop/datums/operative_support.dm b/code/modules/antagonists/nukeop/datums/operative_support.dm index 1b1ea923903d..b10bb4a369c0 100644 --- a/code/modules/antagonists/nukeop/datums/operative_support.dm +++ b/code/modules/antagonists/nukeop/datums/operative_support.dm @@ -32,7 +32,7 @@ owner.current.grant_language(/datum/language/codespeak) /datum/antagonist/nukeop/support/get_spawnpoint() - return pick(GLOB.nukeop_overwatch_start) + return pick(GLOB.nukeop_base_overwatch_start) /datum/antagonist/nukeop/support/forge_objectives() var/datum/objective/overwatch/objective = new diff --git a/code/modules/antagonists/nukeop/datums/operative_team.dm b/code/modules/antagonists/nukeop/datums/operative_team.dm index 4eab9341d48c..98ffe695e47c 100644 --- a/code/modules/antagonists/nukeop/datums/operative_team.dm +++ b/code/modules/antagonists/nukeop/datums/operative_team.dm @@ -18,7 +18,7 @@ objectives += maingoal // when a nuke team is created, the infiltrator has not loaded in yet - it takes some time. so no nuke, we have to wait - addtimer(CALLBACK(src, PROC_REF(assign_nuke_delayed)), 4 SECONDS) + addtimer(CALLBACK(src, PROC_REF(assign_nuke_delayed)), 5 SECONDS) /datum/team/nuclear/roundend_report() var/list/parts = list() @@ -192,7 +192,7 @@ infil_or_nukebase = SPAWN_AT_BASE if(infil_or_nukebase == SPAWN_AT_BASE) - spawn_loc = pick(GLOB.nukeop_start) + spawn_loc = pick(GLOB.nukeop_base_start) var/mob/living/carbon/human/nukie = new(spawn_loc) chosen_one.client.prefs.safe_transfer_prefs_to(nukie, is_antag = TRUE) @@ -200,6 +200,7 @@ var/datum/antagonist/nukeop/antag_datum = new() antag_datum.send_to_spawnpoint = FALSE + antag_datum.give_bonus_tc = FALSE antag_datum.nukeop_outfit = /datum/outfit/syndicate/reinforcement nukie.mind.add_antag_datum(antag_datum, src) @@ -313,20 +314,25 @@ ..() SEND_SIGNAL(src, COMSIG_NUKE_TEAM_ADDITION, new_member.current) -/datum/team/nuclear/proc/assign_nuke_delayed() - assign_nuke() - if(tracked_nuke && memorized_code) - for(var/datum/mind/synd_mind in members) - var/datum/antagonist/nukeop/synd_datum = synd_mind.has_antag_datum(/datum/antagonist/nukeop) - synd_datum?.memorize_code() +/datum/team/nuclear/proc/assign_nuke_delayed(attempts = 0) + if(!assign_nuke()) + if(attempts > 5) + stack_trace("Failed to assign nuke to team after multiple attempts. \ + This likely means the nuke was not found. Please investigate.") + else + addtimer(CALLBACK(src, PROC_REF(assign_nuke_delayed), attempts + 1), 5 SECONDS) + return + + for(var/datum/mind/synd_mind as anything in members) + var/datum/antagonist/nukeop/synd_datum = synd_mind.has_antag_datum(/datum/antagonist/nukeop) + synd_datum?.memorize_code() /datum/team/nuclear/proc/assign_nuke() - memorized_code = random_nukecode() var/obj/machinery/nuclearbomb/syndicate/nuke = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/syndicate) if(!nuke) - stack_trace("Syndicate nuke not found during nuke team creation.") - memorized_code = null - return + return FALSE + + memorized_code = random_nukecode() tracked_nuke = nuke if(nuke.r_code == NUKE_CODE_UNSET) nuke.r_code = memorized_code @@ -334,6 +340,7 @@ memorized_code = nuke.r_code for(var/obj/machinery/nuclearbomb/beer/beernuke as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/nuclearbomb/beer)) beernuke.r_code = memorized_code + return TRUE #undef SPAWN_AT_BASE #undef SPAWN_AT_INFILTRATOR @@ -341,14 +348,14 @@ /datum/team/nuclear/loneop /datum/team/nuclear/loneop/assign_nuke() - memorized_code = random_nukecode() var/obj/machinery/nuclearbomb/selfdestruct/nuke = locate() in SSmachines.get_machines_by_type(/obj/machinery/nuclearbomb/selfdestruct) - if(nuke) - tracked_nuke = nuke - if(nuke.r_code == NUKE_CODE_UNSET) - nuke.r_code = memorized_code - else //Already set by admins/something else? - memorized_code = nuke.r_code - else - stack_trace("Station self-destruct not found during lone op team creation.") - memorized_code = null + if(!nuke) + return FALSE + + memorized_code = random_nukecode() + tracked_nuke = nuke + if(nuke.r_code == NUKE_CODE_UNSET) + nuke.r_code = memorized_code + else //Already set by admins/something else? + memorized_code = nuke.r_code + return TRUE diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index 2f5249158204..a38ffbadce84 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -534,7 +534,7 @@ shock_turf_windup(owner.loc) /obj/item/clothing/suit/armor/reactive/weather/proc/shock_turf_windup(turf/target) - new /obj/effect/temp_visual/telegraphing/thunderbolt(target) + new /obj/effect/temp_visual/telegraphing/circle(target) addtimer(CALLBACK(src, PROC_REF(shock_turf), target), 1 SECONDS) /obj/item/clothing/suit/armor/reactive/weather/proc/shock_turf(turf/target) diff --git a/code/modules/jobs/job_types/antagonists/nuclear_operative.dm b/code/modules/jobs/job_types/antagonists/nuclear_operative.dm index f2e38bf27d74..ac4b7be3435b 100644 --- a/code/modules/jobs/job_types/antagonists/nuclear_operative.dm +++ b/code/modules/jobs/job_types/antagonists/nuclear_operative.dm @@ -2,26 +2,20 @@ title = ROLE_OPERATIVE /datum/job/nuclear_operative/get_roundstart_spawn_point() - return pick(GLOB.nukeop_start) + return pick(GLOB.nukeop_elevator_start) /datum/job/nuclear_operative/get_latejoin_spawn_point() - return pick(GLOB.nukeop_start) + return pick(GLOB.nukeop_base_start) /datum/job/nuclear_operative/leader -/datum/job/nuclear_operative/leader/get_roundstart_spawn_point() - return pick(GLOB.nukeop_leader_start) - /datum/job/nuclear_operative/leader/get_latejoin_spawn_point() - return pick(GLOB.nukeop_leader_start) + return pick(GLOB.nukeop_base_leader_start) /datum/job/nuclear_operative/clown_operative title = ROLE_CLOWN_OPERATIVE /datum/job/nuclear_operative/clown_operative/leader -/datum/job/nuclear_operative/clown_operative/leader/get_roundstart_spawn_point() - return pick(GLOB.nukeop_leader_start) - /datum/job/nuclear_operative/clown_operative/leader/get_latejoin_spawn_point() - return pick(GLOB.nukeop_leader_start) + return pick(GLOB.nukeop_base_leader_start) diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm b/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm index ce57035a7661..b18e740f8cb4 100644 --- a/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm +++ b/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm @@ -123,4 +123,77 @@ resistance_flags = FIRE_PROOF | LAVA_PROOF max_integrity = 200 +/obj/effect/light_emitter/tendril + set_luminosity = 4 + set_cap = 2.5 + light_color = LIGHT_COLOR_LAVA + +/obj/effect/collapse + name = "collapsing necropolis tendril" + desc = "Get your loot and get clear!" + layer = TABLE_LAYER + icon = 'icons/mob/simple/lavaland/nest.dmi' + icon_state = "tendril" + anchored = TRUE + density = TRUE + /// weakref list of which mobs have gotten their loot from this effect. + var/list/collected = list() + /// a bit of light as to make less unfair deaths from the chasm + var/obj/effect/light_emitter/tendril/emitted_light + +/obj/effect/collapse/Initialize(mapload) + . = ..() + emitted_light = new(loc) + visible_message(span_bolddanger("The tendril writhes in fury as the earth around it begins to crack and break apart! Get back!")) + balloon_alert_to_viewers("interact to grab loot before collapse!", vision_distance = 7) + playsound(loc,'sound/effects/tendril_destroyed.ogg', 200, FALSE, 50, TRUE, TRUE) + addtimer(CALLBACK(src, PROC_REF(collapse)), 5 SECONDS) + +/obj/effect/collapse/examine(mob/user) + var/list/examine_messages = ..() + if(isliving(user)) + if(has_collected(user)) + examine_messages += span_boldnotice("You've grabbed what you can, now get out!") + else + examine_messages += span_boldnotice("You might have some time to grab some goodies with an open hand before it collapses!") + return examine_messages + +/obj/effect/collapse/attack_hand(mob/living/collector, list/modifiers) + . = ..() + if(has_collected(collector)) + to_chat(collector, span_danger("You've already gotten some loot, just get out of there with it!")) + return + visible_message(span_warning("Something falls free of the tendril!")) + var/obj/structure/closet/crate/necropolis/tendril/loot = new /obj/structure/closet/crate/necropolis/tendril(loc) + collector.start_pulling(loot) + collected += WEAKREF(collector) + +/obj/effect/collapse/Destroy() + collected.Cut() + QDEL_NULL(emitted_light) + return ..() + +///Helper proc that resolves weakrefs to determine if collector is in collected list, returning a boolean. +/obj/effect/collapse/proc/has_collected(mob/collector) + for(var/datum/weakref/weakref as anything in collected) + var/mob/living/resolved = weakref.resolve() + //it could have been collector, it could not have been, we don't care + if(!resolved) + continue + if(resolved == collector) + return TRUE + return FALSE + +/obj/effect/collapse/proc/collapse() + for(var/mob/viewer in range(7, src)) + shake_camera(viewer, 15, 1) + playsound(get_turf(src),'sound/effects/explosion/explosionfar.ogg', 200, TRUE) + visible_message(span_bolddanger("The tendril falls inward, the ground around it widening into a yawning chasm!")) + for(var/turf/ground in RANGE_TURFS(2, src)) + if(HAS_TRAIT(ground, TRAIT_NO_TERRAFORM)) + continue + if(!ground.density) + ground.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland, flags = CHANGETURF_INHERIT_AIR) + qdel(src) + #undef ASH_WALKER_SPAWN_THRESHOLD diff --git a/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm index bb07998534c0..9f72047fcdea 100644 --- a/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm @@ -218,7 +218,7 @@ var/backstabbed = FALSE var/def_check = target.getarmor(type = BOMB) // Backstab bonus - if(check_behind(user, target) || boosted_mark) + if(check_behind(user, target) && !HAS_TRAIT(target, TRAIT_BACKSTAB_IMMUNE) || boosted_mark) backstabbed = TRUE combined_damage += backstab_bonus playsound(user, backstab_sound, 100, TRUE) //Seriously who spelled it wrong diff --git a/code/modules/mining/equipment/monster_organs/rush_gland.dm b/code/modules/mining/equipment/monster_organs/rush_gland.dm index 69c799741700..f22bf029a8c7 100644 --- a/code/modules/mining/equipment/monster_organs/rush_gland.dm +++ b/code/modules/mining/equipment/monster_organs/rush_gland.dm @@ -24,6 +24,7 @@ /obj/item/organ/monster_core/rush_gland/on_mob_insert(mob/living/carbon/organ_owner) . = ..() RegisterSignal(organ_owner, COMSIG_GOLIATH_TENTACLED_GRABBED, PROC_REF(trigger_organ_action_on_sig)) + RegisterSignal(organ_owner, COMSIG_TENDRIL_TENTACLED_GRABBED, PROC_REF(trigger_organ_action_on_sig)) /obj/item/organ/monster_core/rush_gland/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) . = ..() diff --git a/code/modules/mining/lavaland/mining_loot/megafauna/legion.dm b/code/modules/mining/lavaland/mining_loot/megafauna/legion.dm index cb8fa5532718..4ec0dbfe49e3 100644 --- a/code/modules/mining/lavaland/mining_loot/megafauna/legion.dm +++ b/code/modules/mining/lavaland/mining_loot/megafauna/legion.dm @@ -95,7 +95,7 @@ playsound(src, 'sound/effects/magic/lightningshock.ogg', 10, TRUE, extrarange = SILENCED_SOUND_EXTRARANGE, falloff_distance = 0) targeted_turfs += target_turf balloon_alert(user, "you aim at [target_turf]...") - new /obj/effect/temp_visual/telegraphing/thunderbolt(target_turf) + new /obj/effect/temp_visual/telegraphing/circle(target_turf) addtimer(CALLBACK(src, PROC_REF(throw_thunderbolt), target_turf, power_boosted), 1.5 SECONDS) thunder_charges-- addtimer(CALLBACK(src, PROC_REF(recharge)), thunder_charge_time) diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril.dm b/code/modules/mob/living/basic/lavaland/tendril/tendril.dm new file mode 100644 index 000000000000..b31e8a982a73 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril.dm @@ -0,0 +1,155 @@ +#define HEARTBEAT_NORMAL (1.8 SECONDS) +#define HEARTBEAT_FAST (1 SECONDS) +#define HEARTBEAT_FRANTIC (0.4 SECONDS) + +GLOBAL_LIST_INIT(tendrils, list()) + +/mob/living/basic/mining/tendril + name = "necropolis tendril" + desc = "A vile tendril of corruption, originating deep underground." + icon = 'icons/mob/simple/lavaland/tendril.dmi' + icon_state = "tendril" + icon_living = "tendril" + pixel_w = -8 + base_pixel_w = -8 + status_flags = NONE + mob_biotypes = MOB_ORGANIC | MOB_SKELETAL | MOB_MINING + basic_mob_flags = DEL_ON_DEATH | IMMUNE_TO_FISTS + mob_size = MOB_SIZE_HUGE + maxHealth = 1200 + health = 1200 + + friendly_verb_continuous = "flails at" + friendly_verb_simple = "flail at" + speak_emote = list("resonates") + obj_damage = 100 + melee_damage_lower = 20 + melee_damage_upper = 20 + sharpness = SHARP_POINTY + wound_bonus = CANT_WOUND + attack_sound = 'sound/items/weapons/pierce.ogg' + attack_verb_continuous = "pierces through" + attack_verb_simple = "pierce through" + throw_blocked_message = "does nothing to the thick shell of" + move_resist = INFINITY + + light_range = 3 + light_power = 2 + light_color = LIGHT_COLOR_LAVA + + ai_controller = /datum/ai_controller/basic_controller/tendril + + /// Looping heartbeat sound + var/datum/looping_sound/heartbeat/soundloop + /// Melee attack ability to used in retaliation to melee strikes and whenever it manages to grab someone + var/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_melee/tendril_melee + +/mob/living/basic/mining/tendril/Initialize(mapload) + . = ..() + GLOB.tendrils += src + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/death_drops, /obj/structure/closet/crate/necropolis/tendril) + AddComponent(/datum/component/ai_target_timer) + AddComponent(/datum/component/gps, "Eerie Signal") + AddComponent(/datum/component/basic_mob_attack_telegraph, display_telegraph_overlay = FALSE, telegraph_duration = 0.4 SECONDS) + AddComponent(/datum/component/regenerator, regeneration_delay = 30 SECONDS, brute_per_second = 20) + add_traits(list(TRAIT_BACKSTAB_IMMUNE, TRAIT_IMMOBILIZED), INNATE_TRAIT) + + var/static/list/abilities = list( + /datum/action/cooldown/mob_cooldown/projectile_attack/tendril_lash = BB_TENDRIL_LASH, + /datum/action/cooldown/mob_cooldown/tendril_chaser = BB_TENDRIL_CHASER, + /datum/action/cooldown/mob_cooldown/tendril_cross_spikes = BB_TENDRIL_SPIKES, + ) + grant_actions_by_list(abilities) + + tendril_melee = new(src) + tendril_melee.Grant(src) + AddComponent(/datum/component/revenge_ability, tendril_melee, targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]), max_range = 2, target_self = TRUE) + + soundloop = new(src, start_immediately = FALSE) + soundloop.mid_length = HEARTBEAT_NORMAL + soundloop.pressure_affected = FALSE + soundloop.start() + update_appearance(UPDATE_OVERLAYS) + + var/turf/our_turf = get_turf(src) + for (var/turf/rock in range(4, src)) + var/dist = sqrt((rock.x - our_turf.x) ** 2 + (rock.y - our_turf.y) ** 2) + if (dist > 4.5) + continue + + if (ismineralturf(rock)) + rock.ScrapeAway(null, CHANGETURF_IGNORE_AIR) + + if (istype(rock, /turf/open/misc/asteroid) && prob(100 / sqrt(max(1, dist)))) + rock.ChangeTurf(/turf/open/indestructible/necropolis, null, CHANGETURF_IGNORE_AIR) + +/mob/living/basic/mining/tendril/Destroy() + GLOB.tendrils -= src + QDEL_NULL(soundloop) + + if(!SSachievements.achievements_enabled || (flags_1 & ADMIN_SPAWNED_1)) + return ..() + + for(var/mob/living/killer in view(7, src)) + if(killer.stat || !killer.client) + continue + killer.client.give_award(/datum/award/score/tendril_score, killer) + if (!length(GLOB.tendrils)) + killer.client.give_award(/datum/award/achievement/boss/tendril_exterminator, killer) + + return ..() + +/mob/living/basic/mining/tendril/update_overlays() + . = ..() + . += emissive_appearance(icon, "[icon_state]_e", src, effect_type = EMISSIVE_NO_BLOOM) + . += emissive_appearance(icon, "[icon_state]_e_bloom", src, effect_type = EMISSIVE_BLOOM) + +/mob/living/basic/mining/tendril/Life(seconds_per_tick) + . = ..() + update_heartbeat() // Just a single math op unless we need updating so its fine to put it here + +/mob/living/basic/mining/tendril/updatehealth() + . = ..() + update_heartbeat() + +/mob/living/basic/mining/tendril/proc/update_heartbeat() + if (!soundloop) + return + + var/beat_rate = HEARTBEAT_NORMAL + if (ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + beat_rate = round(HEARTBEAT_FRANTIC + health / maxHealth * (HEARTBEAT_FAST - HEARTBEAT_FRANTIC), 0.05 SECONDS) + + if (beat_rate != soundloop.mid_length) + soundloop.set_mid_length(beat_rate) + +/mob/living/basic/mining/tendril/do_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item, no_effect, fov_effect = TRUE, item_animation_override = null) + if(!no_effect && (visual_effect_icon || used_item)) + do_item_attack_animation(attacked_atom, visual_effect_icon, used_item, animation_type = item_animation_override) + + // Stabby visuals + var/obj/effect/temp_visual/spike_stab/stab = new(get_turf(src)) + var/target_dir = get_dir(src, attacked_atom) + stab.transform = matrix().Turn(dir2angle(target_dir) + rand(-7, 7)) + if (target_dir & NORTH) + stab.pixel_z = 24 + else if (target_dir & SOUTH) + stab.pixel_z = -24 + if (target_dir & EAST) + stab.pixel_w = 24 + else if (target_dir & WEST) + stab.pixel_w = -24 + +/obj/effect/temp_visual/spike_stab + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "spike_small" + duration = 0.4 SECONDS + +/mob/living/basic/mining/tendril/proc/snatch_react() + if (tendril_melee.IsAvailable()) + tendril_melee.Activate(warning = FALSE) + +#undef HEARTBEAT_NORMAL +#undef HEARTBEAT_FAST +#undef HEARTBEAT_FRANTIC diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril_actions.dm b/code/modules/mob/living/basic/lavaland/tendril/tendril_actions.dm new file mode 100644 index 000000000000..ba4bb3b219d8 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril_actions.dm @@ -0,0 +1,358 @@ +/// Fires out two cross patterns of damaging tentacles which reel in anything they hit, then causes a followup attack +/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_lash + name = "Tentacle Lash" + desc = "Lash out with your tentacles in 8 directions, reeling in whatever you hit and unleashing a deadly followup attack afterwards." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "goliath_tentacle_wiggle" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = FALSE + cooldown_time = 8 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + projectile_type = /obj/projectile/tentacle_lash + +/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_lash/Activate(atom/target) + disable_cooldown_actions() + + for (var/swipe_dir in GLOB.cardinals) + var/turf/open/line_turf = get_step(owner, swipe_dir) + for (var/i in 1 to projectile_type::range) + if (!istype(line_turf) || line_turf.is_blocked_turf(exclude_mobs = TRUE)) + break + var/obj/effect/temp_visual/telegraphing/line/telegraph = new(line_turf) + telegraph.dir = swipe_dir + line_turf = get_step(line_turf, swipe_dir) + + SLEEP_CHECK_DEATH(0.8 SECONDS, owner) + + for (var/swipe_dir in GLOB.cardinals) + shoot_projectile(get_turf(owner), get_step(owner, swipe_dir), dir2angle(swipe_dir), owner) + + SLEEP_CHECK_DEATH(1.6 SECONDS, owner) + + for (var/swipe_dir in GLOB.diagonals) + var/turf/open/line_turf = get_step(owner, swipe_dir) + for (var/i in 1 to projectile_type::range) + if (!istype(line_turf) || line_turf.is_blocked_turf(exclude_mobs = TRUE)) + break + var/obj/effect/temp_visual/telegraphing/line/telegraph = new(line_turf) + telegraph.dir = swipe_dir + line_turf = get_step(line_turf, swipe_dir) + + SLEEP_CHECK_DEATH(0.8 SECONDS, owner) + + for (var/swipe_dir in GLOB.diagonals) + shoot_projectile(get_turf(owner), get_step(owner, swipe_dir), dir2angle(swipe_dir), owner) + + SLEEP_CHECK_DEATH(1.2 SECONDS, owner) + StartCooldown() + SLEEP_CHECK_DEATH(0.6 SECONDS, owner) + enable_cooldown_actions() + return TRUE + +/obj/projectile/tentacle_lash + name = "tentacle spike" + icon_state = "tentacle_spike" + pass_flags = PASSTABLE + damage = 5 // +10 from the grab + armor_flag = MELEE + range = 7 + hit_prone_targets = TRUE + hitsound = 'sound/effects/wounds/pierce1.ogg' + sharpness = SHARP_POINTY + /// Beam connecting us and the firer + var/datum/beam/tentacle_beam = null + /// Does this projectile persist and reel in targets? + var/reel_in = TRUE + /// How long does the projectile persist? + var/duration = 1.2 SECONDS + /// Damage dealt to targets who get snatched from entering the beam or being hit directly + var/snatch_damage = 10 + +/obj/projectile/tentacle_lash/stab + damage = 15 + range = 2 + duration = 0.2 SECONDS // Just for visual flair + reel_in = FALSE + +/obj/projectile/tentacle_lash/fire(fire_angle, atom/direct_target) + . = ..() + if (!firer) + return + tentacle_beam = Beam(firer, "goliath_tentacle", beam_type = (reel_in ? /obj/effect/ebeam/reacting : /obj/effect/ebeam), emissive = FALSE) + if (reel_in) + RegisterSignal(tentacle_beam, COMSIG_BEAM_ENTERED, PROC_REF(on_beam_entered)) + +/obj/projectile/tentacle_lash/Destroy() + QDEL_NULL(tentacle_beam) + return ..() + +// Don't range out, stop and persist until we're done +/obj/projectile/tentacle_lash/on_range() + STOP_PROCESSING(SSprojectiles, src) + // Reset to tile center + pixel_x = 0 + pixel_y = 0 + QDEL_IN(src, duration) + +/obj/projectile/tentacle_lash/prehit_pierce(atom/target) + // Ye 'ole colossus cheese + if (astype(target, /mob/living)?.stat == DEAD) + return PROJECTILE_PIERCE_PHASE + return ..() + +/obj/projectile/tentacle_lash/on_hit(atom/target, blocked, pierce_hit) + . = ..() + if (!reel_in || !isliving(target) || blocked >= 100 || pierce_hit || . != BULLET_ACT_HIT) + return + snatch_target(target) + +/obj/projectile/tentacle_lash/proc/on_beam_entered(datum/beam/source, obj/effect/ebeam/hit, atom/movable/entered) + SIGNAL_HANDLER + + if (!reel_in || entered == firer || !isliving(entered)) + return + + var/mob/living/victim = entered + if ((!firer || !firer.faction_check_atom(victim)) && victim.stat != DEAD) + INVOKE_ASYNC(src, PROC_REF(snatch_target), entered) + +/obj/projectile/tentacle_lash/proc/snatch_target(mob/living/victim) + if (HAS_TRAIT(victim, TRAIT_TENTACLE_IMMUNE) || SEND_SIGNAL(victim, COMSIG_TENDRIL_TENTACLED_GRABBED) & COMPONENT_TENDRIL_CANCEL_TENTACLE_GRAB) + return + + to_chat(victim, span_userdanger("You're snatched by [firer]'s tentacles!")) + victim.apply_damage(snatch_damage, BRUTE, BODY_ZONE_CHEST, wound_bonus = CANT_WOUND) + if (QDELETED(victim)) + qdel(src) + return + var/snatch_callback = null + if (istype(firer, /mob/living/basic/mining/tendril)) + var/mob/living/basic/mining/tendril/tendril = firer + snatch_callback = CALLBACK(tendril, TYPE_PROC_REF(/mob/living/basic/mining/tendril, snatch_react)) + victim.throw_at(firer, initial(range), 1, firer, FALSE, gentle = TRUE, callback = snatch_callback) + playsound(victim, hitsound, 50, -3, pressure_affected = FALSE) + qdel(src) + +/// An ability which makes spikes come out of the ground towards your target +/datum/action/cooldown/mob_cooldown/tendril_chaser + name = "Impaling Spikes" + desc = "Send a spiked subterranean tendril chasing after your target." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "spikes_stabbing" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + cooldown_time = 8 SECONDS + click_to_activate = TRUE + shared_cooldown = NONE + /// Lazy list of references to spike trails + var/list/active_chasers + /// Health percentage threshold at which we send out wide charsers after the main target + var/wide_chaser_threshold = 0.7 + +/datum/action/cooldown/mob_cooldown/tendril_chaser/Grant(mob/granted_to) + . = ..() + RegisterSignal(granted_to, COMSIG_MOB_ABILITY_STARTED, PROC_REF(on_ability_started)) + RegisterSignal(granted_to, COMSIG_MOB_ABILITY_FINISHED, PROC_REF(on_ability_finished)) + +// Clean up after ourselves +/datum/action/cooldown/mob_cooldown/tendril_chaser/Remove(mob/removed_from) + UnregisterSignal(removed_from, list(COMSIG_MOB_ABILITY_STARTED, COMSIG_MOB_ABILITY_FINISHED)) + QDEL_LIST(active_chasers) + return ..() + +/datum/action/cooldown/mob_cooldown/tendril_chaser/proc/on_ability_started(mob/living/owner, datum/action/cooldown/activated) + SIGNAL_HANDLER + + // Delete all of our chasers when our owner triggers cross spikes as to not cause guaranteed damage + if (istype(activated, /datum/action/cooldown/mob_cooldown/tendril_cross_spikes)) + QDEL_LIST(active_chasers) + +/datum/action/cooldown/mob_cooldown/tendril_chaser/proc/on_ability_finished(mob/living/owner, datum/action/cooldown/activated) + SIGNAL_HANDLER + + if (istype(activated, /datum/action/cooldown/mob_cooldown/tendril_cross_spikes)) + ResetCooldown() + +/datum/action/cooldown/mob_cooldown/tendril_chaser/Activate(atom/target) + . = ..() + var/primary_type = /obj/effect/temp_visual/effect_trail/tendril_chaser + if (isliving(owner)) + var/mob/living/as_living = owner + if (as_living.health / as_living.maxHealth <= wide_chaser_threshold) + primary_type = /obj/effect/temp_visual/effect_trail/tendril_chaser/wide_area + + var/obj/effect/temp_visual/effect_trail/tendril_chaser/chaser = new primary_type(get_turf(owner), target) + LAZYADD(active_chasers, WEAKREF(chaser)) + RegisterSignal(chaser, COMSIG_QDELETING, PROC_REF(on_chaser_destroyed)) + playsound(owner, 'sound/effects/magic/demon_attack1.ogg', vol = 100, vary = TRUE, pressure_affected = FALSE) + +/// Remove a spike trail from our list of active trails +/datum/action/cooldown/mob_cooldown/tendril_chaser/proc/on_chaser_destroyed(atom/chaser) + SIGNAL_HANDLER + LAZYREMOVE(active_chasers, WEAKREF(chaser)) + +/obj/effect/temp_visual/effect_trail/tendril_chaser + duration = 10 SECONDS + move_speed = 4 + spawned_effect = /obj/effect/temp_visual/emerging_ground_spike/tendril + /// Do we spawn spikes around ourselves as well or only on our own turf? + var/area_spawn = FALSE + +/obj/effect/temp_visual/effect_trail/tendril_chaser/wide_area + area_spawn = TRUE + +/obj/effect/temp_visual/effect_trail/tendril_chaser/add_spawner() + return + +/obj/effect/temp_visual/effect_trail/tendril_chaser/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + if (!area_spawn) + var/turf/spawn_turf = get_turf(src) + if (!(locate(/obj/effect/temp_visual/emerging_ground_spike/tendril) in spawn_turf) && isopenturf(spawn_turf)) + new spawned_effect(spawn_turf) + return + + for (var/spawn_dir in GLOB.cardinals) + if (spawn_dir & REVERSE_DIR(movement_dir)) + continue + var/turf/spawn_loc = get_step(src, spawn_dir) + if (!(locate(/obj/effect/temp_visual/emerging_ground_spike/tendril) in spawn_loc) && isopenturf(spawn_loc)) + new spawned_effect(spawn_loc) + +/obj/effect/temp_visual/emerging_ground_spike/tendril + icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + icon_state = "spikes_stabbing" + duration = 0.7 SECONDS + position_variance = 3 + impale_damage = 10 + damage_blacklist_typecache = list( + /mob/living/basic/mining/tendril, + ) + impale_wound_bonus = CANT_WOUND + // Have we hit someone yet? + var/hit_loser = FALSE + +/obj/effect/temp_visual/emerging_ground_spike/tendril/single + icon_state = "spike" + duration = 1 SECONDS + harm_delay = 0.25 SECONDS + position_variance = 5 + +/obj/effect/temp_visual/emerging_ground_spike/tendril/impale() + . = ..() + hit_loser |= . + RegisterSignal(loc, COMSIG_ATOM_ENTERED, PROC_REF(on_entered)) + +/obj/effect/temp_visual/emerging_ground_spike/tendril/proc/on_entered(atom/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + SIGNAL_HANDLER + + if (!isliving(arrived)) + return + + if (harm_mob(arrived) && !hit_loser) + playsound(src, 'sound/items/weapons/slice.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) + hit_loser = TRUE + +/datum/action/cooldown/mob_cooldown/tendril_cross_spikes + name = "Cross Spikes" + desc = "Create a wave of spikes around yourself, impaling anyone caught in it." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "spike" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = FALSE + cooldown_time = 10 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + /// Health threshold at which we reduce the amount of empty spots on the ground + var/health_threshold = 0.3 + +/datum/action/cooldown/mob_cooldown/tendril_cross_spikes/Activate(atom/target) + disable_cooldown_actions() + spawn_spikes() + SLEEP_CHECK_DEATH(0.4 SECONDS, owner) + spawn_spikes(inverse = TRUE) + StartCooldown() + enable_cooldown_actions() + return TRUE + +/datum/action/cooldown/mob_cooldown/tendril_cross_spikes/proc/spawn_spikes(inverse = FALSE) + var/list/turf/spike_turfs = list() + var/turf/owner_turf = get_turf(owner) + + var/reduced_spawns = FALSE + if (isliving(owner)) + var/mob/living/as_living = owner + if (as_living.health / as_living.maxHealth <= health_threshold) + reduced_spawns = TRUE + + for (var/turf/open/target_turf in oview(7, owner_turf)) + if (sqrt((target_turf.x - owner_turf.x) ** 2 + (target_turf.y - owner_turf.y) ** 2) > 9.5) // big circle is a lie + continue + + if (reduced_spawns) + if (abs(target_turf.x - owner_turf.x) % 2 == abs(target_turf.y - owner_turf.y + inverse) % 2) + new /obj/effect/temp_visual/telegraphing/circle/short(target_turf) + spike_turfs += target_turf + continue + + var/row_diff = inverse ? abs(target_turf.x - owner_turf.x) : abs(target_turf.y - owner_turf.y) + var/column_diff = inverse ? abs(target_turf.y - owner_turf.y) : abs(target_turf.x - owner_turf.x) + var/row = floor((row_diff + 1) / 3) + if (row % 2 == 0) + if (row_diff - row * 3 == 0) + if (column_diff % 4 == 2) + continue + else + if (column_diff % 4 != 0) + continue + else + if (row_diff - row * 3 == 0) + if (column_diff % 4 == 0) + continue + else + if (column_diff % 4 != 2) + continue + + new /obj/effect/temp_visual/telegraphing/circle/short(target_turf) + spike_turfs += target_turf + + SLEEP_CHECK_DEATH(1 SECONDS, owner) + + for (var/turf/open/to_spawn in spike_turfs) + new /obj/effect/temp_visual/emerging_ground_spike/tendril/single(to_spawn) + + SLEEP_CHECK_DEATH(0.8 SECONDS, owner) + +/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_melee + name = "Tentacle Stab" + desc = "Stab nearby hostiles with long tentacles." + button_icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' + button_icon_state = "goliath_tentacle_wiggle" + background_icon_state = "bg_demon" + overlay_icon_state = "bg_demon_border" + click_to_activate = FALSE + cooldown_time = 4 SECONDS + melee_cooldown_time = 0 + shared_cooldown = NONE + projectile_type = /obj/projectile/tentacle_lash/stab + +/datum/action/cooldown/mob_cooldown/projectile_attack/tendril_melee/Activate(atom/target_atom, warning = TRUE) + if (warning) + for (var/stab_dir in GLOB.alldirs) + var/turf/open/stab_turf = get_step(owner, stab_dir) + if (!istype(stab_turf)) + continue + var/obj/effect/temp_visual/telegraphing/line/short/telegraph = new(stab_turf) + telegraph.dir = stab_dir + + SLEEP_CHECK_DEATH(0.5 SECONDS, owner) + + for (var/stab_dir in GLOB.alldirs) + shoot_projectile(get_turf(owner), get_step(owner, stab_dir), firer = owner) + + SLEEP_CHECK_DEATH(0.5 SECONDS, owner) + StartCooldown() + return TRUE diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm b/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm new file mode 100644 index 000000000000..77da7086dfa6 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm @@ -0,0 +1,35 @@ +/datum/ai_controller/basic_controller/tendril + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + ) + + planning_subtrees = list( + /datum/ai_planning_subtree/call_reinforcements/mining, + /datum/ai_planning_subtree/target_retaliate/check_faction, + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser, + /datum/ai_planning_subtree/use_mob_ability/tendril_spikes, + /datum/ai_planning_subtree/use_mob_ability/tendril_lash, + ) + +/datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser + ability_key = BB_TENDRIL_CHASER + operational_datums = list(/datum/component/ai_target_timer) + finish_planning = FALSE + +/datum/ai_planning_subtree/use_mob_ability/tendril_lash + ability_key = BB_TENDRIL_LASH + +/datum/ai_planning_subtree/use_mob_ability/tendril_lash/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if (!controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + return FALSE + return ..() + +/datum/ai_planning_subtree/use_mob_ability/tendril_spikes + ability_key = BB_TENDRIL_SPIKES + +/datum/ai_planning_subtree/use_mob_ability/tendril_spikes/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if (!controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + return FALSE + return ..() diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm index b50b089b0ffa..60288e9c5e74 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm @@ -45,6 +45,8 @@ var/position_variance = 8 /// Damage to deal on impale var/impale_damage = 15 + /// Wound bonus on impale + var/impale_wound_bonus = 0 /// Typecache of types of mobs not to damage var/list/damage_blacklist_typecache = list( /mob/living/basic/meteor_heart, @@ -67,14 +69,22 @@ /obj/effect/temp_visual/emerging_ground_spike/proc/impale() if (!isturf(loc)) return + var/hit_someone = FALSE for(var/mob/living/victim in loc) - if (is_type_in_typecache(victim, damage_blacklist_typecache)) - continue - hit_someone = TRUE - var/target_zone = victim.resting ? BODY_ZONE_CHEST : pick_weight(standing_damage_zones) - victim.apply_damage(impale_damage, damagetype = BRUTE, def_zone = target_zone, sharpness = SHARP_POINTY) + if (harm_mob(victim)) + hit_someone = TRUE + if (hit_someone) playsound(src, 'sound/items/weapons/slice.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) else playsound(src, 'sound/misc/splort.ogg', vol = 25, vary = TRUE, pressure_affected = FALSE) + return hit_someone + +/obj/effect/temp_visual/emerging_ground_spike/proc/harm_mob(mob/living/victim) + if (is_type_in_typecache(victim, damage_blacklist_typecache)) + return FALSE + + var/target_zone = victim.resting ? BODY_ZONE_CHEST : pick_weight(standing_damage_zones) + victim.apply_damage(impale_damage, damagetype = BRUTE, def_zone = target_zone, wound_bonus = impale_wound_bonus, sharpness = SHARP_POINTY) + return TRUE diff --git a/code/modules/modular_computers/computers/item/pda.dm b/code/modules/modular_computers/computers/item/pda.dm index a2c83e5d6e7d..cfb6eb66554a 100644 --- a/code/modules/modular_computers/computers/item/pda.dm +++ b/code/modules/modular_computers/computers/item/pda.dm @@ -362,6 +362,7 @@ /obj/item/modular_computer/pda/silicon/Destroy() silicon_owner = null + robotact = null return ..() ///Silicons don't have the tools (or hands) to make circuits setups with their own PDAs. diff --git a/code/modules/modular_computers/file_system/data.dm b/code/modules/modular_computers/file_system/data.dm index 7721d50b7639..661318f4baf9 100644 --- a/code/modules/modular_computers/file_system/data.dm +++ b/code/modules/modular_computers/file_system/data.dm @@ -98,6 +98,10 @@ src.workspace = workspace src.source_photo_or_painting = source_photo_or_painting +/datum/computer_file/data/paint_project/Destroy(force) + source_photo_or_painting = null + return ..() + /datum/computer_file/data/paint_project/clone(rename) var/datum/computer_file/data/paint_project/temp = ..() temp.workspace = workspace.copy() diff --git a/code/modules/modular_computers/file_system/image_file.dm b/code/modules/modular_computers/file_system/image_file.dm index f029793701d2..8dcfd942f227 100644 --- a/code/modules/modular_computers/file_system/image_file.dm +++ b/code/modules/modular_computers/file_system/image_file.dm @@ -27,6 +27,10 @@ src.image_name = image_name set_source(source_photo_or_painting) +/datum/computer_file/image/Destroy(force) + source_photo_or_painting = null + return ..() + /datum/computer_file/image/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing) . = ..() assign_path() diff --git a/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm index f71d985197c9..5ea1bb1f51b9 100644 --- a/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/chatroom/ntnrc_client.dm @@ -126,8 +126,8 @@ if("PRG_savelog") if(!channel) return - var/logname = stripped_input(params["log_name"]) - if(!logname) + var/logname = trim(params["log_name"], MAX_MESSAGE_LEN) + if(!length(logname) || !filter_filename_pda(logname)) return var/datum/computer_file/data/text/logfile = new() // Now we will generate HTML-compliant file that can actually be viewed/printed. diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm index d25a9894307e..8c3dbfe213a5 100644 --- a/code/modules/modular_computers/file_system/programs/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/file_browser.dm @@ -119,8 +119,8 @@ GLOBAL_LIST_INIT(print_types, init_print_types()) var/datum/computer_file/file = computer.find_file_by_name(params["name"]) if(!file) return - var/newname = reject_bad_name(params["new_name"], allow_numbers = TRUE, cap_after_symbols = FALSE, cap_at_start = FALSE) - if(!newname || newname != params["new_name"]) + var/newname = trim(params["new_name"], MAX_MESSAGE_LEN) + if(!length(newname) || !filter_filename_pda(newname)) playsound(computer, 'sound/machines/terminal/terminal_error.ogg', 25, FALSE) return file.filename = newname @@ -131,8 +131,8 @@ GLOBAL_LIST_INIT(print_types, init_print_types()) var/datum/computer_file/file = computer.find_file_by_name(params["name"], computer.inserted_disk) if(!file) return - var/newname = reject_bad_name(params["new_name"], allow_numbers = TRUE, cap_after_symbols = FALSE, cap_at_start = FALSE) - if(!newname || newname != params["new_name"]) + var/newname = trim(params["new_name"], MAX_MESSAGE_LEN) + if(!length(newname) || !filter_filename_pda(newname)) playsound(computer, 'sound/machines/terminal/terminal_error.ogg', 25, FALSE) return file.filename = newname diff --git a/code/modules/modular_computers/file_system/programs/nanopaint.dm b/code/modules/modular_computers/file_system/programs/nanopaint.dm index 6e2d4bd49ab7..4d499db7c240 100644 --- a/code/modules/modular_computers/file_system/programs/nanopaint.dm +++ b/code/modules/modular_computers/file_system/programs/nanopaint.dm @@ -175,11 +175,14 @@ GLOBAL_LIST_INIT(nanopaint_supported_filetypes, zebra_typecacheof(list(\ return dialog = null var/uid = params["uid"] - var/new_file_name = params["name"] + var/new_file_name = trim(params["name"], MAX_MESSAGE_LEN) var/saving_to_disk = params["onDisk"] var/datum/computer_file/new_file_type = text2path(params["typepath"]) var/extension = new_file_type::filetype var/datum/computer_file/existing_file + if(!length(new_file_name)) + dialog = list("type" = "error", "message" = "No name specified.") + return TRUE if(saving_to_disk) if(!computer.inserted_disk) dialog = list("type" = "error", "message" = "[new_file_name] - The disk has been removed.") @@ -208,6 +211,12 @@ GLOBAL_LIST_INIT(nanopaint_supported_filetypes, zebra_typecacheof(list(\ else INVOKE_ASYNC(src, PROC_REF(write_to_file), user, existing_file) return TRUE + if(is_ic_filtered_for_pdas(new_file_name) || is_soft_ic_filtered_for_pdas(new_file_name)) + dialog = list("type" = "error", "message" = "The entered file name violates company policy.") + return TRUE + if(!filter_illegal_filename_chars(new_file_name)) + dialog = list("type" = "error", "message" = "[new_file_name] - File names cannot include the following characters. \n \\ / : * ? \" < > |") + return TRUE INVOKE_ASYNC(src, PROC_REF(save_file), user, new_file_name, new_file_type, saving_to_disk && computer.inserted_disk) return TRUE @@ -283,7 +292,7 @@ GLOBAL_LIST_INIT(nanopaint_supported_filetypes, zebra_typecacheof(list(\ /datum/computer_file/program/nanopaint/proc/save_file(mob/user, name, file_type, obj/item/disk/computer/target_disk) var/datum/computer_file/file = new file_type() - file.filename = reject_bad_name(name, allow_numbers = TRUE, cap_after_symbols = FALSE, cap_at_start = FALSE) + file.filename = name var/file_stored if(target_disk) file_stored = target_disk.add_file(file) diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm index aa2016c42897..52184d2c9592 100644 --- a/code/modules/power/lighting/light.dm +++ b/code/modules/power/lighting/light.dm @@ -81,6 +81,8 @@ var/power_consumption_rate = 20 ///break if moved, if false also makes it ignore if the wall its on breaks var/break_if_moved = TRUE + /// If TRUE we can break on init + var/allow_break_on_init = TRUE // create a new lighting fixture /obj/machinery/light/Initialize(mapload) @@ -126,13 +128,14 @@ /obj/machinery/light/post_machine_initialize() . = ..() #ifndef MAP_TEST - switch(fitting) - if("tube") - if(prob(2)) - break_light_tube(TRUE) - if("bulb") - if(prob(5)) - break_light_tube(TRUE) + if(allow_break_on_init) + switch(fitting) + if("tube") + if(prob(2)) + break_light_tube(TRUE) + if("bulb") + if(prob(5)) + break_light_tube(TRUE) #endif update(trigger = FALSE) diff --git a/code/modules/procedural_mapping/mapGenerators/lavaland.dm b/code/modules/procedural_mapping/mapGenerators/lavaland.dm index 5251f5e8435a..b638e7908dd8 100644 --- a/code/modules/procedural_mapping/mapGenerators/lavaland.dm +++ b/code/modules/procedural_mapping/mapGenerators/lavaland.dm @@ -18,9 +18,7 @@ /datum/map_generator_module/splatter_layer/lavaland_tendrils spawnableTurfs = list() - spawnableAtoms = list(/obj/structure/spawner/lavaland = 5, - /obj/structure/spawner/lavaland/legion = 5, - /obj/structure/spawner/lavaland/goliath = 5) + spawnableAtoms = list(/mob/living/basic/mining/tendril = 15) /datum/map_generator/lavaland/ground_only modules = list(/datum/map_generator_module/bottom_layer/lavaland_default) diff --git a/code/modules/projectiles/ammunition/ballistic/pistol.dm b/code/modules/projectiles/ammunition/ballistic/pistol.dm index e5ddae7c2e91..0805b0060f9d 100644 --- a/code/modules/projectiles/ammunition/ballistic/pistol.dm +++ b/code/modules/projectiles/ammunition/ballistic/pistol.dm @@ -22,11 +22,6 @@ desc = "A 10mm incendiary bullet casing." projectile_type = /obj/projectile/bullet/incendiary/c10mm -/obj/item/ammo_casing/c10mm/reaper - name = "10mm reaper bullet casing" - desc = "A 10mm reaper bullet casing." - projectile_type = /obj/projectile/bullet/c10mm/reaper - // 9mm (Makarov, Stechkin APS) /obj/item/ammo_casing/c9mm diff --git a/code/modules/projectiles/ammunition/ballistic/smg.dm b/code/modules/projectiles/ammunition/ballistic/smg.dm index 7e84bc1a9182..e2be056126b5 100644 --- a/code/modules/projectiles/ammunition/ballistic/smg.dm +++ b/code/modules/projectiles/ammunition/ballistic/smg.dm @@ -41,3 +41,8 @@ name = ".45 incendiary bullet casing" desc = "A .45 bullet casing." projectile_type = /obj/projectile/bullet/incendiary/c45 + +/obj/item/ammo_casing/c45/reaper + name = ".45 reaper bullet casing" + desc = "A .45 reaper bullet casing." + projectile_type = /obj/projectile/bullet/c45/reaper diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm index 7fbece15863e..27c3d39f6b3c 100644 --- a/code/modules/projectiles/boxes_magazines/external/pistol.dm +++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm @@ -92,25 +92,6 @@ MAGAZINE_TYPE_ARMORPIERCE ammo_type = /obj/item/ammo_casing/c10mm/ap -// Regal Condor (10mm) // - -/obj/item/ammo_box/magazine/r10mm - name = "regal condor magazine (10mm Reaper)" - desc = "A very expensive 10mm handgun magazine, suitable for the Regal Condor. Loaded with \"reaper\" rounds, which are dangerously effective against everything." - icon_state = "r10mm-8" - base_icon_state = "r10mm" - ammo_type = /obj/item/ammo_casing/c10mm/reaper - caliber = CALIBER_10MM - max_ammo = 8 - multiple_sprites = AMMO_BOX_PER_BULLET - multiple_sprite_use_base = TRUE - custom_materials = list( - /datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, - /datum/material/gold = SHEET_MATERIAL_AMOUNT * 10, - /datum/material/silver = SHEET_MATERIAL_AMOUNT * 10, - /datum/material/plasma = SHEET_MATERIAL_AMOUNT * 10, - ) - // M1911 (.45) // /obj/item/ammo_box/magazine/m45 @@ -134,3 +115,22 @@ caliber = CALIBER_50AE max_ammo = 7 multiple_sprites = AMMO_BOX_PER_BULLET + +// Regal Condor (.45) // + +/obj/item/ammo_box/magazine/r45 + name = "regal condor magazine (.45 Reaper)" + desc = "A very expensive .45 handgun magazine, suitable for the Regal Condor. Loaded with \"reaper\" rounds, which are dangerously effective against everything." + icon_state = "r45-8" + base_icon_state = "r45" + ammo_type = /obj/item/ammo_casing/c45/reaper + caliber = CALIBER_10MM + max_ammo = 8 + multiple_sprites = AMMO_BOX_PER_BULLET + multiple_sprite_use_base = TRUE + custom_materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, + /datum/material/gold = SHEET_MATERIAL_AMOUNT * 10, + /datum/material/silver = SHEET_MATERIAL_AMOUNT * 10, + /datum/material/plasma = SHEET_MATERIAL_AMOUNT * 10, + ) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 843f0fcb741c..d0f61730727d 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -124,6 +124,7 @@ add_seclight_point() add_bayonet_point() + add_deep_lore() RegisterSignal(src, COMSIG_ITEM_IN_UNWRAPPED_TRAITOR_MAIL, PROC_REF(on_mail_unwrap)) /obj/item/gun/Destroy() @@ -154,6 +155,10 @@ /obj/item/gun/proc/add_bayonet_point() return +/// For when you want to add the lore element to a gun, this is the proc to use. +/obj/item/gun/proc/add_deep_lore() + return + /obj/item/gun/Exited(atom/movable/gone, direction) . = ..() if(gone == pin) diff --git a/code/modules/projectiles/guns/ballistic/pistol.dm b/code/modules/projectiles/guns/ballistic/pistol.dm index c28ed3c012dd..08ddb0155ed1 100644 --- a/code/modules/projectiles/guns/ballistic/pistol.dm +++ b/code/modules/projectiles/guns/ballistic/pistol.dm @@ -60,6 +60,17 @@ empty_indicator = TRUE suppressor_x_offset = 12 +/obj/item/gun/ballistic/automatic/pistol/clandestine/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "Manufactured by Scarborough Arms, the Ansem is regarded as one of the best sidearms on the market. Minimal recoil combined with \ + high stopping power makes it a favourite amongst soldiers of fortune across known space.
\ +
\ + The ease of concealment due to the weapon's sleek profile has given it a well-earned reputation as the 'smuggler's gun of course'. While \ + space pirate bands tend to favour the HDS-1 created by Nanotrasen due to their low maintenance cost, the Ansem has seen plenty of use by \ + mercernaries, hitmen and espionage agents. Dozens of seized Ansem pistols line evidence lockups across the Spinward. Many with extremely \ + long and bloodied histories from years of service amongst underground criminal elements." \ + ) + /obj/item/gun/ballistic/automatic/pistol/clandestine/fisher name = "\improper Ansem/SC pistol" desc = "A modified variant of the Ansem, spiritual successor to the Makarov, featuring an integral suppressor and push-button trigger on the grip \ @@ -74,6 +85,8 @@ /obj/item/gun/ballistic/automatic/pistol/clandestine/fisher/Initialize(mapload) . = ..() underbarrel = new /obj/item/gun/energy/recharge/fisher(src) + +/obj/item/gun/ballistic/automatic/pistol/clandestine/fisher/add_deep_lore() AddElement(/datum/element/examine_lore, \ lore = "The Ansem/SC is a Scarborough Arms overhaul suite for their own Ansem handgun, designed for special operators who operate operationally, \ especially against people who like using lightbulbs.
\ @@ -125,6 +138,18 @@ lock_back_sound = 'sound/items/weapons/gun/pistol/slide_lock.ogg' bolt_drop_sound = 'sound/items/weapons/gun/pistol/slide_drop.ogg' +/obj/item/gun/ballistic/automatic/pistol/m1911/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "The Colt M1911, created by John Moses Browning centuries ago, is the primordial ancestor of modern automatic firearms. \ + Somehow, to this day, there are still officers who refuse to utilize anything that isn't chambered in 'God's Caliber'.
\ +
\ + Few want to tell any of these people that .45 ACP has struggled to puncture modern ballistic plating for almost five centuries.
\ +
\ + You are VERY confident that this particular example was lathed this decade. Most M1911's seen in the wild today are recreations of the weapon \ + made with modern alloyed metals and manufactoring techniques, mass produced for the consumer market. This does not stop fraudsters from \ + claiming that their weapon is a 'family heirloom' that has saved them from various conflicts that they insist they participated in." \ + ) + /** * Weak 1911 for syndicate chimps. It comes in a 4 TC kit. * 15 damage every.. second? 7 shots to kill. Not fast. @@ -136,6 +161,8 @@ projectile_wound_bonus = -12 pin = /obj/item/firing_pin/monkey +/obj/item/gun/ballistic/automatic/pistol/m1911/chimpgun/add_deep_lore() + return /obj/item/gun/ballistic/automatic/pistol/m1911/no_mag spawnwithmagazine = FALSE @@ -180,17 +207,30 @@ name = "\improper Regal Condor" desc = "Unlike the Desert Eagle, this weapon seems to utilize some kind of advanced internal stabilization system to significantly \ reduce felt recoil and increase overall accuracy, at the cost of using a smaller caliber. \ - This does allow it to fire a very quick 2-round burst. Uses 10mm ammo." + This allows it to fire a very quick 2-round burst. Chambered in .45." icon_state = "reagle" inhand_icon_state = "deagleg" burst_size = 2 burst_delay = 1 projectile_damage_multiplier = 1.25 - accepted_magazine_type = /obj/item/ammo_box/magazine/r10mm + accepted_magazine_type = /obj/item/ammo_box/magazine/r45 actions_types = list(/datum/action/item_action/toggle_firemode) obj_flags = UNIQUE_RENAME // if you did the sidequest, you get the customization custom_materials = list(/datum/material/gold = SHEET_MATERIAL_AMOUNT * 30, /datum/material/silver = SHEET_MATERIAL_AMOUNT * 25, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 11.5, /datum/material/telecrystal = SHEET_MATERIAL_AMOUNT * 4) +/obj/item/gun/ballistic/automatic/pistol/deagle/regal/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "One of the Culling Arms of the Tiger Cooperative, a collection of relic weapons said to have come to Martinson in a frenzied vision from \ + God to enact holy retribution upon the enemies of the faith. Almost all examples of these guns are replicas of the original pattern. This one is no \ + different. However, it carries some of the gravitus of the original firearm. And, some say, the holy might that it wielded against the enemies of the cult.
\ +
\ + The fact that you are looking at this relic weapon means one of two things.
\ +
\ + A) A Tiger Cooperative evangelist has died during or before they could commit a bloody massacre in the name of God. Or
\ +
\ + B) You are that evangelist, and will soon be adding another chapter to this gun's dark history." \ + ) + /obj/item/gun/ballistic/automatic/pistol/aps name = "\improper Stechkin APS machine pistol" desc = "An old Soviet machine pistol. It fires quickly, but kicks like a mule. Uses 9mm ammo. Has a threaded barrel for suppressors." @@ -225,10 +265,9 @@ #define DOORHICKEY_GUN_MIN_DAMAGE 70 #define DOORHICKEY_GUN_MAX_DAMAGE 140 -/obj/item/gun/ballistic/automatic/pistol/doorhickey +/obj/item/gun/ballistic/automatic/pistol/doohickey name = "\improper Liberator" - desc = "A poorly made 3D printed \"gun\", only capable of firing a single shot. Well-known throughout the Spinward Sector \ - after an incident where 3 assistants were killed by shrapnel from such a device exploding while attempting to shoot a mouse." + desc = "A poorly made 3D printed \"gun\", only capable of firing a single shot." icon_state = "doorhickey" custom_materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 2) bolt_type = BOLT_TYPE_NO_BOLT @@ -243,7 +282,7 @@ projectile_damage_multiplier = 0.5 spread = 10 -/obj/item/gun/ballistic/automatic/pistol/doorhickey/unload_ammo(mob/living/user, forced = FALSE) +/obj/item/gun/ballistic/automatic/pistol/doohickey/unload_ammo(mob/living/user, forced = FALSE) if (forced) return ..() @@ -254,7 +293,7 @@ return . = ..() -/obj/item/gun/ballistic/automatic/pistol/doorhickey/load_gun(obj/item/ammo, mob/living/user) +/obj/item/gun/ballistic/automatic/pistol/doohickey/load_gun(obj/item/ammo, mob/living/user) . = ..() if (!.) return @@ -268,7 +307,7 @@ unload_ammo(user, forced = TRUE) return FALSE -/obj/item/gun/ballistic/automatic/pistol/doorhickey/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) +/obj/item/gun/ballistic/automatic/pistol/doohickey/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) var/dmg_multiplier = 1 if (get_dist(target, user) <= 1) @@ -287,7 +326,7 @@ . = ..() projectile_damage_multiplier /= dmg_multiplier -/obj/item/gun/ballistic/automatic/pistol/doorhickey/shoot_live_shot(mob/living/user, pointblank = FALSE, atom/pbtarget = null, message = TRUE) +/obj/item/gun/ballistic/automatic/pistol/doohickey/shoot_live_shot(mob/living/user, pointblank = FALSE, atom/pbtarget = null, message = TRUE) . = ..() if (!.) return @@ -326,6 +365,26 @@ new /obj/effect/decal/cleanable/plastic(get_turf(src)) take_damage(damage_to_take) +/obj/item/gun/ballistic/automatic/pistol/doohickey/add_deep_lore() + AddElement(/datum/element/examine_lore, \ + lore = "The Liberator pattern, according to digital historians, was first posted to a fringe imageboard on the NTNet. The post included a now dead \ + link to a defuncting hosting service through which board members were encouraged to download and 'admire' the design. The thread's author began their \ + post by describing their extreme vitriolic hatred of so-called 'moon men' amongst the Nanotrasen 'elites'. And that, through 'the sauce', they had \ + been shown a means to 'end the oppression of Luna's Tormentors'. They called it the 'Liberator' as a result.
\ +
\ + Response to this thread began as skeptism amongst posters regarding the 3D pattern. Many highlighted the dubious functionality, let alone safe operation \ + of a gun created using cheap plastic and minimal machined parts firing a high pressure round through an unrifled barrel. But the thread suddenly began to \ + gain traction immediately following a particular news article published by the Spinward Dove, a respected editorial in the Spinward Sector. The author of this \ + article recounts that, during a particularly brisk day in New Moscow, they observed three off-duty Nanotrasen assistants attempting to use what the author \ + described as a 'bizarre plastic doohickey' to shoot a mouse at point blank. The horrific aftermath, and images of the rattled but otherwise unscathed mouse, lead many \ + to try and find from where these now crippled assistants had come across such an absurd device. Word spread of the post, and its author's continuously nonsensical \ + rants regarding their hated foe in other areas of the imageboard.
\ +
\ + The author of the thread returned immediately following the news, claiming that those who frequented the imageboard had 'stolen' their 'dreams of freedom', and \ + that clearly the moon men were attempting to discredit their invention through this publicity stunt. A slur-laiden rant from the thread author followed this declaration, \ + leading to the the moderators of the imageboard to step in and close the thread for good." \ + ) + /obj/item/disk/design_disk/liberator name = "illegal 3D printer design disk" diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm index de7922b8ef8d..a47d25218663 100644 --- a/code/modules/projectiles/guns/energy/laser.dm +++ b/code/modules/projectiles/guns/energy/laser.dm @@ -11,8 +11,6 @@ /obj/item/gun/energy/laser/Initialize(mapload) . = ..() - add_deep_lore() - // Only regular lasguns can be slapcrafted if(type != /obj/item/gun/energy/laser) return @@ -305,7 +303,7 @@ // Laser Gun -/obj/item/gun/energy/laser/proc/add_deep_lore() +/obj/item/gun/energy/laser/add_deep_lore() AddElement(/datum/element/examine_lore, \ lore_hint = span_notice("You can [EXAMINE_HINT("look closer")] to learn a little more about [src]."), \ lore = "The NT Type 5 Heat Delivery System (sometimes referred to as the HDS-5 in promotional material) is what truly put Nanotrasen \ diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 3c695a10b738..d7aa59766b3c 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -1251,8 +1251,8 @@ free_hitscan_forceMove = TRUE forceMove(source_loc) starting = source_loc - pixel_x = source.pixel_x - pixel_y = source.pixel_y + pixel_x = source.pixel_x - source.base_pixel_x + pixel_y = source.pixel_y - source.base_pixel_y original = target // Trim off excess pixel_x/y by converting them into turf offset diff --git a/code/modules/projectiles/projectile/bullets/pistol.dm b/code/modules/projectiles/projectile/bullets/pistol.dm index 9c799fb9c0a5..cb852ad446db 100644 --- a/code/modules/projectiles/projectile/bullets/pistol.dm +++ b/code/modules/projectiles/projectile/bullets/pistol.dm @@ -53,26 +53,6 @@ damage = 20 fire_stacks = 3 -/obj/projectile/bullet/c10mm/reaper - name = "10mm reaper pellet" - icon_state = null - damage = 50 - armour_penetration = 40 - tracer_type = /obj/effect/projectile/tracer/sniper - impact_type = /obj/effect/projectile/impact/sniper - muzzle_type = /obj/effect/projectile/muzzle/sniper - hitscan = TRUE - impact_effect_type = null - hitscan_light_intensity = 3 - hitscan_light_range = 0.75 - hitscan_light_color_override = LIGHT_COLOR_DIM_YELLOW - muzzle_flash_intensity = 5 - muzzle_flash_range = 1 - muzzle_flash_color_override = LIGHT_COLOR_DIM_YELLOW - impact_light_intensity = 5 - impact_light_range = 1 - impact_light_color_override = LIGHT_COLOR_DIM_YELLOW - // .160 Smart /obj/projectile/bullet/c160smart diff --git a/code/modules/projectiles/projectile/bullets/smg.dm b/code/modules/projectiles/projectile/bullets/smg.dm index 38280b6f91e9..2c64bac2878c 100644 --- a/code/modules/projectiles/projectile/bullets/smg.dm +++ b/code/modules/projectiles/projectile/bullets/smg.dm @@ -20,6 +20,26 @@ damage = 15 fire_stacks = 2 +/obj/projectile/bullet/c45/reaper + name = ".45 reaper pellet" + icon_state = null + damage = 50 + armour_penetration = 40 + tracer_type = /obj/effect/projectile/tracer/sniper + impact_type = /obj/effect/projectile/impact/sniper + muzzle_type = /obj/effect/projectile/muzzle/sniper + hitscan = TRUE + impact_effect_type = null + hitscan_light_intensity = 3 + hitscan_light_range = 0.75 + hitscan_light_color_override = LIGHT_COLOR_DIM_YELLOW + muzzle_flash_intensity = 5 + muzzle_flash_range = 1 + muzzle_flash_color_override = LIGHT_COLOR_DIM_YELLOW + impact_light_intensity = 5 + impact_light_range = 1 + impact_light_color_override = LIGHT_COLOR_DIM_YELLOW + // 4.6x30mm (Autorifles) /obj/projectile/bullet/c46x30mm diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index 728d0e70e803..8d9f0989c1c6 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -726,7 +726,7 @@ var/obj/effect/decal/cleanable/food/flour/flour_decal = exposed_turf.spawn_unique_cleanable(/obj/effect/decal/cleanable/food/flour) if(flour_decal) - flour_decal.reagents.add_reagent(/datum/reagent/consumable/flour, reac_volume) + flour_decal.init_reagents(/datum/reagent/consumable/flour, reac_volume) /datum/reagent/consumable/cherryjelly name = "Cherry Jelly" diff --git a/code/modules/research/anomaly/anomaly_core.dm b/code/modules/research/anomaly/anomaly_core.dm index d9d15931c0f0..54339a7aacff 100644 --- a/code/modules/research/anomaly/anomaly_core.dm +++ b/code/modules/research/anomaly/anomaly_core.dm @@ -206,7 +206,7 @@ if(!length(possible_targets)) return var/turf/target = pick(possible_targets) - new /obj/effect/temp_visual/telegraphing/thunderbolt(target) + new /obj/effect/temp_visual/telegraphing/circle(target) addtimer(CALLBACK(src, PROC_REF(strike), target), 1 SECONDS) /obj/item/assembly/signaler/anomaly/weather/proc/strike(turf/target) diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm index b09378ea49b8..767c5bfcfb3b 100644 --- a/code/modules/research/designs/weapon_designs.dm +++ b/code/modules/research/designs/weapon_designs.dm @@ -636,7 +636,7 @@ id = "liberator_gun" build_type = AUTOLATHE materials = list(/datum/material/plastic = SHEET_MATERIAL_AMOUNT * 2, /datum/material/iron = SMALL_MATERIAL_AMOUNT * 15) - build_path = /obj/item/gun/ballistic/automatic/pistol/doorhickey + build_path = /obj/item/gun/ballistic/automatic/pistol/doohickey category = list(RND_CATEGORY_IMPORTED) /datum/design/stun_boomerang diff --git a/code/modules/spells/spell_types/right_and_wrong.dm b/code/modules/spells/spell_types/right_and_wrong.dm index 48422a26fdc9..7bf95a8d7054 100644 --- a/code/modules/spells/spell_types/right_and_wrong.dm +++ b/code/modules/spells/spell_types/right_and_wrong.dm @@ -27,7 +27,7 @@ GLOBAL_LIST_INIT(summoned_guns, list( /obj/item/gun/ballistic/automatic/pistol/clandestine/fisher, /obj/item/gun/ballistic/automatic/pistol/deagle, /obj/item/gun/ballistic/automatic/pistol/deagle/regal, - /obj/item/gun/ballistic/automatic/pistol/doorhickey, + /obj/item/gun/ballistic/automatic/pistol/doohickey, /obj/item/gun/ballistic/automatic/pistol/m1911, /obj/item/gun/ballistic/automatic/proto/unrestricted, /obj/item/gun/ballistic/automatic/smartgun, diff --git a/code/modules/unit_tests/door_access.dm b/code/modules/unit_tests/door_access.dm index 595e68297ad1..78e663bb310b 100644 --- a/code/modules/unit_tests/door_access.dm +++ b/code/modules/unit_tests/door_access.dm @@ -173,3 +173,55 @@ door.req_access = list(ACCESS_ENGINEERING, ACCESS_MAINT_TUNNELS) subject.Bump(door) TEST_ASSERT_EQUAL(door.density, TRUE, "Subject opened access-locked airlock while hands blocked!") + +/// Checks that doors with the id wire cut are inaccessible +/datum/unit_test/door_require_id_wire_cut + +/datum/unit_test/door_require_id_wire_cut/Run() + var/obj/machinery/door/airlock/instant/door = EASY_ALLOCATE() + door.interaction_flags_machine |= INTERACT_MACHINE_OFFLINE + door.req_access = list(ACCESS_ENGINEERING) + + var/mob/living/carbon/human/subject = EASY_ALLOCATE() + subject.equipOutfit(/datum/outfit/job/assistant/consistent) + var/obj/item/card/id/advanced/keycard = subject.wear_id + keycard.access = list(ACCESS_ENGINEERING, ACCESS_MAINT_TUNNELS) + + subject.Bump(door) + TEST_ASSERT_EQUAL(door.density, FALSE, "Subject failed to open access-locked airlock before id wire cut!") + door.close() + door.wires.pulse(WIRE_OPEN, subject) + TEST_ASSERT_EQUAL(door.density, TRUE, "Pulsing the door's open wire allowed the subject to open the access-locked airlock without cutting the id wire!") + door.wires.cut(WIRE_IDSCAN, subject) + subject.last_bumped = 0 + subject.Bump(door) + TEST_ASSERT_EQUAL(door.density, TRUE, "Subject opened access-locked airlock with id wire cut!") + door.wires.pulse(WIRE_OPEN, subject) + TEST_ASSERT_EQUAL(door.density, FALSE, "Subject failed to open access-locked airlock with id wire cut and open wire pulsed!") + door.close() + door.wires.cut(WIRE_IDSCAN, subject) // mend + subject.last_bumped = 0 + subject.Bump(door) + TEST_ASSERT_EQUAL(door.density, FALSE, "Subject failed to open access-locked airlock after mending id wire!") + +/// Same as above but testing throwing the item at the door +/datum/unit_test/door_require_id_wire_cut/item_only + +/datum/unit_test/door_require_id_wire_cut/item_only/Run() + var/obj/machinery/door/airlock/instant/door = EASY_ALLOCATE() + door.interaction_flags_machine |= INTERACT_MACHINE_OFFLINE + door.req_access = list(ACCESS_ENGINEERING) + + var/obj/item/card/id/advanced/keycard = EASY_ALLOCATE() + keycard.access = list(ACCESS_ENGINEERING, ACCESS_MAINT_TUNNELS) + + keycard.Bump(door) + TEST_ASSERT_EQUAL(door.density, FALSE, "Throwing an ID at an access-locked airlock failed to open it before id wire cut!") + door.close() + door.wires.cut(WIRE_IDSCAN) + keycard.Bump(door) + TEST_ASSERT_EQUAL(door.density, TRUE, "Throwing an ID at an access-locked airlock succeeded in opening it after id wire cut!") + door.close() + door.wires.cut(WIRE_IDSCAN) // mend + keycard.Bump(door) + TEST_ASSERT_EQUAL(door.density, FALSE, "Throwing an ID at an access-locked airlock failed to open it after mending the id wire!") diff --git a/html/changelogs/AutoChangeLog-pr-96171.yml b/html/changelogs/AutoChangeLog-pr-96171.yml deleted file mode 100644 index 82c646052f3a..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96171.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "lelandkemble" -delete-after: True -changes: - - balance: "Bitrunners can't back out of domains via the ladder while bitrunning glitches remain in them" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-96289.yml b/html/changelogs/AutoChangeLog-pr-96289.yml deleted file mode 100644 index e7cea8825af1..000000000000 --- a/html/changelogs/AutoChangeLog-pr-96289.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Wisemonster" -delete-after: True -changes: - - bugfix: "Fixed Nebula's boulder refinery being constantly spaced." \ No newline at end of file diff --git a/html/changelogs/archive/2026-06.yml b/html/changelogs/archive/2026-06.yml index 3aedd611db81..9f55dcb45881 100644 --- a/html/changelogs/archive/2026-06.yml +++ b/html/changelogs/archive/2026-06.yml @@ -14,3 +14,38 @@ lelandkemble: - balance: The RNG instant kill via organ removal of the mech drill has been removed, and replaced with a very high wounding chance +2026-06-02: + Iajret: + - bugfix: Mending touch works once again + Melbert: + - bugfix: Cutting an airlock's ID wire prevents ID usage again (instead of changing + the door to all access) + - map: Roundstart nuclear operatives now spawn in the nuke base elevator, rather + than in the base itself. This change was done so we could reduce the lag from + loading the nuke base during round startup. + SmArtKar: + - bugfix: Fixed spilled flour not containing any flour + - balance: You can make toolboxes out of meat and paper again + - balance: Lavaland tendrils have been reworked into a full fledged miniboss instead + of a simple mob spawner. + - balance: Lavaland mobs minimum spawn separation distance has been lowered from + 12 to 7 + TheRyeGuyWhoWillNowDie: + - bugfix: jaws of recovery are blacklisted from crafting the jaws of recovery + Wisemonster: + - bugfix: Fixed Nebula's boulder refinery being constantly spaced. + Y0SH1M4S73R: + - bugfix: Non-square paint canvases and Nanopaint projects correctly save to icons. + - bugfix: You can no longer enter empty filenames when saving Nanopaint files. + - spellcheck: User-provided modular computer filenames are now checked against the + pda IC filter. + lelandkemble: + - balance: Bitrunners can't back out of domains via the ladder while bitrunning + glitches remain in them + necromanceranne: + - spellcheck: Adds some additional deep lore to some of the pistols in the game. + FInd your favourite and see if I cared to add one. + - spellcheck: Rewrote the lore for contractor batons and stun batons. + - code_imp: Repathed Liberators. This has no bearing on their function. + - code_imp: Rechambers Regal Condors in .45 and 10mm Reaper rounds to .45 Reaper + rounds. This has no impact on their function. diff --git a/icons/mob/simple/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi index 3f5eb9cbd3b5..f338fab310ae 100644 Binary files a/icons/mob/simple/lavaland/lavaland_monsters.dmi and b/icons/mob/simple/lavaland/lavaland_monsters.dmi differ diff --git a/icons/mob/simple/lavaland/tendril.dmi b/icons/mob/simple/lavaland/tendril.dmi new file mode 100644 index 000000000000..0bbd0ccbe218 Binary files /dev/null and b/icons/mob/simple/lavaland/tendril.dmi differ diff --git a/icons/mob/telegraphing/telegraph.dmi b/icons/mob/telegraphing/telegraph.dmi index 6afddd04b62f..1483234c763b 100644 Binary files a/icons/mob/telegraphing/telegraph.dmi and b/icons/mob/telegraphing/telegraph.dmi differ diff --git a/icons/obj/weapons/guns/ammo.dmi b/icons/obj/weapons/guns/ammo.dmi index 168b55dfcb6b..06eb704f52dc 100644 Binary files a/icons/obj/weapons/guns/ammo.dmi and b/icons/obj/weapons/guns/ammo.dmi differ diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi index 24aa777660d5..19bdf00bac92 100644 Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 2acd31a9e325..d9914a272a00 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3089,7 +3089,6 @@ #include "code\game\objects\structures\icemoon\cave_entrance.dm" #include "code\game\objects\structures\lavaland\geyser.dm" #include "code\game\objects\structures\lavaland\gulag_vent.dm" -#include "code\game\objects\structures\lavaland\necropolis_tendril.dm" #include "code\game\objects\structures\lavaland\ore_vent.dm" #include "code\game\objects\structures\plaques\_plaques.dm" #include "code\game\objects\structures\plaques\static_plaques.dm" @@ -5411,6 +5410,9 @@ #include "code\modules\mob\living\basic\lavaland\raptor\raptor_egg.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_food_trough.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_inheritance.dm" +#include "code\modules\mob\living\basic\lavaland\tendril\tendril.dm" +#include "code\modules\mob\living\basic\lavaland\tendril\tendril_actions.dm" +#include "code\modules\mob\living\basic\lavaland\tendril\tendril_ai.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_ai.dm" #include "code\modules\mob\living\basic\lavaland\watcher\watcher_gaze.dm" diff --git a/tools/UpdatePaths/Scripts/96056_pistols_and_stuff.txt b/tools/UpdatePaths/Scripts/96056_pistols_and_stuff.txt new file mode 100644 index 000000000000..8e3f4b313f01 --- /dev/null +++ b/tools/UpdatePaths/Scripts/96056_pistols_and_stuff.txt @@ -0,0 +1,4 @@ +/obj/item/gun/ballistic/automatic/pistol/doorhickey : /obj/item/gun/ballistic/automatic/pistol/doohickey{@OLD} +/obj/item/ammo_box/magazine/r10mm : /obj/item/ammo_box/magazine/r45{@OLD} +/obj/item/ammo_casing/c10mm/reaper : /obj/item/ammo_casing/c45/reaper{@OLD} +/obj/projectile/bullet/c10mm/reaper : /obj/projectile/bullet/c45/reaper{@OLD} diff --git a/tools/UpdatePaths/Scripts/96186_tendrils.txt b/tools/UpdatePaths/Scripts/96186_tendrils.txt new file mode 100644 index 000000000000..eb2f45499c42 --- /dev/null +++ b/tools/UpdatePaths/Scripts/96186_tendrils.txt @@ -0,0 +1 @@ +/obj/structure/spawner/lavaland : /mob/living/basic/mining/tendril