diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 6e323ac3f46..c59f49b38d9 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -29,6 +29,7 @@ #define JANI_HUD "24" // Sign overlay over cleanable decals #define PRESSURE_HUD "25" // Pressure coloring for tiles #define MALF_AI_HUD "26" // Malf status blips for borgs +#define ANOMALOUS_HUD "27" // Seeing anomalous particulate //by default everything in the hud_list of an atom is an image //a value in hud_list with one of these will change that behavior @@ -46,22 +47,23 @@ #define DATA_HUD_JANITOR 8 #define DATA_HUD_PRESSURE 9 #define DATA_HUD_MALF_AI 10 +#define DATA_HUD_ANOMALOUS 11 //antag HUD defines -#define ANTAG_HUD_CULT 11 -#define ANTAG_HUD_REV 12 -#define ANTAG_HUD_OPS 13 -#define ANTAG_HUD_WIZ 14 -#define ANTAG_HUD_SHADOW 15 -#define ANTAG_HUD_TRAITOR 16 -#define ANTAG_HUD_NINJA 17 -#define ANTAG_HUD_CHANGELING 18 -#define ANTAG_HUD_VAMPIRE 19 -#define ANTAG_HUD_ABDUCTOR 20 -#define DATA_HUD_ABDUCTOR 21 -#define ANTAG_HUD_EVENTMISC 22 -#define ANTAG_HUD_BLOB 23 -#define ANTAG_HUD_ZOMBIE 24 -#define ANTAG_HUD_MIND_FLAYER 25 +#define ANTAG_HUD_CULT 12 +#define ANTAG_HUD_REV 13 +#define ANTAG_HUD_OPS 14 +#define ANTAG_HUD_WIZ 15 +#define ANTAG_HUD_SHADOW 16 +#define ANTAG_HUD_TRAITOR 17 +#define ANTAG_HUD_NINJA 18 +#define ANTAG_HUD_CHANGELING 19 +#define ANTAG_HUD_VAMPIRE 20 +#define ANTAG_HUD_ABDUCTOR 21 +#define DATA_HUD_ABDUCTOR 22 +#define ANTAG_HUD_EVENTMISC 23 +#define ANTAG_HUD_BLOB 24 +#define ANTAG_HUD_ZOMBIE 25 +#define ANTAG_HUD_MIND_FLAYER 26 // Notification action types #define NOTIFY_JUMP "jump" diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index a5c17a62b74..eae255e37ef 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -118,6 +118,8 @@ #define STATUS_EFFECT_BLUESPACESLOWDOWN /datum/status_effect/bluespace_slowdown //Halfs victims next move modifier +#define STATUS_EFFECT_BLUESPACESLOWDOWN_LONG /datum/status_effect/bluespace_slowdown/long + #define STATUS_EFFECT_SHADOW_BOXING /datum/status_effect/shadow_boxing #define STATUS_EFFECT_CLINGTENTACLE /datum/status_effect/cling_tentacle //Imobilises target for 3 seconds diff --git a/code/_globalvars/game_modes.dm b/code/_globalvars/game_modes.dm index 664ae5577d2..7981c71a1bd 100644 --- a/code/_globalvars/game_modes.dm +++ b/code/_globalvars/game_modes.dm @@ -5,3 +5,6 @@ GLOBAL_VAR_INIT(secret_force_mode, "secret") // if this is anything but "secret" GLOBAL_DATUM(start_state, /datum/station_state) // Used in round-end report. Dont ask why it inits as null GLOBAL_VAR(custom_event_msg) + +/// We want anomalous_particulate_tracker to exist only once and be accessible from anywhere. +GLOBAL_DATUM_INIT(anomaly_smash_track, /datum/anomalous_particulate_tracker, new) diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index 1a7713b8bcd..ed5195f77c8 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -14,6 +14,8 @@ SUBSYSTEM_DEF(mapping) var/list/ghostteleportlocs ///List of areas that exist on the station this shift var/list/existing_station_areas + /// Types of areas that exist on the station this shift + var/list/existing_station_areas_types ///What do we have as the lavaland theme today? var/datum/lavaland_theme/lavaland_theme ///What primary cave theme we have picked for cave generation today. @@ -155,6 +157,7 @@ SUBSYSTEM_DEF(mapping) // Now we make a list of areas that exist on the station. Good for if you don't want to select areas that exist for one station but not others. Directly references existing_station_areas = list() + existing_station_areas_types = list() for(var/area/AR as anything in all_areas) var/list/pickable_turfs = list() for(var/turf/turfs in AR) @@ -163,6 +166,7 @@ SUBSYSTEM_DEF(mapping) var/turf/picked = safepick(pickable_turfs) if(picked && is_station_level(picked.z)) existing_station_areas += AR + existing_station_areas_types += AR.type CHECK_TICK // World name diff --git a/code/datums/atom_hud.dm b/code/datums/atom_hud.dm index 86c386bfe4f..7830f3f2ae5 100644 --- a/code/datums/atom_hud.dm +++ b/code/datums/atom_hud.dm @@ -13,6 +13,7 @@ GLOBAL_LIST_INIT(huds, list( DATA_HUD_JANITOR = new/datum/atom_hud/data/janitor(), DATA_HUD_PRESSURE = new/datum/atom_hud/data/pressure(), DATA_HUD_MALF_AI = new/datum/atom_hud/data/human/malf_ai(), + DATA_HUD_ANOMALOUS = new/datum/atom_hud/data/anomalous(), ANTAG_HUD_CULT = new/datum/atom_hud/antag(), ANTAG_HUD_REV = new/datum/atom_hud/antag(), ANTAG_HUD_OPS = new/datum/atom_hud/antag(), @@ -34,6 +35,8 @@ GLOBAL_LIST_INIT(huds, list( var/list/atom/hudatoms = list() //list of all atoms which display this hud var/list/mob/hudusers = list() //list with all mobs who can see the hud var/list/hud_icons = list() //these will be the indexes for the atom's hud_list + /// Do we ignore the invisibility check? Used by anom huds so we can see our stuff. + var/ignore_invisibility_check = FALSE /datum/atom_hud/New() @@ -86,7 +89,7 @@ GLOBAL_LIST_INIT(huds, list( /datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client if(!M || !M.client || !A) return - if(A.invisibility > M.see_invisible) // yee yee ass snowflake check for our yee yee ass snowflake huds + if((A.invisibility > M.see_invisible) && !ignore_invisibility_check) // yee yee ass snowflake check for our yee yee ass snowflake huds return for(var/i in hud_icons) if(A.hud_list[i]) diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index a14b5722e23..b660b64f842 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -238,3 +238,51 @@ if(random_location.is_safe()) return random_location + +/// Returns a random department of areas to pass into get_safe_random_station_turf() for more equal spawning. +/proc/get_safe_random_station_turf_equal_weight() + // Big list of departments, each with lists of each area subtype. + var/static/list/department_areas + if(isnull(department_areas)) + department_areas = list( + subtypesof(/area/station/engineering), \ + subtypesof(/area/station/medical), \ + subtypesof(/area/station/science), \ + subtypesof(/area/station/security), \ + subtypesof(/area/station/service), \ + subtypesof(/area/station/command), \ + subtypesof(/area/station/hallway), \ + subtypesof(/area/station/supply) + ) + + var/list/area/final_department = pick(department_areas) // Pick a department + var/list/area/final_area_list = list() + + for(var/checked_area in final_department) // Check each area to make sure it exists on the station + if(checked_area in SSmapping.existing_station_areas_types) + final_area_list += checked_area + + if(!length(final_area_list)) // Failsafe + return get_safe_random_station_turf() + + return get_safe_random_station_turf(final_area_list) + +/proc/get_safe_random_station_turf(list/areas_to_pick_from = SSmapping.existing_station_areas_types) + for(var/i in 1 to 5) + var/list/turf_list = get_area_turfs(pick(areas_to_pick_from)) + var/turf/target + while(length(turf_list) && !target) + var/I = rand(1, length(turf_list)) + var/turf/checked_turf = turf_list[I] + if(!checked_turf.density) + var/clear = TRUE + for(var/obj/checked_object in checked_turf) + if(checked_object.density) + clear = FALSE + break + if(clear) + target = checked_turf + if(!target) + turf_list.Cut(I, I + 1) + if(target) + return target diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index 89857a5e1ad..498e9c70a2d 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -261,6 +261,9 @@ /datum/status_effect/bluespace_slowdown/on_remove() owner.next_move_modifier /= 2 +/datum/status_effect/bluespace_slowdown/long + duration = 1 MINUTES + /datum/status_effect/shadow_boxing id = "shadow barrage" alert_type = null diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 24daf1ff17a..e5d88f58d0c 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -71,6 +71,11 @@ ..() SSair.add_pressure_hud(user) + +/datum/atom_hud/data/anomalous + hud_icons = list(ANOMALOUS_HUD) + ignore_invisibility_check = TRUE + /* MED/SEC/DIAG HUD HOOKS */ /* @@ -508,6 +513,16 @@ holder.plane = ABOVE_LIGHTING_PLANE holder.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM +/*~~~~~~~~~~~~~~ + ANOMALOUS HUD +~~~~~~~~~~~~~~~*/ +/obj/effect/anomalous_particulate/proc/do_hud_stuff() + var/image/holder = hud_list[ANOMALOUS_HUD] + holder.icon_state = "hud_anom" + holder.alpha = 130 + holder.plane = ABOVE_LIGHTING_PLANE + holder.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM + /*~~~~~~~~~~~~~~ Malf AI HUD ~~~~~~~~~~~~~~~*/ diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index b095d6475f9..503df087e69 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -632,7 +632,7 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) steal_target = O update_explanation_text() if(steal_target.special_equipment) - give_kit(steal_target.special_equipment) + hand_out_equipment() return explanation_text = "Free Objective." @@ -667,9 +667,15 @@ GLOBAL_LIST_INIT(potential_theft_objectives, (subtypesof(/datum/theft_objective) return steal_target /datum/objective/steal/proc/hand_out_equipment() + steal_target?.on_hand_out_equipment(src) give_kit(steal_target?.special_equipment) /datum/objective/steal/update_explanation_text() + if(steal_target.objective_name_overide) + explanation_text = steal_target.objective_name_overide + explanation_text += steal_target.extra_information + return + explanation_text = "Steal [steal_target.name]. One was last seen in [get_location()]. " if(length(steal_target.protected_jobs) && steal_target.job_possession) explanation_text += "It may also be in the possession of the [english_list(steal_target.protected_jobs, and_text = " or ")]. " diff --git a/code/game/gamemodes/steal_items.dm b/code/game/gamemodes/steal_items.dm index 5da86e51a26..27bd83ab91d 100644 --- a/code/game/gamemodes/steal_items.dm +++ b/code/game/gamemodes/steal_items.dm @@ -15,6 +15,8 @@ var/job_possession = TRUE /// Any extra information about the objective var/extra_information = "" + /// Do we overide naming to not say steal at the front? + var/objective_name_overide = null /datum/theft_objective/proc/check_completion(datum/mind/owner) if(!owner.current) @@ -35,6 +37,9 @@ /datum/proc/check_special_completion() //for objectives with special checks (is that slime extract unused? does that intellicard have an ai in it? etcetc) return 1 +/datum/theft_objective/proc/on_hand_out_equipment(datum/objective/steal/our_objective) + return + /datum/theft_objective/antique_laser_gun name = "the captain's antique laser gun" typepath = /obj/item/gun/energy/laser/captain @@ -155,6 +160,24 @@ protected_jobs = list("Quartermaster") job_possession = FALSE +/datum/theft_objective/anomalous_particulate + name = "Anomalous particulate." + objective_name_overide = "Collect 3 clouds of anomalous particulate, and return with the PPPProcessor. Check your bag for further instructions" + special_equipment = /obj/item/storage/box/syndie_kit/anomalous_particulate + job_possession = FALSE + +/datum/theft_objective/anomalous_particulate/on_hand_out_equipment(datum/objective/steal/our_objective) + . = ..() + var/our_chosen_owner = our_objective.get_owners() + GLOB.anomaly_smash_track.add_tracked_mind(our_chosen_owner[1]) + +/datum/theft_objective/anomalous_particulate/check_special_completion(obj/item/I) + if(!istype(I, /obj/item/ppp_processor)) + return FALSE + var/obj/item/ppp_processor/did_we_process = I + if(did_we_process.fully_processed_particulate) + return TRUE + /datum/theft_objective/engraved_dusters name = "the quartermaster's engraved knuckledusters" typepath = /obj/item/melee/knuckleduster/nanotrasen diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index bb56a6e74a0..7490f7ca50e 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -19,6 +19,9 @@ var/countdown_colour var/obj/effect/countdown/anomaly/countdown + /// Used by the canister grenades to modify functions. + var/canister_spawned = FALSE + /// Do we drop a core when we're neutralized? var/drops_core = TRUE @@ -93,6 +96,11 @@ // Else, anomaly core gets deleted by qdel(src). qdel(src) +// Used by the anomalous canister grenades to make them balanced for grenade use. +/obj/effect/anomaly/proc/anomalous_canister_setup() + canister_spawned = TRUE + return + /obj/effect/anomaly/item_interaction(mob/living/user, obj/item/used, list/modifiers) if(istype(used, /obj/item/analyzer)) to_chat(user, "Analyzing... [src]'s unstable field is fluctuating along frequency [format_frequency(aSignal.frequency)], code [aSignal.code].") @@ -107,11 +115,16 @@ aSignal = /obj/item/assembly/signaler/anomaly/grav var/obj/effect/warp_effect/supermatter/warp -/obj/effect/anomaly/grav/Initialize(mapload, new_lifespan, _drops_core = TRUE, event_spawned = TRUE) +/obj/effect/anomaly/grav/Initialize(mapload, new_lifespan, _drops_core = TRUE) . = ..() warp = new(src) vis_contents += warp - if(!event_spawned) //So an anomaly in the hallway is assured to have some risk to it, but not make sm / vetus too much pain + + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + if(!_drops_core) // So an anomaly in the hallway is assured to have some risk to it, but not make sm / vetus too much pain return for(var/I in 1 to 3) if(prob(75)) @@ -119,11 +132,6 @@ if(prob(75)) new /obj/item/shard(loc) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - /obj/effect/anomaly/grav/Destroy() vis_contents -= warp QDEL_NULL(warp) // don't want to leave it hanging @@ -207,7 +215,7 @@ canshock = TRUE for(var/mob/living/M in get_turf(src)) mobShock(M) - if(explosive) //Let us not fuck up the sm that much + if(explosive || canister_spawned) // Let us not fuck up the sm that much tesla_zap(src, zap_range, power, zap_flags) /obj/effect/anomaly/flux/proc/on_atom_entered(datum/source, atom/movable/entered) @@ -224,18 +232,22 @@ canshock = FALSE //Just so you don't instakill yourself if you slam into the anomaly five times in a second. M.electrocute_act(shockdamage, name, flags = SHOCK_NOGLOVES) if(!knockdown) - M.Weaken(explosive ? 6 SECONDS : 3 SECONDS) //Back to being deadly if you touch it, rather than just being able to crawl out of it. Non explosive ones less deadly, since you can't loot them + M.Weaken((explosive || canister_spawned) ? 6 SECONDS : 3 SECONDS) // Back to being deadly if you touch it, rather than just being able to crawl out of it. Non explosive ones less deadly, since you can't loot them else M.KnockDown(3 SECONDS) /obj/effect/anomaly/flux/detonate() - if(explosive) + if(explosive && !canister_spawned) explosion(src, 1, 4, 16, 18, FALSE) // Set adminlog to FALSE for custom logging message_admins("\A [name] has detonated at [impact_area][ADMIN_COORDJMP(impact_area)]") log_admin("\A [name] has detonated at [impact_area]") else new /obj/effect/particle_effect/sparks(loc) +/obj/effect/anomaly/flux/anomalous_canister_setup() + power = 3000 + return ..() + /obj/effect/anomaly/bluespace name = "bluespace anomaly" icon = 'icons/obj/projectiles.dmi' @@ -336,6 +348,10 @@ M.client.screen -= blueeffect qdel(blueeffect) +/obj/effect/anomaly/bluespace/anomalous_canister_setup() + mass_teleporting = FALSE + return ..() + /obj/effect/anomaly/pyro name = "pyroclastic anomaly" icon_state = "mustard" @@ -362,7 +378,7 @@ T.blind_release_air(air) /obj/effect/anomaly/pyro/detonate() - if(produces_slime) + if(produces_slime && !canister_spawned) INVOKE_ASYNC(src, PROC_REF(makepyroslime)) if(drops_core) message_admins("\A [name] has detonated at [impact_area][ADMIN_COORDJMP(impact_area)]") @@ -524,12 +540,14 @@ step_towards(O, src) for(var/mob/living/M in T.contents) step_towards(M, src) - if(drops_core) + if(drops_core || canister_spawned) M.Weaken(3.5 SECONDS) //You ran into a black hole, you ride the pain train. M.KnockDown(7 SECONDS) //Damaging the turf if(T && prob(turf_removal_chance)) + if(isfloorturf(T) && canister_spawned) + return T.ex_act(ex_act_force) #undef ANOMALY_MOVECHANCE diff --git a/code/game/objects/effects/anomalous_particulate.dm b/code/game/objects/effects/anomalous_particulate.dm new file mode 100644 index 00000000000..7af9976d6fa --- /dev/null +++ b/code/game/objects/effects/anomalous_particulate.dm @@ -0,0 +1,485 @@ + +/// The number of anomalous particulate gatherings per objective +#define NUM_ANOM_PER_OBJ 5 + +/datum/anomalous_particulate_tracker + /// The total number of gatherings that have been drained, for tracking. + var/num_drained = 0 + /// List of tracked influences (reality smashes) + var/list/obj/effect/anomalous_particulate/smashes = list() + /// List of minds with the objective to get particulate + var/list/datum/mind/tracked_objectiveholders = list() + +/datum/anomalous_particulate_tracker/Destroy(force) + if(GLOB.anomaly_smash_track == src) + stack_trace("[type] was deleted. Antagonists may no longer access any particulate. Fix it, or call coder support.") + message_admins("The [type] was deleted. Antagonists may no longer access any particulate. Fix it, or call coder support.") + QDEL_LIST_CONTENTS(smashes) + tracked_objectiveholders.Cut() + return ..() + +/** + * Generates a set amount of reality smashes + * based on the number of already existing smashes + * and the number of minds we're tracking. + */ +/datum/anomalous_particulate_tracker/proc/generate_new_influences() + var/how_many_can_we_make = 0 + for(var/antag_number in 1 to length(tracked_objectiveholders)) + how_many_can_we_make += max(NUM_ANOM_PER_OBJ - antag_number + 1, 1) + + var/location_sanity = 0 + while((length(smashes) + num_drained) < how_many_can_we_make && location_sanity < 100) + var/turf/chosen_location = get_safe_random_station_turf_equal_weight() + + // We don't want them close to each other - at least 1 tile of separation + var/list/nearby_things = range(1, chosen_location) + var/obj/effect/anomalous_particulate/what_if_i_have_one = locate() in nearby_things + var/obj/effect/visible_anomalous_particulate/what_if_i_had_one_but_its_used = locate() in nearby_things + if(what_if_i_have_one || what_if_i_had_one_but_its_used) + location_sanity++ + continue + + new /obj/effect/anomalous_particulate(chosen_location) + +/** + * Adds a mind to the list of people that need things spawned + * + * Use this whenever you want to add someone to the list + */ +/datum/anomalous_particulate_tracker/proc/add_tracked_mind(datum/mind/obj_mind) + tracked_objectiveholders |= obj_mind + + // If our holder is on station, generate some new influences + if(ishuman(obj_mind.current) && is_teleport_allowed(obj_mind.current.z)) + generate_new_influences() + +/** + * Removes a mind from the list of people that need things spawned + * + * Use this whenever you want to remove someone from the list + */ +/datum/anomalous_particulate_tracker/proc/remove_tracked_mind(datum/mind/obj_mind) + tracked_objectiveholders -= obj_mind + +#define BLUESPACE 1 +#define GRAV 2 +#define PYRO 3 +#define FLUX 4 +#define VORTEX 5 +#define CRYO 6 + +/obj/effect/visible_anomalous_particulate + name = "cloud of anomalous particulate" + icon_state = "particulate_cloud" + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + alpha = 0 + invisibility = INVISIBILITY_LEVEL_TWO + /// The effect on examine + var/examine_effect + /// The effect on touch / bump + var/bump_effect + /// The effect passively + var/process_effect + +/* + * What does this do? + * First, we choose a sprite for the texture, and rotate it. + * Next, a shape. Also potentially rotated. + * Finally, a transparent overlay to apply over the first texture. + * After that we queue up the fade in and effects + */ +/obj/effect/visible_anomalous_particulate/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(show_presence)), 15 SECONDS) + var/icon/our_icon + switch(rand(1, 10)) // texture + if(1 to 3) + our_icon = icon('icons/effects/effects.dmi', "particulate_cloud") + if(4 to 6) + our_icon = icon('icons/effects/effects.dmi', "cryoanomaly") + if(7 to 8) + our_icon = icon('icons/effects/effects.dmi', "anom") + if(9) + our_icon = icon('icons/effects/effects.dmi', "void_conduit") + if(10) + our_icon = icon('icons/effects/effects.dmi', "static_base") + + var/icon/alpha_mask + switch(rand(1, 8)) // shape + if(1 to 6) + alpha_mask = new('icons/effects/effects.dmi', "void_conduit") + if(7) + alpha_mask = new('icons/effects/effects.dmi', "cryoanomaly") + if(8) + alpha_mask = new('icons/effects/effects.dmi', "anom") + switch(rand(1,8)) + if(1) + alpha_mask.Turn(0) + if(2) + alpha_mask.Turn(90) + if(3) + alpha_mask.Turn(180) + if(4) + alpha_mask.Turn(270) + if(5 to 8) + alpha_mask.Turn(rand(1, 359)) + + switch(rand(1,8)) + if(1) + our_icon.Turn(0) + if(2) + our_icon.Turn(90) + if(3) + our_icon.Turn(180) + if(4) + our_icon.Turn(270) + if(5 to 8) + our_icon.Turn(rand(1, 359)) + + our_icon.AddAlphaMask(alpha_mask) // Finally, let's mix in a distortion effect. + icon = our_icon + switch(rand(1,9)) + if(1) + color = list(0.2,0.45,0,0, 0,1,0,0, 0,0,0.2,0, 0,0,0,1, 0,0,0,0) // green + if(2) + color = list(1,0,0,0, 0,0.4,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) // purple per video 1 + if(3) + color = list(0.4,0,0,0, 0,1.1,0,0, 0,0,1.65,0, -0.3,0.15,0,1, 0,0,0,0) // light blue + if(4) + color = list(0.5,1,0.9,0, 0,1,0,0, 0,0,0.5,0, 0,0,0,1, 0,0,0,0) // green blue + if(5) + color = list(0.8,0,0,0.35,0.2,0, 0.5,0,0.65,0.1,0,0) // red + if(6) + color = list(1.3,0,0,0, 0,1.65,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0) // green blue + if(7) + color = list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, (rand(1,100) / 100),(rand(1,100) / 100),(rand(1,100) / 100),1, 0,0,0,0) // random inverted + if(8) + color = list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0.8,0.85,0.95,1, 0,0,0,0) // cool inverted + if(9) + color = list((rand(1,100) / 100),0,0,0, 0,(rand(1,100) / 100),0,0, 0,0,(rand(1,100) / 100),0, 0,0,0,1, 0,0,0,0) // random normal + + var/mutable_appearance/theme_icon + var/MA_alpha = pick(30, 40, 20, 25, 35, 10, 60) + switch(rand(1, 5)) // final overlay effect + if(1 to 3) + theme_icon = mutable_appearance('icons/effects/effects.dmi', "static_base", FLOAT_LAYER - 1, alpha = MA_alpha, appearance_flags = appearance_flags | RESET_TRANSFORM) + if(4) + theme_icon = mutable_appearance('icons/effects/effects.dmi', "emp_disable", FLOAT_LAYER - 1, alpha = MA_alpha, appearance_flags = appearance_flags | RESET_TRANSFORM) + if(5) + theme_icon = mutable_appearance('icons/effects/effects.dmi', "anom", FLOAT_LAYER - 1, alpha = MA_alpha, appearance_flags = appearance_flags | RESET_TRANSFORM) + theme_icon.blend_mode = BLEND_INSET_OVERLAY + var/matrix/M_three = matrix() + switch(rand(1,8)) + if(1) + M_three.Turn(0) + if(2) + M_three.Turn(90) + if(3) + M_three.Turn(180) + if(4) + M_three.Turn(270) + if(5 to 8) + M_three.Turn(rand(1, 359)) + + theme_icon.transform = M_three + overlays += theme_icon + // Applies some random effects to the final image + switch(rand(1,5)) + if(1) + add_filter("distort_1", 3, motion_blur_filter(1, 1)) + if(2) + add_filter("distort_2", 1, gauss_blur_filter(size = 0.5)) + if(3) + add_filter("distort_3", 2, angular_blur_filter(size = 5)) + if(4) + apply_wibbly_filters(src) + if(5) // No visual effect + alpha = 1 + alpha = 1 // Resets the alpha for the show_presence fade in + alpha = 0 + + +/obj/effect/visible_anomalous_particulate/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + + +/* + * Makes the influence fade in after 15 seconds. + */ +/obj/effect/visible_anomalous_particulate/proc/show_presence() + invisibility = 0 + animate(src, alpha = 180, time = 15 SECONDS) + addtimer(CALLBACK(src, PROC_REF(fade_presence)), 15 SECONDS) + configure_effects() + +/* + * Makes the influence fade out over 20 minutes. + */ +/obj/effect/visible_anomalous_particulate/proc/fade_presence() + animate(src, alpha = 0, time = 20 MINUTES) + QDEL_IN(src, 20 MINUTES) + +/* + * Sets up the effects for looking at it / bumping into it / aoe. + */ +/obj/effect/visible_anomalous_particulate/proc/configure_effects() + examine_effect = pick(BLUESPACE, GRAV, PYRO, FLUX, VORTEX, CRYO) + bump_effect = pick(BLUESPACE, GRAV, PYRO, FLUX, VORTEX, CRYO) + process_effect = pick(BLUESPACE, GRAV, PYRO, FLUX, VORTEX, CRYO) + if(process_effect == VORTEX) + set_light( + l_range = 7, + l_power = -3, + l_color = "#ddd6cf") + START_PROCESSING(SSobj, src) + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_atom_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/obj/effect/visible_anomalous_particulate/proc/on_atom_entered(datum/source, atom/movable/entered) + trigger_bump_effect(entered) + +/obj/effect/visible_anomalous_particulate/Bump(atom/A) + trigger_bump_effect(A) + +/obj/effect/visible_anomalous_particulate/Bumped(atom/movable/AM) + trigger_bump_effect(AM) + +/obj/effect/visible_anomalous_particulate/attack_hand(mob/living/user, list/modifiers) + . = ..() + if(.) + return + trigger_bump_effect(user) + return TRUE + + +/obj/effect/visible_anomalous_particulate/proc/trigger_bump_effect(atom/A) + if(!isliving(A)) + return + var/mob/living/victim = A + switch(bump_effect) + if(BLUESPACE) // Easy out of a department... but you'll be slow and unable to fight well for a minute. + to_chat(victim, "You feel like everything around you is moving twice as fast!") + victim.apply_status_effect(STATUS_EFFECT_BLUESPACESLOWDOWN_LONG) + victim.Slowed(1 MINUTES, 1) + do_teleport(victim, get_turf(victim), 21, sound_in = 'sound/effects/phasein.ogg', safe_turf_pick = TRUE) + if(GRAV) // Yeet + to_chat(victim, "You bounce right off [src]!") + var/atom/target = get_edge_target_turf(victim, get_dir(src, get_step_away(victim, src))) + victim.throw_at(target, 20, 3) + if(PYRO) // I mean yeah, hot + to_chat(victim, "[src] is perhaps a bit hot to the touch.") + victim.adjust_fire_stacks(12) + victim.IgniteMob() + if(FLUX) // Shock them... or charge the cell in the item they are holding! + if(!ishuman(victim)) + victim.electrocute_act(20, src, flags = SHOCK_NOGLOVES) + return + var/list/hand_items = list(victim.get_active_hand(), victim.get_inactive_hand()) + var/charged_item = null + for(var/obj/item in hand_items) + if(istype(item, /obj/item/stock_parts/cell/)) + var/obj/item/stock_parts/cell/C = item + C.charge = C.maxcharge + charged_item = C + break + else if(item.contents) + var/obj/I = null + for(I in item.contents) + if(istype(I, /obj/item/stock_parts/cell/)) + var/obj/item/stock_parts/cell/C = I + C.charge = C.maxcharge + item.update_icon() + charged_item = item + break + if(!charged_item) + victim.electrocute_act(20, src, flags = SHOCK_NOGLOVES) + return + to_chat(victim, "[charged_item] suddenly feels very warm!") + if(VORTEX) // sharp particulate + to_chat(victim, "As you pass through [src], sharp particulate cuts into you!") + victim.adjustBruteLoss(30) + if(CRYO) // slowdown via cold + to_chat(victim, "As you pass through [src], you feel quite cold!") + victim.adjust_bodytemperature(-230) + + +/obj/effect/visible_anomalous_particulate/examine(mob/user) + . = ..() + if(!ishuman(user)) + return + if(hasHUD(user, ANOMALOUS_HUD)) + to_chat(user, "Your protective gear shields you from the anomalous effects. Best not to look at it without them.") + return + + var/mob/living/carbon/human/victim = user + switch(examine_effect) + if(BLUESPACE) // Just a slowdown in spacetime + to_chat(victim, "[src] really begins to draw in your focus, time slips on by around you...") + victim.apply_status_effect(STATUS_EFFECT_BLUESPACESLOWDOWN) + victim.Slowed(15 SECONDS, 1) + if(GRAV) // bonk to the head + to_chat(victim, "Your head hurts just looking at [src]!") + victim.adjustBrainLoss(10) + if(PYRO) // Just a nice warm glow + to_chat(victim, "[src] gives off a nice warm glow.") + if(FLUX) // Emp yourself. This can be considered a benefit. + to_chat(victim, "As you focus on [src], a shock travels through you and your electronics!") + victim.emp_act(EMP_LIGHT) + if(VORTEX) // Darkness consumes you. Or your vision, for a moment + to_chat(victim, "[src] is so dark that... everything else kinda is.") + victim.AdjustEyeBlind(5 SECONDS) + if(CRYO) // A cool refresher. Though a bit toxic. + to_chat(victim, "A cool feeling passes through you, closing your wounds. Feels... a bit off though...") + victim.adjustBruteLoss(-10) + victim.adjustFireLoss(-20) + victim.adjustToxLoss(15) + victim.adjust_bodytemperature(-75) + +/obj/effect/visible_anomalous_particulate/process() + var/turf/our_turf = get_turf(src) + var/area/to_be_modified = get_area(our_turf) + switch(process_effect) + if(BLUESPACE) // Condense a crystal now and then + if(prob(2)) + visible_message( "A drop of blue particulate from [src] condenses into a shining crystal.") + new /obj/item/stack/ore/bluespace_crystal/refined(get_turf(src)) + if(GRAV) // Nice gravity. Invert it. + if(prob(1)) + if(has_gravity(src, our_turf)) + to_be_modified.gravitychange(FALSE, to_be_modified) + else + to_be_modified.gravitychange(TRUE, to_be_modified) + if(PYRO) // Keeps the room warm + var/datum/milla_safe/visible_anomalous_particulate/milla = new() + milla.invoke_async(src) + if(FLUX) // Science we need like 10 power cells please + if(to_be_modified.apc) + var/obj/machinery/power/apc/A = to_be_modified.apc[1] + if(A.operating && A.cell) + A.cell.charge = max(0, A.cell.charge - 25) + if(A.charging == APC_FULLY_CHARGED) // If the cell was full + A.charging = APC_IS_CHARGING // It's no longer full + // Vortex just has darkness, no process needed + if(CRYO) // A cool blue glow. AKA a radioactive one. + if(prob(33)) + radiation_pulse(src, 1600, ALPHA_RAD) + radiation_pulse(src, 1600, BETA_RAD) + radiation_pulse(src, 1600, GAMMA_RAD) + +/datum/milla_safe/visible_anomalous_particulate + +/datum/milla_safe/visible_anomalous_particulate/on_run(obj/effect/visible_anomalous_particulate/source) + var/turf/simulated/L = get_turf(source) + if(!istype(L)) + return + var/datum/gas_mixture/env = get_turf_air(L) + if(env.temperature() == 60 + T0C) + return + var/transfer_moles = 0.25 * env.total_moles() + + var/datum/gas_mixture/removed = env.remove(transfer_moles) + + if(!removed) + return + var/heat_capacity = removed.heat_capacity() + + if(heat_capacity) + if(removed.temperature() < 60 + T0C) + removed.set_temperature(min(removed.temperature() + 80000 / heat_capacity, 1000)) + else + removed.set_temperature(max(removed.temperature() - 80000 / heat_capacity, TCMB)) + env.merge(removed) + +/obj/effect/visible_anomalous_particulate/add_fingerprint(mob/living/M, ignoregloves) + return // No detective you can not scan the fucking influence to find out who touched it + +#undef BLUESPACE +#undef GRAV +#undef PYRO +#undef FLUX +#undef CRYO +#undef VORTEX + +/obj/effect/anomalous_particulate + name = "cloud of anomalous particulate" + icon_state = "hud_anom" + resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF + invisibility = INVISIBILITY_LEVEL_TWO + hud_possible = list(ANOMALOUS_HUD) + /// Whether we're currently being drained or not. + var/being_drained = FALSE + +/obj/effect/anomalous_particulate/Initialize(mapload) + . = ..() + GLOB.anomaly_smash_track.smashes += src + prepare_huds() + for(var/datum/atom_hud/data/anomalous/a_hud in GLOB.huds) + a_hud.add_to_hud(src) + do_hud_stuff() + +/obj/effect/anomalous_particulate/Destroy() + GLOB.anomaly_smash_track.smashes -= src + return ..() + +/obj/effect/anomalous_particulate/add_fingerprint(mob/living/M, ignoregloves) + return // No detective you can not scan the fucking influence to find out who touched it + +/obj/effect/anomalous_particulate/attack_by(obj/item/attacking, mob/user, params) + if(..()) + return FINISH_ATTACK + if(drain_with_device(user, attacking)) + return FINISH_ATTACK + return ..() + +/obj/effect/anomalous_particulate/proc/drain_with_device(mob/user, obj/item/ppp_processor/processor) + if(!istype(processor) || being_drained) + return FALSE + INVOKE_ASYNC(src, PROC_REF(drain_influence), user, processor) + return TRUE + +/** + * Begin to drain the influence, setting being_drained, + * registering an examine signal, and beginning a do_after. + * + * If successful, the influence is drained and deleted. + */ +/obj/effect/anomalous_particulate/proc/drain_influence(mob/living/user, obj/item/ppp_processor/processor) + if(processor.clouds_processed >= 3) + to_chat(user, "Your PPPProcessor is full!") + return + if(processor.clouds_processed == -1) + to_chat(user, "Your PPPProcessor has no canisters to collect particulate with!") + return + being_drained = TRUE + to_chat(user, "Your PPPProcessor begins to energize and collect [src]...") + + if(!do_after(user, 10 SECONDS, target = src, hidden = TRUE)) + being_drained = FALSE + return + + // Aaand now we delete it + after_drain(user, processor) + +/** + * Handle the effects of the drain. + */ +/obj/effect/anomalous_particulate/proc/after_drain(mob/living/user, obj/item/ppp_processor/processor) + if(user) + to_chat(user, "[src] begins to intensify!") + + new /obj/effect/visible_anomalous_particulate(drop_location()) + + processor.collect() + if(processor.clouds_processed >= 3) + to_chat(user, "[processor] has it's 3 canisters filled. Be sure to process the information!") + + GLOB.anomaly_smash_track.num_drained++ + qdel(src) + +#undef NUM_ANOM_PER_OBJ + + diff --git a/code/game/objects/items/theft_items.dm b/code/game/objects/items/theft_items.dm index 06c9849babd..920d2a8a46c 100644 --- a/code/game/objects/items/theft_items.dm +++ b/code/game/objects/items/theft_items.dm @@ -3,7 +3,7 @@ /obj/item/nuke_core name = "plutonium core" desc = "Extremely radioactive. Wear goggles." - icon = 'icons/obj/nuke_tools.dmi' + icon = 'icons/obj/theft_tools.dmi' icon_state = "plutonium_core" inhand_icon_state = "plutoniumcore" resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF @@ -39,7 +39,7 @@ /obj/item/nuke_core_container name = "nuke core container" desc = "A lead-lined secure container produced by the Syndicate for the express purpose of extracting the valuable radioactive cores of nuclear weapons." - icon = 'icons/obj/nuke_tools.dmi' + icon = 'icons/obj/theft_tools.dmi' icon_state = "core_container_empty" inhand_icon_state = "syringe_kit" resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //Don't want people trying to break it open with acid, then destroying the core. @@ -349,7 +349,7 @@ /obj/item/scalpel/supermatter name = "supermatter scalpel" desc = "A scalpel with a fragile tip of condensed hyper-noblium gas, searingly cold to the touch, that can safely shave a sliver off a supermatter crystal." - icon = 'icons/obj/nuke_tools.dmi' + icon = 'icons/obj/theft_tools.dmi' icon_state = "supermatter_scalpel" toolspeed = 0.5 damtype = BURN @@ -363,7 +363,7 @@ /obj/item/retractor/supermatter name = "supermatter extraction tongs" desc = "A pair of tongs made from condensed hyper-noblium gas, searingly cold to the touch, that can safely grip a supermatter sliver." - icon = 'icons/obj/nuke_tools.dmi' + icon = 'icons/obj/theft_tools.dmi' icon_state = "supermatter_tongs" lefthand_file = 'icons/mob/inhands/items_lefthand.dmi' righthand_file = 'icons/mob/inhands/items_righthand.dmi' @@ -440,3 +440,315 @@ ) QDEL_NULL(sliver) update_icon(UPDATE_ICON_STATE) + +// This device is used to analyze and trigger the particulate +/obj/item/ppp_processor + name = "\improper Prototype Portable Particulate Processor" + desc = "The Prototype Portable Particulate Processor, or PPPProcessor, for short, is a device designed to energize, collect, \ + and process anomalous particulate." + icon = 'icons/obj/theft_tools.dmi' + icon_state = "PPP_Processor_0" + worn_icon_state = "gun" + inhand_icon_state = "gun" + lefthand_file = 'icons/mob/inhands/guns_lefthand.dmi' + righthand_file = 'icons/mob/inhands/guns_righthand.dmi' + force = 10 + throwforce = 10 + attack_verb = list("processed", "perforated", "pounded", "pasted", "proded", "punished", "plowed into", "pommeled", "pummeled", "penetrated", "probed", "pulsed", "pulverized", "pillaged", "persecuted", "pasted", "peppered", "plundered", "put down", "pushed") + origin_tech = "programming=6;plasma=6;syndicate=3" + new_attack_chain = TRUE + /// How many clouds have been processed? + var/clouds_processed = 0 + /// Have we used it in hand to fully process the particulate and eject the canisters? + var/fully_processed_particulate = FALSE + /// Are we possibly processing particularly powerful packets? + var/presently_processing_particular_particultate = FALSE + /// The soundloop we use. + var/datum/looping_sound/kitchen/microwave/me_cro_wah_vey + +/obj/item/ppp_processor/Initialize(mapload) + . = ..() + me_cro_wah_vey = new /datum/looping_sound/kitchen/microwave(list(src), FALSE) + +/obj/item/ppp_processor/Destroy() + QDEL_NULL(me_cro_wah_vey) + return ..() + +/obj/item/ppp_processor/examine(mob/user) + . = ..() + if(fully_processed_particulate) + . += "[src] has processed the data you now possess. All you need to do is present it after this shift." + return + if(clouds_processed < 3) + . += "[src] has only [clouds_processed] out of 3 samples." + if(clouds_processed == 3) + . += "[src] has all 3 samples. Use it in hand to process the particulate." + + +/obj/item/ppp_processor/activate_self(mob/user) + if(..()) + return + if(fully_processed_particulate) + to_chat(user, "[src] has already processed and ejected the particulate canisters. Just make sure to escape with the processor!") + return + if(clouds_processed < 3) + to_chat(user, "[src] has only [clouds_processed] out of 3 samples. You still need to collect more!") + return + if(presently_processing_particular_particultate) + to_chat(user, "[src] is presently processing particularly powerful packets of your particular particulate. Wait for it to finish before proceeding.") + return + + to_chat(user, "[src] is presently processing the particulate. Please hold as processing finishes, and be aware it may eject collection canisters.") + if(me_cro_wah_vey) + me_cro_wah_vey.start() + addtimer(CALLBACK(src, PROC_REF(perfectly_processed), user), 15 SECONDS) + presently_processing_particular_particultate = TRUE + +/obj/item/ppp_processor/update_icon_state() + . = ..() + switch(clouds_processed) + if(-1) + icon_state = "PPP_Processor_-1" + if(0) + icon_state = "PPP_Processor_0" + if(1) + icon_state = "PPP_Processor_1" + if(2) + icon_state = "PPP_Processor_2" + if(3) + icon_state = "PPP_Processor_3" + +/obj/item/ppp_processor/proc/collect() + clouds_processed++ + update_icon() + +/obj/item/ppp_processor/proc/perfectly_processed(mob/user) + if(!QDELETED(user)) + to_chat(user, "[src] has perfectly processed the particulate. You may now use the canisters however you wish. Ensure the processor gets back to us.") + me_cro_wah_vey.stop() + presently_processing_particular_particultate = FALSE + clouds_processed = -1 + fully_processed_particulate = TRUE + update_icon() + var/list/potential_grenade_rewards = list() + potential_grenade_rewards += subtypesof(/obj/item/grenade/anomalous_canister) + potential_grenade_rewards += /obj/item/grenade/anomalous_canister + potential_grenade_rewards += /obj/effect/abstract/dummy_mini_spawner + potential_grenade_rewards -= /obj/item/grenade/anomalous_canister/mini + + var/turf/our_turf = get_turf(src) + our_turf.visible_message("Three containers are ejected out of [src].") + for(var/i in 1 to 3) + var/obj/item/new_toy = pick_n_take(potential_grenade_rewards) + new new_toy(get_turf(src)) + +/obj/item/clothing/glasses/hud/anomalous + name = "anomalous particulate scanner HUD" + desc = "A heads-up display that scans for anomalous particulate. Has a built in chameleon function, to help it blend in." + icon_state = "sunhudmed" + inhand_icon_state = "sunglasses" + hud_types = DATA_HUD_ANOMALOUS + + var/datum/action/item_action/chameleon_change/chameleon_action + +/obj/item/clothing/glasses/hud/anomalous/Initialize(mapload) + . = ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "HUD" + chameleon_action.chameleon_blacklist = list() + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/hud/anomalous/Destroy() + QDEL_NULL(chameleon_action) + return ..() + +/obj/item/clothing/glasses/hud/anomalous/emp_act(severity) + . = ..() + chameleon_action.emp_randomise() + +// The parent anomalous grenade type. Spawns a random anomaly. +/obj/item/grenade/anomalous_canister + name = "anomalous particulate canister" + desc = "This canister contains a cloud of charged anomalous particulate. It can be armed to create a temporary anomaly. \ + Be careful, with a glass exterior, it may shatter early when thrown." + icon_state = "anomalous_canister" + inhand_icon_state = "emp" + origin_tech = "magnets=6;combat=6;syndicate=2" + /// The type of anomaly the canister will spawn. + var/obj/effect/anomaly/anomaly_type + /// The lifetime of the anomaly. + var/anomaly_lifetime = 45 SECONDS + /// How many anomalies the grenade will spawn + var/number_of_anomalies = 1 + /// Have we primed? It contains a cloud of particulate matter. Breaking it is going to let the anomaly out. + var/has_primed = FALSE + /// Chance to be a varient that the glass will shatter when thrown earlier than expected. The smaller canisters are more metal spheres, and will not. + var/shatter_chance = 20 + +/obj/item/grenade/anomalous_canister/Initialize(mapload) + . = ..() + anomaly_type = pick(subtypesof(/obj/effect/anomaly)) + if(prob(shatter_chance)) + AddElement(/datum/element/shatters_when_thrown, /obj/effect/decal/cleanable/glass, 1, "shatter") + +/obj/item/grenade/anomalous_canister/Destroy() + prime() + return ..() + +/obj/item/grenade/anomalous_canister/prime() + if(has_primed) + return + has_primed = TRUE + var/turf/our_turf = get_turf(src) + our_turf.visible_message("[src] shatters open, the [(number_of_anomalies - 1) ? "clouds" : "cloud"] of particulate rapidly forming into something more!") + playsound(src, "shatter", 70, TRUE) + update_mob() + for(var/i in 1 to number_of_anomalies) + var/obj/effect/anomaly/A = new anomaly_type(our_turf, anomaly_lifetime, FALSE) + A.anomalous_canister_setup() + if(!QDELETED(src)) + qdel(src) + +/obj/item/grenade/anomalous_canister/dual_core + name = "dual clouded anomalous particulate canister" + icon_state = "anomalous_canister_dual" + number_of_anomalies = 2 + anomaly_lifetime = 30 SECONDS + +/obj/item/grenade/anomalous_canister/dual_core/examine(mob/user) + . = ..() + . += "Two smaller clouds of particulate are swirling around in this canister. It will likely form into two anomalies." + +/obj/item/grenade/anomalous_canister/condensed + name = "condensed anomalous particulate canister" + icon_state = "anomalous_canister_condensed" + anomaly_lifetime = 90 SECONDS + +/obj/item/grenade/anomalous_canister/condensed/examine(mob/user) + . = ..() + . += "A large cloud of resiliant particulate is floating in this canister. It will last longer than other anomalies." + +/obj/item/grenade/anomalous_canister/stabilized + name = "stabilized anomalous particulate canister" + desc = "The cloud of this particulate has stabilized enough for the computer to predict the anomaly it will condense into..." + icon_state = "anomalous_canister_stable" + +/obj/item/grenade/anomalous_canister/stabilized/examine(mob/user) + . = ..() + . += "The predicted result for the cloud to condense into is displayed as \an [anomaly_type::name]!" + +/obj/effect/abstract/dummy_mini_spawner + +/obj/effect/abstract/dummy_mini_spawner/Initialize(mapload) + . = ..() + var/turf/our_turf = get_turf(src) + our_turf.visible_message("One of the containers splits into 3 smaller capsules!") + for(var/i in 1 to 3) + new /obj/item/grenade/anomalous_canister/mini(our_turf) + return INITIALIZE_HINT_QDEL + +/obj/item/grenade/anomalous_canister/mini + name = "miniature anomalous particulate canister" + desc = "This small sphere contains a small cloud of particulate. Likely won't form an anomally, but should still have a noticable impact" + icon_state = "anomalous_canister_mini" + w_class = 1 + shatter_chance = 0 + +/obj/item/grenade/anomalous_canister/mini/prime() + if(has_primed) + return + has_primed = TRUE + update_mob() + var/turf/our_turf = get_turf(src) + our_turf.visible_message("[src] activates and...") + playsound(src, "shatter", 70, TRUE) + switch(anomaly_type) + if(/obj/effect/anomaly/bluespace) // Teleport and slow combat for 15 + if(!is_teleport_allowed(z)) + visible_message("[src]'s fragments begin rapidly vibrating and blink out of existence.") + qdel(src) + return + for(var/mob/living/L in range(7, our_turf)) + do_teleport(L, get_turf(L), 7, sound_in = 'sound/effects/phasein.ogg') + L.apply_status_effect(STATUS_EFFECT_BLUESPACESLOWDOWN) + if(/obj/effect/anomaly/flux) // shock + for(var/mob/living/L in view(7, our_turf)) + L.Beam(get_turf(src), icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5) // What? Why are we beaming from the mob to the turf? Turf to mob generates really odd results. + L.electrocute_act(20, "electrical blast", flags = SHOCK_NOGLOVES) + L.KnockDown(6 SECONDS) + if(/obj/effect/anomaly/grav) // repulse + var/list/thrownatoms = list() + var/atom/throwtarget + var/distfromcaster + for(var/turf/T in range(7, our_turf)) // Done this way so things don't get thrown all around hilariously. + for(var/atom/movable/AM in T) + thrownatoms += AM + + for(var/am in thrownatoms) + var/atom/movable/AM = am + if(AM == src || AM.anchored || AM.move_resist == INFINITY) + continue + + throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(AM, src))) + distfromcaster = get_dist(src, AM) + if(distfromcaster == 0) + if(isliving(AM)) + var/mob/living/M = AM + M.Weaken(8 SECONDS) + M.adjustBruteLoss(5) + to_chat(M, "You're slammed into the floor by an anomalous force!") + else + new /obj/effect/temp_visual/gravpush(get_turf(AM), get_dir(src, AM)) // created sparkles will disappear on their own + if(isliving(AM)) + var/mob/living/M = AM + M.Weaken(3 SECONDS) + to_chat(M, "You're thrown back by a anomalous force!") + spawn(0) + AM.throw_at(throwtarget, ((clamp((5 - (clamp(distfromcaster - 2, 0, distfromcaster))), 3, 5))), 1) // So stuff gets tossed around at the same time. + if(/obj/effect/anomaly/pyro) // burn + var/datum/gas_mixture/air = new() + air.set_temperature(1000) + air.set_toxins(20) + air.set_oxygen(20) + our_turf.blind_release_air(air) + for(var/mob/living/L in view(7, our_turf)) + L.adjust_fire_stacks(6) + L.IgniteMob() + if(/obj/effect/anomaly/cryo) // weaker anomaly grenade + for(var/turf/simulated/floor/T in view(4, our_turf)) + T.MakeSlippery(TURF_WET_ICE) + for(var/mob/living/carbon/C in T) + C.adjust_bodytemperature(-230) + C.apply_status_effect(/datum/status_effect/freon) + if(/obj/effect/anomaly/bhole) // big smoke that doesn't last as long as a smoke grenade + var/datum/effect_system/smoke_spread/smoke + smoke = new /datum/effect_system/smoke_spread/bad() + smoke.set_up(20, FALSE, src) + playsound(our_turf, 'sound/effects/smoke.ogg', 50, TRUE, -3) + smoke.start() + if(!QDELETED(src)) + qdel(src) + +/obj/item/paper/guides/antag/anomalous_particulate + name = "Particulate gathering instructions" + info = {"Instructions on your new PPPProcessor and HUD
+
+ First off, equip your glasses. You will need them to find the particulate. We heavily advise against losing them.
+
+ This will allow you to identify the uncharged anomalous particulate aboard the station. It can be anywhere, stalk out rooms and or use cameras to find it.\ + It will look like purple sparkles, with a yellow and red square targeting highlight.
+
+ Afterwords, approach the particulate with the PPPProcessor, and use it on the particulate. It will charge and capture a sample of it.
+
+ Warning: Charged particulate is dangerous. Wear the goggles and leave the area. Try not to get caught in doing so.
+
+ After collecting 3 diffrent unique samples (We will not accept a sample another agent has collected), use the scanner in hand to begin processing the particulate.
+
+ After a short processing period, processing will be complete and you can bring the processor back to us. Additionally, 3 collection cansiters should eject.
+
+ Feel free to use the canisters however you wish, they should be effective weapons, though do write down the results for us. +

+ We are not liable for any health conditions you may recive from scanning particulate or using the canisters. +"} + diff --git a/code/game/objects/items/weapons/storage/uplink_kits.dm b/code/game/objects/items/weapons/storage/uplink_kits.dm index aaef79aab15..44308c1a80d 100644 --- a/code/game/objects/items/weapons/storage/uplink_kits.dm +++ b/code/game/objects/items/weapons/storage/uplink_kits.dm @@ -518,6 +518,16 @@ new /obj/item/nuke_core_container/supermatter(src) new /obj/item/paper/guides/antag/supermatter_sliver(src) +/obj/item/storage/box/syndie_kit/anomalous_particulate + + desc = "It's just an ordinary box." + icon_state = "box" + +/obj/item/storage/box/syndie_kit/anomalous_particulate/populate_contents() + new /obj/item/ppp_processor(src) + new /obj/item/clothing/glasses/hud/anomalous(src) + new /obj/item/paper/guides/antag/anomalous_particulate(src) + /obj/item/storage/box/syndie_kit/revolver name = "\improper .357 revolver kit" diff --git a/code/modules/antagonists/antag_org/antag_org_syndicate.dm b/code/modules/antagonists/antag_org/antag_org_syndicate.dm index 15c3c8a77bf..67fb55e9251 100644 --- a/code/modules/antagonists/antag_org/antag_org_syndicate.dm +++ b/code/modules/antagonists/antag_org/antag_org_syndicate.dm @@ -56,7 +56,7 @@ Get the job done, and we'll be one step closer to ending Nanotrasen's slave empire." focus = 70 targeted_departments = list(DEPARTMENT_SCIENCE) - theft_targets = list(/datum/theft_objective/reactive, /datum/theft_objective/documents, /datum/theft_objective/hand_tele) + theft_targets = list(/datum/theft_objective/reactive, /datum/theft_objective/documents, /datum/theft_objective/hand_tele, /datum/theft_objective/anomalous_particulate) /datum/antag_org/syndicate/electra // Mostly target Engineering name = "Electra Dynamics" @@ -64,7 +64,7 @@ Nanotrasen's burgeoning monopoly must be stopped. We've transmitted you local points of failure, ensure they fail." focus = 70 targeted_departments = list(DEPARTMENT_ENGINEERING) - theft_targets = list(/datum/theft_objective/supermatter_sliver, /datum/theft_objective/plutonium_core, /datum/theft_objective/captains_modsuit, /datum/theft_objective/magboots) + theft_targets = list(/datum/theft_objective/supermatter_sliver, /datum/theft_objective/plutonium_core, /datum/theft_objective/captains_modsuit, /datum/theft_objective/magboots, /datum/theft_objective/anomalous_particulate) /datum/antag_org/syndicate/spiderclan // Targets one syndicate agent and one non-mindshielded crewmember. name = "Spider Clan" diff --git a/code/modules/events/anomalous_particulate_event.dm b/code/modules/events/anomalous_particulate_event.dm new file mode 100644 index 00000000000..87ce91e5206 --- /dev/null +++ b/code/modules/events/anomalous_particulate_event.dm @@ -0,0 +1,30 @@ +/datum/event/anomalous_particulate_event + +/datum/event/anomalous_particulate_event/start() + var/particulate_to_activate = 2 + if(GLOB.anomaly_smash_track.smashes) + for(var/obj/effect/anomalous_particulate/particulate_chosen in GLOB.anomaly_smash_track.smashes) + if(particulate_to_activate) + if(particulate_chosen.being_drained) + continue + new /obj/effect/visible_anomalous_particulate(particulate_chosen.drop_location()) + GLOB.anomaly_smash_track.num_drained++ + qdel(particulate_chosen) + particulate_to_activate-- + continue + break + var/location_sanity = 0 + while(particulate_to_activate && location_sanity < 100) + var/turf/chosen_location = get_safe_random_station_turf_equal_weight() + + // We don't want them close to each other - at least 1 tile of separation + var/list/nearby_things = range(1, chosen_location) + var/obj/effect/anomalous_particulate/what_if_i_have_one = locate() in nearby_things + var/obj/effect/visible_anomalous_particulate/what_if_i_had_one_but_its_used = locate() in nearby_things + if(what_if_i_have_one || what_if_i_had_one_but_its_used) + location_sanity++ + continue + + new /obj/effect/visible_anomalous_particulate(chosen_location) + particulate_to_activate-- + diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm index b4cd5163c11..f46c37ff75e 100644 --- a/code/modules/events/event_container.dm +++ b/code/modules/events/event_container.dm @@ -192,7 +192,8 @@ GLOBAL_LIST_EMPTY(event_last_fired) new /datum/event_meta(EVENT_LEVEL_MODERATE, "Disease Outbreak", /datum/event/disease_outbreak, 50, list(ASSIGNMENT_MEDICAL = 30), TRUE), new /datum/event_meta(EVENT_LEVEL_MODERATE, "Door Runtime", /datum/event/door_runtime, 50, list(ASSIGNMENT_ENGINEER = 25, ASSIGNMENT_AI = 150), TRUE), new /datum/event_meta(EVENT_LEVEL_MODERATE, "Tourist Arrivals", /datum/event/tourist_arrivals, 100, list(ASSIGNMENT_SECURITY = 15), TRUE), - new /datum/event_meta(EVENT_LEVEL_MODERATE, "Shuttle Loan", /datum/event/shuttle_loan, 100, list(ASSIGNMENT_CARGO = 15, ASSIGNMENT_SECURITY = 10), is_one_shot = TRUE) + new /datum/event_meta(EVENT_LEVEL_MODERATE, "Shuttle Loan", /datum/event/shuttle_loan, 100, list(ASSIGNMENT_CARGO = 15, ASSIGNMENT_SECURITY = 10), is_one_shot = TRUE), + new /datum/event_meta(EVENT_LEVEL_MODERATE, "Anomalous Particulate", /datum/event/anomalous_particulate_event, 250, is_one_shot = TRUE), ) /datum/event_container/major diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index 75919d6485b..2781bd7d17d 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -359,12 +359,15 @@ var/datum/atom_hud/data/human/medadv = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] var/datum/atom_hud/data/human/secbasic = GLOB.huds[DATA_HUD_SECURITY_BASIC] var/datum/atom_hud/data/human/secadv = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] + var/datum/atom_hud/data/anomalous = GLOB.huds[DATA_HUD_ANOMALOUS] if((H in medbasic.hudusers) || (H in medadv.hudusers)) have_hudtypes += EXAMINE_HUD_MEDICAL_READ if(H in secadv.hudusers) have_hudtypes += EXAMINE_HUD_SECURITY_READ if(H in secbasic.hudusers) have_hudtypes += EXAMINE_HUD_SKILLS + if(H in anomalous.hudusers) + have_hudtypes += ANOMALOUS_HUD var/user_accesses = M.get_access() var/secwrite = has_access(null, list(ACCESS_SECURITY, ACCESS_FORENSICS_LOCKERS), user_accesses) // same as obj/machinery/computer/secure_data/req_one_access diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm index 291b813a4c1..45241004609 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/ancient_robot.dm @@ -468,7 +468,7 @@ Difficulty: Hard var/obj/effect/anomaly/bluespace/A = new(spot, time_to_use, FALSE) A.mass_teleporting = FALSE if(GRAV) - var/obj/effect/anomaly/grav/A = new(spot, time_to_use, FALSE, FALSE) + var/obj/effect/anomaly/grav/A = new(spot, time_to_use, FALSE) A.knockdown = TRUE if(PYRO) var/obj/effect/anomaly/pyro/A = new(spot, time_to_use, FALSE) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index dfc14c00061..209aeefc97a 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -76,6 +76,7 @@ IMPTRACK_HUD = 'icons/mob/hud/sechud.dmi', PRESSURE_HUD = 'icons/effects/effects.dmi', MALF_AI_HUD = 'icons/mob/hud/malfhud.dmi', + ANOMALOUS_HUD = 'icons/effects/effects.dmi', ) for(var/hud in hud_possible) diff --git a/code/modules/power/engines/supermatter/supermatter.dm b/code/modules/power/engines/supermatter/supermatter.dm index ac0eedf8375..27edef2575f 100644 --- a/code/modules/power/engines/supermatter/supermatter.dm +++ b/code/modules/power/engines/supermatter/supermatter.dm @@ -1070,7 +1070,7 @@ var/obj/effect/anomaly/flux/A = new(L, 30 SECONDS, FALSE) A.explosive = FALSE if(GRAVITATIONAL_ANOMALY) - new /obj/effect/anomaly/grav(L, 25 SECONDS, FALSE, FALSE) + new /obj/effect/anomaly/grav(L, 25 SECONDS, FALSE) if(BLUESPACE_ANOMALY) new /obj/effect/anomaly/bluespace(L, 24 SECONDS, FALSE, FALSE, TRUE) diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi index 3218df8a135..b2f55cbbbee 100644 Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index 725c60a9c2d..53ed7f1358c 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/obj/grenade.dmi b/icons/obj/grenade.dmi index 51ea2b94d44..6cb4ac7a244 100644 Binary files a/icons/obj/grenade.dmi and b/icons/obj/grenade.dmi differ diff --git a/icons/obj/nuke_tools.dmi b/icons/obj/nuke_tools.dmi deleted file mode 100644 index e1d2e4e05ab..00000000000 Binary files a/icons/obj/nuke_tools.dmi and /dev/null differ diff --git a/icons/obj/theft_tools.dmi b/icons/obj/theft_tools.dmi new file mode 100644 index 00000000000..2b4feb40c84 Binary files /dev/null and b/icons/obj/theft_tools.dmi differ diff --git a/paradise.dme b/paradise.dme index 7fab737b109..38e06cc0843 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1152,6 +1152,7 @@ #include "code\game\objects\structures.dm" #include "code\game\objects\effects\alien_acid.dm" #include "code\game\objects\effects\anomalies.dm" +#include "code\game\objects\effects\anomalous_particulate.dm" #include "code\game\objects\effects\effects.dm" #include "code\game\objects\effects\forcefields.dm" #include "code\game\objects\effects\gibs.dm" @@ -2090,6 +2091,7 @@ #include "code\modules\error_handler\error_viewer.dm" #include "code\modules\events\abductor_event.dm" #include "code\modules\events\alien_infestation.dm" +#include "code\modules\events\anomalous_particulate_event.dm" #include "code\modules\events\anomaly_bluespace.dm" #include "code\modules\events\anomaly_cryo.dm" #include "code\modules\events\anomaly_event.dm"